[
  {
    "path": ".all-contributorsrc",
    "content": "{\n  \"files\": [\n    \"README.md\"\n  ],\n  \"imageSize\": 50,\n  \"commit\": false,\n  \"contributors\": [\n    {\n      \"login\": \"Fullstop000\",\n      \"name\": \"Fullstop000\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/12471960?v=4\",\n      \"profile\": \"https://github.com/Fullstop000\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"rleungx\",\n      \"name\": \"Ryan Leung\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/35896542?v=4\",\n      \"profile\": \"http://rleungx.github.io\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"zzh-wisdom\",\n      \"name\": \"zzh-wisdom\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/52516344?v=4\",\n      \"profile\": \"https://github.com/zzh-wisdom\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"STRRL\",\n      \"name\": \"STRRL\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/20221408?v=4\",\n      \"profile\": \"https://github.com/STRRL\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"SSebo\",\n      \"name\": \"SSebo\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/5784607?v=4\",\n      \"profile\": \"https://github.com/SSebo\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Yisaer\",\n      \"name\": \"Song Gao\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/13427348?v=4\",\n      \"profile\": \"https://yisaer.github.io/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"gauss1314\",\n      \"name\": \"gauss1314\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/3862518?v=4\",\n      \"profile\": \"https://github.com/gauss1314\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"leiysky\",\n      \"name\": \"lei yu\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/22445410?v=4\",\n      \"profile\": \"https://github.com/leiysky\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"niedhui\",\n      \"name\": \"niedhui\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/66329?v=4\",\n      \"profile\": \"https://github.com/niedhui\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"weihanglo\",\n      \"name\": \"Weihang Lo\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/14314532?v=4\",\n      \"profile\": \"https://weihanglo.tw/\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"yikeke\",\n      \"name\": \"Keke Yi\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/40977455?v=4\",\n      \"profile\": \"https://github.com/yikeke\",\n      \"contributions\": [\n        \"content\"\n      ]\n    },\n    {\n      \"login\": \"qxhy123\",\n      \"name\": \"Michael.Yang\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/518969?v=4\",\n      \"profile\": \"https://github.com/qxhy123\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"Rustin-Liu\",\n      \"name\": \"二手掉包工程师\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/29879298?v=4\",\n      \"profile\": \"http://www.rustin.cn\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"ericsyh\",\n      \"name\": \"Eric Shen\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/10498732?v=4\",\n      \"profile\": \"https://github.com/ericsyh\",\n      \"contributions\": [\n        \"code\"\n      ]\n    }\n  ],\n  \"contributorsPerLine\": 10,\n  \"contributorsSortAlphabetically\": true,\n  \"contributorTemplate\": \"<a href=\\\"<%= contributor.profile %>\\\"><img src=\\\"<%= contributor.avatar_url %>\\\" width=\\\"<%= options.imageSize %>px;\\\" alt=\\\"\\\"/></a>\",\n  \"projectName\": \"tidb-dashboard\",\n  \"projectOwner\": \"pingcap\",\n  \"repoType\": \"github\",\n  \"repoHost\": \"https://github.com\",\n  \"skipCi\": true,\n  \"commitConvention\": \"none\"\n}\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: \"\\U0001F41B Bug Report\"\nabout: Something isn't working as expected\ntitle: \"\"\nlabels: type/bug\nassignees: \"\"\n---\n\n## Bug Report\n\nPlease answer these questions before submitting your issue. Thanks!\n\n**What did you do?**\n\n<!-- If possible, provide a recipe for reproducing the error.  -->\n\n**What did you expect to see?**\n\n<!-- Please fill. -->\n\n**What did you see instead?**\n\n<!-- Please fill. -->\n\n**What version of TiDB Dashboard are you using (`./tidb-dashboard --version`)?**\n\n<!-- Please fill. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.md",
    "content": "---\nname: \"\\U0001F680 Feature Request\"\nabout: I have a suggestion\ntitle: \"\"\nlabels: status/discussion, type/feature-request\nassignees: \"\"\n---\n\n## Feature Request\n\n**Is your feature request related to a problem? Please describe:**\n\n<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->\n\n**Describe the feature you'd like:**\n\n<!-- A clear and concise description of what you want to happen. -->\n\n**Describe alternatives you've considered:**\n\n<!-- A clear and concise description of any alternative solutions or features you've considered. -->\n\n**Teachability, Documentation, Adoption, Migration Strategy:**\n\n<!-- If you can, explain some scenarios how users might use this, situations it would be helpful in. Any API designs, mockups, or diagrams are also helpful. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "content": "---\nname: \"\\U0001F914 Question\"\nabout: I have a question\nlabel: \"type/question\"\n---\n\n## Question\n\n<!--\n\nThanks for using TiDB Dashboard! Before asking a question, please take a look in the following places:\n\n- GitHub issues\n  https://github.com/pingcap/tidb-dashboard/issues?q=is%3Aissue\n- Documentation (English)\n  https://docs.pingcap.com/tidb/stable/dashboard-intro\n- Documentation (Chinese)\n  https://docs.pingcap.com/zh/tidb/stable/dashboard-intro\n- AskTUG forum (Chinese)\n  https://asktug.com/\n\nYou might also get a faster response in Slack (English / Chinese):\n  https://slack.tidb.io/invite?team=tidb-community&channel=sig-diagnosis&ref=github_issue_create\n\n-->\n"
  },
  {
    "path": ".github/autolabeler.yml",
    "content": "area/frontend:\n  - /ui\narea/backend:\n  - *.go\n"
  },
  {
    "path": ".github/codecov.yml",
    "content": "comment:\n  layout: \"diff,flags,footer\"\n"
  },
  {
    "path": ".github/stale.yml",
    "content": "daysUntilStale: 60\ndaysUntilClose: 7\nexemptLabels:\n  - type/feature-request\n  - type/pinned\nstaleLabel: type/stale\n"
  },
  {
    "path": ".github/workflows/build.yaml",
    "content": "name: Build\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n      - release-*\n\njobs:\n  backend:\n    name: Backend\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n      - uses: actions/setup-go@v6\n        with:\n          go-version-file: \"go.mod\"\n      - name: Load go module cache\n        uses: actions/cache@v5\n        with:\n          path: |\n            ~/.cache/go-build\n            ~/go/pkg/mod\n          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-go-\n      - name: Load golangci-lint cache\n        uses: actions/cache@v5\n        with:\n          path: ~/.cache/golangci-lint\n          key: ${{ runner.os }}-golint\n          restore-keys: |\n            ${{ runner.os }}-golint\n      - name: Lint and build\n        run: |\n          make dev\n      - name: Check uncommitted lint changes\n        run: |\n          git diff --exit-code\n\n  frontend:\n    name: Frontend\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n      # https://pnpm.io/continuous-integration#github-actions\n      - name: Setup PNPM\n        uses: pnpm/action-setup@v5\n        with:\n          version: 8\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: \"22\"\n          cache: \"pnpm\"\n          cache-dependency-path: \"ui/pnpm-lock.yaml\"\n      - uses: actions/setup-go@v6\n        with:\n          go-version-file: \"go.mod\"\n      - name: Load go module cache\n        uses: actions/cache@v5\n        with:\n          path: |\n            ~/.cache/go-build\n            ~/go/pkg/mod\n          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-go-\n      - name: Install ui packages\n        run: |\n          make ui_deps\n      - name: Check format\n        run: |\n          pnpm fmt-check || (echo \"::error ::Please format your code by using 'pnpm fmt-fix'\"; exit 1)\n        working-directory: ui\n      - name: Build UI\n        run: |\n          make ui\n        env:\n          NO_MINIMIZE: true\n          CI: true\n"
  },
  {
    "path": ".github/workflows/manual-create-pd-pr.yaml",
    "content": "name: Create PD PR Manually\n\non:\n  workflow_dispatch:\n    inputs:\n      release_version:\n        description: 'Release version, e.g. v7.6.0-f7bbcdcf'\n        required: true\n      pd_branchs:\n        description: 'PD branch, e.g. [\"master\", \"release-7.6\"]'\n        default: '[\"master\"]'\n        required: true\n\njobs:\n  pd_pr:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        # https://stackoverflow.com/questions/69781005/combine-dynamic-github-workflow-matrix-with-input-values-and-predefined-values\n        branch: ${{ fromJson(github.event.inputs.pd_branchs) }}\n    name: Create PD PR - ${{ matrix.branch }}\n    steps:\n      - name: Check out PD code base\n        uses: actions/checkout@v4\n        with:\n          repository: tikv/pd\n          ref: ${{ matrix.branch }}\n      - uses: actions/setup-go@v3\n        with:\n          go-version: '1.25.7'\n      - name: Load go module cache\n        uses: actions/cache@v3\n        with:\n          path: |\n            ~/.cache/go-build\n            ~/go/pkg/mod\n          key: ${{ runner.os }}-go-pd-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-go-pd-\n      - name: Update TiDB Dashboard in PD code base\n        run: |\n          scripts/update-dashboard.sh ${{ github.event.inputs.release_version }}\n      - name: Commit PD code base change\n        id: git_commit\n        run: |\n          git diff\n          git config user.name \"baurine\"\n          git config user.email \"2008.hbl@gmail.com\"\n          git add .\n          if git status | grep -q \"Changes to be committed\"\n          then\n            git commit --signoff --message \"chore(dashboard): update TiDB Dashboard to ${{ github.event.inputs.release_version }}\"\n            echo \"::set-output name=committed::1\"\n          else\n            echo \"No changes detected, skipped\"\n          fi\n      - name: Set build ID\n        id: build_id\n        run: echo \"::set-output name=id::$(date +%s)\"\n      - name: Create PR based on PD code base\n        id: cpr\n        uses: peter-evans/create-pull-request@v3\n        if: steps.git_commit.outputs.committed == 1\n        with:\n          push-to-fork: baurine/pd\n          token: ${{ secrets.PAT_TO_PUSH_PD_FORK }}\n          branch: update-tidb-dashboard/${{ matrix.branch }}-${{ github.event.inputs.release_version }}-${{ steps.build_id.outputs.id }}\n          title: 'chore(dashboard): update TiDB Dashboard to ${{ github.event.inputs.release_version }} [${{ matrix.branch }}]'\n          body: |\n            ### What problem does this PR solve?\n\n            Issue Number: ref #4257\n\n            Update TiDB Dashboard to [${{ github.event.inputs.release_version }}](https://github.com/pingcap/tidb-dashboard/releases/tag/${{ github.event.inputs.release_version }}).\n\n            ### Release note\n\n            ```release-note\n            None\n            ```\n      - name: Check outputs\n        if: steps.git_commit.outputs.committed == 1\n        run: |\n          echo \"Pull Request Number - ${{ steps.cpr.outputs.pull-request-number }}\"\n          echo \"Pull Request URL - ${{ steps.cpr.outputs.pull-request-url }}\"\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - \"v*\"\n      - \"!v*-alpha\"\n\njobs:\n  release:\n    name: Release\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      # https://pnpm.io/continuous-integration#github-actions\n      - name: Setup PNPM\n        uses: pnpm/action-setup@v2\n        with:\n          version: 8\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: \"22\"\n          cache: \"pnpm\"\n          cache-dependency-path: \"ui/pnpm-lock.yaml\"\n      - uses: actions/setup-go@v3\n        with:\n          go-version: \"1.25.7\"\n      - name: Load go module cache\n        uses: actions/cache@v3\n        with:\n          path: |\n            ~/.cache/go-build\n            ~/go/pkg/mod\n          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-go-\n\n      - name: Build UI\n        env:\n          REACT_APP_MIXPANEL_TOKEN: ${{ secrets.REACT_APP_MIXPANEL_TOKEN }}\n        run: |\n          make ui\n      - name: Pack UI assets for release\n        working-directory: ui/packages/tidb-dashboard-for-op/dist\n        run: |\n          zip -r ../static-assets.zip .\n\n      # TODO: generate changelog\n      # - name: Generate Changelog\n      #   id: build_changelog\n      #   uses: mikepenz/release-changelog-builder-action@v4.1.0\n      - name: Create release\n        id: create_release\n        uses: fleskesvor/create-release@feature/support-target-commitish\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: ${{ github.ref }}\n          release_name: Internal Version ${{ github.ref }}\n          draft: false\n          prerelease: false\n          # body: ${{steps.build_changelog.outputs.changelog}}\n\n      - name: Upload UI assets\n        uses: actions/upload-release-asset@v1.0.1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ./ui/packages/tidb-dashboard-for-op/static-assets.zip\n          asset_name: static-assets.zip\n          asset_content_type: application/zip\n      - name: Generate embedded UI assets\n        run: |\n          NO_ASSET_BUILD_TAG=1 scripts/embed_ui_assets.sh\n          cp pkg/uiserver/embedded_assets_handler.go embedded_assets_handler.go\n      - name: Pack embedded assets for release\n        run: |\n          zip -r embedded-assets-golang.zip ./embedded_assets_handler.go\n      - name: Upload embedded UI assets\n        uses: actions/upload-release-asset@v1.0.1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          upload_url: ${{ steps.create_release.outputs.upload_url }}\n          asset_path: ./embedded-assets-golang.zip\n          asset_name: embedded-assets-golang.zip\n          asset_content_type: application/zip\n"
  },
  {
    "path": ".github/workflows/test-docker-image.yaml",
    "content": "name: Test Docker Image\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\nconcurrency:\n  group: ${{ github.ref }}-${{ github.workflow }}\n  cancel-in-progress: true\njobs:\n  test-build-docker-image:\n    runs-on: ${{ matrix.platform == 'linux/arm64' && 'ubuntu-24.04-arm' || 'ubuntu-24.04' }}\n    strategy:\n      matrix:\n        platform: [linux/amd64, linux/arm64]\n        remote_dockerfile: # download from: https://github.com/PingCAP-QE/artifacts/tree/main/dockerfiles/cd/builders/tidb-dashboard\n          - Dockerfile # builder is rocky linux 8\n          - centos7/Dockerfile # builder is centos 7\n      fail-fast: true\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Read VERSION file\n        id: getversion\n        run: echo \"::set-output name=version::$(git describe --tags --dirty --always)\"\n\n      - name: Download Dockerfile\n        run: |\n          wget \"https://github.com/PingCAP-QE/artifacts/raw/refs/heads/main/dockerfiles/cd/builders/tidb-dashboard/${{ matrix.remote_dockerfile }}\" -O Dockerfile\n      - name: Build\n        uses: docker/build-push-action@v6\n        with:\n          push: false\n          context: .\n          file: Dockerfile\n          platforms: ${{ matrix.platform }}\n          tags: pingcap/tidb-dashboard:${{ steps.getversion.outputs.version }}\n"
  },
  {
    "path": ".github/workflows/test.yaml",
    "content": "name: Test\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n    branches:\n      - master\n      - release-*\n\njobs:\n  backend_ut:\n    name: Backend - Unit\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - uses: actions/setup-go@v3\n        with:\n          go-version: \"1.25.7\"\n      - name: Load go module cache\n        uses: actions/cache@v3\n        with:\n          path: |\n            ~/.cache/go-build\n            ~/go/pkg/mod\n          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-go-\n      - name: Run unit test\n        run: |\n          make unit_test\n      # - name: Upload coverage to Codecov\n      #   uses: codecov/codecov-action@v2\n      #   with:\n      #     files: ./coverage/unit_test.txt\n      #     fail_ci_if_error: true\n      #     flags: backend_ut\n      #     verbose: true\n\n  backend_integration:\n    runs-on: ubuntu-latest\n    timeout-minutes: 20\n    strategy:\n      matrix:\n        # tidb_version: [nightly, ^6.0, ^5.4, ^5.0]\n        tidb_version: [nightly, ^6.0]\n    name: Backend - Integration - TiDB@${{ matrix.tidb_version }}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n      - uses: actions/setup-go@v3\n        with:\n          go-version: \"1.25.7\"\n      - name: Load go module cache\n        uses: actions/cache@v3\n        with:\n          path: |\n            ~/.cache/go-build\n            ~/go/pkg/mod\n          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-go-\n      - name: Load TiUP cache\n        uses: actions/cache@v3\n        with:\n          path: ~/.tiup/components\n          key: ${{ runner.os }}-tiup-${{ matrix.tidb_version }}\n      - name: Run integration test\n        run: |\n          make integration_test TIDB_VERSION=${{ matrix.tidb_version }}\n      # - name: Upload coverage to Codecov\n      #   uses: codecov/codecov-action@v2\n      #   with:\n      #     files: ./coverage/integration_${{ matrix.tidb_version }}.txt\n      #     fail_ci_if_error: true\n      #     flags: backend_integration\n      #     verbose: true\n\n  # e2e_test:\n  #   runs-on: ubuntu-latest\n  #   timeout-minutes: 30\n  #   strategy:\n  #     fail-fast: false\n  #     matrix:\n  #       # test latest features and compatibility of lower version\n  #       include:\n  #         - feature_version: 6.0.0 # You must ensure feature_version and tidb_version is matching!\n  #           tidb_version: nightly\n  #           without_ngm: true\n  #         - feature_version: 6.0.0\n  #           tidb_version: ^6.0\n  #           without_ngm: true\n  #         - feature_version: 5.4.0\n  #           tidb_version: ^5.4\n  #           without_ngm: true\n  #         - feature_version: 5.0.0\n  #           tidb_version: ^5.0\n  #           without_ngm: true\n  #   name: E2E - TiDB@${{ matrix.tidb_version }}${{ !matrix.without_ngm && '+ngm' || '' }}\n  #   steps:\n  #     - name: Checkout code\n  #       uses: actions/checkout@v4\n  #     # https://pnpm.io/continuous-integration#github-actions\n  #     - name: Setup PNPM\n  #       uses: pnpm/action-setup@v2.2.2\n  #       with:\n  #         version: 7\n  #     - name: Setup Node.js\n  #       uses: actions/setup-node@v4\n  #       with:\n  #         node-version: \"16\"\n  #         cache: \"pnpm\"\n  #         cache-dependency-path: \"ui/pnpm-lock.yaml\"\n  #     - name: Load cypress cache\n  #       uses: actions/cache@v3\n  #       id: cypress-cache\n  #       with:\n  #         path: ~/.cache/Cypress\n  #         key: ${{ runner.os }}-cypress-${{ hashFiles('**/pnpm-lock.yaml') }}\n  #         restore-keys: |\n  #           ${{ runner.os }}-cypress-\n  #     - uses: actions/setup-go@v3\n  #       with:\n  #         go-version: \"1.21\"\n  #     - name: Load go module cache\n  #       uses: actions/cache@v3\n  #       with:\n  #         path: |\n  #           ~/.cache/go-build\n  #           ~/go/pkg/mod\n  #         key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n  #         restore-keys: |\n  #           ${{ runner.os }}-go-\n  #     - name: Load TiUP cache\n  #       uses: actions/cache@v3\n  #       with:\n  #         path: ~/.tiup/components\n  #         key: ${{ runner.os }}-tiup-${{ matrix.tidb_version }}\n  #     - name: Install and run TiUP in the background\n  #       run: |\n  #         chmod u+x scripts/start_tiup.sh\n  #         scripts/start_tiup.sh ${{ matrix.tidb_version }} ${{ matrix.without_ngm }}\n  #     - name: Build UI\n  #       run: |\n  #         make ui\n  #       env:\n  #         NO_MINIMIZE: true\n  #         CI: true\n  #         E2E_TEST: true\n  #     - name: Wait TiUP Playground\n  #       run: |\n  #         chmod u+x scripts/wait_tiup_playground.sh\n  #         scripts/wait_tiup_playground.sh 15 20\n  #     - name: Debug TiUP\n  #       run: |\n  #         source /home/runner/.profile\n  #         tiup --version\n  #         ls /home/runner/.tiup/components/playground/\n  #         DATA_PATH=$(ls /home/runner/.tiup/data/)\n  #         echo $DATA_PATH\n  #         echo \"==== TiDB Log ====\"\n  #         head -n 3 /home/runner/.tiup/data/$DATA_PATH/tidb-0/tidb.log\n  #         echo \"==== TiKV Log ====\"\n  #         head -n 3 /home/runner/.tiup/data/$DATA_PATH/tikv-0/tikv.log\n  #         echo \"==== PD Log ====\"\n  #         head -n 3 /home/runner/.tiup/data/$DATA_PATH/pd-0/pd.log\n  #     - name: Build and run backend in the background\n  #       run: |\n  #         make\n  #         make run &\n  #       env:\n  #         UI: 1\n  #         FEATURE_VERSION: ${{ matrix.feature_version }}\n  #     - name: Run E2E Features Test\n  #       run: make e2e_test\n  #       env:\n  #         SERVER_URL: http://127.0.0.1:12333/dashboard/\n  #         CI: true\n  #         FEATURE_VERSION: ${{ matrix.feature_version }}\n  #         TIDB_VERSION: ${{ matrix.tidb_version }}\n  #         CYPRESS_ALLOW_SCREENSHOT: true\n  #         WITHOUT_NGM: ${{ matrix.without_ngm }}\n  #     - name: Upload coverage to Codecov\n  #       uses: codecov/codecov-action@v2\n  #       with:\n  #         files: ./ui/packages/tidb-dashboard-for-op/.nyc_output/out.json\n  #         fail_ci_if_error: false\n  #         flags: e2e_test\n  #         verbose: true\n"
  },
  {
    "path": ".github/workflows/upload-e2e-snapshots.yaml",
    "content": "# Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\nname: Upload E2E Snapshots\n\non:\n  workflow_dispatch:\n    inputs:\n      ref:\n        description: \"The branch, tag or SHA to create snapshots\"\n        required: true\n      spec:\n        description: \"Specify the spec files to run, example: `topsql/topsql.spec.ts`\"\n        required: true\n\njobs:\n  e2e_test_snapshots:\n    name: Take E2E Test Snapshots\n    runs-on: ubuntu-latest\n    timeout-minutes: 30\n    strategy:\n      fail-fast: false\n      matrix:\n        # test latest features and compatibility of lower version\n        include:\n          - feature_version: 6.0.0\n            tidb_version: latest\n          - feature_version: 5.4.0\n            tidb_version: v5.4.0\n          - feature_version: 5.0.0\n            tidb_version: v5.0.0\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          ref: ${{ github.event.inputs.ref }}\n      # https://pnpm.io/continuous-integration#github-actions\n      - name: Setup PNPM\n        uses: pnpm/action-setup@v2\n        with:\n          version: 8\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: \"22\"\n          cache: \"pnpm\"\n          cache-dependency-path: \"ui/pnpm-lock.yaml\"\n      - uses: actions/setup-go@v3\n        with:\n          go-version: \"1.25.7\"\n      - name: Load go module cache\n        uses: actions/cache@v3\n        with:\n          path: |\n            ~/.cache/go-build\n            ~/go/pkg/mod\n          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-go-\n      - name: Load TiUP cache\n        uses: actions/cache@v3\n        with:\n          path: ~/.tiup/components\n          key: ${{ runner.os }}-tiup\n          restore-keys: |\n            ${{ runner.os }}-tiup\n      - name: Install and run TiUP in the background\n        run: |\n          chmod u+x scripts/start_tiup.sh\n          scripts/start_tiup.sh ${{ matrix.tidb_version }} false\n      - name: Build UI\n        run: |\n          make ui\n        env:\n          NO_MINIMIZE: true\n          CI: true\n      - name: Wait TiUP Playground\n        run: |\n          chmod u+x scripts/wait_tiup_playground.sh\n          scripts/wait_tiup_playground.sh 15 20\n      - name: Debug TiUP\n        run: |\n          source /home/runner/.profile\n          tiup --version\n          ls /home/runner/.tiup/components/playground/\n          DATA_PATH=$(ls /home/runner/.tiup/data/)\n          echo $DATA_PATH\n          echo \"==== TiDB Log ====\"\n          head -n 3 /home/runner/.tiup/data/$DATA_PATH/tidb-0/tidb.log\n          echo \"==== TiKV Log ====\"\n          head -n 3 /home/runner/.tiup/data/$DATA_PATH/tikv-0/tikv.log\n          echo \"==== PD Log ====\"\n          head -n 3 /home/runner/.tiup/data/$DATA_PATH/pd-0/pd.log\n      - name: Build and run backend in the background\n        run: |\n          make\n          make run &\n        env:\n          UI: 1\n          FEATURE_VERSION: ${{ matrix.feature_version }}\n      - name: Delete Previous Snapshots\n        run: rm -rf ${{ github.workspace }}/ui/packages/tidb-dashboard-for-op/cypress/snapshots\n      - name: Run E2E Features Test\n        run: make e2e_test_specify\n        env:\n          SERVER_URL: http://127.0.0.1:12333/dashboard/\n          CI: true\n          FEATURE_VERSION: ${{ matrix.feature_version }}\n          TIDB_VERSION: ${{ matrix.tidb_version }}\n          CYPRESS_ALLOW_SCREENSHOT: true\n          E2E_SPEC: cypress/integration/${{ github.event.inputs.spec }}\n      - name: Archive Test Video\n        if: always()\n        uses: actions/upload-artifact@v2\n        with:\n          name: e2e-video-${{ matrix.feature_version }}\n          path: ${{ github.workspace }}/ui/packages/tidb-dashboard-for-op/cypress/videos/**/*\n      - name: Upload snapshots artifact\n        uses: actions/upload-artifact@v2\n        if: always()\n        with:\n          name: e2e-snapshots-${{ matrix.feature_version }}\n          path: ${{ github.workspace }}/ui/packages/tidb-dashboard-for-op/cypress/snapshots/**/*\n"
  },
  {
    "path": ".gitignore",
    "content": ".env\n/bin\n\n# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\ncoverage/\n\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm\n# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839\n\n.idea\n\n# CMake\ncmake-build-*/\n\n# Mongo Explorer plugin\n.idea/**/mongoSettings.xml\n\n# File-based project format\n*.iws\n\n# IntelliJ\nout/\n\n# VSCode\n.vscode/\n\n# mpeltonen/sbt-idea plugin\n.idea_modules/\n\n# JIRA plugin\natlassian-ide-plugin.xml\n\n# Cursive Clojure plugin\n.idea/replstate.xml\n\n# Crashlytics plugin (for Android Studio and IntelliJ)\ncom_crashlytics_export_strings.xml\ncrashlytics.properties\ncrashlytics-build.properties\nfabric.properties\n\n# Editor-based Rest Client\n.idea/httpRequests\n\n# Android studio 3.1+ serialized cache file\n.idea/caches/build_file_checksums.ser\n\n# ui\n.pnpm-debug.log\n"
  },
  {
    "path": ".golangci.yml",
    "content": "version: \"2\"\nlinters:\n  enable:\n    - asciicheck\n    - dogsled\n    - durationcheck\n    - errorlint\n    - exhaustive\n    - godot\n    - goheader\n    - gosec\n    - importas\n    - misspell\n    - nestif\n    - prealloc\n    - predeclared\n    - revive\n    - unconvert\n    - whitespace\n  settings:\n    exhaustive:\n      default-signifies-exhaustive: true\n    godot:\n      exclude:\n        - ^\\s*@\n    goheader:\n      template: Copyright {{ YEAR }} PingCAP, Inc. Licensed under Apache-2.0.\n    revive:\n      rules:\n        - name: var-naming\n          severity: warning\n          disabled: false\n          exclude: [\"\"]\n          arguments:\n            - [ \"utils\" ] # AllowList\n            - [ \"VM\" ] # DenyList\n            - - skip-package-name-checks: true\n    gosec:\n      excludes:\n        - G115 # Type conversion which leads to integer overflow\n  exclusions:\n    generated: lax\n    presets:\n      - comments\n      - std-error-handling\n    rules:\n      - path: ^pkg/|^cmd/\n        linters:\n          - errorlint\n          - exhaustive\n          - nestif\n          - wastedassign\n      - path: _test.go\n        linters:\n          - gosec\n        test: \"G705\"\n    paths:\n      - swaggerspec\n      - pkg/uiserver\n      - ui\n      - third_party$\n      - builtin$\n      - examples$\nformatters:\n  enable:\n    - gofumpt\n    - goimports\n  settings:\n    goimports:\n      local-prefixes:\n        - github.com/pingcap/tidb-dashboard\n  exclusions:\n    generated: lax\n    paths:\n      - swaggerspec\n      - pkg/uiserver\n      - ui\n      - third_party$\n      - builtin$\n      - examples$\n"
  },
  {
    "path": ".prettierrc",
    "content": "{\n  \"semi\": false,\n  \"trailingComma\": \"none\",\n  \"singleQuote\": true\n}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to TiDB Dashboard\n\nThanks for your interest in contributing to TiDB Dashboard! This document outlines some of the conventions on building, running, and testing TiDB Dashboard, the development workflow, commit message formatting, contact points and other resources.\n\nIf you need any help (for example, mentoring getting started or understanding the codebase), feel free to reach out on the [TiDB Internals forum](https://internals.tidb.io/).\n\n## Setting up a development workspace\n\nThe following steps are describing how to develop TiDB Dashboard by running a self-built and standalone TiDB Dashboard server along with a separated TiDB cluster ([TiDB] + [TiKV] + [PD]). TiDB Dashboard cannot work without a TiDB cluster.\n\nAlthough TiDB Dashboard can also be integrated into [PD], this form is not convenient for developing. Thus we will not cover it here.\n\n### Step 1. Start a TiDB cluster\n\n[TiUP](https://docs.pingcap.com/tidb/stable/tiup-overview) is the official component manager for [TiDB]. It can help you set up a local TiDB cluster in a few minutes.\n\nDownload and install TiUP:\n\n```bash\ncurl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh\n```\n\nDeclare the global environment variable:\n\n> **Note:**\n>\n> After the installation, TiUP displays the absolute path of the corresponding `profile` file. You need to modify the following `source` command according to the path.\n\n```bash\nsource ~/.bash_profile\n```\n\nStart a local TiDB cluster:\n\n```bash\ntiup playground\n```\n\nYou might notice that there is already a TiDB Dashboard integrated into the PD started by TiUP. For development purpose, it will not be used intentionally.\n\n### Step 2. Prepare Dev Prerequisites\n\nThe followings are required for developing TiDB Dashboard:\n\n- git - Version control\n- make - Build tool (run common workflows)\n- [Golang 1.21+](https://golang.org/) - To compile the server.\n- [Node.js 22](https://nodejs.org/) - To compile the front-end.\n- [PNPM 8](https://pnpm.io/) - To manage front-end dependencies.\n- [Java 8+](https://www.java.com/ES/download/) - To generate JavaScript API client by OpenAPI specification.\n\n### Step 3. Build and Run TiDB Dashboard\n\n> Make sure that `tiup playground` is running on the background.\n\nPackage frontend and backend into a single binary:\n\n   ```bash\n   # Build a binary into `bin/tidb-dashboard`.\n   make package\n\n   # Run.\n   make run\n   ```\n\n   You can access TiDB Dashboard now: [http://127.0.0.1:12333/dashboard](http://127.0.0.1:12333/dashboard)\n\n#### Develop Frontend and Backend Separately\n\n1. Build and run TiDB Dashboard back-end server:\n\n   ```bash\n   # In tidb-dashboard directory:\n   make dev && make run\n   ```\n\n2. Build and run front-end server in a new terminal:\n\n   ```bash\n   # In tidb-dashboard directory:\n   cd ui\n   pnpm i # install all dependencies\n   pnpm dev\n   ```\n\n3. That's it! You can access TiDB Dashboard now: [http://127.0.0.1:3001](http://127.0.0.1:3001)\n\n### Step 4. Run E2E Tests (optional)\n\nWhen back-end server and front-end server are both started, E2E tests can be run by:\n\n```bash\ncd ui/packages/tidb-dashboard-for-op\npnpm open:cypress\n```\n\n> Now we have only a few e2e tests. Contributions are welcome!\n\n## Additional Guides\n\n### Style Guidelines\n\nThis project follows the [Uber's Golang style guide](https://github.com/uber-go/guide/blob/master/style.md). Please refer to [its documentation](https://github.com/uber-go/guide/blob/master/style.md) for details.\n\n### Swagger UI\n\nWe use [Swagger] to generate the API server and corresponding clients. Swagger provides a web UI in which you can\nsee all TiDB Dashboard API endpoints and specifications, or even send API requests.\n\nSwagger UI is available at http://localhost:12333/dashboard/api/swagger after the above Step 3 is finished.\n\n### Build and run docker image locally\n\nIf you want to develop docker image locally 🤔.\n\n1. Ensure the Docker Buildx is installed on your local machine.\n\n   > Docker Buildx is included in Docker Desktop for Windows, macOS, and Linux.\n   > Docker Linux packages also include Docker Buildx when installed using the DEB or RPM packages.\n\n2. Build the docker image.\n\n   ```bash\n   # On repository root directory (only build locally, no push remote), run:\n   make docker-build-image-locally-amd64\n\n   # Or, if you want to build the image for arm64 platform (only build locally, no push remote), run:\n   make docker-build-image-locally-arm64\n\n   # Or, if you want to build cross-platform image and push it to your dev docker registry, run:\n   REPOSITORY=your-tidb-dashboard-repository make docker-build-and-push-image\n\n    # Or, if you want to build centos7 based image, run:\n   DOCKERFILE=./dockerfiles/centos7.Dockerfile make docker-build-image-locally-arm64\n\n   # Finally, if you update npm modules or go modules, and want to disable docker layer cache to force rebuild, set NO_CACHE=\"--pull --no-cache\" before make command. For example:\n   NO_CACHE=\"--pull --no-cache\" make docker-build-image-locally-amd64\n   ```\n\n3. Run newly build image with docker-compose.\n\n   > Please make sure that `tiup playground` is not running on the background.\n\n      ```bash\n      # On repository root directory, run:\n      docker-compose -f ./dockerfiles/docker-compose.yml up\n      ```\n\n4. Access TiDB Dashboard at [http://localhost:12333/dashboard](http://localhost:12333/dashboard).\n\n> The old Dashboard **_in PD_** can be accessed at [http://localhost:2379/dashboard](http://localhost:2379/dashboard).\n\n### How to update TiDB Dashboard in PD\n\nTo update the TiDB Dashboard in PD, we need to release a TiDB Dashboard version and submit a PR to PD.\n\n1. In a release branch, likes `release-7.6`, run `make tag` to add a new tag for TiDB Dashboard. The new tag is like `v7.6.x-<sha>`.\n\n1. Push the new tag to the remote repository, it will trigger the CI to release a new TiDB Dashboard version.\n\n1. After the new version is created, go to https://github.com/pingcap/tidb-dashboard/actions/workflows/manual-create-pd-pr.yaml, click `Run workflow` button, fill the required parameters and submit the workflow, it will trigger the CI to create a PR to PD which will update the TiDB Dashboard version in PD.\n\n## Contribution flow\n\nThis is a rough outline of what a contributor's workflow looks like:\n\n- Create a Git branch from where you want to base your work. This is usually master.\n\n- Write code, add test cases, and commit your work (see below for message format).\n\n- Run lints and / or formatters.\n\n  - Backend:\n\n    ```bash\n    # In tidb-dashboard directory:\n    make dev\n    ```\n\n  - Frontend:\n\n    ```bash\n    # In ui directory:\n    pnpm fmt-check\n    ```\n\n    > Recommended to install [Prettier plugin](https://prettier.io/docs/en/editors.html) for your editor so that there will be auto format on save.\n\n- Run tests and make sure all tests pass.\n\n- Push your changes to a branch in your fork of the repository and submit a pull request.\n\n- Your PR will be reviewed by two maintainers, who may request some changes.\n\n  - Once you've made changes, your PR must be re-reviewed and approved.\n\n  - If the PR becomes out of date, you can use GitHub's 'update branch' button.\n\n  - If there are conflicts, you can rebase (or merge) and resolve them locally. Then force push to your PR branch.\n    You do not need to get re-review just for resolving conflicts, but you should request re-review if there are significant changes.\n\n- Our CI system automatically tests all pull requests.\n\n- If all tests passed and got an approval, reviewers will merge your PR.\n\nThanks for your contributions!\n\n### Finding something to work on\n\nFor beginners, we have prepared many suitable tasks for you. Checkout our [help wanted issues](https://github.com/pingcap/tidb-dashboard/issues?q=is%3Aopen+label%3Astatus%2Fhelp-wanted+sort%3Aupdated-desc) for a list, in which we have also marked the difficulty level.\n\nIf you are planning something big, for example, relates to multiple components or changes current behaviors, make sure to open an issue to discuss with us before going on.\n\n### Format of the commit message\n\nWe follow a rough convention for commit messages that is designed to answer two\nquestions: what changed and why. The subject line should feature the what and\nthe body of the commit should describe the why.\n\n```plain\ncluster: add comment for variable declaration.\n\nImprove documentation.\n```\n\nThe format can be described more formally as follows:\n\n```plain\n<subsystem>: <what changed>\n<BLANK LINE>\n<why this change was made>\n<BLANK LINE>\n```\n\nIf the change affects more than one subsystem, you can use comma to separate them like `keyviz, cluster: foo`.\n\nIf the change affects many subsystems, you can use `*` instead, like `*: foo`.\n\nThe body of the commit message should describe why the change was made and at a high level, how the code works.\n\n[pd]: https://github.com/pingcap/pd\n[tidb]: https://github.com/pingcap/tidb\n[tikv]: https://github.com/tikv/tikv\n[tiup]: https://tiup.io\n[swagger]: https://swagger.io\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": "Makefile",
    "content": "DASHBOARD_PKG := github.com/pingcap/tidb-dashboard\n\nBUILD_TAGS ?=\n\nPNPM_INSTALL_TAGS ?=\n\nLDFLAGS ?=\n\nFEATURE_VERSION ?= 999.999.999\n\nWITHOUT_NGM ?= false\n\nE2E_SPEC ?=\n\nUI ?=\n\nRELEASE_VERSION := $(shell git describe --tags --dirty --always)\nGITHASH := $(shell git rev-parse HEAD)\n\nLDFLAGS += -X \"$(DASHBOARD_PKG)/pkg/utils/version.InternalVersion=$(RELEASE_VERSION)\"\nLDFLAGS += -X \"$(DASHBOARD_PKG)/pkg/utils/version.Standalone=Yes\"\nLDFLAGS += -X \"$(DASHBOARD_PKG)/pkg/utils/version.PDVersion=N/A\"\nLDFLAGS += -X \"$(DASHBOARD_PKG)/pkg/utils/version.BuildTime=$(shell date -u '+%Y-%m-%d %I:%M:%S')\"\nLDFLAGS += -X \"$(DASHBOARD_PKG)/pkg/utils/version.BuildGitHash=$(GITHASH)\"\n\nTIDB_VERSION ?= latest\n\n# Docker build variables.\nREPOSITORY ?= pingcap/tidb-dashboard\nIMAGE ?= $(REPOSITORY):$(RELEASE_VERSION)\nAMD64 := linux/amd64\nARM64 := linux/arm64\nPLATFORMS := $(AMD64),$(ARM64)\nDOCKERFILE ?= ./dockerfiles/alpine316.Dockerfile\n# If you want to build with no cache (after update go module, npm module, etc.), set \"NO_CACHE=--pull --no-cache\".\nNO_CACHE ?=\n\nBUILD_GOEXPERIMENT ?=\nBUILD_CGO_ENABLED ?=\nifeq (\"${ENABLE_FIPS}\", \"1\")\n\tBUILD_TAGS += boringcrypto\n\tBUILD_GOEXPERIMENT = GOEXPERIMENT=boringcrypto\n\tBUILD_CGO_ENABLED = CGO_ENABLED=1\nendif\n\ndefault: server\n\n.PHONY: clean\nclean:\n\trm -rf ./coverage\n\n.PHONY: install_tools\ninstall_tools:\n\tscripts/install_go_tools.sh\n\n.PHONY: lint\nlint:\n\tscripts/lint.sh\n\n.PHONY: test\ntest: clean unit_test integration_test\n\n.PHONY: unit_test\nunit_test:\n\t@mkdir -p ./coverage\n\tgo test -v ./pkg/... ./util/...\n\n.PHONY: integration_test\nintegration_test:\n\t@mkdir -p ./coverage\n\t@TIDB_VERSION=${TIDB_VERSION} tests/run.sh\n\n.PHONY: e2e_test\ne2e_test:\n\t@if $(WITHOUT_NGM); then\\\n\t\tmake e2e_without_ngm_test;\\\n\telse\\\n\t\tmake e2e_compat_features_test;\\\n\t\tmake e2e_common_features_test;\\\n\tfi\n\n.PHONY: e2e_compat_features_test\ne2e_compat_features_test:\n\tcd ui &&\\\n\tpnpm i &&\\\n\tcd packages/tidb-dashboard-for-op &&\\\n\tpnpm run:e2e-test:compat-features --env FEATURE_VERSION=$(FEATURE_VERSION) TIDB_VERSION=$(TIDB_VERSION)\n\n.PHONY: e2e_common_features_test\ne2e_common_features_test:\n\tcd ui &&\\\n\tpnpm i &&\\\n\tcd packages/tidb-dashboard-for-op &&\\\n\tpnpm run:e2e-test:common-features --env TIDB_VERSION=$(TIDB_VERSION)\n\n.PHONY: e2e_without_ngm_test\ne2e_without_ngm_test:\n\tcd ui &&\\\n\tpnpm i &&\\\n\tcd packages/tidb-dashboard-for-op &&\\\n\tpnpm run:e2e-test:without-ngm --env TIDB_VERSION=$(TIDB_VERSION) WITHOUT_NGM=$(WITHOUT_NGM)\n\n.PHONY: e2e_test_specify\ne2e_test_specify:\n\tcd ui &&\\\n\tpnpm i &&\\\n\tcd packages/tidb-dashboard-for-op &&\\\n\tpnpm run:e2e-test:specify --env TIDB_VERSION=$(TIDB_VERSION) -- --spec $(E2E_SPEC)\n\n.PHONY: dev\ndev: lint default\n\n.PHONY: ui_deps\nui_deps: install_tools\n\tcd ui &&\\\n\tpnpm i ${PNPM_INSTALL_TAGS}\n\n.PHONY: ui\nui: ui_deps\n\tcd ui &&\\\n\tpnpm build\n\n.PHONY: go_generate\ngo_generate: export PATH := $(shell pwd)/bin:$(PATH)\ngo_generate:\n\tscripts/generate_swagger_spec.sh\n\tgo generate -x ./...\n\n.PHONY: server\nifeq ($(UI),1)\nBUILD_TAGS += ui_server\nendif\nserver: install_tools go_generate\nifeq ($(UI),1)\n\tscripts/embed_ui_assets.sh\nendif\n\t$(BUILD_GOEXPERIMENT) $(BUILD_CGO_ENABLED) go build -o bin/tidb-dashboard -ldflags '$(LDFLAGS)' -tags \"${BUILD_TAGS}\" cmd/tidb-dashboard/main.go\n\n.PHONY: embed_ui_assets\nembed_ui_assets: ui\n\tscripts/embed_ui_assets.sh\n\n.PHONY: package # Build frontend and backend server, and then packages them into a single binary.\npackage: BUILD_TAGS += ui_server\npackage: embed_ui_assets server\n\n.PHONY: docker-build-and-push-image # For locally dev, set IMAGE to your dev docker registry.\ndocker-build-and-push-image: clean\n\tdocker buildx build ${NO_CACHE} --push -t $(IMAGE) --platform $(PLATFORMS) -f $(DOCKERFILE) .\n\n.PHONY: docker-build-image-locally-amd64\ndocker-build-image-locally-amd64: clean\n\tdocker buildx build ${NO_CACHE} --load -t $(IMAGE) --platform $(AMD64) -f $(DOCKERFILE) .\n\tdocker run --rm $(IMAGE) -v\n\n.PHONY: docker-build-image-locally-arm64\ndocker-build-image-locally-arm64: clean\n\tdocker buildx build ${NO_CACHE} --load -t $(IMAGE) --platform $(ARM64) -f $(DOCKERFILE) .\n\tdocker run --rm $(IMAGE) -v\n\n.PHONY: tag\ntag:\n\tnode scripts/create_release_tag.js\n\n.PHONY: run # please ensure that tiup playground is running in the background.\nrun:\n\tbin/tidb-dashboard --debug --experimental --feature-version \"$(FEATURE_VERSION)\" --host 0.0.0.0\n"
  },
  {
    "path": "OWNERS",
    "content": "# See the OWNERS docs at https://go.k8s.io/owners\napprovers:\n  - baurine\n  - breezewish\n  - crazycs520\n  - Deardrops\n  - HunDunDM\n  - iosmanthus\n  - shhdgit\n  - zhongzc\n  - XuHuaiyu\n  - zimulala\n  - nolouch\n  - yibin87\n  - StinsonZhao\n  - bosn\nreviewers:\n  - Fullstop000\n  - Renkai\n  - XuHuaiyu\n  - zimulala\n  - nolouch\n  - yibin87\n"
  },
  {
    "path": "README.md",
    "content": "# TiDB Dashboard\n\n[![GitHub license](https://img.shields.io/github/license/pingcap/tidb-dashboard?style=flat-square)](https://github.com/pingcap/tidb-dashboard/blob/master/LICENSE)\n\nTiDB Dashboard is a Web UI for monitoring, diagnosing and managing the TiDB cluster.\n\n## Documentation\n\n- [Product User Manual (English)](https://docs.pingcap.com/tidb/stable/dashboard-intro)\n- [Product User Manual (Chinese)](https://docs.pingcap.com/zh/tidb/stable/dashboard-intro)\n- [FAQ (English)](https://docs.pingcap.com/tidb/stable/dashboard-faq)\n- [FAQ (Chinese)](https://docs.pingcap.com/zh/tidb/stable/dashboard-faq)\n\n## Question, Suggestion\n\nFeel free to [open GitHub issues](https://github.com/pingcap/tidb-dashboard/issues/new/choose)\nfor questions, support and suggestions.\n\nYou may also consider to reach out on the [TiDB Internals forum](https://internals.tidb.io/) if you encounter any problems about TiDB development.\n\nFor Chinese users, you can visit the PingCAP official user forum [AskTUG.com] to make life easier.\n\n## Getting Started\n\nThe most easy way to use TiDB Dashboard with an existing TiDB cluster is to use the one embedded\ninto [PD]: <http://127.0.0.1:2379/dashboard>. You need PD master branch or 4.0+ version to use\nTiDB Dashboard.\n\nNote: The TiDB Dashboard inside PD may be not up to date. To play with latest TiDB Dashboard, build\nit from source (see next section).\n\n## Contributing & Developing\n\nCheckout our [help wanted issues](https://github.com/pingcap/tidb-dashboard/issues?q=is%3Aopen+label%3Astatus%2Fhelp-wanted+sort%3Aupdated-desc)\nfor a list of recommended tasks, in which we have also marked the difficulty level.\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md) for a detailed step-by-step contributing guide, or steps to\nbuild TiDB Dashboard from source.\n\nIf you need any help, feel free to reach out on the [TiDB Internals forum](https://internals.tidb.io/).\n\nThank you to all the people who already contributed to TiDB Dashboard!\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/Fullstop000\"><img src=\"https://avatars1.githubusercontent.com/u/12471960?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n    <td align=\"center\"><a href=\"http://rleungx.github.io\"><img src=\"https://avatars3.githubusercontent.com/u/35896542?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n    <td align=\"center\"><a href=\"https://github.com/zzh-wisdom\"><img src=\"https://avatars2.githubusercontent.com/u/52516344?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n    <td align=\"center\"><a href=\"https://github.com/STRRL\"><img src=\"https://avatars0.githubusercontent.com/u/20221408?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n    <td align=\"center\"><a href=\"https://github.com/SSebo\"><img src=\"https://avatars0.githubusercontent.com/u/5784607?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n    <td align=\"center\"><a href=\"https://yisaer.github.io/\"><img src=\"https://avatars1.githubusercontent.com/u/13427348?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n    <td align=\"center\"><a href=\"https://github.com/gauss1314\"><img src=\"https://avatars2.githubusercontent.com/u/3862518?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n    <td align=\"center\"><a href=\"https://github.com/leiysky\"><img src=\"https://avatars2.githubusercontent.com/u/22445410?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n    <td align=\"center\"><a href=\"https://github.com/niedhui\"><img src=\"https://avatars0.githubusercontent.com/u/66329?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n    <td align=\"center\"><a href=\"https://weihanglo.tw/\"><img src=\"https://avatars2.githubusercontent.com/u/14314532?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/yikeke\"><img src=\"https://avatars1.githubusercontent.com/u/40977455?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n    <td align=\"center\"><a href=\"https://github.com/qxhy123\"><img src=\"https://avatars2.githubusercontent.com/u/518969?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n    <td align=\"center\"><a href=\"http://www.rustin.cn\"><img src=\"https://avatars0.githubusercontent.com/u/29879298?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n    <td align=\"center\"><a href=\"https://github.com/ericsyh\"><img src=\"https://avatars3.githubusercontent.com/u/10498732?v=4\" width=\"50px;\" alt=\"\"/></a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\n## Architecture\n\nThis repository contains both Dashboard HTTP API and Dashboard UI. Dashboard HTTP API is placed in\n`pkg/` directory, written in Golang. Dashboard UI is placed in `ui/` directory, powered by React.\n\nTiDB Dashboard can also be integrated into PD, as follows:\n\n![](etc/arch_overview.svg)\n\n## License\n\n[Apache License](/LICENSE)\n\nCopyright 2020 PingCAP, Inc.\n\n[pd]: https://github.com/pingcap/pd\n[asktug.com]: https://asktug.com/\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Vulnerability Disclosure and Response Process\n\nTiDB is a fast-growing open source database. To ensure its security, a security vulnerability disclosure and response process is adopted.\n\nThe primary goal of this process is to reduce the total exposure time of users to publicly known vulnerabilities. To quickly fix vulnerabilities of TiDB products, the security team is responsible for the entire vulnerability management process, including internal communication and external disclosure.\n\nIf you find a vulnerability or encounter a security incident involving vulnerabilities of TiDB products, please report it as soon as possible to the TiDB security team (security@tidb.io).\n\nPlease kindly help provide as much vulnerability information as possible in the following format:\n\n- Issue title*:\n\n- Overview*:\n\n- Affected components and version number*:\n\n- CVE number (if any):\n\n- Vulnerability verification process*:\n\n- Contact information*:\n\nThe asterisk (*) indicates the required field.\n\n# Response Time\n\nThe TiDB security team will confirm the vulnerabilities and contact you within 2 working days after your submission.\n\nWe will publicly thank you after fixing the security vulnerability. To avoid negative impact, please keep the vulnerability confidential until we fix it. We would appreciate it if you could obey the following code of conduct:\n\nThe vulnerability will not be disclosed until TiDB releases a patch for it.\n\nThe details of the vulnerability, for example, exploits code, will not be disclosed.\n"
  },
  {
    "path": "cmd/tidb-dashboard/main.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// @title Dashboard API\n// @version 1.0\n// @license.name Apache 2.0\n// @license.url http://www.apache.org/licenses/LICENSE-2.0.html\n// @BasePath /dashboard/api\n// @query.collection.format multi\n// @securityDefinitions.apikey JwtAuth\n// @in header\n// @name Authorization\n\npackage main\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t_ \"net/http/pprof\" // #nosec\n\t\"os\"\n\t\"os/signal\"\n\t\"path\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\n\t\"github.com/pingcap/log\"\n\tflag \"github.com/spf13/pflag\"\n\t\"go.etcd.io/etcd/client/pkg/v3/transport\"\n\t\"go.uber.org/zap\"\n\t\"go.uber.org/zap/zapcore\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\tkeyvisualregion \"github.com/pingcap/tidb-dashboard/pkg/keyvisual/region\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/swaggerserver\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/uiserver\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/version\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\ntype DashboardCLIConfig struct {\n\tListenHost     string\n\tListenPort     int\n\tEnableDebugLog bool\n\tCoreConfig     *config.Config\n\t// key-visual file mode for debug\n\tKVFileStartTime int64\n\tKVFileEndTime   int64\n}\n\n// NewCLIConfig generates the configuration of the dashboard in standalone mode.\nfunc NewCLIConfig() *DashboardCLIConfig {\n\tcfg := &DashboardCLIConfig{}\n\tcfg.CoreConfig = config.Default()\n\n\tflag.StringVarP(&cfg.ListenHost, \"host\", \"h\", \"127.0.0.1\", \"listen host of the Dashboard Server\")\n\tflag.IntVarP(&cfg.ListenPort, \"port\", \"p\", 12333, \"listen port of the Dashboard Server\")\n\tflag.BoolVarP(&cfg.EnableDebugLog, \"debug\", \"d\", false, \"enable debug logs\")\n\tflag.StringVar(&cfg.CoreConfig.DataDir, \"data-dir\", cfg.CoreConfig.DataDir, \"path to the Dashboard Server data directory\")\n\tflag.StringVar(&cfg.CoreConfig.TempDir, \"temp-dir\", cfg.CoreConfig.TempDir, \"path to the Dashboard Server temporary directory, used to store the searched logs\")\n\tflag.StringVar(&cfg.CoreConfig.PublicPathPrefix, \"path-prefix\", cfg.CoreConfig.PublicPathPrefix, \"public URL path prefix for reverse proxies\")\n\tflag.StringVar(&cfg.CoreConfig.PDEndPoint, \"pd\", cfg.CoreConfig.PDEndPoint, \"PD endpoint address that Dashboard Server connects to\")\n\tflag.BoolVar(&cfg.CoreConfig.EnableTelemetry, \"telemetry\", cfg.CoreConfig.EnableTelemetry, \"allow telemetry\")\n\tflag.BoolVar(&cfg.CoreConfig.EnableExperimental, \"experimental\", cfg.CoreConfig.EnableExperimental, \"allow experimental features\")\n\tflag.StringVar(&cfg.CoreConfig.FeatureVersion, \"feature-version\", cfg.CoreConfig.FeatureVersion, \"target TiDB version for standalone mode\")\n\tflag.IntVar(&cfg.CoreConfig.NgmTimeout, \"ngm-timeout\", cfg.CoreConfig.NgmTimeout, \"timeout secs for accessing the ngm API\")\n\tflag.BoolVar(&cfg.CoreConfig.EnableKeyVisualizer, \"keyviz\", true, \"enable/disable key visualizer(default: true)\")\n\tflag.BoolVar(&cfg.CoreConfig.DisableCustomPromAddr, \"disable-custom-prom-addr\", false, \"do not allow custom prometheus address\")\n\n\tshowVersion := flag.BoolP(\"version\", \"v\", false, \"print version information and exit\")\n\n\tclusterCaPath := flag.String(\"cluster-ca\", \"\", \"(TLS between components of the TiDB cluster) path of file that contains list of trusted SSL CAs\")\n\tclusterCertPath := flag.String(\"cluster-cert\", \"\", \"(TLS between components of the TiDB cluster) path of file that contains X509 certificate in PEM format\")\n\tclusterKeyPath := flag.String(\"cluster-key\", \"\", \"(TLS between components of the TiDB cluster) path of file that contains X509 key in PEM format\")\n\tclusterAllowedNames := flag.String(\"cluster-allowed-names\", \"\", \"comma-delimited list of acceptable peer certificate SAN identities\")\n\n\ttidbCaPath := flag.String(\"tidb-ca\", \"\", \"(TLS for MySQL client) path of file that contains list of trusted SSL CAs\")\n\ttidbCertPath := flag.String(\"tidb-cert\", \"\", \"(TLS for MySQL client) path of file that contains X509 certificate in PEM format\")\n\ttidbKeyPath := flag.String(\"tidb-key\", \"\", \"(TLS for MySQL client) path of file that contains X509 key in PEM format\")\n\ttidbAllowedNames := flag.String(\"tidb-allowed-names\", \"\", \"comma-delimited list of acceptable peer certificate SAN identities\")\n\n\t// debug for keyvisual，hide help information\n\tflag.Int64Var(&cfg.KVFileStartTime, \"keyviz-file-start\", 0, \"(debug) start time for file range in file mode\")\n\tflag.Int64Var(&cfg.KVFileEndTime, \"keyviz-file-end\", 0, \"(debug) end time for file range in file mode\")\n\t_ = flag.CommandLine.MarkHidden(\"keyviz-file-start\")\n\t_ = flag.CommandLine.MarkHidden(\"keyviz-file-end\")\n\n\tflag.Parse()\n\tif *showVersion {\n\t\tversion.PrintStandaloneModeInfo()\n\t\t_ = log.Sync()\n\t\tos.Exit(0)\n\t}\n\n\tcfg.CoreConfig.NormalizePublicPathPrefix()\n\n\t// setup TLS config for TiDB components\n\tif len(*clusterCaPath) != 0 && len(*clusterCertPath) != 0 && len(*clusterKeyPath) != 0 {\n\t\ttlsInfo := &transport.TLSInfo{\n\t\t\tTrustedCAFile: *clusterCaPath,\n\t\t\tKeyFile:       *clusterKeyPath,\n\t\t\tCertFile:      *clusterCertPath,\n\t\t}\n\t\tcfg.CoreConfig.ClusterTLSInfo = tlsInfo\n\t\tcfg.CoreConfig.ClusterTLSConfig = buildTLSConfig(tlsInfo, clusterAllowedNames)\n\t}\n\n\t// setup TLS config for MySQL client\n\t// See https://github.com/pingcap/docs/blob/7a62321b3ce9318cbda8697503c920b2a01aeb3d/how-to/secure/enable-tls-clients.md#enable-authentication\n\tif (len(*tidbCertPath) != 0 && len(*tidbKeyPath) != 0) || len(*tidbCaPath) != 0 {\n\t\ttlsInfo := &transport.TLSInfo{\n\t\t\tTrustedCAFile: *tidbCaPath,\n\t\t\tKeyFile:       *tidbKeyPath,\n\t\t\tCertFile:      *tidbCertPath,\n\t\t}\n\t\tcfg.CoreConfig.TiDBTLSConfig = buildTLSConfig(tlsInfo, tidbAllowedNames)\n\t}\n\n\tif err := cfg.CoreConfig.NormalizePDEndPoint(); err != nil {\n\t\tlog.Fatal(\"Invalid PD Endpoint\", zap.Error(err))\n\t}\n\n\t// keyvisual check\n\tstartTime := cfg.KVFileStartTime\n\tendTime := cfg.KVFileEndTime\n\tif startTime != 0 || endTime != 0 {\n\t\t// file mode (debug)\n\t\tif startTime == 0 || endTime == 0 || startTime >= endTime {\n\t\t\tlog.Fatal(\"keyviz-file-start must be smaller than keyviz-file-end, and none of them are 0\")\n\t\t}\n\t}\n\n\treturn cfg\n}\n\nfunc getContext() context.Context {\n\tctx, cancel := context.WithCancel(context.Background())\n\tgo func() {\n\t\tsc := make(chan os.Signal, 1)\n\t\tsignal.Notify(sc,\n\t\t\tsyscall.SIGHUP,\n\t\t\tsyscall.SIGINT,\n\t\t\tsyscall.SIGTERM,\n\t\t\tsyscall.SIGQUIT)\n\t\t<-sc\n\t\tcancel()\n\t}()\n\treturn ctx\n}\n\nfunc buildTLSConfig(tlsInfo *transport.TLSInfo, allowedNames *string) *tls.Config {\n\ttlsConfig, err := tlsInfo.ClientConfig()\n\tif err != nil {\n\t\tlog.Fatal(\"Failed to load certificates\", zap.Error(err))\n\t}\n\n\t// Disable the default server verification routine in favor of a manually defined connection\n\t// verification callback. The custom verification process verifies that the server\n\t// certificate is issued by a trusted root CA, and that the peer certificate identities\n\t// matches at least one entry specified in verifyNames (if specified). This is required\n\t// because tidb-dashboard directs requests to a loopback-bound forwarding proxy, which would\n\t// otherwise cause server hostname verification to fail.\n\ttlsConfig.InsecureSkipVerify = true\n\ttlsConfig.VerifyConnection = func(state tls.ConnectionState) error {\n\t\topts := x509.VerifyOptions{\n\t\t\tIntermediates: x509.NewCertPool(),\n\t\t\tRoots:         tlsConfig.RootCAs,\n\t\t}\n\n\t\tfor _, cert := range state.PeerCertificates[1:] {\n\t\t\topts.Intermediates.AddCert(cert)\n\t\t}\n\n\t\t_, err := state.PeerCertificates[0].Verify(opts)\n\n\t\t// Optionally verify the peer SANs when available. If no peer identities are\n\t\t// provided, simply reuse the verification result of the CA verification.\n\t\tif err != nil || *allowedNames == \"\" {\n\t\t\treturn err\n\t\t}\n\n\t\tfor name := range strings.SplitSeq(*allowedNames, \",\") {\n\t\t\tif slices.Contains(state.PeerCertificates[0].DNSNames, name) {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\tfor _, uri := range state.PeerCertificates[0].URIs {\n\t\t\t\tif name == uri.String() {\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn fmt.Errorf(\n\t\t\t\"no SANs in server certificate (%v, %v) match allowed names %v\",\n\t\t\tstate.PeerCertificates[0].DNSNames,\n\t\t\tstate.PeerCertificates[0].URIs,\n\t\t\tstrings.Split(*allowedNames, \",\"),\n\t\t)\n\t}\n\n\treturn tlsConfig\n}\n\nconst (\n\tdistroResFolderName      string = \"distro-res\"\n\tdistroStringsResFileName string = \"strings.json\"\n)\n\nfunc loadDistroStringsRes() {\n\texePath, err := os.Executable()\n\tif err != nil {\n\t\tlog.Fatal(\"Failed to get executable path\", zap.Error(err))\n\t}\n\n\tdistroStringsResPath := path.Join(path.Dir(exePath), distroResFolderName, distroStringsResFileName)\n\tdistroStringsRes, err := distro.ReadResourceStringsFromFile(distroStringsResPath)\n\tif err != nil {\n\t\tlog.Fatal(\"Failed to load distro strings res\", zap.String(\"path\", distroStringsResPath), zap.Error(err))\n\t}\n\n\tdistro.ReplaceGlobal(distroStringsRes)\n}\n\nfunc main() {\n\t// Flushing any buffered log entries\n\tdefer log.Sync() //nolint:errcheck\n\n\t// init log will register the `pingcap-log` logfmt for\n\t_, _, err := log.InitLogger(&log.Config{})\n\tif err != nil {\n\t\tlog.Fatal(\"failed to init log\", zap.Error(err))\n\t}\n\n\tcliConfig := NewCLIConfig()\n\tctx := getContext()\n\n\tif cliConfig.EnableDebugLog {\n\t\tlog.SetLevel(zapcore.DebugLevel)\n\t}\n\n\tloadDistroStringsRes()\n\n\tlistenAddr := net.JoinHostPort(cliConfig.ListenHost, strconv.Itoa(cliConfig.ListenPort))\n\tlistener, err := net.Listen(\"tcp\", listenAddr)\n\tif err != nil {\n\t\tlog.Fatal(\"Dashboard server listen failed\", zap.String(\"addr\", listenAddr), zap.Error(err))\n\t}\n\n\tvar customKeyVisualProvider *keyvisualregion.DataProvider\n\tif cliConfig.KVFileStartTime > 0 {\n\t\tcustomKeyVisualProvider = &keyvisualregion.DataProvider{\n\t\t\tFileStartTime: cliConfig.KVFileStartTime,\n\t\t\tFileEndTime:   cliConfig.KVFileEndTime,\n\t\t}\n\t}\n\tassets := uiserver.Assets(cliConfig.CoreConfig)\n\ts := apiserver.NewService(\n\t\tcliConfig.CoreConfig,\n\t\tapiserver.StoppedHandler,\n\t\tassets,\n\t\tcustomKeyVisualProvider,\n\t)\n\tif err := s.Start(ctx); err != nil {\n\t\tlog.Fatal(\"Can not start server\", zap.Error(err))\n\t}\n\tdefer s.Stop(context.Background()) //nolint:errcheck\n\n\tmux := http.DefaultServeMux\n\tuiHandler := http.StripPrefix(strings.TrimRight(config.UIPathPrefix, \"/\"), uiserver.Handler(assets))\n\tmux.Handle(\"/\", http.RedirectHandler(config.UIPathPrefix, http.StatusFound))\n\tmux.Handle(config.UIPathPrefix, uiHandler)\n\tmux.Handle(config.APIPathPrefix, apiserver.Handler(s))\n\tmux.Handle(config.SwaggerPathPrefix, swaggerserver.Handler())\n\n\tlog.Info(fmt.Sprintf(\"Dashboard server is listening at %s\", listenAddr))\n\tlog.Info(fmt.Sprintf(\"UI:      http://%s/dashboard/\", net.JoinHostPort(cliConfig.ListenHost, strconv.Itoa(cliConfig.ListenPort))))\n\tlog.Info(fmt.Sprintf(\"API:     http://%s/dashboard/api/\", net.JoinHostPort(cliConfig.ListenHost, strconv.Itoa(cliConfig.ListenPort))))\n\tlog.Info(fmt.Sprintf(\"Swagger: http://%s/dashboard/api/swagger/\", net.JoinHostPort(cliConfig.ListenHost, strconv.Itoa(cliConfig.ListenPort))))\n\n\tsrv := &http.Server{Handler: mux} // nolint:gosec\n\tvar wg sync.WaitGroup\n\twg.Go(func() {\n\t\tif err := srv.Serve(listener); err != http.ErrServerClosed {\n\t\t\tlog.Error(\"Server aborted with an error\", zap.Error(err))\n\t\t}\n\t})\n\n\t<-ctx.Done()\n\tif err := srv.Shutdown(context.Background()); err != nil {\n\t\tlog.Error(\"Can not stop server\", zap.Error(err))\n\t}\n\twg.Wait()\n\tlog.Info(\"Stop dashboard server\")\n}\n"
  },
  {
    "path": "dockerfiles/docker-compose.yml",
    "content": "version: '2'\n\nservices:\n  tidb-dashboard:\n    image: pingcap/tidb-dashboard:nightly\n    ports:\n      - \"12333:12333\"\n    command:\n      - --pd=http://pd:2379\n      - --debug\n      - --experimental\n      - --feature-version=999.999.999\n      - --host=0.0.0.0\n    depends_on:\n      - \"pd\"\n    restart: on-failure\n  pd:\n    image: pingcap/pd:nightly\n    ports:\n      - \"2379:2379\"\n    command:\n      - --name=pd\n      - --client-urls=http://0.0.0.0:2379\n      - --peer-urls=http://0.0.0.0:2380\n      - --advertise-client-urls=http://pd:2379\n      - --advertise-peer-urls=http://pd:2380\n      - --initial-cluster=pd=http://pd:2380\n    restart: on-failure\n  tikv:\n    image: pingcap/tikv:nightly\n    ports:\n      - \"20180:20180\"\n    command:\n      - --addr=0.0.0.0:20160\n      - --advertise-addr=tikv:20160\n      - --status-addr=0.0.0.0:20180\n      - --pd=pd:2379\n    depends_on:\n      - \"pd\"\n    restart: on-failure\n  tidb:\n    image: pingcap/tidb:nightly\n    ports:\n      - \"4000:4000\"\n      - \"10080:10080\"\n    command:\n      - --host=0.0.0.0\n      - --advertise-address=tidb\n      - --store=tikv\n      - --path=pd:2379\n    depends_on:\n      - \"tikv\"\n    restart: on-failure\n  tiflash:\n    image: pingcap/tiflash:nightly\n    volumes:\n      - ./tiflash.toml:/tiflash.toml:ro\n    ports:\n      - \"9000:9000\"\n      - \"8123:8123\"\n      - \"8234:8234\"\n      - \"3930:3930\"\n      - \"20170:20170\"\n      - \"20292:20292\"\n    command:\n      - --config=/tiflash.toml\n    depends_on:\n      - \"tikv\"\n      - \"tidb\"\n    restart: on-failure\n  error-metric-trigger:\n    image: mariadb:10.6.5\n    command:\n      - mysql\n      - -uroot\n      - -htidb\n      - -P4000\n      - -e\n      - \"select * from foo.bar;\"\n    depends_on:\n      - \"tikv\"\n      - \"tidb\"\n      - \"tiflash\"\n    restart: on-failure\n"
  },
  {
    "path": "etc/go.mod",
    "content": "module ignore_etc // a hack to ignore this directory in go commands\n\ngo 1.13\n"
  },
  {
    "path": "etc/manualTestEnv/.gitignore",
    "content": ".vagrant/\ntiup-cluster-*.log\n"
  },
  {
    "path": "etc/manualTestEnv/_shared/Vagrantfile.partial.pubKey.rb",
    "content": "Vagrant.configure(\"2\") do |config|\n  ssh_pub_key = File.readlines(\"#{File.dirname(__FILE__)}/vagrant_key.pub\").first.strip\n\n  config.vm.box = \"hashicorp/bionic64\"\n  config.vm.provision \"zsh\", type: \"shell\", privileged: false, inline: <<-SHELL\n    echo \"Installing zsh\"\n    sudo apt install -y zsh\n    sh -c \"$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\"\n    sudo chsh -s /usr/bin/zsh vagrant\n  SHELL\n\n  config.vm.provision \"private_key\", type: \"shell\", privileged: false, inline: <<-SHELL\n    echo \"Inserting private key\"\n    echo #{ssh_pub_key} >> /home/vagrant/.ssh/authorized_keys\n  SHELL\n\n  config.vm.provision \"ulimit\", type: \"shell\", privileged: true, inline: <<-SHELL\n    echo \"Setting ulimit\"\n    echo \"fs.file-max = 65535\" >> /etc/sysctl.conf\n    sysctl -p\n    echo \"*      hard    nofile   65535\" >> /etc/security/limits.conf\n    echo \"*      soft    nofile   65535\" >> /etc/security/limits.conf\n    echo \"root   hard    nofile   65535\" >> /etc/security/limits.conf\n    echo \"root   hard    nofile   65535\" >> /etc/security/limits.conf\n  SHELL\nend\n"
  },
  {
    "path": "etc/manualTestEnv/_shared/vagrant_key",
    "content": "-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAQEAxboZzYumqNoVOQ/hKKhIZHxNhf5tmnkLZry8i6Xur4FPLDiRxos/\nxVVDx0ynTPOyQVVaXtNxZnAmbR4HuNBzRvNoklwSXazt5YgWeiKCHtPpKFt3PJeE2cn6FJ\np6F6qFChG0NSPbZxJWWxv4noX0U3PLKgHNIehYK2Fu0E6plhSZazzJEVWapwo9d7aGnAsz\nbBCd5TNZ5ogrXn+3bSFcdCbAfWOwYg54a+PzTQlzgt6JmhlEjpFfPhhpBW92pQXxmQ2c17\niPCbA8G++FiaEwA5teex8k1+HzmHf7YjyhPr+I67EzEiIueJg2+0PYbM1p06S8kVTNDXsf\n0eJx4Dr8qQAAA9iFPcpVhT3KVQAAAAdzc2gtcnNhAAABAQDFuhnNi6ao2hU5D+EoqEhkfE\n2F/m2aeQtmvLyLpe6vgU8sOJHGiz/FVUPHTKdM87JBVVpe03FmcCZtHge40HNG82iSXBJd\nrO3liBZ6IoIe0+koW3c8l4TZyfoUmnoXqoUKEbQ1I9tnElZbG/iehfRTc8sqAc0h6FgrYW\n7QTqmWFJlrPMkRVZqnCj13toacCzNsEJ3lM1nmiCtef7dtIVx0JsB9Y7BiDnhr4/NNCXOC\n3omaGUSOkV8+GGkFb3alBfGZDZzXuI8JsDwb74WJoTADm157HyTX4fOYd/tiPKE+v4jrsT\nMSIi54mDb7Q9hszWnTpLyRVM0Nex/R4nHgOvypAAAAAwEAAQAAAQBtk0+/YDgQ9SKzx8AQ\nxwmvXk+cBT76T0BpRAj9HwziiDe3GvZ2YC8MDc+NAEbq11ae7E0zpdv/WAGDkRPYcPShij\n0Wdx3aef4wqLVEJCGWMfvRWLcAhjuiclM73cvxl5c42EzU8jUhrsDapuql9zhKky4w7mSe\n+OL7z3gYyq8isvcQMe+1eXJqiv27AJJfAir+rLJZO/gDW36hOowhnZxYRlVYPgZ8GwetxD\nVdCrgwUgR/2HYmbXYdVxI0PwswGc6rEqs5XXOYRzwvPTvRKdD3J5MxmsvJljT7FMr4kCLT\nX1+aWysk1cgAUIdzzwQL8DLE/N9PFFYdZyNBkZMgedl9AAAAgCtP3F8XYFR18gQLPGLDyQ\nFFg8+JHN9b/yIg2pymC6SI8qEp+GnuEK9IKhqh/Uw14KEKcs/9sgbZo0K9uTBTDG5F6Qmp\nhADVbWXJ/97Xeya6kH2Sa56UKLCQ/uQWBKwLQ0auU/qwxATIZowh31XUXjzVBg6wgUjT7Q\n+3Fk1zGYxnAAAAgQD5USIRUNwkI+htv+f1g8QdmrFAGymcGEkXAixKvBTon9cWQb2iyiK+\n2IO8EwFwRdL5kw2foILCnlp/4FevfxHU7wTcoFEp3PItUlcxYqO8vY2VCZ913oNLKBIt9p\nuFfG2BZM5szMRNMh0svelu61FePsfN5Z8J0ltPrS8UKB95ywAAAIEAywbyNbjz1AxEjWIX\n2Vbk4/MjQyjui8Wi7H0F+LDWyMfPJHzhnbr79Z/lIZmDAo++3EYU9J9s0C+wJ6vXGK+gvC\n7e5qGfT/0J0DwBfLbpeTdDELCa/LmfLWVPzZ9Q+9Fq0AjmW9YXFZ/+qT9xfY1v9XfztFRS\nxR1iXJ42q6ff5NsAAAAeYnJlZXpld2lzaEBCcmVlemV3aXNoTUJQLmxvY2FsAQIDBAU=\n-----END OPENSSH PRIVATE KEY-----\n"
  },
  {
    "path": "etc/manualTestEnv/_shared/vagrant_key.pub",
    "content": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFuhnNi6ao2hU5D+EoqEhkfE2F/m2aeQtmvLyLpe6vgU8sOJHGiz/FVUPHTKdM87JBVVpe03FmcCZtHge40HNG82iSXBJdrO3liBZ6IoIe0+koW3c8l4TZyfoUmnoXqoUKEbQ1I9tnElZbG/iehfRTc8sqAc0h6FgrYW7QTqmWFJlrPMkRVZqnCj13toacCzNsEJ3lM1nmiCtef7dtIVx0JsB9Y7BiDnhr4/NNCXOC3omaGUSOkV8+GGkFb3alBfGZDZzXuI8JsDwb74WJoTADm157HyTX4fOYd/tiPKE+v4jrsTMSIi54mDb7Q9hszWnTpLyRVM0Nex/R4nHgOvyp\n"
  },
  {
    "path": "etc/manualTestEnv/complexCase1/README.md",
    "content": "# complexCase1\n\nTiDB, PD, TiKV, TiFlash each in different hosts.\n\n## Usage\n\n1. Start the box:\n\n   ```bash\n   VAGRANT_EXPERIMENTAL=\"disks\" vagrant up\n   ```\n\n1. Use [TiUP](https://tiup.io/) to deploy the cluster to the box (only need to do it once):\n\n   ```bash\n   tiup cluster deploy complexCase1 v4.0.8 topology.yaml -i ../_shared/vagrant_key -y --user vagrant\n   ```\n\n1. Start the cluster in the box:\n\n   ```bash\n   tiup cluster start complexCase1\n   ```\n\n1. Start TiDB Dashboard server:\n\n   ```bash\n   bin/tidb-dashboard --pd http://10.0.1.31:2379\n   ```\n\n## Cleanup\n\n```bash\ntiup cluster destroy complexCase1 -y\nvagrant destroy --force\n```\n"
  },
  {
    "path": "etc/manualTestEnv/complexCase1/Vagrantfile",
    "content": "load \"#{File.dirname(__FILE__)}/../_shared/Vagrantfile.partial.pubKey.rb\"\n\nVagrant.configure(\"2\") do |config|\n  config.vm.provider \"virtualbox\" do |v|\n    v.memory = 1024\n    v.cpus = 1\n  end\n\n  (1..5).each do |i|\n    config.vm.define \"node#{i}\" do |node|\n      node.vm.network \"private_network\", ip: \"10.0.1.#{i+30}\"\n      (1..4).each do |j|\n        node.vm.disk :disk, size: \"10GB\", name: \"disk-#{i}-#{j}\"\n      end\n    end\n  end\n\n  config.vm.provision \"disk\", type: \"shell\", privileged: false, inline: <<-SHELL\n    echo \"Formatting disks\"\n    sudo mkfs.ext4 -j -L hdd1 /dev/sdb\n    sudo mkfs.ext4 -j -L hdd2 /dev/sdc\n    sudo mkfs.ext4 -j -L hdd3 /dev/sdd\n    sudo mkfs.ext4 -j -L hdd4 /dev/sde\n\n    echo \"Mounting directories\"\n    sudo mkdir -p /pingcap/tidb-data\n    echo \"/dev/sdb /pingcap/tidb-data ext4 defaults 0 0\" | sudo tee -a /etc/fstab\n    sudo mount /pingcap/tidb-data\n\n    sudo mkdir -p /pingcap/tidb-deploy\n    sudo mkdir -p /pingcap/tidb-data/tikv-1\n    sudo mkdir -p /pingcap/tidb-data/tikv-2\n    echo \"/dev/sdc /pingcap/tidb-deploy ext4 defaults 0 0\" | sudo tee -a /etc/fstab\n    echo \"/dev/sdd /pingcap/tidb-data/tikv-1 ext4 defaults 0 0\" | sudo tee -a /etc/fstab\n    echo \"/dev/sde /pingcap/tidb-data/tikv-2 ext4 defaults 0 0\" | sudo tee -a /etc/fstab\n    sudo mount /pingcap/tidb-deploy\n    sudo mount /pingcap/tidb-data/tikv-1\n    sudo mount /pingcap/tidb-data/tikv-2\n  SHELL\nend\n"
  },
  {
    "path": "etc/manualTestEnv/complexCase1/topology.yaml",
    "content": "global:\n  user: tidb\n  deploy_dir: /pingcap/tidb-deploy\n  data_dir: /pingcap/tidb-data\n\nserver_configs:\n  tikv:\n    server.grpc-concurrency: 1\n    raftstore.apply-pool-size: 1\n    raftstore.store-pool-size: 1\n    readpool.unified.max-thread-count: 1\n    readpool.storage.use-unified-pool: false\n    readpool.coprocessor.use-unified-pool: true\n    storage.block-cache.capacity: 256MB\n    raftstore.capacity: 5GB\n\n# Overview:\n#   31: 1 PD, 1 TiDB, 2 TiKV\n#   32:       1 TiDB, 2 TiKV\n#   33: 1 PD,                 1 TiFlash\n#   34:               2 TiKV, 1 TiFlash\n#   35:                       1 TiFlash\n\npd_servers:\n  - host: 10.0.1.31\n  - host: 10.0.1.33\n\ntikv_servers:\n  - host: 10.0.1.31\n    port: 20160\n    status_port: 20180\n    data_dir: /pingcap/tidb-data/tikv-1/tikv-20160\n    config:\n      server.labels: { host: \"tikv1\" }\n  - host: 10.0.1.31\n    port: 20161\n    status_port: 20181\n    data_dir: /pingcap/tidb-data/tikv-2/tikv-20161\n    config:\n      server.labels: { host: \"tikv2\" }\n  - host: 10.0.1.32\n    port: 20160\n    status_port: 20180\n    data_dir: /pingcap/tidb-data/tikv-1/tikv-20160\n    config:\n      server.labels: { host: \"tikv1\" }\n  - host: 10.0.1.32\n    port: 20161\n    status_port: 20181\n    data_dir: /pingcap/tidb-data/tikv-2/tikv-20161\n    config:\n      server.labels: { host: \"tikv2\" }\n  - host: 10.0.1.34\n    port: 20160\n    status_port: 20180\n    data_dir: /pingcap/tidb-data/tikv-1/tikv-20160\n    config:\n      server.labels: { host: \"tikv1\" }\n  - host: 10.0.1.34\n    port: 20161\n    status_port: 20181\n    data_dir: /pingcap/tidb-data/tikv-2/tikv-20161\n    config:\n      server.labels: { host: \"tikv2\" }\n\ntiflash_servers:\n  - host: 10.0.1.33\n    data_dir: /pingcap/tidb-data/tikv-1/tiflash\n  - host: 10.0.1.34\n    data_dir: /pingcap/tidb-data/tikv-2/tiflash\n  - host: 10.0.1.35\n    data_dir: /pingcap/tidb-data/tikv-1/tiflash\n\ntidb_servers:\n  - host: 10.0.1.31\n  - host: 10.0.1.32\n\ngrafana_servers:\n  - host: 10.0.1.31\n\nmonitoring_servers:\n  - host: 10.0.1.31\n\nalertmanager_servers:\n  - host: 10.0.1.31\n"
  },
  {
    "path": "etc/manualTestEnv/multiHost/README.md",
    "content": "# multiHost\n\nTiDB, PD, TiKV, TiFlash each in different hosts.\n\n## Usage\n\n1. Start the box:\n\n   ```bash\n   vagrant up\n   ```\n\n1. Use [TiUP](https://tiup.io/) to deploy the cluster to the box (only need to do it once):\n\n   ```bash\n   tiup cluster deploy multiHost v4.0.8 topology.yaml -i ../_shared/vagrant_key -y --user vagrant\n   ```\n\n1. Start the cluster in the box:\n\n   ```bash\n   tiup cluster start multiHost\n   ```\n\n1. Start TiDB Dashboard server:\n\n   ```bash\n   bin/tidb-dashboard --pd http://10.0.1.11:2379\n   ```\n\n## Cleanup\n\n```bash\ntiup cluster destroy multiHost -y\nvagrant destroy --force\n```\n"
  },
  {
    "path": "etc/manualTestEnv/multiHost/Vagrantfile",
    "content": "load \"#{File.dirname(__FILE__)}/../_shared/Vagrantfile.partial.pubKey.rb\"\n\nVagrant.configure(\"2\") do |config|\n  config.vm.provider \"virtualbox\" do |v|\n    v.memory = 1024\n    v.cpus = 1\n  end\n\n  (1..4).each do |i|\n    config.vm.define \"node#{i}\" do |node|\n      node.vm.network \"private_network\", ip: \"10.0.1.#{i+10}\"\n    end\n  end\nend\n"
  },
  {
    "path": "etc/manualTestEnv/multiHost/topology.yaml",
    "content": "global:\n  user: tidb\n  deploy_dir: tidb-deploy\n  data_dir: tidb-data\n\nserver_configs:\n  tikv:\n    server.grpc-concurrency: 1\n    raftstore.apply-pool-size: 1\n    raftstore.store-pool-size: 1\n    readpool.unified.max-thread-count: 1\n    readpool.storage.use-unified-pool: false\n    readpool.coprocessor.use-unified-pool: true\n    storage.block-cache.capacity: 256MB\n    raftstore.capacity: 10GB\n  pd:\n    replication.enable-placement-rules: true\n\npd_servers:\n  - host: 10.0.1.11\n  - host: 10.0.1.12\n  - host: 10.0.1.13\n\ntikv_servers:\n  - host: 10.0.1.12\n\ntidb_servers:\n  - host: 10.0.1.11\n  - host: 10.0.1.12\n  - host: 10.0.1.13\n\ntiflash_servers:\n  - host: 10.0.1.14\n\ngrafana_servers:\n  - host: 10.0.1.11\n\nmonitoring_servers:\n  - host: 10.0.1.11\n\nalertmanager_servers:\n  - host: 10.0.1.11\n"
  },
  {
    "path": "etc/manualTestEnv/multiReplica/README.md",
    "content": "# multiReplica\n\nMultiple TiKV nodes in different labels.\n\n## Usage\n\n1. Start the box:\n\n   ```bash\n   vagrant up\n   ```\n\n1. Use [TiUP](https://tiup.io/) to deploy the cluster to the box (only need to do it once):\n\n   ```bash\n   tiup cluster deploy multiReplica v4.0.8 topology.yaml -i ../_shared/vagrant_key -y --user vagrant\n   ```\n\n1. Start the cluster in the box:\n\n   ```bash\n   tiup cluster start multiReplica\n   ```\n\n1. Start TiDB Dashboard server:\n\n   ```bash\n   bin/tidb-dashboard --pd http://10.0.1.20:2379\n   ```\n\n## Cleanup\n\n```bash\ntiup cluster destroy multiReplica -y\nvagrant destroy --force\n```\n"
  },
  {
    "path": "etc/manualTestEnv/multiReplica/Vagrantfile",
    "content": "load \"#{File.dirname(__FILE__)}/../_shared/Vagrantfile.partial.pubKey.rb\"\n\nVagrant.configure(\"2\") do |config|\n  config.vm.provider \"virtualbox\" do |v|\n    v.memory = 4 * 1024\n    v.cpus = 2\n  end\n\n  config.vm.network \"private_network\", ip: \"10.0.1.20\"\nend\n"
  },
  {
    "path": "etc/manualTestEnv/multiReplica/topology.yaml",
    "content": "global:\n  user: tidb\n  deploy_dir: tidb-deploy\n  data_dir: tidb-data\n\nserver_configs:\n  tikv:\n    server.grpc-concurrency: 1\n    raftstore.apply-pool-size: 1\n    raftstore.store-pool-size: 1\n    readpool.unified.max-thread-count: 1\n    readpool.storage.use-unified-pool: false\n    readpool.coprocessor.use-unified-pool: true\n    storage.block-cache.capacity: 256MB\n    raftstore.capacity: 10GB\n  pd:\n    replication.location-labels:\n      - zone\n      - rack\n      - host\n\npd_servers:\n  - host: 10.0.1.20\n\ntikv_servers:\n  - host: 10.0.1.20\n    port: 20160\n    status_port: 20180\n    config:\n      server.labels: { host: tikv1, rack: rack1 }\n  - host: 10.0.1.20\n    port: 20161\n    status_port: 20181\n    config:\n      server.labels: { host: tikv1, rack: rack1 }\n  - host: 10.0.1.20\n    port: 20162\n    status_port: 20182\n    config:\n      server.labels: { host: tikv2, rack: rack1 }\n  - host: 10.0.1.20\n    port: 20163\n    status_port: 20183\n    config:\n      server.labels: { host: tikv2, rack: rack1 }\n  - host: 10.0.1.20\n    port: 20164\n    status_port: 20184\n    config:\n      server.labels: { host: tikv3, rack: rack2 }\n  - host: 10.0.1.20\n    port: 20165\n    status_port: 20185\n    config:\n      server.labels: { host: tikv3, rack: rack2 }\n\ntidb_servers:\n  - host: 10.0.1.20\n\ngrafana_servers:\n  - host: 10.0.1.20\n\nmonitoring_servers:\n  - host: 10.0.1.20\n"
  },
  {
    "path": "etc/manualTestEnv/singleHost/README.md",
    "content": "# singleHost\n\nTiDB, PD, TiKV, TiFlash in the same host.\n\n## Usage\n\n1. Start the box:\n\n   ```bash\n   vagrant up\n   ```\n\n1. Use [TiUP](https://tiup.io/) to deploy the cluster to the box (only need to do it once):\n\n   ```bash\n   tiup cluster deploy singleHost v4.0.8 topology.yaml -i ../_shared/vagrant_key -y --user vagrant\n   ```\n\n1. Start the cluster in the box:\n\n   ```bash\n   tiup cluster start singleHost\n   ```\n\n1. Start TiDB Dashboard server:\n\n   ```bash\n   bin/tidb-dashboard --pd http://10.0.1.2:2379\n   ```\n\n## Cleanup\n\n```bash\ntiup cluster destroy singleHost -y\nvagrant destroy --force\n```\n"
  },
  {
    "path": "etc/manualTestEnv/singleHost/Vagrantfile",
    "content": "load \"#{File.dirname(__FILE__)}/../_shared/Vagrantfile.partial.pubKey.rb\"\n\nVagrant.configure(\"2\") do |config|\n  config.vm.provider \"virtualbox\" do |v|\n    v.memory = 3 * 1024\n    v.cpus = 2\n  end\n\n  config.vm.network \"private_network\", ip: \"10.0.1.2\"\nend\n"
  },
  {
    "path": "etc/manualTestEnv/singleHost/topology.yaml",
    "content": "global:\n  user: tidb\n  deploy_dir: tidb-deploy\n  data_dir: tidb-data\n\nserver_configs:\n  tikv:\n    server.grpc-concurrency: 1\n    raftstore.apply-pool-size: 1\n    raftstore.store-pool-size: 1\n    readpool.unified.max-thread-count: 1\n    readpool.storage.use-unified-pool: false\n    readpool.coprocessor.use-unified-pool: true\n    storage.block-cache.capacity: 256MB\n  pd:\n    replication.enable-placement-rules: true\n\npd_servers:\n  - host: 10.0.1.2\n\ntikv_servers:\n  - host: 10.0.1.2\n\ntidb_servers:\n  - host: 10.0.1.2\n\ntiflash_servers:\n  - host: 10.0.1.2\n\ngrafana_servers:\n  - host: 10.0.1.2\n\nmonitoring_servers:\n  - host: 10.0.1.2\n\nalertmanager_servers:\n  - host: 10.0.1.2\n"
  },
  {
    "path": "etc/manualTestEnv/singleHostMultiDisk/.gitignore",
    "content": "data/\n"
  },
  {
    "path": "etc/manualTestEnv/singleHostMultiDisk/README.md",
    "content": "# singleHostMultiDisk\n\nAll instances in a single host, but on different disks.\n\n## Usage\n\n1. Start the box:\n\n   ```bash\n   vagrant up\n   ```\n\n1. Use [TiUP](https://tiup.io/) to deploy the cluster to the box (only need to do it once):\n\n   ```bash\n   tiup cluster deploy singleHostMultiDisk v4.0.8 topology.yaml -i ../_shared/vagrant_key -y --user vagrant\n   ```\n\n1. Start the cluster in the box:\n\n   ```bash\n   tiup cluster start singleHostMultiDisk\n   ```\n\n1. Start TiDB Dashboard server:\n\n   ```bash\n   bin/tidb-dashboard --pd http://10.0.1.3:2379\n   ```\n\n## Cleanup\n\n```bash\ntiup cluster destroy singleHostMultiDisk -y\nvagrant destroy --force\n```\n"
  },
  {
    "path": "etc/manualTestEnv/singleHostMultiDisk/Vagrantfile",
    "content": "load \"#{File.dirname(__FILE__)}/../_shared/Vagrantfile.partial.pubKey.rb\"\n\nVagrant.configure(\"2\") do |config|\n  config.vm.provider \"virtualbox\" do |v|\n    v.memory = 3 * 1024\n    v.cpus = 2\n  end\n\n  config.vm.network \"private_network\", ip: \"10.0.1.3\"\nend\n"
  },
  {
    "path": "etc/manualTestEnv/singleHostMultiDisk/topology.yaml",
    "content": "global:\n  user: vagrant\n  deploy_dir: tidb-deploy\n  data_dir: tidb-data\n\nserver_configs:\n  tikv:\n    server.grpc-concurrency: 1\n    raftstore.apply-pool-size: 1\n    raftstore.store-pool-size: 1\n    readpool.unified.max-thread-count: 1\n    readpool.storage.use-unified-pool: false\n    readpool.coprocessor.use-unified-pool: true\n    storage.block-cache.capacity: 256MB\n  pd:\n    replication.enable-placement-rules: true\n\npd_servers:\n  - host: 10.0.1.3\n\ntikv_servers:\n  - host: 10.0.1.3\n\ntidb_servers:\n  - host: 10.0.1.3\n    deploy_dir: /vagrant/data/tidb\n    log_dir: /vagrant/data/tidb/log\n\ntiflash_servers:\n  - host: 10.0.1.3\n\ngrafana_servers:\n  - host: 10.0.1.3\n\nmonitoring_servers:\n  - host: 10.0.1.3\n\nalertmanager_servers:\n  - host: 10.0.1.3\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/pingcap/tidb-dashboard\n\ngo 1.25.7\n\nrequire (\n\tgithub.com/DATA-DOG/go-sqlmock v1.5.0\n\tgithub.com/Masterminds/semver v1.5.0\n\tgithub.com/ReneKroon/ttlcache/v2 v2.3.0\n\tgithub.com/VividCortex/mysqlerr v1.0.0\n\tgithub.com/Xeoncross/go-aesctr-with-hmac v0.0.0-20200623134604-12b17a7ff502\n\tgithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751\n\tgithub.com/antonmedv/expr v1.9.0\n\tgithub.com/appleboy/gin-jwt/v2 v2.10.3\n\tgithub.com/bitly/go-simplejson v0.5.0\n\tgithub.com/cenkalti/backoff/v4 v4.2.1\n\tgithub.com/fatih/structtag v1.2.0\n\tgithub.com/gin-contrib/gzip v0.0.1\n\tgithub.com/gin-gonic/gin v1.11.0\n\tgithub.com/go-resty/resty/v2 v2.6.0\n\tgithub.com/go-sql-driver/mysql v1.7.0\n\tgithub.com/goccy/go-graphviz v0.0.9\n\tgithub.com/golang-jwt/jwt/v4 v4.5.2\n\tgithub.com/golang/snappy v0.0.4\n\tgithub.com/google/pprof v0.0.0-20211122183932-1daafda22083\n\tgithub.com/google/uuid v1.6.0\n\tgithub.com/gtank/cryptopasta v0.0.0-20170601214702-1f550f6f2f69\n\tgithub.com/henrylee2cn/ameda v1.4.10\n\tgithub.com/jarcoal/httpmock v1.0.8\n\tgithub.com/joho/godotenv v1.4.0\n\tgithub.com/joomcode/errorx v1.0.1\n\tgithub.com/json-iterator/go v1.1.12\n\tgithub.com/minio/sio v0.3.0\n\tgithub.com/oleiade/reflections v1.0.1\n\tgithub.com/pingcap/check v0.0.0-20191216031241-8a5a85928f12\n\tgithub.com/pingcap/errors v0.11.5-0.20200917111840-a15ef68f753d\n\tgithub.com/pingcap/kvproto v0.0.0-20200411081810-b85805c9476c\n\tgithub.com/pingcap/log v0.0.0-20210906054005-afc726e70354\n\tgithub.com/pingcap/tipb v0.0.0-20220718022156-3e2483c20a9e\n\tgithub.com/rs/cors v1.7.0\n\tgithub.com/samber/lo v1.37.0\n\tgithub.com/shhdgit/testfixtures/v3 v3.6.2-0.20211219171712-c4f264d673d3\n\tgithub.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0\n\tgithub.com/spf13/pflag v1.0.5\n\tgithub.com/stretchr/testify v1.11.1\n\tgithub.com/swaggo/http-swagger v1.2.6\n\tgithub.com/swaggo/swag v1.7.9\n\tgithub.com/vmihailenco/msgpack/v5 v5.3.5\n\tgo.etcd.io/etcd/client/pkg/v3 v3.5.15\n\tgo.etcd.io/etcd/client/v3 v3.5.15\n\tgo.uber.org/atomic v1.9.0\n\tgo.uber.org/fx v1.12.0\n\tgo.uber.org/goleak v1.1.10\n\tgo.uber.org/zap v1.19.0\n\tgolang.org/x/oauth2 v0.30.0\n\tgolang.org/x/sync v0.18.0\n\tgoogle.golang.org/grpc v1.75.1\n\tgoogle.golang.org/protobuf v1.36.10\n\tgorm.io/datatypes v1.1.0\n\tgorm.io/driver/mysql v1.4.5\n\tgorm.io/driver/sqlite v1.5.7\n\tgorm.io/gorm v1.25.12\n\tmoul.io/zapgorm2 v1.1.0\n)\n\nrequire (\n\tgithub.com/KyleBanks/depth v1.2.1 // indirect\n\tgithub.com/PuerkitoBio/purell v1.1.1 // indirect\n\tgithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect\n\tgithub.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect\n\tgithub.com/bytedance/gopkg v0.1.3 // indirect\n\tgithub.com/bytedance/sonic v1.14.1 // indirect\n\tgithub.com/bytedance/sonic/loader v0.3.0 // indirect\n\tgithub.com/cloudwego/base64x v0.1.6 // indirect\n\tgithub.com/coreos/go-semver v0.3.0 // indirect\n\tgithub.com/coreos/go-systemd/v22 v22.3.2 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/fogleman/gg v1.3.0 // indirect\n\tgithub.com/gabriel-vasile/mimetype v1.4.10 // indirect\n\tgithub.com/gin-contrib/sse v1.1.0 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.19.5 // indirect\n\tgithub.com/go-openapi/jsonreference v0.19.6 // indirect\n\tgithub.com/go-openapi/spec v0.20.4 // indirect\n\tgithub.com/go-openapi/swag v0.19.15 // indirect\n\tgithub.com/go-playground/locales v0.14.1 // indirect\n\tgithub.com/go-playground/universal-translator v0.18.1 // indirect\n\tgithub.com/go-playground/validator/v10 v10.28.0 // indirect\n\tgithub.com/goccy/go-json v0.10.5 // indirect\n\tgithub.com/goccy/go-yaml v1.18.0 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect\n\tgithub.com/golang/protobuf v1.5.4 // indirect\n\tgithub.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d // indirect\n\tgithub.com/jinzhu/inflection v1.0.0 // indirect\n\tgithub.com/jinzhu/now v1.1.5 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.3.0 // indirect\n\tgithub.com/leodido/go-urn v1.4.0 // indirect\n\tgithub.com/mailru/easyjson v0.7.6 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/mattn/go-sqlite3 v1.14.24 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.4 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/quic-go/qpack v0.5.1 // indirect\n\tgithub.com/quic-go/quic-go v0.55.0 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgithub.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 // indirect\n\tgithub.com/twitchyliquid64/golang-asm v0.15.1 // indirect\n\tgithub.com/ugorji/go/codec v1.3.0 // indirect\n\tgithub.com/vmihailenco/tagparser/v2 v2.0.0 // indirect\n\tgithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect\n\tgo.etcd.io/etcd/api/v3 v3.5.15 // indirect\n\tgo.uber.org/dig v1.9.0 // indirect\n\tgo.uber.org/multierr v1.7.0 // indirect\n\tgolang.org/x/arch v0.22.0 // indirect\n\tgolang.org/x/crypto v0.45.0 // indirect\n\tgolang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect\n\tgolang.org/x/image v0.18.0 // indirect\n\tgolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect\n\tgolang.org/x/mod v0.29.0 // indirect\n\tgolang.org/x/net v0.47.0 // indirect\n\tgolang.org/x/sys v0.38.0 // indirect\n\tgolang.org/x/text v0.31.0 // indirect\n\tgolang.org/x/tools v0.38.0 // indirect\n\tgolang.org/x/tools/godoc v0.1.0-deprecated // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect\n\tgopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=\ngithub.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=\ngithub.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=\ngithub.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=\ngithub.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=\ngithub.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=\ngithub.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=\ngithub.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=\ngithub.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/ReneKroon/ttlcache/v2 v2.3.0 h1:qZnUjRKIrbKHH6vF5T7Y9Izn5ObfTZfyYpGhvz2BKPo=\ngithub.com/ReneKroon/ttlcache/v2 v2.3.0/go.mod h1:zbo6Pv/28e21Z8CzzqgYRArQYGYtjONRxaAKGxzQvG4=\ngithub.com/VividCortex/mysqlerr v1.0.0 h1:5pZ2TZA+YnzPgzBfiUWGqWmKDVNBdrkf9g+DNe1Tiq8=\ngithub.com/VividCortex/mysqlerr v1.0.0/go.mod h1:xERx8E4tBhLvpjzdUyQiSfUxeMcATEQrflDAfXsqcAE=\ngithub.com/Xeoncross/go-aesctr-with-hmac v0.0.0-20200623134604-12b17a7ff502 h1:L8IbaI/W6h5Cwgh0n4zGeZpVK78r/jBf9ASurHo9+/o=\ngithub.com/Xeoncross/go-aesctr-with-hmac v0.0.0-20200623134604-12b17a7ff502/go.mod h1:pmnBM9bxWSiHvC/gSWunUIyDvGn33EkP2CUjxFKtTTM=\ngithub.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=\ngithub.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=\ngithub.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=\ngithub.com/alvaroloes/enumer v1.1.2/go.mod h1:FxrjvuXoDAx9isTJrv4c+T410zFi0DtXIT0m65DJ+Wo=\ngithub.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=\ngithub.com/antonmedv/expr v1.9.0 h1:j4HI3NHEdgDnN9p6oI6Ndr0G5QryMY0FNxT4ONrFDGU=\ngithub.com/antonmedv/expr v1.9.0/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8=\ngithub.com/appleboy/gin-jwt/v2 v2.10.3 h1:KNcPC+XPRNpuoBh+j+rgs5bQxN+SwG/0tHbIqpRoBGc=\ngithub.com/appleboy/gin-jwt/v2 v2.10.3/go.mod h1:LDUaQ8mF2W6LyXIbd5wqlV2SFebuyYs4RDwqMNgpsp8=\ngithub.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4=\ngithub.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=\ngithub.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=\ngithub.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/bitly/go-simplejson v0.5.0 h1:6IH+V8/tVMab511d5bn4M7EwGXZf9Hj6i2xSwkNEM+Y=\ngithub.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=\ngithub.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=\ngithub.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=\ngithub.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=\ngithub.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=\ngithub.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=\ngithub.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=\ngithub.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=\ngithub.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=\ngithub.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=\ngithub.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=\ngithub.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=\ngithub.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=\ngithub.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=\ngithub.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=\ngithub.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=\ngithub.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=\ngithub.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/corona10/goimagehash v1.0.2 h1:pUfB0LnsJASMPGEZLj7tGY251vF+qLGqOgEP4rUs6kA=\ngithub.com/corona10/goimagehash v1.0.2/go.mod h1:/l9umBhvcHQXVtQO1V6Gp1yD20STawkhRnnX0D1bvVI=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=\ngithub.com/denisenkom/go-mssqldb v0.12.0 h1:VtrkII767ttSPNRfFekePK3sctr+joXgO58stqQbtUA=\ngithub.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU=\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/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=\ngithub.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=\ngithub.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=\ngithub.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=\ngithub.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=\ngithub.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=\ngithub.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=\ngithub.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=\ngithub.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=\ngithub.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=\ngithub.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=\ngithub.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=\ngithub.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=\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-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=\ngithub.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=\ngithub.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=\ngithub.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=\ngithub.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=\ngithub.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=\ngithub.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=\ngithub.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=\ngithub.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=\ngithub.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=\ngithub.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=\ngithub.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=\ngithub.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=\ngithub.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=\ngithub.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=\ngithub.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=\ngithub.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=\ngithub.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=\ngithub.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=\ngithub.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=\ngithub.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=\ngithub.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=\ngithub.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=\ngithub.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=\ngithub.com/goccy/go-graphviz v0.0.9 h1:s/FMMJ1Joj6La3S5ApO3Jk2cwM4LpXECC2muFx3IPQQ=\ngithub.com/goccy/go-graphviz v0.0.9/go.mod h1:wXVsXxmyMQU6TN3zGRttjNn3h+iCAS7xQFC6TlNvLhk=\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.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=\ngithub.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\ngithub.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=\ngithub.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=\ngithub.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=\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/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=\ngithub.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=\ngithub.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=\ngithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=\ngithub.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=\ngithub.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=\ngithub.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=\ngithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=\ngithub.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/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.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\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.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=\ngithub.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\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/pprof v0.0.0-20211122183932-1daafda22083 h1:c8EUapQFi+kjzedr4c6WqbwMdmB95+oDBWZ5XFHFYxY=\ngithub.com/google/pprof v0.0.0-20211122183932-1daafda22083/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=\ngithub.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=\ngithub.com/gtank/cryptopasta v0.0.0-20170601214702-1f550f6f2f69 h1:7xsUJsB2NrdcttQPa7JLEaGzvdbk7KvfrjgHZXOQRo0=\ngithub.com/gtank/cryptopasta v0.0.0-20170601214702-1f550f6f2f69/go.mod h1:YLEMZOtU+AZ7dhN9T/IpGhXVGly2bvkJQ+zxj3WeVQo=\ngithub.com/henrylee2cn/ameda v1.4.10 h1:JdvI2Ekq7tapdPsuhrc4CaFiqw6QXFvZIULWJgQyCAk=\ngithub.com/henrylee2cn/ameda v1.4.10/go.mod h1:liZulR8DgHxdK+MEwvZIylGnmcjzQ6N6f2PlWe7nEO4=\ngithub.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d h1:uGg2frlt3IcT7kbV6LEp5ONv4vmoO2FW4qSO+my/aoM=\ngithub.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w=\ngithub.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=\ngithub.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=\ngithub.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=\ngithub.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=\ngithub.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=\ngithub.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=\ngithub.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=\ngithub.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=\ngithub.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=\ngithub.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=\ngithub.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=\ngithub.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=\ngithub.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=\ngithub.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=\ngithub.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=\ngithub.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=\ngithub.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=\ngithub.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=\ngithub.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=\ngithub.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=\ngithub.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=\ngithub.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=\ngithub.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=\ngithub.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=\ngithub.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=\ngithub.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=\ngithub.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=\ngithub.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=\ngithub.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=\ngithub.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=\ngithub.com/jackc/pgtype v1.3.0/go.mod h1:b0JqxHvPmljG+HQ5IsvQ0yqeSi4nGcDTVjFoiLDb0Ik=\ngithub.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=\ngithub.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=\ngithub.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=\ngithub.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=\ngithub.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=\ngithub.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=\ngithub.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=\ngithub.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oAlxAg=\ngithub.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=\ngithub.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=\ngithub.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=\ngithub.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k=\ngithub.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=\ngithub.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=\ngithub.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=\ngithub.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=\ngithub.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=\ngithub.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=\ngithub.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=\ngithub.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=\ngithub.com/joomcode/errorx v1.0.1 h1:CalpDWz14ZHd68fIqluJasJosAewpz2TFaJALrUxjrk=\ngithub.com/joomcode/errorx v1.0.1/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=\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/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=\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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=\ngithub.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=\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/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=\ngithub.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\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.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=\ngithub.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=\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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=\ngithub.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=\ngithub.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=\ngithub.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=\ngithub.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=\ngithub.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=\ngithub.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=\ngithub.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=\ngithub.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=\ngithub.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=\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.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=\ngithub.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\ngithub.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=\ngithub.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=\ngithub.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=\ngithub.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=\ngithub.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=\ngithub.com/minio/sio v0.3.0 h1:syEFBewzOMOYVzSTFpp1MqpSZk8rUNbz8VIIc+PNzus=\ngithub.com/minio/sio v0.3.0/go.mod h1:8b0yPp2avGThviy/+OCJBI6OMpvxoUuiLvE6F1lebhw=\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.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5 h1:BvoENQQU+fZ9uukda/RzCAL/191HHwJA5b13R6diVlY=\ngithub.com/nfnt/resize v0.0.0-20160724205520-891127d8d1b5/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM=\ngithub.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60=\ngithub.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=\ngithub.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=\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.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=\ngithub.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1/go.mod h1:eD5JxqMiuNYyFNmyY9rkJ/slN8y59oEu4Ei7F8OoKWQ=\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/pingcap/check v0.0.0-20190102082844-67f458068fc8/go.mod h1:B1+S9LNcuMyLH/4HMTViQOJevkGiik3wW2AN9zb2fNQ=\ngithub.com/pingcap/check v0.0.0-20191216031241-8a5a85928f12 h1:rfD9v3+ppLPzoQBgZev0qYCpegrwyFx/BUpkApEiKdY=\ngithub.com/pingcap/check v0.0.0-20191216031241-8a5a85928f12/go.mod h1:PYMCGwN0JHjoqGr3HrZoD+b8Tgx8bKnArhSq8YVzUMc=\ngithub.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=\ngithub.com/pingcap/errors v0.11.5-0.20200917111840-a15ef68f753d h1:TH18wFO5Nq/zUQuWu9ms2urgZnLP69XJYiI2JZAkUGc=\ngithub.com/pingcap/errors v0.11.5-0.20200917111840-a15ef68f753d/go.mod h1:g4vx//d6VakjJ0mk7iLBlKA8LFavV/sAVINT/1PFxeQ=\ngithub.com/pingcap/kvproto v0.0.0-20200411081810-b85805c9476c h1:wO9VvZezAU4ZPZj8+P5uWfsT/ppuABjJPmHNrpCQnlc=\ngithub.com/pingcap/kvproto v0.0.0-20200411081810-b85805c9476c/go.mod h1:IOdRDPLyda8GX2hE/jO7gqaCV/PNFh8BZQCQZXfIOqI=\ngithub.com/pingcap/log v0.0.0-20191012051959-b742a5d432e9/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8=\ngithub.com/pingcap/log v0.0.0-20200511115504-543df19646ad/go.mod h1:4rbK1p9ILyIfb6hU7OG2CiWSqMXnp3JMbiaVJ6mvoY8=\ngithub.com/pingcap/log v0.0.0-20210906054005-afc726e70354 h1:SvWCbCPh1YeHd9yQLksvJYAgft6wLTY1aNG81tpyscQ=\ngithub.com/pingcap/log v0.0.0-20210906054005-afc726e70354/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4=\ngithub.com/pingcap/tipb v0.0.0-20220718022156-3e2483c20a9e h1:FBaTXU8C3xgt/drM58VHxojHo/QoG1oPsgWTGvaSpO4=\ngithub.com/pingcap/tipb v0.0.0-20220718022156-3e2483c20a9e/go.mod h1:A7mrd7WHBl1o63LE2bIBGEJMTNWXqhgmYiOvMLxozfs=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=\ngithub.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=\ngithub.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=\ngithub.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=\ngithub.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84=\ngithub.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=\ngithub.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=\ngithub.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=\ngithub.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=\ngithub.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=\ngithub.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=\ngithub.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=\ngithub.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=\ngithub.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4=\ngithub.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=\ngithub.com/shhdgit/testfixtures/v3 v3.6.2-0.20211219171712-c4f264d673d3 h1:qgLFG8/LS7dhYw6SF6yIx+Nfpf4Md9/oxtAYTjl9ayk=\ngithub.com/shhdgit/testfixtures/v3 v3.6.2-0.20211219171712-c4f264d673d3/go.mod h1:Z0OLtuFJ7Y4yLsVijHK8uq95NjGFlYJy+I00ElAEtUQ=\ngithub.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=\ngithub.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0 h1:mj/nMDAwTBiaCqMEs4cYCqF7pO6Np7vhy1D1wcQGz+E=\ngithub.com/shurcooL/httpgzip v0.0.0-20190720172056-320755c1c1b0/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=\ngithub.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=\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 v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM=\ngithub.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=\ngithub.com/swaggo/http-swagger v1.2.6 h1:ihTjChUoSRMpFMjWw+0AkL1Ti4r6v8pCgVYLmQVRlRw=\ngithub.com/swaggo/http-swagger v1.2.6/go.mod h1:CcoICgY3yVDk2u1LQUCMHbAj0fjlxIX+873psXlIKNA=\ngithub.com/swaggo/swag v1.7.9 h1:6vCG5mm43ebDzGlZPMGYrYI4zKFfOr5kicQX8qjeDwc=\ngithub.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU=\ngithub.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=\ngithub.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=\ngithub.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=\ngithub.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=\ngithub.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=\ngithub.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=\ngithub.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=\ngithub.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=\ngithub.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=\ngithub.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=\ngithub.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=\ngithub.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=\ngithub.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=\ngithub.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=\ngithub.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=\ngithub.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=\ngithub.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=\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.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=\ngo.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk=\ngo.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM=\ngo.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA=\ngo.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU=\ngo.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4=\ngo.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU=\ngo.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=\ngo.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=\ngo.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=\ngo.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=\ngo.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=\ngo.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=\ngo.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=\ngo.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=\ngo.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=\ngo.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=\ngo.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=\ngo.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=\ngo.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=\ngo.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=\ngo.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/dig v1.9.0 h1:pJTDXKEhRqBI8W7rU7kwT5EgyRZuSMVSFcZolOvKK9U=\ngo.uber.org/dig v1.9.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw=\ngo.uber.org/fx v1.12.0 h1:+1+3Cz9M0dFMPy9SW9XUIUHye8bnPUm7q7DroNGWYG4=\ngo.uber.org/fx v1.12.0/go.mod h1:egT3Kyg1JFYQkvKLZ3EsykxkNrZxgXS+gKoKo7abERY=\ngo.uber.org/goleak v0.10.0/go.mod h1:VCZuO8V8mFPlL0F5J5GK1rtHV3DrFcQ1R8ryq7FK0aI=\ngo.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=\ngo.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=\ngo.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=\ngo.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=\ngo.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=\ngo.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=\ngo.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=\ngo.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=\ngo.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=\ngo.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=\ngo.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=\ngo.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=\ngo.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=\ngo.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=\ngo.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE=\ngo.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=\ngolang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=\ngolang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=\ngolang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=\ngolang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=\ngolang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=\ngolang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=\ngolang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=\ngolang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=\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-20190313153728-d0100b6bd8b3/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-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=\ngolang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=\ngolang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=\ngolang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/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.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=\ngolang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=\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-20181220203305-927f97764cc3/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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20191002035440-2ec189313ef0/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-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=\ngolang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=\ngolang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=\ngolang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=\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-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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.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-20181228144115-9a3f9b0469bb/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/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-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/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-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/text v0.3.0/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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=\ngolang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/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-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190524210228-3d17549cdc6b/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=\ngolang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191030062658-86caa796c7ab/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191107010934-f79515f33823/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20191114200427-caa0b0f7d508/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-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20201125231158-b5590deeca9b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210112230658-8b4aab62c064/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=\ngolang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=\ngolang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=\ngolang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk=\ngolang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg=\ngolang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\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.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=\ngoogle.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=\ngoogle.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=\ngoogle.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=\ngoogle.golang.org/protobuf v1.36.10/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-20200227125254-8fa46927fb4f/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/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=\ngopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=\ngopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=\ngopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=\ngopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=\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.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/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.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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=\ngorm.io/datatypes v1.1.0 h1:EVp1Z28N4ACpYFK1nHboEIJGIFfjY7vLeieDk8jSHJA=\ngorm.io/datatypes v1.1.0/go.mod h1:SH2K9R+2RMjuX1CkCONrPwoe9JzVv2hkQvEu4bXGojE=\ngorm.io/driver/mysql v1.4.5 h1:u1lytId4+o9dDaNcPCFzNv7h6wvmc92UjNk3z8enSBU=\ngorm.io/driver/mysql v1.4.5/go.mod h1:SxzItlnT1cb6e1e4ZRpgJN2VYtcqJgqnHxWr4wsP8oc=\ngorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=\ngorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg=\ngorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=\ngorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=\ngorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=\ngorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=\ngorm.io/gorm v1.21.9/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=\ngorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=\ngorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=\ngorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/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=\nmoul.io/zapgorm2 v1.1.0 h1:qwAlMBYf+qJkJ7PAzJl4oCe6eS6QGiKAXUPeis0+RBE=\nmoul.io/zapgorm2 v1.1.0/go.mod h1:emRfKjNqSzVj5lcgasBdovIXY1jSOwFz2GQZn1Rddks=\n"
  },
  {
    "path": "pkg/apiserver/apiserver.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage apiserver\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net/http\"\n\t\"sync\"\n\n\t\"github.com/gin-contrib/gzip\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joho/godotenv\"\n\tcors \"github.com/rs/cors/wrapper/gin\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/clusterinfo\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/configuration\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/conprof\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/deadlock\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/debugapi\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/diagnose\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/info\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/logsearch\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/metrics\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/profiling\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/queryeditor\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/topsql\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user/code\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user/code/codeauth\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user/sqlauth\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user/sso\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user/sso/ssoauth\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/visualplan\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/scheduling\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/ticdc\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tiflash\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tiproxy\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tso\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/version\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/pdclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/schedulingclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/ticdcclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tidbclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tiflashclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tikvclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tiproxyclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tsoclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/featureflag\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n\n\t// \"github.com/pingcap/tidb-dashboard/pkg/apiserver/__APP_NAME__\"\n\t// NOTE: Don't remove above comment line, it is a placeholder for code generator.\n\tresourcemanager \"github.com/pingcap/tidb-dashboard/pkg/apiserver/resource_manager\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/slowquery\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/statement\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\tapiutils \"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/httpc\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual\"\n\tkeyvisualregion \"github.com/pingcap/tidb-dashboard/pkg/keyvisual/region\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tikv\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils\"\n)\n\nfunc Handler(s *Service) http.Handler {\n\treturn s.NewStatusAwareHandler(http.HandlerFunc(s.handler), s.stoppedHandler)\n}\n\nvar once sync.Once\n\ntype Service struct {\n\tapp    *fx.App\n\tstatus *utils.ServiceStatus\n\n\tctx    context.Context\n\tcancel context.CancelFunc\n\n\tconfig                  *config.Config\n\tcustomKeyVisualProvider *keyvisualregion.DataProvider\n\tstoppedHandler          http.Handler\n\tuiAssetFS               http.FileSystem\n\n\tapiHandlerEngine *gin.Engine\n}\n\nfunc NewService(cfg *config.Config, stoppedHandler http.Handler, uiAssetFS http.FileSystem, customKeyVisualProvider *keyvisualregion.DataProvider) *Service {\n\tonce.Do(func() {\n\t\t// These global modification will be effective only for the first invoke.\n\t\t_ = godotenv.Load()\n\t\tgin.SetMode(gin.ReleaseMode)\n\t})\n\n\treturn &Service{\n\t\tstatus:                  utils.NewServiceStatus(),\n\t\tconfig:                  cfg,\n\t\tcustomKeyVisualProvider: customKeyVisualProvider,\n\t\tstoppedHandler:          stoppedHandler,\n\t\tuiAssetFS:               uiAssetFS,\n\t}\n}\n\nfunc (s *Service) IsRunning() bool {\n\treturn s.status.IsRunning()\n}\n\nvar Modules = fx.Options(\n\tfx.Provide(\n\t\tnewAPIHandlerEngine,\n\t\tnewClients,\n\t\tdbstore.NewDBStore,\n\t\thttpc.NewHTTPClient,\n\t\tpd.NewEtcdClient,\n\t\tpd.NewPDClient,\n\t\ttso.NewTSOClient,\n\t\tscheduling.NewSchedulingClient,\n\t\tconfig.NewDynamicConfigManager,\n\t\ttidb.NewTiDBClient,\n\t\ttikv.NewTiKVClient,\n\t\ttiflash.NewTiFlashClient,\n\t\tticdc.NewTiCDCClient,\n\t\ttiproxy.NewTiProxyClient,\n\t\tutils.ProvideSysSchema,\n\t\tapiutils.NewNgmProxy,\n\t\tinfo.NewService,\n\t\tclusterinfo.NewService,\n\t\tlogsearch.NewService,\n\t\tdiagnose.NewService,\n\t\tkeyvisual.NewService,\n\t\tmetrics.NewService,\n\t\tqueryeditor.NewService,\n\t\tconfiguration.NewService,\n\t\t// __APP_NAME__.NewService,\n\t\t// NOTE: Don't remove above comment line, it is a placeholder for code generator\n\t),\n\tuser.Module,\n\tcodeauth.Module,\n\tsqlauth.Module,\n\tssoauth.Module,\n\tcode.Module,\n\tsso.Module,\n\tprofiling.Module,\n\tconprof.Module,\n\tstatement.Module,\n\tslowquery.Module,\n\tdebugapi.Module,\n\ttopsql.Module,\n\tvisualplan.Module,\n\tdeadlock.Module,\n\tresourcemanager.Module,\n)\n\nfunc (s *Service) Start(ctx context.Context) error {\n\tif s.IsRunning() {\n\t\treturn nil\n\t}\n\n\ts.ctx, s.cancel = context.WithCancel(ctx)\n\n\ts.app = fx.New(\n\t\tfx.Logger(utils.NewFxPrinter()),\n\t\tfx.Supply(featureflag.NewRegistry(s.config.FeatureVersion)),\n\t\tModules,\n\t\tfx.Provide(\n\t\t\ts.provideLocals,\n\t\t),\n\t\tfx.Populate(&s.apiHandlerEngine),\n\t\tfx.Invoke(\n\t\t\tinfo.RegisterRouter,\n\t\t\tclusterinfo.RegisterRouter,\n\t\t\tprofiling.RegisterRouter,\n\t\t\tlogsearch.RegisterRouter,\n\t\t\tdiagnose.RegisterRouter,\n\t\t\tkeyvisual.RegisterRouter,\n\t\t\tmetrics.RegisterRouter,\n\t\t\tqueryeditor.RegisterRouter,\n\t\t\tconfiguration.RegisterRouter,\n\t\t\t// __APP_NAME__.RegisterRouter,\n\t\t\t// NOTE: Don't remove above comment line, it is a placeholder for code generator\n\t\t\t// Must be at the end\n\t\t\ts.status.Register,\n\t\t),\n\t)\n\n\tif err := s.app.Start(s.ctx); err != nil {\n\t\ts.cleanAfterError()\n\t\treturn err\n\t}\n\n\tversion.Print()\n\n\treturn nil\n}\n\n// TODO: Find a better place to put these client bundles.\nfunc newClients(lc fx.Lifecycle, config *config.Config) (\n\tdbClient *tidbclient.StatusClient,\n\tkvClient *tikvclient.StatusClient,\n\tcsClient *tiflashclient.StatusClient,\n\tpdClient *pdclient.APIClient,\n\tticdcClient *ticdcclient.StatusClient,\n\ttiproxyClient *tiproxyclient.StatusClient,\n\ttsoClient *tsoclient.StatusClient,\n\tschedulingClient *schedulingclient.StatusClient,\n) {\n\thttpConfig := httpclient.Config{\n\t\tTLSConfig: config.ClusterTLSConfig,\n\t}\n\tdbClient = tidbclient.NewStatusClient(httpConfig)\n\tkvClient = tikvclient.NewStatusClient(httpConfig)\n\tcsClient = tiflashclient.NewStatusClient(httpConfig)\n\tpdClient = pdclient.NewAPIClient(httpConfig)\n\tticdcClient = ticdcclient.NewStatusClient(httpConfig)\n\ttiproxyClient = tiproxyclient.NewStatusClient(httpConfig)\n\ttsoClient = tsoclient.NewStatusClient(httpConfig)\n\tschedulingClient = schedulingclient.NewStatusClient(httpConfig)\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tdbClient.SetDefaultCtx(ctx)\n\t\t\tkvClient.SetDefaultCtx(ctx)\n\t\t\tcsClient.SetDefaultCtx(ctx)\n\t\t\tpdClient.SetDefaultCtx(ctx)\n\t\t\treturn nil\n\t\t},\n\t})\n\treturn\n}\n\nfunc (s *Service) cleanAfterError() {\n\ts.cancel()\n\n\t// drop\n\ts.app = nil\n\ts.apiHandlerEngine = nil\n\ts.ctx = nil\n\ts.cancel = nil\n}\n\nfunc (s *Service) Stop(ctx context.Context) error {\n\tif !s.IsRunning() || s.app == nil {\n\t\treturn nil\n\t}\n\n\ts.cancel()\n\terr := s.app.Stop(ctx)\n\n\t// drop\n\ts.app = nil\n\ts.apiHandlerEngine = nil\n\ts.ctx = nil\n\ts.cancel = nil\n\n\treturn err\n}\n\nfunc (s *Service) NewStatusAwareHandler(handler http.Handler, stoppedHandler http.Handler) http.Handler {\n\treturn s.status.NewStatusAwareHandler(handler, stoppedHandler)\n}\n\nfunc (s *Service) handler(w http.ResponseWriter, r *http.Request) {\n\ts.apiHandlerEngine.ServeHTTP(w, r)\n}\n\nfunc (s *Service) provideLocals() (*config.Config, http.FileSystem, *keyvisualregion.DataProvider) {\n\treturn s.config, s.uiAssetFS, s.customKeyVisualProvider\n}\n\nfunc newAPIHandlerEngine() (apiHandlerEngine *gin.Engine, endpoint *gin.RouterGroup) {\n\tapiHandlerEngine = gin.New()\n\tapiHandlerEngine.Use(gin.Recovery())\n\tapiHandlerEngine.Use(cors.AllowAll())\n\tapiHandlerEngine.Use(gzip.Gzip(gzip.DefaultCompression))\n\tapiHandlerEngine.Use(rest.ErrorHandlerFn())\n\n\tendpoint = apiHandlerEngine.Group(\"/dashboard/api\")\n\n\treturn\n}\n\nvar StoppedHandler = http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\tw.WriteHeader(http.StatusNotFound)\n\t_, _ = io.WriteString(w, \"Dashboard is not started.\\n\")\n})\n"
  },
  {
    "path": "pkg/apiserver/clusterinfo/host.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage clusterinfo\n\nimport (\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/pingcap/log\"\n\t\"github.com/samber/lo\"\n\t\"go.uber.org/zap\"\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/clusterinfo/hostinfo\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/topology\"\n)\n\n// fetchAllInstanceHosts fetches all hosts in the cluster and return in ascending order.\nfunc (s *Service) fetchAllInstanceHosts() ([]string, error) {\n\tallHostsMap := make(map[string]struct{})\n\tpdInfo, err := topology.FetchPDTopology(s.params.PDClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, i := range pdInfo {\n\t\tallHostsMap[i.IP] = struct{}{}\n\t}\n\n\ttikvInfo, tiFlashInfo, err := topology.FetchStoreTopology(s.params.PDClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, i := range tikvInfo {\n\t\tallHostsMap[i.IP] = struct{}{}\n\t}\n\tfor _, i := range tiFlashInfo {\n\t\tallHostsMap[i.IP] = struct{}{}\n\t}\n\n\ttidbInfo, err := topology.FetchTiDBTopology(s.lifecycleCtx, s.params.EtcdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, i := range tidbInfo {\n\t\tallHostsMap[i.IP] = struct{}{}\n\t}\n\n\tticdcInfo, err := topology.FetchTiCDCTopology(s.lifecycleCtx, s.params.EtcdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, i := range ticdcInfo {\n\t\tallHostsMap[i.IP] = struct{}{}\n\t}\n\n\ttiproxyInfo, err := topology.FetchTiProxyTopology(s.lifecycleCtx, s.params.EtcdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, i := range tiproxyInfo {\n\t\tallHostsMap[i.IP] = struct{}{}\n\t}\n\n\ttsoInfo, err := topology.FetchTSOTopology(s.lifecycleCtx, s.params.PDClient)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"status code 404\") {\n\t\t\ttsoInfo = []topology.TSOInfo{}\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tfor _, i := range tsoInfo {\n\t\tallHostsMap[i.IP] = struct{}{}\n\t}\n\n\tschedulingInfo, err := topology.FetchSchedulingTopology(s.lifecycleCtx, s.params.PDClient)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"status code 404\") {\n\t\t\tschedulingInfo = []topology.SchedulingInfo{}\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tfor _, i := range schedulingInfo {\n\t\tallHostsMap[i.IP] = struct{}{}\n\t}\n\n\tallHosts := lo.Keys(allHostsMap)\n\tsort.Strings(allHosts)\n\n\treturn allHosts, nil\n}\n\n// fetchAllHostsInfo fetches all hosts and their information.\n// Note: The returned data and error may both exist.\nfunc (s *Service) fetchAllHostsInfo(db *gorm.DB) ([]*hostinfo.Info, error) {\n\tallHosts, err := s.fetchAllInstanceHosts()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tallHostsInfoMap := make(map[string]*hostinfo.Info)\n\tif e := hostinfo.FillFromClusterLoadTable(db, allHostsInfoMap); e != nil {\n\t\tlog.Warn(\"Failed to read cluster_load table\", zap.Error(e))\n\t\terr = e\n\t}\n\tif e := hostinfo.FillFromClusterHardwareTable(db, allHostsInfoMap); e != nil && err == nil {\n\t\tlog.Warn(\"Failed to read cluster_hardware table\", zap.Error(e))\n\t\terr = e\n\t}\n\tif e := hostinfo.FillInstances(db, allHostsInfoMap); e != nil && err == nil {\n\t\tlog.Warn(\"Failed to fill instances for hosts\", zap.Error(e))\n\t\terr = e\n\t}\n\n\tr := make([]*hostinfo.Info, 0, len(allHosts))\n\tfor _, host := range allHosts {\n\t\tif im, ok := allHostsInfoMap[host]; ok {\n\t\t\tr = append(r, im)\n\t\t} else {\n\t\t\t// Missing item\n\t\t\tr = append(r, hostinfo.NewHostInfo(host))\n\t\t}\n\t}\n\treturn r, err\n}\n"
  },
  {
    "path": "pkg/apiserver/clusterinfo/hostinfo/cluster_config.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage hostinfo\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/netutil\"\n)\n\ntype clusterConfigModel struct {\n\tType     string `gorm:\"column:TYPE\"`\n\tInstance string `gorm:\"column:INSTANCE\"`\n\tKey      string `gorm:\"column:KEY\"`\n\tValue    string `gorm:\"column:VALUE\"`\n}\n\nfunc FillInstances(db *gorm.DB, m InfoMap) error {\n\tvar rows []clusterConfigModel\n\tif err := db.\n\t\tTable(\"INFORMATION_SCHEMA.CLUSTER_CONFIG\").\n\t\tWhere(\"(`TYPE` = 'tidb' AND `KEY` = 'log.file.filename') \" +\n\t\t\t\"OR (`TYPE` = 'tikv' AND `KEY` = 'storage.data-dir') \" +\n\t\t\t\"OR (`TYPE` = 'pd' AND `KEY` = 'data-dir') \" +\n\t\t\t\"OR (`TYPE` = 'ticdc' AND `KEY` = 'data-dir')\" +\n\t\t\t\"OR (`TYPE` = 'tiflash' AND (`KEY` = 'engine-store.path' \" +\n\t\t\t\"    OR `KEY` = 'engine-store.storage.main.dir' \" +\n\t\t\t\"    OR `KEY` = 'engine-store.storage.latest.dir'))\").\n\t\tFind(&rows).Error; err != nil {\n\t\treturn err\n\t}\n\n\tfor _, row := range rows {\n\t\thostname, _, err := netutil.ParseHostAndPortFromAddress(row.Instance)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := m[hostname]; !ok {\n\t\t\tm[hostname] = NewHostInfo(hostname)\n\t\t}\n\t\tswitch row.Type {\n\t\tcase \"tiflash\":\n\t\t\tif ins, ok := m[hostname].Instances[row.Instance]; ok {\n\t\t\t\tif ins.Type == row.Type && ins.PartitionPathL != \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tm[hostname].Instances[row.Instance] = &InstanceInfo{\n\t\t\t\t\tType:           row.Type,\n\t\t\t\t\tPartitionPathL: \"\",\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar paths []string\n\t\t\tswitch row.Key {\n\t\t\tcase \"engine-store.path\":\n\t\t\t\titems := strings.Split(row.Value, \",\")\n\t\t\t\tfor _, path := range items {\n\t\t\t\t\tpaths = append(paths, strings.TrimSpace(path))\n\t\t\t\t}\n\t\t\tcase \"engine-store.storage.main.dir\", \"engine-store.storage.latest.dir\":\n\t\t\t\tif err := json.Unmarshal([]byte(row.Value), &paths); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tpaths = []string{row.Value}\n\t\t\t}\n\t\t\tfor _, path := range paths {\n\t\t\t\tmountDir := locateInstanceMountPartition(path, m[hostname].Partitions)\n\t\t\t\tif mountDir != \"\" {\n\t\t\t\t\tm[hostname].Instances[row.Instance].PartitionPathL = strings.ToLower(mountDir)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\tdefault:\n\t\t\tm[hostname].Instances[row.Instance] = &InstanceInfo{\n\t\t\t\tType:           row.Type,\n\t\t\t\tPartitionPathL: strings.ToLower(locateInstanceMountPartition(row.Value, m[hostname].Partitions)),\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// Try to discover which partition this instance is running on.\n// If discover failed, empty string will be returned.\nfunc locateInstanceMountPartition(directoryOrFilePath string, partitions map[string]*PartitionInfo) string {\n\tif len(directoryOrFilePath) == 0 {\n\t\treturn \"\"\n\t}\n\n\tmaxMatchLen := 0\n\tmaxMatchPath := \"\"\n\n\tdirectoryOrFilePathL := strings.ToLower(directoryOrFilePath)\n\n\tfor _, info := range partitions {\n\t\t// FIXME: This may cause wrong result in case sensitive FS.\n\t\tif !strings.HasPrefix(directoryOrFilePathL, strings.ToLower(info.Path)) {\n\t\t\tcontinue\n\t\t}\n\t\tif len(info.Path) > maxMatchLen {\n\t\t\tmaxMatchLen = len(info.Path)\n\t\t\tmaxMatchPath = info.Path\n\t\t}\n\t}\n\n\treturn maxMatchPath\n}\n"
  },
  {
    "path": "pkg/apiserver/clusterinfo/hostinfo/cluster_hardware.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage hostinfo\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"strings\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/netutil\"\n)\n\n// Used to deserialize from JSON_VALUE.\ntype clusterHardwareCPUInfoModel struct {\n\tArch          string `json:\"cpu-arch\"`\n\tLogicalCores  int    `json:\"cpu-logical-cores,string\"`\n\tPhysicalCores int    `json:\"cpu-physical-cores,string\"`\n}\n\n// Used to deserialize from JSON_VALUE.\ntype clusterHardwareDiskModel struct {\n\tPath   string `json:\"path\"`\n\tFSType string `json:\"fstype\"`\n\tFree   int    `json:\"free,string\"`\n\tTotal  int    `json:\"total,string\"`\n}\n\nfunc FillFromClusterHardwareTable(db *gorm.DB, m InfoMap) error {\n\tvar rows []clusterTableModel\n\n\tvar sqlQuery bytes.Buffer\n\tif err := clusterTableQueryTemplate.Execute(&sqlQuery, map[string]string{\n\t\t\"tableName\": \"INFORMATION_SCHEMA.CLUSTER_HARDWARE\",\n\t}); err != nil {\n\t\tpanic(err)\n\t}\n\n\tif err := db.\n\t\tRaw(sqlQuery.String(), []string{\"cpu\", \"disk\"}).\n\t\tScan(&rows).Error; err != nil {\n\t\treturn err\n\t}\n\n\tfor _, row := range rows {\n\t\thostname, _, err := netutil.ParseHostAndPortFromAddress(row.Instance)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := m[hostname]; !ok {\n\t\t\tm[hostname] = NewHostInfo(hostname)\n\t\t}\n\n\t\tswitch {\n\t\tcase row.DeviceType == \"cpu\" && row.DeviceName == \"cpu\":\n\t\t\tif m[hostname].CPUInfo != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar v clusterHardwareCPUInfoModel\n\t\t\terr := json.Unmarshal([]byte(row.JSONValue), &v)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tm[hostname].CPUInfo = &CPUInfo{\n\t\t\t\tArch:          v.Arch,\n\t\t\t\tLogicalCores:  v.LogicalCores,\n\t\t\t\tPhysicalCores: v.PhysicalCores,\n\t\t\t}\n\t\tcase row.DeviceType == \"disk\":\n\t\t\tif m[hostname].PartitionProviderType != \"\" && m[hostname].PartitionProviderType != row.Type {\n\t\t\t\t// Another instance on the same host has already provided disk information, skip.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar v clusterHardwareDiskModel\n\t\t\terr := json.Unmarshal([]byte(row.JSONValue), &v)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif m[hostname].PartitionProviderType == \"\" {\n\t\t\t\tm[hostname].PartitionProviderType = row.Type\n\t\t\t}\n\t\t\tm[hostname].Partitions[strings.ToLower(v.Path)] = &PartitionInfo{\n\t\t\t\tPath:   v.Path,\n\t\t\t\tFSType: v.FSType,\n\t\t\t\tFree:   v.Free,\n\t\t\t\tTotal:  v.Total,\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/apiserver/clusterinfo/hostinfo/cluster_load.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage hostinfo\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/netutil\"\n)\n\n// Used to deserialize from JSON_VALUE.\ntype clusterLoadCPUUsageModel struct {\n\tIdle   float64 `json:\"idle,string\"`\n\tSystem float64 `json:\"system,string\"`\n}\n\n// Used to deserialize from JSON_VALUE.\ntype clusterLoadMemoryVirtualModel struct {\n\tUsed  int `json:\"used,string\"`\n\tTotal int `json:\"total,string\"`\n}\n\nfunc FillFromClusterLoadTable(db *gorm.DB, m InfoMap) error {\n\tvar rows []clusterTableModel\n\n\tvar sqlQuery bytes.Buffer\n\tif err := clusterTableQueryTemplate.Execute(&sqlQuery, map[string]string{\n\t\t\"tableName\": \"INFORMATION_SCHEMA.CLUSTER_LOAD\",\n\t}); err != nil {\n\t\tpanic(err)\n\t}\n\n\tif err := db.\n\t\tRaw(sqlQuery.String(), []string{\"memory\", \"cpu\"}).\n\t\tScan(&rows).Error; err != nil {\n\t\treturn err\n\t}\n\n\tfor _, row := range rows {\n\t\thostname, _, err := netutil.ParseHostAndPortFromAddress(row.Instance)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif _, ok := m[hostname]; !ok {\n\t\t\tm[hostname] = NewHostInfo(hostname)\n\t\t}\n\n\t\tswitch {\n\t\tcase row.DeviceType == \"memory\" && row.DeviceName == \"virtual\":\n\t\t\tif m[hostname].MemoryUsage != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar v clusterLoadMemoryVirtualModel\n\t\t\terr := json.Unmarshal([]byte(row.JSONValue), &v)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tm[hostname].MemoryUsage = &MemoryUsageInfo{\n\t\t\t\tUsed:  v.Used,\n\t\t\t\tTotal: v.Total,\n\t\t\t}\n\t\tcase row.DeviceType == \"cpu\" && row.DeviceName == \"usage\":\n\t\t\tif m[hostname].CPUUsage != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tvar v clusterLoadCPUUsageModel\n\t\t\terr := json.Unmarshal([]byte(row.JSONValue), &v)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tm[hostname].CPUUsage = &CPUUsageInfo{\n\t\t\t\tIdle:   v.Idle,\n\t\t\t\tSystem: v.System,\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/apiserver/clusterinfo/hostinfo/hostinfo.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage hostinfo\n\nimport \"text/template\"\n\ntype CPUUsageInfo struct {\n\tIdle   float64 `json:\"idle\"`\n\tSystem float64 `json:\"system\"`\n}\n\ntype MemoryUsageInfo struct {\n\tUsed  int `json:\"used\"`\n\tTotal int `json:\"total\"`\n}\n\ntype CPUInfo struct {\n\tArch          string `json:\"arch\"`\n\tLogicalCores  int    `json:\"logical_cores\"`\n\tPhysicalCores int    `json:\"physical_cores\"`\n}\n\ntype PartitionInfo struct {\n\tPath   string `json:\"path\"`\n\tFSType string `json:\"fstype\"`\n\tFree   int    `json:\"free\"`\n\tTotal  int    `json:\"total\"`\n}\n\ntype InstanceInfo struct {\n\tType           string `json:\"type\"`\n\tPartitionPathL string `json:\"partition_path_lower\"`\n}\n\ntype Info struct {\n\tHost        string           `json:\"host\"`\n\tCPUInfo     *CPUInfo         `json:\"cpu_info\"`\n\tCPUUsage    *CPUUsageInfo    `json:\"cpu_usage\"`\n\tMemoryUsage *MemoryUsageInfo `json:\"memory_usage\"`\n\n\t// Containing unused partitions. The key is path in lower case.\n\t// Note: deviceName is not used as the key, since TiDB and TiKV may return different deviceName for the same device.\n\tPartitions map[string]*PartitionInfo `json:\"partitions\"`\n\t// The source instance type that provides the partition info.\n\tPartitionProviderType string `json:\"-\"`\n\n\t// Instances in the current host. The key is instance address\n\tInstances map[string]*InstanceInfo `json:\"instances\"`\n}\n\ntype InfoMap = map[string]*Info\n\nvar clusterTableQueryTemplate = template.Must(template.New(\"\").Parse(`\nSELECT\n\t*,\n\tFIELD(LOWER(A.TYPE), 'tiflash', 'tikv', 'pd', 'tidb', 'tiproxy', 'tso', 'scheduling') AS _ORDER\nFROM (\n\tSELECT\n\t\tTYPE, INSTANCE, DEVICE_TYPE, DEVICE_NAME, JSON_OBJECTAGG(NAME, VALUE) AS JSON_VALUE\n\tFROM\n\t\t{{.tableName}}\n\tWHERE\n\t\tDEVICE_TYPE IN (?)\n\tGROUP BY TYPE, INSTANCE, DEVICE_TYPE, DEVICE_NAME\n) AS A\nORDER BY\n\t_ORDER DESC, INSTANCE, DEVICE_TYPE, DEVICE_NAME\n`))\n\ntype clusterTableModel struct {\n\tType       string `gorm:\"column:TYPE\"`        // Example: tidb, tikv\n\tInstance   string `gorm:\"column:INSTANCE\"`    // Example: 127.0.0.1:4000\n\tDeviceType string `gorm:\"column:DEVICE_TYPE\"` // Example: cpu\n\tDeviceName string `gorm:\"column:DEVICE_NAME\"` // Example: usage\n\tJSONValue  string `gorm:\"column:JSON_VALUE\"`  // Only exists by using `clusterTableQueryTemplate`.\n}\n\nfunc NewHostInfo(hostname string) *Info {\n\treturn &Info{\n\t\tHost:       hostname,\n\t\tPartitions: make(map[string]*PartitionInfo),\n\t\tInstances:  make(map[string]*InstanceInfo),\n\t}\n}\n"
  },
  {
    "path": "pkg/apiserver/clusterinfo/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// clusterinfo is a directory for ClusterInfoServer, which could load topology from pd\n// using Etcd v3 interface and pd interface.\n\npackage clusterinfo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/clusterinfo/hostinfo\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/httpc\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/topology\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\ntype ServiceParams struct {\n\tfx.In\n\tPDClient   *pd.Client\n\tEtcdClient *clientv3.Client\n\tHTTPClient *httpc.Client\n\tTiDBClient *tidb.Client\n}\n\ntype Service struct {\n\tparams       ServiceParams\n\tlifecycleCtx context.Context\n}\n\nfunc NewService(lc fx.Lifecycle, p ServiceParams) *Service {\n\ts := &Service{params: p}\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\ts.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\treturn s\n}\n\nfunc RegisterRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/topology\")\n\tendpoint.Use(auth.MWAuthRequired())\n\tendpoint.GET(\"/tidb\", s.getTiDBTopology)\n\tendpoint.GET(\"/ticdc\", s.getTiCDCTopology)\n\tendpoint.GET(\"/tiproxy\", s.getTiProxyTopology)\n\tendpoint.DELETE(\"/tidb/:address\", s.deleteTiDBTopology)\n\tendpoint.GET(\"/store\", s.getStoreTopology)\n\tendpoint.GET(\"/pd\", s.getPDTopology)\n\tendpoint.GET(\"/tso\", s.getTSOTopology)\n\tendpoint.GET(\"/scheduling\", s.getSchedulingTopology)\n\tendpoint.GET(\"/alertmanager\", s.getAlertManagerTopology)\n\tendpoint.GET(\"/alertmanager/:address/count\", s.getAlertManagerCounts)\n\tendpoint.GET(\"/grafana\", s.getGrafanaTopology)\n\n\tendpoint.GET(\"/store_location\", s.getStoreLocationTopology)\n\n\tendpoint = r.Group(\"/host\")\n\tendpoint.Use(auth.MWAuthRequired())\n\tendpoint.Use(utils.MWConnectTiDB(s.params.TiDBClient))\n\tendpoint.GET(\"/all\", s.getHostsInfo)\n\tendpoint.GET(\"/statistics\", s.getStatistics)\n}\n\n// @Summary Hide a TiDB instance\n// @Param address path string true \"ip:port\"\n// @Success 200 \"delete ok\"\n// @Failure 401 {object} rest.ErrorResponse\n// @Security JwtAuth\n// @Router /topology/tidb/{address} [delete]\nfunc (s *Service) deleteTiDBTopology(c *gin.Context) {\n\taddress := c.Param(\"address\")\n\terrorChannel := make(chan error, 2)\n\tttlKey := fmt.Sprintf(\"/topology/tidb/%v/ttl\", address)\n\tnonTTLKey := fmt.Sprintf(\"/topology/tidb/%v/info\", address)\n\tctx, cancel := context.WithTimeout(s.lifecycleCtx, time.Second*5)\n\tdefer cancel()\n\n\tvar wg sync.WaitGroup\n\tfor _, key := range []string{ttlKey, nonTTLKey} {\n\t\twg.Add(1)\n\t\tgo func(toDel string) {\n\t\t\tdefer wg.Done()\n\t\t\tif _, err := s.params.EtcdClient.Delete(ctx, toDel); err != nil {\n\t\t\t\terrorChannel <- err\n\t\t\t}\n\t\t}(key)\n\t}\n\twg.Wait()\n\tvar err error\n\tselect {\n\tcase err = <-errorChannel:\n\tdefault:\n\t}\n\tclose(errorChannel)\n\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, nil)\n}\n\n// @ID getTiDBTopology\n// @Summary Get all TiDB instances\n// @Success 200 {array} topology.TiDBInfo\n// @Router /topology/tidb [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getTiDBTopology(c *gin.Context) {\n\tinstances, err := topology.FetchTiDBTopology(s.lifecycleCtx, s.params.EtcdClient)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, instances)\n}\n\n// @ID getTiCDCTopology\n// @Summary Get all TiCDC instances\n// @Success 200 {array} topology.TiCDCInfo\n// @Router /topology/ticdc [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getTiCDCTopology(c *gin.Context) {\n\tinstances, err := topology.FetchTiCDCTopology(s.lifecycleCtx, s.params.EtcdClient)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, instances)\n}\n\n// @ID getTiProxyTopology\n// @Summary Get all TiProxy instances\n// @Success 200 {array} topology.TiProxyInfo\n// @Router /topology/tiproxy [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getTiProxyTopology(c *gin.Context) {\n\tinstances, err := topology.FetchTiProxyTopology(s.lifecycleCtx, s.params.EtcdClient)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, instances)\n}\n\n// @ID getTSOTopology\n// @Summary Get all TSO instances\n// @Success 200 {array} topology.TSOInfo\n// @Router /topology/tso [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getTSOTopology(c *gin.Context) {\n\tinstances, err := topology.FetchTSOTopology(s.lifecycleCtx, s.params.PDClient)\n\tif err != nil {\n\t\t// TODO: refine later\n\t\tif strings.Contains(err.Error(), \"status code 404\") {\n\t\t\trest.Error(c, rest.ErrNotFound.Wrap(err, \"api not found\"))\n\t\t} else {\n\t\t\trest.Error(c, err)\n\t\t}\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, instances)\n}\n\n// @ID getSchedulingTopology\n// @Summary Get all Scheduling instances\n// @Success 200 {array} topology.SchedulingInfo\n// @Router /topology/scheduling [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getSchedulingTopology(c *gin.Context) {\n\tinstances, err := topology.FetchSchedulingTopology(s.lifecycleCtx, s.params.PDClient)\n\tif err != nil {\n\t\t// TODO: refine later\n\t\tif strings.Contains(err.Error(), \"status code 404\") {\n\t\t\trest.Error(c, rest.ErrNotFound.Wrap(err, \"api not found\"))\n\t\t} else {\n\t\t\trest.Error(c, err)\n\t\t}\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, instances)\n}\n\ntype StoreTopologyResponse struct {\n\tTiKV    []topology.StoreInfo `json:\"tikv\"`\n\tTiFlash []topology.StoreInfo `json:\"tiflash\"`\n}\n\n// @ID getStoreTopology\n// @Summary Get all TiKV / TiFlash instances\n// @Success 200 {object} StoreTopologyResponse\n// @Router /topology/store [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getStoreTopology(c *gin.Context) {\n\ttikvInstances, tiFlashInstances, err := topology.FetchStoreTopology(s.params.PDClient)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, StoreTopologyResponse{\n\t\tTiKV:    tikvInstances,\n\t\tTiFlash: tiFlashInstances,\n\t})\n}\n\n// @ID getStoreLocationTopology\n// @Summary Get location labels of all TiKV / TiFlash instances\n// @Success 200 {object} topology.StoreLocation\n// @Router /topology/store_location [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getStoreLocationTopology(c *gin.Context) {\n\tstoreLocation, err := topology.FetchStoreLocation(s.params.PDClient)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, storeLocation)\n}\n\n// @ID getPDTopology\n// @Summary Get all PD instances\n// @Success 200 {array} topology.PDInfo\n// @Router /topology/pd [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getPDTopology(c *gin.Context) {\n\tinstances, err := topology.FetchPDTopology(s.params.PDClient)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, instances)\n}\n\n// @ID getAlertManagerTopology\n// @Summary Get AlertManager instance\n// @Success 200 {object} topology.AlertManagerInfo\n// @Router /topology/alertmanager [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getAlertManagerTopology(c *gin.Context) {\n\tinstance, err := topology.FetchAlertManagerTopology(s.lifecycleCtx, s.params.EtcdClient)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, instance)\n}\n\n// @ID getGrafanaTopology\n// @Summary Get Grafana instance\n// @Success 200 {object} topology.GrafanaInfo\n// @Router /topology/grafana [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getGrafanaTopology(c *gin.Context) {\n\tinstance, err := topology.FetchGrafanaTopology(s.lifecycleCtx, s.params.EtcdClient)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, instance)\n}\n\n// @ID getAlertManagerCounts\n// @Summary Get current alert count from AlertManager\n// @Success 200 {object} int\n// @Param address path string true \"ip:port\"\n// @Router /topology/alertmanager/{address}/count [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getAlertManagerCounts(c *gin.Context) {\n\taddress := c.Param(\"address\")\n\tif address == \"\" {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"address is empty\"))\n\t\treturn\n\t}\n\tinfo, err := topology.FetchAlertManagerTopology(c.Request.Context(), s.params.EtcdClient)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tif info == nil {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"alertmanager not found\"))\n\t\treturn\n\t}\n\tif address != fmt.Sprintf(\"%s:%d\", info.IP, info.Port) {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"address not match\"))\n\t\treturn\n\t}\n\tcnt, err := fetchAlertManagerCounts(s.lifecycleCtx, address, s.params.HTTPClient)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, cnt)\n}\n\ntype GetHostsInfoResponse struct {\n\tHosts   []*hostinfo.Info   `json:\"hosts\"`\n\tWarning rest.ErrorResponse `json:\"warning\"`\n}\n\n// @ID clusterInfoGetHostsInfo\n// @Summary Get information of all hosts\n// @Router /host/all [get]\n// @Security JwtAuth\n// @Success 200 {object} GetHostsInfoResponse\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getHostsInfo(c *gin.Context) {\n\tdb := utils.GetTiDBConnection(c)\n\n\tinfo, err := s.fetchAllHostsInfo(db)\n\tif err != nil && info == nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tvar warning rest.ErrorResponse\n\tif err != nil {\n\t\twarning = rest.NewErrorResponse(err)\n\t}\n\n\tc.JSON(http.StatusOK, GetHostsInfoResponse{\n\t\tHosts:   info,\n\t\tWarning: warning,\n\t})\n}\n\n// @ID clusterInfoGetStatistics\n// @Summary Get cluster statistics\n// @Router /host/statistics [get]\n// @Security JwtAuth\n// @Success 200 {object} ClusterStatistics\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getStatistics(c *gin.Context) {\n\tdb := utils.GetTiDBConnection(c)\n\tstats, err := s.calculateStatistics(db)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, stats)\n}\n"
  },
  {
    "path": "pkg/apiserver/clusterinfo/statistics.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage clusterinfo\n\nimport (\n\t\"net\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/samber/lo\"\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/clusterinfo/hostinfo\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/topology\"\n)\n\ntype ClusterStatisticsPartial struct {\n\tNumberOfHosts            int `json:\"number_of_hosts\"`\n\tNumberOfInstances        int `json:\"number_of_instances\"`\n\tTotalMemoryCapacityBytes int `json:\"total_memory_capacity_bytes\"`\n\tTotalPhysicalCores       int `json:\"total_physical_cores\"`\n\tTotalLogicalCores        int `json:\"total_logical_cores\"`\n}\n\ntype ClusterStatistics struct {\n\tProbeFailureHosts   int                                  `json:\"probe_failure_hosts\"`\n\tVersions            []string                             `json:\"versions\"`\n\tTotalStats          *ClusterStatisticsPartial            `json:\"total_stats\"`\n\tStatsByInstanceKind map[string]*ClusterStatisticsPartial `json:\"stats_by_instance_kind\"`\n}\n\ntype instanceKindHostImmediateInfo struct {\n\tmemoryCapacity int\n\tphysicalCores  int\n\tlogicalCores   int\n}\n\ntype instanceKindImmediateInfo struct {\n\tinstances map[string]struct{}\n\thosts     map[string]*instanceKindHostImmediateInfo\n}\n\nfunc newInstanceKindImmediateInfo() *instanceKindImmediateInfo {\n\treturn &instanceKindImmediateInfo{\n\t\tinstances: make(map[string]struct{}),\n\t\thosts:     make(map[string]*instanceKindHostImmediateInfo),\n\t}\n}\n\nfunc sumInt(array []int) int {\n\tresult := 0\n\tfor _, v := range array {\n\t\tresult += v\n\t}\n\treturn result\n}\n\nfunc (info *instanceKindImmediateInfo) ToResult() *ClusterStatisticsPartial {\n\treturn &ClusterStatisticsPartial{\n\t\tNumberOfHosts:            len(lo.Keys(info.hosts)),\n\t\tNumberOfInstances:        len(lo.Keys(info.instances)),\n\t\tTotalMemoryCapacityBytes: sumInt(lo.Map(lo.Values(info.hosts), func(x *instanceKindHostImmediateInfo, _ int) int { return x.memoryCapacity })),\n\t\tTotalPhysicalCores:       sumInt(lo.Map(lo.Values(info.hosts), func(x *instanceKindHostImmediateInfo, _ int) int { return x.physicalCores })),\n\t\tTotalLogicalCores:        sumInt(lo.Map(lo.Values(info.hosts), func(x *instanceKindHostImmediateInfo, _ int) int { return x.logicalCores })),\n\t}\n}\n\nfunc (s *Service) calculateStatistics(db *gorm.DB) (*ClusterStatistics, error) {\n\tglobalHostsSet := make(map[string]struct{})\n\tglobalFailureHostsSet := make(map[string]struct{})\n\tglobalVersionsSet := make(map[string]struct{})\n\tglobalInfo := newInstanceKindImmediateInfo()\n\tinfoByIk := make(map[string]*instanceKindImmediateInfo)\n\tinfoByIk[\"pd\"] = newInstanceKindImmediateInfo()\n\tinfoByIk[\"tidb\"] = newInstanceKindImmediateInfo()\n\tinfoByIk[\"tikv\"] = newInstanceKindImmediateInfo()\n\tinfoByIk[\"tiflash\"] = newInstanceKindImmediateInfo()\n\tinfoByIk[\"ticdc\"] = newInstanceKindImmediateInfo()\n\tinfoByIk[\"tiproxy\"] = newInstanceKindImmediateInfo()\n\tinfoByIk[\"tso\"] = newInstanceKindImmediateInfo()\n\tinfoByIk[\"scheduling\"] = newInstanceKindImmediateInfo()\n\n\t// Fill from topology info\n\tpdInfo, err := topology.FetchPDTopology(s.params.PDClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, i := range pdInfo {\n\t\tglobalHostsSet[i.IP] = struct{}{}\n\t\tglobalVersionsSet[i.Version] = struct{}{}\n\t\tglobalInfo.instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t\tinfoByIk[\"pd\"].instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t}\n\ttikvInfo, tiFlashInfo, err := topology.FetchStoreTopology(s.params.PDClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, i := range tikvInfo {\n\t\tglobalHostsSet[i.IP] = struct{}{}\n\t\tglobalVersionsSet[i.Version] = struct{}{}\n\t\tglobalInfo.instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t\tinfoByIk[\"tikv\"].instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t}\n\tfor _, i := range tiFlashInfo {\n\t\tglobalHostsSet[i.IP] = struct{}{}\n\t\tglobalVersionsSet[i.Version] = struct{}{}\n\t\tglobalInfo.instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t\tinfoByIk[\"tiflash\"].instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t}\n\ttidbInfo, err := topology.FetchTiDBTopology(s.lifecycleCtx, s.params.EtcdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, i := range tidbInfo {\n\t\tglobalHostsSet[i.IP] = struct{}{}\n\t\tglobalVersionsSet[i.Version] = struct{}{}\n\t\tglobalInfo.instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t\tinfoByIk[\"tidb\"].instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t}\n\tticdcInfo, err := topology.FetchTiCDCTopology(s.lifecycleCtx, s.params.EtcdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, i := range ticdcInfo {\n\t\tglobalHostsSet[i.IP] = struct{}{}\n\t\tglobalVersionsSet[i.Version] = struct{}{}\n\t\tglobalInfo.instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t\tinfoByIk[\"ticdc\"].instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t}\n\ttiproxyInfo, err := topology.FetchTiProxyTopology(s.lifecycleCtx, s.params.EtcdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, i := range tiproxyInfo {\n\t\tglobalHostsSet[i.IP] = struct{}{}\n\t\tglobalVersionsSet[i.Version] = struct{}{}\n\t\tglobalInfo.instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t\tinfoByIk[\"tiproxy\"].instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t}\n\ttsoInfo, err := topology.FetchTSOTopology(s.lifecycleCtx, s.params.PDClient)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"status code 404\") {\n\t\t\ttsoInfo = []topology.TSOInfo{}\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tfor _, i := range tsoInfo {\n\t\tglobalHostsSet[i.IP] = struct{}{}\n\t\tglobalVersionsSet[i.Version] = struct{}{}\n\t\tglobalInfo.instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t\tinfoByIk[\"tso\"].instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t}\n\tschedulingInfo, err := topology.FetchSchedulingTopology(s.lifecycleCtx, s.params.PDClient)\n\tif err != nil {\n\t\tif strings.Contains(err.Error(), \"status code 404\") {\n\t\t\tschedulingInfo = []topology.SchedulingInfo{}\n\t\t} else {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tfor _, i := range schedulingInfo {\n\t\tglobalHostsSet[i.IP] = struct{}{}\n\t\tglobalVersionsSet[i.Version] = struct{}{}\n\t\tglobalInfo.instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t\tinfoByIk[\"scheduling\"].instances[net.JoinHostPort(i.IP, strconv.Itoa(int(i.Port)))] = struct{}{}\n\t}\n\n\t// Fill from hardware info\n\tallHostsInfoMap := make(map[string]*hostinfo.Info)\n\tif e := hostinfo.FillFromClusterLoadTable(db, allHostsInfoMap); e != nil {\n\t\treturn nil, err\n\t}\n\tif e := hostinfo.FillFromClusterHardwareTable(db, allHostsInfoMap); e != nil {\n\t\treturn nil, err\n\t}\n\tfor host, hi := range allHostsInfoMap {\n\t\tif hi.MemoryUsage.Total > 0 && hi.CPUInfo.PhysicalCores > 0 && hi.CPUInfo.LogicalCores > 0 {\n\t\t\t// Put success host info into `globalInfo.hosts`.\n\t\t\tglobalInfo.hosts[host] = &instanceKindHostImmediateInfo{\n\t\t\t\tmemoryCapacity: hi.MemoryUsage.Total,\n\t\t\t\tphysicalCores:  hi.CPUInfo.PhysicalCores,\n\t\t\t\tlogicalCores:   hi.CPUInfo.LogicalCores,\n\t\t\t}\n\t\t}\n\t}\n\n\t// Fill hosts in each instance kind according to the global hosts info\n\tfor _, i := range pdInfo {\n\t\tif v, ok := globalInfo.hosts[i.IP]; ok {\n\t\t\tinfoByIk[\"pd\"].hosts[i.IP] = v\n\t\t} else {\n\t\t\tglobalFailureHostsSet[i.IP] = struct{}{}\n\t\t}\n\t}\n\tfor _, i := range tikvInfo {\n\t\tif v, ok := globalInfo.hosts[i.IP]; ok {\n\t\t\tinfoByIk[\"tikv\"].hosts[i.IP] = v\n\t\t} else {\n\t\t\tglobalFailureHostsSet[i.IP] = struct{}{}\n\t\t}\n\t}\n\tfor _, i := range tiFlashInfo {\n\t\tif v, ok := globalInfo.hosts[i.IP]; ok {\n\t\t\tinfoByIk[\"tiflash\"].hosts[i.IP] = v\n\t\t} else {\n\t\t\tglobalFailureHostsSet[i.IP] = struct{}{}\n\t\t}\n\t}\n\tfor _, i := range tidbInfo {\n\t\tif v, ok := globalInfo.hosts[i.IP]; ok {\n\t\t\tinfoByIk[\"tidb\"].hosts[i.IP] = v\n\t\t} else {\n\t\t\tglobalFailureHostsSet[i.IP] = struct{}{}\n\t\t}\n\t}\n\tfor _, i := range ticdcInfo {\n\t\tif v, ok := globalInfo.hosts[i.IP]; ok {\n\t\t\tinfoByIk[\"ticdc\"].hosts[i.IP] = v\n\t\t} else {\n\t\t\tglobalFailureHostsSet[i.IP] = struct{}{}\n\t\t}\n\t}\n\tfor _, i := range tiproxyInfo {\n\t\tif v, ok := globalInfo.hosts[i.IP]; ok {\n\t\t\tinfoByIk[\"tiproxy\"].hosts[i.IP] = v\n\t\t} else {\n\t\t\tglobalFailureHostsSet[i.IP] = struct{}{}\n\t\t}\n\t}\n\tfor _, i := range tsoInfo {\n\t\tif v, ok := globalInfo.hosts[i.IP]; ok {\n\t\t\tinfoByIk[\"tso\"].hosts[i.IP] = v\n\t\t} else {\n\t\t\tglobalFailureHostsSet[i.IP] = struct{}{}\n\t\t}\n\t}\n\tfor _, i := range schedulingInfo {\n\t\tif v, ok := globalInfo.hosts[i.IP]; ok {\n\t\t\tinfoByIk[\"scheduling\"].hosts[i.IP] = v\n\t\t} else {\n\t\t\tglobalFailureHostsSet[i.IP] = struct{}{}\n\t\t}\n\t}\n\n\t// Generate result..\n\tversions := lo.Keys(globalVersionsSet)\n\tsort.Strings(versions)\n\n\tstatsByIk := make(map[string]*ClusterStatisticsPartial)\n\tfor ik, info := range infoByIk {\n\t\tstatsByIk[ik] = info.ToResult()\n\t}\n\n\treturn &ClusterStatistics{\n\t\tProbeFailureHosts:   len(lo.Keys(globalFailureHostsSet)),\n\t\tVersions:            versions,\n\t\tTotalStats:          globalInfo.ToResult(),\n\t\tStatsByInstanceKind: statsByIk,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/apiserver/clusterinfo/topology.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage clusterinfo\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/httpc\"\n)\n\nfunc fetchAlertManagerCounts(ctx context.Context, alertManagerAddr string, httpClient *httpc.Client) (int, error) {\n\t// FIXME: Use httpClient.SendGetRequest\n\n\turi := fmt.Sprintf(\"http://%s/api/v2/alerts\", alertManagerAddr)\n\treq, err := http.NewRequestWithContext(ctx, \"GET\", uri, nil)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tresp, err := httpClient.Do(req)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn 0, fmt.Errorf(\"alert manager API returns non success status code\")\n\t}\n\n\tdata, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tvar alerts []struct{}\n\terr = json.Unmarshal(data, &alerts)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\treturn len(alerts), nil\n}\n"
  },
  {
    "path": "pkg/apiserver/configuration/editable.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage configuration\n\nimport \"strings\"\n\n// Hard coded items comes from https://docs.pingcap.com/tidb/stable/dynamic-config\n\nvar editableConfigItemsRaw = map[ItemKind]string{\n\tItemKindTiKVConfig: `\nraftstore.sync-log\nraftstore.raft-entry-max-size\nraftstore.raft-log-gc-tick-interval\nraftstore.raft-log-gc-threshold\nraftstore.raft-log-gc-count-limit\nraftstore.raft-log-gc-size-limit\nraftstore.raft-entry-cache-life-time\nraftstore.raft-reject-transfer-leader-duration\nraftstore.split-region-check-tick-interval\nraftstore.region-split-check-diff\nraftstore.region-compact-check-interval\nraftstore.region-compact-check-step\nraftstore.region-compact-min-tombstones\nraftstore.region-compact-tombstones-percent\nraftstore.pd-heartbeat-tick-interval\nraftstore.pd-store-heartbeat-tick-interval\nraftstore.snap-mgr-gc-tick-interval\nraftstore.snap-gc-timeout\nraftstore.lock-cf-compact-interval\nraftstore.lock-cf-compact-bytes-threshold\nraftstore.messages-per-tick\nraftstore.max-peer-down-duration\nraftstore.max-leader-missing-duration\nraftstore.abnormal-leader-missing-duration\nraftstore.peer-stale-state-check-interval\nraftstore.consistency-check-interval\nraftstore.raft-store-max-leader-lease\nraftstore.allow-remove-leader\nraftstore.merge-check-tick-interval\nraftstore.cleanup-import-sst-interval\nraftstore.local-read-batch-size\nraftstore.hibernate-timeout\ncoprocessor.split-region-on-table\ncoprocessor.batch-split-limit\ncoprocessor.region-max-size\ncoprocessor.region-split-size\ncoprocessor.region-max-keys\ncoprocessor.region-split-keys\npessimistic-txn.wait-for-lock-timeout\npessimistic-txn.wake-up-delay-duration\npessimistic-txn.pipelined\ngc.ratio-threshold\ngc.batch-keys\ngc.max-write-bytes-per-sec\ngc.enable-compaction-filter\ngc.compaction-filter-skip-version-check\nraftdb.defaultcf.block-cache-size\nraftdb.defaultcf.write-buffer-size\nraftdb.defaultcf.max-write-buffer-number\nraftdb.defaultcf.max-bytes-for-level-base\nraftdb.defaultcf.target-file-size-base\nraftdb.defaultcf.level0-file-num-compaction-trigger\nraftdb.defaultcf.level0-slowdown-writes-trigger\nraftdb.defaultcf.level0-stop-writes-trigger\nraftdb.defaultcf.max-compaction-bytes\nraftdb.defaultcf.max-bytes-for-level-multiplier\nraftdb.defaultcf.disable-auto-compactions\nraftdb.defaultcf.soft-pending-compaction-bytes-limit\nraftdb.defaultcf.hard-pending-compaction-bytes-limit\nraftdb.defaultcf.titan.blob-run-mode\nrocksdb.max-total-wal-size\nrocksdb.max-background-jobs\nrocksdb.max-open-files\nrocksdb.compaction-readahead-size\nrocksdb.bytes-per-sync\nrocksdb.wal-bytes-per-sync\nrocksdb.writable-file-max-buffer-size\nrocksdb.raftcf.block-cache-size\nrocksdb.raftcf.write-buffer-size\nrocksdb.raftcf.max-write-buffer-number\nrocksdb.raftcf.max-bytes-for-level-base\nrocksdb.raftcf.target-file-size-base\nrocksdb.raftcf.level0-file-num-compaction-trigger\nrocksdb.raftcf.level0-slowdown-writes-trigger\nrocksdb.raftcf.level0-stop-writes-trigger\nrocksdb.raftcf.max-compaction-bytes\nrocksdb.raftcf.max-bytes-for-level-multiplier\nrocksdb.raftcf.disable-auto-compactions\nrocksdb.raftcf.soft-pending-compaction-bytes-limit\nrocksdb.raftcf.hard-pending-compaction-bytes-limit\nrocksdb.raftcf.titan.blob-run-mode\nrocksdb.defaultcf.block-cache-size\nrocksdb.defaultcf.write-buffer-size\nrocksdb.defaultcf.max-write-buffer-number\nrocksdb.defaultcf.max-bytes-for-level-base\nrocksdb.defaultcf.target-file-size-base\nrocksdb.defaultcf.level0-file-num-compaction-trigger\nrocksdb.defaultcf.level0-slowdown-writes-trigger\nrocksdb.defaultcf.level0-stop-writes-trigger\nrocksdb.defaultcf.max-compaction-bytes\nrocksdb.defaultcf.max-bytes-for-level-multiplier\nrocksdb.defaultcf.disable-auto-compactions\nrocksdb.defaultcf.soft-pending-compaction-bytes-limit\nrocksdb.defaultcf.hard-pending-compaction-bytes-limit\nrocksdb.defaultcf.titan.blob-run-mode\nrocksdb.lockcf.block-cache-size\nrocksdb.lockcf.write-buffer-size\nrocksdb.lockcf.max-write-buffer-number\nrocksdb.lockcf.max-bytes-for-level-base\nrocksdb.lockcf.target-file-size-base\nrocksdb.lockcf.level0-file-num-compaction-trigger\nrocksdb.lockcf.level0-slowdown-writes-trigger\nrocksdb.lockcf.level0-stop-writes-trigger\nrocksdb.lockcf.max-compaction-bytes\nrocksdb.lockcf.max-bytes-for-level-multiplier\nrocksdb.lockcf.disable-auto-compactions\nrocksdb.lockcf.soft-pending-compaction-bytes-limit\nrocksdb.lockcf.hard-pending-compaction-bytes-limit\nrocksdb.lockcf.titan.blob-run-mode\nstorage.block-cache.capacity\nbackup.num-threads\nsplit.qps-threshold\nsplit.split-balance-score\nsplit.split-contained-score\n`,\n\tItemKindPDConfig: `\nlog.level\ncluster-version\nschedule.max-merge-region-size\nschedule.max-merge-region-keys\nschedule.patrol-region-interval\nschedule.split-merge-interval\nschedule.max-snapshot-count\nschedule.max-pending-peer-count\nschedule.max-store-down-time\nschedule.leader-schedule-policy\nschedule.leader-schedule-limit\nschedule.region-schedule-limit\nschedule.replica-schedule-limit\nschedule.merge-schedule-limit\nschedule.hot-region-schedule-limit\nschedule.hot-region-cache-hits-threshold\nschedule.high-space-ratio\nschedule.low-space-ratio\nschedule.tolerant-size-ratio\nschedule.enable-remove-down-replica\nschedule.enable-replace-offline-replica\nschedule.enable-make-up-replica\nschedule.enable-remove-extra-replica\nschedule.enable-location-replacement\nschedule.enable-cross-table-merge\nschedule.enable-one-way-merge\nreplication.max-replicas\nreplication.location-labels\nreplication.enable-placement-rules\nreplication.strictly-match-label\npd-server.use-region-storage\npd-server.max-gap-reset-ts\npd-server.key-type\npd-server.metric-storage\npd-server.dashboard-address\nreplication-mode.replication-mode\n`,\n\n\t// Mark all global variables in TiDB being editable.\n\t// Due to https://github.com/pingcap/tidb/issues/18517 we have to hard code all global variables for now.\n\t// TODO: We'd better provide a Editable system variable table as well.\n\tItemKindTiDBVariable: `\ngtid_mode\nflush_time\nlow_priority_updates\nsession_track_gtids\nndbinfo_max_rows\nndb_index_stat_option\nold_passwords\nmax_connections\nbig_tables\nslave_pending_jobs_size_max\nvalidate_password_check_user_name\nvalidate_password_number_count\nsql_select_limit\nndb_show_foreign_key_mock_tables\ndefault_week_format\nbinlog_error_action\nslave_transaction_retries\ndefault_storage_engine\nmax_connect_errors\nsync_binlog\ninnodb_fast_shutdown\nlog_backward_compatible_user_definitions\nft_boolean_syntax\ntable_definition_cache\nsql_mode\nserver_id\ninnodb_flushing_avg_loops\ntmp_table_size\ninnodb_max_purge_lag\npreload_buffer_size\nslave_checkpoint_period\ncheck_proxy_users\ninnodb_flush_log_at_timeout\ninnodb_max_undo_log_size\nrange_alloc_block_size\nconnect_timeout\nmax_execution_time\ncollation_server\ninnodb_old_blocks_pct\ninnodb_file_format\ninnodb_compression_failure_threshold_pct\ninnodb_checksum_algorithm\nrelay_log_info_repository\nsql_log_bin\nsuper_read_only\nmax_delayed_threads\nnew\nmyisam_sort_buffer_size\noptimizer_trace_offset\ninnodb_buffer_pool_dump_at_shutdown\nsql_notes\ninnodb_cmp_per_index_enabled\ninnodb_ft_server_stopword_table\nbinlog_group_commit_sync_delay\nbinlog_group_commit_sync_no_delay_count\ninnodb_log_write_ahead_size\ngeneral_log\nvalidate_password_dictionary_file\nbinlog_order_commits\nmaster_verify_checksum\nkey_cache_division_limit\nrpl_semi_sync_master_trace_level\nmax_insert_delayed_threads\ntime_zone\ninnodb_max_dirty_pages_pct\ninnodb_file_per_table\ninnodb_log_compressed_pages\nmaster_info_repository\nrpl_stop_slave_timeout\ninnodb_monitor_reset\ninnodb_print_all_deadlocks\nslave_net_timeout\nkey_buffer_size\nforeign_key_checks\nhost_cache_size\ndelay_key_write\ninnodb_file_format_max\ndebug\nlog_warnings\noffline_mode\ninnodb_strict_mode\ninnodb_rollback_segments\njoin_buffer_size\nmax_binlog_size\nsync_master_info\nconcurrent_insert\ninnodb_adaptive_hash_index\ninnodb_ft_enable_stopword\ngeneral_log_file\ninnodb_support_xa\ninnodb_compression_level\ninit_slave\nblock_encryption_mode\nmax_length_for_sort_data\ninteractive_timeout\ninnodb_optimize_fulltext_only\nquery_cache_type\nquery_alloc_block_size\nslave_compressed_protocol\ninit_connect\nrpl_semi_sync_slave_trace_level\nquery_prealloc_size\nmax_user_connections\ninnodb_api_trx_level\nexpire_logs_days\nbinlog_rows_query_log_events\ndefault_password_lifetime\ninnodb_status_output_locks\nmax_error_count\nmax_write_lock_count\ninnodb_stats_persistent_sample_pages\nshow_compatibility_56\nlog_slow_slave_statements\ninnodb_spin_wait_delay\nthread_cache_size\nlog_slow_admin_statements\nauto_increment_offset\ninnodb_max_dirty_pages_pct_lwm\nlog_queries_not_using_indexes\nquery_cache_wlock_invalidate\nsql_buffer_result\ncharacter_set_filesystem\ncollation_database\nauto_increment_increment\nauto_increment_offset\nmax_heap_table_size\ndiv_precision_increment\ninnodb_lru_scan_depth\ninnodb_purge_rseg_truncate_frequency\nsql_auto_is_null\ninnodb_ft_user_stopword_table\ninnodb_log_checksum_algorithm\nsort_buffer_size\ninnodb_flush_neighbors\ninnodb_purge_batch_size\nslave_checkpoint_group\ncharacter_set_client\ninnodb_buffer_pool_dump_now\nrelay_log_purge\nndb_distribution\nmyisam_data_pointer_size\nndb_optimization_delay\ninnodb_ft_num_word_optimize\nmax_join_size\nmax_seeks_for_key\ndelayed_insert_timeout\nmax_relay_log_size\nmax_sort_length\nndb_eventbuffer_free_percent\nbinlog_max_flush_queue_time\ninnodb_fill_factor\nlog_syslog_facility\ntransaction_write_set_extraction\nndb_blob_write_batch_bytes\nautomatic_sp_privileges\ninnodb_flush_sync\ninnodb_monitor_disable\nslave_parallel_type\ninnodb_adaptive_flushing_lwm\ninnodb_buffer_pool_load_now\nprofiling\nsha256_password_proxy_users\nsql_quote_show_create\nbinlogging_impossible_mode\nquery_cache_size\ninnodb_stats_transient_sample_pages\ninnodb_stats_on_metadata\nndb_force_send\nlog_timestamps\nslave_parallel_workers\nevent_scheduler\nndb_deferred_constraints\nlog_syslog_include_pid\ninnodb_disable_sort_file_cache\nlog_error_verbosity\ninnodb_replication_delay\nslow_query_log\ninnodb_stats_auto_recalc\nlc_messages\nbulk_insert_buffer_size\nbinlog_direct_non_transactional_updates\ninnodb_change_buffering\nsql_big_selects\ncharacter_set_results\ninnodb_max_purge_lag_delay\nsession_track_schema\ninnodb_io_capacity_max\ninnodb_autoextend_increment\nbinlog_format\noptimizer_trace\nread_rnd_buffer_size\nnet_write_timeout\ninnodb_buffer_pool_load_abort\ntx_isolation\ntransaction_isolation\ncollation_connection\nrpl_semi_sync_master_timeout\ntransaction_prealloc_size\nsync_relay_log\ninnodb_ft_result_cache_limit\ninnodb_ft_enable_diag_print\nstored_program_cache\ninnodb_adaptive_max_sleep_delay\nsession_track_system_variables\ninnodb_change_buffer_max_size\nlog_bin_trust_function_creators\nmysql_native_password_proxy_users\nread_only\ninnodb_stats_persistent\nsession_track_state_change\ndelayed_queue_size\nlog_syslog\ntransaction_alloc_block_size\nsql_slave_skip_counter\ninnodb_large_prefix\ninnodb_io_capacity\nmax_binlog_cache_size\nndb_index_stat_enable\nexecuted_gtids_compression_period\nold_alter_table\nlong_query_time\nlog_throttle_queries_not_using_indexes\nbinlog_cache_size\ninnodb_compression_pad_pct_max\ninnodb_commit_concurrency\nenforce_gtid_consistency\nsecure_auth\ninnodb_random_read_ahead\nunique_checks\ninternal_tmp_disk_storage_engine\nmyisam_repair_threads\nndb_eventbuffer_max_alloc\ninnodb_read_ahead_threshold\nkey_cache_block_size\nrpl_semi_sync_slave_enabled\ngtid_purged\nmax_binlog_stmt_cache_size\nlock_wait_timeout\nread_buffer_size\nmax_sp_recursion_depth\nrpl_semi_sync_master_enabled\nslow_query_log_file\ninnodb_thread_sleep_delay\ninnodb_ft_aux_table\nsql_warnings\nkeep_files_on_create\nslave_preserve_commit_order\nslave_exec_mode\nbinlog_stmt_cache_size\ntable_open_cache\nautocommit\ndefault_tmp_storage_engine\noptimizer_search_depth\nmax_points_in_geometry\ninnodb_stats_sample_pages\nprofiling_history_size\ncharacter_set_database\nstorage_engine\nsql_log_off\nlog_syslog_tag\ntx_read_only\ntransaction_read_only\nrpl_semi_sync_master_wait_point\ninnodb_undo_log_truncate\ngtid_executed_compression_period\nndb_log_empty_epochs\nmax_prepared_stmt_count\noptimizer_trace_max_mem_size\nnet_retry_count\noptimizer_trace_features\ninnodb_flush_log_at_trx_commit\nrewriter_enabled\nquery_cache_min_res_unit\nupdatable_views_with_limit\noptimizer_prune_level\nslave_sql_verify_checksum\ncompletion_type\nbinlog_checksum\nshow_old_temporals\nquery_cache_limit\ninnodb_buffer_pool_size\ninnodb_adaptive_flushing\nwait_timeout\ninnodb_monitor_enable\ninnodb_buffer_pool_filename\nslow_launch_time\nslave_max_allowed_packet\nndb_use_transactions\ninnodb_concurrency_tickets\ninnodb_monitor_reset_all\nndb_log_updated_only\ninnodb_old_blocks_time\ninnodb_stats_method\ninnodb_lock_wait_timeout\nlocal_infile\nmyisam_stats_method\ninnodb_table_locks\nnet_buffer_length\nrpl_semi_sync_master_wait_for_slave_count\nbinlog_row_image\nmyisam_max_sort_file_size\nrpl_semi_sync_master_wait_no_slave\ngroup_concat_max_len\nrewriter_verbose\ninnodb_undo_logs\ndelayed_insert_limit\nflush\neq_range_index_dive_limit\ncharacter_set_connection\nmyisam_use_mmap\nndb_join_pushdown\ncharacter_set_server\nvalidate_password_special_char_count\nslave_rows_search_algorithms\nndbinfo_show_hidden\nnet_read_timeout\nmax_allowed_packet\nsync_relay_log_info\noptimizer_trace_limit\nvalidate_password_length\nndb_log_binlog_index\ninnodb_api_bk_commit_interval\ninnodb_sync_spin_loops\nsql_safe_updates\ninnodb_thread_concurrency\nslave_allow_batching\ninnodb_buffer_pool_dump_pct\nlc_time_names\nmax_statement_time\nend_markers_in_json\navoid_temporal_upgrade\nkey_cache_age_threshold\ninnodb_status_output\nmin_examined_row_limit\nsync_frm\ninnodb_online_alter_log_max_size\ninformation_schema_stats_expiry\nthread_pool_size\nwindowing_use_high_precision\ntidb_opt_broadcast_join\ntidb_build_stats_concurrency\ntidb_auto_analyze_ratio\ntidb_auto_analyze_start_time\ntidb_auto_analyze_end_time\ntidb_executor_concurrency\ntidb_distsql_scan_concurrency\ntidb_opt_insubq_to_join_and_agg\ntidb_opt_correlation_threshold\ntidb_opt_correlation_exp_factor\ntidb_opt_cpu_factor\ntidb_opt_tiflash_concurrency_factor\ntidb_opt_copcpu_factor\ntidb_opt_network_factor\ntidb_opt_scan_factor\ntidb_opt_desc_factor\ntidb_opt_seek_factor\ntidb_opt_memory_factor\ntidb_opt_disk_factor\ntidb_opt_concurrency_factor\ntidb_index_join_batch_size\ntidb_index_lookup_size\ntidb_index_lookup_concurrency\ntidb_index_lookup_join_concurrency\ntidb_index_serial_scan_concurrency\ntidb_skip_utf8_check\ntidb_skip_ascii_check\ntidb_max_chunk_size\ntidb_allow_batch_cop\ntidb_init_chunk_size\ntidb_enable_cascades_planner\ntidb_enable_index_merge\ntidb_enable_table_partition\ntidb_hash_join_concurrency\ntidb_projection_concurrency\ntidb_hashagg_partial_concurrency\ntidb_hashagg_final_concurrency\ntidb_window_concurrency\ntidb_enable_parallel_apply\ntidb_backoff_lock_fast\ntidb_backoff_weight\ntidb_retry_limit\ntidb_disable_txn_auto_retry\ntidb_constraint_check_in_place\ntidb_txn_mode\ntidb_row_format_version\ntidb_enable_window_function\ntidb_enable_vectorized_expression\ntidb_enable_fast_analyze\ntidb_skip_isolation_level_check\ntidb_ddl_reorg_worker_cnt\ntidb_ddl_reorg_batch_size\ntidb_ddl_error_count_limit\ntidb_max_delta_schema_count\ntidb_opt_join_reorder_threshold\ntidb_scatter_region\ntidb_enable_noop_functions\ntidb_enable_stmt_summary\ntidb_stmt_summary_internal_query\ntidb_stmt_summary_refresh_interval\ntidb_stmt_summary_history_size\ntidb_stmt_summary_max_stmt_count\ntidb_stmt_summary_max_sql_length\ntidb_capture_plan_baselines\ntidb_use_plan_baselines\ntidb_evolve_plan_baselines\ntidb_evolve_plan_task_max_time\ntidb_evolve_plan_task_start_time\ntidb_evolve_plan_task_end_time\ntidb_store_limit\nallow_auto_random_explicit_insert\ntidb_enable_clustered_index\ntidb_slow_log_masking\ntidb_log_desensitization\ntidb_shard_allocate_step\ntidb_enable_telemetry\n`,\n}\n\nvar editableConfigItems = map[ItemKind]map[string]struct{}{}\n\nfunc init() {\n\tfor kind, str := range editableConfigItemsRaw {\n\t\teditableConfigItems[kind] = make(map[string]struct{})\n\t\tconfigItems := strings.SplitSeq(strings.TrimSpace(str), \"\\n\")\n\t\tfor key := range configItems {\n\t\t\teditableConfigItems[kind][key] = struct{}{}\n\t\t}\n\t}\n}\n\nfunc isConfigItemEditable(kind ItemKind, key string) bool {\n\tif _, ok := editableConfigItems[kind]; !ok {\n\t\treturn false\n\t}\n\tif _, ok := editableConfigItems[kind][key]; !ok {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "pkg/apiserver/configuration/flatten.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage configuration\n\nimport (\n\t\"encoding/json\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n)\n\nfunc flattenRecursive(nestedConfig map[string]interface{}) map[string]interface{} {\n\tflatMap := make(map[string]interface{})\n\tflatten(flatMap, nestedConfig, \"\")\n\treturn flatMap\n}\n\nfunc flatten(flatMap map[string]interface{}, nested interface{}, prefix string) {\n\tswitch n := nested.(type) {\n\tcase map[string]interface{}:\n\t\tfor k, v := range n {\n\t\t\tpath := k\n\t\t\tif prefix != \"\" {\n\t\t\t\tpath = prefix + \".\" + k\n\t\t\t}\n\t\t\tflatten(flatMap, v, path)\n\t\t}\n\tcase []interface{}:\n\t\t// For array, serialize as json string directly\n\t\tj, err := json.Marshal(n)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"Failed to serialize config value\", zap.Any(\"value\", n), zap.Error(err))\n\t\t\tflatMap[prefix] = nil\n\t\t} else {\n\t\t\tflatMap[prefix] = string(j)\n\t\t}\n\tcase nil:\n\t\tflatMap[prefix] = \"\"\n\tdefault: // don't flatten arrays\n\t\tflatMap[prefix] = nested\n\t}\n}\n"
  },
  {
    "path": "pkg/apiserver/configuration/router.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage configuration\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nfunc RegisterRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/configuration\")\n\tendpoint.Use(auth.MWAuthRequired())\n\tendpoint.Use(utils.MWConnectTiDB(s.params.TiDBClient))\n\tendpoint.Use(utils.MWForbidByExperimentalFlag(s.params.Config.EnableExperimental))\n\tendpoint.GET(\"/all\", s.getHandler)\n\tendpoint.POST(\"/edit\", auth.MWRequireWritePriv(), s.editHandler)\n}\n\n// @ID configurationGetAll\n// @Summary Get all configurations\n// @Success 200 {object} AllConfigItems\n// @Router /configuration/all [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 403 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) getHandler(c *gin.Context) {\n\tdb := utils.GetTiDBConnection(c)\n\tr, err := s.getAllConfigItems(db)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, r)\n}\n\ntype EditRequest struct {\n\tKind     ItemKind    `json:\"kind\"`\n\tID       string      `json:\"id\"`\n\tNewValue interface{} `json:\"new_value\"`\n}\n\ntype EditResponse struct {\n\tWarnings []rest.ErrorResponse `json:\"warnings\"`\n}\n\n// @ID configurationEdit\n// @Summary Edit a configuration\n// @Param request body EditRequest true \"Request body\"\n// @Success 200 {object} EditResponse\n// @Router /configuration/edit [post]\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 403 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) editHandler(c *gin.Context) {\n\tvar req EditRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tdb := utils.GetTiDBConnection(c)\n\twarnings, err := s.editConfig(db, req.Kind, req.ID, req.NewValue)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tvar resp EditResponse\n\tresp.Warnings = warnings\n\n\tc.JSON(http.StatusOK, resp)\n}\n"
  },
  {
    "path": "pkg/apiserver/configuration/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage configuration\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"sort\"\n\t\"strconv\"\n\n\t\"github.com/joomcode/errorx\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/fx\"\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tikv\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/topology\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nvar (\n\tErrNS                    = errorx.NewNamespace(\"error.api.config\")\n\tErrListTopologyFailed    = ErrNS.NewType(\"list_topology_failed\")\n\tErrListConfigItemsFailed = ErrNS.NewType(\"list_config_items_failed\")\n\tErrNotEditable           = ErrNS.NewType(\"not_editable\")\n\tErrEditFailed            = ErrNS.NewType(\"edit_failed\")\n)\n\ntype ServiceParams struct {\n\tfx.In\n\tConfig     *config.Config\n\tPDClient   *pd.Client\n\tEtcdClient *clientv3.Client\n\tTiDBClient *tidb.Client\n\tTiKVClient *tikv.Client\n}\n\ntype Service struct {\n\tparams       ServiceParams\n\tlifecycleCtx context.Context\n}\n\nfunc NewService(lc fx.Lifecycle, p ServiceParams) *Service {\n\tservice := &Service{params: p}\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tservice.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn service\n}\n\ntype ItemKind string\n\nconst (\n\tItemKindTiKVConfig   ItemKind = \"tikv_config\"\n\tItemKindPDConfig     ItemKind = \"pd_config\"\n\tItemKindTiDBConfig   ItemKind = \"tidb_config\"\n\tItemKindTiDBVariable ItemKind = \"tidb_variable\"\n)\n\ntype channelItem struct {\n\tErr                  error\n\tSourceDisplayAddress string\n\tSourceKind           ItemKind\n\tValues               map[string]interface{}\n}\n\nfunc processNestedConfigAPIResponse(data []byte) (map[string]interface{}, error) {\n\tnestedConfig := make(map[string]interface{})\n\tif err := json.Unmarshal(data, &nestedConfig); err != nil {\n\t\treturn nil, err\n\t}\n\n\tplainConfig := flattenRecursive(nestedConfig)\n\treturn plainConfig, nil\n}\n\nfunc (s *Service) getConfigItemsFromPDToChannel(ch chan<- channelItem) {\n\tr, err := s.getConfigItemsFromPD()\n\tif err != nil {\n\t\tch <- channelItem{Err: ErrListConfigItemsFailed.Wrap(err, \"Failed to list PD config items\")}\n\t\treturn\n\t}\n\tch <- channelItem{\n\t\tErr:        nil,\n\t\tSourceKind: ItemKindPDConfig,\n\t\tValues:     r,\n\t}\n}\n\nfunc (s *Service) getConfigItemsFromPD() (map[string]interface{}, error) {\n\tdata, err := s.params.PDClient.SendGetRequest(\"/config\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn processNestedConfigAPIResponse(data)\n}\n\nfunc (s *Service) getConfigItemsFromTiDBToChannel(tidb *topology.TiDBInfo, ch chan<- channelItem) {\n\tdisplayAddress := net.JoinHostPort(tidb.IP, strconv.Itoa(int(tidb.Port)))\n\n\tr, err := s.getConfigItemsFromTiDB(tidb.IP, int(tidb.StatusPort))\n\tif err != nil {\n\t\tch <- channelItem{Err: ErrListConfigItemsFailed.Wrap(err, \"Failed to list %s config items of %s\", distro.R().TiDB, displayAddress)}\n\t\treturn\n\t}\n\tch <- channelItem{\n\t\tErr:                  nil,\n\t\tSourceDisplayAddress: displayAddress,\n\t\tSourceKind:           ItemKindTiDBConfig,\n\t\tValues:               r,\n\t}\n}\n\nfunc (s *Service) getConfigItemsFromTiDB(host string, statusPort int) (map[string]interface{}, error) {\n\tdata, err := s.params.TiDBClient.WithStatusAPIAddress(host, statusPort).SendGetRequest(\"/config\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn processNestedConfigAPIResponse(data)\n}\n\nfunc (s *Service) getConfigItemsFromTiKVToChannel(tikv *topology.StoreInfo, ch chan<- channelItem) {\n\tdisplayAddress := net.JoinHostPort(tikv.IP, strconv.Itoa(int(tikv.Port)))\n\n\tr, err := s.getConfigItemsFromTiKV(tikv.IP, int(tikv.StatusPort))\n\tif err != nil {\n\t\tch <- channelItem{Err: ErrListConfigItemsFailed.Wrap(err, \"Failed to list TiKV config items of %s\", displayAddress)}\n\t\treturn\n\t}\n\tch <- channelItem{\n\t\tErr:                  nil,\n\t\tSourceDisplayAddress: displayAddress,\n\t\tSourceKind:           ItemKindTiKVConfig,\n\t\tValues:               r,\n\t}\n}\n\nfunc (s *Service) getConfigItemsFromTiKV(host string, statusPort int) (map[string]interface{}, error) {\n\tdata, err := s.params.TiKVClient.SendGetRequest(host, statusPort, \"/config\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn processNestedConfigAPIResponse(data)\n}\n\ntype ShowVariableItem struct {\n\tName  string `gorm:\"column:Variable_name\"`\n\tValue string `gorm:\"column:Value\"`\n}\n\nfunc (s *Service) getGlobalVariablesFromTiDBToChannel(db *gorm.DB, ch chan<- channelItem) {\n\tr, err := s.getGlobalVariablesFromTiDB(db)\n\tif err != nil {\n\t\tch <- channelItem{Err: ErrListConfigItemsFailed.Wrap(err, \"Failed to list %s variables\", distro.R().TiDB)}\n\t\treturn\n\t}\n\tch <- channelItem{\n\t\tErr:        nil,\n\t\tSourceKind: ItemKindTiDBVariable,\n\t\tValues:     r,\n\t}\n}\n\nfunc (s *Service) getGlobalVariablesFromTiDB(db *gorm.DB) (map[string]interface{}, error) {\n\tvar rows []ShowVariableItem\n\tif err := db.Raw(\"SHOW GLOBAL VARIABLES\").Find(&rows).Error; err != nil {\n\t\treturn nil, err\n\t}\n\tresult := make(map[string]interface{})\n\tfor _, r := range rows {\n\t\tresult[r.Name] = r.Value\n\t}\n\treturn result, nil\n}\n\ntype Item struct {\n\tID           string      `json:\"id\"`\n\tIsEditable   bool        `json:\"is_editable\"`\n\tIsMultiValue bool        `json:\"is_multi_value\"` // TODO: Support per-instance config\n\tValue        interface{} `json:\"value\"`          // When multi value present, this contains one of the value\n}\n\ntype AllConfigItems struct {\n\tErrors []rest.ErrorResponse `json:\"errors\"`\n\tItems  map[ItemKind][]Item  `json:\"items\"`\n}\n\nfunc (s *Service) getAllConfigItems(db *gorm.DB) (*AllConfigItems, error) {\n\ttikvInfo, _, err := topology.FetchStoreTopology(s.params.PDClient)\n\tif err != nil {\n\t\treturn nil, ErrListTopologyFailed.Wrap(err, \"Failed to list TiKV stores\")\n\t}\n\n\ttidbInfo, err := topology.FetchTiDBTopology(s.lifecycleCtx, s.params.EtcdClient)\n\tif err != nil {\n\t\treturn nil, ErrListTopologyFailed.Wrap(err, \"Failed to list %s instances\", distro.R().TiDB)\n\t}\n\n\tch := make(chan channelItem)\n\twaitItems := 0\n\n\t{\n\t\twaitItems++\n\t\tgo s.getConfigItemsFromPDToChannel(ch)\n\t}\n\t{\n\t\twaitItems++\n\t\tgo s.getGlobalVariablesFromTiDBToChannel(db, ch)\n\t}\n\tfor _, item := range tikvInfo {\n\t\t// TODO: What about tombstone stores?\n\t\twaitItems++\n\t\titem2 := item\n\t\tgo s.getConfigItemsFromTiKVToChannel(&item2, ch)\n\t}\n\tfor _, item := range tidbInfo {\n\t\twaitItems++\n\t\titem2 := item\n\t\tgo s.getConfigItemsFromTiDBToChannel(&item2, ch)\n\t}\n\n\terrors := make([]rest.ErrorResponse, 0)\n\tsuccessItems := make([]channelItem, 0)\n\n\tfor i := 0; i < waitItems; i++ {\n\t\titem := <-ch\n\t\tif item.Err != nil {\n\t\t\terrors = append(errors, rest.NewErrorResponse(item.Err))\n\t\t\tcontinue\n\t\t}\n\t\tsuccessItems = append(successItems, item)\n\t}\n\tclose(ch)\n\n\t// The first occurred value of each config item\n\tvaluesMap := make(map[ItemKind]map[string]interface{})\n\t// Number of config item key occurred to detect missing config items\n\toccurTimesMap := make(map[ItemKind]map[string]int)\n\t// Whether each config item has different values\n\tidenticalMap := make(map[ItemKind]map[string]bool)\n\t// The expected number of occur times\n\texpectedOccurTimes := make(map[ItemKind]int)\n\n\tfor _, item := range successItems {\n\t\tif _, ok := expectedOccurTimes[item.SourceKind]; !ok {\n\t\t\texpectedOccurTimes[item.SourceKind] = 1\n\t\t} else {\n\t\t\texpectedOccurTimes[item.SourceKind]++\n\t\t}\n\t\tif _, ok := valuesMap[item.SourceKind]; !ok {\n\t\t\tvaluesMap[item.SourceKind] = make(map[string]interface{})\n\t\t\toccurTimesMap[item.SourceKind] = make(map[string]int)\n\t\t\tidenticalMap[item.SourceKind] = make(map[string]bool)\n\t\t}\n\t\tfor key, value := range item.Values {\n\t\t\tif _, ok := valuesMap[item.SourceKind][key]; !ok {\n\t\t\t\tvaluesMap[item.SourceKind][key] = value\n\t\t\t\toccurTimesMap[item.SourceKind][key] = 1\n\t\t\t\tidenticalMap[item.SourceKind][key] = true\n\t\t\t} else {\n\t\t\t\toccurTimesMap[item.SourceKind][key]++\n\t\t\t\tif value != valuesMap[item.SourceKind][key] {\n\t\t\t\t\tidenticalMap[item.SourceKind][key] = false\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tresult := make(map[ItemKind][]Item)\n\tfor kind, v := range valuesMap {\n\t\tresult[kind] = make([]Item, 0)\n\t\tfor configKey, configValue := range v {\n\t\t\t// There are two cases when a config item has multiple values:\n\t\t\t// 1. Values are not equal\n\t\t\t// 2. Value is missing\n\t\t\tisMultiValue := !identicalMap[kind][configKey]\n\t\t\tvalue := configValue\n\t\t\tif !isMultiValue && occurTimesMap[kind][configKey] < expectedOccurTimes[kind] {\n\t\t\t\tisMultiValue = false\n\t\t\t}\n\n\t\t\tresult[kind] = append(result[kind], Item{\n\t\t\t\tID:           configKey,\n\t\t\t\tIsEditable:   isConfigItemEditable(kind, configKey),\n\t\t\t\tIsMultiValue: isMultiValue,\n\t\t\t\tValue:        value,\n\t\t\t})\n\t\t}\n\n\t\ts := result[kind]\n\t\tsort.Slice(s, func(i, j int) bool {\n\t\t\treturn s[i].ID < s[j].ID\n\t\t})\n\t}\n\n\treturn &AllConfigItems{\n\t\tErrors: errors,\n\t\tItems:  result,\n\t}, nil\n}\n\nfunc (s *Service) editConfig(db *gorm.DB, kind ItemKind, id string, newValue interface{}) ([]rest.ErrorResponse, error) {\n\tif !isConfigItemEditable(kind, id) {\n\t\treturn nil, ErrNotEditable.New(\"Configuration `%s` is not editable\", id)\n\t}\n\tbody := make(map[string]interface{})\n\tbody[id] = newValue\n\tbodyJSON, err := json.Marshal(&body)\n\tif err != nil {\n\t\treturn nil, ErrEditFailed.WrapWithNoMessage(err)\n\t}\n\n\tswitch kind {\n\tcase ItemKindPDConfig:\n\t\t_, err := s.params.PDClient.SendPostRequest(\"/config\", bytes.NewBuffer(bodyJSON))\n\t\tif err != nil {\n\t\t\treturn nil, ErrEditFailed.WrapWithNoMessage(err)\n\t\t}\n\tcase ItemKindTiKVConfig:\n\t\ttikvInfo, _, err := topology.FetchStoreTopology(s.params.PDClient)\n\t\tif err != nil {\n\t\t\treturn nil, ErrEditFailed.WrapWithNoMessage(ErrListTopologyFailed.WrapWithNoMessage(err))\n\t\t}\n\t\tfailures := make([]error, 0)\n\t\tfor _, kvStore := range tikvInfo {\n\t\t\t// TODO: What about tombstone stores?\n\t\t\t_, err := s.params.TiKVClient.SendPostRequest(kvStore.IP, int(kvStore.StatusPort), \"/config\", bytes.NewBuffer(bodyJSON))\n\t\t\tif err != nil {\n\t\t\t\tfailures = append(failures, ErrEditFailed.Wrap(err, \"Failed to edit config for TiKV instance `%s`\", net.JoinHostPort(kvStore.IP, strconv.Itoa(int(kvStore.Port)))))\n\t\t\t}\n\t\t}\n\t\tif len(failures) == len(tikvInfo) {\n\t\t\tif len(failures) > 0 {\n\t\t\t\treturn nil, failures[0]\n\t\t\t}\n\t\t\treturn nil, nil\n\t\t}\n\t\twarnings := make([]rest.ErrorResponse, 0)\n\t\tfor _, err := range failures {\n\t\t\twarnings = append(warnings, rest.NewErrorResponse(err))\n\t\t}\n\t\treturn warnings, nil\n\tcase ItemKindTiDBVariable:\n\t\t// We have checked the correctness of id, so no need to worry about injections\n\t\tif err := db.Exec(fmt.Sprintf(\"SET GLOBAL %s = ?\", id), newValue).Error; err != nil {\n\t\t\treturn nil, ErrEditFailed.WrapWithNoMessage(err)\n\t\t}\n\tdefault:\n\t\treturn nil, ErrEditFailed.New(\"Edit failed, not implemented\")\n\t}\n\n\treturn nil, nil\n}\n"
  },
  {
    "path": "pkg/apiserver/conprof/module.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage conprof\n\nimport (\n\t\"go.uber.org/fx\"\n)\n\nvar Module = fx.Options(\n\tfx.Provide(newService),\n\tfx.Invoke(registerRouter),\n)\n"
  },
  {
    "path": "pkg/apiserver/conprof/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// conprof is short for continuous profiling\npackage conprof\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/util/featureflag\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\ntype ServiceParams struct {\n\tfx.In\n\n\tEtcdClient   *clientv3.Client\n\tConfig       *config.Config\n\tNgmProxy     *utils.NgmProxy\n\tFeatureFlags *featureflag.Registry\n}\n\ntype Service struct {\n\tFeatureFlagConprof *featureflag.FeatureFlag\n\n\tparams       ServiceParams\n\tlifecycleCtx context.Context\n}\n\nfunc newService(lc fx.Lifecycle, p ServiceParams) *Service {\n\ts := &Service{\n\t\tFeatureFlagConprof: p.FeatureFlags.Register(\"conprof\", \">= 5.3.0\"),\n\t\tparams:             p,\n\t}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\ts.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn s\n}\n\n// Register register the handlers to the service.\nfunc registerRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/continuous_profiling\")\n\n\tendpoint.Use(s.FeatureFlagConprof.VersionGuard())\n\t{\n\t\tendpoint.GET(\"/config\", auth.MWAuthRequired(), s.params.NgmProxy.Route(\"/config\"))\n\t\tendpoint.POST(\"/config\", auth.MWAuthRequired(), auth.MWRequireWritePriv(), s.params.NgmProxy.Route(\"/config\"))\n\t\tendpoint.GET(\"/components\", auth.MWAuthRequired(), s.params.NgmProxy.Route(\"/continuous_profiling/components\"))\n\t\tendpoint.GET(\"/estimate_size\", auth.MWAuthRequired(), s.params.NgmProxy.Route(\"/continuous_profiling/estimate_size\"))\n\t\tendpoint.GET(\"/group_profiles\", auth.MWAuthRequired(), s.params.NgmProxy.Route(\"/continuous_profiling/group_profiles\"))\n\t\tendpoint.GET(\"/group_profile/detail\", auth.MWAuthRequired(), s.params.NgmProxy.Route(\"/continuous_profiling/group_profile/detail\"))\n\n\t\tendpoint.GET(\"/action_token\", auth.MWAuthRequired(), s.GenConprofActionToken)\n\t\tendpoint.GET(\"/download\", s.parseJWTToken, s.params.NgmProxy.Route(\"/continuous_profiling/download\"))\n\t\tendpoint.GET(\"/single_profile/view\", s.parseJWTToken, s.params.NgmProxy.Route(\"/continuous_profiling/single_profile/view\"))\n\t}\n}\n\ntype ContinuousProfilingConfig struct {\n\tEnable               bool `json:\"enable\"`\n\tProfileSeconds       int  `json:\"profile_seconds\"`\n\tIntervalSeconds      int  `json:\"interval_seconds\"`\n\tTimeoutSeconds       int  `json:\"timeout_seconds\"`\n\tDataRetentionSeconds int  `json:\"data_retention_seconds\"`\n}\n\ntype NgMonitoringConfig struct {\n\tContinuousProfiling ContinuousProfilingConfig `json:\"continuous_profiling\"`\n}\n\n// @Summary Get Continuous Profiling Config\n// @Success 200 {object} NgMonitoringConfig\n// @Router /continuous_profiling/config [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) ConprofConfig(_ *gin.Context) {\n\t// dummy, for generate openapi\n}\n\n// @Summary Update Continuous Profiling Config\n// @Router /continuous_profiling/config [post]\n// @Param request body NgMonitoringConfig true \"Request body\"\n// @Security JwtAuth\n// @Success 200 {string} string \"ok\"\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) UpdateConprofConfig(_ *gin.Context) {\n\t// dummy, for generate openapi\n}\n\ntype Component struct {\n\tName       string `json:\"name\"`\n\tIP         string `json:\"ip\"`\n\tPort       uint   `json:\"port\"`\n\tStatusPort uint   `json:\"status_port\"`\n}\n\n// @Summary Get current scraping components\n// @Success 200 {array} Component\n// @Router /continuous_profiling/components [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) ConprofComponents(_ *gin.Context) {\n\t// dummy, for generate openapi\n}\n\ntype EstimateSizeRes struct {\n\tInstanceCount int `json:\"instance_count\"`\n\tProfileSize   int `json:\"profile_size\"`\n}\n\n// @Summary Get Estimate Size\n// @Router /continuous_profiling/estimate_size [get]\n// @Security JwtAuth\n// @Success 200 {object} EstimateSizeRes\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) EstimateSize(_ *gin.Context) {\n\t// dummy, for generate openapi\n}\n\ntype GetGroupProfileReq struct {\n\tBeginTime int `json:\"begin_time\"`\n\tEndTime   int `json:\"end_time\"`\n}\n\ntype ComponentNum struct {\n\tTiDB    int `json:\"tidb\"`\n\tPD      int `json:\"pd\"`\n\tTiKV    int `json:\"tikv\"`\n\tTiFlash int `json:\"tiflash\"`\n\tTiCDC   int `json:\"ticdc\"`\n}\n\ntype GroupProfiles struct {\n\tTs          int64        `json:\"ts\"`\n\tProfileSecs int          `json:\"profile_duration_secs\"`\n\tState       string       `json:\"state\"`\n\tCompNum     ComponentNum `json:\"component_num\"`\n}\n\ntype GroupProfileDetail struct {\n\tTs             int64           `json:\"ts\"`\n\tProfileSecs    int             `json:\"profile_duration_secs\"`\n\tState          string          `json:\"state\"`\n\tTargetProfiles []ProfileDetail `json:\"target_profiles\"`\n}\n\ntype ProfileDetail struct {\n\tState  string `json:\"state\"`\n\tError  string `json:\"error\"`\n\tType   string `json:\"profile_type\"`\n\tTarget Target `json:\"target\"`\n}\n\ntype Target struct {\n\tComponent string `json:\"component\"`\n\tAddress   string `json:\"address\"`\n}\n\n// @Summary Get Group Profiles\n// @Router /continuous_profiling/group_profiles [get]\n// @Param q query GetGroupProfileReq true \"Query\"\n// @Security JwtAuth\n// @Success 200 {array} GroupProfiles\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) ConprofGroupProfiles(_ *gin.Context) {\n\t// dummy, for generate openapi\n}\n\n// @Summary Get Group Profile Detail\n// @Router /continuous_profiling/group_profile/detail [get]\n// @Param ts query number true \"timestamp\"\n// @Security JwtAuth\n// @Success 200 {object} GroupProfileDetail\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) ConprofGroupProfileDetail(_ *gin.Context) {\n\t// dummy, for generate openapi\n}\n\n// @Summary Get action token for download or view profile\n// @Router /continuous_profiling/action_token [get]\n// @Param q query string true \"target query string\"\n// @Security JwtAuth\n// @Success 200 {string} string\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) GenConprofActionToken(c *gin.Context) {\n\tq := c.Query(\"q\")\n\ttoken, err := utils.NewJWTString(\"conprof\", q)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.String(http.StatusOK, token)\n}\n\n// @Summary Download Group Profile files\n// @Router /continuous_profiling/download [get]\n// @Param ts query number true \"timestamp\"\n// @Security JwtAuth\n// @Produce application/x-gzip\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) ConprofDownload(_ *gin.Context) {\n\t// dummy, for generate openapi\n}\n\nfunc (s *Service) parseJWTToken(c *gin.Context) {\n\ttoken := c.Query(\"token\")\n\tqueryStr, err := utils.ParseJWTString(\"conprof\", token)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.WrapWithNoMessage(err))\n\t\tc.Abort()\n\t\treturn\n\t}\n\tc.Request.URL.RawQuery = queryStr\n}\n\ntype ViewSingleProfileReq struct {\n\tTs          int    `json:\"ts\"`\n\tProfileType string `json:\"profile_type\"`\n\tComponent   string `json:\"component\"`\n\tAddress     string `json:\"address\"`\n}\n\n// @Summary View Single Profile files\n// @Router /continuous_profiling/single_profile/view [get]\n// @Param q query ViewSingleProfileReq true \"Query\"\n// @Security JwtAuth\n// @Produce html\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) ConprofViewProfile(_ *gin.Context) {\n\t// dummy, for generate openapi\n}\n"
  },
  {
    "path": "pkg/apiserver/deadlock/model.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage deadlock\n\nimport \"time\"\n\ntype Model struct {\n\tInstance       string    `gorm:\"column:INSTANCE\" json:\"instance\"`\n\tDeadlockID     uint64    `gorm:\"column:DEADLOCK_ID\" json:\"id\"`\n\tOccurTime      time.Time `gorm:\"column:OCCUR_TIME\" json:\"occur_time\"`\n\tRetryable      bool      `gorm:\"column:RETRYABLE\" json:\"retryable\"`\n\tTryLockTrxID   uint64    `gorm:\"column:TRY_LOCK_TRX_ID\" json:\"try_lock_trx_id\"`\n\tTryHoldingLock uint64    `gorm:\"column:TRX_HOLDING_LOCK\" json:\"trx_holding_lock\"`\n\tCurrentSQL     string    `gorm:\"column:CURRENT_SQL_DIGEST_TEXT\" json:\"current_sql\"`\n\tKey            string    `gorm:\"column:KEY\" json:\"key\"`\n\tKeyInfo        string    `gorm:\"column:KEY_INFO\" json:\"key_info\"`\n}\n"
  },
  {
    "path": "pkg/apiserver/deadlock/module.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage deadlock\n\nimport \"go.uber.org/fx\"\n\nvar Module = fx.Options(\n\tfx.Provide(newService),\n\tfx.Invoke(registerRouter),\n)\n"
  },
  {
    "path": "pkg/apiserver/deadlock/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage deadlock\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\tcommonUtils \"github.com/pingcap/tidb-dashboard/pkg/utils\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nconst (\n\tDeadlockTable = \"INFORMATION_SCHEMA.CLUSTER_DEADLOCKS\"\n)\n\ntype ServiceParams struct {\n\tfx.In\n\tTiDBClient *tidb.Client\n\tSysSchema  *commonUtils.SysSchema\n}\n\ntype Service struct {\n\tparams ServiceParams\n}\n\nfunc newService(p ServiceParams) *Service {\n\treturn &Service{params: p}\n}\n\nfunc registerRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/deadlock\")\n\tendpoint.Use(\n\t\tauth.MWAuthRequired(),\n\t\tutils.MWConnectTiDB(s.params.TiDBClient),\n\t)\n\t{\n\t\tendpoint.GET(\"/list\", s.getList)\n\t}\n}\n\n// @Summary List all deadlock records\n// @Success 200 {array} Model\n// @Router /deadlock/list [get]\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getList(c *gin.Context) {\n\tdb := utils.GetTiDBConnection(c)\n\tvar results []Model\n\terr := db.Table(DeadlockTable).Find(&results).Error\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tc.JSON(http.StatusOK, results)\n}\n"
  },
  {
    "path": "pkg/apiserver/debugapi/apis.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage debugapi\n\nimport (\n\t\"github.com/go-resty/resty/v2\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/debugapi/endpoint\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/topo\"\n)\n\nvar commonParamPprofKinds = endpoint.APIParamEnum(\"kind\", true, []endpoint.EnumItemDefinition{\n\t{Value: \"allocs\"},\n\t{Value: \"block\"},\n\t{Value: \"cmdline\"},\n\t{Value: \"goroutine\"},\n\t{Value: \"heap\"},\n\t{Value: \"mutex\"},\n\t{Value: \"profile\"},\n\t{Value: \"threadcreate\"},\n\t{Value: \"trace\"},\n})\n\nvar commonParamPprofSeconds = endpoint.APIParamEnum(\"seconds\", false, []endpoint.EnumItemDefinition{\n\t{Value: \"10\", DisplayAs: \"10s\"},\n\t{Value: \"30\", DisplayAs: \"30s\"},\n\t{Value: \"60\", DisplayAs: \"60s\"},\n})\n\nvar commonParamPprofDebug = endpoint.APIParamEnum(\"debug\", false, []endpoint.EnumItemDefinition{\n\t{Value: \"0\", DisplayAs: \"Raw Format\"},\n\t{Value: \"1\", DisplayAs: \"Legacy Text Format\"},\n\t{Value: \"2\", DisplayAs: \"Text Format\"},\n})\n\nvar commonParamConfigFormat = endpoint.APIParamEnum(\"format\", false, []endpoint.EnumItemDefinition{\n\t{Value: \"toml\"},\n\t{Value: \"json\"},\n})\n\nvar apiEndpoints = []endpoint.APIDefinition{\n\t// TiDB Endpoints\n\t{\n\t\tID:        \"tidb_stats_by_table\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/stats/dump/{db}/{table}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamDBName(\"db\", true),\n\t\t\tendpoint.APIParamTableName(\"table\", true),\n\t\t},\n\t},\n\t{\n\t\tID:        \"tidb_stats_by_table_timestamp\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/stats/dump/{db}/{table}/{yyyyMMddHHmmss}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamDBName(\"db\", true),\n\t\t\tendpoint.APIParamTableName(\"table\", true),\n\t\t\tendpoint.APIParamText(\"yyyyMMddHHmmss\", true),\n\t\t},\n\t},\n\t{\n\t\tID:        \"tidb_settings\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/settings\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"tidb_schema\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/schema\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamTableID(\"table_id\", false),\n\t\t},\n\t},\n\t{\n\t\tID:        \"tidb_schema_by_db\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/schema/{db}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamDBName(\"db\", true),\n\t\t},\n\t},\n\t{\n\t\tID:        \"tidb_schema_by_table\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/schema/{db}/{table}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamDBName(\"db\", true),\n\t\t\tendpoint.APIParamTableName(\"table\", true),\n\t\t},\n\t},\n\t{\n\t\tID:        \"tidb_schema_by_table_id\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/db-table/{tableID}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamTableID(\"tableID\", true),\n\t\t},\n\t},\n\t{\n\t\tID:        \"tidb_ddl_history\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/ddl/history\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamInt(\"start_job_id\", false),\n\t\t\tendpoint.APIParamIntWithDefaultVal(\"limit\", false, \"10\"),\n\t\t},\n\t},\n\t{\n\t\tID:        \"tidb_server_info\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/info\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"tidb_all_servers_info\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/info/all\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"tidb_all_regions_meta\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/regions/meta\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"tidb_region_meta_by_id\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/regions/{regionID}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamInt(\"regionID\", true),\n\t\t},\n\t},\n\t{\n\t\tID:        \"tidb_table_regions\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/tables/{db}/{table}/regions\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamDBName(\"db\", true),\n\t\t\tendpoint.APIParamTableName(\"table\", true),\n\t\t},\n\t},\n\t{\n\t\tID:        \"tidb_hot_regions\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/regions/hot\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"tidb_pprof\",\n\t\tComponent: topo.KindTiDB,\n\t\tPath:      \"/debug/pprof/{kind}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tcommonParamPprofKinds,\n\t\t},\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tcommonParamPprofSeconds,\n\t\t\tcommonParamPprofDebug,\n\t\t},\n\t},\n\t// PD Endpoints\n\t{\n\t\tID:        \"pd_cluster_info\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/cluster\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"pd_cluster_status\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/cluster/status\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"pd_configs_all\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/config\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"pd_health\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/health\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"pd_hot_read\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/hotspot/regions/read\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"pd_hot_write\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/hotspot/regions/write\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"pd_hot_stores\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/hotspot/stores\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"pd_labels_all\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/labels\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"pd_members_all\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/members\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"pd_leader\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/leader\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"pd_operators\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/operators\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamEnum(\"kind\", false, []endpoint.EnumItemDefinition{\n\t\t\t\t{Value: \"admin\"},\n\t\t\t\t{Value: \"leader\"},\n\t\t\t\t{Value: \"region\"},\n\t\t\t}),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_regions_all\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/regions\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"pd_region_by_id\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/region/id/{regionID}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamInt(\"regionID\", true),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_region_by_key\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/region/key/{regionKey}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamPDKey(\"regionKey\", true),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_regions_sibling_by_id\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/regions/sibling/{regionID}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamInt(\"regionID\", true),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_regions_store\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/regions/store/{storeID}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamInt(\"storeID\", true),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_regions_by_top_read\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/regions/readflow\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamInt(\"limit\", false),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_regions_by_top_write\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/regions/writeflow\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamInt(\"limit\", false),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_regions_by_top_conf_ver\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/regions/confver\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamInt(\"limit\", false),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_regions_by_top_version\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/regions/version\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamInt(\"limit\", false),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_regions_by_top_size\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/regions/size\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamInt(\"limit\", false),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_regions_by_state\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/regions/check/{state}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamEnum(\"state\", true, []endpoint.EnumItemDefinition{\n\t\t\t\t{Value: \"miss-peer\", DisplayAs: \"Regions that miss peer\"},\n\t\t\t\t{Value: \"extra-peer\", DisplayAs: \"Regions that has extra peer\"},\n\t\t\t\t{Value: \"down-peer\", DisplayAs: \"Regions that has down peer\"},\n\t\t\t\t{Value: \"pending-peer\", DisplayAs: \"Regions that has pending peer\"},\n\t\t\t\t{Value: \"offline-peer\", DisplayAs: \"Regions that has offline peer\"},\n\t\t\t\t{Value: \"empty-region\", DisplayAs: \"Empty regions\"},\n\t\t\t}),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_schedulers_all\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/schedulers\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamEnum(\"status\", false, []endpoint.EnumItemDefinition{\n\t\t\t\t{Value: \"paused\"},\n\t\t\t\t{Value: \"disabled\"},\n\t\t\t}),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_stores_all\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/stores\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\t// TODO: Actually it accepts multiple values.\n\t\t\tendpoint.APIParamEnum(\"state\", false, []endpoint.EnumItemDefinition{\n\t\t\t\t{Value: \"0\", DisplayAs: \"Up\"},\n\t\t\t\t{Value: \"1\", DisplayAs: \"Offline\"},\n\t\t\t\t{Value: \"2\", DisplayAs: \"Tombstone\"},\n\t\t\t}),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_stores_by_label\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/labels/stores\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamText(\"name\", true),\n\t\t\tendpoint.APIParamText(\"value\", true),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_store_by_id\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/pd/api/v1/store/{storeID}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tendpoint.APIParamInt(\"storeID\", true),\n\t\t},\n\t},\n\t{\n\t\tID:        \"pd_pprof\",\n\t\tComponent: topo.KindPD,\n\t\tPath:      \"/debug/pprof/{kind}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tcommonParamPprofKinds,\n\t\t},\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tcommonParamPprofSeconds,\n\t\t\tcommonParamPprofDebug,\n\t\t},\n\t},\n\t// TiKV Endpoints\n\t{\n\t\tID:        \"tikv_config\",\n\t\tComponent: topo.KindTiKV,\n\t\tPath:      \"/config\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"tikv_pprof_profile\",\n\t\tComponent: topo.KindTiKV,\n\t\tPath:      \"/debug/pprof/profile\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tcommonParamPprofSeconds,\n\t\t},\n\t\tBeforeSendRequest: func(req *httpclient.LazyRequest) {\n\t\t\treq.SetHeader(\"Content-Type\", \"application/protobuf\")\n\t\t},\n\t},\n\t// TiFlash Endpoints\n\t{\n\t\tID:        \"tiflash_config\",\n\t\tComponent: topo.KindTiFlash,\n\t\tPath:      \"/config\",\n\t\tMethod:    resty.MethodGet,\n\t},\n\t{\n\t\tID:        \"tiflash_pprof_profile\",\n\t\tComponent: topo.KindTiFlash,\n\t\tPath:      \"/debug/pprof/profile\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tcommonParamPprofSeconds,\n\t\t},\n\t\tBeforeSendRequest: func(req *httpclient.LazyRequest) {\n\t\t\treq.SetHeader(\"Content-Type\", \"application/protobuf\")\n\t\t},\n\t},\n\t// TiProxy Endpoints\n\t{\n\t\tID:        \"tiproxy_config\",\n\t\tComponent: topo.KindTiProxy,\n\t\tPath:      \"/api/admin/config\",\n\t\tMethod:    resty.MethodGet,\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tcommonParamConfigFormat,\n\t\t},\n\t},\n\t{\n\t\tID:        \"tiproxy_pprof\",\n\t\tComponent: topo.KindTiProxy,\n\t\tPath:      \"/debug/pprof/{kind}\",\n\t\tMethod:    resty.MethodGet,\n\t\tPathParams: []endpoint.APIParamDefinition{\n\t\t\tcommonParamPprofKinds,\n\t\t},\n\t\tQueryParams: []endpoint.APIParamDefinition{\n\t\t\tcommonParamPprofSeconds,\n\t\t\tcommonParamPprofDebug,\n\t\t},\n\t},\n}\n"
  },
  {
    "path": "pkg/apiserver/debugapi/endpoint/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage endpoint\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "pkg/apiserver/debugapi/endpoint/errors.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage endpoint\n\nimport (\n\t\"github.com/joomcode/errorx\"\n)\n\nvar (\n\tErrNS               = errorx.NewNamespace(\"debug_api.endpoint\")\n\tErrUnknownComponent = ErrNS.NewType(\"unknown_component\")\n\tErrInvalidEndpoint  = ErrNS.NewType(\"invalid_endpoint\")\n)\n"
  },
  {
    "path": "pkg/apiserver/debugapi/endpoint/models.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage endpoint\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/topo\"\n)\n\n// APIDefinition defines what an API endpoints accepts.\n// APIDefinition can be \"resolved\" to become a request when its parameter values are given via RequestPayload.\ntype APIDefinition struct {\n\tID          string               `json:\"id\"`\n\tComponent   topo.Kind            `json:\"component\"`\n\tPath        string               `json:\"path\"`\n\tMethod      string               `json:\"method\"`\n\tPathParams  []APIParamDefinition `json:\"path_params\"`  // e.g. /stats/dump/{db}/{table} -> db, table\n\tQueryParams []APIParamDefinition `json:\"query_params\"` // e.g. /debug/pprof?seconds=1 -> seconds\n\n\tBeforeSendRequest func(req *httpclient.LazyRequest) `json:\"-\"`\n}\n\ntype APIParamResolveFn func(value string) ([]string, error)\n\n// APIParamDefinition defines what an API endpoint parameter accepts and how it should look like in the UI.\n// Usually this struct doesn't need to be manually constructed. Use APIParamXxx() helpers.\ntype APIParamDefinition struct {\n\tName             string            `json:\"name\"`\n\tRequired         bool              `json:\"required\"`\n\tUIComponentKind  string            `json:\"ui_kind\"`\n\tUIComponentProps interface{}       `json:\"ui_props\"` // varies by different ui kinds\n\tOnResolve        APIParamResolveFn `json:\"-\"`\n}\n\nfunc (d *APIParamDefinition) Resolve(value string) ([]string, error) {\n\tif d.OnResolve == nil {\n\t\treturn []string{value}, nil\n\t}\n\treturn d.OnResolve(value)\n}\n\n// UIComponentTextProps is the type of UIComponentProps when UIComponentKind is \"text\".\ntype UIComponentTextProps struct {\n\tPlaceholder string `json:\"placeholder\"`\n\tDefaultVal  string `json:\"default_val\"`\n}\n\nfunc APIParamText(name string, required bool) APIParamDefinition {\n\treturn APIParamDefinition{\n\t\tName:            name,\n\t\tRequired:        required,\n\t\tUIComponentKind: \"text\",\n\t}\n}\n\nfunc APIParamInt(name string, required bool) APIParamDefinition {\n\treturn APIParamIntWithDefaultVal(name, required, \"\")\n}\n\nfunc APIParamIntWithDefaultVal(name string, required bool, defVal string) APIParamDefinition {\n\tplaceHolder := \"(int)\"\n\tif defVal != \"\" {\n\t\tplaceHolder = fmt.Sprintf(\"(int, default: %s)\", defVal)\n\t}\n\treturn APIParamDefinition{\n\t\tName:            name,\n\t\tRequired:        required,\n\t\tUIComponentKind: \"text\",\n\t\tUIComponentProps: UIComponentTextProps{\n\t\t\tPlaceholder: placeHolder,\n\t\t\tDefaultVal:  defVal,\n\t\t},\n\t\tOnResolve: func(value string) ([]string, error) {\n\t\t\tif _, err := strconv.Atoi(value); err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"'%s' is not a int\", value)\n\t\t\t}\n\t\t\treturn []string{value}, nil\n\t\t},\n\t}\n}\n\nfunc APIParamDBName(name string, required bool) APIParamDefinition {\n\treturn APIParamDefinition{\n\t\tName:            name,\n\t\tRequired:        required,\n\t\tUIComponentKind: \"db_dropdown\",\n\t}\n}\n\nfunc APIParamTableName(name string, required bool) APIParamDefinition {\n\treturn APIParamDefinition{\n\t\tName:            name,\n\t\tRequired:        required,\n\t\tUIComponentKind: \"table_dropdown\",\n\t}\n}\n\nfunc APIParamTableID(name string, required bool) APIParamDefinition {\n\treturn APIParamDefinition{\n\t\tName:            name,\n\t\tRequired:        required,\n\t\tUIComponentKind: \"table_id_dropdown\",\n\t}\n}\n\n// UIComponentDropdownProps is the type of UIComponentProps when UIComponentKind is \"dropdown\".\ntype UIComponentDropdownProps struct {\n\tItems []EnumItemDefinition `json:\"items\"`\n}\n\ntype EnumItemDefinition struct {\n\tValue     string `json:\"value\"`\n\tDisplayAs string `json:\"display_as\"` // Optional\n}\n\nfunc APIParamEnum(name string, required bool, items []EnumItemDefinition) APIParamDefinition {\n\treturn APIParamDefinition{\n\t\tName:             name,\n\t\tRequired:         required,\n\t\tUIComponentKind:  \"dropdown\",\n\t\tUIComponentProps: UIComponentDropdownProps{Items: items},\n\t\tOnResolve: func(value string) ([]string, error) {\n\t\t\tfor _, item := range items {\n\t\t\t\tif item.Value == value {\n\t\t\t\t\treturn []string{value}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn nil, fmt.Errorf(\"'%s' is not a valid enum value\", value)\n\t\t},\n\t}\n}\n\n// Below are some special API param kinds.\n\nfunc APIParamPDKey(name string, required bool) APIParamDefinition {\n\treturn APIParamDefinition{\n\t\tName:            name,\n\t\tRequired:        required,\n\t\tUIComponentKind: \"text\",\n\t\tUIComponentProps: UIComponentTextProps{\n\t\t\tPlaceholder: \"(hex key, e.g. 748000...)\",\n\t\t},\n\t\tOnResolve: func(value string) ([]string, error) {\n\t\t\tkeyBinary, err := hex.DecodeString(value)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, fmt.Errorf(\"'%s' is not a valid hex key\", value)\n\t\t\t}\n\t\t\treturn []string{string(keyBinary)}, nil\n\t\t},\n\t}\n}\n"
  },
  {
    "path": "pkg/apiserver/debugapi/endpoint/models_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage endpoint\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestAPIParamPDKey(t *testing.T) {\n\tp := APIParamPDKey(\"foo\", true)\n\trequire.Equal(t, p.Name, \"foo\")\n\trequire.True(t, p.Required)\n\n\tv, err := p.Resolve(\"fooo\")\n\trequire.Nil(t, v)\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"'fooo' is not a valid hex key\")\n\n\tv, err = p.Resolve(\"0x0011\")\n\trequire.Nil(t, v)\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"'0x0011' is not a valid hex key\")\n\n\tv, err = p.Resolve(\"0011\")\n\trequire.Equal(t, []string{\"\\x00\\x11\"}, v)\n\trequire.Nil(t, err)\n}\n\nfunc TestAPIParamEnum(t *testing.T) {\n\tp := APIParamEnum(\"bar\", false, []EnumItemDefinition{\n\t\t{Value: \"v1\"},\n\t\t{Value: \"v2\", DisplayAs: \"d1\"},\n\t})\n\trequire.Equal(t, p.Name, \"bar\")\n\trequire.False(t, p.Required)\n\n\tv, err := p.Resolve(\"x\")\n\trequire.Nil(t, v)\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"'x' is not a valid enum value\")\n\n\tv, err = p.Resolve(\"\")\n\trequire.Nil(t, v)\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"'' is not a valid enum value\")\n\n\tv, err = p.Resolve(\"v1\")\n\trequire.Equal(t, []string{\"v1\"}, v)\n\trequire.Nil(t, err)\n\n\tv, err = p.Resolve(\"d1\")\n\trequire.Nil(t, v)\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"'d1' is not a valid enum value\")\n}\n\nfunc TestAPIParamInt(t *testing.T) {\n\tp := APIParamInt(\"ix\", true)\n\trequire.Equal(t, p.Name, \"ix\")\n\trequire.True(t, p.Required)\n\n\tv, err := p.Resolve(\"ab\")\n\trequire.Nil(t, v)\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"'ab' is not a int\")\n\n\tv, err = p.Resolve(\"123.4\")\n\trequire.Nil(t, v)\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"'123.4' is not a int\")\n\n\tv, err = p.Resolve(\"123\")\n\trequire.Equal(t, []string{\"123\"}, v)\n\trequire.Nil(t, err)\n}\n"
  },
  {
    "path": "pkg/apiserver/debugapi/endpoint/payload.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage endpoint\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"strconv\"\n\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/topology\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/pdclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/schedulingclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/ticdcclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tidbclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tiflashclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tikvclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tiproxyclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tsoclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n\t\"github.com/pingcap/tidb-dashboard/util/topo\"\n)\n\n// RequestPayload describes how a server-side request should be sent, by describing the API endpoint to send\n// and its parameter values. The content of this struct is specified by the user so that it should be carefully\n// checked.\ntype RequestPayload struct {\n\tAPI         string            `json:\"api_id\"`\n\tHost        string            `json:\"host\"`\n\tPort        int               `json:\"port\"`\n\tParamValues map[string]string `json:\"param_values\"`\n}\n\ntype HTTPClients struct {\n\tPDAPIClient            *pdclient.APIClient\n\tTiDBStatusClient       *tidbclient.StatusClient\n\tTiKVStatusClient       *tikvclient.StatusClient\n\tTiFlashStatusClient    *tiflashclient.StatusClient\n\tTiCDCStatusClient      *ticdcclient.StatusClient\n\tTiProxyStatusClient    *tiproxyclient.StatusClient\n\tTSOStatusClient        *tsoclient.StatusClient\n\tSchedulingStatusClient *schedulingclient.StatusClient\n}\n\nfunc (c HTTPClients) GetHTTPClientByNodeKind(kind topo.Kind) *httpclient.Client {\n\tswitch kind {\n\tcase topo.KindPD:\n\t\tif c.PDAPIClient == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn c.PDAPIClient.Client\n\tcase topo.KindTiDB:\n\t\tif c.TiDBStatusClient == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn c.TiDBStatusClient.Client\n\tcase topo.KindTiKV:\n\t\tif c.TiKVStatusClient == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn c.TiKVStatusClient.Client\n\tcase topo.KindTiFlash:\n\t\tif c.TiFlashStatusClient == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn c.TiFlashStatusClient.Client\n\tcase topo.KindTiCDC:\n\t\tif c.TiCDCStatusClient == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn c.TiCDCStatusClient.Client\n\tcase topo.KindTiProxy:\n\t\tif c.TiProxyStatusClient == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn c.TiProxyStatusClient.Client\n\tdefault:\n\t\treturn nil\n\t}\n}\n\n// RequestPayloadResolver resolves the request payload using specified API definitions.\n//\n// The relationship is below:\n//\n//\tRequestPayload ---(RequestPayloadResolver.ResolvePayload)---> ResolvedRequestPayload\ntype RequestPayloadResolver struct {\n\tapis       []APIDefinition\n\tapiMapByID map[string]*APIDefinition\n}\n\nfunc NewRequestPayloadResolver(apis []APIDefinition, acceptedClients HTTPClients) *RequestPayloadResolver {\n\t// Filter APIs by accepted clients\n\tfilteredAPIs := make([]APIDefinition, 0, len(apis))\n\tfor _, api := range apis {\n\t\thttpClient := acceptedClients.GetHTTPClientByNodeKind(api.Component)\n\t\tif httpClient != nil {\n\t\t\tfilteredAPIs = append(filteredAPIs, api)\n\t\t}\n\t}\n\n\tapiMapByID := make(map[string]*APIDefinition)\n\tfor idx := range filteredAPIs {\n\t\tapi := &filteredAPIs[idx]\n\t\tapiMapByID[api.ID] = api\n\t}\n\treturn &RequestPayloadResolver{\n\t\tapis:       filteredAPIs,\n\t\tapiMapByID: apiMapByID,\n\t}\n}\n\nfunc (r *RequestPayloadResolver) ListAPIs() []APIDefinition {\n\treturn r.apis\n}\n\nvar pathReplaceRegexp = regexp.MustCompile(`\\{(\\w+)\\}`)\n\nfunc (r *RequestPayloadResolver) ResolvePayload(payload RequestPayload) (*ResolvedRequestPayload, error) {\n\tif payload.ParamValues == nil {\n\t\t// let's make life easier\n\t\tpayload.ParamValues = make(map[string]string)\n\t}\n\n\tapi, ok := r.apiMapByID[payload.API]\n\tif !ok {\n\t\treturn nil, rest.ErrBadRequest.New(\"Unknown API endpoint '%s'\", payload.API)\n\t}\n\n\tresolvedPayload := &ResolvedRequestPayload{\n\t\tapi:         api,\n\t\thost:        payload.Host,\n\t\tport:        payload.Port,\n\t\tpath:        \"\", // will be filled later\n\t\tqueryValues: url.Values{},\n\t}\n\n\t// Resolve path\n\tpathValues := map[string]string{}\n\tfor _, pathParam := range api.PathParams {\n\t\t// path param should always be required\n\t\tif payload.ParamValues[pathParam.Name] == \"\" {\n\t\t\treturn nil, rest.ErrBadRequest.New(\"parameter '%s' is required\", pathParam.Name)\n\t\t}\n\n\t\tresolvedValue, err := pathParam.Resolve(payload.ParamValues[pathParam.Name])\n\t\tif err != nil {\n\t\t\treturn nil, rest.ErrBadRequest.Wrap(err, \"parameter '%s' is invalid\", pathParam.Name)\n\t\t}\n\n\t\tpathValues[pathParam.Name] = resolvedValue[0]\n\t}\n\tresolvedPayload.path = pathReplaceRegexp.ReplaceAllStringFunc(api.Path, func(s string) string {\n\t\tkey := pathReplaceRegexp.ReplaceAllString(s, \"${1}\")\n\t\tval := url.PathEscape(pathValues[key])\n\t\treturn val\n\t})\n\n\t// Resolve query\n\tfor _, queryParam := range api.QueryParams {\n\t\tif payload.ParamValues[queryParam.Name] == \"\" {\n\t\t\tif queryParam.Required {\n\t\t\t\treturn nil, rest.ErrBadRequest.New(\"parameter '%s' is required\", queryParam.Name)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\tresolvedValue, err := queryParam.Resolve(payload.ParamValues[queryParam.Name])\n\t\tif err != nil {\n\t\t\treturn nil, rest.ErrBadRequest.Wrap(err, \"parameter '%s' is invalid\", queryParam.Name)\n\t\t}\n\n\t\tresolvedPayload.queryValues[queryParam.Name] = resolvedValue\n\t}\n\n\treturn resolvedPayload, nil\n}\n\n// ResolvedRequestPayload describes the final request to send by the server.\n// It is constructed by from the RequestPayload and the corresponding APIDefinition.\ntype ResolvedRequestPayload struct {\n\tapi         *APIDefinition\n\thost        string\n\tport        int\n\tpath        string\n\tqueryValues url.Values\n}\n\nfunc (p *ResolvedRequestPayload) SendRequestAndPipe(\n\tctx context.Context,\n\tclientsToUse HTTPClients,\n\tetcdClient *clientv3.Client,\n\tpdClient *pd.Client,\n\tw io.Writer,\n) (respNoBody *http.Response, err error) {\n\tif etcdClient != nil && pdClient != nil { // It can only be false in tests.\n\t\tif err := p.verifyEndpoint(ctx, etcdClient, pdClient); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\thttpClient := clientsToUse.GetHTTPClientByNodeKind(p.api.Component)\n\tif httpClient == nil {\n\t\treturn nil, ErrUnknownComponent.New(\"Unknown component '%s'\", p.api.Component)\n\t}\n\treq := httpClient.LR().\n\t\tSetDebugTag(\"origin:debug_api\").\n\t\tSetTLSAwareBaseURL(fmt.Sprintf(\"http://%s\", net.JoinHostPort(p.host, strconv.Itoa(p.port)))).\n\t\tSetMethod(p.api.Method).\n\t\tSetURL(p.path).\n\t\tSetQueryParamsFromValues(p.queryValues)\n\tif p.api.BeforeSendRequest != nil {\n\t\tp.api.BeforeSendRequest(req)\n\t}\n\tresp := req.Send()\n\t_, respNoBody, err = resp.PipeBody(w)\n\treturn\n}\n\nfunc (p *ResolvedRequestPayload) verifyEndpoint(ctx context.Context, etcdClient *clientv3.Client, pdClient *pd.Client) error {\n\tswitch p.api.Component {\n\tcase topo.KindTiDB:\n\t\tinfos, err := topology.FetchTiDBTopology(ctx, etcdClient)\n\t\tif err != nil {\n\t\t\treturn ErrInvalidEndpoint.Wrap(err, \"failed to fetch tidb topology\")\n\t\t}\n\t\tmatched := false\n\t\tfor _, info := range infos {\n\t\t\tif info.IP == p.host && info.StatusPort == uint(p.port) {\n\t\t\t\tmatched = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\treturn ErrInvalidEndpoint.New(\"invalid endpoint '%s:%d'\", p.host, p.port)\n\t\t}\n\tcase topo.KindTiKV, topo.KindTiFlash:\n\t\ttikvInfos, tiflashInfos, err := topology.FetchStoreTopology(pdClient)\n\t\tif err != nil {\n\t\t\treturn ErrInvalidEndpoint.Wrap(err, \"failed to fetch store topology\")\n\t\t}\n\t\tmatched := false\n\t\tif p.api.Component == topo.KindTiKV {\n\t\t\tfor _, info := range tikvInfos {\n\t\t\t\tif info.IP == p.host && info.StatusPort == uint(p.port) {\n\t\t\t\t\tmatched = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfor _, info := range tiflashInfos {\n\t\t\t\tif info.IP == p.host && info.StatusPort == uint(p.port) {\n\t\t\t\t\tmatched = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\treturn ErrInvalidEndpoint.New(\"invalid endpoint '%s:%d'\", p.host, p.port)\n\t\t}\n\tcase topo.KindPD:\n\t\tinfos, err := topology.FetchPDTopology(pdClient)\n\t\tif err != nil {\n\t\t\treturn ErrInvalidEndpoint.Wrap(err, \"failed to fetch pd topology\")\n\t\t}\n\t\tmatched := false\n\t\tfor _, info := range infos {\n\t\t\tif info.IP == p.host && info.Port == uint(p.port) {\n\t\t\t\tmatched = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\treturn ErrInvalidEndpoint.New(\"invalid endpoint '%s:%d'\", p.host, p.port)\n\t\t}\n\tcase topo.KindTiCDC:\n\t\tinfos, err := topology.FetchTiCDCTopology(ctx, etcdClient)\n\t\tif err != nil {\n\t\t\treturn ErrInvalidEndpoint.Wrap(err, \"failed to fetch ticdc topology\")\n\t\t}\n\t\tmatched := false\n\t\tfor _, info := range infos {\n\t\t\tif info.IP == p.host && info.Port == uint(p.port) {\n\t\t\t\tmatched = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\treturn ErrInvalidEndpoint.New(\"invalid endpoint '%s:%d'\", p.host, p.port)\n\t\t}\n\tcase topo.KindTiProxy:\n\t\tinfos, err := topology.FetchTiProxyTopology(ctx, etcdClient)\n\t\tif err != nil {\n\t\t\treturn ErrInvalidEndpoint.Wrap(err, \"failed to fetch tiproxy topology\")\n\t\t}\n\t\tmatched := false\n\t\tfor _, info := range infos {\n\t\t\tif info.IP == p.host && info.StatusPort == uint(p.port) {\n\t\t\t\tmatched = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\treturn ErrInvalidEndpoint.New(\"invalid endpoint '%s:%d'\", p.host, p.port)\n\t\t}\n\tcase topo.KindTSO:\n\t\tinfos, err := topology.FetchTSOTopology(ctx, pdClient)\n\t\tif err != nil {\n\t\t\treturn ErrInvalidEndpoint.Wrap(err, \"failed to fetch tso topology\")\n\t\t}\n\t\tmatched := false\n\t\tfor _, info := range infos {\n\t\t\tif info.IP == p.host && info.Port == uint(p.port) {\n\t\t\t\tmatched = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\treturn ErrInvalidEndpoint.New(\"invalid endpoint '%s:%d'\", p.host, p.port)\n\t\t}\n\tcase topo.KindScheduling:\n\t\tinfos, err := topology.FetchSchedulingTopology(ctx, pdClient)\n\t\tif err != nil {\n\t\t\treturn ErrInvalidEndpoint.Wrap(err, \"failed to fetch scheduling topology\")\n\t\t}\n\t\tmatched := false\n\t\tfor _, info := range infos {\n\t\t\tif info.IP == p.host && info.Port == uint(p.port) {\n\t\t\t\tmatched = true\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif !matched {\n\t\t\treturn ErrInvalidEndpoint.New(\"invalid endpoint '%s:%d'\", p.host, p.port)\n\t\t}\n\tdefault:\n\t\treturn ErrUnknownComponent.New(\"Unknown component '%s'\", p.api.Component)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/apiserver/debugapi/endpoint/payload_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage endpoint\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"testing\"\n\n\t\"github.com/go-resty/resty/v2\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tidbclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/topo\"\n)\n\nfunc TestRequestPayloadResolver(t *testing.T) {\n\tclients := HTTPClients{\n\t\tTiDBStatusClient: tidbclient.NewStatusClient(httpclient.Config{}),\n\t}\n\tapis := []APIDefinition{\n\t\t{\n\t\t\tID:        \"one_pd_api\",\n\t\t\tComponent: topo.KindPD,\n\t\t\tPath:      \"/foo\",\n\t\t\tMethod:    resty.MethodGet,\n\t\t},\n\t\t{\n\t\t\tID:        \"one_tidb_api\",\n\t\t\tComponent: topo.KindTiDB,\n\t\t\tPath:      \"/test/{pathParam}\",\n\t\t\tMethod:    resty.MethodGet,\n\t\t\tPathParams: []APIParamDefinition{\n\t\t\t\t{\n\t\t\t\t\tName:     \"pathParam\",\n\t\t\t\t\tRequired: true,\n\t\t\t\t},\n\t\t\t},\n\t\t\tQueryParams: []APIParamDefinition{\n\t\t\t\t{\n\t\t\t\t\tName:     \"queryParam\",\n\t\t\t\t\tRequired: true,\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tName:     \"queryParam2\",\n\t\t\t\t\tRequired: false,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:        \"another_tidb_api\",\n\t\t\tComponent: topo.KindTiDB,\n\t\t\tPath:      \"/foo/{regionID}\",\n\t\t\tMethod:    resty.MethodGet,\n\t\t\tPathParams: []APIParamDefinition{\n\t\t\t\t{\n\t\t\t\t\tName:     \"regionID\",\n\t\t\t\t\tRequired: false,\n\t\t\t\t},\n\t\t\t},\n\t\t\tQueryParams: []APIParamDefinition{\n\t\t\t\t{\n\t\t\t\t\tName:     \"state\",\n\t\t\t\t\tRequired: false,\n\t\t\t\t\tOnResolve: func(value string) ([]string, error) {\n\t\t\t\t\t\tif value == \"__INVALID__\" {\n\t\t\t\t\t\t\treturn nil, fmt.Errorf(\"invalid\")\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn []string{\"a\" + value + \"b\"}, nil\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tID:        \"one_tiflash_api\",\n\t\t\tComponent: topo.KindTiFlash,\n\t\t\tPath:      \"/bar\",\n\t\t\tMethod:    resty.MethodGet,\n\t\t},\n\t}\n\tresolver := NewRequestPayloadResolver(apis, clients)\n\n\t// APIs without an accepted client will be ignored.\n\t{\n\t\tapis := resolver.ListAPIs()\n\t\trequire.Len(t, apis, 2)\n\t\trequire.Equal(t, \"one_tidb_api\", apis[0].ID)\n\t\trequire.Equal(t, \"another_tidb_api\", apis[1].ID)\n\t}\n\n\t// Resolve\n\tresolved, err := resolver.ResolvePayload(RequestPayload{\n\t\tAPI:  \"one_tidb_api\",\n\t\tHost: \"tidb-1.internal\",\n\t\tPort: 12345,\n\t\tParamValues: map[string]string{\n\t\t\t\"pathParam\":  \"p1\",\n\t\t\t\"queryParam\": \"q1\",\n\t\t},\n\t})\n\trequire.Nil(t, err)\n\trequire.Equal(t, resolved.api, &apis[1])\n\trequire.Equal(t, \"tidb-1.internal\", resolved.host)\n\trequire.Equal(t, 12345, resolved.port)\n\trequire.Equal(t, \"/test/p1\", resolved.path)\n\trequire.Equal(t, url.Values{\"queryParam\": []string{\"q1\"}}, resolved.queryValues)\n\n\tresolved, err = resolver.ResolvePayload(RequestPayload{\n\t\tAPI:  \"another_tidb_api\",\n\t\tHost: \"tidb-1.internal\",\n\t\tPort: 12345,\n\t\tParamValues: map[string]string{\n\t\t\t\"regionID\": \"35\",\n\t\t},\n\t})\n\trequire.Nil(t, err)\n\trequire.Equal(t, resolved.api, &apis[2])\n\trequire.Equal(t, \"tidb-1.internal\", resolved.host)\n\trequire.Equal(t, 12345, resolved.port)\n\trequire.Equal(t, \"/foo/35\", resolved.path)\n\trequire.Equal(t, url.Values{}, resolved.queryValues)\n\n\t// Resolve unknown API endpoint\n\tresolved, err = resolver.ResolvePayload(RequestPayload{\n\t\tAPI: \"foo\",\n\t})\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"Unknown API endpoint 'foo'\")\n\trequire.Nil(t, resolved)\n\n\t// Resolve filtered API endpoint\n\tresolved, err = resolver.ResolvePayload(RequestPayload{\n\t\tAPI: \"one_pd_api\",\n\t})\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"Unknown API endpoint 'one_pd_api'\")\n\trequire.Nil(t, resolved)\n\n\t// Resolve without specifying the path param\n\tresolved, err = resolver.ResolvePayload(RequestPayload{\n\t\tAPI:  \"one_tidb_api\",\n\t\tHost: \"tidb-1.internal\",\n\t\tPort: 12345,\n\t\tParamValues: map[string]string{\n\t\t\t\"queryParam\": \"q1\",\n\t\t},\n\t})\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"parameter 'pathParam' is required\")\n\trequire.Nil(t, resolved)\n\n\t// Resolve without specifying the path param (even if path param is not set to required)\n\tresolved, err = resolver.ResolvePayload(RequestPayload{\n\t\tAPI:  \"another_tidb_api\",\n\t\tHost: \"tidb-1.internal\",\n\t\tPort: 12345,\n\t})\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"parameter 'regionID' is required\")\n\trequire.Nil(t, resolved)\n\n\tresolved, err = resolver.ResolvePayload(RequestPayload{\n\t\tAPI:  \"another_tidb_api\",\n\t\tHost: \"tidb-1.internal\",\n\t\tPort: 12345,\n\t\tParamValues: map[string]string{\n\t\t\t\"regionID\": \"\",\n\t\t},\n\t})\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"parameter 'regionID' is required\")\n\trequire.Nil(t, resolved)\n\n\t// Resolve without specifying the required query param\n\tresolved, err = resolver.ResolvePayload(RequestPayload{\n\t\tAPI:  \"one_tidb_api\",\n\t\tHost: \"tidb-1.internal\",\n\t\tPort: 12345,\n\t\tParamValues: map[string]string{\n\t\t\t\"pathParam\": \"p1\",\n\t\t},\n\t})\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"parameter 'queryParam' is required\")\n\trequire.Nil(t, resolved)\n\n\t// Resolve with optional query param\n\tresolved, err = resolver.ResolvePayload(RequestPayload{\n\t\tAPI:  \"one_tidb_api\",\n\t\tHost: \"tidb-x.internal\",\n\t\tPort: 5431,\n\t\tParamValues: map[string]string{\n\t\t\t\"pathParam\":   \"abc/def?q=x\",\n\t\t\t\"queryParam\":  \"q\",\n\t\t\t\"queryParam2\": \"q?foo\",\n\t\t},\n\t})\n\trequire.Nil(t, err)\n\trequire.Equal(t, resolved.api, &apis[1])\n\trequire.Equal(t, \"tidb-x.internal\", resolved.host)\n\trequire.Equal(t, 5431, resolved.port)\n\trequire.Equal(t, \"/test/abc%2Fdef%3Fq=x\", resolved.path)\n\trequire.Equal(t, url.Values{\"queryParam\": []string{\"q\"}, \"queryParam2\": []string{\"q?foo\"}}, resolved.queryValues)\n\n\t// Resolve empty optional query param\n\tresolved, err = resolver.ResolvePayload(RequestPayload{\n\t\tAPI:  \"another_tidb_api\",\n\t\tHost: \"tidb-1.internal\",\n\t\tPort: 12345,\n\t\tParamValues: map[string]string{\n\t\t\t\"regionID\": \"35\",\n\t\t\t\"state\":    \"\",\n\t\t},\n\t})\n\trequire.Nil(t, err)\n\trequire.Equal(t, resolved.api, &apis[2])\n\trequire.Equal(t, \"tidb-1.internal\", resolved.host)\n\trequire.Equal(t, 12345, resolved.port)\n\trequire.Equal(t, \"/foo/35\", resolved.path)\n\trequire.Equal(t, url.Values{}, resolved.queryValues)\n\n\t// Resolve with invalid query param (OnResolve returns error)\n\tresolved, err = resolver.ResolvePayload(RequestPayload{\n\t\tAPI:  \"another_tidb_api\",\n\t\tHost: \"tidb-1.internal\",\n\t\tPort: 12345,\n\t\tParamValues: map[string]string{\n\t\t\t\"regionID\": \"123\",\n\t\t\t\"state\":    \"__INVALID__\",\n\t\t},\n\t})\n\trequire.NotNil(t, err)\n\trequire.Contains(t, err.Error(), \"parameter 'state' is invalid, cause: invalid\")\n\trequire.Nil(t, resolved)\n\n\t// Resolve param with OnResolve returns something\n\tresolved, err = resolver.ResolvePayload(RequestPayload{\n\t\tAPI:  \"another_tidb_api\",\n\t\tHost: \"tidb-1.internal\",\n\t\tPort: 12345,\n\t\tParamValues: map[string]string{\n\t\t\t\"regionID\": \"35\",\n\t\t\t\"state\":    \"v\",\n\t\t},\n\t})\n\trequire.Nil(t, err)\n\trequire.Equal(t, resolved.api, &apis[2])\n\trequire.Equal(t, \"tidb-1.internal\", resolved.host)\n\trequire.Equal(t, 12345, resolved.port)\n\trequire.Equal(t, \"/foo/35\", resolved.path)\n\trequire.Equal(t, url.Values{\"state\": []string{\"avb\"}}, resolved.queryValues)\n}\n\nfunc TestResolvedRequestPayload(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = fmt.Fprintln(w, r.URL.String())\n\t\t_, _ = fmt.Fprintln(w, r.Header.Get(\"x-test-header\"))\n\t}))\n\tdefer ts.Close()\n\n\taddr := ts.Listener.Addr().(*net.TCPAddr)\n\trp := ResolvedRequestPayload{\n\t\tapi: &APIDefinition{\n\t\t\tID:        \"api_id\",\n\t\t\tComponent: topo.KindTiDB,\n\t\t\tPath:      \"/does_not_matter\",\n\t\t\tMethod:    resty.MethodGet,\n\t\t\tBeforeSendRequest: func(req *httpclient.LazyRequest) {\n\t\t\t\treq.SetHeader(\"x-test-header\", \"hello\")\n\t\t\t},\n\t\t},\n\t\thost:        addr.IP.String(),\n\t\tport:        addr.Port,\n\t\tpath:        \"/abc\",\n\t\tqueryValues: nil,\n\t}\n\n\tclient := tidbclient.NewStatusClient(httpclient.Config{})\n\tclients := HTTPClients{\n\t\tTiDBStatusClient: client,\n\t}\n\n\tbuf := bytes.Buffer{}\n\t_, err := rp.SendRequestAndPipe(context.Background(), clients, nil, nil, &buf)\n\n\tassert.Nil(t, err)\n\tassert.Equal(t, \"/abc\\nhello\\n\", buf.String())\n}\n"
  },
  {
    "path": "pkg/apiserver/debugapi/module.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage debugapi\n\nimport \"go.uber.org/fx\"\n\nvar Module = fx.Options(\n\tfx.Provide(newService),\n\tfx.Invoke(registerRouter),\n)\n"
  },
  {
    "path": "pkg/apiserver/debugapi/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage debugapi\n\nimport (\n\t\"fmt\"\n\t\"mime\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/debugapi/endpoint\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/pdclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/schedulingclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/ticdcclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tidbclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tiflashclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tikvclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tiproxyclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/tsoclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest/fileswap\"\n)\n\nfunc registerRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tep := r.Group(\"/debug_api\")\n\tep.GET(\"/download\", s.Download)\n\t{\n\t\tep.Use(auth.MWAuthRequired())\n\t\tep.GET(\"/endpoints\", s.GetEndpoints)\n\t\tep.POST(\"/endpoint\", s.RequestEndpoint)\n\t}\n}\n\ntype ServiceParams struct {\n\tfx.In\n\tPDAPIClient            *pdclient.APIClient\n\tTiDBStatusClient       *tidbclient.StatusClient\n\tTiKVStatusClient       *tikvclient.StatusClient\n\tTiFlashStatusClient    *tiflashclient.StatusClient\n\tTiCDCStatusClient      *ticdcclient.StatusClient\n\tTiProxyStatusClient    *tiproxyclient.StatusClient\n\tEtcdClient             *clientv3.Client\n\tPDClient               *pd.Client\n\tTSOStatusClient        *tsoclient.StatusClient\n\tSchedulingStatusClient *schedulingclient.StatusClient\n}\n\ntype Service struct {\n\thttpClients endpoint.HTTPClients\n\tetcdClient  *clientv3.Client\n\tpdClient    *pd.Client\n\tresolver    *endpoint.RequestPayloadResolver\n\tfSwap       *fileswap.Handler\n}\n\nfunc newService(p ServiceParams) *Service {\n\thttpClients := endpoint.HTTPClients{\n\t\tPDAPIClient:            p.PDAPIClient,\n\t\tTiDBStatusClient:       p.TiDBStatusClient,\n\t\tTiKVStatusClient:       p.TiKVStatusClient,\n\t\tTiFlashStatusClient:    p.TiFlashStatusClient,\n\t\tTiCDCStatusClient:      p.TiCDCStatusClient,\n\t\tTiProxyStatusClient:    p.TiProxyStatusClient,\n\t\tTSOStatusClient:        p.TSOStatusClient,\n\t\tSchedulingStatusClient: p.SchedulingStatusClient,\n\t}\n\treturn &Service{\n\t\thttpClients: httpClients,\n\t\tetcdClient:  p.EtcdClient,\n\t\tpdClient:    p.PDClient,\n\t\tresolver:    endpoint.NewRequestPayloadResolver(apiEndpoints, httpClients),\n\t\tfSwap:       fileswap.New(),\n\t}\n}\n\nfunc getExtFromContentTypeHeader(contentType string) string {\n\tmediaType, _, err := mime.ParseMediaType(contentType)\n\tif err != nil || len(mediaType) == 0 {\n\t\treturn \".bin\"\n\t}\n\n\t// Some explicit overrides\n\tif mediaType == \"text/plain\" {\n\t\treturn \".txt\"\n\t}\n\n\tif mediaType == \"application/toml\" {\n\t\treturn \".toml\"\n\t}\n\n\texts, err := mime.ExtensionsByType(mediaType)\n\tif err == nil && len(exts) > 0 {\n\t\t// Note: the first element might not be the most common one\n\t\treturn exts[0]\n\t}\n\n\treturn \".bin\"\n}\n\n// @Summary Send request remote endpoint and return a token for downloading results\n// @Security JwtAuth\n// @ID debugAPIRequestEndpoint\n// @Param req body endpoint.RequestPayload true \"request payload\"\n// @Success 200 {object} string\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /debug_api/endpoint [post]\nfunc (s *Service) RequestEndpoint(c *gin.Context) {\n\tvar req endpoint.RequestPayload\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tresolved, err := s.resolver.ResolvePayload(req)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\twriter, err := s.fSwap.NewFileWriter(\"debug_api\")\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tdefer func() {\n\t\t_ = writer.Close()\n\t}()\n\n\tresp, err := resolved.SendRequestAndPipe(c.Request.Context(), s.httpClients, s.etcdClient, s.pdClient, writer)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\text := getExtFromContentTypeHeader(resp.Header.Get(\"Content-Type\"))\n\tfileName := fmt.Sprintf(\"%s_%d%s\", req.API, time.Now().Unix(), ext)\n\tdownloadToken, err := writer.GetDownloadToken(fileName, time.Minute*5)\n\tif err != nil {\n\t\t// This shall never happen\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tc.String(http.StatusOK, downloadToken)\n}\n\n// @Summary Download a finished request result\n// @Param token query string true \"download token\"\n// @Success 200 {object} string\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /debug_api/download [get]\nfunc (s *Service) Download(c *gin.Context) {\n\ts.fSwap.HandleDownloadRequest(c)\n}\n\n// @Summary Get all endpoints\n// @ID debugAPIGetEndpoints\n// @Security JwtAuth\n// @Success 200 {array} endpoint.APIDefinition\n// @Failure 401 {object} rest.ErrorResponse\n// @Router /debug_api/endpoints [get]\nfunc (s *Service) GetEndpoints(c *gin.Context) {\n\tc.JSON(http.StatusOK, s.resolver.ListAPIs())\n}\n"
  },
  {
    "path": "pkg/apiserver/diagnose/compare.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage diagnose\n\nimport (\n\t\"container/heap\"\n\t\"fmt\"\n\t\"math\"\n\t\"slices\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"github.com/pingcap/errors\"\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n)\n\nfunc GetCompareReportTablesForDisplay(startTime1, endTime1, startTime2, endTime2 string, db *gorm.DB, sqliteDB *dbstore.DB, reportID string) []*TableDef {\n\terrRows := checkBeforeReport(db)\n\tif len(errRows) > 0 {\n\t\treturn []*TableDef{GenerateReportError(errRows)}\n\t}\n\tvar resultTables []*TableDef\n\tresultTables = append(resultTables, GetCompareHeaderTimeTable(startTime1, endTime1, startTime2, endTime2))\n\tvar tables0, tables1, tables2, tables3, tables4 []*TableDef\n\tvar errRows0, errRows1, errRows2, errRows3, errRows4 []TableRowDef\n\tvar compareDiagnoseTable, abnormalSlowQuery *TableDef\n\tvar wg sync.WaitGroup\n\twg.Add(7)\n\tvar progress, totalTableCount int32\n\tgo func() {\n\t\t// Get Header tables.\n\t\ttables0, errRows0 = GetReportHeaderTables(startTime2, endTime2, db, sqliteDB, reportID, &progress, &totalTableCount)\n\t\terrRows = append(errRows, errRows0...)\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\t// Get tables in 2 ranges\n\t\ttables1, errRows1 = GetReportTablesIn2Range(startTime1, endTime1, startTime2, endTime2, db, sqliteDB, reportID, &progress, &totalTableCount)\n\t\terrRows = append(errRows, errRows1...)\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\t// Get compare refer tables\n\t\ttables2, errRows2 = getCompareTables(startTime1, endTime1, db, sqliteDB, reportID, &progress, &totalTableCount)\n\t\terrRows = append(errRows, errRows2...)\n\t\twg.Done()\n\t}()\n\n\tgo func() {\n\t\t// Get compare tables\n\t\ttables3, errRows3 = getCompareTables(startTime2, endTime2, db.Session(&gorm.Session{NewDB: true}), sqliteDB, reportID, &progress, &totalTableCount)\n\t\terrRows = append(errRows, errRows3...)\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\ttbl, errRow := CompareDiagnose(startTime1, endTime1, startTime2, endTime2, db)\n\t\tif errRow != nil {\n\t\t\terrRows = append(errRows, *errRow)\n\t\t} else {\n\t\t\tcompareDiagnoseTable = &tbl\n\t\t}\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\ttbl, errRow := getTiDBAbnormalSlowQueryOnly(startTime1, endTime1, startTime2, endTime2, db)\n\t\tif errRow != nil {\n\t\t\terrRows = append(errRows, *errRow)\n\t\t} else {\n\t\t\tabnormalSlowQuery = &tbl\n\t\t}\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\t// Get end tables\n\t\ttables4, errRows4 = GetReportEndTables(startTime2, endTime2, db, sqliteDB, reportID, &progress, &totalTableCount)\n\t\terrRows = append(errRows, errRows4...)\n\t\twg.Done()\n\t}()\n\twg.Wait()\n\n\ttables, errs := CompareTables(tables2, tables3)\n\terrRows = append(errRows, errs...)\n\tresultTables = append(resultTables, tables0...)\n\tif abnormalSlowQuery != nil {\n\t\tresultTables = append(resultTables, abnormalSlowQuery)\n\t}\n\tresultTables = append(resultTables, tables1...)\n\tif compareDiagnoseTable != nil {\n\t\tresultTables = append(resultTables, compareDiagnoseTable)\n\t}\n\tresultTables = append(resultTables, tables...)\n\tresultTables = append(resultTables, tables4...)\n\n\tif len(errRows) > 0 {\n\t\tresultTables = append(resultTables, GenerateReportError(errRows))\n\t}\n\treturn resultTables\n}\n\nfunc CompareTables(tables1, tables2 []*TableDef) ([]*TableDef, []TableRowDef) {\n\tvar errRows []TableRowDef\n\tdr := &diffRows{}\n\tresultTables := make([]*TableDef, 1, len(tables1))\n\tfor _, tbl1 := range tables1 {\n\t\tfor _, tbl2 := range tables2 {\n\t\t\tif strings.Join(tbl1.Category, \",\") == strings.Join(tbl2.Category, \",\") &&\n\t\t\t\ttbl1.Title == tbl2.Title {\n\t\t\t\ttable, err := compareTable(tbl1, tbl2, dr)\n\t\t\t\tif err != nil {\n\t\t\t\t\terrRows = appendErrorRow(*tbl1, err, errRows)\n\t\t\t\t} else if table != nil {\n\t\t\t\t\tresultTables = append(resultTables, table)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tresultTables[0] = GenerateDiffTable(*dr)\n\treturn resultTables, errRows\n}\n\nfunc GenerateDiffTable(dr diffRows) *TableDef {\n\tl := dr.Len()\n\tsort.Slice(dr, func(i, j int) bool {\n\t\tabs1 := math.Abs(math.Round(dr[i].ratio*100) / 100)\n\t\tabs2 := math.Abs(math.Round(dr[j].ratio*100) / 100)\n\t\tif abs1 != abs2 {\n\t\t\treturn abs1 > abs2\n\t\t}\n\t\tvi1, err1 := parseFloat(dr[i].v1)\n\t\tvi2, err2 := parseFloat(dr[i].v2)\n\t\tvj1, err3 := parseFloat(dr[j].v1)\n\t\tvj2, err4 := parseFloat(dr[j].v2)\n\t\tif err1 != nil || err2 != nil || err3 != nil || err4 != nil {\n\t\t\t// should never be error herr.\n\t\t\treturn false\n\t\t}\n\t\treturn math.Abs(vi2-vi1) > math.Abs(vj1-vj2)\n\t})\n\n\ttype groupValue struct {\n\t\tname     string\n\t\tratioIdx int\n\t}\n\trows := make([]TableRowDef, 0, l)\n\trowMap := make(map[groupValue]int, l)\n\tfor i := range dr {\n\t\trow := dr[i]\n\t\tname := \"\"\n\t\tif labels := strings.Split(row.label, \",\"); len(labels) > 0 {\n\t\t\tname = labels[0]\n\t\t}\n\t\tif len(name) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tlabel := \"\"\n\t\tif len(name) < len(row.label) {\n\t\t\tlabel = row.label[len(name)+1:]\n\t\t}\n\t\tvs := []string{\n\t\t\trow.table,\n\t\t\tname,\n\t\t\tlabel,\n\t\t\tfmt.Sprintf(\"%.2f\", row.ratio),\n\t\t\trow.v1,\n\t\t\trow.v2,\n\t\t\trow.colName,\n\t\t}\n\t\tif idx, ok := rowMap[groupValue{name: name, ratioIdx: row.ratioIdx}]; ok {\n\t\t\tvar lastValue []string\n\t\t\tif len(rows[idx].SubValues) == 0 {\n\t\t\t\tlastValue = rows[idx].Values\n\t\t\t} else {\n\t\t\t\tlastIdx := len(rows[idx].SubValues) - 1\n\t\t\t\tlastValue = rows[idx].SubValues[lastIdx]\n\t\t\t}\n\t\t\tequal := true\n\t\t\tfor i := 3; i <= 5; i++ {\n\t\t\t\tif vs[i] != lastValue[i] {\n\t\t\t\t\tequal = false\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !equal {\n\t\t\t\trows[idx].SubValues = append(rows[idx].SubValues, vs)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\trowMap[groupValue{name: name, ratioIdx: row.ratioIdx}] = len(rows)\n\t\trows = append(rows, TableRowDef{\n\t\t\tValues:  vs,\n\t\t\tComment: row.comment,\n\t\t})\n\t}\n\treturn &TableDef{\n\t\tCategory: []string{CategoryOverview},\n\t\tTitle:    \"max_diff_item\",\n\t\tComment:  \"\",\n\t\tColumn:   []string{\"TABLE\", \"METRIC_NAME\", \"LABEL\", \"MAX_DIFF\", \"t1.VALUE\", \"t2.VALUE\", \"VALUE_TYPE\"},\n\t\tRows:     rows,\n\t}\n}\n\nfunc compareTable(table1, table2 *TableDef, dr *diffRows) (_ *TableDef, err error) {\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\terr = errors.Errorf(\"compare table %s ,%s panic\", table1.Category, table1.Title)\n\t\t}\n\t}()\n\tif strings.Contains(strings.ToLower(table1.Title), \"config\") {\n\t\treturn compareTableWithNonUniqueKey(table1, table2, &diffRows{})\n\t}\n\tlabelsMap1, err := getTableLablesMap(table1)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlabelsMap2, err := getTableLablesMap(table2)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresultRows := make([]TableRowDef, 0, len(table1.Rows))\n\tfor i := range table1.Rows {\n\t\tlabel1 := genRowLabel(table1.Rows[i].Values, table1.joinColumns)\n\t\trow2, ok := labelsMap2[label1]\n\t\tif !ok {\n\t\t\trow2 = &TableRowDef{}\n\t\t}\n\t\tnewRow, err := joinRow(&table1.Rows[i], row2, table1, dr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresultRows = append(resultRows, *newRow)\n\t}\n\tfor i := range table2.Rows {\n\t\tlabel2 := genRowLabel(table2.Rows[i].Values, table2.joinColumns)\n\t\t_, ok := labelsMap1[label2]\n\t\tif ok {\n\t\t\tcontinue\n\t\t}\n\t\trow1 := &TableRowDef{}\n\t\tnewRow, err := joinRow(row1, &table2.Rows[i], table1, dr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresultRows = append(resultRows, *newRow)\n\t}\n\n\tresultTable := &TableDef{\n\t\tCategory:       table1.Category,\n\t\tTitle:          table1.Title,\n\t\tComment:        table1.Comment,\n\t\tjoinColumns:    nil,\n\t\tcompareColumns: nil,\n\t}\n\tcolumns := make([]string, 0, len(table1.Column)*2-len(table1.joinColumns))\n\tfor i := range table1.Column {\n\t\tif checkIn(i, table1.joinColumns) {\n\t\t\tcolumns = append(columns, table1.Column[i])\n\t\t} else {\n\t\t\tcolumns = append(columns, \"t1.\"+table1.Column[i])\n\t\t}\n\t}\n\tfor i := range table2.Column {\n\t\tif !checkIn(i, table2.joinColumns) {\n\t\t\tcolumns = append(columns, \"t2.\"+table2.Column[i])\n\t\t}\n\t}\n\tfor _, idx := range table1.compareColumns {\n\t\tcolumns = append(columns, table1.Column[idx]+\"_DIFF_RATIO\")\n\t}\n\tsort.Slice(resultRows, func(i, j int) bool {\n\t\treturn math.Abs(resultRows[i].ratio) > math.Abs(resultRows[j].ratio)\n\t})\n\tif len(table1.compareColumns) > 0 {\n\t\tfor _, idx := range table1.compareColumns {\n\t\t\tcomment := table1.Column[idx] + \"_DIFF_RATIO=\" + fmt.Sprintf(\"if t2.%[1]s > t1.%[1]s => { t2.%[1]s / t1.%[1]s - 1 } else => { 1 - t1.%[1]s / t2.%[1]s }\", table1.Column[idx])\n\t\t\tif len(resultTable.Comment) > 0 {\n\t\t\t\tresultTable.Comment += \", \\n\"\n\t\t\t}\n\t\t\tresultTable.Comment += comment\n\t\t}\n\t}\n\n\tresultTable.Column = columns\n\tresultTable.Rows = resultRows\n\treturn resultTable, nil\n}\n\nfunc compareTableWithNonUniqueKey(table1, table2 *TableDef, dr *diffRows) (_ *TableDef, err error) {\n\tdefer func() {\n\t\tdefer func() {\n\t\t\tif v := recover(); v != nil {\n\t\t\t\terr = errors.Errorf(\"join table error %v,%v\", table1.Category, table1.Title)\n\t\t\t}\n\t\t}()\n\t}()\n\tlabelsMap1, err := getTableLablesMapWithNonUniqueJoinKey(table1)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlabelsMap2, err := getTableLablesMapWithNonUniqueJoinKey(table2)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tresultRows := make([]TableRowDef, 0, len(table1.Rows))\n\tfor i := range table1.Rows {\n\t\tlabel1 := genRowLabel(table1.Rows[i].Values, table1.joinColumns)\n\t\tvar row2 *TableRowDef\n\t\tif row2s, ok := labelsMap2[label1]; ok && len(row2s) > 0 {\n\t\t\trow2 = row2s[0]\n\t\t\tif len(row2s) == 1 {\n\t\t\t\tdelete(labelsMap2, label1)\n\t\t\t} else {\n\t\t\t\tlabelsMap2[label1] = row2s[1:]\n\t\t\t}\n\t\t} else {\n\t\t\tdelete(labelsMap2, label1)\n\t\t\trow2 = &TableRowDef{}\n\t\t}\n\t\tif row1s, ok := labelsMap1[label1]; ok {\n\t\t\tif len(row1s) <= 1 {\n\t\t\t\tdelete(labelsMap1, label1)\n\t\t\t} else {\n\t\t\t\tlabelsMap1[label1] = row1s[1:]\n\t\t\t}\n\t\t}\n\t\tnewRow, err := joinRow(&table1.Rows[i], row2, table1, dr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresultRows = append(resultRows, *newRow)\n\t}\n\tfor len(labelsMap2) > 0 {\n\t\tfor label, row2s := range labelsMap2 {\n\t\t\tif len(row2s) == 0 {\n\t\t\t\tdelete(labelsMap2, label)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trow2 := row2s[0]\n\t\t\tif len(row2s) == 1 {\n\t\t\t\tdelete(labelsMap2, label)\n\t\t\t} else {\n\t\t\t\tlabelsMap2[label] = row2s[1:]\n\t\t\t}\n\t\t\tvar row1 *TableRowDef\n\t\t\tif row1s, ok := labelsMap1[label]; ok && len(row1s) > 0 {\n\t\t\t\trow1 = row1s[0]\n\t\t\t\tif len(row1s) == 0 {\n\t\t\t\t\tdelete(labelsMap1, label)\n\t\t\t\t} else {\n\t\t\t\t\tlabelsMap1[label] = row1s[1:]\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tdelete(labelsMap1, label)\n\t\t\t\trow1 = &TableRowDef{}\n\t\t\t}\n\t\t\tnewRow, err := joinRow(row1, row2, table1, dr)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tresultRows = append(resultRows, *newRow)\n\t\t}\n\t}\n\n\tresultTable := &TableDef{\n\t\tCategory:       table1.Category,\n\t\tTitle:          table1.Title,\n\t\tComment:        table1.Comment,\n\t\tjoinColumns:    nil,\n\t\tcompareColumns: nil,\n\t}\n\tcolumns := make([]string, 0, len(table1.Column)*2-len(table1.joinColumns))\n\tfor i := range table1.Column {\n\t\tif checkIn(i, table1.joinColumns) {\n\t\t\tcolumns = append(columns, table1.Column[i])\n\t\t} else {\n\t\t\tcolumns = append(columns, \"t1.\"+table1.Column[i])\n\t\t}\n\t}\n\tfor i := range table2.Column {\n\t\tif !checkIn(i, table2.joinColumns) {\n\t\t\tcolumns = append(columns, \"t2.\"+table2.Column[i])\n\t\t}\n\t}\n\tfor _, idx := range table1.compareColumns {\n\t\tcolumns = append(columns, table1.Column[idx]+\"_DIFF_RATIO\")\n\t}\n\tsort.Slice(resultRows, func(i, j int) bool {\n\t\tif len(table1.joinColumns) > 0 {\n\t\t\tidx := table1.joinColumns[0]\n\t\t\tif len(resultRows[i].Values) > (idx+1) &&\n\t\t\t\tlen(resultRows[j].Values) > (idx+1) {\n\t\t\t\tif resultRows[i].Values[idx] != resultRows[j].Values[idx] {\n\t\t\t\t\treturn resultRows[i].Values[idx] < resultRows[j].Values[idx]\n\t\t\t\t}\n\t\t\t\treturn resultRows[i].Values[0] < resultRows[j].Values[0]\n\t\t\t}\n\t\t}\n\t\treturn false\n\t})\n\tfor _, idx := range table1.compareColumns {\n\t\tcomment := table1.Column[idx] + \"_DIFF_RATIO=\" + fmt.Sprintf(\"(t2.%[1]s-t1.%[1]s)/max(t2.%[1]s, t1.%[1]s)\", table1.Column[idx])\n\t\tif len(resultTable.Comment) > 0 {\n\t\t\tresultTable.Comment += \", \\n\"\n\t\t}\n\t\tresultTable.Comment += comment\n\t}\n\n\tresultTable.Column = columns\n\tresultTable.Rows = resultRows\n\treturn resultTable, nil\n}\n\nfunc getTableLablesMapWithNonUniqueJoinKey(table *TableDef) (map[string][]*TableRowDef, error) {\n\tif len(table.joinColumns) == 0 {\n\t\treturn nil, errors.Errorf(\"category %v,table %v doesn't have join columns\", strings.Join(table.Category, \",\"), table.Title)\n\t}\n\tlabelsMap := make(map[string][]*TableRowDef, len(table.Rows))\n\tfor i := range table.Rows {\n\t\tlabel := genRowLabel(table.Rows[i].Values, table.joinColumns)\n\t\tlabelsMap[label] = append(labelsMap[label], &table.Rows[i])\n\t}\n\treturn labelsMap, nil\n}\n\nfunc joinRow(row1, row2 *TableRowDef, table *TableDef, dr *diffRows) (*TableRowDef, error) {\n\trowsMap1, err := genRowsLablesMap(table, row1.SubValues)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trowsMap2, err := genRowsLablesMap(table, row2.SubValues)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsubJoinRows := make([]*newJoinRow, 0, len(row1.SubValues))\n\tfor _, subRow1 := range row1.SubValues {\n\t\tlabel := genRowLabel(subRow1, table.joinColumns)\n\t\tsubRow2 := rowsMap2[label]\n\t\tratio, ratios, idx, err := calculateDiffRatio(subRow1, subRow2, table)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Errorf(\"category %v,table %v, calculate diff ratio error: %v,  %v,%v\", strings.Join(table.Category, \",\"), table.Title, err.Error(), subRow1, subRow2)\n\t\t}\n\t\tsubJoinRows = append(subJoinRows, &newJoinRow{\n\t\t\trow1:   subRow1,\n\t\t\trow2:   subRow2,\n\t\t\tratio:  ratio,\n\t\t\tratios: ratios,\n\t\t})\n\t\tdr.addRow(table, label, ratio, subRow1, subRow2, idx, row1.Comment)\n\t}\n\n\tfor _, subRow2 := range row2.SubValues {\n\t\tlabel := genRowLabel(subRow2, table.joinColumns)\n\t\tsubRow1, ok := rowsMap1[label]\n\t\tif ok {\n\t\t\tcontinue\n\t\t}\n\t\tratio, ratios, idx, err := calculateDiffRatio(subRow1, subRow2, table)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Errorf(\"category %v,table %v, calculate diff ratio error: %v,  %v,%v\", strings.Join(table.Category, \",\"), table.Title, err.Error(), subRow1, subRow2)\n\t\t}\n\n\t\tsubJoinRows = append(subJoinRows, &newJoinRow{\n\t\t\trow1:   subRow1,\n\t\t\trow2:   subRow2,\n\t\t\tratio:  ratio,\n\t\t\tratios: ratios,\n\t\t})\n\t\tdr.addRow(table, label, ratio, subRow1, subRow2, idx, row2.Comment)\n\t}\n\n\tsort.Slice(subJoinRows, func(i, j int) bool {\n\t\treturn math.Abs(subJoinRows[i].ratio) > math.Abs(subJoinRows[j].ratio)\n\t})\n\ttotalRatio := float64(0)\n\tvar totalRatios []float64\n\tresultSubRows := make([][]string, 0, len(row1.SubValues))\n\tfor _, r := range subJoinRows {\n\t\tresultSubRows = append(resultSubRows, r.genNewRow(table))\n\t}\n\n\ttotalRatioIdx := -1\n\tif len(row1.Values) != len(row2.Values) {\n\t\ttotalRatio = 1\n\t\ttotalRatios = nil\n\t} else {\n\t\ttotalRatio, totalRatios, totalRatioIdx, err = calculateDiffRatio(row1.Values, row2.Values, table)\n\t\tif err != nil {\n\t\t\treturn nil, errors.Errorf(\"category %v,table %v, calculate diff ratio error: %v,  %v,%v\", strings.Join(table.Category, \",\"), table.Title, err.Error(), row1.Values, row2.Values)\n\t\t}\n\t}\n\tif len(row1.SubValues) == 0 && len(row2.SubValues) == 0 {\n\t\tlabel := \"\"\n\t\tif len(row1.Values) >= len(table.Column) {\n\t\t\tlabel = genRowLabel(row1.Values, table.joinColumns)\n\t\t} else if len(row2.Values) >= len(table.Column) {\n\t\t\tlabel = genRowLabel(row2.Values, table.joinColumns)\n\t\t}\n\t\tdr.addRow(table, label, totalRatio, row1.Values, row2.Values, totalRatioIdx, row1.Comment)\n\t}\n\n\tresultJoinRow := newJoinRow{\n\t\trow1:   row1.Values,\n\t\trow2:   row2.Values,\n\t\tratio:  totalRatio,\n\t\tratios: totalRatios,\n\t}\n\n\tresultRow := &TableRowDef{\n\t\tValues:    resultJoinRow.genNewRow(table),\n\t\tSubValues: resultSubRows,\n\t\tratio:     totalRatio,\n\t\tComment:   row1.Comment,\n\t}\n\treturn resultRow, nil\n}\n\ntype diffRow struct {\n\ttable    string\n\tlabel    string\n\tratio    float64\n\tratioIdx int\n\tcolName  string\n\tv1       string\n\tv2       string\n\tcomment  string\n}\n\ntype diffRows []diffRow\n\nfunc (r diffRows) Len() int           { return len(r) }\nfunc (r diffRows) Less(i, j int) bool { return math.Abs(r[i].ratio) < math.Abs(r[j].ratio) }\nfunc (r diffRows) Swap(i, j int)      { r[i], r[j] = r[j], r[i] }\n\nfunc (r *diffRows) Push(x interface{}) {\n\t*r = append(*r, x.(diffRow))\n}\n\nfunc (r *diffRows) Pop() interface{} {\n\told := *r\n\tn := len(old)\n\tx := old[n-1]\n\t*r = old[0 : n-1]\n\treturn x\n}\n\nfunc (r *diffRows) addRow(table *TableDef, label string, ratio float64, vs1, vs2 []string, idx int, comment string) {\n\tif ratio == 0 {\n\t\treturn\n\t}\n\ttableName := strings.Join(table.Category, \"-\") + \", \" + table.Title\n\tcolName := \"\"\n\tif idx > 0 && idx < len(table.Column) {\n\t\tcomment = comment + \", the value is \" + table.Column[idx]\n\t\tcolName = table.Column[idx]\n\t}\n\tv1 := \"\"\n\tv2 := \"\"\n\tif idx >= 0 {\n\t\tif idx < len(vs1) {\n\t\t\tv1 = vs1[idx]\n\t\t}\n\t\tif idx < len(vs2) {\n\t\t\tv2 = vs2[idx]\n\t\t}\n\t}\n\tr.appendRow(diffRow{\n\t\ttable:    tableName,\n\t\tlabel:    label,\n\t\tratio:    ratio,\n\t\tratioIdx: idx,\n\t\tcolName:  colName,\n\t\tv1:       v1,\n\t\tv2:       v2,\n\t\tcomment:  comment,\n\t})\n}\n\nfunc (r *diffRows) appendRow(row diffRow) {\n\theap.Push(r, row)\n\tif r.Len() > 500 {\n\t\theap.Pop(r)\n\t}\n}\n\ntype newJoinRow struct {\n\trow1   []string\n\trow2   []string\n\tratio  float64\n\tratios []float64\n}\n\nfunc (r *newJoinRow) genNewRow(table *TableDef) []string {\n\tnewRow := make([]string, 0, len(r.row1)+len(r.row2))\n\tif len(r.row1) == 0 {\n\t\tnewRow = append(newRow, make([]string, len(r.row2))...)\n\t\tfor i := range r.row2 {\n\t\t\tif checkIn(i, table.joinColumns) {\n\t\t\t\tnewRow[i] = r.row2[i]\n\t\t\t} else {\n\t\t\t\tnewRow = append(newRow, r.row2[i])\n\t\t\t}\n\t\t}\n\t\tfor i := range table.compareColumns {\n\t\t\tif len(r.ratios) > i {\n\t\t\t\tnewRow = append(newRow, convertFloatToString(r.ratios[i]))\n\t\t\t} else {\n\t\t\t\tnewRow = append(newRow, convertFloatToString(r.ratio))\n\t\t\t}\n\t\t}\n\t\treturn newRow\n\t}\n\n\tnewRow = append(newRow, r.row1...)\n\tif len(r.row2) == 0 {\n\t\tnewRow = append(newRow, make([]string, len(r.row1)-len(table.joinColumns))...)\n\t\tfor i := range table.compareColumns {\n\t\t\tif len(r.ratios) > i {\n\t\t\t\tnewRow = append(newRow, convertFloatToString(r.ratios[i]))\n\t\t\t} else {\n\t\t\t\tnewRow = append(newRow, convertFloatToString(r.ratio))\n\t\t\t}\n\t\t}\n\t\treturn newRow\n\t}\n\tfor i := range r.row2 {\n\t\tif !checkIn(i, table.joinColumns) {\n\t\t\tnewRow = append(newRow, r.row2[i])\n\t\t}\n\t}\n\tfor i := range table.compareColumns {\n\t\tif len(r.ratios) > i {\n\t\t\tnewRow = append(newRow, convertFloatToString(r.ratios[i]))\n\t\t} else {\n\t\t\tnewRow = append(newRow, convertFloatToString(r.ratio))\n\t\t}\n\t}\n\treturn newRow\n}\n\nfunc calculateDiffRatio(row1, row2 []string, table *TableDef) (float64, []float64, int, error) {\n\tif len(table.compareColumns) == 0 {\n\t\treturn 0, nil, -1, nil\n\t}\n\tif len(row1) == 0 && len(row2) == 0 {\n\t\treturn 0, nil, -1, nil\n\t}\n\tratios := make([]float64, 0, len(table.compareColumns))\n\tmaxRatio := float64(0)\n\tneedBetter := false\n\tmaxIdx := -1\n\tfor _, idx := range table.compareColumns {\n\t\tvar f1, f2 float64\n\t\tvar err error\n\t\tif idx < len(row1) {\n\t\t\tf1, err = parseFloat(row1[idx])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, nil, -1, err\n\t\t\t}\n\t\t}\n\t\tif idx < len(row2) {\n\t\t\tf2, err = parseFloat(row2[idx])\n\t\t\tif err != nil {\n\t\t\t\treturn 0, nil, -1, err\n\t\t\t}\n\t\t}\n\t\tif f1 == f2 {\n\t\t\tratios = append(ratios, 0)\n\t\t\tcontinue\n\t\t}\n\t\tratio := float64(0)\n\t\tif f1 == 0 {\n\t\t\tratio = f2\n\t\t} else if f2 == 0 {\n\t\t\tratio = 0 - f1\n\t\t} else if f2 > f1 {\n\t\t\tratio = f2/f1 - 1\n\t\t} else {\n\t\t\tratio = 1 - f1/f2\n\t\t}\n\t\tratios = append(ratios, ratio)\n\t\tif (f1 == 0 || f2 == 0) && maxRatio != 0 && !needBetter {\n\t\t\tcontinue\n\t\t}\n\t\tif math.Abs(ratio) > math.Abs(maxRatio) || (needBetter && f1 != 0 && f2 != 0) {\n\t\t\tmaxRatio = ratio\n\t\t\tneedBetter = f1 == 0 || f2 == 0\n\t\t\tmaxIdx = idx\n\t\t}\n\t}\n\treturn maxRatio, ratios, maxIdx, nil\n}\n\nfunc parseFloat(s string) (float64, error) {\n\tif len(s) == 0 {\n\t\treturn float64(0), nil\n\t}\n\tcases := []struct {\n\t\tsuffix string\n\t\tratio  float64\n\t}{\n\t\t{\" GB\", float64(1024 * 1024 * 1024)},\n\t\t{\" MB\", float64(1024 * 1024)},\n\t\t{\" KB\", float64(1024)},\n\t\t{\"%\", float64(1)},\n\t\t{\" s\", float64(1)},\n\t\t{\" ms\", float64(1) / float64(1000)},\n\t\t{\" us\", float64(1) / float64(10e5)},\n\t}\n\tratio := float64(1)\n\tfor _, c := range cases {\n\t\tif strings.HasSuffix(s, c.suffix) {\n\t\t\tratio = c.ratio\n\t\t\ts = s[:len(s)-len(c.suffix)]\n\t\t\tbreak\n\t\t}\n\t}\n\tf, err := strconv.ParseFloat(s, 64)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\treturn f * ratio, nil\n}\n\nfunc checkIn(idx int, idxs []int) bool {\n\treturn slices.Contains(idxs, idx)\n}\n\nfunc genRowLabel(row []string, joinColumns []int) string {\n\tvar label strings.Builder\n\tfor i, idx := range joinColumns {\n\t\tif i > 0 {\n\t\t\tlabel.WriteString(\",\")\n\t\t}\n\t\tlabel.WriteString(row[idx])\n\t}\n\treturn label.String()\n}\n\nfunc genRowsLablesMap(table *TableDef, rows [][]string) (map[string][]string, error) {\n\tlabelsMap := make(map[string][]string, len(rows))\n\tfor i := range rows {\n\t\tlabel := genRowLabel(rows[i], table.joinColumns)\n\t\t_, ok := labelsMap[label]\n\t\tif ok {\n\t\t\treturn nil, errors.Errorf(\"category %v,table %v has duplicate join label: %v\", strings.Join(table.Category, \",\"), table.Title, label)\n\t\t}\n\t\tlabelsMap[label] = rows[i]\n\t}\n\treturn labelsMap, nil\n}\n\nfunc getTableLablesMap(table *TableDef) (map[string]*TableRowDef, error) {\n\tif len(table.joinColumns) == 0 {\n\t\treturn nil, errors.Errorf(\"category %v,table %v doesn't have join columns\", strings.Join(table.Category, \",\"), table.Title)\n\t}\n\tlabelsMap := make(map[string]*TableRowDef, len(table.Rows))\n\tfor i := range table.Rows {\n\t\tlabel := genRowLabel(table.Rows[i].Values, table.joinColumns)\n\t\t_, ok := labelsMap[label]\n\t\tif ok {\n\t\t\treturn nil, errors.Errorf(\"category %v,table %v has duplicate join label: %v\", strings.Join(table.Category, \",\"), table.Title, label)\n\t\t}\n\t\tlabelsMap[label] = &table.Rows[i]\n\t}\n\treturn labelsMap, nil\n}\n\nfunc getCompareTables(startTime, endTime string, db *gorm.DB, sqliteDB *dbstore.DB, reportID string, progress, totalTableCount *int32) ([]*TableDef, []TableRowDef) {\n\tfuncs := []getTableFunc{\n\t\t// Node\n\t\tGetLoadTable,\n\t\tGetCPUUsageTable,\n\t\tGetTiKVThreadCPUTable,\n\t\tGetGoroutinesCountTable,\n\t\tGetProcessMemUsageTable,\n\n\t\t// Overview\n\t\tGetTotalTimeConsumeTable,\n\t\tGetTotalErrorTable,\n\n\t\t// TiDB\n\t\tGetTiDBTimeConsumeTable,\n\t\tGetTiDBConnectionCountTable,\n\t\tGetTiDBTxnTableData,\n\t\tGetTiDBStatisticsInfo,\n\t\tGetTiDBDDLOwner,\n\n\t\t// PD\n\t\tGetPDTimeConsumeTable,\n\t\tGetPDSchedulerInfo,\n\t\tGetPDClusterStatusTable,\n\t\tGetStoreStatusTable,\n\t\tGetPDEtcdStatusTable,\n\n\t\t// TiKV\n\t\tGetTiKVTotalTimeConsumeTable,\n\t\tGetTiKVRocksDBTimeConsumeTable,\n\t\tGetTiKVErrorTable,\n\t\tGetTiKVStoreInfo,\n\t\tGetTiKVRegionSizeInfo,\n\t\tGetTiKVCopInfo,\n\t\tGetTiKVSchedulerInfo,\n\t\tGetTiKVRaftInfo,\n\t\tGetTiKVSnapshotInfo,\n\t\tGetTiKVGCInfo,\n\t\tGetTiKVTaskInfo,\n\t\tGetTiKVCacheHitTable,\n\n\t\t// Config\n\t\tGetPDConfigInfo,\n\t\tGetPDConfigChangeInfo,\n\t\tGetTiDBGCConfigInfo,\n\t\tGetTiDBGCConfigChangeInfo,\n\t\tGetTiKVRocksDBConfigInfo,\n\t\tGetTiKVRocksDBConfigChangeInfo,\n\t\tGetTiKVRaftStoreConfigInfo,\n\t\tGetTiKVRaftStoreConfigChangeInfo,\n\t}\n\tatomic.AddInt32(totalTableCount, int32(len(funcs)))\n\treturn getTablesParallel(startTime, endTime, db, funcs, sqliteDB, reportID, progress, totalTableCount)\n}\n\nfunc GetReportHeaderTables(startTime, endTime string, db *gorm.DB, sqliteDB *dbstore.DB, reportID string, progress, totalTableCount *int32) ([]*TableDef, []TableRowDef) {\n\tfuncs := []func(string, string, *gorm.DB) (TableDef, error){\n\t\t// Header\n\t\tGetClusterHardwareInfoTable,\n\t\tGetClusterInfoTable,\n\t}\n\tatomic.AddInt32(totalTableCount, int32(len(funcs)))\n\treturn getTablesParallel(startTime, endTime, db, funcs, sqliteDB, reportID, progress, totalTableCount)\n}\n\nfunc GetReportEndTables(startTime, endTime string, db *gorm.DB, sqliteDB *dbstore.DB, reportID string, progress, totalTableCount *int32) ([]*TableDef, []TableRowDef) {\n\tfuncs := []func(string, string, *gorm.DB) (TableDef, error){\n\t\tGetTiDBCurrentConfig,\n\t\tGetPDCurrentConfig,\n\t\tGetTiKVCurrentConfig,\n\t}\n\tatomic.AddInt32(totalTableCount, int32(len(funcs)))\n\treturn getTablesParallel(startTime, endTime, db, funcs, sqliteDB, reportID, progress, totalTableCount)\n}\n\nfunc GetCompareHeaderTimeTable(startTime1, endTime1, startTime2, endTime2 string) *TableDef {\n\treturn &TableDef{\n\t\tCategory: []string{CategoryHeader},\n\t\tTitle:    \"compare_report_time_range\",\n\t\tComment:  \"\",\n\t\tColumn:   []string{\"T1.START_TIME\", \"T1.END_TIME\", \"T2.START_TIME\", \"T2.END_TIME\"},\n\t\tRows: []TableRowDef{\n\t\t\t{Values: []string{startTime1, endTime1, startTime2, endTime2}},\n\t\t},\n\t}\n}\n\nfunc GetReportTablesIn2Range(startTime1, endTime1, startTime2, endTime2 string, db *gorm.DB, sqliteDB *dbstore.DB, reportID string, progress, totalTableCount *int32) ([]*TableDef, []TableRowDef) {\n\tfuncs := []func(string, string, *gorm.DB) (TableDef, error){\n\t\t// TiDB\n\t\tGetTiDBTopNSlowQuery,\n\t\tGetTiDBTopNSlowQueryGroupByDigest,\n\t\tGetTiDBSlowQueryWithDiffPlan,\n\n\t\t// Diagnose\n\t\tGetAllDiagnoseReport,\n\t}\n\tatomic.AddInt32(totalTableCount, int32(len(funcs)*2))\n\n\ttables := make([]*TableDef, 0, len(funcs))\n\tvar errRows []TableRowDef\n\n\tvar tables1, tables2 []*TableDef\n\tvar errRows1, errRows2 []TableRowDef\n\tvar wg sync.WaitGroup\n\twg.Add(2)\n\tgo func() {\n\t\ttables1, errRows1 = getTablesParallel(startTime1, endTime1, db, funcs, sqliteDB, reportID, progress, totalTableCount)\n\t\terrRows = append(errRows, errRows1...)\n\t\tfor _, tbl := range tables1 {\n\t\t\tif tbl.Rows != nil {\n\t\t\t\ttbl.Title += \"_in_time_range_t1\"\n\t\t\t}\n\t\t}\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\ttables2, errRows2 = getTablesParallel(startTime2, endTime2, db, funcs, sqliteDB, reportID, progress, totalTableCount)\n\t\terrRows = append(errRows, errRows2...)\n\t\tfor _, tbl := range tables2 {\n\t\t\tif tbl.Rows != nil {\n\t\t\t\ttbl.Title += \"_in_time_range_t2\"\n\t\t\t}\n\t\t}\n\t\twg.Done()\n\t}()\n\twg.Wait()\n\n\tfor len(tables1) > 0 && len(tables2) > 0 {\n\t\ttables = append(tables, tables1[0])\n\t\ttables = append(tables, tables2[0])\n\t\ttables1 = tables1[1:]\n\t\ttables2 = tables2[1:]\n\t}\n\ttables = append(tables, tables1...)\n\ttables = append(tables, tables2...)\n\treturn tables, errRows\n}\n\nfunc appendErrorRow(tbl TableDef, err error, errRows []TableRowDef) []TableRowDef {\n\tif err == nil {\n\t\treturn errRows\n\t}\n\tcategory := \"\"\n\tif tbl.Category != nil {\n\t\tcategory = strings.Join(tbl.Category, \",\")\n\t}\n\terrRows = append(errRows, TableRowDef{Values: []string{category, tbl.Title, err.Error()}})\n\treturn errRows\n}\n\nfunc getTiDBAbnormalSlowQueryOnly(startTime1, endTime1, startTime2, endTime2 string, db *gorm.DB) (TableDef, *TableRowDef) {\n\tsql := fmt.Sprintf(`select * from\n    (select /*+ agg_to_cop(), hash_agg() */ count(*),\n         min(time),\n         sum(query_time) as sum_query_time,\n         sum(process_time) as sum_process_time,\n         sum(wait_time) as sum_wait_time,\n         sum(commit_time),\n         sum(request_count),\n         sum(process_keys),\n         sum(write_keys),\n         max(cop_proc_max),\n         min(query),min(prev_stmt),\n         digest\n    from information_schema.cluster_slow_query\n    where time >= '%s'\n            and time < '%s'\n            and is_internal = false\n    group by  digest) as t1\nwhere t1.digest not in\n    (select /*+ agg_to_cop(), hash_agg() */ digest\n    from information_schema.cluster_slow_query\n    where time >= '%s'\n            and time < '%s'\n    group by  digest)\norder by  t1.sum_query_time desc limit 10`, startTime2, endTime2, startTime1, endTime1)\n\ttable := TableDef{\n\t\tCategory: []string{CategoryTiDB},\n\t\tTitle:    \"slow_query_t2\",\n\t\tComment:  sql,\n\t\tColumn:   []string{\"count(*)\", \"min(time)\", \"sum(query_time)\", \"sum(Process_time)\", \"sum(Wait_time)\", \"sum(Commit_time)\", \"sum(Request_count)\", \"sum(process_keys)\", \"sum(Write_keys)\", \"max(Cop_proc_max)\", \"min(query)\", \"min(prev_stmt)\", \"digest\"},\n\t}\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, &TableRowDef{Values: []string{strings.Join(table.Category, \",\"), table.Title, err.Error()}}\n\t}\n\ttable.Rows = useSubRowForLongColumnValue(rows, len(table.Column)-3)\n\treturn table, nil\n}\n\nfunc useSubRowForLongColumnValue(rows []TableRowDef, colIdx int) []TableRowDef {\n\tmaxLen := 100\n\tfor i := range rows {\n\t\trow := rows[i]\n\t\tif len(row.Values) <= colIdx {\n\t\t\tcontinue\n\t\t}\n\t\tif len(row.Values[colIdx]) > maxLen {\n\t\t\tsubRow := make([]string, len(row.Values))\n\t\t\tsubRow[colIdx] = row.Values[colIdx]\n\t\t\trows[i].Values[colIdx] = row.Values[colIdx][:100]\n\t\t\trows[i].SubValues = append(rows[i].SubValues, subRow)\n\t\t}\n\t}\n\treturn rows\n}\n"
  },
  {
    "path": "pkg/apiserver/diagnose/diagnose.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage diagnose\n\nimport (\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\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/goccy/go-graphviz\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/uiserver\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nconst (\n\ttimeLayout = \"2006-01-02 15:04:05\"\n)\n\nvar graphvizMutex sync.Mutex\n\ntype Service struct {\n\t// FIXME: Use fx.In\n\tconfig     *config.Config\n\tdb         *dbstore.DB\n\ttidbClient *tidb.Client\n\tfileServer http.Handler\n}\n\nfunc NewService(config *config.Config, tidbClient *tidb.Client, db *dbstore.DB, uiAssetFS http.FileSystem) *Service {\n\terr := autoMigrate(db)\n\tif err != nil {\n\t\tlog.Fatal(\"Failed to initialize database\", zap.Error(err))\n\t}\n\n\treturn &Service{\n\t\tconfig:     config,\n\t\tdb:         db,\n\t\ttidbClient: tidbClient,\n\t\tfileServer: uiserver.Handler(uiAssetFS),\n\t}\n}\n\nfunc RegisterRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/diagnose\")\n\tendpoint.GET(\"/reports\",\n\t\tauth.MWAuthRequired(),\n\t\ts.reportsHandler)\n\tendpoint.POST(\"/reports\",\n\t\tauth.MWAuthRequired(),\n\t\tutils.MWConnectTiDB(s.tidbClient),\n\t\ts.genReportHandler)\n\tendpoint.GET(\"/reports/:id/detail\", s.reportHTMLHandler)\n\tendpoint.GET(\"/reports/:id/data.js\", s.reportDataHandler)\n\tendpoint.GET(\"/reports/:id/status\",\n\t\tauth.MWAuthRequired(),\n\t\ts.reportStatusHandler)\n\n\tendpoint.POST(\"/metrics_relation/generate\", auth.MWAuthRequired(), s.metricsRelationHandler)\n\tendpoint.GET(\"/metrics_relation/view\", s.metricsRelationViewHandler)\n\n\tendpoint.POST(\"/diagnosis\",\n\t\tauth.MWAuthRequired(),\n\t\tutils.MWConnectTiDB((s.tidbClient)),\n\t\ts.genDiagnosisHandler)\n}\n\nfunc (s *Service) generateMetricsRelation(startTime, endTime time.Time, graphType string) (string, error) {\n\tparams := url.Values{}\n\tparams.Add(\"start\", startTime.Format(time.RFC3339))\n\tparams.Add(\"end\", endTime.Format(time.RFC3339))\n\tparams.Add(\"type\", graphType)\n\tencodedParams := params.Encode()\n\n\tdata, err := s.tidbClient.SendGetRequest(\"/metrics/profile?\" + encodedParams)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tfile, err := os.CreateTemp(\"\", \"metrics*.svg\")\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to create temp file: %v\", err)\n\t}\n\t_ = file.Close()\n\n\tg := graphviz.New()\n\t// FIXME: should share a global mutex for profiling.\n\tgraphvizMutex.Lock()\n\tdefer graphvizMutex.Unlock()\n\tgraph, err := graphviz.ParseBytes(data)\n\tif err != nil {\n\t\t_ = os.Remove(file.Name())\n\t\treturn \"\", fmt.Errorf(\"failed to parse DOT file: %v\", err)\n\t}\n\n\tif err := g.RenderFilename(graph, graphviz.SVG, file.Name()); err != nil {\n\t\t_ = os.Remove(file.Name())\n\t\treturn \"\", fmt.Errorf(\"failed to render SVG: %v\", err)\n\t}\n\n\treturn file.Name(), nil\n}\n\ntype GenerateMetricsRelationRequest struct {\n\tStartTime int64  `json:\"start_time\"`\n\tEndTime   int64  `json:\"end_time\"`\n\tType      string `json:\"type\"`\n}\n\n// @Id diagnoseGenerateMetricsRelationship\n// @Summary Generate metrics relationship graph.\n// @Param request body GenerateMetricsRelationRequest true \"Request body\"\n// @Router /diagnose/metrics_relation/generate [post]\n// @Success 200 {string} string\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) metricsRelationHandler(c *gin.Context) {\n\tvar req GenerateMetricsRelationRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tstartTime := time.Unix(req.StartTime, 0)\n\tendTime := time.Unix(req.EndTime, 0)\n\n\tpath, err := s.generateMetricsRelation(startTime, endTime, req.Type)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\ttoken, err := utils.NewJWTString(\"diagnose/metrics\", path)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tc.JSON(http.StatusOK, token)\n}\n\n// @Summary View metrics relationship graph.\n// @Produce image/svg\n// @Param token query string true \"token\"\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /diagnose/metrics_relation/view [get]\nfunc (s *Service) metricsRelationViewHandler(c *gin.Context) {\n\ttoken := c.Query(\"token\")\n\tpath, err := utils.ParseJWTString(\"diagnose/metrics\", token)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tdata, err := os.ReadFile(filepath.Clean(path))\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\t// Do not remove it? Otherwise the link will just expire..\n\t// _ = os.Remove(path)\n\n\tc.Data(http.StatusOK, \"image/svg+xml\", data)\n}\n\ntype GenerateReportRequest struct {\n\tStartTime        int64 `json:\"start_time\"`\n\tEndTime          int64 `json:\"end_time\"`\n\tCompareStartTime int64 `json:\"compare_start_time\"`\n\tCompareEndTime   int64 `json:\"compare_end_time\"`\n}\n\n// @Summary SQL diagnosis reports history\n// @Description Get sql diagnosis reports history\n// @Success 200 {array} Report\n// @Router /diagnose/reports [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) reportsHandler(c *gin.Context) {\n\treports, err := GetReports(s.db)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, reports)\n}\n\n// @Summary SQL diagnosis report\n// @Description Generate sql diagnosis report\n// @Param request body GenerateReportRequest true \"Request body\"\n// @Success 200 {object} int\n// @Router /diagnose/reports [post]\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) genReportHandler(c *gin.Context) {\n\tvar req GenerateReportRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tstartTime := time.Unix(req.StartTime, 0)\n\tendTime := time.Unix(req.EndTime, 0)\n\tvar compareStartTime, compareEndTime *time.Time\n\tif req.CompareStartTime != 0 && req.CompareEndTime != 0 {\n\t\tcompareStartTime = new(time.Time)\n\t\tcompareEndTime = new(time.Time)\n\t\t*compareStartTime = time.Unix(req.CompareStartTime, 0)\n\t\t*compareEndTime = time.Unix(req.CompareEndTime, 0)\n\t}\n\n\treportID, err := NewReport(s.db, startTime, endTime, compareStartTime, compareEndTime)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tdb := utils.TakeTiDBConnection(c)\n\n\tgo func() {\n\t\tdefer utils.CloseTiDBConnection(db) //nolint:errcheck\n\n\t\tvar tables []*TableDef\n\t\tif compareStartTime == nil || compareEndTime == nil {\n\t\t\ttables = GetReportTablesForDisplay(startTime.Format(timeLayout), endTime.Format(timeLayout), db, s.db, reportID)\n\t\t} else {\n\t\t\ttables = GetCompareReportTablesForDisplay(\n\t\t\t\tcompareStartTime.Format(timeLayout), compareEndTime.Format(timeLayout),\n\t\t\t\tstartTime.Format(timeLayout), endTime.Format(timeLayout),\n\t\t\t\tdb, s.db, reportID)\n\t\t}\n\t\t_ = UpdateReportProgress(s.db, reportID, 100)\n\t\tcontent, err := json.Marshal(tables)\n\t\tif err == nil {\n\t\t\t_ = SaveReportContent(s.db, reportID, string(content))\n\t\t}\n\t}()\n\n\tc.JSON(http.StatusOK, reportID)\n}\n\n// @Summary Diagnosis report status\n// @Description Get diagnosis report status\n// @Param id path string true \"report id\"\n// @Success 200 {object} Report\n// @Router /diagnose/reports/{id}/status [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) reportStatusHandler(c *gin.Context) {\n\tid := c.Param(\"id\")\n\treport, err := GetReport(s.db, id)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, &report)\n}\n\n// @Summary SQL diagnosis report\n// @Description Get sql diagnosis report HTML\n// @Produce html\n// @Param id path string true \"report id\"\n// @Success 200 {string} string\n// @Router /diagnose/reports/{id}/detail [get]\nfunc (s *Service) reportHTMLHandler(c *gin.Context) {\n\tdefer func(old string) {\n\t\tc.Request.URL.Path = old\n\t}(c.Request.URL.Path)\n\n\tc.Request.URL.Path = \"diagnoseReport.html\"\n\ts.fileServer.ServeHTTP(c.Writer, c.Request)\n}\n\n// @Summary SQL diagnosis report data\n// @Description Get sql diagnosis report data\n// @Produce text/javascript\n// @Param id path string true \"report id\"\n// @Success 200 {string} string\n// @Router /diagnose/reports/{id}/data.js [get]\nfunc (s *Service) reportDataHandler(c *gin.Context) {\n\tid := c.Param(\"id\")\n\treport, err := GetReport(s.db, id)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tdata := \"window.__diagnosis_data__ = \" + report.Content\n\tc.Data(http.StatusOK, \"text/javascript\", []byte(data))\n}\n\ntype GenDiagnosisReportRequest struct {\n\tStartTime int64  `json:\"start_time\"`\n\tEndTime   int64  `json:\"end_time\"`\n\tKind      string `json:\"kind\"` // values: config, error, performance\n}\n\n// @Summary SQL diagnosis report\n// @Description Generate sql diagnosis report\n// @Produce json\n// @Param request body GenDiagnosisReportRequest true \"Request body\"\n// @Success 200 {object} TableDef\n// @Router /diagnose/diagnosis [post]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) genDiagnosisHandler(c *gin.Context) {\n\tvar req GenDiagnosisReportRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.WrapWithNoMessage(err))\n\t\treturn\n\t}\n\n\tstartTime := time.Unix(req.StartTime, 0)\n\tendTime := time.Unix(req.EndTime, 0)\n\n\tvar rules []string\n\tswitch req.Kind {\n\tcase \"config\":\n\t\trules = []string{\"config\", \"version\"}\n\tcase \"error\":\n\t\trules = []string{\"critical-error\"}\n\tcase \"performance\":\n\t\trules = []string{\"node-load\", \"threshold-check\"}\n\t}\n\n\tdb := utils.TakeTiDBConnection(c)\n\tdefer utils.CloseTiDBConnection(db) //nolint:errcheck\n\ttable, err := GetDiagnoseReport(startTime.Format(timeLayout), endTime.Format(timeLayout), db, rules)\n\tif err != nil {\n\t\ttableErr := TableRowDef{Values: []string{CategoryDiagnose, \"diagnose\", err.Error()}}\n\t\ttable = *GenerateReportError([]TableRowDef{tableErr})\n\t}\n\tc.JSON(http.StatusOK, table)\n}\n"
  },
  {
    "path": "pkg/apiserver/diagnose/inspection.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage diagnose\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gorm.io/gorm\"\n)\n\ntype clusterInspection struct {\n\treferStartTime string\n\treferEndTime   string\n\tstartTime      string\n\tendTime        string\n\tdb             *gorm.DB\n}\n\nfunc CompareDiagnose(referStartTime, referEndTime, startTime, endTime string, db *gorm.DB) (TableDef, *TableRowDef) {\n\tc := &clusterInspection{\n\t\treferStartTime: referStartTime,\n\t\treferEndTime:   referEndTime,\n\t\tstartTime:      startTime,\n\t\tendTime:        endTime,\n\t\tdb:             db,\n\t}\n\ttable := TableDef{\n\t\tCategory: []string{CategoryDiagnose},\n\t\tTitle:    \"compare_diagnose\",\n\t\tComment:  \"\",\n\t\tColumn:   []string{\"RULE\", \"DETAIL\"},\n\t}\n\tdetails, err := c.inspectForAffectByBigQuery()\n\tif err != nil {\n\t\treturn table, &TableRowDef{Values: []string{strings.Join(table.Category, \",\"), table.Title, err.Error()}}\n\t}\n\tif len(details) > 0 {\n\t\tsubRows := make([][]string, 0, len(details))\n\t\tfor i := range details {\n\t\t\tsubRows = append(subRows, []string{\"\", details[i]})\n\t\t}\n\t\trow := TableRowDef{\n\t\t\tValues: []string{\n\t\t\t\t\"big-query\",\n\t\t\t\t\"may have big query in diagnose time range\",\n\t\t\t},\n\t\t\tSubValues: subRows,\n\t\t\tComment:   \"diagnose for big query/write that affect the qps or duration\",\n\t\t}\n\t\ttable.Rows = []TableRowDef{row}\n\t}\n\treturn table, nil\n}\n\nfunc (c *clusterInspection) inspectForAffectByBigQuery() ([]string, error) {\n\tchecks := []struct {\n\t\tquery     metricQuery\n\t\tct        compareType\n\t\tthreshold float64\n\t}{\n\t\t{\n\t\t\tquery: &queryQPS{\n\t\t\t\tbaseQuery: baseQuery{\n\t\t\t\t\ttable:  \"tidb_qps\",\n\t\t\t\t\tlabels: []string{\"instance\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tct:        compareLT,\n\t\t\tthreshold: 0.95,\n\t\t},\n\t\t{\n\t\t\tquery: &queryQuantile{\n\t\t\t\tbaseQuery: baseQuery{\n\t\t\t\t\ttable:     \"tidb_query_duration\",\n\t\t\t\t\tlabels:    []string{\"instance\"},\n\t\t\t\t\tcondition: \"value is not null and quantile=0.999\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tct:        compareGT,\n\t\t\tthreshold: 1.2,\n\t\t},\n\t}\n\totherInfoChecks := []struct {\n\t\tquery     metricQuery\n\t\tct        compareType\n\t\tthreshold float64\n\t}{\n\t\t{\n\t\t\tquery: &queryQuantile{\n\t\t\t\tbaseQuery: baseQuery{\n\t\t\t\t\ttable:     \"tidb_cop_duration\",\n\t\t\t\t\tlabels:    []string{\"instance\"},\n\t\t\t\t\tcondition: \"value is not null and quantile=0.999\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tct:        compareGT,\n\t\t\tthreshold: 2,\n\t\t},\n\t\t{\n\t\t\t// Check for big write transaction\n\t\t\tquery: &queryQuantile{\n\t\t\t\tbaseQuery: baseQuery{\n\t\t\t\t\ttable:     \"tidb_kv_write_num\",\n\t\t\t\t\tlabels:    []string{\"instance\"},\n\t\t\t\t\tcondition: \"value is not null and quantile=0.999\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tct:        compareGT,\n\t\t\tthreshold: 2,\n\t\t},\n\t\t{\n\t\t\tquery: &queryTotal{\n\t\t\t\tbaseQuery: baseQuery{\n\t\t\t\t\ttable:  \"tikv_cop_scan_keys_total_num\",\n\t\t\t\t\tlabels: []string{\"instance\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tct:        compareGT,\n\t\t\tthreshold: 2.0,\n\t\t},\n\t\t{\n\t\t\t// Check for tikv storage handle time\n\t\t\tquery: &queryQuantile{\n\t\t\t\tbaseQuery: baseQuery{\n\t\t\t\t\ttable:     \"tikv_storage_async_request_duration\",\n\t\t\t\t\tlabels:    []string{\"instance\", \"type\"},\n\t\t\t\t\tcondition: \"value is not null and quantile=0.999\",\n\t\t\t\t},\n\t\t\t},\n\t\t\tct:        compareGT,\n\t\t\tthreshold: 2,\n\t\t},\n\t\t{\n\t\t\tquery: &queryTotal{\n\t\t\t\tbaseQuery: baseQuery{\n\t\t\t\t\ttable:  \"pd_operator_step_finish_total_count\",\n\t\t\t\t\tlabels: []string{\"type\"},\n\t\t\t\t},\n\t\t\t},\n\t\t\tct:        compareGT,\n\t\t\tthreshold: 1.0,\n\t\t},\n\t}\n\ttotalDiffs := make([]metricDiff, 0, len(checks))\n\tfor _, ck := range checks {\n\t\terr := c.compareMetric(ck.query)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tpartDiffs := ck.query.compare()\n\t\tpartDiffs = checkDiffs(partDiffs, ck.ct, ck.threshold)\n\t\ttotalDiffs = append(totalDiffs, partDiffs...)\n\t}\n\t// If both qps and query latency was not become worse, return.\n\tif len(totalDiffs) == 0 {\n\t\treturn nil, nil\n\t}\n\n\t// Only for get more information\n\tfor _, ck := range otherInfoChecks {\n\t\terr := c.compareMetric(ck.query)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tpartDiffs := ck.query.compare()\n\t\tpartDiffs = checkDiffs(partDiffs, ck.ct, ck.threshold)\n\t\ttotalDiffs = append(totalDiffs, partDiffs...)\n\t}\n\n\tvar detailSQL string\n\tvar err error\n\tdetailSQL, err = c.queryBigQueryInSlowLog()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(detailSQL) == 0 {\n\t\tdetailSQL, err = c.queryExpensiveQueryInTiDBLog()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tdetails := genMetricDiffsString(totalDiffs)\n\tif len(detailSQL) > 0 {\n\t\tdetails = append(details, \"try to check the slow query only appear in diagnose time range with sql: \\n\"+detailSQL)\n\t}\n\treturn details, nil\n}\n\nfunc (c *clusterInspection) compareMetric(query metricQuery) error {\n\targ := &queryArg{\n\t\tstartTime: c.referStartTime,\n\t\tendTime:   c.referEndTime,\n\t}\n\terr := queryMetric(query, arg, c.db)\n\tif err != nil {\n\t\treturn err\n\t}\n\tquery.setRefer()\n\n\targ.startTime = c.startTime\n\targ.endTime = c.endTime\n\terr = queryMetric(query, arg, c.db)\n\tif err != nil {\n\t\treturn err\n\t}\n\tquery.setCurrent()\n\treturn nil\n}\n\ntype metricQuery interface {\n\tinit()\n\tsetRefer()\n\tsetCurrent()\n\tcompare() []metricDiff\n\tgenerateSQL(arg *queryArg) string\n\tappendRow(row []string) error\n}\n\ntype baseQuery struct {\n\ttable     string\n\tlabels    []string\n\tcondition string\n}\n\nfunc (b *baseQuery) genCondition(arg *queryArg) string {\n\tcondition := fmt.Sprintf(\"where time >= '%s' and time < '%s' \", arg.startTime, arg.endTime)\n\tif len(b.condition) > 0 {\n\t\tcondition = condition + \"and \" + b.condition\n\t}\n\treturn condition\n}\n\ntype avgMaxMin struct {\n\tavg int\n\tmax int\n\tmin int\n}\n\ntype queryQPS struct {\n\tbaseQuery\n\tresult map[string]avgMaxMin\n\n\trefer   map[string]avgMaxMin\n\tcurrent map[string]avgMaxMin\n}\n\nfunc (s *queryQPS) init() {\n\ts.result = make(map[string]avgMaxMin)\n}\n\nfunc (s *queryQPS) setRefer() {\n\ts.refer = s.result\n\ts.result = nil\n}\n\nfunc (s *queryQPS) setCurrent() {\n\ts.current = s.result\n\ts.result = nil\n}\n\nfunc (s *queryQPS) compare() []metricDiff {\n\tdiffs := make([]metricDiff, 0, len(s.current))\n\tfor label, v := range s.current {\n\t\trv := s.refer[label]\n\t\tdiff := newMetricDiff(s.table, label, float64(rv.avg), float64(v.avg))\n\t\tdiffs = append(diffs, diff)\n\t}\n\treturn diffs\n}\n\nfunc (s *queryQPS) generateSQL(arg *queryArg) string {\n\tfield := \"\"\n\tfor i, label := range s.labels {\n\t\tif i > 0 {\n\t\t\tfield += \",\"\n\t\t}\n\t\tfield = field + \"t1.`\" + label + \"`\"\n\t}\n\tcondition := s.genCondition(arg)\n\tsql := fmt.Sprintf(\"select %[4]s, avg(value),max(value),min(value) from (select `%[3]v`, sum(value) as value from metrics_schema.%[1]s %[2]s group by `%[3]s`,time) as t1 group by %[4]s having avg(value)>0\",\n\t\ts.table, condition, strings.Join(s.labels, \"`,`\"), field)\n\tprepareSQL := \"set @@tidb_metric_query_step=30;set @@tidb_metric_query_range_duration=30;\"\n\tsql = prepareSQL + sql\n\treturn sql\n}\n\nfunc (s *queryQPS) appendRow(row []string) error {\n\tlabel := strings.Join(row[:len(s.labels)], \",\")\n\tvalues, err := batchAtoi(row[len(s.labels):])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.result[label] = avgMaxMin{\n\t\tavg: values[0],\n\t\tmax: values[1],\n\t\tmin: values[2],\n\t}\n\treturn nil\n}\n\nfunc queryMetric(query metricQuery, arg *queryArg, db *gorm.DB) error {\n\tquery.init()\n\tsql := query.generateSQL(arg)\n\trows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, row := range rows {\n\t\terr = query.appendRow(row)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\ntype queryQuantile struct {\n\tbaseQuery\n\tresult  map[string]durationValue\n\trefer   map[string]durationValue\n\tcurrent map[string]durationValue\n}\n\ntype durationValue struct {\n\tavg float64\n\tmax float64\n}\n\nfunc (s *queryQuantile) init() {\n\ts.result = make(map[string]durationValue)\n}\n\nfunc (s *queryQuantile) setRefer() {\n\ts.refer = s.result\n\ts.result = nil\n}\n\nfunc (s *queryQuantile) setCurrent() {\n\ts.current = s.result\n\ts.result = nil\n}\n\nfunc (s *queryQuantile) compare() []metricDiff {\n\tdiffs := make([]metricDiff, 0, len(s.current))\n\tfor label, v := range s.current {\n\t\trv := s.refer[label]\n\t\tdiff := newMetricDiff(s.table, label, rv.avg, v.avg)\n\t\tdiffs = append(diffs, diff)\n\t}\n\treturn diffs\n}\n\nfunc (s *queryQuantile) generateSQL(arg *queryArg) string {\n\tprepareSQL := \"set @@tidb_metric_query_step=30;set @@tidb_metric_query_range_duration=30;\"\n\tsql := fmt.Sprintf(\"select `%[1]s`, avg(value),max(value) from metrics_schema.%[2]s %[3]s group by `%[1]s`\",\n\t\tstrings.Join(s.labels, \"`,`\"), s.table, s.genCondition(arg))\n\tsql = prepareSQL + sql\n\treturn sql\n}\n\nfunc (s *queryQuantile) appendRow(row []string) error {\n\tlabel := strings.Join(row[:len(s.labels)], \",\")\n\tvalues, err := batchAtof(row[len(s.labels):])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.result[label] = durationValue{\n\t\tavg: values[0],\n\t\tmax: values[1],\n\t}\n\treturn nil\n}\n\ntype queryTotal struct {\n\tbaseQuery\n\tresult  map[string]float64\n\trefer   map[string]float64\n\tcurrent map[string]float64\n}\n\nfunc (s *queryTotal) init() {\n\ts.result = make(map[string]float64)\n}\n\nfunc (s *queryTotal) setRefer() {\n\ts.refer = s.result\n\ts.result = nil\n}\n\nfunc (s *queryTotal) setCurrent() {\n\ts.current = s.result\n\ts.result = nil\n}\n\nfunc (s *queryTotal) compare() []metricDiff {\n\tdiffs := make([]metricDiff, 0, len(s.current))\n\tfor label, v := range s.current {\n\t\trv := s.refer[label]\n\t\tdiff := newMetricDiff(s.table, label, rv, v)\n\t\tdiffs = append(diffs, diff)\n\t}\n\treturn diffs\n}\n\nfunc (s *queryTotal) generateSQL(arg *queryArg) string {\n\tprepareSQL := \"set @@tidb_metric_query_step=60;set @@tidb_metric_query_range_duration=60;\"\n\tsql := fmt.Sprintf(\"select `%[1]s`, sum(value) as total from metrics_schema.%[2]s %[3]s group by `%[1]s` having total > 0\",\n\t\tstrings.Join(s.labels, \"`,`\"), s.table, s.genCondition(arg))\n\tsql = prepareSQL + sql\n\treturn sql\n}\n\nfunc (s *queryTotal) appendRow(row []string) error {\n\tlabel := strings.Join(row[:len(s.labels)], \",\")\n\tvalues, err := batchAtof(row[len(s.labels):])\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.result[label] = values[0]\n\treturn nil\n}\n\nfunc (c *clusterInspection) queryBigQueryInSlowLog() (string, error) {\n\tsql := fmt.Sprintf(`select count(*) from\n    (select sum(Process_time) as sum_process_time,\n         digest\n    from information_schema.CLUSTER_SLOW_QUERY\n    where time >= '%s'\n            AND time < '%s'\n\t\t\tAND Is_internal = false\n    group by  digest) AS t1\n\twhere t1.digest NOT IN\n    (select digest\n    from information_schema.CLUSTER_SLOW_QUERY\n    where time >= '%s'\n            and time < '%s'\n    group by  digest);`, c.startTime, c.endTime, c.referStartTime, c.referEndTime)\n\trows, err := querySQL(c.db, sql)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(rows) == 0 || len(rows[0]) == 0 {\n\t\treturn \"\", nil\n\t}\n\tcount, err := strconv.Atoi(rows[0][0])\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif count == 0 {\n\t\treturn \"\", nil\n\t}\n\treturn fmt.Sprintf(`select * from\n    (select count(*),\n         min(time),\n         sum(query_time) AS sum_query_time,\n         sum(Process_time) AS sum_process_time,\n         sum(Wait_time) AS sum_wait_time,\n         sum(Commit_time),\n         sum(Request_count),\n         sum(process_keys),\n         sum(Write_keys),\n         max(Cop_proc_max),\n         min(query),min(prev_stmt),\n         digest\n    from information_schema.CLUSTER_SLOW_QUERY\n    where time >= '%s'\n            and time < '%s'\n            and Is_internal = false\n    group by  digest) AS t1\n\twhere t1.digest NOT IN\n    (select digest\n    from information_schema.CLUSTER_SLOW_QUERY\n    where time >= '%s'\n            AND time < '%s'\n    group by  digest)\n\torder by  t1.sum_query_time desc limit 10;`, c.startTime, c.endTime, c.referStartTime, c.referEndTime), nil\n}\n\nfunc (c *clusterInspection) queryExpensiveQueryInTiDBLog() (string, error) {\n\tsql := fmt.Sprintf(`select count(*) from information_schema.cluster_log where type='tidb' and time >= '%s' and time < '%s' and level = 'warn' and message LIKE '%s'`,\n\t\tc.startTime, c.endTime, \"%expensive_query%\")\n\trows, err := querySQL(c.db, sql)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif len(rows) == 0 || len(rows[0]) == 0 {\n\t\treturn \"\", nil\n\t}\n\tcount, err := strconv.Atoi(rows[0][0])\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif count == 0 {\n\t\treturn \"\", nil\n\t}\n\tsql = strings.Replace(sql, \"count(*)\", \"*\", 1)\n\treturn sql, nil\n}\n\nfunc batchAtof(ss []string) ([]float64, error) {\n\tre := make([]float64, len(ss))\n\tfor i := range ss {\n\t\tv, err := strconv.ParseFloat(ss[i], 64)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tre[i] = v\n\t}\n\treturn re, nil\n}\n\nfunc batchAtoi(ss []string) ([]int, error) {\n\tre := make([]int, len(ss))\n\tfor i := range ss {\n\t\tv, err := strconv.ParseFloat(ss[i], 64)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tre[i] = int(math.Round(v))\n\t}\n\treturn re, nil\n}\n\nfunc calculateDiff(refer float64, check float64) float64 {\n\tif refer != 0 {\n\t\treturn check / refer\n\t}\n\treturn check\n}\n\ntype metricDiff struct {\n\ttp    string\n\tlabel string\n\tratio float64\n\trv    float64\n\tv     float64\n}\n\nfunc newMetricDiff(tp, label string, refer, check float64) metricDiff {\n\treturn metricDiff{\n\t\ttp:    tp,\n\t\tlabel: label,\n\t\tratio: calculateDiff(refer, check),\n\t\trv:    refer,\n\t\tv:     check,\n\t}\n}\n\nfunc (d metricDiff) String() string {\n\tif d.ratio > 1 {\n\t\treturn fmt.Sprintf(\"%s,%s: ↑ %.2f (%.2f / %.2f)\", d.tp, d.label, d.ratio, d.v, d.rv)\n\t}\n\treturn fmt.Sprintf(\"%s,%s: ↓ %.2f (%.2f / %.2f)\", d.tp, d.label, d.ratio, d.v, d.rv)\n}\n\ntype compareType bool\n\nconst (\n\tcompareLT compareType = false\n\tcompareGT compareType = true\n)\n\nfunc checkDiffs(diffs []metricDiff, tp compareType, threshold float64) []metricDiff {\n\tvar result []metricDiff\n\tfor i := range diffs {\n\t\tswitch tp {\n\t\tcase compareLT:\n\t\t\tif diffs[i].ratio < threshold {\n\t\t\t\tresult = append(result, diffs[i])\n\t\t\t}\n\t\tcase compareGT:\n\t\t\tif diffs[i].ratio > threshold {\n\t\t\t\tresult = append(result, diffs[i])\n\t\t\t}\n\t\t}\n\t}\n\treturn result\n}\n\nfunc genMetricDiffsString(diffs []metricDiff) []string {\n\tss := make([]string, 0, len(diffs))\n\tfor i := range diffs {\n\t\tss = append(ss, diffs[i].String())\n\t}\n\treturn ss\n}\n"
  },
  {
    "path": "pkg/apiserver/diagnose/model.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage diagnose\n\nimport (\n\t\"time\"\n\n\t\"github.com/google/uuid\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n)\n\ntype Report struct {\n\tID               string     `gorm:\"primary_key;size:40\" json:\"id\"`\n\tCreatedAt        time.Time  `json:\"created_at\"`\n\tProgress         int        `json:\"progress\"` // 0~100\n\tContent          string     `json:\"content\"`\n\tStartTime        time.Time  `json:\"start_time\"`\n\tEndTime          time.Time  `json:\"end_time\"`\n\tCompareStartTime *time.Time `json:\"compare_start_time\"`\n\tCompareEndTime   *time.Time `json:\"compare_end_time\"`\n}\n\nfunc (Report) TableName() string {\n\treturn \"diagnose_reports\"\n}\n\nfunc autoMigrate(db *dbstore.DB) error {\n\treturn db.AutoMigrate(&Report{})\n}\n\nfunc NewReport(db *dbstore.DB, startTime, endTime time.Time, compareStartTime, compareEndTime *time.Time) (string, error) {\n\treport := Report{\n\t\tID:               uuid.New().String(),\n\t\tCreatedAt:        time.Now(),\n\t\tStartTime:        startTime,\n\t\tEndTime:          endTime,\n\t\tCompareStartTime: compareStartTime,\n\t\tCompareEndTime:   compareEndTime,\n\t}\n\terr := db.Create(&report).Error\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn report.ID, nil\n}\n\nfunc GetReports(db *dbstore.DB) ([]Report, error) {\n\tvar reports []Report\n\terr := db.\n\t\tSelect(\"id, created_at, progress, start_time, end_time, compare_start_time, compare_end_time\").\n\t\tOrder(\"created_at desc\").\n\t\tFind(&reports).Error\n\treturn reports, err\n}\n\nfunc GetReport(db *dbstore.DB, reportID string) (*Report, error) {\n\tvar report Report\n\terr := db.Where(\"id = ?\", reportID).First(&report).Error\n\treturn &report, err\n}\n\nfunc UpdateReportProgress(db *dbstore.DB, reportID string, progress int) error {\n\tvar report Report\n\treport.ID = reportID\n\treturn db.Model(&report).Update(\"progress\", progress).Error\n}\n\nfunc SaveReportContent(db *dbstore.DB, reportID string, content string) error {\n\tvar report Report\n\treport.ID = reportID\n\treturn db.Model(&report).Update(\"content\", content).Error\n}\n"
  },
  {
    "path": "pkg/apiserver/diagnose/query.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage diagnose\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"gorm.io/gorm\"\n)\n\ntype rowQuery interface {\n\tqueryRow(arg *queryArg, db *gorm.DB) (*TableRowDef, error)\n}\n\ntype queryArg struct {\n\ttotalTime float64\n\tstartTime string\n\tendTime   string\n\tquantiles []float64\n}\n\nfunc newQueryArg(startTime, endTime string) *queryArg {\n\treturn &queryArg{\n\t\tstartTime: startTime,\n\t\tendTime:   endTime,\n\t\tquantiles: []float64{0.999, 0.99, 0.90, 0.80},\n\t}\n}\n\ntype AvgMaxMinTableDef struct {\n\tname      string\n\ttbl       string\n\tcondition string\n\tlabels    []string\n\tComment   string\n}\n\n// Table schema\n// METRIC_NAME , LABEL, AVG(VALUE), MAX(VALUE), MIN(VALUE),.\nfunc (t AvgMaxMinTableDef) queryRow(arg *queryArg, db *gorm.DB) (*TableRowDef, error) {\n\tif len(t.name) == 0 {\n\t\tt.name = t.tbl\n\t}\n\tcondition := fmt.Sprintf(\"where time >= '%s' and time < '%s' \", arg.startTime, arg.endTime)\n\tif len(t.condition) > 0 {\n\t\tcondition = condition + \"and \" + t.condition\n\t}\n\tsql := fmt.Sprintf(\"select '%s', '', avg(value), max(value), min(value) from metrics_schema.%s %s\",\n\t\tt.name, t.tbl, condition)\n\trows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(rows) == 0 {\n\t\treturn nil, nil\n\t}\n\tif len(t.labels) == 0 {\n\t\treturn t.genRow(rows[0], nil), nil\n\t}\n\n\tsql = fmt.Sprintf(\"select '%[1]s',`%[2]v`, avg(value), max(value), min(value) from metrics_schema.%[3]v %[4]s group by `%[2]v` order by avg(value) desc\",\n\t\tt.name, strings.Join(t.labels, \"`,`\"), t.tbl, condition)\n\tsubRows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor i := range subRows {\n\t\trow := subRows[i]\n\t\trow[1] = strings.Join(row[1:1+len(t.labels)], \",\")\n\t\tnewRow := row[:2]\n\t\tnewRow = append(newRow, row[1+len(t.labels):]...)\n\t\tsubRows[i] = newRow\n\t}\n\treturn t.genRow(rows[0], subRows), nil\n}\n\nfunc (t AvgMaxMinTableDef) genRow(values []string, subValues [][]string) *TableRowDef {\n\tspecialHandle := func(row []string) []string {\n\t\tif len(row) == 0 {\n\t\t\treturn row\n\t\t}\n\t\trow[2] = RoundFloatString(row[2])\n\t\trow[3] = RoundFloatString(row[3])\n\t\trow[4] = RoundFloatString(row[4])\n\t\treturn row\n\t}\n\n\tvalues = specialHandle(values)\n\tfor i := range subValues {\n\t\tsubValues[i] = specialHandle(subValues[i])\n\t}\n\n\treturn &TableRowDef{\n\t\tValues:    values,\n\t\tSubValues: subValues,\n\t\tComment:   t.Comment,\n\t}\n}\n\ntype sumValueQuery struct {\n\tname      string\n\ttbl       string\n\tcondition string\n\tlabels    []string\n\tcomment   string\n}\n\n// Table schema\n// METRIC_NAME , LABEL  TOTAL_VALUE.\nfunc (t sumValueQuery) queryRow(arg *queryArg, db *gorm.DB) (*TableRowDef, error) {\n\tif len(t.name) == 0 {\n\t\tt.name = t.tbl\n\t}\n\tcondition := fmt.Sprintf(\"where time >= '%s' and time < '%s' \", arg.startTime, arg.endTime)\n\tif len(t.condition) > 0 {\n\t\tcondition = condition + \"and \" + t.condition\n\t}\n\tsql := fmt.Sprintf(\"select '%s', '', sum(value) from metrics_schema.%s %s\",\n\t\tt.name, t.tbl, condition)\n\trows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(rows) == 0 {\n\t\treturn nil, nil\n\t}\n\tif len(t.labels) == 0 {\n\t\treturn t.genRow(rows[0], nil), nil\n\t}\n\n\tsql = fmt.Sprintf(\"select '%[1]v',`%[2]v`, sum(value) from metrics_schema.%[3]v %[4]s group by `%[2]v` having sum(value) > 0 order by sum(value) desc\",\n\t\tt.name, strings.Join(t.labels, \"`,`\"), t.tbl, condition)\n\tsubRows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor i := range subRows {\n\t\trow := subRows[i]\n\t\trow[1] = strings.Join(row[1:1+len(t.labels)], \",\")\n\t\tnewRow := row[:2]\n\t\tnewRow = append(newRow, row[1+len(t.labels):]...)\n\t\tsubRows[i] = newRow\n\t}\n\treturn t.genRow(rows[0], subRows), nil\n}\n\nfunc (t sumValueQuery) genRow(values []string, subValues [][]string) *TableRowDef {\n\tspecialHandle := func(row []string) []string {\n\t\tif len(row) == 0 {\n\t\t\treturn row\n\t\t}\n\t\trow[2] = RoundFloatString(row[2])\n\t\treturn row\n\t}\n\n\tvalues = specialHandle(values)\n\tfor i := range subValues {\n\t\tsubValues[i] = specialHandle(subValues[i])\n\t}\n\treturn &TableRowDef{\n\t\tValues:    values,\n\t\tSubValues: subValues,\n\t\tComment:   genComment(t.comment, t.labels),\n\t}\n}\n\ntype totalTimeByLabelsTableDef struct {\n\tname    string\n\ttbl     string\n\tlabels  []string\n\tcomment string\n}\n\n// Table schema\n// METRIC_NAME , LABEL , TIME_RATIO ,  TOTAL_VALUE , TOTAL_COUNT , P999 , P99 , P90 , P80.\nfunc (t totalTimeByLabelsTableDef) queryRow(arg *queryArg, db *gorm.DB) (*TableRowDef, error) {\n\tsql := t.genSumarySQLs(arg.totalTime, arg.startTime, arg.endTime, arg.quantiles)\n\trows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(rows) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tif len(t.labels) == 0 {\n\t\treturn t.genRow(rows[0], nil), nil\n\t}\n\n\tif arg.totalTime == 0 && len(rows[0][3]) > 0 {\n\t\ttotalTime, err := strconv.ParseFloat(rows[0][3], 64)\n\t\tif err == nil {\n\t\t\targ.totalTime = totalTime\n\t\t}\n\t}\n\n\tsql = t.genDetailSQLs(arg.totalTime, arg.startTime, arg.endTime, arg.quantiles)\n\tsubRows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor i := range subRows {\n\t\trow := subRows[i]\n\t\trow[1] = strings.Join(row[1:1+len(t.labels)], \",\")\n\t\tnewRow := row[:2]\n\t\tnewRow = append(newRow, row[1+len(t.labels):]...)\n\t\tsubRows[i] = newRow\n\t}\n\treturn t.genRow(rows[0], subRows), nil\n}\n\nfunc (t totalTimeByLabelsTableDef) genRow(values []string, subValues [][]string) *TableRowDef {\n\tspecialHandle := func(row []string) []string {\n\t\tif len(row) == 0 {\n\t\t\treturn row\n\t\t}\n\t\tname := row[0]\n\t\tif strings.HasSuffix(name, \"(us)\") {\n\t\t\tif len(row[3]) == 0 {\n\t\t\t\treturn row\n\t\t\t}\n\t\t\tfor _, i := range []int{2, 3, 5, 6, 7, 8} {\n\t\t\t\tv, err := strconv.ParseFloat(row[i], 64)\n\t\t\t\tif err == nil {\n\t\t\t\t\trow[i] = fmt.Sprintf(\"%f\", v/10e5)\n\t\t\t\t}\n\t\t\t}\n\t\t\trow[0] = name[:len(name)-4]\n\t\t}\n\t\tif len(row[4]) > 0 {\n\t\t\trow[4] = convertFloatToInt(row[4])\n\t\t}\n\t\tfor _, i := range []int{2, 3, 5, 6, 7, 8} {\n\t\t\trow[i] = RoundFloatString(row[i])\n\t\t}\n\t\treturn row\n\t}\n\n\tvalues = specialHandle(values)\n\tfor i := range subValues {\n\t\tsubValues[i] = specialHandle(subValues[i])\n\t}\n\n\treturn &TableRowDef{\n\t\tValues:    values,\n\t\tSubValues: subValues,\n\t\tComment:   genComment(t.comment, t.labels),\n\t}\n}\n\nfunc (t totalTimeByLabelsTableDef) genSumarySQLs(totalTime float64, startTime, endTime string, quantiles []float64) string {\n\tsqls := []string{ //nolint:prealloc\n\t\tfmt.Sprintf(\"select '%[1]s','', if(%[2]v>0,sum(value)/%[2]v,1) , sum(value) from metrics_schema.%[3]s_total_time where time >= '%[4]s' and time < '%[5]s'\",\n\t\t\tt.name, totalTime, t.tbl, startTime, endTime),\n\t\tfmt.Sprintf(\"select sum(value) from metrics_schema.%s_total_count where time >= '%s' and time < '%s'\",\n\t\t\tt.tbl, startTime, endTime),\n\t}\n\tfor _, quantile := range quantiles {\n\t\tsql := fmt.Sprintf(\"select max(value) as max_value from metrics_schema.%s_duration where time >= '%s' and time < '%s' and quantile=%f\",\n\t\t\tt.tbl, startTime, endTime, quantile)\n\t\tsqls = append(sqls, sql)\n\t}\n\tvar fields strings.Builder\n\ttbls := \"\"\n\tfor i, sql := range sqls {\n\t\tif i > 0 {\n\t\t\tfields.WriteString(\",\")\n\t\t\ttbls += \"join \"\n\t\t}\n\t\tfmt.Fprintf(&fields, \"t%v.*\", i)\n\t\ttbls += fmt.Sprintf(\" (%s) as t%v \", sql, i)\n\t}\n\tjoinSQL := fmt.Sprintf(\"select %v from %v\", fields.String(), tbls)\n\treturn joinSQL\n}\n\nfunc (t totalTimeByLabelsTableDef) genDetailSQLs(totalTime float64, startTime, endTime string, quantiles []float64) string {\n\tif len(t.labels) == 0 {\n\t\treturn \"\"\n\t}\n\tvar joinSQL strings.Builder\n\tjoinSQL.WriteString(\"select t0.*,t1.total_count\")\n\tsqls := []string{\n\t\tfmt.Sprintf(\"select '%[1]s', `%[6]s`, if(%[2]v>0,sum(value)/%[2]v,1) , sum(value) as total from metrics_schema.%[3]s_total_time where time >= '%[4]s' and time < '%[5]s' group by `%[6]s` having sum(value) > 0\",\n\t\t\tt.name, totalTime, t.tbl, startTime, endTime, strings.Join(t.labels, \"`,`\")),\n\n\t\tfmt.Sprintf(\"select `%[4]s`, sum(value) as total_count from metrics_schema.%[1]s_total_count where time >= '%[2]s' and time < '%[3]s' group by `%[4]s`\",\n\t\t\tt.tbl, startTime, endTime, strings.Join(t.labels, \"`,`\")),\n\t}\n\tfor i, quantile := range quantiles {\n\t\tsql := fmt.Sprintf(\"select `%[5]s`, max(value) as max_value from metrics_schema.%[1]s_duration where time >= '%[2]s' and time < '%[3]s' and quantile=%[4]f group by `%[5]s`\",\n\t\t\tt.tbl, startTime, endTime, quantile, strings.Join(t.labels, \"`,`\"))\n\t\tsqls = append(sqls, sql)\n\t\tfmt.Fprintf(&joinSQL, \",t%v.max_value\", i+2)\n\t}\n\tjoinSQL.WriteString(\" from \")\n\tfor i, sql := range sqls {\n\t\tfmt.Fprintf(&joinSQL, \" (%s) as t%v \", sql, i)\n\t\tif i != len(sqls)-1 {\n\t\t\tjoinSQL.WriteString(\"join \")\n\t\t}\n\t}\n\tjoinSQL.WriteString(\" where \")\n\tfor i := 0; i < len(sqls)-1; i++ {\n\t\tfor j, label := range t.labels {\n\t\t\tif i > 0 || j > 0 {\n\t\t\t\tjoinSQL.WriteString(\"and \")\n\t\t\t}\n\t\t\tfmt.Fprintf(&joinSQL, \" t%v.%s = t%v.%s \", i, label, i+1, label)\n\t\t}\n\t}\n\tjoinSQL.WriteString(\" order by t0.total desc\")\n\treturn joinSQL.String()\n}\n\ntype totalValueAndTotalCountTableDef struct {\n\tname     string\n\ttbl      string\n\tsumTbl   string\n\tcountTbl string\n\tlabels   []string\n\tcomment  string\n}\n\n// Table schema\n// METRIC_NAME , LABEL  TOTAL_VALUE , TOTAL_COUNT , P999 , P99 , P90 , P80.\nfunc (t totalValueAndTotalCountTableDef) queryRow(arg *queryArg, db *gorm.DB) (*TableRowDef, error) {\n\tsql := t.genSumarySQLs(arg.startTime, arg.endTime, arg.quantiles)\n\trows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(rows) == 0 {\n\t\treturn nil, nil\n\t}\n\n\tif len(t.labels) == 0 {\n\t\treturn t.genRow(rows[0], nil), nil\n\t}\n\tsql = t.genDetailSQLs(arg.startTime, arg.endTime, arg.quantiles)\n\tsubRows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor i := range subRows {\n\t\trow := subRows[i]\n\t\trow[1] = strings.Join(row[1:1+len(t.labels)], \",\")\n\t\tnewRow := row[:2]\n\t\tnewRow = append(newRow, row[1+len(t.labels):]...)\n\t\tsubRows[i] = newRow\n\t}\n\treturn t.genRow(rows[0], subRows), nil\n}\n\nfunc (t totalValueAndTotalCountTableDef) genRow(values []string, subValues [][]string) *TableRowDef {\n\tspecialHandle := func(row []string) []string {\n\t\tfor i := 2; i < len(row); i++ {\n\t\t\tif len(row[i]) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trow[i] = convertFloatToInt(row[i])\n\t\t}\n\t\treturn row\n\t}\n\n\tvalues = specialHandle(values)\n\tfor i := range subValues {\n\t\tsubValues[i] = specialHandle(subValues[i])\n\t}\n\n\treturn &TableRowDef{\n\t\tValues:    values,\n\t\tSubValues: subValues,\n\t\tComment:   genComment(t.comment, t.labels),\n\t}\n}\n\nfunc (t totalValueAndTotalCountTableDef) genSumarySQLs(startTime, endTime string, quantiles []float64) string {\n\tsqls := []string{ //nolint:prealloc\n\t\tfmt.Sprintf(\"select '%[1]s','' , sum(value) from metrics_schema.%[2]s where time >= '%[3]s' and time < '%[4]s'\",\n\t\t\tt.name, t.sumTbl, startTime, endTime),\n\t\tfmt.Sprintf(\"select sum(value) from metrics_schema.%s where time >= '%s' and time < '%s'\",\n\t\t\tt.countTbl, startTime, endTime),\n\t}\n\tfor _, quantile := range quantiles {\n\t\tsql := fmt.Sprintf(\"select max(value) as max_value from metrics_schema.%s where time >= '%s' and time < '%s' and quantile=%f\",\n\t\t\tt.tbl, startTime, endTime, quantile)\n\t\tsqls = append(sqls, sql)\n\t}\n\tvar fields strings.Builder\n\ttbls := \"\"\n\tfor i, sql := range sqls {\n\t\tif i > 0 {\n\t\t\tfields.WriteString(\",\")\n\t\t\ttbls += \"join \"\n\t\t}\n\t\tfmt.Fprintf(&fields, \"t%v.*\", i)\n\t\ttbls += fmt.Sprintf(\" (%s) as t%v \", sql, i)\n\t}\n\tjoinSQL := fmt.Sprintf(\"select %v from %v\", fields.String(), tbls)\n\treturn joinSQL\n}\n\nfunc (t totalValueAndTotalCountTableDef) genDetailSQLs(startTime, endTime string, quantiles []float64) string {\n\tif len(t.labels) == 0 {\n\t\treturn \"\"\n\t}\n\tvar joinSQL strings.Builder\n\tjoinSQL.WriteString(\"select t0.*,t1.count\")\n\tsqls := []string{\n\t\tfmt.Sprintf(\"select '%[1]s', `%[5]s` , sum(value) as total from metrics_schema.%[2]s where time >= '%[3]s' and time < '%[4]s' group by `%[5]s` having sum(value) > 0\",\n\t\t\tt.name, t.sumTbl, startTime, endTime, strings.Join(t.labels, \"`,`\")),\n\t\tfmt.Sprintf(\"select `%[4]s`, sum(value) as count from metrics_schema.%[1]s where time >= '%[2]s' and time < '%[3]s' group by `%[4]s`\",\n\t\t\tt.countTbl, startTime, endTime, strings.Join(t.labels, \"`,`\")),\n\t}\n\tfor i, quantile := range quantiles {\n\t\tsql := fmt.Sprintf(\"select `%[5]s`, max(value) as max_value from metrics_schema.%[1]s where time >= '%[2]s' and time < '%[3]s' and quantile=%[4]f group by `%[5]s`\",\n\t\t\tt.tbl, startTime, endTime, quantile, strings.Join(t.labels, \"`,`\"))\n\t\tsqls = append(sqls, sql)\n\t\tfmt.Fprintf(&joinSQL, \",t%v.max_value\", i+2)\n\t}\n\tjoinSQL.WriteString(\" from \")\n\tfor i, sql := range sqls {\n\t\tfmt.Fprintf(&joinSQL, \" (%s) as t%v \", sql, i)\n\t\tif i != len(sqls)-1 {\n\t\t\tjoinSQL.WriteString(\"join \")\n\t\t}\n\t}\n\tjoinSQL.WriteString(\" where \")\n\tfor i := 0; i < len(sqls)-1; i++ {\n\t\tfor j, label := range t.labels {\n\t\t\tif i > 0 || j > 0 {\n\t\t\t\tjoinSQL.WriteString(\"and \")\n\t\t\t}\n\t\t\tfmt.Fprintf(&joinSQL, \" t%v.%s = t%v.%s \", i, label, i+1, label)\n\t\t}\n\t}\n\tjoinSQL.WriteString(\" order by t0.total desc\")\n\treturn joinSQL.String()\n}\n\nfunc querySQL(db *gorm.DB, sql string) ([][]string, error) {\n\tif len(sql) == 0 {\n\t\treturn nil, nil\n\t}\n\n\trows, err := db.Raw(sql).Rows()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer rows.Close()\n\t// Read all rows.\n\tresultRows := make([][]string, 0, 2)\n\tfor rows.Next() {\n\t\tcols, err1 := rows.Columns()\n\t\tif err1 != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// See https://stackoverflow.com/questions/14477941/read-select-columns-into-string-in-go\n\t\trawResult := make([][]byte, len(cols))\n\t\tdest := make([]interface{}, len(cols))\n\t\tfor i := range rawResult {\n\t\t\tdest[i] = &rawResult[i]\n\t\t}\n\n\t\terr1 = rows.Scan(dest...)\n\t\tif err1 != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\tresultRow := []string{}\n\t\tfor _, raw := range rawResult {\n\t\t\tval := \"\"\n\t\t\tif raw != nil {\n\t\t\t\tval = string(raw)\n\t\t\t}\n\n\t\t\tresultRow = append(resultRow, val)\n\t\t}\n\t\tresultRows = append(resultRows, resultRow)\n\t}\n\tif err = rows.Err(); err != nil {\n\t\treturn nil, err\n\t}\n\treturn resultRows, nil\n}\n\nfunc convertFloatToInt(s string) string {\n\tf, err := strconv.ParseFloat(s, 64)\n\tif err != nil {\n\t\treturn s\n\t}\n\tf = math.Round(f)\n\treturn fmt.Sprintf(\"%.0f\", f)\n}\n\nfunc convertFloatToSize(s string) string {\n\tf, err := strconv.ParseFloat(s, 64)\n\tif err != nil {\n\t\treturn s\n\t}\n\tif mb := f / float64(1024*1024*1024); mb > 1 {\n\t\tf = math.Round(mb*1000) / 1000\n\t\treturn fmt.Sprintf(\"%.3f GB\", f)\n\t}\n\tif mb := f / float64(1024*1024); mb > 1 {\n\t\tf = math.Round(mb*1000) / 1000\n\t\treturn fmt.Sprintf(\"%.3f MB\", f)\n\t}\n\tkb := f / float64(1024)\n\tf = math.Round(kb*1000) / 1000\n\treturn fmt.Sprintf(\"%.3f KB\", f)\n}\n\nfunc convertFloatToDuration(s string, ratio float64) string {\n\tf, err := strconv.ParseFloat(s, 64)\n\tif err != nil {\n\t\treturn s\n\t}\n\tf = f * ratio\n\tif f > 10 {\n\t\tf = math.Round(f*1000) / 1000\n\t\treturn fmt.Sprintf(\"%.0f s\", f)\n\t}\n\tif ms := f * 1000; ms > 10 {\n\t\tf = math.Round(ms*1000) / 1000\n\t\treturn fmt.Sprintf(\"%.0f ms\", f)\n\t}\n\tus := f * 1000 * 1000\n\tf = math.Round(us*1000) / 1000\n\treturn fmt.Sprintf(\"%.0f us\", f)\n}\n\nfunc convertFloatToSizeByRows(rows []TableRowDef, idx int) {\n\tfor i := range rows {\n\t\tconvertFloatToSizeByRow(&rows[i], idx)\n\t}\n}\n\nfunc convertFloatToSizeByRow(row *TableRowDef, idx int) {\n\tif len(row.Values) < (idx + 1) {\n\t\treturn\n\t}\n\trow.Values[idx] = convertFloatToSize(row.Values[idx])\n\tfor j := range row.SubValues {\n\t\tif len(row.SubValues[j]) < (idx + 1) {\n\t\t\tcontinue\n\t\t}\n\t\trow.SubValues[j][idx] = convertFloatToSize(row.SubValues[j][idx])\n\t}\n}\n\nfunc RoundFloatString(s string) string {\n\tf, err := strconv.ParseFloat(s, 64)\n\tif err != nil {\n\t\treturn s\n\t}\n\treturn convertFloatToString(f)\n}\n\nfunc convertFloatToString(f float64) string {\n\tif f == 0 {\n\t\treturn \"0\"\n\t}\n\tsign := float64(1)\n\tif f < 0 {\n\t\tsign = -1\n\t\tf = 0 - f\n\t}\n\ttmp := f\n\tn := 2\n\tfor tmp <= 0.01 {\n\t\ttmp = tmp * 10\n\t\tn++\n\t\tif n > 15 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tvalue := math.Pow10(n)\n\tf = math.Round(f*value) / value\n\n\tformat := `%.` + strconv.FormatInt(int64(n), 10) + `f`\n\tstr := fmt.Sprintf(format, f*sign)\n\tif strings.Contains(str, \".\") {\n\t\tfor strings.HasSuffix(str, \"0\") {\n\t\t\tstr = str[:len(str)-1]\n\t\t}\n\t}\n\tif strings.HasSuffix(str, \".\") {\n\t\treturn str[:len(str)-1]\n\t}\n\treturn str\n}\n\nfunc genComment(comment string, labels []string) string {\n\tif len(labels) > 0 {\n\t\tif len(comment) > 0 {\n\t\t\tcomment += \",\"\n\t\t}\n\t\tcomment = fmt.Sprintf(\"%s the label is [%s]\", comment, strings.Join(labels, \", \"))\n\t}\n\treturn comment\n}\n\nfunc sortRowsByIndex(resultRows []TableRowDef, idx int) {\n\t// sort sub rows.\n\tfor j := range resultRows {\n\t\tsubValues := resultRows[j].SubValues\n\t\tsort.Slice(subValues, func(i, j int) bool {\n\t\t\tif len(subValues[i]) < (idx+1) || len(subValues[j]) < (idx+1) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tv1, err1 := parseFloat(subValues[i][idx])\n\t\t\tv2, err2 := parseFloat(subValues[j][idx])\n\t\t\tif err1 != nil || err2 != nil {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn v1 > v2\n\t\t})\n\t\tresultRows[j].SubValues = subValues\n\t}\n\tsort.Slice(resultRows, func(i, j int) bool {\n\t\tif len(resultRows[i].Values) < (idx+1) || len(resultRows[j].Values) < (idx+1) {\n\t\t\treturn false\n\t\t}\n\t\tv1, err1 := parseFloat(resultRows[i].Values[idx])\n\t\tv2, err2 := parseFloat(resultRows[j].Values[idx])\n\t\tif err1 != nil || err2 != nil {\n\t\t\treturn false\n\t\t}\n\t\treturn v1 > v2\n\t})\n}\n"
  },
  {
    "path": "pkg/apiserver/diagnose/report.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage diagnose\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"runtime\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n)\n\ntype TableDef struct {\n\tCategory       []string `json:\"category\"` // The category of the table, such as [TiDB]\n\tTitle          string   `json:\"title\"`\n\tComment        string   `json:\"comment\"`\n\tjoinColumns    []int\n\tcompareColumns []int\n\tColumn         []string      `json:\"column\"`\n\tRows           []TableRowDef `json:\"rows\"`\n}\n\ntype TableRowDef struct {\n\tValues    []string   `json:\"values\"`\n\tSubValues [][]string `json:\"sub_values\"` // SubValues need fold default.\n\tratio     float64\n\tComment   string `json:\"comment\"`\n}\n\nfunc (t TableDef) ColumnWidth() []int {\n\tfieldLen := make([]int, 0, len(t.Column))\n\tif len(t.Rows) == 0 {\n\t\treturn fieldLen\n\t}\n\tfor i := 0; i < len(t.Column); i++ {\n\t\tl := 0\n\t\tfor _, row := range t.Rows {\n\t\t\tif l < len(row.Values[i]) {\n\t\t\t\tl = len(row.Values[i])\n\t\t\t}\n\t\t\tfor _, subRow := range row.SubValues {\n\t\t\t\tif l < len(subRow[i]) {\n\t\t\t\t\tl = len(subRow[i])\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor _, col := range t.Column {\n\t\t\tif l < len(col) {\n\t\t\t\tl = len(col)\n\t\t\t}\n\t\t}\n\t\tfieldLen = append(fieldLen, l)\n\t}\n\treturn fieldLen\n}\n\nconst (\n\t// Category names.\n\tCategoryHeader   = \"header\"\n\tCategoryDiagnose = \"diagnose\"\n\tCategoryLoad     = \"load\"\n\tCategoryOverview = \"overview\"\n\tCategoryTiDB     = \"TiDB\"\n\tCategoryPD       = \"PD\"\n\tCategoryTiKV     = \"TiKV\"\n\tCategoryConfig   = \"config\"\n\tCategoryError    = \"error\"\n)\n\nfunc GetReportTablesForDisplay(startTime, endTime string, db *gorm.DB, sqliteDB *dbstore.DB, reportID string) []*TableDef {\n\terrRows := checkBeforeReport(db)\n\tif len(errRows) > 0 {\n\t\treturn []*TableDef{GenerateReportError(errRows)}\n\t}\n\ttables := GetReportTables(startTime, endTime, db, sqliteDB, reportID)\n\n\tlastCategory := \"\"\n\tfor _, tbl := range tables {\n\t\tif tbl == nil {\n\t\t\tcontinue\n\t\t}\n\t\tcategory := strings.Join(tbl.Category, \",\")\n\t\tif category != lastCategory {\n\t\t\tlastCategory = category\n\t\t} else {\n\t\t\ttbl.Category = []string{\"\"}\n\t\t}\n\t}\n\treturn tables\n}\n\nfunc checkBeforeReport(db *gorm.DB) (errRows []TableRowDef) {\n\tcommand := \"you can use this shell command to set the config: `curl -X POST -d '{\\\"metric-storage\\\":\\\"http://{PROMETHEUS_ADDRESS}\\\"}' http://{PD_ADDRESS}/pd/api/v1/config`, \\n\" +\n\t\t\"PROMETHEUS_ADDRESS is the prometheus address, It's used for query metric data; PD_ADDRESS is the HTTP API address of PD server, all PD servers need to set this config. \\n\" +\n\t\t\"Here is an example: `curl -X POST -d '{\\\"metric-storage\\\":\\\"http://127.0.0.1:9090\\\"}' http://127.0.0.1:2379/pd/api/v1/config`\"\n\t// Check for query metric.\n\tsql := \"select count(*) from metrics_schema.up;\"\n\t_, err := querySQL(db, sql)\n\tif err != nil {\n\t\terrRows = append(errRows, TableRowDef{\n\t\t\tValues: []string{\n\t\t\t\t\"check before report\",\n\t\t\t\t\"metrics_schema.up\",\n\t\t\t\terr.Error() + \", \\n\" +\n\t\t\t\t\t\"Currently, the PD config `pd-server.metric-storage` value should be prometheus address, please check whether the config value is correct, you can use below sql check the value: \\n\" +\n\t\t\t\t\t\"select * from information_schema.cluster_config where type='pd' and `key` ='pd-server.metric-storage'; , \\n\" + command,\n\t\t\t},\n\t\t})\n\t\treturn\n\t}\n\treturn nil\n}\n\ntype getTableFunc = func(string, string, *gorm.DB) (TableDef, error)\n\nfunc GetReportTables(startTime, endTime string, db *gorm.DB, sqliteDB *dbstore.DB, reportID string) []*TableDef {\n\tfuncs := []getTableFunc{\n\t\t// Header\n\t\tGetHeaderTimeTable,\n\t\tGetClusterHardwareInfoTable,\n\t\tGetClusterInfoTable,\n\n\t\t// Diagnose\n\t\tGetAllDiagnoseReport,\n\n\t\t// Load\n\t\tGetLoadTable,\n\t\tGetCPUUsageTable,\n\t\tGetProcessMemUsageTable,\n\t\tGetTiKVThreadCPUTable,\n\t\tGetGoroutinesCountTable,\n\n\t\t// Overview\n\t\tGetTotalTimeConsumeTable,\n\t\tGetTotalErrorTable,\n\n\t\t// TiDB\n\t\tGetTiDBTimeConsumeTable,\n\t\tGetTiDBConnectionCountTable,\n\t\tGetTiDBTxnTableData,\n\t\tGetTiDBStatisticsInfo,\n\t\tGetTiDBDDLOwner,\n\t\tGetTiDBTopNSlowQuery,\n\t\tGetTiDBTopNSlowQueryGroupByDigest,\n\t\tGetTiDBSlowQueryWithDiffPlan,\n\n\t\t// PD\n\t\tGetPDTimeConsumeTable,\n\t\tGetPDSchedulerInfo,\n\t\tGetPDClusterStatusTable,\n\t\tGetStoreStatusTable,\n\t\tGetPDEtcdStatusTable,\n\n\t\t// TiKV\n\t\tGetTiKVTotalTimeConsumeTable,\n\t\tGetTiKVRocksDBTimeConsumeTable,\n\t\tGetTiKVErrorTable,\n\t\tGetTiKVStoreInfo,\n\t\tGetTiKVRegionSizeInfo,\n\t\tGetTiKVCopInfo,\n\t\tGetTiKVSchedulerInfo,\n\t\tGetTiKVRaftInfo,\n\t\tGetTiKVSnapshotInfo,\n\t\tGetTiKVGCInfo,\n\t\tGetTiKVTaskInfo,\n\t\tGetTiKVCacheHitTable,\n\n\t\t// Config\n\t\tGetPDConfigInfo,\n\t\tGetPDConfigChangeInfo,\n\t\tGetTiDBGCConfigInfo,\n\t\tGetTiDBGCConfigChangeInfo,\n\t\tGetTiKVRocksDBConfigInfo,\n\t\tGetTiKVRocksDBConfigChangeInfo,\n\t\tGetTiKVRaftStoreConfigInfo,\n\t\tGetTiKVRaftStoreConfigChangeInfo,\n\t\tGetTiDBCurrentConfig,\n\t\tGetPDCurrentConfig,\n\t\tGetTiKVCurrentConfig,\n\t}\n\n\tvar progress int32\n\ttotalTableCount := int32(len(funcs))\n\ttables, errRows := getTablesParallel(startTime, endTime, db, funcs, sqliteDB, reportID, &progress, &totalTableCount)\n\ttables = append(tables, GenerateReportError(errRows))\n\treturn tables\n}\n\nfunc getTablesParallel(startTime, endTime string, db *gorm.DB, funcs []getTableFunc, sqliteDB *dbstore.DB, reportID string, progress, totalTableCount *int32) ([]*TableDef, []TableRowDef) {\n\t// get the local CPU count for concurrence\n\tconc := min(runtime.NumCPU(), 20)\n\tif conc > len(funcs) {\n\t\tconc = len(funcs)\n\t}\n\ttaskChan := func2task(funcs)\n\tresChan := make(chan *tblAndErr, len(funcs))\n\tvar wg sync.WaitGroup\n\n\t// get table concurrently\n\tfor i := 0; i < conc; i++ {\n\t\twg.Add(1)\n\t\tgo doGetTable(taskChan, resChan, &wg, startTime, endTime, db, sqliteDB, reportID, progress, totalTableCount)\n\t}\n\twg.Wait()\n\t// all task done, close the resChan\n\tclose(resChan)\n\n\ttblAndErrSlice := make([]tblAndErr, 0, cap(resChan))\n\tfor tblAndErr := range resChan {\n\t\ttblAndErrSlice = append(tblAndErrSlice, *tblAndErr)\n\t}\n\tsort.Slice(tblAndErrSlice, func(i, j int) bool {\n\t\treturn tblAndErrSlice[i].taskID < tblAndErrSlice[j].taskID\n\t})\n\n\ttables := make([]*TableDef, 0, len(tblAndErrSlice)+1)\n\terrRows := make([]TableRowDef, 0, len(tblAndErrSlice))\n\tfor _, v := range tblAndErrSlice {\n\t\tif v.tbl != nil {\n\t\t\ttables = append(tables, v.tbl)\n\t\t}\n\t\tif v.err != nil {\n\t\t\terrRows = append(errRows, *v.err)\n\t\t}\n\t}\n\treturn tables, errRows\n}\n\ntype tblAndErr struct {\n\ttbl    *TableDef\n\terr    *TableRowDef\n\ttaskID int\n}\n\n// 1.doGetTable gets the task from taskChan,and close the taskChan if taskChan is empty.\n// 2.doGetTable puts the tblAndErr result to resChan.\n// 3.if taskChan is empty, put a true in doneChan.\nfunc doGetTable(taskChan chan *task, resChan chan *tblAndErr, wg *sync.WaitGroup, startTime, endTime string, db *gorm.DB, sqliteDB *dbstore.DB, reportID string, progress, totalTableCount *int32) {\n\tdefer wg.Done()\n\tfor task := range taskChan {\n\t\tf := task.t\n\t\tvar tbl TableDef\n\t\tvar err error\n\t\tfunc() {\n\t\t\tdefer func() {\n\t\t\t\tif r := recover(); r != nil {\n\t\t\t\t\ttbl.Title = fmt.Sprintf(\"panic_in_table_%v\", task.taskID)\n\t\t\t\t\terr = fmt.Errorf(\"panic: %v\", r)\n\t\t\t\t}\n\t\t\t}()\n\t\t\ttbl, err = f(startTime, endTime, db)\n\t\t}()\n\t\tnewProgress := atomic.AddInt32(progress, 1)\n\t\ttblAndErr := tblAndErr{}\n\t\tif err != nil {\n\t\t\tcategory := strings.Join(tbl.Category, \",\")\n\t\t\ttblAndErr.err = &TableRowDef{Values: []string{category, tbl.Title, err.Error()}}\n\t\t}\n\t\tif tbl.Rows != nil {\n\t\t\ttblAndErr.tbl = &tbl\n\t\t}\n\t\ttblAndErr.taskID = task.taskID\n\t\tresChan <- &tblAndErr\n\t\tif sqliteDB != nil {\n\t\t\t_ = UpdateReportProgress(sqliteDB, reportID, int((newProgress*100)/atomic.LoadInt32(totalTableCount)))\n\t\t}\n\t}\n}\n\ntype task struct {\n\tt      getTableFunc\n\ttaskID int // taskID for arrange the tables in order\n}\n\n// change the get-Table-func to task.\nfunc func2task(funcs []getTableFunc) chan *task {\n\ttaskChan := make(chan *task, len(funcs))\n\tfor i := range funcs {\n\t\ttaskChan <- &task{funcs[i], i}\n\t}\n\tclose(taskChan)\n\treturn taskChan\n}\n\nfunc GenerateReportError(errRows []TableRowDef) *TableDef {\n\treturn &TableDef{\n\t\tCategory: []string{CategoryError},\n\t\tTitle:    \"generate_report_error\",\n\t\tComment:  \"\",\n\t\tColumn:   []string{\"CATEGORY\", \"TABLE\", \"ERROR\"},\n\t\tRows:     errRows,\n\t}\n}\n\nfunc GetHeaderTimeTable(startTime, endTime string, _ *gorm.DB) (TableDef, error) {\n\treturn TableDef{\n\t\tCategory: []string{CategoryHeader},\n\t\tTitle:    \"report_time_range\",\n\t\tComment:  \"\",\n\t\tColumn:   []string{\"START_TIME\", \"END_TIME\"},\n\t\tRows: []TableRowDef{\n\t\t\t{Values: []string{startTime, endTime}},\n\t\t},\n\t}, nil\n}\n\nfunc GetAllDiagnoseReport(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\treturn GetDiagnoseReport(startTime, endTime, db, nil)\n}\n\nfunc GetDiagnoseReport(startTime, endTime string, db *gorm.DB, rules []string) (TableDef, error) {\n\ttable := TableDef{\n\t\tCategory: []string{CategoryDiagnose},\n\t\tTitle:    \"diagnose\",\n\t\tComment:  \"\",\n\t\tColumn:   []string{\"RULE\", \"ITEM\", \"TYPE\", \"INSTANCE\", \"STATUS_ADDRESS\", \"VALUE\", \"REFERENCE\", \"SEVERITY\", \"DETAILS\"},\n\t}\n\tsql := fmt.Sprintf(\"select /*+ time_range('%s','%s') */ %s from information_schema.INSPECTION_RESULT\", startTime, endTime, strings.Join(table.Column, \",\"))\n\tif len(rules) > 0 {\n\t\tsql = fmt.Sprintf(\"%s where RULE in ('%s')\", sql, strings.Join(rules, \"','\"))\n\t}\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\tnewRows := make([]TableRowDef, 0, len(rows))\n\trowIdxMap := make(map[string]int)\n\tfor _, row := range rows {\n\t\tif len(row.Values) < len(table.Column) {\n\t\t\tcontinue\n\t\t}\n\t\t// rule + item\n\t\tname := row.Values[0] + row.Values[1]\n\t\tidx, ok := rowIdxMap[name]\n\t\tif ok && idx < len(newRows) {\n\t\t\tnewRows[idx].SubValues = append(newRows[idx].SubValues, row.Values)\n\t\t\tcontinue\n\t\t}\n\t\tnewRows = append(newRows, row)\n\t\trowIdxMap[name] = len(newRows) - 1\n\t}\n\ttable.Rows = newRows\n\treturn table, nil\n}\n\nfunc GetTotalTimeConsumeTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []totalTimeByLabelsTableDef{\n\t\t{name: \"tidb_query\", tbl: \"tidb_query\", labels: []string{\"sql_type\"}},\n\t\t{name: \"tidb_get_token(us)\", tbl: \"tidb_get_token\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_parse\", tbl: \"tidb_parse\", labels: []string{\"sql_type\"}},\n\t\t{name: \"tidb_compile\", tbl: \"tidb_compile\", labels: []string{\"sql_type\"}},\n\t\t{name: \"tidb_execute\", tbl: \"tidb_execute\", labels: []string{\"sql_type\"}},\n\t\t{name: \"tidb_distsql_execution\", tbl: \"tidb_distsql_execution\", labels: []string{\"type\"}},\n\t\t{name: \"tidb_cop\", tbl: \"tidb_cop\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_transaction\", tbl: \"tidb_transaction\", labels: []string{\"sql_type\"}},\n\t\t{name: \"tidb_transaction_local_latch_wait\", tbl: \"tidb_transaction_local_latch_wait\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_txn_cmd\", tbl: \"tidb_txn_cmd\", labels: []string{\"type\"}},\n\t\t{name: \"tidb_kv_backoff\", tbl: \"tidb_kv_backoff\", labels: []string{\"type\"}},\n\t\t{name: \"tidb_kv_request\", tbl: \"tidb_kv_request\", labels: []string{\"type\"}},\n\t\t{name: \"tidb_slow_query\", tbl: \"tidb_slow_query\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_slow_query_cop_process\", tbl: \"tidb_slow_query_cop_process\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_slow_query_cop_wait\", tbl: \"tidb_slow_query_cop_wait\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_ddl_handle_job\", tbl: \"tidb_ddl\", labels: []string{\"type\"}},\n\t\t{name: \"tidb_ddl_worker\", tbl: \"tidb_ddl_worker\", labels: []string{\"action\"}},\n\t\t{name: \"tidb_ddl_update_self_version\", tbl: \"tidb_ddl_update_self_version\", labels: []string{\"result\"}},\n\t\t{name: \"tidb_owner_handle_syncer\", tbl: \"tidb_owner_handle_syncer\", labels: []string{\"type\"}},\n\t\t{name: \"tidb_ddl_batch_add_index\", tbl: \"tidb_ddl_batch_add_index\", labels: []string{\"type\"}},\n\t\t{name: \"tidb_ddl_deploy_syncer\", tbl: \"tidb_ddl_deploy_syncer\", labels: []string{\"type\"}},\n\t\t{name: \"tidb_load_schema\", tbl: \"tidb_load_schema\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_meta_operation\", tbl: \"tidb_meta_operation\", labels: []string{\"type\"}},\n\t\t{name: \"tidb_auto_id_request\", tbl: \"tidb_auto_id_request\", labels: []string{\"type\"}},\n\t\t{name: \"tidb_statistics_auto_analyze\", tbl: \"tidb_statistics_auto_analyze\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_gc\", tbl: \"tidb_gc\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_gc_push_task\", tbl: \"tidb_gc_push_task\", labels: []string{\"type\"}},\n\t\t{name: \"tidb_batch_client_unavailable\", tbl: \"tidb_batch_client_unavailable\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_batch_client_wait\", tbl: \"tidb_batch_client_wait\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_batch_client_wait_conn\", tbl: \"tidb_batch_client_wait_conn\", labels: []string{\"instance\"}},\n\t\t// PD\n\t\t{name: \"pd_tso_rpc\", tbl: \"pd_tso_rpc\", labels: []string{\"instance\"}},\n\t\t{name: \"pd_tso_wait\", tbl: \"pd_tso_wait\", labels: []string{\"instance\"}},\n\t\t{name: \"pd_client_cmd\", tbl: \"pd_client_cmd\", labels: []string{\"type\"}},\n\t\t{name: \"pd_client_request_rpc\", tbl: \"pd_request_rpc\", labels: []string{\"type\"}},\n\t\t{name: \"pd_grpc_completed_commands\", tbl: \"pd_grpc_completed_commands\", labels: []string{\"grpc_method\"}},\n\t\t{name: \"pd_operator_finish\", tbl: \"pd_operator_finish\", labels: []string{\"type\"}},\n\t\t{name: \"pd_operator_step_finish\", tbl: \"pd_operator_step_finish\", labels: []string{\"type\"}},\n\t\t{name: \"pd_handle_transactions\", tbl: \"pd_handle_transactions\", labels: []string{\"result\"}},\n\t\t{name: \"pd_region_heartbeat\", tbl: \"pd_region_heartbeat\", labels: []string{\"address\"}},\n\t\t{name: \"etcd_wal_fsync\", tbl: \"etcd_wal_fsync\", labels: []string{\"instance\"}},\n\t\t{name: \"pd_peer_round_trip\", tbl: \"pd_peer_round_trip\", labels: []string{\"To\"}},\n\t\t// TiKV\n\t\t{name: \"tikv_grpc_message\", tbl: \"tikv_grpc_message\", labels: []string{\"type\"}},\n\t\t{name: \"tikv_cop_request\", tbl: \"tikv_cop_request\", labels: []string{\"req\"}},\n\t\t{name: \"tikv_cop_handle\", tbl: \"tikv_cop_handle\", labels: []string{\"req\"}},\n\t\t{name: \"tikv_cop_wait\", tbl: \"tikv_cop_wait\", labels: []string{\"req\"}},\n\t\t{name: \"tikv_scheduler_command\", tbl: \"tikv_scheduler_command\", labels: []string{\"type\"}},\n\t\t{name: \"tikv_scheduler_latch_wait\", tbl: \"tikv_scheduler_latch_wait\", labels: []string{\"type\"}},\n\t\t{name: \"tikv_storage_async_request\", tbl: \"tikv_storage_async_request\", labels: []string{\"type\"}},\n\t\t{name: \"tikv_scheduler_processing_read\", tbl: \"tikv_scheduler_processing_read\", labels: []string{\"type\"}},\n\t\t{name: \"tikv_raft_propose_wait\", tbl: \"tikv_raftstore_propose_wait\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_raft_process\", tbl: \"tikv_raftstore_process\", labels: []string{\"type\"}},\n\t\t{name: \"tikv_raft_append_log\", tbl: \"tikv_raftstore_append_log\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_raft_commit_log\", tbl: \"tikv_raftstore_commit_log\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_raft_apply_wait\", tbl: \"tikv_raftstore_apply_wait\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_raft_apply_log\", tbl: \"tikv_raftstore_apply_log\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_raft_store_events\", tbl: \"tikv_raft_store_events\", labels: []string{\"type\"}},\n\t\t{name: \"tikv_handle_snapshot\", tbl: \"tikv_handle_snapshot\", labels: []string{\"type\"}},\n\t\t{name: \"tikv_send_snapshot\", tbl: \"tikv_send_snapshot\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_check_split\", tbl: \"tikv_check_split\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_ingest_sst\", tbl: \"tikv_ingest_sst\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_gc_tasks\", tbl: \"tikv_gc_tasks\", labels: []string{\"task\"}},\n\t\t{name: \"tikv_pd_request\", tbl: \"tikv_pd_request\", labels: []string{\"type\"}},\n\t\t{name: \"tikv_lock_manager_deadlock_detect\", tbl: \"tikv_lock_manager_deadlock_detect\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_lock_manager_waiter_lifetime\", tbl: \"tikv_lock_manager_waiter_lifetime\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_backup_range\", tbl: \"tikv_backup_range\", labels: []string{\"type\"}},\n\t\t{name: \"tikv_backup\", tbl: \"tikv_backup\", labels: []string{\"instance\"}},\n\t}\n\n\tdefs := make([]rowQuery, 0, len(defs1))\n\tfor i := range defs1 {\n\t\tdefs = append(defs, defs1[i])\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryOverview},\n\t\tTitle:          \"total_time_consume\",\n\t\tComment:        ``,\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{3, 4, 5},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TIME_RATIO\", \"TOTAL_TIME\", \"TOTAL_COUNT\", \"P999\", \"P99\", \"P90\", \"P80\"},\n\t}\n\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\targ := newQueryArg(startTime, endTime)\n\tspecialHandle := func(row []string) []string {\n\t\tif arg.totalTime == 0 && len(row[3]) > 0 {\n\t\t\ttotalTime, err := strconv.ParseFloat(row[3], 64)\n\t\t\tif err == nil {\n\t\t\t\targ.totalTime = totalTime\n\t\t\t}\n\t\t}\n\t\treturn row\n\t}\n\tappendRows := func(row TableRowDef) {\n\t\trow.Values = specialHandle(row.Values)\n\t\tfor i := range row.SubValues {\n\t\t\trow.SubValues[i] = specialHandle(row.SubValues[i])\n\t\t}\n\t\tresultRows = append(resultRows, row)\n\t}\n\n\terr := getTableRows(defs, arg, db, appendRows)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetTotalErrorTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []sumValueQuery{\n\t\t{tbl: \"tidb_binlog_error_total_count\", labels: []string{\"instance\"}},\n\t\t{tbl: \"tidb_handshake_error_total_count\", labels: []string{\"instance\"}},\n\t\t{tbl: \"tidb_transaction_retry_error_total_count\", labels: []string{\"sql_type\"}},\n\t\t{tbl: \"tidb_kv_region_error_total_count\", labels: []string{\"type\"}},\n\t\t{tbl: \"tidb_schema_lease_error_total_count\", labels: []string{\"instance\"}},\n\t\t{tbl: \"tikv_grpc_error_total_count\", labels: []string{\"type\"}},\n\t\t{tbl: \"tikv_critical_error_total_count\", labels: []string{\"type\"}},\n\t\t{tbl: \"tikv_scheduler_is_busy_total_count\", labels: []string{\"type\"}},\n\t\t{tbl: \"tikv_channel_full_total_count\", labels: []string{\"type\"}},\n\t\t{tbl: \"tikv_coprocessor_request_error_total_count\", labels: []string{\"reason\"}},\n\t\t{tbl: \"tikv_engine_write_stall\", labels: []string{\"instance\"}},\n\t\t{tbl: \"tikv_server_report_failures_total_count\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_storage_async_request_error\", tbl: \"tikv_storage_async_requests_total_count\", labels: []string{\"type\"}, condition: \"status not in ('all','success')\"},\n\t\t{tbl: \"tikv_lock_manager_detect_error_total_count\", labels: []string{\"type\"}},\n\t\t{tbl: \"tikv_backup_errors_total_count\", labels: []string{\"error\"}},\n\t\t{tbl: \"node_network_in_errors_total_count\", labels: []string{\"instance\"}},\n\t\t{tbl: \"node_network_out_errors_total_count\", labels: []string{\"instance\"}},\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryOverview},\n\t\tTitle:          \"total_error\",\n\t\tComment:        ``,\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TOTAL_COUNT\"},\n\t}\n\n\trows, err := getSumValueTableData(defs1, startTime, endTime, db)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetTiDBTimeConsumeTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []totalTimeByLabelsTableDef{\n\t\t{name: \"tidb_query\", tbl: \"tidb_query\", labels: []string{\"instance\", \"sql_type\"}},\n\t\t{name: \"tidb_get_token(us)\", tbl: \"tidb_get_token\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_parse\", tbl: \"tidb_parse\", labels: []string{\"instance\", \"sql_type\"}},\n\t\t{name: \"tidb_compile\", tbl: \"tidb_compile\", labels: []string{\"instance\", \"sql_type\"}},\n\t\t{name: \"tidb_execute\", tbl: \"tidb_execute\", labels: []string{\"instance\", \"sql_type\"}},\n\t\t{name: \"tidb_distsql_execution\", tbl: \"tidb_distsql_execution\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tidb_cop\", tbl: \"tidb_cop\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_transaction\", tbl: \"tidb_transaction\", labels: []string{\"instance\", \"sql_type\", \"type\"}},\n\t\t{name: \"tidb_transaction_local_latch_wait\", tbl: \"tidb_transaction_local_latch_wait\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_kv_backoff\", tbl: \"tidb_kv_backoff\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tidb_kv_request\", tbl: \"tidb_kv_request\", labels: []string{\"instance\", \"store\", \"type\"}},\n\t\t{name: \"tidb_slow_query\", tbl: \"tidb_slow_query\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_slow_query_cop_process\", tbl: \"tidb_slow_query_cop_process\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_slow_query_cop_wait\", tbl: \"tidb_slow_query_cop_wait\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_ddl_handle_job\", tbl: \"tidb_ddl\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tidb_ddl_worker\", tbl: \"tidb_ddl_worker\", labels: []string{\"instance\", \"type\", \"result\", \"action\"}},\n\t\t{name: \"tidb_ddl_update_self_version\", tbl: \"tidb_ddl_update_self_version\", labels: []string{\"instance\", \"result\"}},\n\t\t{name: \"tidb_owner_handle_syncer\", tbl: \"tidb_owner_handle_syncer\", labels: []string{\"instance\", \"type\", \"result\"}},\n\t\t{name: \"tidb_ddl_batch_add_index\", tbl: \"tidb_ddl_batch_add_index\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tidb_ddl_deploy_syncer\", tbl: \"tidb_ddl_deploy_syncer\", labels: []string{\"instance\", \"type\", \"result\"}},\n\t\t{name: \"tidb_load_schema\", tbl: \"tidb_load_schema\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_meta_operation\", tbl: \"tidb_meta_operation\", labels: []string{\"instance\", \"type\", \"result\"}},\n\t\t{name: \"tidb_auto_id_request\", tbl: \"tidb_auto_id_request\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tidb_statistics_auto_analyze\", tbl: \"tidb_statistics_auto_analyze\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_gc\", tbl: \"tidb_gc\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_gc_push_task\", tbl: \"tidb_gc_push_task\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tidb_batch_client_unavailable\", tbl: \"tidb_batch_client_unavailable\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_batch_client_wait\", tbl: \"tidb_batch_client_wait\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_batch_client_wait_conn\", tbl: \"tidb_batch_client_wait_conn\", labels: []string{\"instance\"}},\n\t\t{name: \"pd_tso_rpc\", tbl: \"pd_tso_rpc\", labels: []string{\"instance\"}},\n\t\t{name: \"pd_tso_wait\", tbl: \"pd_tso_wait\", labels: []string{\"instance\"}},\n\t}\n\n\tdefs := make([]rowQuery, 0, len(defs1))\n\tfor i := range defs1 {\n\t\tdefs = append(defs, defs1[i])\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiDB},\n\t\tTitle:          \"tidb_time_consume\",\n\t\tComment:        ``,\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{3, 4, 5},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TIME_RATIO\", \"TOTAL_TIME\", \"TOTAL_COUNT\", \"P999\", \"P99\", \"P90\", \"P80\"},\n\t}\n\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\targ := newQueryArg(startTime, endTime)\n\tspecialHandle := func(row []string) []string {\n\t\tif arg.totalTime == 0 && len(row[3]) > 0 {\n\t\t\ttotalTime, err := strconv.ParseFloat(row[3], 64)\n\t\t\tif err == nil {\n\t\t\t\targ.totalTime = totalTime\n\t\t\t}\n\t\t}\n\t\treturn row\n\t}\n\tappendRows := func(row TableRowDef) {\n\t\trow.Values = specialHandle(row.Values)\n\t\tfor i := range row.SubValues {\n\t\t\trow.SubValues[i] = specialHandle(row.SubValues[i])\n\t\t}\n\t\tresultRows = append(resultRows, row)\n\t}\n\n\terr := getTableRows(defs, arg, db, appendRows)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetTiDBTxnTableData(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []totalValueAndTotalCountTableDef{\n\t\t{name: \"tidb_transaction_retry_num\", tbl: \"tidb_transaction_retry_num\", sumTbl: \"tidb_transaction_retry_total_num\", countTbl: \"tidb_transaction_retry_num_total_count\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_transaction_statement_num\", tbl: \"tidb_transaction_statement_num\", sumTbl: \"tidb_transaction_statement_total_num\", countTbl: \"tidb_transaction_statement_num_total_count\", labels: []string{\"sql_type\"}},\n\t\t{name: \"tidb_txn_region_num\", tbl: \"tidb_txn_region_num\", sumTbl: \"tidb_txn_region_total_num\", countTbl: \"tidb_txn_region_num_total_count\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_txn_kv_write_num\", tbl: \"tidb_kv_write_num\", sumTbl: \"tidb_kv_write_total_num\", countTbl: \"tidb_kv_write_num_total_count\", labels: []string{\"instance\"}},\n\t\t{name: \"tidb_txn_kv_write_size\", tbl: \"tidb_kv_write_size\", sumTbl: \"tidb_kv_write_total_size\", countTbl: \"tidb_kv_write_size_total_count\", labels: []string{\"instance\"}},\n\t}\n\tdefs2 := []sumValueQuery{\n\t\t{name: \"tidb_load_safepoint_total_num\", tbl: \"tidb_load_safepoint_total_num\", labels: []string{\"type\"}},\n\t\t{name: \"tidb_lock_resolver_total_num\", tbl: \"tidb_lock_resolver_total_num\", labels: []string{\"type\"}},\n\t}\n\n\tdefs := make([]rowQuery, 0, len(defs1)+len(defs2))\n\tfor i := range defs1 {\n\t\tdefs = append(defs, defs1[i])\n\t}\n\tfor i := range defs2 {\n\t\tdefs = append(defs, defs2[i])\n\t}\n\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\n\tquantiles := []float64{0.999, 0.99, 0.90, 0.80}\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiDB},\n\t\tTitle:          \"transaction\",\n\t\tComment:        ``,\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2, 3, 4, 5},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TOTAL_VALUE\", \"TOTAL_COUNT\", \"P999\", \"P99\", \"P90\", \"P80\"},\n\t}\n\n\tspecialHandle := func(row []string) []string {\n\t\tfor len(row) < 8 {\n\t\t\trow = append(row, \"\")\n\t\t}\n\n\t\tfor i := 2; i < len(row); i++ {\n\t\t\tif len(row[i]) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif row[0] == \"tidb_txn_kv_write_size\" && i != 3 {\n\t\t\t\trow[i] = convertFloatToSize(row[i])\n\t\t\t} else {\n\t\t\t\trow[i] = convertFloatToInt(row[i])\n\t\t\t}\n\t\t}\n\t\treturn row\n\t}\n\n\tappendRows := func(row TableRowDef) {\n\t\trow.Values = specialHandle(row.Values)\n\t\tfor i := range row.SubValues {\n\t\t\trow.SubValues[i] = specialHandle(row.SubValues[i])\n\t\t}\n\t\tresultRows = append(resultRows, row)\n\t}\n\n\targ := &queryArg{\n\t\tstartTime: startTime,\n\t\tendTime:   endTime,\n\t\tquantiles: quantiles,\n\t}\n\n\terr := getTableRows(defs, arg, db, appendRows)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetTiDBConnectionCountTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tsql := fmt.Sprintf(\"select instance, avg(value), max(value), min(value) from metrics_schema.tidb_connection_count where time >= '%s' and time < '%s' group by instance order by avg(value) desc\",\n\t\tstartTime, endTime)\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiDB},\n\t\tTitle:          \"tidb_connection_count\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0},\n\t\tcompareColumns: []int{1, 2, 3},\n\t\tColumn:         []string{\"INSTANCE\", \"AVG\", \"MAX\", \"MIN\"},\n\t}\n\trows, err := getSQLRoundRows(db, sql, []int{1, 2, 3}, \"\")\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetTiDBStatisticsInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []sumValueQuery{\n\t\t{name: \"pseudo_estimation_total_count\", tbl: \"tidb_statistics_pseudo_estimation_total_count\", labels: []string{\"instance\"}},\n\t\t{name: \"dump_feedback_total_count\", tbl: \"tidb_statistics_dump_feedback_total_count\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"store_query_feedback_total_count\", tbl: \"tidb_statistics_store_query_feedback_total_count\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"update_stats_total_count\", tbl: \"tidb_statistics_update_stats_total_count\", labels: []string{\"instance\", \"type\"}},\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiDB},\n\t\tTitle:          \"statistics_info\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TOTAL_COUNT\"},\n\t}\n\n\trows, err := getSumValueTableData(defs1, startTime, endTime, db)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, err\n}\n\nfunc GetTiDBDDLOwner(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tsql := fmt.Sprintf(\"select min(time),instance from metrics_schema.tidb_ddl_worker_total_count where time>='%s' and time<'%s' and value>0 and type='run_job' group by instance order by min(time);\",\n\t\tstartTime, endTime)\n\n\ttable := TableDef{\n\t\tCategory:    []string{CategoryTiDB},\n\t\tTitle:       \"ddl_owner\",\n\t\tComment:     \"\",\n\t\tjoinColumns: []int{1},\n\t\tColumn:      []string{\"MIN_TIME\", \"DDL OWNER\"},\n\t}\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetPDConfigInfo(startTime, _ string, db *gorm.DB) (TableDef, error) {\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryConfig},\n\t\tTitle:          \"scheduler_initial_config\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 2},\n\t\tcompareColumns: []int{1},\n\t\tColumn:         []string{\"CONFIG_ITEM\", \"VALUE\", \"CURRENT_VALUE\", \"DIFF_WITH_CURRENT\"},\n\t}\n\tsql := fmt.Sprintf(`select t1.type,t1.value,t2.value,t1.value!=t2.value from\n\t\t(select distinct type,value from metrics_schema.pd_scheduler_config where time = '%[1]s' and value>0) as t1 join\n\t\t(select distinct type,value from metrics_schema.pd_scheduler_config where time = now() and value>0) as t2\n\t\twhere t1.type=t2.type order by abs(t2.value-t1.value) desc`, startTime)\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\tif len(rows) > 0 {\n\t\ttable.Rows = rows\n\t}\n\treturn table, nil\n}\n\nfunc GetPDConfigChangeInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tsql := fmt.Sprintf(`select t1.* from\n\t\t(select min(time) as time,type,value from metrics_schema.pd_scheduler_config where time>='%[1]s' and time<'%[2]s' group by type,value order by type) as t1 join\n\t\t(select type, count(distinct value) as count from metrics_schema.pd_scheduler_config where time>='%[1]s' and time<'%[2]s' group by type order by count desc) as t2\n\t\twhere t1.type=t2.type and t2.count > 1 order by t2.count desc, t1.time;`, startTime, endTime)\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryConfig},\n\t\tTitle:          \"scheduler_change_config\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{1},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"APPROXIMATE_CHANGE_TIME\", \"CONFIG_ITEM\", \"VALUE\"},\n\t}\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetTiDBGCConfigInfo(startTime, _ string, db *gorm.DB) (TableDef, error) {\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryConfig},\n\t\tTitle:          \"tidb_gc_initial_config\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 2},\n\t\tcompareColumns: []int{1},\n\t\tColumn:         []string{\"CONFIG_ITEM\", \"VALUE\", \"CURRENT_VALUE\", \"DIFF_WITH_CURRENT\"},\n\t}\n\tsql := fmt.Sprintf(`select t1.type,t1.value,t2.value,t1.value!=t2.value from\n\t\t(select distinct type,value from metrics_schema.tidb_gc_config where time = '%[1]s' and value>0) as t1 join\n\t\t(select distinct type,value from metrics_schema.tidb_gc_config where time = now() and value>0) as t2\n\t\twhere t1.type=t2.type order by abs(t2.value-t1.value) desc`, startTime)\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetTiDBGCConfigChangeInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tsql := fmt.Sprintf(`select t1.* from\n\t\t(select min(time) as time,type,value from metrics_schema.tidb_gc_config where time>='%[1]s' and time<'%[2]s' and value > 0 group by type,value order by type) as t1 join\n\t\t(select type, count(distinct value) as count from metrics_schema.tidb_gc_config where time>='%[1]s' and time<'%[2]s' and value > 0 group by type order by count desc) as t2\n\t\twhere t1.type=t2.type and t2.count>1 order by t2.count desc, t1.time;`, startTime, endTime)\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryConfig},\n\t\tTitle:          \"tidb_gc_change_config\",\n\t\tComment:        ``,\n\t\tjoinColumns:    []int{1},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"APPROXIMATE_CHANGE_TIME\", \"CONFIG_ITEM\", \"VALUE\"},\n\t}\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetTiKVRocksDBConfigInfo(startTime, _ string, db *gorm.DB) (TableDef, error) {\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryConfig},\n\t\tTitle:          \"tikv_rocksdb_initial_config\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1, 3},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"CONFIG_ITEM\", \"INSTANCE\", \"VALUE\", \"CURRENT_VALUE\", \"DIFF_WITH_CURRENT\", \"DISTINCT_VALUES_IN_INSTANCE\"},\n\t}\n\tsql := fmt.Sprintf(`select t1.name,'', t1.value,t2.value,t1.value!=t2.value, t1.count from\n\t\t(select concat(name,' , ',cf) as name, min(value) as value, count(distinct value) as count from metrics_schema.tikv_config_rocksdb where time = '%[1]s' group by cf, name) as t1 join\n\t\t(select concat(name,' , ',cf) as name, min(value) as value from metrics_schema.tikv_config_rocksdb where time = now()   group by cf, name) as t2\n\t\twhere t1.name=t2.name order by abs(t2.value-t1.value) desc,t1.count desc, t1.name`, startTime)\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\t// var subRows []TableRowDef\n\tsubRowsMap := make(map[string][][]string)\n\tfor i, row := range rows {\n\t\tif len(row.Values) < 6 {\n\t\t\tcontinue\n\t\t}\n\t\tif row.Values[5] == \"1\" {\n\t\t\tcontinue\n\t\t}\n\t\tif len(subRowsMap) == 0 {\n\t\t\tsql = fmt.Sprintf(`select t1.name,t1.instance,t1.value,t2.value,t1.value!=t2.value, '' from\n\t\t\t(select concat(name,' , ',cf) as name,instance, value from metrics_schema.tikv_config_rocksdb where time = '%[1]s' group by cf, name, instance, value) as t1 join\n\t\t\t(select concat(name,' , ',cf) as name,instance, value from metrics_schema.tikv_config_rocksdb where time = now()   group by cf, name, instance, value) as t2\n\t\t\twhere t1.name=t2.name and t1.instance = t2.instance order by abs(t2.value-t1.value) desc, t1.name`, startTime)\n\t\t\tsubRows, err := getSQLRows(db, sql)\n\t\t\tif err != nil {\n\t\t\t\treturn table, err\n\t\t\t}\n\t\t\tfor _, subRow := range subRows {\n\t\t\t\tif len(subRow.Values) < 6 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsubRowsMap[subRow.Values[0]] = append(subRowsMap[subRow.Values[0]], subRow.Values)\n\t\t\t}\n\t\t}\n\t\trows[i].SubValues = subRowsMap[row.Values[0]]\n\t\tif len(rows[i].SubValues) > 0 && row.Values[4] == \"0\" {\n\t\t\tfor _, subRow := range rows[i].SubValues {\n\t\t\t\tif row.Values[4] != \"0\" {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif len(subRow) != 6 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\trows[i].Values[4] = subRow[4]\n\t\t\t}\n\t\t}\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetTiKVRocksDBConfigChangeInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tsql := fmt.Sprintf(`select t1.* from\n\t\t(select min(time) as time,concat(name,' , ',cf) as name,instance,value from metrics_schema.tikv_config_rocksdb where time>='%[1]s' and time<'%[2]s'         group by name,cf,instance,value order by name) as t1 join\n\t\t(select concat(name,' , ',cf) as name,instance, count(distinct value) as count from metrics_schema.tikv_config_rocksdb where time>='%[1]s' and time<'%[2]s' group by name,cf,instance order by count desc) as t2\n\t\twhere t1.name=t2.name and t1.instance = t2.instance and t2.count>1 order by t1.name,instance, t2.count desc, t1.time;`, startTime, endTime)\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryConfig},\n\t\tTitle:          \"tikv_rocksdb_change_config\",\n\t\tComment:        ``,\n\t\tjoinColumns:    []int{1, 2},\n\t\tcompareColumns: []int{3},\n\t\tColumn:         []string{\"APPROXIMATE_CHANGE_TIME\", \"CONFIG_ITEM\", \"INSTANCE\", \"VALUE\"},\n\t}\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetTiKVRaftStoreConfigInfo(startTime, _ string, db *gorm.DB) (TableDef, error) {\n\ttable := TableDef{\n\t\tCategory: []string{CategoryConfig},\n\t\tTitle:    \"tikv_raftstore_initial_config\",\n\t\tComment:  \"\",\n\n\t\tjoinColumns:    []int{0, 1, 3},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"CONFIG_ITEM\", \"INSTANCE\", \"VALUE\", \"CURRENT_VALUE\", \"DIFF_WITH_CURRENT\", \"DISTINCT_VALUES_IN_INSTANCE\"},\n\t}\n\tsql := fmt.Sprintf(`select t1.name,'', t1.value,t2.value,t1.value!=t2.value, t1.count from\n\t\t(select name, min(value) as value, count(distinct value) as count from metrics_schema.tikv_config_raftstore where time = '%[1]s' group by name) as t1 join\n\t\t(select name, min(value) as value                                 from metrics_schema.tikv_config_raftstore where time = now()   group by name) as t2\n\t\twhere t1.name=t2.name order by abs(t2.value-t1.value) desc,t1.count desc, t1.name`, startTime)\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\t// var subRows []TableRowDef\n\tsubRowsMap := make(map[string][][]string)\n\tfor i, row := range rows {\n\t\tif len(row.Values) < 6 {\n\t\t\tcontinue\n\t\t}\n\t\tif row.Values[5] == \"1\" {\n\t\t\tcontinue\n\t\t}\n\t\tif len(subRowsMap) == 0 {\n\t\t\tsql = fmt.Sprintf(`select t1.name,t1.instance,t1.value,t2.value,t1.value!=t2.value, '' from\n\t\t\t(select name,instance, value from metrics_schema.tikv_config_raftstore where time = '%[1]s' group by name, instance, value) as t1 join\n\t\t\t(select name,instance, value from metrics_schema.tikv_config_raftstore where time = now()   group by name, instance, value) as t2\n\t\t\twhere t1.name=t2.name and t1.instance = t2.instance order by abs(t2.value-t1.value) desc, t1.name`, startTime)\n\t\t\tsubRows, err := getSQLRows(db, sql)\n\t\t\tif err != nil {\n\t\t\t\treturn table, err\n\t\t\t}\n\t\t\tfor _, subRow := range subRows {\n\t\t\t\tif len(subRow.Values) < 6 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tsubRowsMap[subRow.Values[0]] = append(subRowsMap[subRow.Values[0]], subRow.Values)\n\t\t\t}\n\t\t}\n\t\trows[i].SubValues = subRowsMap[row.Values[0]]\n\t\tif len(rows[i].SubValues) > 0 && row.Values[4] == \"0\" {\n\t\t\tfor _, subRow := range rows[i].SubValues {\n\t\t\t\tif row.Values[4] != \"0\" {\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tif len(subRow) != 6 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\trows[i].Values[4] = subRow[4]\n\t\t\t}\n\t\t}\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetTiKVRaftStoreConfigChangeInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tsql := fmt.Sprintf(`select t1.* from\n\t\t(select min(time) as time,name,instance,value from metrics_schema.tikv_config_raftstore where time>='%[1]s' and time<'%[2]s'         group by name,instance,value order by name) as t1 join\n\t\t(select name,instance, count(distinct value) as count from metrics_schema.tikv_config_raftstore where time>='%[1]s' and time<'%[2]s' group by name,instance order by count desc) as t2\n\t\twhere t1.name=t2.name and t1.instance = t2.instance and t2.count>1 order by t1.name,instance,t2.count desc, t1.time;`, startTime, endTime)\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryConfig},\n\t\tTitle:          \"tikv_raftstore_change_config\",\n\t\tComment:        ``,\n\t\tjoinColumns:    []int{1, 2},\n\t\tcompareColumns: []int{3},\n\t\tColumn:         []string{\"APPROXIMATE_CHANGE_TIME\", \"CONFIG_ITEM\", \"INSTANCE\", \"VALUE\"},\n\t}\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetPDTimeConsumeTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []totalTimeByLabelsTableDef{\n\t\t{name: \"pd_client_cmd\", tbl: \"pd_client_cmd\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"pd_client_request_rpc\", tbl: \"pd_request_rpc\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"pd_grpc_completed_commands\", tbl: \"pd_grpc_completed_commands\", labels: []string{\"instance\", \"grpc_method\"}},\n\t\t{name: \"pd_operator_finish\", tbl: \"pd_operator_finish\", labels: []string{\"type\"}},\n\t\t{name: \"pd_operator_step_finish\", tbl: \"pd_operator_step_finish\", labels: []string{\"type\"}},\n\t\t{name: \"pd_handle_transactions\", tbl: \"pd_handle_transactions\", labels: []string{\"instance\", \"result\"}},\n\t\t{name: \"pd_region_heartbeat\", tbl: \"pd_region_heartbeat\", labels: []string{\"address\", \"store\"}},\n\t\t{name: \"etcd_wal_fsync\", tbl: \"etcd_wal_fsync\", labels: []string{\"instance\"}},\n\t\t{name: \"pd_peer_round_trip\", tbl: \"pd_peer_round_trip\", labels: []string{\"instance\", \"To\"}},\n\t}\n\n\tdefs := make([]rowQuery, 0, len(defs1))\n\tfor i := range defs1 {\n\t\tdefs = append(defs, defs1[i])\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryPD},\n\t\tTitle:          \"pd_time_consume\",\n\t\tComment:        ``,\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{3, 4, 5},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TIME_RATIO\", \"TOTAL_TIME\", \"TOTAL_COUNT\", \"P999\", \"P99\", \"P90\", \"P80\"},\n\t}\n\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\targ := newQueryArg(startTime, endTime)\n\tappendRows := func(row TableRowDef) {\n\t\tresultRows = append(resultRows, row)\n\t\targ.totalTime = 0\n\t}\n\n\terr := getTableRows(defs, arg, db, appendRows)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetPDSchedulerInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []sumValueQuery{\n\t\t{name: \"blance-leader-in\", tbl: \"pd_scheduler_balance_leader\", condition: \"type='move-leader' and address like '%-in'\", labels: []string{\"address\"}},\n\t\t{name: \"blance-leader-out\", tbl: \"pd_scheduler_balance_leader\", condition: \"type='move-leader' and address like '%-out'\", labels: []string{\"address\"}},\n\t\t{name: \"blance-region-in\", tbl: \"pd_scheduler_balance_region\", condition: \"type='move-peer' and address like '%-in'\", labels: []string{\"address\"}},\n\t\t{name: \"blance-region-out\", tbl: \"pd_scheduler_balance_region\", condition: \"type='move-peer' and address like '%-out'\", labels: []string{\"address\"}},\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryPD},\n\t\tTitle:          \"balance_leader_region\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TOTAL_COUNT\"},\n\t}\n\n\trows, err := getSumValueTableData(defs1, startTime, endTime, db)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, err\n}\n\nfunc GetTiKVRegionSizeInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []totalValueAndTotalCountTableDef{\n\t\t{name: \"Approximate Region size\", tbl: \"tikv_approximate_region_size\", sumTbl: \"tikv_approximate_region_total_size\", countTbl: \"tikv_approximate_region_size_total_count\", labels: []string{\"instance\"}},\n\t}\n\tdefs := make([]rowQuery, 0, len(defs1))\n\tfor i := range defs1 {\n\t\tdefs = append(defs, defs1[i])\n\t}\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\n\tspecialHandle := func(row []string) []string {\n\t\tif len(row) == 8 {\n\t\t\t// total value and total count is not right.\n\t\t\ttmpRow := row[:2]\n\t\t\ttmpRow = append(tmpRow, row[4:]...)\n\t\t\trow = tmpRow\n\t\t}\n\t\tfor i := 2; i < len(row); i++ {\n\t\t\tif len(row[i]) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\trow[i] = convertFloatToSize(row[i])\n\t\t}\n\t\treturn row\n\t}\n\n\tappendRows := func(row TableRowDef) {\n\t\trow.Values = specialHandle(row.Values)\n\t\tfor i := range row.SubValues {\n\t\t\trow.SubValues[i] = specialHandle(row.SubValues[i])\n\t\t}\n\t\tresultRows = append(resultRows, row)\n\t}\n\n\tquantiles := []float64{0.99, 0.90, 0.80, 0.50}\n\targ := &queryArg{\n\t\tstartTime: startTime,\n\t\tendTime:   endTime,\n\t\tquantiles: quantiles,\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiKV},\n\t\tTitle:          \"approximate_region_size\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2, 3, 4, 5},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"P99\", \"P90\", \"P80\", \"P50\"},\n\t}\n\terr := getTableRows(defs, arg, db, appendRows)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetTiKVStoreInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []sumValueQuery{\n\t\t{name: \"store size\", tbl: \"tikv_engine_size\", labels: []string{\"instance\", \"type\"}},\n\t}\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiKV},\n\t\tTitle:          \"tikv_engine_size\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TOTAL_COUNT\"},\n\t}\n\trows, err := getSumValueTableData(defs1, startTime, endTime, db)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\tconvertFloatToSizeByRows(rows, 2)\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetTiKVTotalTimeConsumeTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []totalTimeByLabelsTableDef{\n\t\t{name: \"tikv_grpc_message\", tbl: \"tikv_grpc_message\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tikv_cop_request\", tbl: \"tikv_cop_request\", labels: []string{\"instance\", \"req\"}},\n\t\t{name: \"tikv_cop_handle\", tbl: \"tikv_cop_handle\", labels: []string{\"instance\", \"req\"}},\n\t\t{name: \"tikv_cop_wait\", tbl: \"tikv_cop_wait\", labels: []string{\"instance\", \"req\"}},\n\t\t{name: \"tikv_scheduler_command\", tbl: \"tikv_scheduler_command\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tikv_scheduler_latch_wait\", tbl: \"tikv_scheduler_latch_wait\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tikv_storage_async_request\", tbl: \"tikv_storage_async_request\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tikv_scheduler_processing_read\", tbl: \"tikv_scheduler_processing_read\", labels: []string{\"type\"}},\n\t\t{name: \"tikv_raft_propose_wait\", tbl: \"tikv_raftstore_propose_wait\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_raft_process\", tbl: \"tikv_raftstore_process\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tikv_raft_append_log\", tbl: \"tikv_raftstore_append_log\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_raft_commit_log\", tbl: \"tikv_raftstore_commit_log\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_raft_apply_wait\", tbl: \"tikv_raftstore_apply_wait\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_raft_apply_log\", tbl: \"tikv_raftstore_apply_log\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_raft_store_events\", tbl: \"tikv_raft_store_events\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tikv_handle_snapshot\", tbl: \"tikv_handle_snapshot\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tikv_send_snapshot\", tbl: \"tikv_send_snapshot\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_check_split\", tbl: \"tikv_check_split\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_ingest_sst\", tbl: \"tikv_ingest_sst\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_gc_tasks\", tbl: \"tikv_gc_tasks\", labels: []string{\"instance\", \"task\"}},\n\t\t{name: \"tikv_pd_request\", tbl: \"tikv_pd_request\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tikv_lock_manager_deadlock_detect\", tbl: \"tikv_lock_manager_deadlock_detect\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_lock_manager_waiter_lifetime\", tbl: \"tikv_lock_manager_waiter_lifetime\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_backup_range\", tbl: \"tikv_backup_range\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tikv_backup\", tbl: \"tikv_backup\", labels: []string{\"instance\"}},\n\t}\n\n\tdefs := make([]rowQuery, 0, len(defs1))\n\tfor i := range defs1 {\n\t\tdefs = append(defs, defs1[i])\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiKV},\n\t\tTitle:          \"tikv_time_consume\",\n\t\tComment:        ``,\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{3, 4, 5},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TIME_RATIO\", \"TOTAL_TIME\", \"TOTAL_COUNT\", \"P999\", \"P99\", \"P90\", \"P80\"},\n\t}\n\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\targ := newQueryArg(startTime, endTime)\n\tspecialHandle := func(row []string) []string {\n\t\tif arg.totalTime == 0 && len(row[3]) > 0 {\n\t\t\ttotalTime, err := strconv.ParseFloat(row[3], 64)\n\t\t\tif err == nil {\n\t\t\t\targ.totalTime = totalTime\n\t\t\t}\n\t\t}\n\t\treturn row\n\t}\n\tappendRows := func(row TableRowDef) {\n\t\trow.Values = specialHandle(row.Values)\n\t\tfor i := range row.SubValues {\n\t\t\trow.SubValues[i] = specialHandle(row.SubValues[i])\n\t\t}\n\t\tresultRows = append(resultRows, row)\n\t}\n\n\terr := getTableRows(defs, arg, db, appendRows)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetTiKVSchedulerInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []totalValueAndTotalCountTableDef{\n\t\t{name: \"tikv_scheduler_keys_read\", tbl: \"tikv_scheduler_keys_read\", sumTbl: \"tikv_scheduler_keys_total_read\", countTbl: \"tikv_scheduler_keys_read_total_count\", labels: []string{\"instance\", \"type\"}},\n\t\t{name: \"tikv_scheduler_keys_written\", tbl: \"tikv_scheduler_keys_written\", sumTbl: \"tikv_scheduler_keys_total_written\", countTbl: \"tikv_scheduler_keys_written_total_count\", labels: []string{\"instance\", \"type\"}},\n\t}\n\tdefs2 := []sumValueQuery{\n\t\t{tbl: \"tikv_scheduler_scan_details_total_num\", labels: []string{\"instance\", \"req\", \"tag\"}},\n\t\t{tbl: \"tikv_scheduler_stage_total_num\", labels: []string{\"instance\", \"type\", \"stage\"}},\n\t}\n\n\tdefs := make([]rowQuery, 0, len(defs1))\n\tfor i := range defs1 {\n\t\tdefs = append(defs, defs1[i])\n\t}\n\tfor i := range defs2 {\n\t\tdefs = append(defs, defs2[i])\n\t}\n\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\tspecialHandle := func(row []string) []string {\n\t\tfor len(row) < 8 {\n\t\t\trow = append(row, \"\")\n\t\t}\n\t\treturn row\n\t}\n\n\tappendRows := func(row TableRowDef) {\n\t\trow.Values = specialHandle(row.Values)\n\t\tfor i := range row.SubValues {\n\t\t\trow.SubValues[i] = specialHandle(row.SubValues[i])\n\t\t}\n\t\tresultRows = append(resultRows, row)\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiKV},\n\t\tTitle:          \"scheduler_info\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2, 3, 4, 5},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TOTAL_VALUE\", \"TOTAL_COUNT\", \"P999\", \"P99\", \"P90\", \"P80\"},\n\t}\n\targ := newQueryArg(startTime, endTime)\n\terr := getTableRows(defs, arg, db, appendRows)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetTiKVGCInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []sumValueQuery{\n\t\t{tbl: \"tikv_gc_keys_total_num\", labels: []string{\"instance\", \"cf\", \"tag\"}},\n\t\t{name: \"tidb_gc_worker_action_total_num\", tbl: \"tidb_gc_worker_action_opm\", labels: []string{\"instance\", \"type\"}},\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiKV},\n\t\tTitle:          \"gc_info\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TOTAL_VALUE\"},\n\t}\n\trows, err := getSumValueTableData(defs1, startTime, endTime, db)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetTiKVTaskInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []sumValueQuery{\n\t\t{tbl: \"tikv_worker_handled_tasks_total_num\", labels: []string{\"instance\", \"name\"}},\n\t\t{tbl: \"tikv_worker_pending_tasks_total_num\", labels: []string{\"instance\", \"name\"}},\n\t\t{tbl: \"tikv_futurepool_handled_tasks_total_num\", labels: []string{\"instance\", \"name\"}},\n\t\t{tbl: \"tikv_futurepool_pending_tasks_total_num\", labels: []string{\"instance\", \"name\"}},\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiKV},\n\t\tTitle:          \"task_info\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TOTAL_VALUE\"},\n\t}\n\trows, err := getSumValueTableData(defs1, startTime, endTime, db)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc getSumValueTableData(defs1 []sumValueQuery, startTime, endTime string, db *gorm.DB) ([]TableRowDef, error) {\n\tdefs := make([]rowQuery, 0, len(defs1))\n\tfor i := range defs1 {\n\t\tdefs = append(defs, defs1[i])\n\t}\n\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\tspecialHandle := func(row []string) []string {\n\t\tfor len(row) < 3 {\n\t\t\treturn row\n\t\t}\n\t\trow[2] = convertFloatToInt(row[2])\n\t\treturn row\n\t}\n\n\tappendRows := func(row TableRowDef) {\n\t\trow.Values = specialHandle(row.Values)\n\t\tfor i := range row.SubValues {\n\t\t\trow.SubValues[i] = specialHandle(row.SubValues[i])\n\t\t}\n\t\tresultRows = append(resultRows, row)\n\t}\n\targ := newQueryArg(startTime, endTime)\n\terr := getTableRows(defs, arg, db, appendRows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn resultRows, nil\n}\n\nfunc GetTiKVSnapshotInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []totalValueAndTotalCountTableDef{\n\t\t{name: \"tikv_snapshot_kv_count\", tbl: \"tikv_snapshot_kv_count\", sumTbl: \"tikv_snapshot_kv_total_count\", countTbl: \"tikv_snapshot_kv_count_total_count\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_snapshot_size\", tbl: \"tikv_snapshot_size\", sumTbl: \"tikv_snapshot_total_size\", countTbl: \"tikv_snapshot_size_total_count\", labels: []string{\"instance\"}},\n\t}\n\tdefs2 := []sumValueQuery{\n\t\t{tbl: \"tikv_snapshot_state_total_count\", labels: []string{\"instance\", \"type\"}},\n\t}\n\tdefs := make([]rowQuery, 0, len(defs1))\n\tfor i := range defs1 {\n\t\tdefs = append(defs, defs1[i])\n\t}\n\tfor i := range defs2 {\n\t\tdefs = append(defs, defs2[i])\n\t}\n\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\tspecialHandle := func(row []string) []string {\n\t\tfor len(row) < 8 {\n\t\t\trow = append(row, \"\")\n\t\t}\n\t\treturn row\n\t}\n\n\tappendRows := func(row TableRowDef) {\n\t\trow.Values = specialHandle(row.Values)\n\t\tfor i := range row.SubValues {\n\t\t\trow.SubValues[i] = specialHandle(row.SubValues[i])\n\t\t}\n\t\tresultRows = append(resultRows, row)\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiKV},\n\t\tTitle:          \"snapshot_info\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2, 3, 4, 5},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TOTAL_VALUE\", \"TOTAL_COUNT\", \"P999\", \"P99\", \"P90\", \"P80\"},\n\t}\n\targ := newQueryArg(startTime, endTime)\n\terr := getTableRows(defs, arg, db, appendRows)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetTiKVCopInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []sumValueQuery{\n\t\t{name: \"tikv_cop_scan_keys_num\", tbl: \"tikv_cop_scan_keys_total_num\", labels: []string{\"instance\", \"req\"}},\n\t\t{tbl: \"tikv_cop_total_response_total_size\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_cop_scan_num\", tbl: \"tikv_cop_scan_details_total\", labels: []string{\"instance\", \"req\", \"tag\", \"cf\"}},\n\t}\n\tdefs := make([]rowQuery, 0, len(defs1))\n\tfor i := range defs1 {\n\t\tdefs = append(defs, defs1[i])\n\t}\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\tappendRows := func(row TableRowDef) {\n\t\tif len(row.Values) == 3 && row.Values[0] == \"tikv_cop_total_response_total_size\" {\n\t\t\tconvertFloatToSizeByRow(&row, 2)\n\t\t}\n\t\tresultRows = append(resultRows, row)\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiKV},\n\t\tTitle:          \"coprocessor_info\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TOTAL_VALUE\"},\n\t}\n\targ := newQueryArg(startTime, endTime)\n\terr := getTableRows(defs, arg, db, appendRows)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetTiKVRaftInfo(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []sumValueQuery{\n\t\t{tbl: \"tikv_raft_sent_messages_total_num\", labels: []string{\"instance\", \"type\"}},\n\t\t{tbl: \"tikv_flush_messages_total_num\", labels: []string{\"instance\"}},\n\t\t{tbl: \"tikv_receive_messages_total_num\", labels: []string{\"instance\"}},\n\t\t{tbl: \"tikv_raft_dropped_messages_total\", labels: []string{\"instance\", \"type\"}},\n\t\t{tbl: \"tikv_raft_proposals_total_num\", labels: []string{\"instance\", \"type\"}},\n\t}\n\tdefs := make([]rowQuery, 0, len(defs1))\n\tfor i := range defs1 {\n\t\tdefs = append(defs, defs1[i])\n\t}\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\tappendRows := func(row TableRowDef) {\n\t\tresultRows = append(resultRows, row)\n\t}\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiKV},\n\t\tTitle:          \"raft_info\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TOTAL_VALUE\"},\n\t}\n\targ := newQueryArg(startTime, endTime)\n\terr := getTableRows(defs, arg, db, appendRows)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetTiKVErrorTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []sumValueQuery{\n\t\t{tbl: \"tikv_grpc_error_total_count\", labels: []string{\"instance\", \"type\"}},\n\t\t{tbl: \"tikv_critical_error_total_count\", labels: []string{\"instance\", \"type\"}},\n\t\t{tbl: \"tikv_scheduler_is_busy_total_count\", labels: []string{\"instance\", \"db\", \"type\", \"stage\"}},\n\t\t{tbl: \"tikv_channel_full_total_count\", labels: []string{\"instance\", \"db\", \"type\"}},\n\t\t{tbl: \"tikv_coprocessor_request_error_total_count\", labels: []string{\"instance\", \"reason\"}},\n\t\t{tbl: \"tikv_engine_write_stall\", labels: []string{\"instance\", \"db\"}},\n\t\t{tbl: \"tikv_server_report_failures_total_count\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_storage_async_request_error\", tbl: \"tikv_storage_async_requests_total_count\", labels: []string{\"instance\", \"status\", \"type\"}, condition: \"status not in ('all','success')\"},\n\t\t{tbl: \"tikv_lock_manager_detect_error_total_count\", labels: []string{\"instance\", \"type\"}},\n\t\t{tbl: \"tikv_backup_errors_total_count\", labels: []string{\"instance\", \"error\"}},\n\t}\n\tdefs := make([]rowQuery, 0, len(defs1))\n\tfor i := range defs1 {\n\t\tdefs = append(defs, defs1[i])\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiKV},\n\t\tTitle:          \"tikv_error\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"TOTAL_COUNT\"},\n\t}\n\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\n\tspecialHandle := func(row []string) []string {\n\t\trow[2] = convertFloatToInt(row[2])\n\t\treturn row\n\t}\n\tappendRows := func(row TableRowDef) {\n\t\trow.Values = specialHandle(row.Values)\n\t\tfor i := range row.SubValues {\n\t\t\trow.SubValues[i] = specialHandle(row.SubValues[i])\n\t\t}\n\t\tresultRows = append(resultRows, row)\n\t}\n\n\targ := &queryArg{\n\t\tstartTime: startTime,\n\t\tendTime:   endTime,\n\t}\n\terr := getTableRows(defs, arg, db, appendRows)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetTiDBCurrentConfig(_, _ string, db *gorm.DB) (TableDef, error) {\n\tsql := \"select `key`,`value` from information_schema.CLUSTER_CONFIG where type='tidb' group by `key`,`value` order by `key`;\"\n\ttable := TableDef{\n\t\tCategory: []string{CategoryConfig},\n\t\tTitle:    \"tidb_current_config\",\n\t\tComment:  \"\",\n\t\tColumn:   []string{\"KEY\", \"VALUE\"},\n\t}\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetPDCurrentConfig(_, _ string, db *gorm.DB) (TableDef, error) {\n\tsql := \"select `key`,`value` from information_schema.CLUSTER_CONFIG where type='pd' group by `key`,`value` order by `key`;\"\n\ttable := TableDef{\n\t\tCategory: []string{CategoryConfig},\n\t\tTitle:    \"pd_current_config\",\n\t\tComment:  \"\",\n\t\tColumn:   []string{\"KEY\", \"VALUE\"},\n\t}\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetTiKVCurrentConfig(_, _ string, db *gorm.DB) (TableDef, error) {\n\tsql := \"select `key`,`value` from information_schema.CLUSTER_CONFIG where type='tikv' group by `key`,`value` order by `key`;\"\n\ttable := TableDef{\n\t\tCategory: []string{CategoryConfig},\n\t\tTitle:    \"tikv_current_config\",\n\t\tComment:  \"\",\n\t\tColumn:   []string{\"KEY\", \"VALUE\"},\n\t}\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc getSQLRows(db *gorm.DB, sql string) ([]TableRowDef, error) {\n\trows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tresultRows := make([]TableRowDef, len(rows))\n\tfor i := range rows {\n\t\tresultRows[i] = TableRowDef{Values: rows[i]}\n\t}\n\treturn resultRows, nil\n}\n\nfunc getSQLRoundRows(db *gorm.DB, sql string, nums []int, comment string) ([]TableRowDef, error) {\n\trows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, i := range nums {\n\t\tfor _, row := range rows {\n\t\t\trow[i] = RoundFloatString(row[i])\n\t\t}\n\t}\n\tresultRows := make([]TableRowDef, len(rows))\n\tfor i := range rows {\n\t\tresultRows[i] = TableRowDef{Values: rows[i], Comment: comment}\n\t}\n\treturn resultRows, nil\n}\n\nfunc getTableRows(defs []rowQuery, arg *queryArg, db *gorm.DB, appendRows func(def TableRowDef)) error {\n\tfor _, def := range defs {\n\t\trow, err := def.queryRow(arg, db)\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tcontinue\n\t\t}\n\t\tif row == nil {\n\t\t\tcontinue\n\t\t}\n\t\tappendRows(*row)\n\t}\n\treturn nil\n}\n\nfunc NewTableRowDef(values []string, subValues [][]string) TableRowDef {\n\treturn TableRowDef{\n\t\tValues:    values,\n\t\tSubValues: subValues,\n\t}\n}\n\nfunc getAvgValueTableData(defs1 []AvgMaxMinTableDef, startTime, endTime string, db *gorm.DB) ([]TableRowDef, error) {\n\tdefs := make([]rowQuery, 0, len(defs1))\n\tfor i := range defs1 {\n\t\tdefs = append(defs, defs1[i])\n\t}\n\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\tappendRows := func(row TableRowDef) {\n\t\tresultRows = append(resultRows, row)\n\t}\n\targ := newQueryArg(startTime, endTime)\n\terr := getTableRows(defs, arg, db, appendRows)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn resultRows, nil\n}\n\nfunc GetLoadTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []AvgMaxMinTableDef{\n\t\t{name: \"node_disk_io_utilization\", tbl: \"node_disk_io_util\", labels: []string{\"instance\", \"device\"}},\n\t\t{name: \"node_disk_write_latency\", tbl: \"node_disk_write_latency\", labels: []string{\"instance\", \"device\"}},\n\t\t{name: \"node_disk_read_latency\", tbl: \"node_disk_read_latency\", labels: []string{\"instance\", \"device\"}},\n\t\t{name: \"tikv_disk_read_bytes\", tbl: \"tikv_disk_read_bytes\", labels: []string{\"instance\", \"device\"}},\n\t\t{name: \"tikv_disk_write_bytes\", tbl: \"tikv_disk_write_bytes\", labels: []string{\"instance\", \"device\"}},\n\t\t{name: \"node_network_in_traffic\", tbl: \"node_network_in_traffic\", labels: []string{\"instance\", \"device\"}},\n\t\t{name: \"node_network_out_traffic\", tbl: \"node_network_out_traffic\", labels: []string{\"instance\", \"device\"}},\n\t\t{name: \"node_tcp_in_use\", tbl: \"node_tcp_in_use\", labels: []string{\"instance\"}},\n\t\t{name: \"node_tcp_connections\", tbl: \"node_tcp_connections\", labels: []string{\"instance\"}},\n\t}\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryLoad},\n\t\tTitle:          \"node_load_info\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2, 3, 4},\n\t\tColumn:         []string{\"METRIC_NAME\", \"instance\", \"AVG\", \"MAX\", \"MIN\"},\n\t}\n\trows := make([]TableRowDef, 0, 4)\n\t// get cpu usage\n\trow, err := getAvgMaxMinCPUUsage(startTime, endTime, db)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\trows = append(rows, *row)\n\t// get memory usage\n\trow, err = getAvgMaxMinMemoryUsage(startTime, endTime, db)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\trows = append(rows, *row)\n\tpartRows, err := getAvgValueTableData(defs1, startTime, endTime, db)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\tspecialHandle := func(row []string) []string {\n\t\tif len(row) < 5 {\n\t\t\treturn row\n\t\t}\n\t\tfor i := 2; i < 5; i++ {\n\t\t\tif len(row[i]) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tswitch row[0] {\n\t\t\tcase \"node_disk_io_utilization\":\n\t\t\t\tf, err := strconv.ParseFloat(row[i], 64)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn row\n\t\t\t\t}\n\t\t\t\trow[i] = convertFloatToString(f*100) + \"%\"\n\t\t\tcase \"node_disk_write_latency\", \"node_disk_read_latency\":\n\t\t\t\trow[i] = convertFloatToDuration(row[i], float64(1))\n\t\t\tcase \"node_tcp_in_use\", \"node_tcp_connections\":\n\t\t\t\trow[i] = convertFloatToInt(row[i])\n\t\t\tdefault:\n\t\t\t\trow[i] = convertFloatToSize(row[i])\n\t\t\t}\n\t\t}\n\t\treturn row\n\t}\n\tfor _, row := range partRows {\n\t\trow.Values = specialHandle(row.Values)\n\t\tfor i := range row.SubValues {\n\t\t\trow.SubValues[i] = specialHandle(row.SubValues[i])\n\t\t}\n\t}\n\trows = append(rows, partRows...)\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc getAvgMaxMinCPUUsage(startTime, endTime string, db *gorm.DB) (*TableRowDef, error) {\n\tcondition := fmt.Sprintf(\"where time >= '%s' and time < '%s' \", startTime, endTime)\n\tsql := fmt.Sprintf(\"select 'node_cpu_usage', '', 100-avg(value),100-min(value),100-max(value) from metrics_schema.node_cpu_usage %s and mode='idle'\", condition)\n\trows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(rows) == 0 {\n\t\treturn nil, nil\n\t}\n\tsql = fmt.Sprintf(\"select 'node_cpu_usage', instance, 100-avg(value) as avg_value,100-min(value),100-max(value) from metrics_schema.node_cpu_usage %s and mode='idle' group by instance order by avg_value desc\", condition)\n\tsubRows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tspecialHandle := func(row []string) []string {\n\t\tif len(row) == 0 {\n\t\t\treturn row\n\t\t}\n\t\tfor i := 2; i <= 4; i++ {\n\t\t\tif len(row[i]) > 0 {\n\t\t\t\trow[i] = RoundFloatString(row[i]) + \"%\"\n\t\t\t}\n\t\t}\n\t\treturn row\n\t}\n\trows[0] = specialHandle(rows[0])\n\tfor i := range subRows {\n\t\tsubRows[i] = specialHandle(subRows[i])\n\t}\n\treturn &TableRowDef{\n\t\tValues:    rows[0],\n\t\tSubValues: subRows,\n\t}, nil\n}\n\nfunc getAvgMaxMinMemoryUsage(startTime, endTime string, db *gorm.DB) (*TableRowDef, error) {\n\tcondition := fmt.Sprintf(\"where time >= '%s' and time < '%s' \", startTime, endTime)\n\tsql := fmt.Sprintf(`select 'node_mem_usage','', 100*(1-t1.avg_value/t2.total),100*(1-t1.min_value/t2.total), 100*(1-t1.max_value/t2.total) from\n\t\t\t(select avg(value) as avg_value,max(value) as max_value,min(value) as min_value from metrics_schema.node_memory_available %[1]s) as t1 join\n\t\t\t(select max(value) as total from metrics_schema.node_total_memory %[1]s) as t2;`, condition)\n\trows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(rows) == 0 {\n\t\treturn nil, nil\n\t}\n\tsql = fmt.Sprintf(`select 'node_mem_usage',t1.instance, 100*(1-t1.avg_value/t2.total) as avg_value, 100*(1-t1.min_value/t2.total), 100*(1-t1.max_value/t2.total)  from\n\t\t\t(select instance, avg(value) as avg_value,max(value) as max_value,min(value) as min_value from metrics_schema.node_memory_available %[1]s GROUP BY instance) as t1 join\n\t\t\t(select instance, max(value) as total from metrics_schema.node_total_memory %[1]s GROUP BY instance) as t2 where t1.instance = t2.instance order by avg_value desc;`, condition)\n\tsubRows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tspecialHandle := func(row []string) []string {\n\t\tif len(row) == 0 {\n\t\t\treturn row\n\t\t}\n\t\tfor i := 2; i <= 4; i++ {\n\t\t\tif len(row[i]) > 0 {\n\t\t\t\trow[i] = RoundFloatString(row[i]) + \"%\"\n\t\t\t}\n\t\t}\n\t\treturn row\n\t}\n\trows[0] = specialHandle(rows[0])\n\tfor i := range subRows {\n\t\tsubRows[i] = specialHandle(subRows[i])\n\t}\n\treturn &TableRowDef{\n\t\tValues:    rows[0],\n\t\tSubValues: subRows,\n\t}, nil\n}\n\nfunc GetCPUUsageTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tsql := fmt.Sprintf(\"select instance, job, avg(value),max(value),min(value) from metrics_schema.process_cpu_usage where time >= '%s' and time < '%s' and job not in ('overwritten-nodes','overwritten-cluster') group by instance, job order by avg(value) desc\",\n\t\tstartTime, endTime)\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryLoad},\n\t\tTitle:          \"process_cpu_usage\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2, 3, 4},\n\t\tColumn:         []string{\"INSTANCE\", \"JOB\", \"AVG\", \"MAX\", \"MIN\"},\n\t}\n\trows, err := getSQLRoundRows(db, sql, []int{2, 3, 4}, \"\")\n\tif err != nil {\n\t\treturn table, err\n\t}\n\tspecialHandle := func(row []string) []string {\n\t\tif len(row) < 5 {\n\t\t\treturn row\n\t\t}\n\t\tfor i := 2; i < 5; i++ {\n\t\t\tf, err := strconv.ParseFloat(row[i], 64)\n\t\t\tif err != nil {\n\t\t\t\treturn row\n\t\t\t}\n\t\t\trow[i] = convertFloatToString(f*100) + \"%\"\n\t\t}\n\t\treturn row\n\t}\n\tfor i := range rows {\n\t\trows[i].Values = specialHandle(rows[i].Values)\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetProcessMemUsageTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tsql := fmt.Sprintf(\"select instance, job, avg(value),max(value),min(value) from metrics_schema.tidb_process_mem_usage where time >= '%s' and time < '%s' and job not in ('overwritten-nodes','overwritten-cluster') group by instance, job order by avg(value) desc\",\n\t\tstartTime, endTime)\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryLoad},\n\t\tTitle:          \"process_memory_usage\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2, 3, 4},\n\t\tColumn:         []string{\"INSTANCE\", \"JOB\", \"AVG\", \"MAX\", \"MIN\"},\n\t}\n\trows, err := getSQLRoundRows(db, sql, []int{2, 3, 4}, \"\")\n\tif err != nil {\n\t\treturn table, err\n\t}\n\tspecialHandle := func(row []string) []string {\n\t\tif len(row) < 5 {\n\t\t\treturn row\n\t\t}\n\t\tfor i := 2; i < 5; i++ {\n\t\t\trow[i] = convertFloatToSize(row[i])\n\t\t}\n\t\treturn row\n\t}\n\tfor i := range rows {\n\t\trows[i].Values = specialHandle(rows[i].Values)\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetGoroutinesCountTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tsql := fmt.Sprintf(\"select instance, job, avg(value), max(value), min(value) from metrics_schema.goroutines_count where job in ('tidb','pd') and time >= '%s' and time < '%s' group by instance, job order by avg(value) desc\",\n\t\tstartTime, endTime)\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryLoad},\n\t\tTitle:          \"tidb/pd_goroutines_count\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2, 3, 4},\n\t\tColumn:         []string{\"INSTANCE\", \"JOB\", \"AVG\", \"MAX\", \"MIN\"},\n\t}\n\trows, err := getSQLRoundRows(db, sql, []int{2, 3, 4}, \"\")\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetTiKVThreadCPUTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs := []AvgMaxMinTableDef{\n\t\t{name: \"grpc\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'grpc%'\"},\n\t\t{name: \"raftstore\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'raftstore_%'\"},\n\t\t{name: \"Async apply\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'apply%'\"},\n\t\t{name: \"sched_worker\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'sched_%'\"},\n\t\t{name: \"snapshot\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'snap%'\"},\n\t\t{name: \"unified read pool\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'unified_read_po%'\"},\n\t\t{name: \"storage read pool\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'store_read%'\"},\n\t\t{name: \"storage read pool normal\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'store_read_norm%'\"},\n\t\t{name: \"storage read pool high\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'store_read_high%'\"},\n\t\t{name: \"storage read pool low\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'store_read_low%'\"},\n\t\t{name: \"cop\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'cop%'\"},\n\t\t{name: \"cop normal\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'cop_normal%'\"},\n\t\t{name: \"cop high\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'cop_high%'\"},\n\t\t{name: \"cop low\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'cop_low%'\"},\n\t\t{name: \"rocksdb\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'rocksdb%'\"},\n\t\t{name: \"gc\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name like 'gc_worker%'\"},\n\t\t{name: \"split_check\", tbl: \"tikv_thread_cpu\", labels: []string{\"instance\"}, condition: \"name = 'split_check'\"},\n\t}\n\tconfigKeys := map[string]string{\n\t\t\"grpc\":                     \"server.grpc-concurrency\",\n\t\t\"sched_worker\":             \"storage.scheduler-worker-pool-size\",\n\t\t\"raftstore\":                \"raftstore.store-pool-size\",\n\t\t\"Async apply\":              \"raftstore.apply-pool-size\",\n\t\t\"unified read pool\":        \"readpool.unified.max-thread-count\",\n\t\t\"storage read pool high\":   \"readpool.storage.high-concurrency\",\n\t\t\"storage read pool low\":    \"readpool.storage.low-concurrency\",\n\t\t\"storage read pool normal\": \"readpool.storage.normal-concurrency\",\n\t\t\"cop high\":                 \"readpool.coprocessor.high-concurrency\",\n\t\t\"cop low\":                  \"readpool.coprocessor.low-concurrency\",\n\t\t\"cop normal\":               \"readpool.coprocessor.normal-concurrency\",\n\t}\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryLoad},\n\t\tTitle:          \"tikv_thread_cpu_usage\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2, 3, 4},\n\t\tColumn:         []string{\"METRIC_NAME\", \"INSTANCE\", \"AVG\", \"MAX\", \"MIN\", \"CONFIG_KEY\", \"CURRENT_CONFIG_VALUE\"},\n\t}\n\ttype instanceKey struct {\n\t\tinstance string\n\t\tkey      string\n\t}\n\tvar keysBuf bytes.Buffer\n\tidx := 0\n\tfor _, v := range configKeys {\n\t\tif idx > 0 {\n\t\t\tkeysBuf.WriteByte(',')\n\t\t}\n\t\tkeysBuf.WriteByte('\\'')\n\t\tkeysBuf.WriteString(v)\n\t\tkeysBuf.WriteByte('\\'')\n\t\tidx++\n\t}\n\tsql := fmt.Sprintf(\"select t2.status_address, t1.`key`,t1.value from (select instance, `key`,value from information_schema.cluster_config where type='tikv' and `key` in (%s) ) as t1 join \"+\n\t\t\"(select instance,status_address from information_schema.cluster_info where type='tikv') as t2 where t1.instance=t2.instance\", keysBuf.String())\n\trows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\tcfgMap := make(map[instanceKey]string)\n\tfor _, row := range rows {\n\t\tcfgMap[instanceKey{\n\t\t\tinstance: row[0],\n\t\t\tkey:      row[1],\n\t\t}] = row[2]\n\t}\n\tspecialHandle := func(row []string) []string {\n\t\tif len(row) < 7 {\n\t\t\treturn row\n\t\t}\n\t\tfor i := 2; i < 5; i++ {\n\t\t\tf, err := strconv.ParseFloat(row[i], 64)\n\t\t\tif err != nil {\n\t\t\t\treturn row\n\t\t\t}\n\t\t\trow[i] = convertFloatToString(f*100) + \"%\"\n\t\t}\n\t\t// get config value\n\t\tif cfgValue, ok := cfgMap[instanceKey{\n\t\t\tinstance: row[1],\n\t\t\tkey:      configKeys[row[0]],\n\t\t}]; ok {\n\t\t\trow[5] = configKeys[row[0]]\n\t\t\trow[6] = cfgValue\n\t\t}\n\t\treturn row\n\t}\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\tappendRows := func(row TableRowDef) {\n\t\trow.Values = specialHandle(row.Values)\n\t\tfor i := range row.SubValues {\n\t\t\trow.SubValues[i] = specialHandle(row.SubValues[i])\n\t\t}\n\t\tresultRows = append(resultRows, row)\n\t}\n\n\tfor _, def := range defs {\n\t\tcondition := fmt.Sprintf(\"where time >= '%s' and time < '%s' \", startTime, endTime)\n\t\tif len(def.condition) > 0 {\n\t\t\tcondition = condition + \"and \" + def.condition\n\t\t}\n\t\tsql := fmt.Sprintf(\"select '%[1]s', '', avg(sum_value),max(sum_value),min(sum_value),'','' from ( select sum(value) as sum_value from metrics_schema.%[2]s %[3]s group by %[4]s, time) as t1\",\n\t\t\tdef.name, def.tbl, condition, def.labels[0])\n\t\trows, err := querySQL(db, sql)\n\t\tif err != nil {\n\t\t\treturn table, err\n\t\t}\n\t\tif len(rows) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tsql = fmt.Sprintf(\"select '%[1]s', %[2]s,avg(sum_value),max(sum_value),min(sum_value),'','' from ( select %[2]s,sum(value) as sum_value from metrics_schema.%[3]s %[4]s group by %[2]s,time) as t1 group by %[2]s order by avg(sum_value) desc\",\n\t\t\tdef.name, def.labels[0], def.tbl, condition)\n\t\tsubRows, err := querySQL(db, sql)\n\t\tif err != nil {\n\t\t\treturn table, err\n\t\t}\n\t\tappendRows(TableRowDef{\n\t\t\tValues:    rows[0],\n\t\t\tSubValues: subRows,\n\t\t\tComment:   def.Comment,\n\t\t})\n\t}\n\tsortRowsByIndex(resultRows, 2)\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetStoreStatusTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs1 := []AvgMaxMinTableDef{\n\t\t{name: \"region_score\", tbl: \"pd_scheduler_store_status\", condition: \"type = 'region_score'\", labels: []string{\"address\"}},\n\t\t{name: \"leader_score\", tbl: \"pd_scheduler_store_status\", condition: \"type = 'leader_score'\", labels: []string{\"address\"}},\n\t\t{name: \"region_count\", tbl: \"pd_scheduler_store_status\", condition: \"type = 'region_count'\", labels: []string{\"address\"}},\n\t\t{name: \"leader_count\", tbl: \"pd_scheduler_store_status\", condition: \"type = 'leader_count'\", labels: []string{\"address\"}},\n\t\t{name: \"region_size\", tbl: \"pd_scheduler_store_status\", condition: \"type = 'region_size'\", labels: []string{\"address\"}},\n\t\t{name: \"leader_size\", tbl: \"pd_scheduler_store_status\", condition: \"type = 'leader_size'\", labels: []string{\"address\"}},\n\t}\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryPD},\n\t\tTitle:          \"store_status\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2, 3, 4},\n\t\tColumn:         []string{\"METRIC_NAME\", \"INSTANCE\", \"AVG\", \"MAX\", \"MIN\"},\n\t}\n\trows, err := getAvgValueTableData(defs1, startTime, endTime, db)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetPDClusterStatusTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tsql := fmt.Sprintf(\"select type, max(value), min(value) from metrics_schema.pd_cluster_status where time >= '%s' and time < '%s' group by type\",\n\t\tstartTime, endTime)\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryPD},\n\t\tTitle:          \"cluster_status\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0},\n\t\tcompareColumns: []int{1, 2},\n\t\tColumn:         []string{\"TYPE\", \"MAX\", \"MIN\"},\n\t}\n\trows, err := getSQLRoundRows(db, sql, []int{1, 2}, \"\")\n\tif err != nil {\n\t\treturn table, err\n\t}\n\tfor i := range rows {\n\t\tif len(rows[i].Values) != 3 {\n\t\t\tcontinue\n\t\t}\n\t\tswitch rows[i].Values[0] {\n\t\tcase \"store_disconnected_count\":\n\t\tcase \"leader_count\":\n\t\t\trows[i].Comment = \"The total number of leader Regions\"\n\t\tcase \"store_up_count\":\n\t\t\trows[i].Comment = \"The count of healthy stores\"\n\t\tcase \"storage_capacity\", \"storage_size\":\n\t\t\trows[i].Values[1] = convertFloatToSize(rows[i].Values[1])\n\t\t\trows[i].Values[2] = convertFloatToSize(rows[i].Values[2])\n\t\t}\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetPDEtcdStatusTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tsql := fmt.Sprintf(\"select type, max(value), min(value) from metrics_schema.pd_server_etcd_state where time >= '%s' and time < '%s' group by type\",\n\t\tstartTime, endTime)\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryPD},\n\t\tTitle:          \"etcd_status\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0},\n\t\tcompareColumns: []int{1, 2},\n\t\tColumn:         []string{\"TYPE\", \"MAX\", \"MIN\"},\n\t}\n\trows, err := getSQLRoundRows(db, sql, []int{1, 2}, \"\")\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetClusterInfoTable(_, _ string, db *gorm.DB) (TableDef, error) {\n\tsql := \"SELECT `TYPE`,`INSTANCE`,`STATUS_ADDRESS`,`VERSION`,`GIT_HASH`,`START_TIME`,`UPTIME`,`SERVER_ID` FROM information_schema.cluster_info ORDER BY `TYPE`,`START_TIME` DESC\"\n\ttable := TableDef{\n\t\tCategory:    []string{CategoryHeader},\n\t\tTitle:       \"cluster_info\",\n\t\tComment:     \"\",\n\t\tjoinColumns: []int{0, 1, 2, 3, 4},\n\t\tColumn:      []string{\"TYPE\", \"INSTANCE\", \"STATUS_ADDRESS\", \"VERSION\", \"GIT_HASH\", \"START_TIME\", \"UPTIME\", \"SERVER_ID\"},\n\t}\n\trows, err := getSQLRoundRows(db, sql, nil, \"\")\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\nfunc GetTiKVCacheHitTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\ttables := []AvgMaxMinTableDef{\n\t\t{name: \"tikv_memtable_hit\", tbl: \"tikv_memtable_hit\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_block_all_cache_hit\", tbl: \"tikv_block_all_cache_hit\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_block_index_cache_hit\", tbl: \"tikv_block_index_cache_hit\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_block_filter_cache_hit\", tbl: \"tikv_block_filter_cache_hit\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_block_data_cache_hit\", tbl: \"tikv_block_data_cache_hit\", labels: []string{\"instance\"}},\n\t\t{name: \"tikv_block_bloom_prefix_cache_hit\", tbl: \"tikv_block_bloom_prefix_cache_hit\", labels: []string{\"instance\"}},\n\t}\n\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiKV},\n\t\tTitle:          \"cache_hit\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2, 3, 4},\n\t\tColumn:         []string{\"METRIC_NAME\", \"INSTANCE\", \"AVG\", \"MAX\", \"MIN\"},\n\t}\n\trows, err := getAvgValueTableData(tables, startTime, endTime, db)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\tspecialHandle := func(row []string) []string {\n\t\tif len(row) < 5 {\n\t\t\treturn row\n\t\t}\n\t\tfor i := 2; i < 5; i++ {\n\t\t\tf, err := strconv.ParseFloat(row[i], 64)\n\t\t\tif err != nil {\n\t\t\t\treturn row\n\t\t\t}\n\t\t\trow[i] = convertFloatToString(f*100) + \"%\"\n\t\t}\n\t\treturn row\n\t}\n\tfor i := range rows {\n\t\trows[i].Values = specialHandle(rows[i].Values)\n\t\tfor j := range rows[i].SubValues {\n\t\t\trows[i].SubValues[j] = specialHandle(rows[i].SubValues[j])\n\t\t}\n\t}\n\ttable.Rows = rows\n\treturn table, nil\n}\n\ntype hardWare struct {\n\tinstance string\n\tType     map[string]int\n\tcpu      map[string]int\n\tmemory   float64\n\tdisk     map[string]float64\n\tuptime   string\n}\n\nfunc GetClusterHardwareInfoTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tresultRows := make([]TableRowDef, 0, 1)\n\ttable := TableDef{\n\t\tCategory: []string{CategoryHeader},\n\t\tTitle:    \"cluster_hardware\",\n\t\tComment:  \"\",\n\t\tColumn:   []string{\"HOST\", \"INSTANCE\", \"CPU_CORES\", \"MEMORY (GB)\", \"DISK (GB)\", \"UPTIME (DAY)\"},\n\t}\n\tsql := `SELECT instance,type,NAME,VALUE\n\t\tFROM information_schema.CLUSTER_HARDWARE\n\t\tWHERE device_type='cpu'\n\t\tgroup by instance,type,VALUE,NAME HAVING NAME = 'cpu-physical-cores'\n\t\tOR NAME = 'cpu-logical-cores' ORDER BY INSTANCE`\n\trows, err := querySQL(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\tm := make(map[string]*hardWare)\n\tvar s string\n\tfor _, row := range rows {\n\t\tidx := strings.Index(row[0], \":\")\n\t\ts := row[0][:idx]\n\t\tcpuCnt, err := strconv.Atoi(row[3])\n\t\tif err != nil {\n\t\t\treturn table, err\n\t\t}\n\t\t_, ok := m[s]\n\t\tif !ok {\n\t\t\tm[s] = &hardWare{s, map[string]int{row[1]: 1}, make(map[string]int), 0, make(map[string]float64), \"\"}\n\t\t}\n\t\tm[s].Type[row[1]]++\n\t\tif _, ok := m[s].cpu[row[2]]; !ok {\n\t\t\tm[s].cpu[row[2]] = cpuCnt\n\t\t}\n\t}\n\tsql = \"SELECT instance,value FROM information_schema.CLUSTER_HARDWARE WHERE device_type='memory' and name = 'capacity' group by instance,value\"\n\trows, err = querySQL(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\tfor _, row := range rows {\n\t\ts = row[0][:strings.Index(row[0], \":\")]\n\t\tmemCnt, err := strconv.ParseFloat(row[1], 64)\n\t\tif err != nil {\n\t\t\treturn table, err\n\t\t}\n\t\tm[s].memory = memCnt\n\t}\n\tsql = \"SELECT `INSTANCE`,`DEVICE_NAME`,`VALUE` from information_schema.CLUSTER_HARDWARE where `NAME` = 'total' AND `DEVICE_TYPE` = 'disk' AND `DEVICE_NAME` NOT LIKE '%loop%' group by instance,device_name,value\"\n\trows, err = querySQL(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\tfor _, row := range rows {\n\t\ts = row[0][:strings.Index(row[0], \":\")]\n\t\tdiskCnt, err := strconv.ParseFloat(row[2], 64)\n\t\tif err != nil {\n\t\t\treturn table, err\n\t\t}\n\t\tif _, ok := m[s].disk[row[1]]; !ok {\n\t\t\tm[s].disk[row[1]] = diskCnt\n\t\t}\n\t}\n\n\tsql = `SELECT instance,max(value)/60/60/24\n\tFROM metrics_schema.node_uptime\n\twhere time >= '%[1]s' and time < '%[2]s'\n\tGROUP BY instance`\n\tsql = fmt.Sprintf(sql, startTime, endTime)\n\trows, err = querySQL(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\n\tfor _, row := range rows {\n\t\ts = row[0][:strings.Index(row[0], \":\")]\n\t\tif _, ok := m[s]; ok {\n\t\t\tm[s].uptime = row[1]\n\t\t} else {\n\t\t\tm[s] = &hardWare{s, make(map[string]int), nil, 0, make(map[string]float64), \"\"}\n\t\t}\n\t}\n\trows = rows[:0]\n\tfor _, v := range m {\n\t\trow := make([]string, 6)\n\t\trow[0] = v.instance\n\t\tfor k, va := range v.Type {\n\t\t\trow[1] += fmt.Sprintf(\"%[1]s*%[2]s \", k, strconv.Itoa(va/2))\n\t\t}\n\t\trow[2] = strconv.Itoa(v.cpu[\"cpu-physical-cores\"]) + \"/\" + strconv.Itoa(v.cpu[\"cpu-logical-cores\"])\n\t\trow[3] = fmt.Sprintf(\"%f\", v.memory/(1024*1024*1024))\n\t\tfor k, va := range v.disk {\n\t\t\trow[4] += fmt.Sprintf(\"%[1]s: %[2]f    \", k, va/(1024*1024*1024))\n\t\t}\n\t\trow[5] = v.uptime\n\t\trows = append(rows, row)\n\t}\n\tfor _, row := range rows {\n\t\tresultRows = append(resultRows, NewTableRowDef(row, nil))\n\t}\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetTiKVRocksDBTimeConsumeTable(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tdefs := []struct {\n\t\tname       string\n\t\tmaxTbl     string\n\t\ttbl        string\n\t\tconditions []string\n\t\tcomment    string\n\t}{\n\t\t{\n\t\t\tname:       \"get duration\",\n\t\t\tmaxTbl:     \"tikv_engine_max_get_duration\",\n\t\t\ttbl:        \"tikv_engine_avg_get_duration\",\n\t\t\tconditions: []string{\"type='get_average'\", \"type='get_max'\", \"type='get_percentile99'\", \"type='get_percentile95'\"},\n\t\t\tcomment:    \"The time consumed when rocksdb executing get operations\",\n\t\t},\n\t\t{\n\t\t\tname:       \"seek duration\",\n\t\t\tmaxTbl:     \"tikv_engine_max_seek_duration\",\n\t\t\ttbl:        \"tikv_engine_avg_seek_duration\",\n\t\t\tconditions: []string{\"type='seek_average'\", \"type='seek_max'\", \"type='seek_percentile99'\", \"type='seek_percentile95'\"},\n\t\t\tcomment:    \"The time consumed when rocksdb executing seek operations\",\n\t\t},\n\t\t{\n\t\t\tname:       \"write duration\",\n\t\t\tmaxTbl:     \"tikv_engine_write_duration\",\n\t\t\ttbl:        \"tikv_engine_write_duration\",\n\t\t\tconditions: []string{\"type='write_average'\", \"type='write_max'\", \"type='write_percentile99'\", \"type='write_percentile95'\"},\n\t\t\tcomment:    \"The time consumed when rocksdb executing write operations\",\n\t\t},\n\t\t{\n\t\t\tname:       \"WAL sync duration\",\n\t\t\tmaxTbl:     \"tikv_wal_sync_max_duration\",\n\t\t\ttbl:        \"tikv_wal_sync_duration\",\n\t\t\tconditions: []string{\"type='wal_file_sync_average'\", \"type='wal_file_sync_max'\", \"type='wal_file_sync_percentile99'\", \"type='wal_file_sync_percentile95'\"},\n\t\t\tcomment:    \"The time consumed when rocksdb executing WAL sync operations\",\n\t\t},\n\t\t{\n\t\t\tname:       \"compaction duration\",\n\t\t\tmaxTbl:     \"tikv_compaction_max_duration\",\n\t\t\ttbl:        \"tikv_compaction_duration\",\n\t\t\tconditions: []string{\"type='compaction_time_average'\", \"type='compaction_time_max'\", \"type='compaction_time_percentile99'\", \"type='compaction_time_percentile95'\"},\n\t\t\tcomment:    \"The time consumed when rocksdb executing compaction operations\",\n\t\t},\n\t\t{\n\t\t\tname:       \"SST read duration\",\n\t\t\tmaxTbl:     \"tikv_sst_read_max_duration\",\n\t\t\ttbl:        \"tikv_sst_read_duration\",\n\t\t\tconditions: []string{\"type='sst_read_micros_average'\", \"type='sst_read_micros_max'\", \"type='sst_read_micros_percentile99'\", \"type='sst_read_micros_percentile95'\"},\n\t\t\tcomment:    \"The time consumed when rocksdb reading SST files\",\n\t\t},\n\t\t{\n\t\t\tname:       \"write stall duration\",\n\t\t\tmaxTbl:     \"tikv_write_stall_max_duration\",\n\t\t\ttbl:        \"tikv_write_stall_avg_duration\",\n\t\t\tconditions: []string{\"type='write_stall_average'\", \"type='write_stall_max'\", \"type='write_stall_percentile99'\", \"type='write_stall_percentile95'\"},\n\t\t\tcomment:    \"The time which is caused by write stall\",\n\t\t},\n\t}\n\ttable := TableDef{\n\t\tCategory:       []string{CategoryTiKV},\n\t\tTitle:          \"rocksdb_time_consume\",\n\t\tComment:        \"\",\n\t\tjoinColumns:    []int{0, 1},\n\t\tcompareColumns: []int{2, 3, 4, 5},\n\t\tColumn:         []string{\"METRIC_NAME\", \"LABEL\", \"AVG\", \"MAX\", \"P99\", \"P95\"},\n\t}\n\ttimeCondition := fmt.Sprintf(\"where time >= '%s' and time < '%s' \", startTime, endTime)\n\n\tspecialHandle := func(row []string) []string {\n\t\tif len(row) < 6 {\n\t\t\treturn row\n\t\t}\n\t\tfor i := 2; i < 6; i++ {\n\t\t\trow[i] = convertFloatToDuration(row[i], float64(1)/float64(10e5))\n\t\t}\n\t\treturn row\n\t}\n\n\tresultRows := make([]TableRowDef, 0, len(defs))\n\tfor _, def := range defs {\n\t\t// get sum rows\n\t\tsql := fmt.Sprintf(\"select '%s', '', t0.*, t1.*,t2.*,t3.* from \", def.name)\n\t\tfor i := range def.conditions {\n\t\t\tcondition := timeCondition\n\t\t\tif len(def.conditions[i]) > 0 {\n\t\t\t\tcondition = condition + \" and \" + def.conditions[i]\n\t\t\t}\n\t\t\t// avg value\n\t\t\tif i == 0 {\n\t\t\t\tsql = sql + fmt.Sprintf(\"(select avg(value) from metrics_schema.%s %s) as t%v \", def.tbl, condition, i)\n\t\t\t} else {\n\t\t\t\tsql = sql + fmt.Sprintf(\"join (select max(value) from metrics_schema.%s %s) as t%v \", def.tbl, condition, i)\n\t\t\t}\n\t\t}\n\t\trows, err := querySQL(db, sql)\n\t\tif err != nil {\n\t\t\treturn table, err\n\t\t}\n\t\tif len(rows) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tsql = fmt.Sprintf(\"select '%s', t0.instance, t0.value, t1.value,t2.value,t3.value from \", def.name)\n\t\tfor i := range def.conditions {\n\t\t\tcondition := timeCondition\n\t\t\tif len(def.conditions[i]) > 0 {\n\t\t\t\tcondition = condition + \" and \" + def.conditions[i]\n\t\t\t}\n\t\t\t// avg value\n\t\t\tif i == 0 {\n\t\t\t\tsql = sql + fmt.Sprintf(\"(select instance, avg(value) as value from metrics_schema.%s %s group by instance) as t%v \", def.tbl, condition, i)\n\t\t\t} else {\n\t\t\t\tsql = sql + fmt.Sprintf(\"join (select instance, max(value) as value from metrics_schema.%s %s group by instance) as t%v \", def.tbl, condition, i)\n\t\t\t}\n\t\t}\n\t\tsql += \" on t0.instance = t1.instance and t1.instance = t2.instance and t2.instance = t3.instance order by t0.value desc\"\n\t\tsubRows, err := querySQL(db, sql)\n\t\tif err != nil {\n\t\t\treturn table, err\n\t\t}\n\t\trows[0] = specialHandle(rows[0])\n\t\tfor i := range subRows {\n\t\t\tsubRows[i] = specialHandle(subRows[i])\n\t\t}\n\t\tresultRows = append(resultRows, TableRowDef{\n\t\t\tValues:    rows[0],\n\t\t\tSubValues: subRows,\n\t\t\tComment:   def.comment,\n\t\t})\n\t}\n\ttable.Rows = resultRows\n\treturn table, nil\n}\n\nfunc GetTiDBTopNSlowQuery(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tcolumns := []string{\"query_time\", \"parse_time\", \"compile_time\", \"prewrite_time\", \"commit_time\", \"process_time\", \"wait_time\", \"backoff_time\", \"cop_proc_max\", \"cop_wait_max\", \"query\"}\n\tsql := fmt.Sprintf(\"select %s from information_schema.cluster_slow_query where time >= '%s' and time < '%s' order by query_time desc limit 10;\",\n\t\tstrings.Join(columns, \",\"), startTime, endTime)\n\ttable := TableDef{\n\t\tCategory: []string{CategoryTiDB},\n\t\tTitle:    \"top_10_slow_query\",\n\t\tComment:  \"\",\n\t\tColumn:   columns,\n\t}\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = useSubRowForLongColumnValue(rows, len(table.Column)-1)\n\treturn table, nil\n}\n\nfunc GetTiDBTopNSlowQueryGroupByDigest(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tcolumns := []string{\"*\", \"query_time\", \"parse_time\", \"compile_time\", \"prewrite_time\", \"commit_time\", \"process_time\", \"wait_time\", \"backoff_time\", \"cop_proc_max\", \"cop_wait_max\", \"query\"}\n\tfor i := range columns {\n\t\tswitch columns[i] {\n\t\tcase \"*\":\n\t\t\tcolumns[i] = \"count(*)\"\n\t\tcase \"query\":\n\t\t\tcolumns[i] = \"min(query)\"\n\t\tdefault:\n\t\t\tcolumns[i] = \"sum(\" + columns[i] + \")\"\n\t\t}\n\t}\n\tsql := fmt.Sprintf(\"select /*+ AGG_TO_COP(), HASH_AGG() */ %s from information_schema.cluster_slow_query where time >= '%s' and time < '%s' group by digest order by sum(query_time) desc limit 10;\",\n\t\tstrings.Join(columns, \",\"), startTime, endTime)\n\ttable := TableDef{\n\t\tCategory: []string{CategoryTiDB},\n\t\tTitle:    \"top_10_slow_query_group_by_digest\",\n\t\tComment:  \"\",\n\t\tColumn:   columns,\n\t}\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = useSubRowForLongColumnValue(rows, len(table.Column)-1)\n\treturn table, nil\n}\n\nfunc GetTiDBSlowQueryWithDiffPlan(startTime, endTime string, db *gorm.DB) (TableDef, error) {\n\tsql := fmt.Sprintf(\"select /*+ AGG_TO_COP(), HASH_AGG() */ digest, min(query) from information_schema.cluster_slow_query where time >= '%s' and time < '%s' group by digest having max(plan_digest) != min(plan_digest);\",\n\t\tstartTime, endTime)\n\ttable := TableDef{\n\t\tCategory: []string{CategoryTiDB},\n\t\tTitle:    \"slow_query_with_diff_plan\",\n\t\tComment:  \"\",\n\t\tColumn:   []string{\"digest\", \"query\"},\n\t}\n\trows, err := getSQLRows(db, sql)\n\tif err != nil {\n\t\treturn table, err\n\t}\n\ttable.Rows = useSubRowForLongColumnValue(rows, 1)\n\treturn table, nil\n}\n"
  },
  {
    "path": "pkg/apiserver/diagnose/report_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage diagnose\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/check\"\n)\n\nfunc TestT(t *testing.T) {\n\tcheck.CustomVerboseFlag = true\n\tcheck.TestingT(t)\n}\n\nvar _ = check.Suite(&testReportSuite{})\n\ntype testReportSuite struct{}\n\nfunc (t *testReportSuite) TestCompareTable(c *check.C) {\n\ttable1 := TableDef{\n\t\tCategory:       []string{\"header\"},\n\t\tTitle:          \"test\",\n\t\tjoinColumns:    []int{1},\n\t\tcompareColumns: []int{2},\n\t\tColumn:         []string{\"c1\", \"c2\", \"c3\"},\n\t\tRows:           nil,\n\t}\n\n\tcases := []struct {\n\t\trows1 []TableRowDef\n\t\trows2 []TableRowDef\n\t\tout   []TableRowDef\n\t}{\n\t\t{\n\t\t\trows1: nil,\n\t\t\trows2: nil,\n\t\t\tout:   []TableRowDef{},\n\t\t},\n\t\t{\n\t\t\trows1: []TableRowDef{\n\t\t\t\t{Values: []string{\"0\", \"0\", \"0\"}},\n\t\t\t},\n\t\t\trows2: nil,\n\t\t\tout: []TableRowDef{\n\t\t\t\t{Values: []string{\"0\", \"0\", \"0\", \"\", \"\", \"1\"}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\trows1: []TableRowDef{\n\t\t\t\t{Values: []string{\"0\", \"0\", \"0\"}},\n\t\t\t},\n\t\t\trows2: []TableRowDef{\n\t\t\t\t{Values: []string{\"1\", \"1\", \"1\"}},\n\t\t\t},\n\t\t\tout: []TableRowDef{\n\t\t\t\t{Values: []string{\"0\", \"0\", \"0\", \"\", \"\", \"1\"}},\n\t\t\t\t{Values: []string{\"\", \"1\", \"\", \"1\", \"1\", \"1\"}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\trows1: []TableRowDef{\n\t\t\t\t{Values: []string{\"0\", \"0\", \"0\"}},\n\t\t\t},\n\t\t\trows2: []TableRowDef{\n\t\t\t\t{Values: []string{\"1\", \"0\", \"0\"}},\n\t\t\t},\n\t\t\tout: []TableRowDef{\n\t\t\t\t{Values: []string{\"0\", \"0\", \"0\", \"1\", \"0\", \"0\"}},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\trows1: []TableRowDef{\n\t\t\t\t{Values: []string{\"0\", \"0\", \"0\"}},\n\t\t\t},\n\t\t\trows2: []TableRowDef{\n\t\t\t\t{Values: []string{\"1\", \"0\", \"1\"}},\n\t\t\t},\n\t\t\tout: []TableRowDef{\n\t\t\t\t{Values: []string{\"0\", \"0\", \"0\", \"1\", \"1\", \"1\"}},\n\t\t\t},\n\t\t},\n\t}\n\n\tdr := &diffRows{}\n\tfor _, cas := range cases {\n\t\tt1 := table1\n\t\tt2 := table1\n\t\tt1.Rows = cas.rows1\n\t\tt2.Rows = cas.rows2\n\t\tt, err := compareTable(&t1, &t2, dr)\n\t\tc.Assert(err, check.IsNil)\n\t\tc.Assert(len(t.Rows), check.Equals, len(cas.out))\n\t\tfor i, row := range t.Rows {\n\t\t\tc.Assert(row.Values, check.DeepEquals, cas.out[i].Values)\n\t\t\tc.Assert(len(row.SubValues), check.Equals, len(cas.out[i].SubValues))\n\t\t\tfor j, subRow := range cas.out[i].SubValues {\n\t\t\t\tc.Assert(subRow, check.DeepEquals, row.SubValues[j])\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (t *testReportSuite) TestRoundFloatString(c *check.C) {\n\tcases := []struct {\n\t\tin  string\n\t\tout string\n\t}{\n\t\t{\"0\", \"0\"},\n\t\t{\"1\", \"1\"},\n\t\t{\"0.8\", \"0.8\"},\n\t\t{\"0.99\", \"0.99\"},\n\t\t{\"1.12345\", \"1.12\"},\n\t\t{\"1.1256\", \"1.13\"},\n\t\t{\"12345678.1256\", \"12345678.13\"},\n\t\t{\"0.1256\", \"0.13\"},\n\t\t{\"0.00234\", \"0.002\"},\n\t\t{\"0.00254\", \"0.003\"},\n\t\t{\"0.000000056\", \"0.00000006\"},\n\t\t{\"0.00000000000000054\", \"0.0000000000000005\"},\n\t\t{\"0.00000000000000056\", \"0.0000000000000006\"},\n\t\t{\"65.20832000000001\", \"65.21\"},\n\t}\n\tfor _, cas := range cases {\n\t\tresult := RoundFloatString(cas.in)\n\t\tc.Assert(result, check.Equals, cas.out)\n\t}\n}\n"
  },
  {
    "path": "pkg/apiserver/info/info.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage info\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/Masterminds/semver\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/samber/lo\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/topology\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/version\"\n\t\"github.com/pingcap/tidb-dashboard/util/featureflag\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\ntype ServiceParams struct {\n\tfx.In\n\tEtcdClient   *clientv3.Client\n\tConfig       *config.Config\n\tLocalStore   *dbstore.DB\n\tTiDBClient   *tidb.Client\n\tFeatureFlags *featureflag.Registry\n}\n\ntype Service struct {\n\tparams       ServiceParams\n\tlifecycleCtx context.Context\n}\n\nfunc NewService(lc fx.Lifecycle, p ServiceParams) *Service {\n\ts := &Service{params: p}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\ts.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn s\n}\n\nfunc RegisterRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/info\")\n\tendpoint.GET(\"/info\", s.infoHandler)\n\tendpoint.Use(auth.MWAuthRequired())\n\tendpoint.GET(\"/whoami\", s.WhoamiHandler)\n\n\tendpoint.Use(utils.MWConnectTiDB(s.params.TiDBClient))\n\tendpoint.GET(\"/databases\", s.databasesHandler)\n\tendpoint.GET(\"/tables\", s.tablesHandler)\n}\n\ntype InfoResponse struct { // nolint\n\tVersion            *version.Info  `json:\"version\"`\n\tEnableTelemetry    bool           `json:\"enable_telemetry\"`\n\tEnableExperimental bool           `json:\"enable_experimental\"`\n\tSupportedFeatures  []string       `json:\"supported_features\"`\n\tNgmState           utils.NgmState `json:\"ngm_state\"`\n}\n\n// @ID infoGet\n// @Summary Get information about this TiDB Dashboard\n// @Success 200 {object} InfoResponse\n// @Router /info/info [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) infoHandler(c *gin.Context) {\n\t// Checking ngm deployments\n\t// drop \"-alpha-xxx\" suffix\n\tversionWithoutSuffix := strings.Split(s.params.Config.FeatureVersion, \"-\")[0]\n\tv, err := semver.NewVersion(versionWithoutSuffix)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tconstraint, err := semver.NewConstraint(\">= v5.4.0\")\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tngmState := utils.NgmStateNotSupported\n\tif constraint.Check(v) {\n\t\tngmState = utils.NgmStateNotStarted\n\t\taddr, err := topology.FetchNgMonitoringTopology(s.lifecycleCtx, s.params.EtcdClient)\n\t\tif err == nil && addr != \"\" {\n\t\t\tngmState = utils.NgmStateStarted\n\t\t}\n\t}\n\n\tresp := InfoResponse{\n\t\tVersion:            version.GetInfo(),\n\t\tEnableTelemetry:    s.params.Config.EnableTelemetry,\n\t\tEnableExperimental: s.params.Config.EnableExperimental,\n\t\tSupportedFeatures:  s.params.FeatureFlags.SupportedFeatures(),\n\t\tNgmState:           ngmState,\n\t}\n\tc.JSON(http.StatusOK, resp)\n}\n\ntype WhoAmIResponse struct {\n\tDisplayName string `json:\"display_name\"`\n\tIsShareable bool   `json:\"is_shareable\"`\n\tIsWriteable bool   `json:\"is_writeable\"`\n}\n\n// @ID infoWhoami\n// @Summary Get information about current session\n// @Success 200 {object} WhoAmIResponse\n// @Router /info/whoami [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) WhoamiHandler(c *gin.Context) {\n\tsessionUser := utils.GetSession(c)\n\tresp := WhoAmIResponse{\n\t\tDisplayName: sessionUser.DisplayName,\n\t\tIsShareable: sessionUser.IsShareable,\n\t\tIsWriteable: sessionUser.IsWriteable,\n\t}\n\tc.JSON(http.StatusOK, resp)\n}\n\n// @ID infoListDatabases\n// @Summary List all databases\n// @Success 200 {object} []string\n// @Router /info/databases [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) databasesHandler(c *gin.Context) {\n\ttype databaseSchemas struct {\n\t\tDatabases string `gorm:\"column:Database\"`\n\t}\n\tvar result []databaseSchemas\n\tdb := utils.GetTiDBConnection(c)\n\terr := db.Raw(\"SHOW DATABASES\").Scan(&result).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tstrs := []string{}\n\tfor _, v := range result {\n\t\tstrs = append(strs, strings.ToLower(v.Databases))\n\t}\n\tsort.Strings(strs)\n\tc.JSON(http.StatusOK, strs)\n}\n\ntype tableSchema struct {\n\tTableName string `gorm:\"column:TABLE_NAME\" json:\"table_name\"`\n\tTableID   string `gorm:\"column:TIDB_TABLE_ID\" json:\"table_id\"`\n}\n\n// @ID infoListTables\n// @Summary List tables by database name\n// @Success 200 {object} []tableSchema\n// @Router /info/tables [get]\n// @Param database_name query string false \"Database name\"\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) tablesHandler(c *gin.Context) {\n\tvar result []tableSchema\n\tdb := utils.GetTiDBConnection(c)\n\ttx := db.Select([]string{\"TABLE_NAME\", \"TIDB_TABLE_ID\"}).Table(\"INFORMATION_SCHEMA.TABLES\")\n\tdatabaseName := c.Query(\"database_name\")\n\n\tif databaseName != \"\" {\n\t\ttx = tx.Where(\"LOWER(TABLE_SCHEMA) = ?\", strings.ToLower(databaseName))\n\t}\n\n\terr := tx.Order(\"TABLE_NAME\").Scan(&result).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tresult = lo.Map(result, func(item tableSchema, _ int) tableSchema {\n\t\titem.TableName = strings.ToLower(item.TableName)\n\t\treturn item\n\t})\n\tc.JSON(http.StatusOK, result)\n}\n"
  },
  {
    "path": "pkg/apiserver/logsearch/models.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage logsearch\n\nimport (\n\t\"database/sql/driver\"\n\t\"encoding/json\"\n\t\"os\"\n\n\t\"github.com/pingcap/kvproto/pkg/diagnosticspb\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/model\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n)\n\ntype TaskState int\n\nconst (\n\tTaskStateRunning  TaskState = 1\n\tTaskStateFinished TaskState = 2\n\tTaskStateError    TaskState = 3\n)\n\ntype TaskGroupState int\n\nconst (\n\tTaskGroupStateRunning  TaskGroupState = 1\n\tTaskGroupStateFinished TaskGroupState = 2\n)\n\ntype LogLevel int32\n\nconst (\n\tLogLevelUnknown  LogLevel = 0\n\tLogLevelDebug    LogLevel = 1\n\tLogLevelInfo     LogLevel = 2\n\tLogLevelWarn     LogLevel = 3\n\tLogLevelTrace    LogLevel = 4\n\tLogLevelCritical LogLevel = 5\n\tLogLevelError    LogLevel = 6\n)\n\nvar PBLogLevelSlice = []diagnosticspb.LogLevel{\n\tdiagnosticspb.LogLevel(LogLevelUnknown),\n\tdiagnosticspb.LogLevel(LogLevelDebug),\n\tdiagnosticspb.LogLevel(LogLevelInfo),\n\tdiagnosticspb.LogLevel(LogLevelWarn),\n\tdiagnosticspb.LogLevel(LogLevelTrace),\n\tdiagnosticspb.LogLevel(LogLevelCritical),\n\tdiagnosticspb.LogLevel(LogLevelError),\n}\n\ntype SearchLogRequest struct {\n\tStartTime int64    `json:\"start_time\"`\n\tEndTime   int64    `json:\"end_time\"`\n\tMinLevel  LogLevel `json:\"min_level\"`\n\t// We use a string array to represent multiple CNF pattern sceniaor like:\n\t// SELECT * FROM t WHERE c LIKE '%s%' and c REGEXP '.*a.*' because\n\t// Golang and Rust don't support perl-like (?=re1)(?=re2)\n\tPatterns []string `json:\"patterns\"`\n}\n\nfunc (r *SearchLogRequest) ConvertToPB(target diagnosticspb.SearchLogRequest_Target) *diagnosticspb.SearchLogRequest {\n\tlevels := PBLogLevelSlice[r.MinLevel:]\n\treturn &diagnosticspb.SearchLogRequest{\n\t\tStartTime: r.StartTime,\n\t\tEndTime:   r.EndTime,\n\t\tLevels:    levels,\n\t\tPatterns:  r.Patterns,\n\t\tTarget:    target,\n\t}\n}\n\nfunc (r *SearchLogRequest) Scan(src interface{}) error {\n\treturn json.Unmarshal([]byte(src.(string)), r)\n}\n\nfunc (r *SearchLogRequest) Value() (driver.Value, error) {\n\tval, err := json.Marshal(r)\n\treturn string(val), err\n}\n\ntype TaskModel struct {\n\tID               uint                     `json:\"id\" gorm:\"primary_key\"`\n\tTaskGroupID      uint                     `json:\"task_group_id\" gorm:\"index\"`\n\tTarget           *model.RequestTargetNode `json:\"target\" gorm:\"embedded;embedded_prefix:target_\"`\n\tState            TaskState                `json:\"state\" gorm:\"index\"`\n\tLogStorePath     *string                  `json:\"log_store_path\" gorm:\"type:text\"`\n\tSlowLogStorePath *string                  `json:\"slow_log_store_path\" gorm:\"type:text\"`\n\tSize             int64                    `json:\"size\" gorm:\"index\"`\n\tError            *string                  `json:\"error\" gorm:\"type:text\"`\n}\n\nfunc (TaskModel) TableName() string {\n\treturn \"log_search_tasks\"\n}\n\n// Note: this function does not save model itself.\nfunc (task *TaskModel) RemoveDataAndPreview(db *dbstore.DB) {\n\tif task.LogStorePath != nil {\n\t\t_ = os.RemoveAll(*task.LogStorePath)\n\t\ttask.LogStorePath = nil\n\t}\n\tdb.Where(\"task_id = ?\", task.ID).Delete(&PreviewModel{})\n}\n\ntype TaskGroupModel struct {\n\tID            uint                          `json:\"id\" gorm:\"primary_key\"`\n\tSearchRequest *SearchLogRequest             `json:\"search_request\" gorm:\"type:text\"`\n\tState         TaskGroupState                `json:\"state\" gorm:\"index\"`\n\tTargetStats   model.RequestTargetStatistics `json:\"target_stats\" gorm:\"embedded;embedded_prefix:target_stats_\"`\n\tLogStoreDir   *string                       `json:\"log_store_dir\" gorm:\"type:text\"`\n}\n\nfunc (TaskGroupModel) TableName() string {\n\treturn \"log_search_task_groups\"\n}\n\nfunc (tg *TaskGroupModel) Delete(db *dbstore.DB) {\n\tif tg.LogStoreDir != nil {\n\t\t_ = os.RemoveAll(*tg.LogStoreDir)\n\t}\n\tdb.Where(\"task_group_id = ?\", tg.ID).Delete(&PreviewModel{})\n\tdb.Where(\"task_group_id = ?\", tg.ID).Delete(&TaskModel{})\n\tdb.Where(\"id = ?\", tg.ID).Delete(&TaskGroupModel{})\n}\n\ntype PreviewModel struct {\n\tID          uint                   `json:\"id\" grom:\"primary_key\"`\n\tTaskID      uint                   `json:\"task_id\" gorm:\"index:task\"`\n\tTaskGroupID uint                   `json:\"task_group_id\" gorm:\"index:task_group\"`\n\tTime        int64                  `json:\"time\" gorm:\"index:task,task_group\"`\n\tLevel       diagnosticspb.LogLevel `json:\"level\" gorm:\"type:integer\" swaggertype:\"integer\"`\n\tMessage     string                 `json:\"message\" gorm:\"type:text\"`\n}\n\nfunc (PreviewModel) TableName() string {\n\treturn \"log_previews\"\n}\n\nfunc autoMigrate(db *dbstore.DB) error {\n\treturn db.AutoMigrate(&TaskModel{}, &TaskGroupModel{}, &PreviewModel{})\n}\n\nfunc cleanupAllTasks(db *dbstore.DB) {\n\tvar taskGroups []*TaskGroupModel\n\tdb.Find(&taskGroups)\n\tfor _, tg := range taskGroups {\n\t\ttg.Delete(db)\n\t}\n}\n"
  },
  {
    "path": "pkg/apiserver/logsearch/pack.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage logsearch\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n\t\"github.com/pingcap/tidb-dashboard/util/ziputil\"\n)\n\nfunc serveTaskForDownload(task *TaskModel, c *gin.Context) {\n\tlogPath := task.LogStorePath\n\tif logPath == nil {\n\t\tlogPath = task.SlowLogStorePath\n\t}\n\tif logPath == nil {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"Log is not ready\"))\n\t\treturn\n\t}\n\tc.FileAttachment(*logPath, fmt.Sprintf(\"logs-%s.zip\", task.Target.FileName()))\n}\n\nfunc serveMultipleTaskForDownload(tasks []*TaskModel, c *gin.Context) {\n\tfilePaths := make([]string, 0, len(tasks))\n\tfor _, task := range tasks {\n\t\tlogPath := task.LogStorePath\n\t\tif logPath == nil {\n\t\t\tlogPath = task.SlowLogStorePath\n\t\t}\n\t\tif logPath == nil {\n\t\t\trest.Error(c, rest.ErrBadRequest.New(\"Some logs are not available\"))\n\t\t\treturn\n\t\t}\n\t\tfilePaths = append(filePaths, *logPath)\n\t}\n\n\tc.Writer.Header().Set(\"Content-type\", \"application/octet-stream\")\n\tc.Writer.Header().Set(\"Content-Disposition\", \"attachment; filename=\\\"logs.zip\\\"\")\n\terr := ziputil.WriteZipFromFiles(c.Writer, filePaths, false)\n\tif err != nil {\n\t\tlog.Error(\"Stream zip pack failed\", zap.Error(err))\n\t}\n}\n"
  },
  {
    "path": "pkg/apiserver/logsearch/scheduler.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage logsearch\n\nimport (\n\t\"sync\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n)\n\nconst (\n\tTaskMaxPreviewLines      = 500\n\tTaskGroupMaxPreviewLines = 5000\n)\n\ntype Scheduler struct {\n\trunningTaskGroups sync.Map\n\tservice           *Service\n}\n\nfunc NewScheduler(service *Service) *Scheduler {\n\treturn &Scheduler{\n\t\trunningTaskGroups: sync.Map{},\n\t\tservice:           service,\n\t}\n}\n\nfunc (s *Scheduler) AsyncStart(taskGroupModel *TaskGroupModel, tasksModel []*TaskModel) bool {\n\tlog.Debug(\"Scheduler start task group\", zap.Uint(\"task_group_id\", taskGroupModel.ID))\n\n\tpreviewsLinesPerTask := min(TaskGroupMaxPreviewLines/len(tasksModel), TaskMaxPreviewLines)\n\n\ttaskGroup := &TaskGroup{\n\t\tservice:                s.service,\n\t\tmodel:                  taskGroupModel,\n\t\ttasks:                  nil, // Tasks are created only after successfully adding to the sync map.\n\t\ttasksMu:                sync.Mutex{},\n\t\tmaxPreviewLinesPerTask: previewsLinesPerTask,\n\t}\n\t_, alreadyRunning := s.runningTaskGroups.LoadOrStore(taskGroup.model.ID, taskGroup)\n\tif alreadyRunning {\n\t\tlog.Warn(\"Scheduler start task group failed, task group is already running\", zap.Uint(\"task_group_id\", taskGroupModel.ID))\n\t\treturn false\n\t}\n\n\ttaskGroup.InitTasks(s.service.lifecycleCtx, tasksModel)\n\n\tgo func() {\n\t\ttaskGroup.SyncRun()\n\t\ts.runningTaskGroups.Delete(taskGroup.model.ID)\n\n\t\tlog.Debug(\"Scheduler task group finished\", zap.Uint(\"task_group_id\", taskGroupModel.ID))\n\t}()\n\n\treturn true\n}\n\nfunc (s *Scheduler) AsyncAbort(taskGroupID uint) bool {\n\tlog.Debug(\"Scheduler abort task group\", zap.Uint(\"task_group_id\", taskGroupID))\n\n\tv, ok := s.runningTaskGroups.Load(taskGroupID)\n\tif !ok {\n\t\tlog.Warn(\"Scheduler abort task group failed, task group is not running\", zap.Uint(\"task_group_id\", taskGroupID))\n\t\treturn false\n\t}\n\ttaskGroup := v.(*TaskGroup)\n\ttaskGroup.AbortAll()\n\treturn true\n}\n"
  },
  {
    "path": "pkg/apiserver/logsearch/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage logsearch\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/model\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\ntype Service struct {\n\t// FIXME: Use fx.In\n\tlifecycleCtx context.Context\n\n\tconfig            *config.Config\n\tlogStoreDirectory string\n\tdb                *dbstore.DB\n\tscheduler         *Scheduler\n}\n\nfunc NewService(lc fx.Lifecycle, config *config.Config, db *dbstore.DB) *Service {\n\tdir := config.TempDir\n\tif dir == \"\" {\n\t\tvar err error\n\t\tdir, err = os.MkdirTemp(\"\", \"dashboard-logs\")\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"Failed to create directory for storing logs\", zap.Error(err))\n\t\t}\n\t}\n\terr := autoMigrate(db)\n\tif err != nil {\n\t\tlog.Fatal(\"Failed to initialize database\", zap.Error(err))\n\t}\n\tcleanupAllTasks(db)\n\n\tservice := &Service{\n\t\tconfig:            config,\n\t\tlogStoreDirectory: dir,\n\t\tdb:                db,\n\t\tscheduler:         nil, // will be filled after scheduler is created\n\t}\n\tscheduler := NewScheduler(service)\n\tservice.scheduler = scheduler\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tservice.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn service\n}\n\nfunc RegisterRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/logs\")\n\t{\n\t\tendpoint.GET(\"/download\", s.DownloadLogs)\n\t\tendpoint.Use(auth.MWAuthRequired())\n\t\t{\n\t\t\tendpoint.GET(\"/download/acquire_token\", s.GetDownloadToken)\n\t\t\tendpoint.PUT(\"/taskgroup\", s.CreateTaskGroup)\n\t\t\tendpoint.GET(\"/taskgroups\", s.GetAllTaskGroups)\n\t\t\tendpoint.GET(\"/taskgroups/:id\", s.GetTaskGroup)\n\t\t\tendpoint.GET(\"/taskgroups/:id/preview\", s.GetTaskGroupPreview)\n\t\t\tendpoint.POST(\"/taskgroups/:id/retry\", s.RetryTask)\n\t\t\tendpoint.POST(\"/taskgroups/:id/cancel\", s.CancelTask)\n\t\t\tendpoint.DELETE(\"/taskgroups/:id\", s.DeleteTaskGroup)\n\t\t}\n\t}\n}\n\ntype CreateTaskGroupRequest struct {\n\tRequest SearchLogRequest          `json:\"request\" binding:\"required\"`\n\tTargets []model.RequestTargetNode `json:\"targets\" binding:\"required\"`\n}\n\ntype TaskGroupResponse struct {\n\tTaskGroup TaskGroupModel `json:\"task_group\"`\n\tTasks     []*TaskModel   `json:\"tasks\"`\n}\n\n// @Summary Create and run a new log search task group\n// @Param request body CreateTaskGroupRequest true \"Request body\"\n// @Security JwtAuth\n// @Success 200 {object} TaskGroupResponse\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /logs/taskgroup [put]\nfunc (s *Service) CreateTaskGroup(c *gin.Context) {\n\tvar req CreateTaskGroupRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tif len(req.Targets) == 0 {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"Expect at least 1 target\"))\n\t\treturn\n\t}\n\tstats := model.NewRequestTargetStatisticsFromArray(&req.Targets)\n\ttaskGroup := TaskGroupModel{\n\t\tSearchRequest: &req.Request,\n\t\tState:         TaskGroupStateRunning,\n\t\tTargetStats:   stats,\n\t}\n\tif err := s.db.Create(&taskGroup).Error; err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\ttasks := make([]*TaskModel, 0, len(req.Targets))\n\tfor _, t := range req.Targets {\n\t\ttarget := t\n\t\ttask := &TaskModel{\n\t\t\tTaskGroupID: taskGroup.ID,\n\t\t\tTarget:      &target,\n\t\t\tState:       TaskStateRunning,\n\t\t}\n\t\t// Ignore task creation errors\n\t\ts.db.Create(task)\n\t\ttasks = append(tasks, task)\n\t}\n\tif !s.scheduler.AsyncStart(&taskGroup, tasks) {\n\t\tlog.Error(\"Failed to start task group\", zap.Uint(\"task_group_id\", taskGroup.ID))\n\t}\n\tresp := TaskGroupResponse{\n\t\tTaskGroup: taskGroup,\n\t\tTasks:     tasks,\n\t}\n\tc.JSON(http.StatusOK, resp)\n}\n\n// @Summary List all log search task groups\n// @Security JwtAuth\n// @Success 200 {array} TaskGroupModel\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /logs/taskgroups [get]\nfunc (s *Service) GetAllTaskGroups(c *gin.Context) {\n\tvar taskGroups []*TaskGroupModel\n\terr := s.db.Find(&taskGroups).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tc.JSON(http.StatusOK, taskGroups)\n}\n\n// @Summary List tasks in a log search task group\n// @Param id path string true \"Task Group ID\"\n// @Security JwtAuth\n// @Success 200 {object} TaskGroupResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /logs/taskgroups/{id} [get]\nfunc (s *Service) GetTaskGroup(c *gin.Context) {\n\ttaskGroupID := c.Param(\"id\")\n\tvar taskGroup TaskGroupModel\n\tvar tasks []*TaskModel\n\terr := s.db.First(&taskGroup, \"id = ?\", taskGroupID).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\terr = s.db.Where(\"task_group_id = ?\", taskGroupID).Find(&tasks).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tresp := TaskGroupResponse{\n\t\tTaskGroup: taskGroup,\n\t\tTasks:     tasks,\n\t}\n\tc.JSON(http.StatusOK, resp)\n}\n\n// @Summary Preview a log search task group\n// @Param id path string true \"task group id\"\n// @Security JwtAuth\n// @Success 200 {array} PreviewModel\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /logs/taskgroups/{id}/preview [get]\nfunc (s *Service) GetTaskGroupPreview(c *gin.Context) {\n\ttaskGroupID := c.Param(\"id\")\n\tvar lines []PreviewModel\n\terr := s.db.\n\t\tWhere(\"task_group_id = ?\", taskGroupID).\n\t\tOrder(\"time\").\n\t\tLimit(TaskMaxPreviewLines).\n\t\tFind(&lines).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, lines)\n}\n\n// @Summary Retry failed tasks in a log search task group\n// @Param id path string true \"task group id\"\n// @Security JwtAuth\n// @Success 200 {object} rest.EmptyResponse\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /logs/taskgroups/{id}/retry [post]\nfunc (s *Service) RetryTask(c *gin.Context) {\n\ttaskGroupID, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\t// Currently we can only retry finished task group.\n\ttaskGroup := TaskGroupModel{}\n\tif err := s.db.Where(\"id = ? AND state = ?\", taskGroupID, TaskGroupStateFinished).First(&taskGroup).Error; err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\ttasks := make([]*TaskModel, 0)\n\tif err := s.db.Where(\"task_group_id = ? AND state = ?\", taskGroupID, TaskStateError).Find(&tasks).Error; err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tif len(tasks) == 0 {\n\t\t// No tasks to retry\n\t\tc.JSON(http.StatusOK, rest.EmptyResponse{})\n\t\treturn\n\t}\n\n\t// Reset task status\n\ttaskGroup.State = TaskGroupStateRunning\n\ts.db.Save(&taskGroup)\n\tfor _, task := range tasks {\n\t\ttask.Error = nil\n\t\ttask.State = TaskStateRunning\n\t\ts.db.Save(task)\n\t}\n\n\tif !s.scheduler.AsyncStart(&taskGroup, tasks) {\n\t\tlog.Error(\"Failed to retry task group\", zap.Uint(\"task_group_id\", taskGroup.ID))\n\t}\n\tc.JSON(http.StatusOK, rest.EmptyResponse{})\n}\n\n// @Summary Cancel running tasks in a log search task group\n// @Param id path string true \"task group id\"\n// @Security JwtAuth\n// @Success 200 {object} rest.EmptyResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 400 {object} rest.ErrorResponse\n// @Router /logs/taskgroups/{id}/cancel [post]\nfunc (s *Service) CancelTask(c *gin.Context) {\n\ttaskGroupID, err := strconv.Atoi(c.Param(\"id\"))\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\ttaskGroup := TaskGroupModel{}\n\terr = s.db.First(&taskGroup, taskGroupID).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tif taskGroup.State != TaskGroupStateRunning {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"Task is not running\"))\n\t\treturn\n\t}\n\ts.scheduler.AsyncAbort(uint(taskGroupID))\n\tc.JSON(http.StatusOK, rest.EmptyResponse{})\n}\n\n// @Summary Delete a log search task group\n// @Param id path string true \"task group id\"\n// @Security JwtAuth\n// @Success 200 {object} rest.EmptyResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /logs/taskgroups/{id} [delete]\nfunc (s *Service) DeleteTaskGroup(c *gin.Context) {\n\ttaskGroupID := c.Param(\"id\")\n\ttaskGroup := TaskGroupModel{}\n\terr := s.db.Where(\"id = ? AND state != ?\", taskGroupID, TaskGroupStateRunning).First(&taskGroup).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\ttaskGroup.Delete(s.db)\n\tc.JSON(http.StatusOK, rest.EmptyResponse{})\n}\n\n// @Summary Generate a download token for downloading logs\n// @Produce plain\n// @Param id query []string false \"task id\" collectionFormat(csv)\n// @Security JwtAuth\n// @Success 200 {string} string \"xxx\"\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Router /logs/download/acquire_token [get]\nfunc (s *Service) GetDownloadToken(c *gin.Context) {\n\tids := c.QueryArray(\"id\")\n\tstr := strings.Join(ids, \",\")\n\ttoken, err := utils.NewJWTString(\"logs/download\", str)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.String(http.StatusOK, token)\n}\n\n// @Summary Download logs\n// @Produce application/x-tar,application/zip\n// @Param token query string true \"download token\"\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /logs/download [get]\nfunc (s *Service) DownloadLogs(c *gin.Context) {\n\ttoken := c.Query(\"token\")\n\tstr, err := utils.ParseJWTString(\"logs/download\", token)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tids := strings.Split(str, \",\")\n\ttasks := make([]*TaskModel, 0, len(ids))\n\tfor _, id := range ids {\n\t\tvar task TaskModel\n\t\tif s.db.\n\t\t\tWhere(\"id = ? AND state = ?\", id, TaskStateFinished).\n\t\t\tFirst(&task).\n\t\t\tError == nil {\n\t\t\ttasks = append(tasks, &task)\n\t\t\t// Ignore errors silently\n\t\t}\n\t}\n\n\tswitch len(tasks) {\n\tcase 0:\n\t\trest.Error(c, rest.ErrBadRequest.New(\"Expect at least 1 target\"))\n\tcase 1:\n\t\tserveTaskForDownload(tasks[0], c)\n\tdefault:\n\t\tserveMultipleTaskForDownload(tasks, c)\n\t}\n}\n"
  },
  {
    "path": "pkg/apiserver/logsearch/task.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage logsearch\n\nimport (\n\t\"archive/zip\"\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"math\"\n\t\"net\"\n\t\"os\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/pingcap/kvproto/pkg/diagnosticspb\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\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/pingcap/tidb-dashboard/pkg/apiserver/model\"\n)\n\n// MaxRecvMsgSize set max gRPC receive message size received from server. If any message size is larger than\n// current value, an error will be reported from gRPC.\nvar MaxRecvMsgSize = math.MaxInt64 - 1\n\ntype TaskGroup struct {\n\tservice                *Service\n\tmodel                  *TaskGroupModel\n\ttasks                  []*Task\n\ttasksMu                sync.Mutex\n\tmaxPreviewLinesPerTask int\n}\n\nfunc (tg *TaskGroup) InitTasks(ctx context.Context, taskModels []*TaskModel) {\n\t// Tasks are assigned after inserting into scheduler, thus it has a chance to run parallel with Abort.\n\ttg.tasksMu.Lock()\n\tdefer tg.tasksMu.Unlock()\n\n\tif tg.tasks != nil {\n\t\tpanic(\"LogSearchTaskGroup's task is already initialized\")\n\t}\n\ttg.tasks = make([]*Task, 0, len(taskModels))\n\tfor _, taskModel := range taskModels {\n\t\tctx, cancel := context.WithCancel(ctx)\n\t\ttg.tasks = append(tg.tasks, &Task{\n\t\t\ttaskGroup: tg,\n\t\t\tmodel:     taskModel,\n\t\t\tctx:       ctx,\n\t\t\tcancel:    cancel,\n\t\t})\n\t}\n}\n\nfunc (tg *TaskGroup) SyncRun() {\n\tlog.Debug(\"LogSearchTaskGroup start\", zap.Uint(\"task_group_id\", tg.model.ID))\n\n\t// Create log directory\n\tdir := path.Join(tg.service.logStoreDirectory, strconv.Itoa(int(tg.model.ID)))\n\terr := os.MkdirAll(dir, 0o777) // #nosec\n\tif err == nil {\n\t\ttg.model.LogStoreDir = &dir\n\t\ttg.service.db.Save(tg.model)\n\t}\n\n\twg := sync.WaitGroup{}\n\tfor _, task := range tg.tasks {\n\t\twg.Add(1)\n\t\tgo func(task *Task) {\n\t\t\ttask.SyncRun()\n\t\t\twg.Done()\n\t\t}(task)\n\t}\n\twg.Wait()\n\n\tlog.Debug(\"LogSearchTaskGroup finished\", zap.Uint(\"task_group_id\", tg.model.ID))\n\ttg.model.State = TaskGroupStateFinished\n\ttg.service.db.Save(tg.model)\n}\n\n// This function is multi-thread safe.\nfunc (tg *TaskGroup) AbortAll() {\n\tlog.Debug(\"LogSearchTaskGroup abort\", zap.Uint(\"task_group_id\", tg.model.ID))\n\n\ttg.tasksMu.Lock()\n\tdefer tg.tasksMu.Unlock()\n\n\tfor _, task := range tg.tasks {\n\t\ttask.Abort()\n\t}\n}\n\ntype Task struct {\n\ttaskGroup *TaskGroup\n\tmodel     *TaskModel\n\tctx       context.Context\n\tcancel    context.CancelFunc\n}\n\nfunc (t *Task) String() string {\n\treturn fmt.Sprintf(\"LogSearchTask { id = %d, target = %s, task_group_id = %d }\", t.model.ID, t.model.Target, t.taskGroup.model.ID)\n}\n\n// This function is multi-thread safe.\nfunc (t *Task) Abort() {\n\tlog.Debug(\"LogSearchTask abort\", zap.Any(\"task\", t))\n\n\tif t.cancel != nil {\n\t\tt.cancel()\n\t}\n}\n\nfunc (t *Task) setError(err error) {\n\terrStr := err.Error()\n\tt.model.Error = &errStr\n}\n\nfunc (t *Task) accumulateLogSize(path *string) {\n\tif path != nil {\n\t\tstat, err := os.Stat(*path)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"Can NOT fetch log file size for LogSearchTask\",\n\t\t\t\tzap.String(\"dir\", *path),\n\t\t\t\tzap.Any(\"task\", t),\n\t\t\t\tzap.String(\"err\", err.Error()),\n\t\t\t)\n\t\t} else {\n\t\t\tt.model.Size += stat.Size()\n\t\t}\n\t}\n}\n\nfunc (t *Task) SyncRun() {\n\tdefer func() {\n\t\tif t.model.Error != nil {\n\t\t\tlog.Warn(\"LogSearchTask stopped with error\",\n\t\t\t\tzap.Any(\"task\", t),\n\t\t\t\tzap.String(\"err\", *t.model.Error),\n\t\t\t)\n\t\t\tt.model.RemoveDataAndPreview(t.taskGroup.service.db)\n\t\t\tt.model.State = TaskStateError\n\t\t\tt.taskGroup.service.db.Save(t.model)\n\t\t\treturn\n\t\t}\n\t\tt.model.State = TaskStateFinished\n\t\tt.accumulateLogSize(t.model.LogStorePath)\n\t\tt.accumulateLogSize(t.model.SlowLogStorePath)\n\t\tlog.Debug(\"LogSearchTask finished\", zap.Any(\"task\", t))\n\t\tt.taskGroup.service.db.Save(t.model)\n\t}()\n\n\tlog.Debug(\"LogSearchTask start\", zap.Any(\"task\", t))\n\n\tif t.taskGroup.model.LogStoreDir == nil {\n\t\tt.setError(fmt.Errorf(\"failed to create temporary directory\"))\n\t\treturn\n\t}\n\n\tsecureOpt := grpc.WithTransportCredentials(insecure.NewCredentials())\n\tif t.taskGroup.service.config.ClusterTLSConfig != nil {\n\t\tcreds := credentials.NewTLS(t.taskGroup.service.config.ClusterTLSConfig)\n\t\tsecureOpt = grpc.WithTransportCredentials(creds)\n\t}\n\n\tconn, err := grpc.Dial(net.JoinHostPort(t.model.Target.IP, strconv.Itoa(t.model.Target.Port)), //nolint:staticcheck // Dial is deprecated, but we use it here temporarily\n\t\tsecureOpt,\n\t\tgrpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(MaxRecvMsgSize)),\n\t)\n\tif err != nil {\n\t\tt.setError(err)\n\t\treturn\n\t}\n\tdefer conn.Close()\n\n\tcli := diagnosticspb.NewDiagnosticsClient(conn)\n\tt.searchLog(cli, diagnosticspb.SearchLogRequest_Normal)\n\t// Only TiKV support searching slow log now\n\tif t.model.Target.Kind == model.NodeKindTiKV {\n\t\tt.searchLog(cli, diagnosticspb.SearchLogRequest_Slow)\n\t}\n}\n\nfunc (t *Task) searchLog(client diagnosticspb.DiagnosticsClient, targetType diagnosticspb.SearchLogRequest_Target) {\n\tif t.model.Error != nil {\n\t\treturn\n\t}\n\treq := t.taskGroup.model.SearchRequest.ConvertToPB(targetType)\n\tpatterns := make([]string, len(req.Patterns))\n\tfor i, p := range req.Patterns {\n\t\tpatterns[i] = \"(?i)\" + p\n\t}\n\treq.Patterns = patterns\n\tstream, err := client.SearchLog(t.ctx, req)\n\tif err != nil {\n\t\tt.setError(err)\n\t\treturn\n\t}\n\n\t// Create zip file for the log in the log directory\n\tfileName := t.model.Target.FileName()\n\tif targetType == diagnosticspb.SearchLogRequest_Slow {\n\t\tfileName = fileName + \"-slow\"\n\t}\n\tsavedPath := path.Join(*t.taskGroup.model.LogStoreDir, fileName+\".zip\")\n\tf, err := os.Create(filepath.Clean(savedPath))\n\tif err != nil {\n\t\tt.setError(err)\n\t\treturn\n\t}\n\tdefer f.Close() // #nosec\n\n\t// TODO: Could we use a memory buffer for this and flush the writer regularly to avoid OOM.\n\t// This might perform an faster processing. This could also avoid creating an empty .zip\n\t// firstly even if the searching result is empty.\n\tzw := zip.NewWriter(f)\n\tdefer zw.Close()\n\tdefer zw.Flush()\n\n\twriter, err := zw.Create(fileName + \".log\")\n\tif err != nil {\n\t\tt.setError(err)\n\t\treturn\n\t}\n\n\tbufWriter := bufio.NewWriterSize(writer, 16*1024*1024) // 16M buffer size\n\tdefer bufWriter.Flush()\n\n\tt.model.State = TaskStateRunning\n\tpreviewLogLinesCount := 0\n\tfor {\n\t\tres, err := stream.Recv()\n\t\tif err != nil {\n\t\t\tif err != io.EOF {\n\t\t\t\tt.setError(err)\n\t\t\t}\n\t\t\tif previewLogLinesCount != 0 {\n\t\t\t\tif targetType == diagnosticspb.SearchLogRequest_Normal {\n\t\t\t\t\tt.model.LogStorePath = &savedPath\n\t\t\t\t} else {\n\t\t\t\t\tt.model.SlowLogStorePath = &savedPath\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\tfor _, msg := range res.Messages {\n\t\t\tline := logMessageToString(msg)\n\t\t\t_, err := bufWriter.Write(*(*[]byte)(unsafe.Pointer(&line))) // #nosec\n\t\t\tif err != nil {\n\t\t\t\tt.setError(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif previewLogLinesCount < t.taskGroup.maxPreviewLinesPerTask {\n\t\t\t\tt.taskGroup.service.db.Create(&PreviewModel{\n\t\t\t\t\tTaskID:      t.model.ID,\n\t\t\t\t\tTaskGroupID: t.taskGroup.model.ID,\n\t\t\t\t\tTime:        msg.Time,\n\t\t\t\t\tLevel:       msg.Level,\n\t\t\t\t\tMessage:     msg.Message,\n\t\t\t\t})\n\t\t\t\tpreviewLogLinesCount++\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc logMessageToString(msg *diagnosticspb.LogMessage) string {\n\ttimeStr := time.Unix(0, msg.Time*int64(time.Millisecond)).Format(\"2006/01/02 15:04:05.000 -07:00\")\n\treturn fmt.Sprintf(\"[%s] [%s] %s\\n\", timeStr, msg.Level.String(), msg.Message)\n}\n"
  },
  {
    "path": "pkg/apiserver/metrics/prom_resolve.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage metrics\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/topology\"\n)\n\nconst (\n\tpromCacheTTL = time.Second * 5\n)\n\ntype promAddressCacheEntity struct {\n\taddress string\n\tcacheAt time.Time\n}\n\ntype pdServerConfig struct {\n\tMetricStorage string `json:\"metric-storage\"`\n}\n\ntype pdConfig struct {\n\tPdServer pdServerConfig `json:\"pd-server\"`\n}\n\n// Check and normalize a Prometheus address supplied by user.\nfunc normalizeCustomizedPromAddress(addr string) (string, error) {\n\tif !strings.HasPrefix(addr, \"http://\") && !strings.HasPrefix(addr, \"https://\") {\n\t\taddr = \"http://\" + addr\n\t}\n\tu, err := url.Parse(addr)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"invalid Prometheus address format: %v\", err)\n\t}\n\tif len(u.Host) == 0 || len(u.Scheme) == 0 {\n\t\treturn \"\", fmt.Errorf(\"invalid Prometheus address format\")\n\t}\n\t// Normalize the address, remove unnecessary parts.\n\taddr = fmt.Sprintf(\"%s://%s%s\", u.Scheme, u.Host, strings.TrimSuffix(u.Path, \"/\"))\n\treturn addr, nil\n}\n\n// Resolve the customized Prometheus address in PD config. If it is not configured, empty address will be returned.\n// The returned address must be valid. If an invalid Prometheus address is configured, errors will be returned.\nfunc (s *Service) resolveCustomizedPromAddress(acceptInvalidAddr bool) (string, error) {\n\t// Lookup \"metric-storage\" cluster config in PD.\n\tdata, err := s.params.PDClient.SendGetRequest(\"/config\")\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tvar config pdConfig\n\tif err := json.Unmarshal(data, &config); err != nil {\n\t\treturn \"\", err\n\t}\n\taddr := config.PdServer.MetricStorage\n\tif len(addr) > 0 {\n\t\tif acceptInvalidAddr {\n\t\t\treturn addr, nil\n\t\t}\n\t\t// Verify whether address is valid. If not valid, throw error.\n\t\taddr, err = normalizeCustomizedPromAddress(addr)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\treturn addr, nil\n\t}\n\treturn \"\", nil\n}\n\n// Resolve the Prometheus address recorded by deployment tools in the `/topology` etcd namespace.\n// If the address is not recorded (for example, when Prometheus is not deployed), empty address will be returned.\nfunc (s *Service) resolveDeployedPromAddress() (string, error) {\n\tpi, err := topology.FetchPrometheusTopology(s.lifecycleCtx, s.params.EtcdClient)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif pi == nil {\n\t\treturn \"\", nil\n\t}\n\treturn fmt.Sprintf(\"http://%s\", net.JoinHostPort(pi.IP, strconv.Itoa(int(pi.Port)))), nil\n}\n\n// Resolve the final Prometheus address. When user has customized an address, this address is returned. Otherwise,\n// address recorded by deployment tools will be returned.\n// If neither custom address nor deployed address is available, empty address will be returned.\nfunc (s *Service) resolveFinalPromAddress() (string, error) {\n\taddr, err := s.resolveCustomizedPromAddress(false)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif addr != \"\" {\n\t\treturn addr, nil\n\t}\n\taddr, err = s.resolveDeployedPromAddress()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif addr != \"\" {\n\t\treturn addr, nil\n\t}\n\treturn \"\", nil\n}\n\n// Get the final Prometheus address from cache. If cache item is not valid, the address will be resolved from PD\n// or etcd and then the cache will be updated.\nfunc (s *Service) getPromAddressFromCache() (string, error) {\n\tfn := func() (string, error) {\n\t\t// Check whether cache is valid, and use the cache if possible.\n\t\tif v := s.promAddressCache.Load(); v != nil {\n\t\t\tentity := v.(*promAddressCacheEntity)\n\t\t\tif entity.cacheAt.Add(promCacheTTL).After(time.Now()) {\n\t\t\t\treturn entity.address, nil\n\t\t\t}\n\t\t}\n\n\t\t// Cache is not valid, read from PD and etcd.\n\t\taddr, err := s.resolveFinalPromAddress()\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\n\t\ts.promAddressCache.Store(&promAddressCacheEntity{\n\t\t\taddress: addr,\n\t\t\tcacheAt: time.Now(),\n\t\t})\n\n\t\treturn addr, nil\n\t}\n\n\tresolveResult, err, _ := s.promRequestGroup.Do(\"any_key\", func() (interface{}, error) {\n\t\treturn fn()\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn resolveResult.(string), nil\n}\n\n// Set the customized Prometheus address. Address can be empty or a valid address like `http://host:port`.\n// If address is set to empty, address from deployment tools will be used later.\nfunc (s *Service) setCustomPromAddress(addr string) (string, error) {\n\tvar err error\n\tif len(addr) > 0 {\n\t\taddr, err = normalizeCustomizedPromAddress(addr)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t}\n\n\tbody := make(map[string]interface{})\n\tbody[\"metric-storage\"] = addr\n\tbodyJSON, err := json.Marshal(&body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t_, err = s.params.PDClient.SendPostRequest(\"/config\", bytes.NewBuffer(bodyJSON))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Invalidate cache immediately.\n\ts.promAddressCache.Store(&promAddressCacheEntity{\n\t\taddress: addr,\n\t\tcacheAt: time.Time{},\n\t})\n\n\treturn addr, nil\n}\n"
  },
  {
    "path": "pkg/apiserver/metrics/prom_resolve_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage metrics\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\n// https://github.com/pingcap/tidb-dashboard/issues/1560\nfunc Test_normalizeCustomizedPromAddress(t *testing.T) {\n\taddr, err := normalizeCustomizedPromAddress(\"http://infra-tidb-monitoring-shadow2-prod-0a01da41:9090\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"http://infra-tidb-monitoring-shadow2-prod-0a01da41:9090\", addr)\n\n\taddr, err = normalizeCustomizedPromAddress(\"http://infra-tidb-monitoring-shadow2-prod-0a01da41:9090/\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"http://infra-tidb-monitoring-shadow2-prod-0a01da41:9090\", addr)\n\n\taddr, err = normalizeCustomizedPromAddress(\"http://infra-tidb-monitoring-shadow2-prod-0a01da41:9090/_/tsdb/\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"http://infra-tidb-monitoring-shadow2-prod-0a01da41:9090/_/tsdb\", addr)\n}\n"
  },
  {
    "path": "pkg/apiserver/metrics/router.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage metrics\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strconv\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\ntype QueryRequest struct {\n\tStartTimeSec int    `json:\"start_time_sec\" form:\"start_time_sec\"`\n\tEndTimeSec   int    `json:\"end_time_sec\" form:\"end_time_sec\"`\n\tStepSec      int    `json:\"step_sec\" form:\"step_sec\"`\n\tQuery        string `json:\"query\" form:\"query\"`\n}\n\ntype QueryResponse struct {\n\tStatus string                 `json:\"status\"`\n\tData   map[string]interface{} `json:\"data\"`\n}\n\nfunc RegisterRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/metrics\")\n\tendpoint.Use(auth.MWAuthRequired())\n\tendpoint.GET(\"/query\", s.queryMetrics)\n\tendpoint.GET(\"/prom_address\", s.getPromAddressConfig)\n\tendpoint.PUT(\"/prom_address\", auth.MWRequireWritePriv(), s.putCustomPromAddress)\n}\n\n// @Summary Query metrics\n// @Description Query metrics in the given range\n// @Param q query QueryRequest true \"Query\"\n// @Success 200 {object} QueryResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Security JwtAuth\n// @Router /metrics/query [get]\nfunc (s *Service) queryMetrics(c *gin.Context) {\n\tvar req QueryRequest\n\tif err := c.ShouldBindQuery(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\taddr, err := s.getPromAddressFromCache()\n\tif err != nil {\n\t\trest.Error(c, ErrLoadPrometheusAddressFailed.Wrap(err, \"Load prometheus address failed\"))\n\t\treturn\n\t}\n\tif addr == \"\" {\n\t\trest.Error(c, ErrPrometheusNotFound.New(\"Prometheus is not deployed in the cluster\"))\n\t\treturn\n\t}\n\n\tparams := url.Values{}\n\tparams.Add(\"query\", req.Query)\n\tparams.Add(\"start\", strconv.Itoa(req.StartTimeSec))\n\tparams.Add(\"end\", strconv.Itoa(req.EndTimeSec))\n\tparams.Add(\"step\", strconv.Itoa(req.StepSec))\n\n\turi := fmt.Sprintf(\"%s/api/v1/query_range?%s\", addr, params.Encode())\n\tpromReq, err := http.NewRequestWithContext(s.lifecycleCtx, http.MethodGet, uri, nil)\n\tif err != nil {\n\t\trest.Error(c, ErrPrometheusQueryFailed.Wrap(err, \"failed to build Prometheus request\"))\n\t\treturn\n\t}\n\n\tpromResp, err := s.params.HTTPClient.WithTimeout(defaultPromQueryTimeout).Do(promReq)\n\tif err != nil {\n\t\trest.Error(c, ErrPrometheusQueryFailed.Wrap(err, \"failed to send requests to Prometheus\"))\n\t\treturn\n\t}\n\n\tdefer promResp.Body.Close()\n\tif promResp.StatusCode != http.StatusOK {\n\t\trest.Error(c, ErrPrometheusQueryFailed.New(\"failed to query Prometheus\"))\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(promResp.Body)\n\tif err != nil {\n\t\trest.Error(c, ErrPrometheusQueryFailed.Wrap(err, \"failed to read Prometheus query result\"))\n\t\treturn\n\t}\n\n\tc.Data(promResp.StatusCode, promResp.Header.Get(\"content-type\"), body)\n}\n\ntype GetPromAddressConfigResponse struct {\n\tCustomizedAddr string `json:\"customized_addr\"`\n\tDeployedAddr   string `json:\"deployed_addr\"`\n}\n\n// @ID metricsGetPromAddress\n// @Summary Get the Prometheus address cluster config\n// @Success 200 {object} GetPromAddressConfigResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Security JwtAuth\n// @Router /metrics/prom_address [get]\nfunc (s *Service) getPromAddressConfig(c *gin.Context) {\n\tcAddr, err := s.resolveCustomizedPromAddress(true)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tdAddr, err := s.resolveDeployedPromAddress()\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, GetPromAddressConfigResponse{\n\t\tCustomizedAddr: cAddr,\n\t\tDeployedAddr:   dAddr,\n\t})\n}\n\ntype PutCustomPromAddressRequest struct {\n\tAddr string `json:\"address\"`\n}\n\ntype PutCustomPromAddressResponse struct {\n\tNormalizedAddr string `json:\"normalized_address\"`\n}\n\n// @ID metricsSetCustomPromAddress\n// @Summary Set or clear the customized Prometheus address\n// @Param request body PutCustomPromAddressRequest true \"Request body\"\n// @Success 200 {object} PutCustomPromAddressResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Security JwtAuth\n// @Router /metrics/prom_address [put]\nfunc (s *Service) putCustomPromAddress(c *gin.Context) {\n\tvar req PutCustomPromAddressRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tif s.params.Config.DisableCustomPromAddr && req.Addr != \"\" {\n\t\trest.Error(c, rest.ErrForbidden.New(\"custom prometheus address has been disabled\"))\n\t\treturn\n\t}\n\taddr, err := s.setCustomPromAddress(req.Addr)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, PutCustomPromAddressResponse{\n\t\tNormalizedAddr: addr,\n\t})\n}\n"
  },
  {
    "path": "pkg/apiserver/metrics/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage metrics\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/joomcode/errorx\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/atomic\"\n\t\"go.uber.org/fx\"\n\t\"golang.org/x/sync/singleflight\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/httpc\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n)\n\nvar (\n\tErrNS                          = errorx.NewNamespace(\"error.api.metrics\")\n\tErrLoadPrometheusAddressFailed = ErrNS.NewType(\"load_prom_address_failed\")\n\tErrPrometheusNotFound          = ErrNS.NewType(\"prom_not_found\")\n\tErrPrometheusQueryFailed       = ErrNS.NewType(\"prom_query_failed\")\n)\n\nconst (\n\tdefaultPromQueryTimeout = time.Second * 30\n)\n\ntype ServiceParams struct {\n\tfx.In\n\tConfig     *config.Config\n\tHTTPClient *httpc.Client\n\tEtcdClient *clientv3.Client\n\tPDClient   *pd.Client\n}\n\ntype Service struct {\n\tparams       ServiceParams\n\tlifecycleCtx context.Context\n\n\tpromRequestGroup singleflight.Group\n\tpromAddressCache atomic.Value\n}\n\nfunc NewService(lc fx.Lifecycle, p ServiceParams) *Service {\n\ts := &Service{params: p}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\ts.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn s\n}\n"
  },
  {
    "path": "pkg/apiserver/model/common_models.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage model\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype NodeKind string\n\nconst (\n\tNodeKindTiDB       NodeKind = \"tidb\"\n\tNodeKindTiKV       NodeKind = \"tikv\"\n\tNodeKindPD         NodeKind = \"pd\"\n\tNodeKindTiFlash    NodeKind = \"tiflash\"\n\tNodeKindTiCDC      NodeKind = \"ticdc\"\n\tNodeKindTiProxy    NodeKind = \"tiproxy\"\n\tNodeKindTSO        NodeKind = \"tso\"\n\tNodeKindScheduling NodeKind = \"scheduling\"\n)\n\ntype RequestTargetNode struct {\n\tKind        NodeKind `json:\"kind\" gorm:\"size:8\" example:\"tidb\"`\n\tDisplayName string   `json:\"display_name\" gorm:\"size:32\" example:\"127.0.0.1:4000\"`\n\tIP          string   `json:\"ip\" gorm:\"size:32\" example:\"127.0.0.1\"`\n\tPort        int      `json:\"port\" example:\"4000\"`\n}\n\nfunc (n *RequestTargetNode) String() string {\n\treturn fmt.Sprintf(\"%s(%s)\", n.Kind, n.DisplayName)\n}\n\nfunc (n *RequestTargetNode) FileName() string {\n\tdisplayName := strings.NewReplacer(\":\", \"_\").Replace(n.DisplayName)\n\treturn fmt.Sprintf(\"%s_%s\", n.Kind, displayName)\n}\n\ntype RequestTargetStatistics struct {\n\tNumTiKVNodes       int `json:\"num_tikv_nodes\"`\n\tNumTiDBNodes       int `json:\"num_tidb_nodes\"`\n\tNumPDNodes         int `json:\"num_pd_nodes\"`\n\tNumTiFlashNodes    int `json:\"num_tiflash_nodes\"`\n\tNumTiCDCNodes      int `json:\"num_ticdc_nodes\"`\n\tNumTiProxyNodes    int `json:\"num_tiproxy_nodes\"`\n\tNumTSONodes        int `json:\"num_tso_nodes\"`\n\tNumSchedulingNodes int `json:\"num_scheduling_nodes\"`\n}\n\nfunc NewRequestTargetStatisticsFromArray(arr *[]RequestTargetNode) RequestTargetStatistics {\n\tstats := RequestTargetStatistics{}\n\tfor _, node := range *arr {\n\t\tswitch node.Kind {\n\t\tcase NodeKindTiDB:\n\t\t\tstats.NumTiDBNodes++\n\t\tcase NodeKindTiKV:\n\t\t\tstats.NumTiKVNodes++\n\t\tcase NodeKindPD:\n\t\t\tstats.NumPDNodes++\n\t\tcase NodeKindTiFlash:\n\t\t\tstats.NumTiFlashNodes++\n\t\tcase NodeKindTiCDC:\n\t\t\tstats.NumTiCDCNodes++\n\t\tcase NodeKindTiProxy:\n\t\t\tstats.NumTiProxyNodes++\n\t\tcase NodeKindTSO:\n\t\t\tstats.NumTSONodes++\n\t\tcase NodeKindScheduling:\n\t\t\tstats.NumSchedulingNodes++\n\t\t}\n\t}\n\treturn stats\n}\n"
  },
  {
    "path": "pkg/apiserver/profiling/fetcher.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage profiling\n\nimport (\n\t_ \"embed\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/scheduling\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/ticdc\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tiflash\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tikv\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tiproxy\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tso\"\n)\n\nconst (\n\tmaxProfilingTimeout = time.Minute * 5\n)\n\ntype fetchOptions struct {\n\tip   string\n\tport int\n\tpath string\n}\n\ntype profileFetcher interface {\n\tfetch(op *fetchOptions) ([]byte, error)\n}\n\ntype fetchers struct {\n\ttikv       profileFetcher\n\ttiflash    profileFetcher\n\ttidb       profileFetcher\n\tpd         profileFetcher\n\tticdc      profileFetcher\n\ttiproxy    profileFetcher\n\ttso        profileFetcher\n\tscheduling profileFetcher\n}\n\nvar newFetchers = fx.Provide(func(\n\ttikvClient *tikv.Client,\n\ttidbClient *tidb.Client,\n\tpdClient *pd.Client,\n\ttiflashClient *tiflash.Client,\n\tticdcClient *ticdc.Client,\n\ttiproxyClient *tiproxy.Client,\n\ttsoClient *tso.Client,\n\tschedulingClient *scheduling.Client,\n\tconfig *config.Config,\n) *fetchers {\n\treturn &fetchers{\n\t\ttikv: &tikvFetcher{\n\t\t\tclient: tikvClient,\n\t\t},\n\t\ttiflash: &tiflashFetcher{\n\t\t\tclient: tiflashClient,\n\t\t},\n\t\ttidb: &tidbFetcher{\n\t\t\tclient: tidbClient,\n\t\t},\n\t\tpd: &pdFetcher{\n\t\t\tclient:              pdClient,\n\t\t\tstatusAPIHTTPScheme: config.GetClusterHTTPScheme(),\n\t\t},\n\t\tticdc: &ticdcFecther{\n\t\t\tclient: ticdcClient,\n\t\t},\n\t\ttiproxy: &tiproxyFecther{\n\t\t\tclient: tiproxyClient,\n\t\t},\n\t\ttso: &tsoFetcher{\n\t\t\tclient: tsoClient,\n\t\t},\n\t\tscheduling: &schedulingFetcher{\n\t\t\tclient: schedulingClient,\n\t\t},\n\t}\n})\n\ntype tikvFetcher struct {\n\tclient *tikv.Client\n}\n\n//go:embed jeprof.in\nvar jeprof string\n\nfunc (f *tikvFetcher) fetch(op *fetchOptions) ([]byte, error) {\n\tif strings.HasSuffix(op.path, \"heap\") {\n\t\tscheme := f.client.GetHTTPScheme()\n\t\tcmd := exec.Command(\"perl\", \"/dev/stdin\", \"--raw\", scheme+\"://\"+op.ip+\":\"+strconv.Itoa(op.port)+op.path) //nolint:gosec\n\t\tcmd.Stdin = strings.NewReader(jeprof)\n\t\tif f.client.GetTLSInfo() != nil {\n\t\t\tcmd.Env = append(os.Environ(), fmt.Sprintf(\n\t\t\t\t\"URL_FETCHER=curl -s --cert %s --key %s --cacert %s\",\n\t\t\t\tf.client.GetTLSInfo().CertFile,\n\t\t\t\tf.client.GetTLSInfo().KeyFile,\n\t\t\t\tf.client.GetTLSInfo().TrustedCAFile,\n\t\t\t))\n\t\t}\n\t\tstdout, err := cmd.StdoutPipe()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tstderr, err := cmd.StderrPipe()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// use jeprof to fetch tikv heap profile\n\t\terr = cmd.Start()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdata, err := io.ReadAll(stdout)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\terrMsg, err := io.ReadAll(stderr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\terr = cmd.Wait()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to fetch tikv heap profile: %s\", errMsg)\n\t\t}\n\t\treturn data, nil\n\t}\n\treturn f.client.WithTimeout(maxProfilingTimeout).AddRequestHeader(\"Content-Type\", \"application/protobuf\").SendGetRequest(op.ip, op.port, op.path)\n}\n\ntype tiflashFetcher struct {\n\tclient *tiflash.Client\n}\n\nfunc (f *tiflashFetcher) fetch(op *fetchOptions) ([]byte, error) {\n\tif strings.HasSuffix(op.path, \"heap\") {\n\t\tscheme := f.client.GetHTTPScheme()\n\t\tcmd := exec.Command(\"perl\", \"/dev/stdin\", \"--raw\", scheme+\"://\"+op.ip+\":\"+strconv.Itoa(op.port)+op.path) //nolint:gosec\n\t\tcmd.Stdin = strings.NewReader(jeprof)\n\t\tstdout, err := cmd.StdoutPipe()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tstderr, err := cmd.StderrPipe()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\t// use jeprof to fetch tiflash heap profile\n\t\terr = cmd.Start()\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdata, err := io.ReadAll(stdout)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\terrMsg, err := io.ReadAll(stderr)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\terr = cmd.Wait()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to fetch tiflash heap profile: %s\", errMsg)\n\t\t}\n\t\treturn data, nil\n\t}\n\treturn f.client.WithTimeout(maxProfilingTimeout).AddRequestHeader(\"Content-Type\", \"application/protobuf\").SendGetRequest(op.ip, op.port, op.path)\n}\n\ntype tidbFetcher struct {\n\tclient *tidb.Client\n}\n\nfunc (f *tidbFetcher) fetch(op *fetchOptions) ([]byte, error) {\n\treturn f.client.WithEnforcedStatusAPIAddress(op.ip, op.port).WithStatusAPITimeout(maxProfilingTimeout).SendGetRequest(op.path)\n}\n\ntype pdFetcher struct {\n\tclient              *pd.Client\n\tstatusAPIHTTPScheme string\n}\n\nfunc (f *pdFetcher) fetch(op *fetchOptions) ([]byte, error) {\n\tbaseURL := fmt.Sprintf(\"%s://%s\", f.statusAPIHTTPScheme, net.JoinHostPort(op.ip, strconv.Itoa(op.port)))\n\treturn f.client.\n\t\tWithTimeout(maxProfilingTimeout).\n\t\tWithBaseURL(baseURL).\n\t\tWithoutPrefix(). // pprof API does not have /pd/api/v1 prefix\n\t\tSendGetRequest(op.path)\n}\n\ntype ticdcFecther struct {\n\tclient *ticdc.Client\n}\n\nfunc (f *ticdcFecther) fetch(op *fetchOptions) ([]byte, error) {\n\treturn f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.ip, op.port, op.path)\n}\n\ntype tiproxyFecther struct {\n\tclient *tiproxy.Client\n}\n\nfunc (f *tiproxyFecther) fetch(op *fetchOptions) ([]byte, error) {\n\treturn f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.ip, op.port, op.path)\n}\n\ntype tsoFetcher struct {\n\tclient *tso.Client\n}\n\nfunc (f *tsoFetcher) fetch(op *fetchOptions) ([]byte, error) {\n\treturn f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.ip, op.port, op.path)\n}\n\ntype schedulingFetcher struct {\n\tclient *scheduling.Client\n}\n\nfunc (f *schedulingFetcher) fetch(op *fetchOptions) ([]byte, error) {\n\treturn f.client.WithTimeout(maxProfilingTimeout).SendGetRequest(op.ip, op.port, op.path)\n}\n"
  },
  {
    "path": "pkg/apiserver/profiling/jeprof.in",
    "content": "#! /usr/bin/env perl\n\n# Copyright (c) 1998-2007, Google Inc.\n# All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without\n# modification, are permitted provided that the following conditions are\n# met:\n#\n#     * Redistributions of source code must retain the above copyright\n# notice, this list of conditions and the following disclaimer.\n#     * Redistributions in binary form must reproduce the above\n# copyright notice, this list of conditions and the following disclaimer\n# in the documentation and/or other materials provided with the\n# distribution.\n#     * Neither the name of Google Inc. nor the names of its\n# contributors may be used to endorse or promote products derived from\n# this software without specific prior written permission.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n# \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\n# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\n# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\n# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\n# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\n# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n# ---\n# Program for printing the profile generated by common/profiler.cc,\n# or by the heap profiler (common/debugallocation.cc)\n#\n# The profile contains a sequence of entries of the form:\n#       <count> <stack trace>\n# This program parses the profile, and generates user-readable\n# output.\n#\n# Examples:\n#\n# % tools/jeprof \"program\" \"profile\"\n#   Enters \"interactive\" mode\n#\n# % tools/jeprof --text \"program\" \"profile\"\n#   Generates one line per procedure\n#\n# % tools/jeprof --gv \"program\" \"profile\"\n#   Generates annotated call-graph and displays via \"gv\"\n#\n# % tools/jeprof --gv --focus=Mutex \"program\" \"profile\"\n#   Restrict to code paths that involve an entry that matches \"Mutex\"\n#\n# % tools/jeprof --gv --focus=Mutex --ignore=string \"program\" \"profile\"\n#   Restrict to code paths that involve an entry that matches \"Mutex\"\n#   and does not match \"string\"\n#\n# % tools/jeprof --list=IBF_CheckDocid \"program\" \"profile\"\n#   Generates disassembly listing of all routines with at least one\n#   sample that match the --list=<regexp> pattern.  The listing is\n#   annotated with the flat and cumulative sample counts at each line.\n#\n# % tools/jeprof --disasm=IBF_CheckDocid \"program\" \"profile\"\n#   Generates disassembly listing of all routines with at least one\n#   sample that match the --disasm=<regexp> pattern.  The listing is\n#   annotated with the flat and cumulative sample counts at each PC value.\n#\n# TODO: Use color to indicate files?\n\nuse strict;\nuse warnings;\nuse Getopt::Long;\nuse Cwd;\n\nmy $JEPROF_VERSION = \"unknown\";\nmy $PPROF_VERSION = \"2.0\";\n\n# These are the object tools we use which can come from a\n# user-specified location using --tools, from the JEPROF_TOOLS\n# environment variable, or from the environment.\nmy %obj_tool_map = (\n  \"objdump\" => \"objdump\",\n  \"nm\" => \"nm\",\n  \"addr2line\" => \"addr2line\",\n  \"c++filt\" => \"c++filt\",\n  ## ConfigureObjTools may add architecture-specific entries:\n  #\"nm_pdb\" => \"nm-pdb\",       # for reading windows (PDB-format) executables\n  #\"addr2line_pdb\" => \"addr2line-pdb\",                                # ditto\n  #\"otool\" => \"otool\",         # equivalent of objdump on OS X\n);\n# NOTE: these are lists, so you can put in commandline flags if you want.\nmy @DOT = (\"dot\");          # leave non-absolute, since it may be in /usr/local\nmy @GV = (\"gv\");\nmy @EVINCE = (\"evince\");    # could also be xpdf or perhaps acroread\nmy @KCACHEGRIND = (\"kcachegrind\");\nmy @PS2PDF = (\"ps2pdf\");\n# These are used for dynamic profiles\nmy @URL_FETCHER = (\"curl\", \"-s\", \"--fail\");\n\n# These are the web pages that servers need to support for dynamic profiles\nmy $HEAP_PAGE = \"/pprof/heap\";\nmy $PROFILE_PAGE = \"/pprof/profile\";   # must support cgi-param \"?seconds=#\"\nmy $PMUPROFILE_PAGE = \"/pprof/pmuprofile(?:\\\\?.*)?\"; # must support cgi-param\n                                                # ?seconds=#&event=x&period=n\nmy $GROWTH_PAGE = \"/pprof/growth\";\nmy $CONTENTION_PAGE = \"/pprof/contention\";\nmy $WALL_PAGE = \"/pprof/wall(?:\\\\?.*)?\";  # accepts options like namefilter\nmy $FILTEREDPROFILE_PAGE = \"/pprof/filteredprofile(?:\\\\?.*)?\";\nmy $CENSUSPROFILE_PAGE = \"/pprof/censusprofile(?:\\\\?.*)?\"; # must support cgi-param\n                                                       # \"?seconds=#\",\n                                                       # \"?tags_regexp=#\" and\n                                                       # \"?type=#\".\nmy $SYMBOL_PAGE = \"/pprof/symbol\";     # must support symbol lookup via POST\nmy $PROGRAM_NAME_PAGE = \"/pprof/cmdline\";\n\n# These are the web pages that can be named on the command line.\n# All the alternatives must begin with /.\nmy $PROFILES = \"($HEAP_PAGE|$PROFILE_PAGE|$PMUPROFILE_PAGE|\" .\n               \"$GROWTH_PAGE|$CONTENTION_PAGE|$WALL_PAGE|\" .\n               \"$FILTEREDPROFILE_PAGE|$CENSUSPROFILE_PAGE)\";\n\n# default binary name\nmy $UNKNOWN_BINARY = \"(unknown)\";\n\n# There is a pervasive dependency on the length (in hex characters,\n# i.e., nibbles) of an address, distinguishing between 32-bit and\n# 64-bit profiles.  To err on the safe size, default to 64-bit here:\nmy $address_length = 16;\n\nmy $dev_null = \"/dev/null\";\nif (! -e $dev_null && $^O =~ /MSWin/) {    # $^O is the OS perl was built for\n  $dev_null = \"nul\";\n}\n\n# A list of paths to search for shared object files\nmy @prefix_list = ();\n\n# Special routine name that should not have any symbols.\n# Used as separator to parse \"addr2line -i\" output.\nmy $sep_symbol = '_fini';\nmy $sep_address = undef;\n\n##### Argument parsing #####\n\nsub usage_string {\n  return <<EOF;\nUsage:\njeprof [options] <program> <profiles>\n   <profiles> is a space separated list of profile names.\njeprof [options] <symbolized-profiles>\n   <symbolized-profiles> is a list of profile files where each file contains\n   the necessary symbol mappings  as well as profile data (likely generated\n   with --raw).\njeprof [options] <profile>\n   <profile> is a remote form.  Symbols are obtained from host:port$SYMBOL_PAGE\n\n   Each name can be:\n   /path/to/profile        - a path to a profile file\n   host:port[/<service>]   - a location of a service to get profile from\n\n   The /<service> can be $HEAP_PAGE, $PROFILE_PAGE, /pprof/pmuprofile,\n                         $GROWTH_PAGE, $CONTENTION_PAGE, /pprof/wall,\n                         $CENSUSPROFILE_PAGE, or /pprof/filteredprofile.\n   For instance:\n     jeprof http://myserver.com:80$HEAP_PAGE\n   If /<service> is omitted, the service defaults to $PROFILE_PAGE (cpu profiling).\njeprof --symbols <program>\n   Maps addresses to symbol names.  In this mode, stdin should be a\n   list of library mappings, in the same format as is found in the heap-\n   and cpu-profile files (this loosely matches that of /proc/self/maps\n   on linux), followed by a list of hex addresses to map, one per line.\n\n   For more help with querying remote servers, including how to add the\n   necessary server-side support code, see this filename (or one like it):\n\n   /usr/doc/gperftools-$PPROF_VERSION/pprof_remote_servers.html\n\nOptions:\n   --cum               Sort by cumulative data\n   --base=<base>       Subtract <base> from <profile> before display\n   --interactive       Run in interactive mode (interactive \"help\" gives help) [default]\n   --seconds=<n>       Length of time for dynamic profiles [default=30 secs]\n   --add_lib=<file>    Read additional symbols and line info from the given library\n   --lib_prefix=<dir>  Comma separated list of library path prefixes\n\nReporting Granularity:\n   --addresses         Report at address level\n   --lines             Report at source line level\n   --functions         Report at function level [default]\n   --files             Report at source file level\n\nOutput type:\n   --text              Generate text report\n   --callgrind         Generate callgrind format to stdout\n   --gv                Generate Postscript and display\n   --evince            Generate PDF and display\n   --web               Generate SVG and display\n   --list=<regexp>     Generate source listing of matching routines\n   --disasm=<regexp>   Generate disassembly of matching routines\n   --symbols           Print demangled symbol names found at given addresses\n   --dot               Generate DOT file to stdout\n   --ps                Generate Postcript to stdout\n   --pdf               Generate PDF to stdout\n   --svg               Generate SVG to stdout\n   --gif               Generate GIF to stdout\n   --raw               Generate symbolized jeprof data (useful with remote fetch)\n   --collapsed         Generate collapsed stacks for building flame graphs\n                       (see http://www.brendangregg.com/flamegraphs.html)\n\nHeap-Profile Options:\n   --inuse_space       Display in-use (mega)bytes [default]\n   --inuse_objects     Display in-use objects\n   --alloc_space       Display allocated (mega)bytes\n   --alloc_objects     Display allocated objects\n   --show_bytes        Display space in bytes\n   --drop_negative     Ignore negative differences\n\nContention-profile options:\n   --total_delay       Display total delay at each region [default]\n   --contentions       Display number of delays at each region\n   --mean_delay        Display mean delay at each region\n\nCall-graph Options:\n   --nodecount=<n>     Show at most so many nodes [default=80]\n   --nodefraction=<f>  Hide nodes below <f>*total [default=.005]\n   --edgefraction=<f>  Hide edges below <f>*total [default=.001]\n   --maxdegree=<n>     Max incoming/outgoing edges per node [default=8]\n   --focus=<regexp>    Focus on backtraces with nodes matching <regexp>\n   --thread=<n>        Show profile for thread <n>\n   --ignore=<regexp>   Ignore backtraces with nodes matching <regexp>\n   --scale=<n>         Set GV scaling [default=0]\n   --heapcheck         Make nodes with non-0 object counts\n                       (i.e. direct leak generators) more visible\n   --retain=<regexp>   Retain only nodes that match <regexp>\n   --exclude=<regexp>  Exclude all nodes that match <regexp>\n\nMiscellaneous:\n   --tools=<prefix or binary:fullpath>[,...]   \\$PATH for object tool pathnames\n   --test              Run unit tests\n   --help              This message\n   --version           Version information\n   --debug-syms-by-id  (Linux only) Find debug symbol files by build ID as well as by name\n\nEnvironment Variables:\n   JEPROF_TMPDIR        Profiles directory. Defaults to \\$HOME/jeprof\n   JEPROF_TOOLS         Prefix for object tools pathnames\n   URL_FETCHER          Command to fetch remote profiles\n\nExamples:\n\njeprof /bin/ls ls.prof\n                       Enters \"interactive\" mode\njeprof --text /bin/ls ls.prof\n                       Outputs one line per procedure\njeprof --web /bin/ls ls.prof\n                       Displays annotated call-graph in web browser\njeprof --gv /bin/ls ls.prof\n                       Displays annotated call-graph via 'gv'\njeprof --gv --focus=Mutex /bin/ls ls.prof\n                       Restricts to code paths including a .*Mutex.* entry\njeprof --gv --focus=Mutex --ignore=string /bin/ls ls.prof\n                       Code paths including Mutex but not string\njeprof --list=getdir /bin/ls ls.prof\n                       (Per-line) annotated source listing for getdir()\njeprof --disasm=getdir /bin/ls ls.prof\n                       (Per-PC) annotated disassembly for getdir()\n\njeprof http://localhost:1234/\n                       Enters \"interactive\" mode\njeprof --text localhost:1234\n                       Outputs one line per procedure for localhost:1234\njeprof --raw localhost:1234 > ./local.raw\njeprof --text ./local.raw\n                       Fetches a remote profile for later analysis and then\n                       analyzes it in text mode.\nEOF\n}\n\nsub version_string {\n  return <<EOF\njeprof (part of jemalloc $JEPROF_VERSION)\nbased on pprof (part of gperftools $PPROF_VERSION)\n\nCopyright 1998-2007 Google Inc.\n\nThis is BSD licensed software; see the source for copying conditions\nand license information.\nThere is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\nPARTICULAR PURPOSE.\nEOF\n}\n\nsub usage {\n  my $msg = shift;\n  print STDERR \"$msg\\n\\n\";\n  print STDERR usage_string();\n  print STDERR \"\\nFATAL ERROR: $msg\\n\";    # just as a reminder\n  exit(1);\n}\n\nsub Init() {\n  # Setup tmp-file name and handler to clean it up.\n  # We do this in the very beginning so that we can use\n  # error() and cleanup() function anytime here after.\n  $main::tmpfile_sym = \"/tmp/jeprof$$.sym\";\n  $main::tmpfile_ps = \"/tmp/jeprof$$\";\n  $main::next_tmpfile = 0;\n  $SIG{'INT'} = \\&sighandler;\n\n  # Cache from filename/linenumber to source code\n  $main::source_cache = ();\n\n  $main::opt_help = 0;\n  $main::opt_version = 0;\n\n  $main::opt_cum = 0;\n  $main::opt_base = '';\n  $main::opt_addresses = 0;\n  $main::opt_lines = 0;\n  $main::opt_functions = 0;\n  $main::opt_files = 0;\n  $main::opt_lib_prefix = \"\";\n\n  $main::opt_text = 0;\n  $main::opt_callgrind = 0;\n  $main::opt_list = \"\";\n  $main::opt_disasm = \"\";\n  $main::opt_symbols = 0;\n  $main::opt_gv = 0;\n  $main::opt_evince = 0;\n  $main::opt_web = 0;\n  $main::opt_dot = 0;\n  $main::opt_ps = 0;\n  $main::opt_pdf = 0;\n  $main::opt_gif = 0;\n  $main::opt_svg = 0;\n  $main::opt_raw = 0;\n  $main::opt_collapsed = 0;\n\n  $main::opt_nodecount = 80;\n  $main::opt_nodefraction = 0.005;\n  $main::opt_edgefraction = 0.001;\n  $main::opt_maxdegree = 8;\n  $main::opt_focus = '';\n  $main::opt_thread = undef;\n  $main::opt_ignore = '';\n  $main::opt_scale = 0;\n  $main::opt_heapcheck = 0;\n  $main::opt_retain = '';\n  $main::opt_exclude = '';\n  $main::opt_seconds = 30;\n  $main::opt_lib = \"\";\n\n  $main::opt_inuse_space   = 0;\n  $main::opt_inuse_objects = 0;\n  $main::opt_alloc_space   = 0;\n  $main::opt_alloc_objects = 0;\n  $main::opt_show_bytes    = 0;\n  $main::opt_drop_negative = 0;\n  $main::opt_interactive   = 0;\n\n  $main::opt_total_delay = 0;\n  $main::opt_contentions = 0;\n  $main::opt_mean_delay = 0;\n\n  $main::opt_tools   = \"\";\n  $main::opt_debug   = 0;\n  $main::opt_test    = 0;\n  $main::opt_debug_syms_by_id = 0;\n\n  # These are undocumented flags used only by unittests.\n  $main::opt_test_stride = 0;\n\n  # Are we using $SYMBOL_PAGE?\n  $main::use_symbol_page = 0;\n\n  # Files returned by TempName.\n  %main::tempnames = ();\n\n  # Type of profile we are dealing with\n  # Supported types:\n  #     cpu\n  #     heap\n  #     growth\n  #     contention\n  $main::profile_type = '';     # Empty type means \"unknown\"\n\n  GetOptions(\"help!\"          => \\$main::opt_help,\n             \"version!\"       => \\$main::opt_version,\n             \"cum!\"           => \\$main::opt_cum,\n             \"base=s\"         => \\$main::opt_base,\n             \"seconds=i\"      => \\$main::opt_seconds,\n             \"add_lib=s\"      => \\$main::opt_lib,\n             \"lib_prefix=s\"   => \\$main::opt_lib_prefix,\n             \"functions!\"     => \\$main::opt_functions,\n             \"lines!\"         => \\$main::opt_lines,\n             \"addresses!\"     => \\$main::opt_addresses,\n             \"files!\"         => \\$main::opt_files,\n             \"text!\"          => \\$main::opt_text,\n             \"callgrind!\"     => \\$main::opt_callgrind,\n             \"list=s\"         => \\$main::opt_list,\n             \"disasm=s\"       => \\$main::opt_disasm,\n             \"symbols!\"       => \\$main::opt_symbols,\n             \"gv!\"            => \\$main::opt_gv,\n             \"evince!\"        => \\$main::opt_evince,\n             \"web!\"           => \\$main::opt_web,\n             \"dot!\"           => \\$main::opt_dot,\n             \"ps!\"            => \\$main::opt_ps,\n             \"pdf!\"           => \\$main::opt_pdf,\n             \"svg!\"           => \\$main::opt_svg,\n             \"gif!\"           => \\$main::opt_gif,\n             \"raw!\"           => \\$main::opt_raw,\n             \"collapsed!\"     => \\$main::opt_collapsed,\n             \"interactive!\"   => \\$main::opt_interactive,\n             \"nodecount=i\"    => \\$main::opt_nodecount,\n             \"nodefraction=f\" => \\$main::opt_nodefraction,\n             \"edgefraction=f\" => \\$main::opt_edgefraction,\n             \"maxdegree=i\"    => \\$main::opt_maxdegree,\n             \"focus=s\"        => \\$main::opt_focus,\n             \"thread=s\"       => \\$main::opt_thread,\n             \"ignore=s\"       => \\$main::opt_ignore,\n             \"scale=i\"        => \\$main::opt_scale,\n             \"heapcheck\"      => \\$main::opt_heapcheck,\n             \"retain=s\"       => \\$main::opt_retain,\n             \"exclude=s\"      => \\$main::opt_exclude,\n             \"inuse_space!\"   => \\$main::opt_inuse_space,\n             \"inuse_objects!\" => \\$main::opt_inuse_objects,\n             \"alloc_space!\"   => \\$main::opt_alloc_space,\n             \"alloc_objects!\" => \\$main::opt_alloc_objects,\n             \"show_bytes!\"    => \\$main::opt_show_bytes,\n             \"drop_negative!\" => \\$main::opt_drop_negative,\n             \"total_delay!\"   => \\$main::opt_total_delay,\n             \"contentions!\"   => \\$main::opt_contentions,\n             \"mean_delay!\"    => \\$main::opt_mean_delay,\n             \"tools=s\"        => \\$main::opt_tools,\n             \"test!\"          => \\$main::opt_test,\n             \"debug!\"         => \\$main::opt_debug,\n             \"debug-syms-by-id!\" => \\$main::opt_debug_syms_by_id,\n             # Undocumented flags used only by unittests:\n             \"test_stride=i\"  => \\$main::opt_test_stride,\n      ) || usage(\"Invalid option(s)\");\n\n  # Deal with the standard --help and --version\n  if ($main::opt_help) {\n    print usage_string();\n    exit(0);\n  }\n\n  if ($main::opt_version) {\n    print version_string();\n    exit(0);\n  }\n\n  # Disassembly/listing/symbols mode requires address-level info\n  if ($main::opt_disasm || $main::opt_list || $main::opt_symbols) {\n    $main::opt_functions = 0;\n    $main::opt_lines = 0;\n    $main::opt_addresses = 1;\n    $main::opt_files = 0;\n  }\n\n  # Check heap-profiling flags\n  if ($main::opt_inuse_space +\n      $main::opt_inuse_objects +\n      $main::opt_alloc_space +\n      $main::opt_alloc_objects > 1) {\n    usage(\"Specify at most on of --inuse/--alloc options\");\n  }\n\n  # Check output granularities\n  my $grains =\n      $main::opt_functions +\n      $main::opt_lines +\n      $main::opt_addresses +\n      $main::opt_files +\n      0;\n  if ($grains > 1) {\n    usage(\"Only specify one output granularity option\");\n  }\n  if ($grains == 0) {\n    $main::opt_functions = 1;\n  }\n\n  # Check output modes\n  my $modes =\n      $main::opt_text +\n      $main::opt_callgrind +\n      ($main::opt_list eq '' ? 0 : 1) +\n      ($main::opt_disasm eq '' ? 0 : 1) +\n      ($main::opt_symbols == 0 ? 0 : 1) +\n      $main::opt_gv +\n      $main::opt_evince +\n      $main::opt_web +\n      $main::opt_dot +\n      $main::opt_ps +\n      $main::opt_pdf +\n      $main::opt_svg +\n      $main::opt_gif +\n      $main::opt_raw +\n      $main::opt_collapsed +\n      $main::opt_interactive +\n      0;\n  if ($modes > 1) {\n    usage(\"Only specify one output mode\");\n  }\n  if ($modes == 0) {\n    if (-t STDOUT) {  # If STDOUT is a tty, activate interactive mode\n      $main::opt_interactive = 1;\n    } else {\n      $main::opt_text = 1;\n    }\n  }\n\n  if ($main::opt_test) {\n    RunUnitTests();\n    # Should not return\n    exit(1);\n  }\n\n  # Binary name and profile arguments list\n  $main::prog = \"\";\n  @main::pfile_args = ();\n\n  # Override url_fetcher variable if URL_FETCHER environment variable is set\n  if ($ENV{URL_FETCHER}) {\n    @URL_FETCHER = split(' ', $ENV{URL_FETCHER});\n  }\n\n  # Remote profiling without a binary (using $SYMBOL_PAGE instead)\n  if (@ARGV > 0) {\n    if (IsProfileURL($ARGV[0])) {\n      $main::use_symbol_page = 1;\n    } elsif (IsSymbolizedProfileFile($ARGV[0])) {\n      $main::use_symbolized_profile = 1;\n      $main::prog = $UNKNOWN_BINARY;  # will be set later from the profile file\n    }\n  }\n\n  if ($main::use_symbol_page || $main::use_symbolized_profile) {\n    # We don't need a binary!\n    my %disabled = ('--lines' => $main::opt_lines,\n                    '--disasm' => $main::opt_disasm);\n    for my $option (keys %disabled) {\n      usage(\"$option cannot be used without a binary\") if $disabled{$option};\n    }\n    # Set $main::prog later...\n    scalar(@ARGV) || usage(\"Did not specify profile file\");\n  } elsif ($main::opt_symbols) {\n    # --symbols needs a binary-name (to run nm on, etc) but not profiles\n    $main::prog = shift(@ARGV) || usage(\"Did not specify program\");\n  } else {\n    $main::prog = shift(@ARGV) || usage(\"Did not specify program\");\n    scalar(@ARGV) || usage(\"Did not specify profile file\");\n  }\n\n  # Parse profile file/location arguments\n  foreach my $farg (@ARGV) {\n    if ($farg =~ m/(.*)\\@([0-9]+)(|\\/.*)$/ ) {\n      my $machine = $1;\n      my $num_machines = $2;\n      my $path = $3;\n      for (my $i = 0; $i < $num_machines; $i++) {\n        unshift(@main::pfile_args, \"$i.$machine$path\");\n      }\n    } else {\n      unshift(@main::pfile_args, $farg);\n    }\n  }\n\n  if ($main::use_symbol_page) {\n    unless (IsProfileURL($main::pfile_args[0])) {\n      error(\"The first profile should be a remote form to use $SYMBOL_PAGE\\n\");\n    }\n    CheckSymbolPage();\n    $main::prog = FetchProgramName();\n  } elsif (!$main::use_symbolized_profile) {  # may not need objtools!\n    ConfigureObjTools($main::prog)\n  }\n\n  # Break the opt_lib_prefix into the prefix_list array\n  @prefix_list = split (',', $main::opt_lib_prefix);\n\n  # Remove trailing / from the prefixes, in the list to prevent\n  # searching things like /my/path//lib/mylib.so\n  foreach (@prefix_list) {\n    s|/+$||;\n  }\n\n  # Flag to prevent us from trying over and over to use\n  #  elfutils if it's not installed (used only with\n  #  --debug-syms-by-id option).\n  $main::gave_up_on_elfutils = 0;\n}\n\nsub FilterAndPrint {\n  my ($profile, $symbols, $libs, $thread) = @_;\n\n  # Get total data in profile\n  my $total = TotalProfile($profile);\n\n  # Remove uniniteresting stack items\n  $profile = RemoveUninterestingFrames($symbols, $profile);\n\n  # Focus?\n  if ($main::opt_focus ne '') {\n    $profile = FocusProfile($symbols, $profile, $main::opt_focus);\n  }\n\n  # Ignore?\n  if ($main::opt_ignore ne '') {\n    $profile = IgnoreProfile($symbols, $profile, $main::opt_ignore);\n  }\n\n  my $calls = ExtractCalls($symbols, $profile);\n\n  # Reduce profiles to required output granularity, and also clean\n  # each stack trace so a given entry exists at most once.\n  my $reduced = ReduceProfile($symbols, $profile);\n\n  # Get derived profiles\n  my $flat = FlatProfile($reduced);\n  my $cumulative = CumulativeProfile($reduced);\n\n  # Print\n  if (!$main::opt_interactive) {\n    if ($main::opt_disasm) {\n      PrintDisassembly($libs, $flat, $cumulative, $main::opt_disasm);\n    } elsif ($main::opt_list) {\n      PrintListing($total, $libs, $flat, $cumulative, $main::opt_list, 0);\n    } elsif ($main::opt_text) {\n      # Make sure the output is empty when have nothing to report\n      # (only matters when --heapcheck is given but we must be\n      # compatible with old branches that did not pass --heapcheck always):\n      if ($total != 0) {\n        printf(\"Total%s: %s %s\\n\",\n               (defined($thread) ? \" (t$thread)\" : \"\"),\n               Unparse($total), Units());\n      }\n      PrintText($symbols, $flat, $cumulative, -1);\n    } elsif ($main::opt_raw) {\n      PrintSymbolizedProfile($symbols, $profile, $main::prog);\n    } elsif ($main::opt_collapsed) {\n      PrintCollapsedStacks($symbols, $profile);\n    } elsif ($main::opt_callgrind) {\n      PrintCallgrind($calls);\n    } else {\n      if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {\n        if ($main::opt_gv) {\n          RunGV(TempName($main::next_tmpfile, \"ps\"), \"\");\n        } elsif ($main::opt_evince) {\n          RunEvince(TempName($main::next_tmpfile, \"pdf\"), \"\");\n        } elsif ($main::opt_web) {\n          my $tmp = TempName($main::next_tmpfile, \"svg\");\n          RunWeb($tmp);\n          # The command we run might hand the file name off\n          # to an already running browser instance and then exit.\n          # Normally, we'd remove $tmp on exit (right now),\n          # but fork a child to remove $tmp a little later, so that the\n          # browser has time to load it first.\n          delete $main::tempnames{$tmp};\n          if (fork() == 0) {\n            sleep 5;\n            unlink($tmp);\n            exit(0);\n          }\n        }\n      } else {\n        cleanup();\n        exit(1);\n      }\n    }\n  } else {\n    InteractiveMode($profile, $symbols, $libs, $total);\n  }\n}\n\nsub Main() {\n  Init();\n  $main::collected_profile = undef;\n  @main::profile_files = ();\n  $main::op_time = time();\n\n  # Printing symbols is special and requires a lot less info that most.\n  if ($main::opt_symbols) {\n    PrintSymbols(*STDIN);   # Get /proc/maps and symbols output from stdin\n    return;\n  }\n\n  # Fetch all profile data\n  FetchDynamicProfiles();\n\n  # this will hold symbols that we read from the profile files\n  my $symbol_map = {};\n\n  # Read one profile, pick the last item on the list\n  my $data = ReadProfile($main::prog, $main::profile_files[0]);\n  my $profile = $data->{profile};\n  my $pcs = $data->{pcs};\n  my $libs = $data->{libs};   # Info about main program and shared libraries\n  $symbol_map = MergeSymbols($symbol_map, $data->{symbols});\n\n  # Add additional profiles, if available.\n  if (scalar(@main::profile_files) > 1) {\n    foreach my $pname (@main::profile_files[1..$#main::profile_files]) {\n      my $data2 = ReadProfile($main::prog, $pname);\n      $profile = AddProfile($profile, $data2->{profile});\n      $pcs = AddPcs($pcs, $data2->{pcs});\n      $symbol_map = MergeSymbols($symbol_map, $data2->{symbols});\n    }\n  }\n\n  # Subtract base from profile, if specified\n  if ($main::opt_base ne '') {\n    my $base = ReadProfile($main::prog, $main::opt_base);\n    $profile = SubtractProfile($profile, $base->{profile});\n    $pcs = AddPcs($pcs, $base->{pcs});\n    $symbol_map = MergeSymbols($symbol_map, $base->{symbols});\n  }\n\n  # Collect symbols\n  my $symbols;\n  if ($main::use_symbolized_profile) {\n    $symbols = FetchSymbols($pcs, $symbol_map);\n  } elsif ($main::use_symbol_page) {\n    $symbols = FetchSymbols($pcs);\n  } else {\n    # TODO(csilvers): $libs uses the /proc/self/maps data from profile1,\n    # which may differ from the data from subsequent profiles, especially\n    # if they were run on different machines.  Use appropriate libs for\n    # each pc somehow.\n    $symbols = ExtractSymbols($libs, $pcs);\n  }\n\n  if (!defined($main::opt_thread)) {\n    FilterAndPrint($profile, $symbols, $libs);\n  }\n  if (defined($data->{threads})) {\n    foreach my $thread (sort { $a <=> $b } keys(%{$data->{threads}})) {\n      if (defined($main::opt_thread) &&\n          ($main::opt_thread eq '*' || $main::opt_thread == $thread)) {\n        my $thread_profile = $data->{threads}{$thread};\n        FilterAndPrint($thread_profile, $symbols, $libs, $thread);\n      }\n    }\n  }\n\n  cleanup();\n  exit(0);\n}\n\n##### Entry Point #####\n\nMain();\n\n# Temporary code to detect if we're running on a Goobuntu system.\n# These systems don't have the right stuff installed for the special\n# Readline libraries to work, so as a temporary workaround, we default\n# to using the normal stdio code, rather than the fancier readline-based\n# code\nsub ReadlineMightFail {\n  if (-e '/lib/libtermcap.so.2') {\n    return 0;  # libtermcap exists, so readline should be okay\n  } else {\n    return 1;\n  }\n}\n\nsub RunGV {\n  my $fname = shift;\n  my $bg = shift;       # \"\" or \" &\" if we should run in background\n  if (!system(ShellEscape(@GV, \"--version\") . \" >$dev_null 2>&1\")) {\n    # Options using double dash are supported by this gv version.\n    # Also, turn on noantialias to better handle bug in gv for\n    # postscript files with large dimensions.\n    # TODO: Maybe we should not pass the --noantialias flag\n    # if the gv version is known to work properly without the flag.\n    system(ShellEscape(@GV, \"--scale=$main::opt_scale\", \"--noantialias\", $fname)\n           . $bg);\n  } else {\n    # Old gv version - only supports options that use single dash.\n    print STDERR ShellEscape(@GV, \"-scale\", $main::opt_scale) . \"\\n\";\n    system(ShellEscape(@GV, \"-scale\", \"$main::opt_scale\", $fname) . $bg);\n  }\n}\n\nsub RunEvince {\n  my $fname = shift;\n  my $bg = shift;       # \"\" or \" &\" if we should run in background\n  system(ShellEscape(@EVINCE, $fname) . $bg);\n}\n\nsub RunWeb {\n  my $fname = shift;\n  print STDERR \"Loading web page file:///$fname\\n\";\n\n  if (`uname` =~ /Darwin/) {\n    # OS X: open will use standard preference for SVG files.\n    system(\"/usr/bin/open\", $fname);\n    return;\n  }\n\n  # Some kind of Unix; try generic symlinks, then specific browsers.\n  # (Stop once we find one.)\n  # Works best if the browser is already running.\n  my @alt = (\n    \"/etc/alternatives/gnome-www-browser\",\n    \"/etc/alternatives/x-www-browser\",\n    \"google-chrome\",\n    \"firefox\",\n  );\n  foreach my $b (@alt) {\n    if (system($b, $fname) == 0) {\n      return;\n    }\n  }\n\n  print STDERR \"Could not load web browser.\\n\";\n}\n\nsub RunKcachegrind {\n  my $fname = shift;\n  my $bg = shift;       # \"\" or \" &\" if we should run in background\n  print STDERR \"Starting '@KCACHEGRIND \" . $fname . $bg . \"'\\n\";\n  system(ShellEscape(@KCACHEGRIND, $fname) . $bg);\n}\n\n\n##### Interactive helper routines #####\n\nsub InteractiveMode {\n  $| = 1;  # Make output unbuffered for interactive mode\n  my ($orig_profile, $symbols, $libs, $total) = @_;\n\n  print STDERR \"Welcome to jeprof!  For help, type 'help'.\\n\";\n\n  # Use ReadLine if it's installed and input comes from a console.\n  if ( -t STDIN &&\n       !ReadlineMightFail() &&\n       defined(eval {require Term::ReadLine}) ) {\n    my $term = new Term::ReadLine 'jeprof';\n    while ( defined ($_ = $term->readline('(jeprof) '))) {\n      $term->addhistory($_) if /\\S/;\n      if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) {\n        last;    # exit when we get an interactive command to quit\n      }\n    }\n  } else {       # don't have readline\n    while (1) {\n      print STDERR \"(jeprof) \";\n      $_ = <STDIN>;\n      last if ! defined $_ ;\n      s/\\r//g;         # turn windows-looking lines into unix-looking lines\n\n      # Save some flags that might be reset by InteractiveCommand()\n      my $save_opt_lines = $main::opt_lines;\n\n      if (!InteractiveCommand($orig_profile, $symbols, $libs, $total, $_)) {\n        last;    # exit when we get an interactive command to quit\n      }\n\n      # Restore flags\n      $main::opt_lines = $save_opt_lines;\n    }\n  }\n}\n\n# Takes two args: orig profile, and command to run.\n# Returns 1 if we should keep going, or 0 if we were asked to quit\nsub InteractiveCommand {\n  my($orig_profile, $symbols, $libs, $total, $command) = @_;\n  $_ = $command;                # just to make future m//'s easier\n  if (!defined($_)) {\n    print STDERR \"\\n\";\n    return 0;\n  }\n  if (m/^\\s*quit/) {\n    return 0;\n  }\n  if (m/^\\s*help/) {\n    InteractiveHelpMessage();\n    return 1;\n  }\n  # Clear all the mode options -- mode is controlled by \"$command\"\n  $main::opt_text = 0;\n  $main::opt_callgrind = 0;\n  $main::opt_disasm = 0;\n  $main::opt_list = 0;\n  $main::opt_gv = 0;\n  $main::opt_evince = 0;\n  $main::opt_cum = 0;\n\n  if (m/^\\s*(text|top)(\\d*)\\s*(.*)/) {\n    $main::opt_text = 1;\n\n    my $line_limit = ($2 ne \"\") ? int($2) : 10;\n\n    my $routine;\n    my $ignore;\n    ($routine, $ignore) = ParseInteractiveArgs($3);\n\n    my $profile = ProcessProfile($total, $orig_profile, $symbols, \"\", $ignore);\n    my $reduced = ReduceProfile($symbols, $profile);\n\n    # Get derived profiles\n    my $flat = FlatProfile($reduced);\n    my $cumulative = CumulativeProfile($reduced);\n\n    PrintText($symbols, $flat, $cumulative, $line_limit);\n    return 1;\n  }\n  if (m/^\\s*callgrind\\s*([^ \\n]*)/) {\n    $main::opt_callgrind = 1;\n\n    # Get derived profiles\n    my $calls = ExtractCalls($symbols, $orig_profile);\n    my $filename = $1;\n    if ( $1 eq '' ) {\n      $filename = TempName($main::next_tmpfile, \"callgrind\");\n    }\n    PrintCallgrind($calls, $filename);\n    if ( $1 eq '' ) {\n      RunKcachegrind($filename, \" & \");\n      $main::next_tmpfile++;\n    }\n\n    return 1;\n  }\n  if (m/^\\s*(web)?list\\s*(.+)/) {\n    my $html = (defined($1) && ($1 eq \"web\"));\n    $main::opt_list = 1;\n\n    my $routine;\n    my $ignore;\n    ($routine, $ignore) = ParseInteractiveArgs($2);\n\n    my $profile = ProcessProfile($total, $orig_profile, $symbols, \"\", $ignore);\n    my $reduced = ReduceProfile($symbols, $profile);\n\n    # Get derived profiles\n    my $flat = FlatProfile($reduced);\n    my $cumulative = CumulativeProfile($reduced);\n\n    PrintListing($total, $libs, $flat, $cumulative, $routine, $html);\n    return 1;\n  }\n  if (m/^\\s*disasm\\s*(.+)/) {\n    $main::opt_disasm = 1;\n\n    my $routine;\n    my $ignore;\n    ($routine, $ignore) = ParseInteractiveArgs($1);\n\n    # Process current profile to account for various settings\n    my $profile = ProcessProfile($total, $orig_profile, $symbols, \"\", $ignore);\n    my $reduced = ReduceProfile($symbols, $profile);\n\n    # Get derived profiles\n    my $flat = FlatProfile($reduced);\n    my $cumulative = CumulativeProfile($reduced);\n\n    PrintDisassembly($libs, $flat, $cumulative, $routine);\n    return 1;\n  }\n  if (m/^\\s*(gv|web|evince)\\s*(.*)/) {\n    $main::opt_gv = 0;\n    $main::opt_evince = 0;\n    $main::opt_web = 0;\n    if ($1 eq \"gv\") {\n      $main::opt_gv = 1;\n    } elsif ($1 eq \"evince\") {\n      $main::opt_evince = 1;\n    } elsif ($1 eq \"web\") {\n      $main::opt_web = 1;\n    }\n\n    my $focus;\n    my $ignore;\n    ($focus, $ignore) = ParseInteractiveArgs($2);\n\n    # Process current profile to account for various settings\n    my $profile = ProcessProfile($total, $orig_profile, $symbols,\n                                 $focus, $ignore);\n    my $reduced = ReduceProfile($symbols, $profile);\n\n    # Get derived profiles\n    my $flat = FlatProfile($reduced);\n    my $cumulative = CumulativeProfile($reduced);\n\n    if (PrintDot($main::prog, $symbols, $profile, $flat, $cumulative, $total)) {\n      if ($main::opt_gv) {\n        RunGV(TempName($main::next_tmpfile, \"ps\"), \" &\");\n      } elsif ($main::opt_evince) {\n        RunEvince(TempName($main::next_tmpfile, \"pdf\"), \" &\");\n      } elsif ($main::opt_web) {\n        RunWeb(TempName($main::next_tmpfile, \"svg\"));\n      }\n      $main::next_tmpfile++;\n    }\n    return 1;\n  }\n  if (m/^\\s*$/) {\n    return 1;\n  }\n  print STDERR \"Unknown command: try 'help'.\\n\";\n  return 1;\n}\n\n\nsub ProcessProfile {\n  my $total_count = shift;\n  my $orig_profile = shift;\n  my $symbols = shift;\n  my $focus = shift;\n  my $ignore = shift;\n\n  # Process current profile to account for various settings\n  my $profile = $orig_profile;\n  printf(\"Total: %s %s\\n\", Unparse($total_count), Units());\n  if ($focus ne '') {\n    $profile = FocusProfile($symbols, $profile, $focus);\n    my $focus_count = TotalProfile($profile);\n    printf(\"After focusing on '%s': %s %s of %s (%0.1f%%)\\n\",\n           $focus,\n           Unparse($focus_count), Units(),\n           Unparse($total_count), ($focus_count*100.0) / $total_count);\n  }\n  if ($ignore ne '') {\n    $profile = IgnoreProfile($symbols, $profile, $ignore);\n    my $ignore_count = TotalProfile($profile);\n    printf(\"After ignoring '%s': %s %s of %s (%0.1f%%)\\n\",\n           $ignore,\n           Unparse($ignore_count), Units(),\n           Unparse($total_count),\n           ($ignore_count*100.0) / $total_count);\n  }\n\n  return $profile;\n}\n\nsub InteractiveHelpMessage {\n  print STDERR <<ENDOFHELP;\nInteractive jeprof mode\n\nCommands:\n  gv\n  gv [focus] [-ignore1] [-ignore2]\n      Show graphical hierarchical display of current profile.  Without\n      any arguments, shows all samples in the profile.  With the optional\n      \"focus\" argument, restricts the samples shown to just those where\n      the \"focus\" regular expression matches a routine name on the stack\n      trace.\n\n  web\n  web [focus] [-ignore1] [-ignore2]\n      Like GV, but displays profile in your web browser instead of using\n      Ghostview. Works best if your web browser is already running.\n      To change the browser that gets used:\n      On Linux, set the /etc/alternatives/gnome-www-browser symlink.\n      On OS X, change the Finder association for SVG files.\n\n  list [routine_regexp] [-ignore1] [-ignore2]\n      Show source listing of routines whose names match \"routine_regexp\"\n\n  weblist [routine_regexp] [-ignore1] [-ignore2]\n     Displays a source listing of routines whose names match \"routine_regexp\"\n     in a web browser.  You can click on source lines to view the\n     corresponding disassembly.\n\n  top [--cum] [-ignore1] [-ignore2]\n  top20 [--cum] [-ignore1] [-ignore2]\n  top37 [--cum] [-ignore1] [-ignore2]\n      Show top lines ordered by flat profile count, or cumulative count\n      if --cum is specified.  If a number is present after 'top', the\n      top K routines will be shown (defaults to showing the top 10)\n\n  disasm [routine_regexp] [-ignore1] [-ignore2]\n      Show disassembly of routines whose names match \"routine_regexp\",\n      annotated with sample counts.\n\n  callgrind\n  callgrind [filename]\n      Generates callgrind file. If no filename is given, kcachegrind is called.\n\n  help - This listing\n  quit or ^D - End jeprof\n\nFor commands that accept optional -ignore tags, samples where any routine in\nthe stack trace matches the regular expression in any of the -ignore\nparameters will be ignored.\n\nFurther pprof details are available at this location (or one similar):\n\n /usr/doc/gperftools-$PPROF_VERSION/cpu_profiler.html\n /usr/doc/gperftools-$PPROF_VERSION/heap_profiler.html\n\nENDOFHELP\n}\nsub ParseInteractiveArgs {\n  my $args = shift;\n  my $focus = \"\";\n  my $ignore = \"\";\n  my @x = split(/ +/, $args);\n  foreach $a (@x) {\n    if ($a =~ m/^(--|-)lines$/) {\n      $main::opt_lines = 1;\n    } elsif ($a =~ m/^(--|-)cum$/) {\n      $main::opt_cum = 1;\n    } elsif ($a =~ m/^-(.*)/) {\n      $ignore .= (($ignore ne \"\") ? \"|\" : \"\" ) . $1;\n    } else {\n      $focus .= (($focus ne \"\") ? \"|\" : \"\" ) . $a;\n    }\n  }\n  if ($ignore ne \"\") {\n    print STDERR \"Ignoring samples in call stacks that match '$ignore'\\n\";\n  }\n  return ($focus, $ignore);\n}\n\n##### Output code #####\n\nsub TempName {\n  my $fnum = shift;\n  my $ext = shift;\n  my $file = \"$main::tmpfile_ps.$fnum.$ext\";\n  $main::tempnames{$file} = 1;\n  return $file;\n}\n\n# Print profile data in packed binary format (64-bit) to standard out\nsub PrintProfileData {\n  my $profile = shift;\n\n  # print header (64-bit style)\n  # (zero) (header-size) (version) (sample-period) (zero)\n  print pack('L*', 0, 0, 3, 0, 0, 0, 1, 0, 0, 0);\n\n  foreach my $k (keys(%{$profile})) {\n    my $count = $profile->{$k};\n    my @addrs = split(/\\n/, $k);\n    if ($#addrs >= 0) {\n      my $depth = $#addrs + 1;\n      # int(foo / 2**32) is the only reliable way to get rid of bottom\n      # 32 bits on both 32- and 64-bit systems.\n      print pack('L*', $count & 0xFFFFFFFF, int($count / 2**32));\n      print pack('L*', $depth & 0xFFFFFFFF, int($depth / 2**32));\n\n      foreach my $full_addr (@addrs) {\n        my $addr = $full_addr;\n        $addr =~ s/0x0*//;  # strip off leading 0x, zeroes\n        if (length($addr) > 16) {\n          print STDERR \"Invalid address in profile: $full_addr\\n\";\n          next;\n        }\n        my $low_addr = substr($addr, -8);       # get last 8 hex chars\n        my $high_addr = substr($addr, -16, 8);  # get up to 8 more hex chars\n        print pack('L*', hex('0x' . $low_addr), hex('0x' . $high_addr));\n      }\n    }\n  }\n}\n\n# Print symbols and profile data\nsub PrintSymbolizedProfile {\n  my $symbols = shift;\n  my $profile = shift;\n  my $prog = shift;\n\n  $SYMBOL_PAGE =~ m,[^/]+$,;    # matches everything after the last slash\n  my $symbol_marker = $&;\n\n  print '--- ', $symbol_marker, \"\\n\";\n  if (defined($prog)) {\n    print 'binary=', $prog, \"\\n\";\n  }\n  while (my ($pc, $name) = each(%{$symbols})) {\n    my $sep = ' ';\n    print '0x', $pc;\n    # We have a list of function names, which include the inlined\n    # calls.  They are separated (and terminated) by --, which is\n    # illegal in function names.\n    for (my $j = 2; $j <= $#{$name}; $j += 3) {\n      print $sep, $name->[$j];\n      $sep = '--';\n    }\n    print \"\\n\";\n  }\n  print '---', \"\\n\";\n\n  my $profile_marker;\n  if ($main::profile_type eq 'heap') {\n    $HEAP_PAGE =~ m,[^/]+$,;    # matches everything after the last slash\n    $profile_marker = $&;\n  } elsif ($main::profile_type eq 'growth') {\n    $GROWTH_PAGE =~ m,[^/]+$,;    # matches everything after the last slash\n    $profile_marker = $&;\n  } elsif ($main::profile_type eq 'contention') {\n    $CONTENTION_PAGE =~ m,[^/]+$,;    # matches everything after the last slash\n    $profile_marker = $&;\n  } else { # elsif ($main::profile_type eq 'cpu')\n    $PROFILE_PAGE =~ m,[^/]+$,;    # matches everything after the last slash\n    $profile_marker = $&;\n  }\n\n  print '--- ', $profile_marker, \"\\n\";\n  if (defined($main::collected_profile)) {\n    # if used with remote fetch, simply dump the collected profile to output.\n    open(SRC, \"<$main::collected_profile\");\n    while (<SRC>) {\n      print $_;\n    }\n    close(SRC);\n  } else {\n    # --raw/http: For everything to work correctly for non-remote profiles, we\n    # would need to extend PrintProfileData() to handle all possible profile\n    # types, re-enable the code that is currently disabled in ReadCPUProfile()\n    # and FixCallerAddresses(), and remove the remote profile dumping code in\n    # the block above.\n    die \"--raw/http: jeprof can only dump remote profiles for --raw\\n\";\n    # dump a cpu-format profile to standard out\n    PrintProfileData($profile);\n  }\n}\n\n# Print text output\nsub PrintText {\n  my $symbols = shift;\n  my $flat = shift;\n  my $cumulative = shift;\n  my $line_limit = shift;\n\n  my $total = TotalProfile($flat);\n\n  # Which profile to sort by?\n  my $s = $main::opt_cum ? $cumulative : $flat;\n\n  my $running_sum = 0;\n  my $lines = 0;\n  foreach my $k (sort { GetEntry($s, $b) <=> GetEntry($s, $a) || $a cmp $b }\n                 keys(%{$cumulative})) {\n    my $f = GetEntry($flat, $k);\n    my $c = GetEntry($cumulative, $k);\n    $running_sum += $f;\n\n    my $sym = $k;\n    if (exists($symbols->{$k})) {\n      $sym = $symbols->{$k}->[0] . \" \" . $symbols->{$k}->[1];\n      if ($main::opt_addresses) {\n        $sym = $k . \" \" . $sym;\n      }\n    }\n\n    if ($f != 0 || $c != 0) {\n      printf(\"%8s %6s %6s %8s %6s %s\\n\",\n             Unparse($f),\n             Percent($f, $total),\n             Percent($running_sum, $total),\n             Unparse($c),\n             Percent($c, $total),\n             $sym);\n    }\n    $lines++;\n    last if ($line_limit >= 0 && $lines >= $line_limit);\n  }\n}\n\n# Callgrind format has a compression for repeated function and file\n# names.  You show the name the first time, and just use its number\n# subsequently.  This can cut down the file to about a third or a\n# quarter of its uncompressed size.  $key and $val are the key/value\n# pair that would normally be printed by callgrind; $map is a map from\n# value to number.\nsub CompressedCGName {\n  my($key, $val, $map) = @_;\n  my $idx = $map->{$val};\n  # For very short keys, providing an index hurts rather than helps.\n  if (length($val) <= 3) {\n    return \"$key=$val\\n\";\n  } elsif (defined($idx)) {\n    return \"$key=($idx)\\n\";\n  } else {\n    # scalar(keys $map) gives the number of items in the map.\n    $idx = scalar(keys(%{$map})) + 1;\n    $map->{$val} = $idx;\n    return \"$key=($idx) $val\\n\";\n  }\n}\n\n# Print the call graph in a way that's suiteable for callgrind.\nsub PrintCallgrind {\n  my $calls = shift;\n  my $filename;\n  my %filename_to_index_map;\n  my %fnname_to_index_map;\n\n  if ($main::opt_interactive) {\n    $filename = shift;\n    print STDERR \"Writing callgrind file to '$filename'.\\n\"\n  } else {\n    $filename = \"&STDOUT\";\n  }\n  open(CG, \">$filename\");\n  printf CG (\"events: Hits\\n\\n\");\n  foreach my $call ( map { $_->[0] }\n                     sort { $a->[1] cmp $b ->[1] ||\n                            $a->[2] <=> $b->[2] }\n                     map { /([^:]+):(\\d+):([^ ]+)( -> ([^:]+):(\\d+):(.+))?/;\n                           [$_, $1, $2] }\n                     keys %$calls ) {\n    my $count = int($calls->{$call});\n    $call =~ /([^:]+):(\\d+):([^ ]+)( -> ([^:]+):(\\d+):(.+))?/;\n    my ( $caller_file, $caller_line, $caller_function,\n         $callee_file, $callee_line, $callee_function ) =\n       ( $1, $2, $3, $5, $6, $7 );\n\n    # TODO(csilvers): for better compression, collect all the\n    # caller/callee_files and functions first, before printing\n    # anything, and only compress those referenced more than once.\n    printf CG CompressedCGName(\"fl\", $caller_file, \\%filename_to_index_map);\n    printf CG CompressedCGName(\"fn\", $caller_function, \\%fnname_to_index_map);\n    if (defined $6) {\n      printf CG CompressedCGName(\"cfl\", $callee_file, \\%filename_to_index_map);\n      printf CG CompressedCGName(\"cfn\", $callee_function, \\%fnname_to_index_map);\n      printf CG (\"calls=$count $callee_line\\n\");\n    }\n    printf CG (\"$caller_line $count\\n\\n\");\n  }\n}\n\n# Print disassembly for all all routines that match $main::opt_disasm\nsub PrintDisassembly {\n  my $libs = shift;\n  my $flat = shift;\n  my $cumulative = shift;\n  my $disasm_opts = shift;\n\n  my $total = TotalProfile($flat);\n\n  foreach my $lib (@{$libs}) {\n    my $symbol_table = GetProcedureBoundaries($lib->[0], $disasm_opts);\n    my $offset = AddressSub($lib->[1], $lib->[3]);\n    foreach my $routine (sort ByName keys(%{$symbol_table})) {\n      my $start_addr = $symbol_table->{$routine}->[0];\n      my $end_addr = $symbol_table->{$routine}->[1];\n      # See if there are any samples in this routine\n      my $length = hex(AddressSub($end_addr, $start_addr));\n      my $addr = AddressAdd($start_addr, $offset);\n      for (my $i = 0; $i < $length; $i++) {\n        if (defined($cumulative->{$addr})) {\n          PrintDisassembledFunction($lib->[0], $offset,\n                                    $routine, $flat, $cumulative,\n                                    $start_addr, $end_addr, $total);\n          last;\n        }\n        $addr = AddressInc($addr);\n      }\n    }\n  }\n}\n\n# Return reference to array of tuples of the form:\n#       [start_address, filename, linenumber, instruction, limit_address]\n# E.g.,\n#       [\"0x806c43d\", \"/foo/bar.cc\", 131, \"ret\", \"0x806c440\"]\nsub Disassemble {\n  my $prog = shift;\n  my $offset = shift;\n  my $start_addr = shift;\n  my $end_addr = shift;\n\n  my $objdump = $obj_tool_map{\"objdump\"};\n  my $cmd = ShellEscape($objdump, \"-C\", \"-d\", \"-l\", \"--no-show-raw-insn\",\n                        \"--start-address=0x$start_addr\",\n                        \"--stop-address=0x$end_addr\", $prog);\n  open(OBJDUMP, \"$cmd |\") || error(\"$cmd: $!\\n\");\n  my @result = ();\n  my $filename = \"\";\n  my $linenumber = -1;\n  my $last = [\"\", \"\", \"\", \"\"];\n  while (<OBJDUMP>) {\n    s/\\r//g;         # turn windows-looking lines into unix-looking lines\n    chop;\n    if (m|\\s*([^:\\s]+):(\\d+)\\s*$|) {\n      # Location line of the form:\n      #   <filename>:<linenumber>\n      $filename = $1;\n      $linenumber = $2;\n    } elsif (m/^ +([0-9a-f]+):\\s*(.*)/) {\n      # Disassembly line -- zero-extend address to full length\n      my $addr = HexExtend($1);\n      my $k = AddressAdd($addr, $offset);\n      $last->[4] = $k;   # Store ending address for previous instruction\n      $last = [$k, $filename, $linenumber, $2, $end_addr];\n      push(@result, $last);\n    }\n  }\n  close(OBJDUMP);\n  return @result;\n}\n\n# The input file should contain lines of the form /proc/maps-like\n# output (same format as expected from the profiles) or that looks\n# like hex addresses (like \"0xDEADBEEF\").  We will parse all\n# /proc/maps output, and for all the hex addresses, we will output\n# \"short\" symbol names, one per line, in the same order as the input.\nsub PrintSymbols {\n  my $maps_and_symbols_file = shift;\n\n  # ParseLibraries expects pcs to be in a set.  Fine by us...\n  my @pclist = ();   # pcs in sorted order\n  my $pcs = {};\n  my $map = \"\";\n  foreach my $line (<$maps_and_symbols_file>) {\n    $line =~ s/\\r//g;    # turn windows-looking lines into unix-looking lines\n    if ($line =~ /\\b(0x[0-9a-f]+)\\b/i) {\n      push(@pclist, HexExtend($1));\n      $pcs->{$pclist[-1]} = 1;\n    } else {\n      $map .= $line;\n    }\n  }\n\n  my $libs = ParseLibraries($main::prog, $map, $pcs);\n  my $symbols = ExtractSymbols($libs, $pcs);\n\n  foreach my $pc (@pclist) {\n    # ->[0] is the shortname, ->[2] is the full name\n    print(($symbols->{$pc}->[0] || \"??\") . \"\\n\");\n  }\n}\n\n\n# For sorting functions by name\nsub ByName {\n  return ShortFunctionName($a) cmp ShortFunctionName($b);\n}\n\n# Print source-listing for all all routines that match $list_opts\nsub PrintListing {\n  my $total = shift;\n  my $libs = shift;\n  my $flat = shift;\n  my $cumulative = shift;\n  my $list_opts = shift;\n  my $html = shift;\n\n  my $output = \\*STDOUT;\n  my $fname = \"\";\n\n  if ($html) {\n    # Arrange to write the output to a temporary file\n    $fname = TempName($main::next_tmpfile, \"html\");\n    $main::next_tmpfile++;\n    if (!open(TEMP, \">$fname\")) {\n      print STDERR \"$fname: $!\\n\";\n      return;\n    }\n    $output = \\*TEMP;\n    print $output HtmlListingHeader();\n    printf $output (\"<div class=\\\"legend\\\">%s<br>Total: %s %s</div>\\n\",\n                    $main::prog, Unparse($total), Units());\n  }\n\n  my $listed = 0;\n  foreach my $lib (@{$libs}) {\n    my $symbol_table = GetProcedureBoundaries($lib->[0], $list_opts);\n    my $offset = AddressSub($lib->[1], $lib->[3]);\n    foreach my $routine (sort ByName keys(%{$symbol_table})) {\n      # Print if there are any samples in this routine\n      my $start_addr = $symbol_table->{$routine}->[0];\n      my $end_addr = $symbol_table->{$routine}->[1];\n      my $length = hex(AddressSub($end_addr, $start_addr));\n      my $addr = AddressAdd($start_addr, $offset);\n      for (my $i = 0; $i < $length; $i++) {\n        if (defined($cumulative->{$addr})) {\n          $listed += PrintSource(\n            $lib->[0], $offset,\n            $routine, $flat, $cumulative,\n            $start_addr, $end_addr,\n            $html,\n            $output);\n          last;\n        }\n        $addr = AddressInc($addr);\n      }\n    }\n  }\n\n  if ($html) {\n    if ($listed > 0) {\n      print $output HtmlListingFooter();\n      close($output);\n      RunWeb($fname);\n    } else {\n      close($output);\n      unlink($fname);\n    }\n  }\n}\n\nsub HtmlListingHeader {\n  return <<'EOF';\n<DOCTYPE html>\n<html>\n<head>\n<title>Pprof listing</title>\n<style type=\"text/css\">\nbody {\n  font-family: sans-serif;\n}\nh1 {\n  font-size: 1.5em;\n  margin-bottom: 4px;\n}\n.legend {\n  font-size: 1.25em;\n}\n.line {\n  color: #aaaaaa;\n}\n.nop {\n  color: #aaaaaa;\n}\n.unimportant {\n  color: #cccccc;\n}\n.disasmloc {\n  color: #000000;\n}\n.deadsrc {\n  cursor: pointer;\n}\n.deadsrc:hover {\n  background-color: #eeeeee;\n}\n.livesrc {\n  color: #0000ff;\n  cursor: pointer;\n}\n.livesrc:hover {\n  background-color: #eeeeee;\n}\n.asm {\n  color: #008800;\n  display: none;\n}\n</style>\n<script type=\"text/javascript\">\nfunction jeprof_toggle_asm(e) {\n  var target;\n  if (!e) e = window.event;\n  if (e.target) target = e.target;\n  else if (e.srcElement) target = e.srcElement;\n\n  if (target) {\n    var asm = target.nextSibling;\n    if (asm && asm.className == \"asm\") {\n      asm.style.display = (asm.style.display == \"block\" ? \"\" : \"block\");\n      e.preventDefault();\n      return false;\n    }\n  }\n}\n</script>\n</head>\n<body>\nEOF\n}\n\nsub HtmlListingFooter {\n  return <<'EOF';\n</body>\n</html>\nEOF\n}\n\nsub HtmlEscape {\n  my $text = shift;\n  $text =~ s/&/&amp;/g;\n  $text =~ s/</&lt;/g;\n  $text =~ s/>/&gt;/g;\n  return $text;\n}\n\n# Returns the indentation of the line, if it has any non-whitespace\n# characters.  Otherwise, returns -1.\nsub Indentation {\n  my $line = shift;\n  if (m/^(\\s*)\\S/) {\n    return length($1);\n  } else {\n    return -1;\n  }\n}\n\n# If the symbol table contains inlining info, Disassemble() may tag an\n# instruction with a location inside an inlined function.  But for\n# source listings, we prefer to use the location in the function we\n# are listing.  So use MapToSymbols() to fetch full location\n# information for each instruction and then pick out the first\n# location from a location list (location list contains callers before\n# callees in case of inlining).\n#\n# After this routine has run, each entry in $instructions contains:\n#   [0] start address\n#   [1] filename for function we are listing\n#   [2] line number for function we are listing\n#   [3] disassembly\n#   [4] limit address\n#   [5] most specific filename (may be different from [1] due to inlining)\n#   [6] most specific line number (may be different from [2] due to inlining)\nsub GetTopLevelLineNumbers {\n  my ($lib, $offset, $instructions) = @_;\n  my $pcs = [];\n  for (my $i = 0; $i <= $#{$instructions}; $i++) {\n    push(@{$pcs}, $instructions->[$i]->[0]);\n  }\n  my $symbols = {};\n  MapToSymbols($lib, $offset, $pcs, $symbols);\n  for (my $i = 0; $i <= $#{$instructions}; $i++) {\n    my $e = $instructions->[$i];\n    push(@{$e}, $e->[1]);\n    push(@{$e}, $e->[2]);\n    my $addr = $e->[0];\n    my $sym = $symbols->{$addr};\n    if (defined($sym)) {\n      if ($#{$sym} >= 2 && $sym->[1] =~ m/^(.*):(\\d+)$/) {\n        $e->[1] = $1;  # File name\n        $e->[2] = $2;  # Line number\n      }\n    }\n  }\n}\n\n# Print source-listing for one routine\nsub PrintSource {\n  my $prog = shift;\n  my $offset = shift;\n  my $routine = shift;\n  my $flat = shift;\n  my $cumulative = shift;\n  my $start_addr = shift;\n  my $end_addr = shift;\n  my $html = shift;\n  my $output = shift;\n\n  # Disassemble all instructions (just to get line numbers)\n  my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);\n  GetTopLevelLineNumbers($prog, $offset, \\@instructions);\n\n  # Hack 1: assume that the first source file encountered in the\n  # disassembly contains the routine\n  my $filename = undef;\n  for (my $i = 0; $i <= $#instructions; $i++) {\n    if ($instructions[$i]->[2] >= 0) {\n      $filename = $instructions[$i]->[1];\n      last;\n    }\n  }\n  if (!defined($filename)) {\n    print STDERR \"no filename found in $routine\\n\";\n    return 0;\n  }\n\n  # Hack 2: assume that the largest line number from $filename is the\n  # end of the procedure.  This is typically safe since if P1 contains\n  # an inlined call to P2, then P2 usually occurs earlier in the\n  # source file.  If this does not work, we might have to compute a\n  # density profile or just print all regions we find.\n  my $lastline = 0;\n  for (my $i = 0; $i <= $#instructions; $i++) {\n    my $f = $instructions[$i]->[1];\n    my $l = $instructions[$i]->[2];\n    if (($f eq $filename) && ($l > $lastline)) {\n      $lastline = $l;\n    }\n  }\n\n  # Hack 3: assume the first source location from \"filename\" is the start of\n  # the source code.\n  my $firstline = 1;\n  for (my $i = 0; $i <= $#instructions; $i++) {\n    if ($instructions[$i]->[1] eq $filename) {\n      $firstline = $instructions[$i]->[2];\n      last;\n    }\n  }\n\n  # Hack 4: Extend last line forward until its indentation is less than\n  # the indentation we saw on $firstline\n  my $oldlastline = $lastline;\n  {\n    if (!open(FILE, \"<$filename\")) {\n      print STDERR \"$filename: $!\\n\";\n      return 0;\n    }\n    my $l = 0;\n    my $first_indentation = -1;\n    while (<FILE>) {\n      s/\\r//g;         # turn windows-looking lines into unix-looking lines\n      $l++;\n      my $indent = Indentation($_);\n      if ($l >= $firstline) {\n        if ($first_indentation < 0 && $indent >= 0) {\n          $first_indentation = $indent;\n          last if ($first_indentation == 0);\n        }\n      }\n      if ($l >= $lastline && $indent >= 0) {\n        if ($indent >= $first_indentation) {\n          $lastline = $l+1;\n        } else {\n          last;\n        }\n      }\n    }\n    close(FILE);\n  }\n\n  # Assign all samples to the range $firstline,$lastline,\n  # Hack 4: If an instruction does not occur in the range, its samples\n  # are moved to the next instruction that occurs in the range.\n  my $samples1 = {};        # Map from line number to flat count\n  my $samples2 = {};        # Map from line number to cumulative count\n  my $running1 = 0;         # Unassigned flat counts\n  my $running2 = 0;         # Unassigned cumulative counts\n  my $total1 = 0;           # Total flat counts\n  my $total2 = 0;           # Total cumulative counts\n  my %disasm = ();          # Map from line number to disassembly\n  my $running_disasm = \"\";  # Unassigned disassembly\n  my $skip_marker = \"---\\n\";\n  if ($html) {\n    $skip_marker = \"\";\n    for (my $l = $firstline; $l <= $lastline; $l++) {\n      $disasm{$l} = \"\";\n    }\n  }\n  my $last_dis_filename = '';\n  my $last_dis_linenum = -1;\n  my $last_touched_line = -1;  # To detect gaps in disassembly for a line\n  foreach my $e (@instructions) {\n    # Add up counts for all address that fall inside this instruction\n    my $c1 = 0;\n    my $c2 = 0;\n    for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {\n      $c1 += GetEntry($flat, $a);\n      $c2 += GetEntry($cumulative, $a);\n    }\n\n    if ($html) {\n      my $dis = sprintf(\"      %6s %6s \\t\\t%8s: %s \",\n                        HtmlPrintNumber($c1),\n                        HtmlPrintNumber($c2),\n                        UnparseAddress($offset, $e->[0]),\n                        CleanDisassembly($e->[3]));\n\n      # Append the most specific source line associated with this instruction\n      if (length($dis) < 80) { $dis .= (' ' x (80 - length($dis))) };\n      $dis = HtmlEscape($dis);\n      my $f = $e->[5];\n      my $l = $e->[6];\n      if ($f ne $last_dis_filename) {\n        $dis .= sprintf(\"<span class=disasmloc>%s:%d</span>\",\n                        HtmlEscape(CleanFileName($f)), $l);\n      } elsif ($l ne $last_dis_linenum) {\n        # De-emphasize the unchanged file name portion\n        $dis .= sprintf(\"<span class=unimportant>%s</span>\" .\n                        \"<span class=disasmloc>:%d</span>\",\n                        HtmlEscape(CleanFileName($f)), $l);\n      } else {\n        # De-emphasize the entire location\n        $dis .= sprintf(\"<span class=unimportant>%s:%d</span>\",\n                        HtmlEscape(CleanFileName($f)), $l);\n      }\n      $last_dis_filename = $f;\n      $last_dis_linenum = $l;\n      $running_disasm .= $dis;\n      $running_disasm .= \"\\n\";\n    }\n\n    $running1 += $c1;\n    $running2 += $c2;\n    $total1 += $c1;\n    $total2 += $c2;\n    my $file = $e->[1];\n    my $line = $e->[2];\n    if (($file eq $filename) &&\n        ($line >= $firstline) &&\n        ($line <= $lastline)) {\n      # Assign all accumulated samples to this line\n      AddEntry($samples1, $line, $running1);\n      AddEntry($samples2, $line, $running2);\n      $running1 = 0;\n      $running2 = 0;\n      if ($html) {\n        if ($line != $last_touched_line && $disasm{$line} ne '') {\n          $disasm{$line} .= \"\\n\";\n        }\n        $disasm{$line} .= $running_disasm;\n        $running_disasm = '';\n        $last_touched_line = $line;\n      }\n    }\n  }\n\n  # Assign any leftover samples to $lastline\n  AddEntry($samples1, $lastline, $running1);\n  AddEntry($samples2, $lastline, $running2);\n  if ($html) {\n    if ($lastline != $last_touched_line && $disasm{$lastline} ne '') {\n      $disasm{$lastline} .= \"\\n\";\n    }\n    $disasm{$lastline} .= $running_disasm;\n  }\n\n  if ($html) {\n    printf $output (\n      \"<h1>%s</h1>%s\\n<pre onClick=\\\"jeprof_toggle_asm()\\\">\\n\" .\n      \"Total:%6s %6s (flat / cumulative %s)\\n\",\n      HtmlEscape(ShortFunctionName($routine)),\n      HtmlEscape(CleanFileName($filename)),\n      Unparse($total1),\n      Unparse($total2),\n      Units());\n  } else {\n    printf $output (\n      \"ROUTINE ====================== %s in %s\\n\" .\n      \"%6s %6s Total %s (flat / cumulative)\\n\",\n      ShortFunctionName($routine),\n      CleanFileName($filename),\n      Unparse($total1),\n      Unparse($total2),\n      Units());\n  }\n  if (!open(FILE, \"<$filename\")) {\n    print STDERR \"$filename: $!\\n\";\n    return 0;\n  }\n  my $l = 0;\n  while (<FILE>) {\n    s/\\r//g;         # turn windows-looking lines into unix-looking lines\n    $l++;\n    if ($l >= $firstline - 5 &&\n        (($l <= $oldlastline + 5) || ($l <= $lastline))) {\n      chop;\n      my $text = $_;\n      if ($l == $firstline) { print $output $skip_marker; }\n      my $n1 = GetEntry($samples1, $l);\n      my $n2 = GetEntry($samples2, $l);\n      if ($html) {\n        # Emit a span that has one of the following classes:\n        #    livesrc -- has samples\n        #    deadsrc -- has disassembly, but with no samples\n        #    nop     -- has no matching disasembly\n        # Also emit an optional span containing disassembly.\n        my $dis = $disasm{$l};\n        my $asm = \"\";\n        if (defined($dis) && $dis ne '') {\n          $asm = \"<span class=\\\"asm\\\">\" . $dis . \"</span>\";\n        }\n        my $source_class = (($n1 + $n2 > 0)\n                            ? \"livesrc\"\n                            : (($asm ne \"\") ? \"deadsrc\" : \"nop\"));\n        printf $output (\n          \"<span class=\\\"line\\\">%5d</span> \" .\n          \"<span class=\\\"%s\\\">%6s %6s %s</span>%s\\n\",\n          $l, $source_class,\n          HtmlPrintNumber($n1),\n          HtmlPrintNumber($n2),\n          HtmlEscape($text),\n          $asm);\n      } else {\n        printf $output(\n          \"%6s %6s %4d: %s\\n\",\n          UnparseAlt($n1),\n          UnparseAlt($n2),\n          $l,\n          $text);\n      }\n      if ($l == $lastline)  { print $output $skip_marker; }\n    };\n  }\n  close(FILE);\n  if ($html) {\n    print $output \"</pre>\\n\";\n  }\n  return 1;\n}\n\n# Return the source line for the specified file/linenumber.\n# Returns undef if not found.\nsub SourceLine {\n  my $file = shift;\n  my $line = shift;\n\n  # Look in cache\n  if (!defined($main::source_cache{$file})) {\n    if (100 < scalar keys(%main::source_cache)) {\n      # Clear the cache when it gets too big\n      $main::source_cache = ();\n    }\n\n    # Read all lines from the file\n    if (!open(FILE, \"<$file\")) {\n      print STDERR \"$file: $!\\n\";\n      $main::source_cache{$file} = [];  # Cache the negative result\n      return undef;\n    }\n    my $lines = [];\n    push(@{$lines}, \"\");        # So we can use 1-based line numbers as indices\n    while (<FILE>) {\n      push(@{$lines}, $_);\n    }\n    close(FILE);\n\n    # Save the lines in the cache\n    $main::source_cache{$file} = $lines;\n  }\n\n  my $lines = $main::source_cache{$file};\n  if (($line < 0) || ($line > $#{$lines})) {\n    return undef;\n  } else {\n    return $lines->[$line];\n  }\n}\n\n# Print disassembly for one routine with interspersed source if available\nsub PrintDisassembledFunction {\n  my $prog = shift;\n  my $offset = shift;\n  my $routine = shift;\n  my $flat = shift;\n  my $cumulative = shift;\n  my $start_addr = shift;\n  my $end_addr = shift;\n  my $total = shift;\n\n  # Disassemble all instructions\n  my @instructions = Disassemble($prog, $offset, $start_addr, $end_addr);\n\n  # Make array of counts per instruction\n  my @flat_count = ();\n  my @cum_count = ();\n  my $flat_total = 0;\n  my $cum_total = 0;\n  foreach my $e (@instructions) {\n    # Add up counts for all address that fall inside this instruction\n    my $c1 = 0;\n    my $c2 = 0;\n    for (my $a = $e->[0]; $a lt $e->[4]; $a = AddressInc($a)) {\n      $c1 += GetEntry($flat, $a);\n      $c2 += GetEntry($cumulative, $a);\n    }\n    push(@flat_count, $c1);\n    push(@cum_count, $c2);\n    $flat_total += $c1;\n    $cum_total += $c2;\n  }\n\n  # Print header with total counts\n  printf(\"ROUTINE ====================== %s\\n\" .\n         \"%6s %6s %s (flat, cumulative) %.1f%% of total\\n\",\n         ShortFunctionName($routine),\n         Unparse($flat_total),\n         Unparse($cum_total),\n         Units(),\n         ($cum_total * 100.0) / $total);\n\n  # Process instructions in order\n  my $current_file = \"\";\n  for (my $i = 0; $i <= $#instructions; ) {\n    my $e = $instructions[$i];\n\n    # Print the new file name whenever we switch files\n    if ($e->[1] ne $current_file) {\n      $current_file = $e->[1];\n      my $fname = $current_file;\n      $fname =~ s|^\\./||;   # Trim leading \"./\"\n\n      # Shorten long file names\n      if (length($fname) >= 58) {\n        $fname = \"...\" . substr($fname, -55);\n      }\n      printf(\"-------------------- %s\\n\", $fname);\n    }\n\n    # TODO: Compute range of lines to print together to deal with\n    # small reorderings.\n    my $first_line = $e->[2];\n    my $last_line = $first_line;\n    my %flat_sum = ();\n    my %cum_sum = ();\n    for (my $l = $first_line; $l <= $last_line; $l++) {\n      $flat_sum{$l} = 0;\n      $cum_sum{$l} = 0;\n    }\n\n    # Find run of instructions for this range of source lines\n    my $first_inst = $i;\n    while (($i <= $#instructions) &&\n           ($instructions[$i]->[2] >= $first_line) &&\n           ($instructions[$i]->[2] <= $last_line)) {\n      $e = $instructions[$i];\n      $flat_sum{$e->[2]} += $flat_count[$i];\n      $cum_sum{$e->[2]} += $cum_count[$i];\n      $i++;\n    }\n    my $last_inst = $i - 1;\n\n    # Print source lines\n    for (my $l = $first_line; $l <= $last_line; $l++) {\n      my $line = SourceLine($current_file, $l);\n      if (!defined($line)) {\n        $line = \"?\\n\";\n        next;\n      } else {\n        $line =~ s/^\\s+//;\n      }\n      printf(\"%6s %6s %5d: %s\",\n             UnparseAlt($flat_sum{$l}),\n             UnparseAlt($cum_sum{$l}),\n             $l,\n             $line);\n    }\n\n    # Print disassembly\n    for (my $x = $first_inst; $x <= $last_inst; $x++) {\n      my $e = $instructions[$x];\n      printf(\"%6s %6s    %8s: %6s\\n\",\n             UnparseAlt($flat_count[$x]),\n             UnparseAlt($cum_count[$x]),\n             UnparseAddress($offset, $e->[0]),\n             CleanDisassembly($e->[3]));\n    }\n  }\n}\n\n# Print DOT graph\nsub PrintDot {\n  my $prog = shift;\n  my $symbols = shift;\n  my $raw = shift;\n  my $flat = shift;\n  my $cumulative = shift;\n  my $overall_total = shift;\n\n  # Get total\n  my $local_total = TotalProfile($flat);\n  my $nodelimit = int($main::opt_nodefraction * $local_total);\n  my $edgelimit = int($main::opt_edgefraction * $local_total);\n  my $nodecount = $main::opt_nodecount;\n\n  # Find nodes to include\n  my @list = (sort { abs(GetEntry($cumulative, $b)) <=>\n                     abs(GetEntry($cumulative, $a))\n                     || $a cmp $b }\n              keys(%{$cumulative}));\n  my $last = $nodecount - 1;\n  if ($last > $#list) {\n    $last = $#list;\n  }\n  while (($last >= 0) &&\n         (abs(GetEntry($cumulative, $list[$last])) <= $nodelimit)) {\n    $last--;\n  }\n  if ($last < 0) {\n    print STDERR \"No nodes to print\\n\";\n    return 0;\n  }\n\n  if ($nodelimit > 0 || $edgelimit > 0) {\n    printf STDERR (\"Dropping nodes with <= %s %s; edges with <= %s abs(%s)\\n\",\n                   Unparse($nodelimit), Units(),\n                   Unparse($edgelimit), Units());\n  }\n\n  # Open DOT output file\n  my $output;\n  my $escaped_dot = ShellEscape(@DOT);\n  my $escaped_ps2pdf = ShellEscape(@PS2PDF);\n  if ($main::opt_gv) {\n    my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, \"ps\"));\n    $output = \"| $escaped_dot -Tps2 >$escaped_outfile\";\n  } elsif ($main::opt_evince) {\n    my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, \"pdf\"));\n    $output = \"| $escaped_dot -Tps2 | $escaped_ps2pdf - $escaped_outfile\";\n  } elsif ($main::opt_ps) {\n    $output = \"| $escaped_dot -Tps2\";\n  } elsif ($main::opt_pdf) {\n    $output = \"| $escaped_dot -Tps2 | $escaped_ps2pdf - -\";\n  } elsif ($main::opt_web || $main::opt_svg) {\n    # We need to post-process the SVG, so write to a temporary file always.\n    my $escaped_outfile = ShellEscape(TempName($main::next_tmpfile, \"svg\"));\n    $output = \"| $escaped_dot -Tsvg >$escaped_outfile\";\n  } elsif ($main::opt_gif) {\n    $output = \"| $escaped_dot -Tgif\";\n  } else {\n    $output = \">&STDOUT\";\n  }\n  open(DOT, $output) || error(\"$output: $!\\n\");\n\n  # Title\n  printf DOT (\"digraph \\\"%s; %s %s\\\" {\\n\",\n              $prog,\n              Unparse($overall_total),\n              Units());\n  if ($main::opt_pdf) {\n    # The output is more printable if we set the page size for dot.\n    printf DOT (\"size=\\\"8,11\\\"\\n\");\n  }\n  printf DOT (\"node [width=0.375,height=0.25];\\n\");\n\n  # Print legend\n  printf DOT (\"Legend [shape=box,fontsize=24,shape=plaintext,\" .\n              \"label=\\\"%s\\\\l%s\\\\l%s\\\\l%s\\\\l%s\\\\l\\\"];\\n\",\n              $prog,\n              sprintf(\"Total %s: %s\", Units(), Unparse($overall_total)),\n              sprintf(\"Focusing on: %s\", Unparse($local_total)),\n              sprintf(\"Dropped nodes with <= %s abs(%s)\",\n                      Unparse($nodelimit), Units()),\n              sprintf(\"Dropped edges with <= %s %s\",\n                      Unparse($edgelimit), Units())\n              );\n\n  # Print nodes\n  my %node = ();\n  my $nextnode = 1;\n  foreach my $a (@list[0..$last]) {\n    # Pick font size\n    my $f = GetEntry($flat, $a);\n    my $c = GetEntry($cumulative, $a);\n\n    my $fs = 8;\n    if ($local_total > 0) {\n      $fs = 8 + (50.0 * sqrt(abs($f * 1.0 / $local_total)));\n    }\n\n    $node{$a} = $nextnode++;\n    my $sym = $a;\n    $sym =~ s/\\s+/\\\\n/g;\n    $sym =~ s/::/\\\\n/g;\n\n    # Extra cumulative info to print for non-leaves\n    my $extra = \"\";\n    if ($f != $c) {\n      $extra = sprintf(\"\\\\rof %s (%s)\",\n                       Unparse($c),\n                       Percent($c, $local_total));\n    }\n    my $style = \"\";\n    if ($main::opt_heapcheck) {\n      if ($f > 0) {\n        # make leak-causing nodes more visible (add a background)\n        $style = \",style=filled,fillcolor=gray\"\n      } elsif ($f < 0) {\n        # make anti-leak-causing nodes (which almost never occur)\n        # stand out as well (triple border)\n        $style = \",peripheries=3\"\n      }\n    }\n\n    printf DOT (\"N%d [label=\\\"%s\\\\n%s (%s)%s\\\\r\" .\n                \"\\\",shape=box,fontsize=%.1f%s];\\n\",\n                $node{$a},\n                $sym,\n                Unparse($f),\n                Percent($f, $local_total),\n                $extra,\n                $fs,\n                $style,\n               );\n  }\n\n  # Get edges and counts per edge\n  my %edge = ();\n  my $n;\n  my $fullname_to_shortname_map = {};\n  FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map);\n  foreach my $k (keys(%{$raw})) {\n    # TODO: omit low %age edges\n    $n = $raw->{$k};\n    my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k);\n    for (my $i = 1; $i <= $#translated; $i++) {\n      my $src = $translated[$i];\n      my $dst = $translated[$i-1];\n      #next if ($src eq $dst);  # Avoid self-edges?\n      if (exists($node{$src}) && exists($node{$dst})) {\n        my $edge_label = \"$src\\001$dst\";\n        if (!exists($edge{$edge_label})) {\n          $edge{$edge_label} = 0;\n        }\n        $edge{$edge_label} += $n;\n      }\n    }\n  }\n\n  # Print edges (process in order of decreasing counts)\n  my %indegree = ();   # Number of incoming edges added per node so far\n  my %outdegree = ();  # Number of outgoing edges added per node so far\n  foreach my $e (sort { $edge{$b} <=> $edge{$a} } keys(%edge)) {\n    my @x = split(/\\001/, $e);\n    $n = $edge{$e};\n\n    # Initialize degree of kept incoming and outgoing edges if necessary\n    my $src = $x[0];\n    my $dst = $x[1];\n    if (!exists($outdegree{$src})) { $outdegree{$src} = 0; }\n    if (!exists($indegree{$dst})) { $indegree{$dst} = 0; }\n\n    my $keep;\n    if ($indegree{$dst} == 0) {\n      # Keep edge if needed for reachability\n      $keep = 1;\n    } elsif (abs($n) <= $edgelimit) {\n      # Drop if we are below --edgefraction\n      $keep = 0;\n    } elsif ($outdegree{$src} >= $main::opt_maxdegree ||\n             $indegree{$dst} >= $main::opt_maxdegree) {\n      # Keep limited number of in/out edges per node\n      $keep = 0;\n    } else {\n      $keep = 1;\n    }\n\n    if ($keep) {\n      $outdegree{$src}++;\n      $indegree{$dst}++;\n\n      # Compute line width based on edge count\n      my $fraction = abs($local_total ? (3 * ($n / $local_total)) : 0);\n      if ($fraction > 1) { $fraction = 1; }\n      my $w = $fraction * 2;\n      if ($w < 1 && ($main::opt_web || $main::opt_svg)) {\n        # SVG output treats line widths < 1 poorly.\n        $w = 1;\n      }\n\n      # Dot sometimes segfaults if given edge weights that are too large, so\n      # we cap the weights at a large value\n      my $edgeweight = abs($n) ** 0.7;\n      if ($edgeweight > 100000) { $edgeweight = 100000; }\n      $edgeweight = int($edgeweight);\n\n      my $style = sprintf(\"setlinewidth(%f)\", $w);\n      if ($x[1] =~ m/\\(inline\\)/) {\n        $style .= \",dashed\";\n      }\n\n      # Use a slightly squashed function of the edge count as the weight\n      printf DOT (\"N%s -> N%s [label=%s, weight=%d, style=\\\"%s\\\"];\\n\",\n                  $node{$x[0]},\n                  $node{$x[1]},\n                  Unparse($n),\n                  $edgeweight,\n                  $style);\n    }\n  }\n\n  print DOT (\"}\\n\");\n  close(DOT);\n\n  if ($main::opt_web || $main::opt_svg) {\n    # Rewrite SVG to be more usable inside web browser.\n    RewriteSvg(TempName($main::next_tmpfile, \"svg\"));\n  }\n\n  return 1;\n}\n\nsub RewriteSvg {\n  my $svgfile = shift;\n\n  open(SVG, $svgfile) || die \"open temp svg: $!\";\n  my @svg = <SVG>;\n  close(SVG);\n  unlink $svgfile;\n  my $svg = join('', @svg);\n\n  # Dot's SVG output is\n  #\n  #    <svg width=\"___\" height=\"___\"\n  #     viewBox=\"___\" xmlns=...>\n  #    <g id=\"graph0\" transform=\"...\">\n  #    ...\n  #    </g>\n  #    </svg>\n  #\n  # Change it to\n  #\n  #    <svg width=\"100%\" height=\"100%\"\n  #     xmlns=...>\n  #    $svg_javascript\n  #    <g id=\"viewport\" transform=\"translate(0,0)\">\n  #    <g id=\"graph0\" transform=\"...\">\n  #    ...\n  #    </g>\n  #    </g>\n  #    </svg>\n\n  # Fix width, height; drop viewBox.\n  $svg =~ s/(?s)<svg width=\"[^\"]+\" height=\"[^\"]+\"(.*?)viewBox=\"[^\"]+\"/<svg width=\"100%\" height=\"100%\"$1/;\n\n  # Insert script, viewport <g> above first <g>\n  my $svg_javascript = SvgJavascript();\n  my $viewport = \"<g id=\\\"viewport\\\" transform=\\\"translate(0,0)\\\">\\n\";\n  $svg =~ s/<g id=\"graph\\d\"/$svg_javascript$viewport$&/;\n\n  # Insert final </g> above </svg>.\n  $svg =~ s/(.*)(<\\/svg>)/$1<\\/g>$2/;\n  $svg =~ s/<g id=\"graph\\d\"(.*?)/<g id=\"viewport\"$1/;\n\n  if ($main::opt_svg) {\n    # --svg: write to standard output.\n    print $svg;\n  } else {\n    # Write back to temporary file.\n    open(SVG, \">$svgfile\") || die \"open $svgfile: $!\";\n    print SVG $svg;\n    close(SVG);\n  }\n}\n\nsub SvgJavascript {\n  return <<'EOF';\n<script type=\"text/ecmascript\"><![CDATA[\n// SVGPan\n// http://www.cyberz.org/blog/2009/12/08/svgpan-a-javascript-svg-panzoomdrag-library/\n// Local modification: if(true || ...) below to force panning, never moving.\n\n/**\n *  SVGPan library 1.2\n * ====================\n *\n * Given an unique existing element with id \"viewport\", including the\n * the library into any SVG adds the following capabilities:\n *\n *  - Mouse panning\n *  - Mouse zooming (using the wheel)\n *  - Object dargging\n *\n * Known issues:\n *\n *  - Zooming (while panning) on Safari has still some issues\n *\n * Releases:\n *\n * 1.2, Sat Mar 20 08:42:50 GMT 2010, Zeng Xiaohui\n *\tFixed a bug with browser mouse handler interaction\n *\n * 1.1, Wed Feb  3 17:39:33 GMT 2010, Zeng Xiaohui\n *\tUpdated the zoom code to support the mouse wheel on Safari/Chrome\n *\n * 1.0, Andrea Leofreddi\n *\tFirst release\n *\n * This code is licensed under the following BSD license:\n *\n * Copyright 2009-2010 Andrea Leofreddi <a.leofreddi@itcharm.com>. All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without modification, are\n * permitted provided that the following conditions are met:\n *\n *    1. Redistributions of source code must retain the above copyright notice, this list of\n *       conditions and the following disclaimer.\n *\n *    2. Redistributions in binary form must reproduce the above copyright notice, this list\n *       of conditions and the following disclaimer in the documentation and/or other materials\n *       provided with the distribution.\n *\n * THIS SOFTWARE IS PROVIDED BY Andrea Leofreddi ``AS IS'' AND ANY EXPRESS OR IMPLIED\n * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND\n * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Andrea Leofreddi OR\n * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\n * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\n * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\n * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n *\n * The views and conclusions contained in the software and documentation are those of the\n * authors and should not be interpreted as representing official policies, either expressed\n * or implied, of Andrea Leofreddi.\n */\n\nvar root = document.documentElement;\n\nvar state = 'none', stateTarget, stateOrigin, stateTf;\n\nsetupHandlers(root);\n\n/**\n * Register handlers\n */\nfunction setupHandlers(root){\n\tsetAttributes(root, {\n\t\t\"onmouseup\" : \"add(evt)\",\n\t\t\"onmousedown\" : \"handleMouseDown(evt)\",\n\t\t\"onmousemove\" : \"handleMouseMove(evt)\",\n\t\t\"onmouseup\" : \"handleMouseUp(evt)\",\n\t\t//\"onmouseout\" : \"handleMouseUp(evt)\", // Decomment this to stop the pan functionality when dragging out of the SVG element\n\t});\n\n\tif(navigator.userAgent.toLowerCase().indexOf('webkit') >= 0)\n\t\twindow.addEventListener('mousewheel', handleMouseWheel, false); // Chrome/Safari\n\telse\n\t\twindow.addEventListener('DOMMouseScroll', handleMouseWheel, false); // Others\n\n\tvar g = svgDoc.getElementById(\"svg\");\n\tg.width = \"100%\";\n\tg.height = \"100%\";\n}\n\n/**\n * Instance an SVGPoint object with given event coordinates.\n */\nfunction getEventPoint(evt) {\n\tvar p = root.createSVGPoint();\n\n\tp.x = evt.clientX;\n\tp.y = evt.clientY;\n\n\treturn p;\n}\n\n/**\n * Sets the current transform matrix of an element.\n */\nfunction setCTM(element, matrix) {\n\tvar s = \"matrix(\" + matrix.a + \",\" + matrix.b + \",\" + matrix.c + \",\" + matrix.d + \",\" + matrix.e + \",\" + matrix.f + \")\";\n\n\telement.setAttribute(\"transform\", s);\n}\n\n/**\n * Dumps a matrix to a string (useful for debug).\n */\nfunction dumpMatrix(matrix) {\n\tvar s = \"[ \" + matrix.a + \", \" + matrix.c + \", \" + matrix.e + \"\\n  \" + matrix.b + \", \" + matrix.d + \", \" + matrix.f + \"\\n  0, 0, 1 ]\";\n\n\treturn s;\n}\n\n/**\n * Sets attributes of an element.\n */\nfunction setAttributes(element, attributes){\n\tfor (i in attributes)\n\t\telement.setAttributeNS(null, i, attributes[i]);\n}\n\n/**\n * Handle mouse move event.\n */\nfunction handleMouseWheel(evt) {\n\tif(evt.preventDefault)\n\t\tevt.preventDefault();\n\n\tevt.returnValue = false;\n\n\tvar svgDoc = evt.target.ownerDocument;\n\n\tvar delta;\n\n\tif(evt.wheelDelta)\n\t\tdelta = evt.wheelDelta / 3600; // Chrome/Safari\n\telse\n\t\tdelta = evt.detail / -90; // Mozilla\n\n\tvar z = 1 + delta; // Zoom factor: 0.9/1.1\n\n\tvar g = svgDoc.getElementById(\"viewport\");\n\n\tvar p = getEventPoint(evt);\n\n\tp = p.matrixTransform(g.getCTM().inverse());\n\n\t// Compute new scale matrix in current mouse position\n\tvar k = root.createSVGMatrix().translate(p.x, p.y).scale(z).translate(-p.x, -p.y);\n\n        setCTM(g, g.getCTM().multiply(k));\n\n\tstateTf = stateTf.multiply(k.inverse());\n}\n\n/**\n * Handle mouse move event.\n */\nfunction handleMouseMove(evt) {\n\tif(evt.preventDefault)\n\t\tevt.preventDefault();\n\n\tevt.returnValue = false;\n\n\tvar svgDoc = evt.target.ownerDocument;\n\n\tvar g = svgDoc.getElementById(\"viewport\");\n\n\tif(state == 'pan') {\n\t\t// Pan mode\n\t\tvar p = getEventPoint(evt).matrixTransform(stateTf);\n\n\t\tsetCTM(g, stateTf.inverse().translate(p.x - stateOrigin.x, p.y - stateOrigin.y));\n\t} else if(state == 'move') {\n\t\t// Move mode\n\t\tvar p = getEventPoint(evt).matrixTransform(g.getCTM().inverse());\n\n\t\tsetCTM(stateTarget, root.createSVGMatrix().translate(p.x - stateOrigin.x, p.y - stateOrigin.y).multiply(g.getCTM().inverse()).multiply(stateTarget.getCTM()));\n\n\t\tstateOrigin = p;\n\t}\n}\n\n/**\n * Handle click event.\n */\nfunction handleMouseDown(evt) {\n\tif(evt.preventDefault)\n\t\tevt.preventDefault();\n\n\tevt.returnValue = false;\n\n\tvar svgDoc = evt.target.ownerDocument;\n\n\tvar g = svgDoc.getElementById(\"viewport\");\n\n\tif(true || evt.target.tagName == \"svg\") {\n\t\t// Pan mode\n\t\tstate = 'pan';\n\n\t\tstateTf = g.getCTM().inverse();\n\n\t\tstateOrigin = getEventPoint(evt).matrixTransform(stateTf);\n\t} else {\n\t\t// Move mode\n\t\tstate = 'move';\n\n\t\tstateTarget = evt.target;\n\n\t\tstateTf = g.getCTM().inverse();\n\n\t\tstateOrigin = getEventPoint(evt).matrixTransform(stateTf);\n\t}\n}\n\n/**\n * Handle mouse button release event.\n */\nfunction handleMouseUp(evt) {\n\tif(evt.preventDefault)\n\t\tevt.preventDefault();\n\n\tevt.returnValue = false;\n\n\tvar svgDoc = evt.target.ownerDocument;\n\n\tif(state == 'pan' || state == 'move') {\n\t\t// Quit pan mode\n\t\tstate = '';\n\t}\n}\n\n]]></script>\nEOF\n}\n\n# Provides a map from fullname to shortname for cases where the\n# shortname is ambiguous.  The symlist has both the fullname and\n# shortname for all symbols, which is usually fine, but sometimes --\n# such as overloaded functions -- two different fullnames can map to\n# the same shortname.  In that case, we use the address of the\n# function to disambiguate the two.  This function fills in a map that\n# maps fullnames to modified shortnames in such cases.  If a fullname\n# is not present in the map, the 'normal' shortname provided by the\n# symlist is the appropriate one to use.\nsub FillFullnameToShortnameMap {\n  my $symbols = shift;\n  my $fullname_to_shortname_map = shift;\n  my $shortnames_seen_once = {};\n  my $shortnames_seen_more_than_once = {};\n\n  foreach my $symlist (values(%{$symbols})) {\n    # TODO(csilvers): deal with inlined symbols too.\n    my $shortname = $symlist->[0];\n    my $fullname = $symlist->[2];\n    if ($fullname !~ /<[0-9a-fA-F]+>$/) {  # fullname doesn't end in an address\n      next;       # the only collisions we care about are when addresses differ\n    }\n    if (defined($shortnames_seen_once->{$shortname}) &&\n        $shortnames_seen_once->{$shortname} ne $fullname) {\n      $shortnames_seen_more_than_once->{$shortname} = 1;\n    } else {\n      $shortnames_seen_once->{$shortname} = $fullname;\n    }\n  }\n\n  foreach my $symlist (values(%{$symbols})) {\n    my $shortname = $symlist->[0];\n    my $fullname = $symlist->[2];\n    # TODO(csilvers): take in a list of addresses we care about, and only\n    # store in the map if $symlist->[1] is in that list.  Saves space.\n    next if defined($fullname_to_shortname_map->{$fullname});\n    if (defined($shortnames_seen_more_than_once->{$shortname})) {\n      if ($fullname =~ /<0*([^>]*)>$/) {   # fullname has address at end of it\n        $fullname_to_shortname_map->{$fullname} = \"$shortname\\@$1\";\n      }\n    }\n  }\n}\n\n# Return a small number that identifies the argument.\n# Multiple calls with the same argument will return the same number.\n# Calls with different arguments will return different numbers.\nsub ShortIdFor {\n  my $key = shift;\n  my $id = $main::uniqueid{$key};\n  if (!defined($id)) {\n    $id = keys(%main::uniqueid) + 1;\n    $main::uniqueid{$key} = $id;\n  }\n  return $id;\n}\n\n# Translate a stack of addresses into a stack of symbols\nsub TranslateStack {\n  my $symbols = shift;\n  my $fullname_to_shortname_map = shift;\n  my $k = shift;\n\n  my @addrs = split(/\\n/, $k);\n  my @result = ();\n  for (my $i = 0; $i <= $#addrs; $i++) {\n    my $a = $addrs[$i];\n\n    # Skip large addresses since they sometimes show up as fake entries on RH9\n    if (length($a) > 8 && $a gt \"7fffffffffffffff\") {\n      next;\n    }\n\n    if ($main::opt_disasm || $main::opt_list) {\n      # We want just the address for the key\n      push(@result, $a);\n      next;\n    }\n\n    my $symlist = $symbols->{$a};\n    if (!defined($symlist)) {\n      $symlist = [$a, \"\", $a];\n    }\n\n    # We can have a sequence of symbols for a particular entry\n    # (more than one symbol in the case of inlining).  Callers\n    # come before callees in symlist, so walk backwards since\n    # the translated stack should contain callees before callers.\n    for (my $j = $#{$symlist}; $j >= 2; $j -= 3) {\n      my $func = $symlist->[$j-2];\n      my $fileline = $symlist->[$j-1];\n      my $fullfunc = $symlist->[$j];\n      if (defined($fullname_to_shortname_map->{$fullfunc})) {\n        $func = $fullname_to_shortname_map->{$fullfunc};\n      }\n      if ($j > 2) {\n        $func = \"$func (inline)\";\n      }\n\n      # Do not merge nodes corresponding to Callback::Run since that\n      # causes confusing cycles in dot display.  Instead, we synthesize\n      # a unique name for this frame per caller.\n      if ($func =~ m/Callback.*::Run$/) {\n        my $caller = ($i > 0) ? $addrs[$i-1] : 0;\n        $func = \"Run#\" . ShortIdFor($caller);\n      }\n\n      if ($main::opt_addresses) {\n        push(@result, \"$a $func $fileline\");\n      } elsif ($main::opt_lines) {\n        if ($func eq '??' && $fileline eq '??:0') {\n          push(@result, \"$a\");\n        } else {\n          push(@result, \"$func $fileline\");\n        }\n      } elsif ($main::opt_functions) {\n        if ($func eq '??') {\n          push(@result, \"$a\");\n        } else {\n          push(@result, $func);\n        }\n      } elsif ($main::opt_files) {\n        if ($fileline eq '??:0' || $fileline eq '') {\n          push(@result, \"$a\");\n        } else {\n          my $f = $fileline;\n          $f =~ s/:\\d+$//;\n          push(@result, $f);\n        }\n      } else {\n        push(@result, $a);\n        last;  # Do not print inlined info\n      }\n    }\n  }\n\n  # print join(\",\", @addrs), \" => \", join(\",\", @result), \"\\n\";\n  return @result;\n}\n\n# Generate percent string for a number and a total\nsub Percent {\n  my $num = shift;\n  my $tot = shift;\n  if ($tot != 0) {\n    return sprintf(\"%.1f%%\", $num * 100.0 / $tot);\n  } else {\n    return ($num == 0) ? \"nan\" : (($num > 0) ? \"+inf\" : \"-inf\");\n  }\n}\n\n# Generate pretty-printed form of number\nsub Unparse {\n  my $num = shift;\n  if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {\n    if ($main::opt_inuse_objects || $main::opt_alloc_objects) {\n      return sprintf(\"%d\", $num);\n    } else {\n      if ($main::opt_show_bytes) {\n        return sprintf(\"%d\", $num);\n      } else {\n        return sprintf(\"%.1f\", $num / 1048576.0);\n      }\n    }\n  } elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) {\n    return sprintf(\"%.3f\", $num / 1e9); # Convert nanoseconds to seconds\n  } else {\n    return sprintf(\"%d\", $num);\n  }\n}\n\n# Alternate pretty-printed form: 0 maps to \".\"\nsub UnparseAlt {\n  my $num = shift;\n  if ($num == 0) {\n    return \".\";\n  } else {\n    return Unparse($num);\n  }\n}\n\n# Alternate pretty-printed form: 0 maps to \"\"\nsub HtmlPrintNumber {\n  my $num = shift;\n  if ($num == 0) {\n    return \"\";\n  } else {\n    return Unparse($num);\n  }\n}\n\n# Return output units\nsub Units {\n  if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {\n    if ($main::opt_inuse_objects || $main::opt_alloc_objects) {\n      return \"objects\";\n    } else {\n      if ($main::opt_show_bytes) {\n        return \"B\";\n      } else {\n        return \"MB\";\n      }\n    }\n  } elsif ($main::profile_type eq 'contention' && !$main::opt_contentions) {\n    return \"seconds\";\n  } else {\n    return \"samples\";\n  }\n}\n\n##### Profile manipulation code #####\n\n# Generate flattened profile:\n# If count is charged to stack [a,b,c,d], in generated profile,\n# it will be charged to [a]\nsub FlatProfile {\n  my $profile = shift;\n  my $result = {};\n  foreach my $k (keys(%{$profile})) {\n    my $count = $profile->{$k};\n    my @addrs = split(/\\n/, $k);\n    if ($#addrs >= 0) {\n      AddEntry($result, $addrs[0], $count);\n    }\n  }\n  return $result;\n}\n\n# Generate cumulative profile:\n# If count is charged to stack [a,b,c,d], in generated profile,\n# it will be charged to [a], [b], [c], [d]\nsub CumulativeProfile {\n  my $profile = shift;\n  my $result = {};\n  foreach my $k (keys(%{$profile})) {\n    my $count = $profile->{$k};\n    my @addrs = split(/\\n/, $k);\n    foreach my $a (@addrs) {\n      AddEntry($result, $a, $count);\n    }\n  }\n  return $result;\n}\n\n# If the second-youngest PC on the stack is always the same, returns\n# that pc.  Otherwise, returns undef.\nsub IsSecondPcAlwaysTheSame {\n  my $profile = shift;\n\n  my $second_pc = undef;\n  foreach my $k (keys(%{$profile})) {\n    my @addrs = split(/\\n/, $k);\n    if ($#addrs < 1) {\n      return undef;\n    }\n    if (not defined $second_pc) {\n      $second_pc = $addrs[1];\n    } else {\n      if ($second_pc ne $addrs[1]) {\n        return undef;\n      }\n    }\n  }\n  return $second_pc;\n}\n\nsub ExtractSymbolNameInlineStack {\n  my $symbols = shift;\n  my $address = shift;\n\n  my @stack = ();\n\n  if (exists $symbols->{$address}) {\n    my @localinlinestack = @{$symbols->{$address}};\n    for (my $i = $#localinlinestack; $i > 0; $i-=3) {\n      my $file = $localinlinestack[$i-1];\n      my $fn = $localinlinestack[$i-0];\n\n      if ($file eq \"?\" || $file eq \":0\") {\n        $file = \"??:0\";\n      }\n      if ($fn eq '??') {\n        # If we can't get the symbol name, at least use the file information.\n        $fn = $file;\n      }\n      my $suffix = \"[inline]\";\n      if ($i == 2) {\n        $suffix = \"\";\n      }\n      push (@stack, $fn.$suffix);\n    }\n  }\n  else {\n    # If we can't get a symbol name, at least fill in the address.\n    push (@stack, $address);\n  }\n\n  return @stack;\n}\n\nsub ExtractSymbolLocation {\n  my $symbols = shift;\n  my $address = shift;\n  # 'addr2line' outputs \"??:0\" for unknown locations; we do the\n  # same to be consistent.\n  my $location = \"??:0:unknown\";\n  if (exists $symbols->{$address}) {\n    my $file = $symbols->{$address}->[1];\n    if ($file eq \"?\") {\n      $file = \"??:0\"\n    }\n    $location = $file . \":\" . $symbols->{$address}->[0];\n  }\n  return $location;\n}\n\n# Extracts a graph of calls.\nsub ExtractCalls {\n  my $symbols = shift;\n  my $profile = shift;\n\n  my $calls = {};\n  while( my ($stack_trace, $count) = each %$profile ) {\n    my @address = split(/\\n/, $stack_trace);\n    my $destination = ExtractSymbolLocation($symbols, $address[0]);\n    AddEntry($calls, $destination, $count);\n    for (my $i = 1; $i <= $#address; $i++) {\n      my $source = ExtractSymbolLocation($symbols, $address[$i]);\n      my $call = \"$source -> $destination\";\n      AddEntry($calls, $call, $count);\n      $destination = $source;\n    }\n  }\n\n  return $calls;\n}\n\nsub FilterFrames {\n  my $symbols = shift;\n  my $profile = shift;\n\n  if ($main::opt_retain eq '' && $main::opt_exclude eq '') {\n    return $profile;\n  }\n\n  my $result = {};\n  foreach my $k (keys(%{$profile})) {\n    my $count = $profile->{$k};\n    my @addrs = split(/\\n/, $k);\n    my @path = ();\n    foreach my $a (@addrs) {\n      my $sym;\n      if (exists($symbols->{$a})) {\n        $sym = $symbols->{$a}->[0];\n      } else {\n        $sym = $a;\n      }\n      if ($main::opt_retain ne '' && $sym !~ m/$main::opt_retain/) {\n        next;\n      }\n      if ($main::opt_exclude ne '' && $sym =~ m/$main::opt_exclude/) {\n        next;\n      }\n      push(@path, $a);\n    }\n    if (scalar(@path) > 0) {\n      my $reduced_path = join(\"\\n\", @path);\n      AddEntry($result, $reduced_path, $count);\n    }\n  }\n\n  return $result;\n}\n\nsub PrintCollapsedStacks {\n  my $symbols = shift;\n  my $profile = shift;\n\n  while (my ($stack_trace, $count) = each %$profile) {\n    my @address = split(/\\n/, $stack_trace);\n    my @names = reverse ( map { ExtractSymbolNameInlineStack($symbols, $_) } @address );\n    printf(\"%s %d\\n\", join(\";\", @names), $count);\n  }\n}\n\nsub RemoveUninterestingFrames {\n  my $symbols = shift;\n  my $profile = shift;\n\n  # List of function names to skip\n  my %skip = ();\n  my $skip_regexp = 'NOMATCH';\n  if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {\n    foreach my $name ('@JEMALLOC_PREFIX@calloc',\n                      'cfree',\n                      '@JEMALLOC_PREFIX@malloc',\n                      'je_malloc_default',\n                      'newImpl',\n                      'void* newImpl',\n                      'fallbackNewImpl',\n                      'void* fallbackNewImpl',\n                      '@JEMALLOC_PREFIX@free',\n                      '@JEMALLOC_PREFIX@memalign',\n                      '@JEMALLOC_PREFIX@posix_memalign',\n                      '@JEMALLOC_PREFIX@aligned_alloc',\n                      'pvalloc',\n                      '@JEMALLOC_PREFIX@valloc',\n                      '@JEMALLOC_PREFIX@realloc',\n                      '@JEMALLOC_PREFIX@mallocx',\n                      '@JEMALLOC_PREFIX@rallocx',\n                      'do_rallocx',\n                      '@JEMALLOC_PREFIX@xallocx',\n                      '@JEMALLOC_PREFIX@dallocx',\n                      '@JEMALLOC_PREFIX@sdallocx',\n                      '@JEMALLOC_PREFIX@sdallocx_noflags',\n                      'tc_calloc',\n                      'tc_cfree',\n                      'tc_malloc',\n                      'tc_free',\n                      'tc_memalign',\n                      'tc_posix_memalign',\n                      'tc_pvalloc',\n                      'tc_valloc',\n                      'tc_realloc',\n                      'tc_new',\n                      'tc_delete',\n                      'tc_newarray',\n                      'tc_deletearray',\n                      'tc_new_nothrow',\n                      'tc_newarray_nothrow',\n                      'do_malloc',\n                      '::do_malloc',   # new name -- got moved to an unnamed ns\n                      '::do_malloc_or_cpp_alloc',\n                      'DoSampledAllocation',\n                      'simple_alloc::allocate',\n                      '__malloc_alloc_template::allocate',\n                      '__builtin_delete',\n                      '__builtin_new',\n                      '__builtin_vec_delete',\n                      '__builtin_vec_new',\n                      'operator new',\n                      'operator new[]',\n                      # The entry to our memory-allocation routines on OS X\n                      'malloc_zone_malloc',\n                      'malloc_zone_calloc',\n                      'malloc_zone_valloc',\n                      'malloc_zone_realloc',\n                      'malloc_zone_memalign',\n                      'malloc_zone_free',\n                      # These mark the beginning/end of our custom sections\n                      '__start_google_malloc',\n                      '__stop_google_malloc',\n                      '__start_malloc_hook',\n                      '__stop_malloc_hook') {\n      $skip{$name} = 1;\n      $skip{\"_\" . $name} = 1;   # Mach (OS X) adds a _ prefix to everything\n    }\n    # TODO: Remove TCMalloc once everything has been\n    # moved into the tcmalloc:: namespace and we have flushed\n    # old code out of the system.\n    $skip_regexp = \"TCMalloc|^tcmalloc::\";\n  } elsif ($main::profile_type eq 'contention') {\n    foreach my $vname ('base::RecordLockProfileData',\n                       'base::SubmitMutexProfileData',\n                       'base::SubmitSpinLockProfileData',\n                       'Mutex::Unlock',\n                       'Mutex::UnlockSlow',\n                       'Mutex::ReaderUnlock',\n                       'MutexLock::~MutexLock',\n                       'SpinLock::Unlock',\n                       'SpinLock::SlowUnlock',\n                       'SpinLockHolder::~SpinLockHolder') {\n      $skip{$vname} = 1;\n    }\n  } elsif ($main::profile_type eq 'cpu') {\n    # Drop signal handlers used for CPU profile collection\n    # TODO(dpeng): this should not be necessary; it's taken\n    # care of by the general 2nd-pc mechanism below.\n    foreach my $name ('ProfileData::Add',           # historical\n                      'ProfileData::prof_handler',  # historical\n                      'CpuProfiler::prof_handler',\n                      '__FRAME_END__',\n                      '__pthread_sighandler',\n                      '__restore') {\n      $skip{$name} = 1;\n    }\n  } else {\n    # Nothing skipped for unknown types\n  }\n\n  if ($main::profile_type eq 'cpu') {\n    # If all the second-youngest program counters are the same,\n    # this STRONGLY suggests that it is an artifact of measurement,\n    # i.e., stack frames pushed by the CPU profiler signal handler.\n    # Hence, we delete them.\n    # (The topmost PC is read from the signal structure, not from\n    # the stack, so it does not get involved.)\n    while (my $second_pc = IsSecondPcAlwaysTheSame($profile)) {\n      my $result = {};\n      my $func = '';\n      if (exists($symbols->{$second_pc})) {\n        $second_pc = $symbols->{$second_pc}->[0];\n      }\n      print STDERR \"Removing $second_pc from all stack traces.\\n\";\n      foreach my $k (keys(%{$profile})) {\n        my $count = $profile->{$k};\n        my @addrs = split(/\\n/, $k);\n        splice @addrs, 1, 1;\n        my $reduced_path = join(\"\\n\", @addrs);\n        AddEntry($result, $reduced_path, $count);\n      }\n      $profile = $result;\n    }\n  }\n\n  my $result = {};\n  foreach my $k (keys(%{$profile})) {\n    my $count = $profile->{$k};\n    my @addrs = split(/\\n/, $k);\n    my @path = ();\n    foreach my $a (@addrs) {\n      if (exists($symbols->{$a})) {\n        my $func = $symbols->{$a}->[0];\n        if ($skip{$func} || ($func =~ m/$skip_regexp/)) {\n          # Throw away the portion of the backtrace seen so far, under the\n          # assumption that previous frames were for functions internal to the\n          # allocator.\n          @path = ();\n          next;\n        }\n      }\n      push(@path, $a);\n    }\n    my $reduced_path = join(\"\\n\", @path);\n    AddEntry($result, $reduced_path, $count);\n  }\n\n  $result = FilterFrames($symbols, $result);\n\n  return $result;\n}\n\n# Reduce profile to granularity given by user\nsub ReduceProfile {\n  my $symbols = shift;\n  my $profile = shift;\n  my $result = {};\n  my $fullname_to_shortname_map = {};\n  FillFullnameToShortnameMap($symbols, $fullname_to_shortname_map);\n  foreach my $k (keys(%{$profile})) {\n    my $count = $profile->{$k};\n    my @translated = TranslateStack($symbols, $fullname_to_shortname_map, $k);\n    my @path = ();\n    my %seen = ();\n    $seen{''} = 1;      # So that empty keys are skipped\n    foreach my $e (@translated) {\n      # To avoid double-counting due to recursion, skip a stack-trace\n      # entry if it has already been seen\n      if (!$seen{$e}) {\n        $seen{$e} = 1;\n        push(@path, $e);\n      }\n    }\n    my $reduced_path = join(\"\\n\", @path);\n    AddEntry($result, $reduced_path, $count);\n  }\n  return $result;\n}\n\n# Does the specified symbol array match the regexp?\nsub SymbolMatches {\n  my $sym = shift;\n  my $re = shift;\n  if (defined($sym)) {\n    for (my $i = 0; $i < $#{$sym}; $i += 3) {\n      if ($sym->[$i] =~ m/$re/ || $sym->[$i+1] =~ m/$re/) {\n        return 1;\n      }\n    }\n  }\n  return 0;\n}\n\n# Focus only on paths involving specified regexps\nsub FocusProfile {\n  my $symbols = shift;\n  my $profile = shift;\n  my $focus = shift;\n  my $result = {};\n  foreach my $k (keys(%{$profile})) {\n    my $count = $profile->{$k};\n    my @addrs = split(/\\n/, $k);\n    foreach my $a (@addrs) {\n      # Reply if it matches either the address/shortname/fileline\n      if (($a =~ m/$focus/) || SymbolMatches($symbols->{$a}, $focus)) {\n        AddEntry($result, $k, $count);\n        last;\n      }\n    }\n  }\n  return $result;\n}\n\n# Focus only on paths not involving specified regexps\nsub IgnoreProfile {\n  my $symbols = shift;\n  my $profile = shift;\n  my $ignore = shift;\n  my $result = {};\n  foreach my $k (keys(%{$profile})) {\n    my $count = $profile->{$k};\n    my @addrs = split(/\\n/, $k);\n    my $matched = 0;\n    foreach my $a (@addrs) {\n      # Reply if it matches either the address/shortname/fileline\n      if (($a =~ m/$ignore/) || SymbolMatches($symbols->{$a}, $ignore)) {\n        $matched = 1;\n        last;\n      }\n    }\n    if (!$matched) {\n      AddEntry($result, $k, $count);\n    }\n  }\n  return $result;\n}\n\n# Get total count in profile\nsub TotalProfile {\n  my $profile = shift;\n  my $result = 0;\n  foreach my $k (keys(%{$profile})) {\n    $result += $profile->{$k};\n  }\n  return $result;\n}\n\n# Add A to B\nsub AddProfile {\n  my $A = shift;\n  my $B = shift;\n\n  my $R = {};\n  # add all keys in A\n  foreach my $k (keys(%{$A})) {\n    my $v = $A->{$k};\n    AddEntry($R, $k, $v);\n  }\n  # add all keys in B\n  foreach my $k (keys(%{$B})) {\n    my $v = $B->{$k};\n    AddEntry($R, $k, $v);\n  }\n  return $R;\n}\n\n# Merges symbol maps\nsub MergeSymbols {\n  my $A = shift;\n  my $B = shift;\n\n  my $R = {};\n  foreach my $k (keys(%{$A})) {\n    $R->{$k} = $A->{$k};\n  }\n  if (defined($B)) {\n    foreach my $k (keys(%{$B})) {\n      $R->{$k} = $B->{$k};\n    }\n  }\n  return $R;\n}\n\n\n# Add A to B\nsub AddPcs {\n  my $A = shift;\n  my $B = shift;\n\n  my $R = {};\n  # add all keys in A\n  foreach my $k (keys(%{$A})) {\n    $R->{$k} = 1\n  }\n  # add all keys in B\n  foreach my $k (keys(%{$B})) {\n    $R->{$k} = 1\n  }\n  return $R;\n}\n\n# Subtract B from A\nsub SubtractProfile {\n  my $A = shift;\n  my $B = shift;\n\n  my $R = {};\n  foreach my $k (keys(%{$A})) {\n    my $v = $A->{$k} - GetEntry($B, $k);\n    if ($v < 0 && $main::opt_drop_negative) {\n      $v = 0;\n    }\n    AddEntry($R, $k, $v);\n  }\n  if (!$main::opt_drop_negative) {\n    # Take care of when subtracted profile has more entries\n    foreach my $k (keys(%{$B})) {\n      if (!exists($A->{$k})) {\n        AddEntry($R, $k, 0 - $B->{$k});\n      }\n    }\n  }\n  return $R;\n}\n\n# Get entry from profile; zero if not present\nsub GetEntry {\n  my $profile = shift;\n  my $k = shift;\n  if (exists($profile->{$k})) {\n    return $profile->{$k};\n  } else {\n    return 0;\n  }\n}\n\n# Add entry to specified profile\nsub AddEntry {\n  my $profile = shift;\n  my $k = shift;\n  my $n = shift;\n  if (!exists($profile->{$k})) {\n    $profile->{$k} = 0;\n  }\n  $profile->{$k} += $n;\n}\n\n# Add a stack of entries to specified profile, and add them to the $pcs\n# list.\nsub AddEntries {\n  my $profile = shift;\n  my $pcs = shift;\n  my $stack = shift;\n  my $count = shift;\n  my @k = ();\n\n  foreach my $e (split(/\\s+/, $stack)) {\n    my $pc = HexExtend($e);\n    $pcs->{$pc} = 1;\n    push @k, $pc;\n  }\n  AddEntry($profile, (join \"\\n\", @k), $count);\n}\n\n##### Code to profile a server dynamically #####\n\nsub CheckSymbolPage {\n  my $url = SymbolPageURL();\n  my $command = ShellEscape(@URL_FETCHER, $url);\n  open(SYMBOL, \"$command |\") or error($command);\n  my $line = <SYMBOL>;\n  $line =~ s/\\r//g;         # turn windows-looking lines into unix-looking lines\n  close(SYMBOL);\n  unless (defined($line)) {\n    error(\"$url doesn't exist\\n\");\n  }\n\n  if ($line =~ /^num_symbols:\\s+(\\d+)$/) {\n    if ($1 == 0) {\n      error(\"Stripped binary. No symbols available.\\n\");\n    }\n  } else {\n    error(\"Failed to get the number of symbols from $url\\n\");\n  }\n}\n\nsub IsProfileURL {\n  my $profile_name = shift;\n  if (-f $profile_name) {\n    printf STDERR \"Using local file $profile_name.\\n\";\n    return 0;\n  }\n  return 1;\n}\n\nsub ParseProfileURL {\n  my $profile_name = shift;\n\n  if (!defined($profile_name) || $profile_name eq \"\") {\n    return ();\n  }\n\n  # Split profile URL - matches all non-empty strings, so no test.\n  $profile_name =~ m,^(https?://)?([^/]+)(.*?)(/|$PROFILES)?$,;\n\n  my $proto = $1 || \"http://\";\n  my $hostport = $2;\n  my $prefix = $3;\n  my $profile = $4 || \"/\";\n\n  my $baseurl = \"$proto$hostport$prefix\";\n  return ($hostport, $baseurl, $profile);\n}\n\n# We fetch symbols from the first profile argument.\nsub SymbolPageURL {\n  my ($hostport, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]);\n  return \"$baseURL$SYMBOL_PAGE\";\n}\n\nsub FetchProgramName() {\n  my ($hostport, $baseURL, $path) = ParseProfileURL($main::pfile_args[0]);\n  my $url = \"$baseURL$PROGRAM_NAME_PAGE\";\n  my $command_line = ShellEscape(@URL_FETCHER, $url);\n  open(CMDLINE, \"$command_line |\") or error($command_line);\n  my $cmdline = <CMDLINE>;\n  $cmdline =~ s/\\r//g;   # turn windows-looking lines into unix-looking lines\n  close(CMDLINE);\n  error(\"Failed to get program name from $url\\n\") unless defined($cmdline);\n  $cmdline =~ s/\\x00.+//;  # Remove argv[1] and latters.\n  $cmdline =~ s!\\n!!g;  # Remove LFs.\n  return $cmdline;\n}\n\n# Gee, curl's -L (--location) option isn't reliable at least\n# with its 7.12.3 version.  Curl will forget to post data if\n# there is a redirection.  This function is a workaround for\n# curl.  Redirection happens on borg hosts.\nsub ResolveRedirectionForCurl {\n  my $url = shift;\n  my $command_line = ShellEscape(@URL_FETCHER, \"--head\", $url);\n  open(CMDLINE, \"$command_line |\") or error($command_line);\n  while (<CMDLINE>) {\n    s/\\r//g;         # turn windows-looking lines into unix-looking lines\n    if (/^Location: (.*)/) {\n      $url = $1;\n    }\n  }\n  close(CMDLINE);\n  return $url;\n}\n\n# Add a timeout flat to URL_FETCHER.  Returns a new list.\nsub AddFetchTimeout {\n  my $timeout = shift;\n  my @fetcher = @_;\n  if (defined($timeout)) {\n    if (join(\" \", @fetcher) =~ m/\\bcurl -s/) {\n      push(@fetcher, \"--max-time\", sprintf(\"%d\", $timeout));\n    } elsif (join(\" \", @fetcher) =~ m/\\brpcget\\b/) {\n      push(@fetcher, sprintf(\"--deadline=%d\", $timeout));\n    }\n  }\n  return @fetcher;\n}\n\n# Reads a symbol map from the file handle name given as $1, returning\n# the resulting symbol map.  Also processes variables relating to symbols.\n# Currently, the only variable processed is 'binary=<value>' which updates\n# $main::prog to have the correct program name.\nsub ReadSymbols {\n  my $in = shift;\n  my $map = {};\n  while (<$in>) {\n    s/\\r//g;         # turn windows-looking lines into unix-looking lines\n    # Removes all the leading zeroes from the symbols, see comment below.\n    if (m/^0x0*([0-9a-f]+)\\s+(.+)/) {\n      $map->{$1} = $2;\n    } elsif (m/^---/) {\n      last;\n    } elsif (m/^([a-z][^=]*)=(.*)$/ ) {\n      my ($variable, $value) = ($1, $2);\n      for ($variable, $value) {\n        s/^\\s+//;\n        s/\\s+$//;\n      }\n      if ($variable eq \"binary\") {\n        if ($main::prog ne $UNKNOWN_BINARY && $main::prog ne $value) {\n          printf STDERR (\"Warning: Mismatched binary name '%s', using '%s'.\\n\",\n                         $main::prog, $value);\n        }\n        $main::prog = $value;\n      } else {\n        printf STDERR (\"Ignoring unknown variable in symbols list: \" .\n            \"'%s' = '%s'\\n\", $variable, $value);\n      }\n    }\n  }\n  return $map;\n}\n\nsub URLEncode {\n  my $str = shift;\n  $str =~ s/([^A-Za-z0-9\\-_.!~*'()])/ sprintf \"%%%02x\", ord $1 /eg;\n  return $str;\n}\n\nsub AppendSymbolFilterParams {\n  my $url = shift;\n  my @params = ();\n  if ($main::opt_retain ne '') {\n    push(@params, sprintf(\"retain=%s\", URLEncode($main::opt_retain)));\n  }\n  if ($main::opt_exclude ne '') {\n    push(@params, sprintf(\"exclude=%s\", URLEncode($main::opt_exclude)));\n  }\n  if (scalar @params > 0) {\n    $url = sprintf(\"%s?%s\", $url, join(\"&\", @params));\n  }\n  return $url;\n}\n\n# Fetches and processes symbols to prepare them for use in the profile output\n# code.  If the optional 'symbol_map' arg is not given, fetches symbols from\n# $SYMBOL_PAGE for all PC values found in profile.  Otherwise, the raw symbols\n# are assumed to have already been fetched into 'symbol_map' and are simply\n# extracted and processed.\nsub FetchSymbols {\n  my $pcset = shift;\n  my $symbol_map = shift;\n\n  my %seen = ();\n  my @pcs = grep { !$seen{$_}++ } keys(%$pcset);  # uniq\n\n  if (!defined($symbol_map)) {\n    my $post_data = join(\"+\", sort((map {\"0x\" . \"$_\"} @pcs)));\n\n    open(POSTFILE, \">$main::tmpfile_sym\");\n    print POSTFILE $post_data;\n    close(POSTFILE);\n\n    my $url = SymbolPageURL();\n\n    my $command_line;\n    if (join(\" \", @URL_FETCHER) =~ m/\\bcurl -s/) {\n      $url = ResolveRedirectionForCurl($url);\n      $url = AppendSymbolFilterParams($url);\n      $command_line = ShellEscape(@URL_FETCHER, \"-d\", \"\\@$main::tmpfile_sym\",\n                                  $url);\n    } else {\n      $url = AppendSymbolFilterParams($url);\n      $command_line = (ShellEscape(@URL_FETCHER, \"--post\", $url)\n                       . \" < \" . ShellEscape($main::tmpfile_sym));\n    }\n    # We use c++filt in case $SYMBOL_PAGE gives us mangled symbols.\n    my $escaped_cppfilt = ShellEscape($obj_tool_map{\"c++filt\"});\n    open(SYMBOL, \"$command_line | $escaped_cppfilt |\") or error($command_line);\n    $symbol_map = ReadSymbols(*SYMBOL{IO});\n    close(SYMBOL);\n  }\n\n  my $symbols = {};\n  foreach my $pc (@pcs) {\n    my $fullname;\n    # For 64 bits binaries, symbols are extracted with 8 leading zeroes.\n    # Then /symbol reads the long symbols in as uint64, and outputs\n    # the result with a \"0x%08llx\" format which get rid of the zeroes.\n    # By removing all the leading zeroes in both $pc and the symbols from\n    # /symbol, the symbols match and are retrievable from the map.\n    my $shortpc = $pc;\n    $shortpc =~ s/^0*//;\n    # Each line may have a list of names, which includes the function\n    # and also other functions it has inlined.  They are separated (in\n    # PrintSymbolizedProfile), by --, which is illegal in function names.\n    my $fullnames;\n    if (defined($symbol_map->{$shortpc})) {\n      $fullnames = $symbol_map->{$shortpc};\n    } else {\n      $fullnames = \"0x\" . $pc;  # Just use addresses\n    }\n    my $sym = [];\n    $symbols->{$pc} = $sym;\n    foreach my $fullname (split(\"--\", $fullnames)) {\n      my $name = ShortFunctionName($fullname);\n      push(@{$sym}, $name, \"?\", $fullname);\n    }\n  }\n  return $symbols;\n}\n\nsub BaseName {\n  my $file_name = shift;\n  $file_name =~ s!^.*/!!;  # Remove directory name\n  return $file_name;\n}\n\nsub MakeProfileBaseName {\n  my ($binary_name, $profile_name) = @_;\n  my ($hostport, $baseURL, $path) = ParseProfileURL($profile_name);\n  my $binary_shortname = BaseName($binary_name);\n  return sprintf(\"%s.%s.%s\",\n                 $binary_shortname, $main::op_time, $hostport);\n}\n\nsub FetchDynamicProfile {\n  my $binary_name = shift;\n  my $profile_name = shift;\n  my $fetch_name_only = shift;\n  my $encourage_patience = shift;\n\n  if (!IsProfileURL($profile_name)) {\n    return $profile_name;\n  } else {\n    my ($hostport, $baseURL, $path) = ParseProfileURL($profile_name);\n    if ($path eq \"\" || $path eq \"/\") {\n      # Missing type specifier defaults to cpu-profile\n      $path = $PROFILE_PAGE;\n    }\n\n    my $profile_file = MakeProfileBaseName($binary_name, $profile_name);\n\n    my $url = \"$baseURL$path\";\n    my $fetch_timeout = undef;\n    if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE/) {\n      if ($path =~ m/[?]/) {\n        $url .= \"&\";\n      } else {\n        $url .= \"?\";\n      }\n      $url .= sprintf(\"seconds=%d\", $main::opt_seconds);\n      $fetch_timeout = $main::opt_seconds * 1.01 + 60;\n      # Set $profile_type for consumption by PrintSymbolizedProfile.\n      $main::profile_type = 'cpu';\n    } else {\n      # For non-CPU profiles, we add a type-extension to\n      # the target profile file name.\n      my $suffix = $path;\n      $suffix =~ s,/,.,g;\n      $profile_file .= $suffix;\n      # Set $profile_type for consumption by PrintSymbolizedProfile.\n      if ($path =~ m/$HEAP_PAGE/) {\n        $main::profile_type = 'heap';\n      } elsif ($path =~ m/$GROWTH_PAGE/) {\n        $main::profile_type = 'growth';\n      } elsif ($path =~ m/$CONTENTION_PAGE/) {\n        $main::profile_type = 'contention';\n      }\n    }\n\n    my $profile_dir = $ENV{\"JEPROF_TMPDIR\"} || ($ENV{HOME} . \"/jeprof\");\n    if (! -d $profile_dir) {\n      mkdir($profile_dir)\n          || die(\"Unable to create profile directory $profile_dir: $!\\n\");\n    }\n    my $tmp_profile = \"$profile_dir/.tmp.$profile_file\";\n    my $real_profile = \"$profile_dir/$profile_file\";\n\n    if ($fetch_name_only > 0) {\n      return $real_profile;\n    }\n\n    my @fetcher = AddFetchTimeout($fetch_timeout, @URL_FETCHER);\n    my $cmd = ShellEscape(@fetcher, $url) . \" > \" . ShellEscape($tmp_profile);\n    if ($path =~ m/$PROFILE_PAGE|$PMUPROFILE_PAGE|$CENSUSPROFILE_PAGE/){\n      print STDERR \"Gathering CPU profile from $url for $main::opt_seconds seconds to\\n  ${real_profile}\\n\";\n      if ($encourage_patience) {\n        print STDERR \"Be patient...\\n\";\n      }\n    } else {\n      print STDERR \"Fetching $path profile from $url to\\n  ${real_profile}\\n\";\n    }\n\n    (system($cmd) == 0) || error(\"Failed to get profile: $cmd: $!\\n\");\n    (system(\"mv\", $tmp_profile, $real_profile) == 0) || error(\"Unable to rename profile\\n\");\n    print STDERR \"Wrote profile to $real_profile\\n\";\n    $main::collected_profile = $real_profile;\n    return $main::collected_profile;\n  }\n}\n\n# Collect profiles in parallel\nsub FetchDynamicProfiles {\n  my $items = scalar(@main::pfile_args);\n  my $levels = log($items) / log(2);\n\n  if ($items == 1) {\n    $main::profile_files[0] = FetchDynamicProfile($main::prog, $main::pfile_args[0], 0, 1);\n  } else {\n    # math rounding issues\n    if ((2 ** $levels) < $items) {\n     $levels++;\n    }\n    my $count = scalar(@main::pfile_args);\n    for (my $i = 0; $i < $count; $i++) {\n      $main::profile_files[$i] = FetchDynamicProfile($main::prog, $main::pfile_args[$i], 1, 0);\n    }\n    print STDERR \"Fetching $count profiles, Be patient...\\n\";\n    FetchDynamicProfilesRecurse($levels, 0, 0);\n    $main::collected_profile = join(\" \\\\\\n    \", @main::profile_files);\n  }\n}\n\n# Recursively fork a process to get enough processes\n# collecting profiles\nsub FetchDynamicProfilesRecurse {\n  my $maxlevel = shift;\n  my $level = shift;\n  my $position = shift;\n\n  if (my $pid = fork()) {\n    $position = 0 | ($position << 1);\n    TryCollectProfile($maxlevel, $level, $position);\n    wait;\n  } else {\n    $position = 1 | ($position << 1);\n    TryCollectProfile($maxlevel, $level, $position);\n    cleanup();\n    exit(0);\n  }\n}\n\n# Collect a single profile\nsub TryCollectProfile {\n  my $maxlevel = shift;\n  my $level = shift;\n  my $position = shift;\n\n  if ($level >= ($maxlevel - 1)) {\n    if ($position < scalar(@main::pfile_args)) {\n      FetchDynamicProfile($main::prog, $main::pfile_args[$position], 0, 0);\n    }\n  } else {\n    FetchDynamicProfilesRecurse($maxlevel, $level+1, $position);\n  }\n}\n\n##### Parsing code #####\n\n# Provide a small streaming-read module to handle very large\n# cpu-profile files.  Stream in chunks along a sliding window.\n# Provides an interface to get one 'slot', correctly handling\n# endian-ness differences.  A slot is one 32-bit or 64-bit word\n# (depending on the input profile).  We tell endianness and bit-size\n# for the profile by looking at the first 8 bytes: in cpu profiles,\n# the second slot is always 3 (we'll accept anything that's not 0).\nBEGIN {\n  package CpuProfileStream;\n\n  sub new {\n    my ($class, $file, $fname) = @_;\n    my $self = { file        => $file,\n                 base        => 0,\n                 stride      => 512 * 1024,   # must be a multiple of bitsize/8\n                 slots       => [],\n                 unpack_code => \"\",           # N for big-endian, V for little\n                 perl_is_64bit => 1,          # matters if profile is 64-bit\n    };\n    bless $self, $class;\n    # Let unittests adjust the stride\n    if ($main::opt_test_stride > 0) {\n      $self->{stride} = $main::opt_test_stride;\n    }\n    # Read the first two slots to figure out bitsize and endianness.\n    my $slots = $self->{slots};\n    my $str;\n    read($self->{file}, $str, 8);\n    # Set the global $address_length based on what we see here.\n    # 8 is 32-bit (8 hexadecimal chars); 16 is 64-bit (16 hexadecimal chars).\n    $address_length = ($str eq (chr(0)x8)) ? 16 : 8;\n    if ($address_length == 8) {\n      if (substr($str, 6, 2) eq chr(0)x2) {\n        $self->{unpack_code} = 'V';  # Little-endian.\n      } elsif (substr($str, 4, 2) eq chr(0)x2) {\n        $self->{unpack_code} = 'N';  # Big-endian\n      } else {\n        ::error(\"$fname: header size >= 2**16\\n\");\n      }\n      @$slots = unpack($self->{unpack_code} . \"*\", $str);\n    } else {\n      # If we're a 64-bit profile, check if we're a 64-bit-capable\n      # perl.  Otherwise, each slot will be represented as a float\n      # instead of an int64, losing precision and making all the\n      # 64-bit addresses wrong.  We won't complain yet, but will\n      # later if we ever see a value that doesn't fit in 32 bits.\n      my $has_q = 0;\n      eval { $has_q = pack(\"Q\", \"1\") ? 1 : 1; };\n      if (!$has_q) {\n        $self->{perl_is_64bit} = 0;\n      }\n      read($self->{file}, $str, 8);\n      if (substr($str, 4, 4) eq chr(0)x4) {\n        # We'd love to use 'Q', but it's a) not universal, b) not endian-proof.\n        $self->{unpack_code} = 'V';  # Little-endian.\n      } elsif (substr($str, 0, 4) eq chr(0)x4) {\n        $self->{unpack_code} = 'N';  # Big-endian\n      } else {\n        ::error(\"$fname: header size >= 2**32\\n\");\n      }\n      my @pair = unpack($self->{unpack_code} . \"*\", $str);\n      # Since we know one of the pair is 0, it's fine to just add them.\n      @$slots = (0, $pair[0] + $pair[1]);\n    }\n    return $self;\n  }\n\n  # Load more data when we access slots->get(X) which is not yet in memory.\n  sub overflow {\n    my ($self) = @_;\n    my $slots = $self->{slots};\n    $self->{base} += $#$slots + 1;   # skip over data we're replacing\n    my $str;\n    read($self->{file}, $str, $self->{stride});\n    if ($address_length == 8) {      # the 32-bit case\n      # This is the easy case: unpack provides 32-bit unpacking primitives.\n      @$slots = unpack($self->{unpack_code} . \"*\", $str);\n    } else {\n      # We need to unpack 32 bits at a time and combine.\n      my @b32_values = unpack($self->{unpack_code} . \"*\", $str);\n      my @b64_values = ();\n      for (my $i = 0; $i < $#b32_values; $i += 2) {\n        # TODO(csilvers): if this is a 32-bit perl, the math below\n        #    could end up in a too-large int, which perl will promote\n        #    to a double, losing necessary precision.  Deal with that.\n        #    Right now, we just die.\n        my ($lo, $hi) = ($b32_values[$i], $b32_values[$i+1]);\n        if ($self->{unpack_code} eq 'N') {    # big-endian\n          ($lo, $hi) = ($hi, $lo);\n        }\n        my $value = $lo + $hi * (2**32);\n        if (!$self->{perl_is_64bit} &&   # check value is exactly represented\n            (($value % (2**32)) != $lo || int($value / (2**32)) != $hi)) {\n          ::error(\"Need a 64-bit perl to process this 64-bit profile.\\n\");\n        }\n        push(@b64_values, $value);\n      }\n      @$slots = @b64_values;\n    }\n  }\n\n  # Access the i-th long in the file (logically), or -1 at EOF.\n  sub get {\n    my ($self, $idx) = @_;\n    my $slots = $self->{slots};\n    while ($#$slots >= 0) {\n      if ($idx < $self->{base}) {\n        # The only time we expect a reference to $slots[$i - something]\n        # after referencing $slots[$i] is reading the very first header.\n        # Since $stride > |header|, that shouldn't cause any lookback\n        # errors.  And everything after the header is sequential.\n        print STDERR \"Unexpected look-back reading CPU profile\";\n        return -1;   # shrug, don't know what better to return\n      } elsif ($idx > $self->{base} + $#$slots) {\n        $self->overflow();\n      } else {\n        return $slots->[$idx - $self->{base}];\n      }\n    }\n    # If we get here, $slots is [], which means we've reached EOF\n    return -1;  # unique since slots is supposed to hold unsigned numbers\n  }\n}\n\n# Reads the top, 'header' section of a profile, and returns the last\n# line of the header, commonly called a 'header line'.  The header\n# section of a profile consists of zero or more 'command' lines that\n# are instructions to jeprof, which jeprof executes when reading the\n# header.  All 'command' lines start with a %.  After the command\n# lines is the 'header line', which is a profile-specific line that\n# indicates what type of profile it is, and perhaps other global\n# information about the profile.  For instance, here's a header line\n# for a heap profile:\n#   heap profile:     53:    38236 [  5525:  1284029] @ heapprofile\n# For historical reasons, the CPU profile does not contain a text-\n# readable header line.  If the profile looks like a CPU profile,\n# this function returns \"\".  If no header line could be found, this\n# function returns undef.\n#\n# The following commands are recognized:\n#   %warn -- emit the rest of this line to stderr, prefixed by 'WARNING:'\n#\n# The input file should be in binmode.\nsub ReadProfileHeader {\n  local *PROFILE = shift;\n  my $firstchar = \"\";\n  my $line = \"\";\n  read(PROFILE, $firstchar, 1);\n  seek(PROFILE, -1, 1);                    # unread the firstchar\n  if ($firstchar !~ /[[:print:]]/) {       # is not a text character\n    return \"\";\n  }\n  while (defined($line = <PROFILE>)) {\n    $line =~ s/\\r//g;   # turn windows-looking lines into unix-looking lines\n    if ($line =~ /^%warn\\s+(.*)/) {        # 'warn' command\n      # Note this matches both '%warn blah\\n' and '%warn\\n'.\n      print STDERR \"WARNING: $1\\n\";        # print the rest of the line\n    } elsif ($line =~ /^%/) {\n      print STDERR \"Ignoring unknown command from profile header: $line\";\n    } else {\n      # End of commands, must be the header line.\n      return $line;\n    }\n  }\n  return undef;     # got to EOF without seeing a header line\n}\n\nsub IsSymbolizedProfileFile {\n  my $file_name = shift;\n  if (!(-e $file_name) || !(-r $file_name)) {\n    return 0;\n  }\n  # Check if the file contains a symbol-section marker.\n  open(TFILE, \"<$file_name\");\n  binmode TFILE;\n  my $firstline = ReadProfileHeader(*TFILE);\n  close(TFILE);\n  if (!$firstline) {\n    return 0;\n  }\n  $SYMBOL_PAGE =~ m,[^/]+$,;    # matches everything after the last slash\n  my $symbol_marker = $&;\n  return $firstline =~ /^--- *$symbol_marker/;\n}\n\n# Parse profile generated by common/profiler.cc and return a reference\n# to a map:\n#      $result->{version}     Version number of profile file\n#      $result->{period}      Sampling period (in microseconds)\n#      $result->{profile}     Profile object\n#      $result->{threads}     Map of thread IDs to profile objects\n#      $result->{map}         Memory map info from profile\n#      $result->{pcs}         Hash of all PC values seen, key is hex address\nsub ReadProfile {\n  my $prog = shift;\n  my $fname = shift;\n  my $result;            # return value\n\n  $CONTENTION_PAGE =~ m,[^/]+$,;    # matches everything after the last slash\n  my $contention_marker = $&;\n  $GROWTH_PAGE  =~ m,[^/]+$,;    # matches everything after the last slash\n  my $growth_marker = $&;\n  $SYMBOL_PAGE =~ m,[^/]+$,;    # matches everything after the last slash\n  my $symbol_marker = $&;\n  $PROFILE_PAGE =~ m,[^/]+$,;    # matches everything after the last slash\n  my $profile_marker = $&;\n  $HEAP_PAGE =~ m,[^/]+$,;    # matches everything after the last slash\n  my $heap_marker = $&;\n\n  # Look at first line to see if it is a heap or a CPU profile.\n  # CPU profile may start with no header at all, and just binary data\n  # (starting with \\0\\0\\0\\0) -- in that case, don't try to read the\n  # whole firstline, since it may be gigabytes(!) of data.\n  open(PROFILE, \"<$fname\") || error(\"$fname: $!\\n\");\n  binmode PROFILE;      # New perls do UTF-8 processing\n  my $header = ReadProfileHeader(*PROFILE);\n  if (!defined($header)) {   # means \"at EOF\"\n    error(\"Profile is empty.\\n\");\n  }\n\n  my $symbols;\n  if ($header =~ m/^--- *$symbol_marker/o) {\n    # Verify that the user asked for a symbolized profile\n    if (!$main::use_symbolized_profile) {\n      # we have both a binary and symbolized profiles, abort\n      error(\"FATAL ERROR: Symbolized profile\\n   $fname\\ncannot be used with \" .\n            \"a binary arg. Try again without passing\\n   $prog\\n\");\n    }\n    # Read the symbol section of the symbolized profile file.\n    $symbols = ReadSymbols(*PROFILE{IO});\n    # Read the next line to get the header for the remaining profile.\n    $header = ReadProfileHeader(*PROFILE) || \"\";\n  }\n\n  if ($header =~ m/^--- *($heap_marker|$growth_marker)/o) {\n    # Skip \"--- ...\" line for profile types that have their own headers.\n    $header = ReadProfileHeader(*PROFILE) || \"\";\n  }\n\n  $main::profile_type = '';\n\n  if ($header =~ m/^heap profile:.*$growth_marker/o) {\n    $main::profile_type = 'growth';\n    $result =  ReadHeapProfile($prog, *PROFILE, $header);\n  } elsif ($header =~ m/^heap profile:/) {\n    $main::profile_type = 'heap';\n    $result =  ReadHeapProfile($prog, *PROFILE, $header);\n  } elsif ($header =~ m/^heap/) {\n    $main::profile_type = 'heap';\n    $result = ReadThreadedHeapProfile($prog, $fname, $header);\n  } elsif ($header =~ m/^--- *$contention_marker/o) {\n    $main::profile_type = 'contention';\n    $result = ReadSynchProfile($prog, *PROFILE);\n  } elsif ($header =~ m/^--- *Stacks:/) {\n    print STDERR\n      \"Old format contention profile: mistakenly reports \" .\n      \"condition variable signals as lock contentions.\\n\";\n    $main::profile_type = 'contention';\n    $result = ReadSynchProfile($prog, *PROFILE);\n  } elsif ($header =~ m/^--- *$profile_marker/) {\n    # the binary cpu profile data starts immediately after this line\n    $main::profile_type = 'cpu';\n    $result = ReadCPUProfile($prog, $fname, *PROFILE);\n  } else {\n    if (defined($symbols)) {\n      # a symbolized profile contains a format we don't recognize, bail out\n      error(\"$fname: Cannot recognize profile section after symbols.\\n\");\n    }\n    # no ascii header present -- must be a CPU profile\n    $main::profile_type = 'cpu';\n    $result = ReadCPUProfile($prog, $fname, *PROFILE);\n  }\n\n  close(PROFILE);\n\n  # if we got symbols along with the profile, return those as well\n  if (defined($symbols)) {\n    $result->{symbols} = $symbols;\n  }\n\n  return $result;\n}\n\n# Subtract one from caller pc so we map back to call instr.\n# However, don't do this if we're reading a symbolized profile\n# file, in which case the subtract-one was done when the file\n# was written.\n#\n# We apply the same logic to all readers, though ReadCPUProfile uses an\n# independent implementation.\nsub FixCallerAddresses {\n  my $stack = shift;\n  # --raw/http: Always subtract one from pc's, because PrintSymbolizedProfile()\n  # dumps unadjusted profiles.\n  {\n    $stack =~ /(\\s)/;\n    my $delimiter = $1;\n    my @addrs = split(' ', $stack);\n    my @fixedaddrs;\n    $#fixedaddrs = $#addrs;\n    if ($#addrs >= 0) {\n      $fixedaddrs[0] = $addrs[0];\n    }\n    for (my $i = 1; $i <= $#addrs; $i++) {\n      $fixedaddrs[$i] = AddressSub($addrs[$i], \"0x1\");\n    }\n    return join $delimiter, @fixedaddrs;\n  }\n}\n\n# CPU profile reader\nsub ReadCPUProfile {\n  my $prog = shift;\n  my $fname = shift;       # just used for logging\n  local *PROFILE = shift;\n  my $version;\n  my $period;\n  my $i;\n  my $profile = {};\n  my $pcs = {};\n\n  # Parse string into array of slots.\n  my $slots = CpuProfileStream->new(*PROFILE, $fname);\n\n  # Read header.  The current header version is a 5-element structure\n  # containing:\n  #   0: header count (always 0)\n  #   1: header \"words\" (after this one: 3)\n  #   2: format version (0)\n  #   3: sampling period (usec)\n  #   4: unused padding (always 0)\n  if ($slots->get(0) != 0 ) {\n    error(\"$fname: not a profile file, or old format profile file\\n\");\n  }\n  $i = 2 + $slots->get(1);\n  $version = $slots->get(2);\n  $period = $slots->get(3);\n  # Do some sanity checking on these header values.\n  if ($version > (2**32) || $period > (2**32) || $i > (2**32) || $i < 5) {\n    error(\"$fname: not a profile file, or corrupted profile file\\n\");\n  }\n\n  # Parse profile\n  while ($slots->get($i) != -1) {\n    my $n = $slots->get($i++);\n    my $d = $slots->get($i++);\n    if ($d > (2**16)) {  # TODO(csilvers): what's a reasonable max-stack-depth?\n      my $addr = sprintf(\"0%o\", $i * ($address_length == 8 ? 4 : 8));\n      print STDERR \"At index $i (address $addr):\\n\";\n      error(\"$fname: stack trace depth >= 2**32\\n\");\n    }\n    if ($slots->get($i) == 0) {\n      # End of profile data marker\n      $i += $d;\n      last;\n    }\n\n    # Make key out of the stack entries\n    my @k = ();\n    for (my $j = 0; $j < $d; $j++) {\n      my $pc = $slots->get($i+$j);\n      # Subtract one from caller pc so we map back to call instr.\n      $pc--;\n      $pc = sprintf(\"%0*x\", $address_length, $pc);\n      $pcs->{$pc} = 1;\n      push @k, $pc;\n    }\n\n    AddEntry($profile, (join \"\\n\", @k), $n);\n    $i += $d;\n  }\n\n  # Parse map\n  my $map = '';\n  seek(PROFILE, $i * 4, 0);\n  read(PROFILE, $map, (stat PROFILE)[7]);\n\n  my $r = {};\n  $r->{version} = $version;\n  $r->{period} = $period;\n  $r->{profile} = $profile;\n  $r->{libs} = ParseLibraries($prog, $map, $pcs);\n  $r->{pcs} = $pcs;\n\n  return $r;\n}\n\nsub HeapProfileIndex {\n  my $index = 1;\n  if ($main::opt_inuse_space) {\n    $index = 1;\n  } elsif ($main::opt_inuse_objects) {\n    $index = 0;\n  } elsif ($main::opt_alloc_space) {\n    $index = 3;\n  } elsif ($main::opt_alloc_objects) {\n    $index = 2;\n  }\n  return $index;\n}\n\nsub ReadMappedLibraries {\n  my $fh = shift;\n  my $map = \"\";\n  # Read the /proc/self/maps data\n  while (<$fh>) {\n    s/\\r//g;         # turn windows-looking lines into unix-looking lines\n    $map .= $_;\n  }\n  return $map;\n}\n\nsub ReadMemoryMap {\n  my $fh = shift;\n  my $map = \"\";\n  # Read /proc/self/maps data as formatted by DumpAddressMap()\n  my $buildvar = \"\";\n  while (<PROFILE>) {\n    s/\\r//g;         # turn windows-looking lines into unix-looking lines\n    # Parse \"build=<dir>\" specification if supplied\n    if (m/^\\s*build=(.*)\\n/) {\n      $buildvar = $1;\n    }\n\n    # Expand \"$build\" variable if available\n    $_ =~ s/\\$build\\b/$buildvar/g;\n\n    $map .= $_;\n  }\n  return $map;\n}\n\nsub AdjustSamples {\n  my ($sample_adjustment, $sampling_algorithm, $n1, $s1, $n2, $s2) = @_;\n  if ($sample_adjustment) {\n    if ($sampling_algorithm == 2) {\n      # Remote-heap version 2\n      # The sampling frequency is the rate of a Poisson process.\n      # This means that the probability of sampling an allocation of\n      # size X with sampling rate Y is 1 - exp(-X/Y)\n      if ($n1 != 0) {\n        my $ratio = (($s1*1.0)/$n1)/($sample_adjustment);\n        my $scale_factor = 1/(1 - exp(-$ratio));\n        $n1 *= $scale_factor;\n        $s1 *= $scale_factor;\n      }\n      if ($n2 != 0) {\n        my $ratio = (($s2*1.0)/$n2)/($sample_adjustment);\n        my $scale_factor = 1/(1 - exp(-$ratio));\n        $n2 *= $scale_factor;\n        $s2 *= $scale_factor;\n      }\n    } else {\n      # Remote-heap version 1\n      my $ratio;\n      $ratio = (($s1*1.0)/$n1)/($sample_adjustment);\n      if ($ratio < 1) {\n        $n1 /= $ratio;\n        $s1 /= $ratio;\n      }\n      $ratio = (($s2*1.0)/$n2)/($sample_adjustment);\n      if ($ratio < 1) {\n        $n2 /= $ratio;\n        $s2 /= $ratio;\n      }\n    }\n  }\n  return ($n1, $s1, $n2, $s2);\n}\n\nsub ReadHeapProfile {\n  my $prog = shift;\n  local *PROFILE = shift;\n  my $header = shift;\n\n  my $index = HeapProfileIndex();\n\n  # Find the type of this profile.  The header line looks like:\n  #    heap profile:   1246:  8800744 [  1246:  8800744] @ <heap-url>/266053\n  # There are two pairs <count: size>, the first inuse objects/space, and the\n  # second allocated objects/space.  This is followed optionally by a profile\n  # type, and if that is present, optionally by a sampling frequency.\n  # For remote heap profiles (v1):\n  # The interpretation of the sampling frequency is that the profiler, for\n  # each sample, calculates a uniformly distributed random integer less than\n  # the given value, and records the next sample after that many bytes have\n  # been allocated.  Therefore, the expected sample interval is half of the\n  # given frequency.  By default, if not specified, the expected sample\n  # interval is 128KB.  Only remote-heap-page profiles are adjusted for\n  # sample size.\n  # For remote heap profiles (v2):\n  # The sampling frequency is the rate of a Poisson process. This means that\n  # the probability of sampling an allocation of size X with sampling rate Y\n  # is 1 - exp(-X/Y)\n  # For version 2, a typical header line might look like this:\n  # heap profile:   1922: 127792360 [  1922: 127792360] @ <heap-url>_v2/524288\n  # the trailing number (524288) is the sampling rate. (Version 1 showed\n  # double the 'rate' here)\n  my $sampling_algorithm = 0;\n  my $sample_adjustment = 0;\n  chomp($header);\n  my $type = \"unknown\";\n  if ($header =~ m\"^heap profile:\\s*(\\d+):\\s+(\\d+)\\s+\\[\\s*(\\d+):\\s+(\\d+)\\](\\s*@\\s*([^/]*)(/(\\d+))?)?\") {\n    if (defined($6) && ($6 ne '')) {\n      $type = $6;\n      my $sample_period = $8;\n      # $type is \"heapprofile\" for profiles generated by the\n      # heap-profiler, and either \"heap\" or \"heap_v2\" for profiles\n      # generated by sampling directly within tcmalloc.  It can also\n      # be \"growth\" for heap-growth profiles.  The first is typically\n      # found for profiles generated locally, and the others for\n      # remote profiles.\n      if (($type eq \"heapprofile\") || ($type !~ /heap/) ) {\n        # No need to adjust for the sampling rate with heap-profiler-derived data\n        $sampling_algorithm = 0;\n      } elsif ($type =~ /_v2/) {\n        $sampling_algorithm = 2;     # version 2 sampling\n        if (defined($sample_period) && ($sample_period ne '')) {\n          $sample_adjustment = int($sample_period);\n        }\n      } else {\n        $sampling_algorithm = 1;     # version 1 sampling\n        if (defined($sample_period) && ($sample_period ne '')) {\n          $sample_adjustment = int($sample_period)/2;\n        }\n      }\n    } else {\n      # We detect whether or not this is a remote-heap profile by checking\n      # that the total-allocated stats ($n2,$s2) are exactly the\n      # same as the in-use stats ($n1,$s1).  It is remotely conceivable\n      # that a non-remote-heap profile may pass this check, but it is hard\n      # to imagine how that could happen.\n      # In this case it's so old it's guaranteed to be remote-heap version 1.\n      my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4);\n      if (($n1 == $n2) && ($s1 == $s2)) {\n        # This is likely to be a remote-heap based sample profile\n        $sampling_algorithm = 1;\n      }\n    }\n  }\n\n  if ($sampling_algorithm > 0) {\n    # For remote-heap generated profiles, adjust the counts and sizes to\n    # account for the sample rate (we sample once every 128KB by default).\n    if ($sample_adjustment == 0) {\n      # Turn on profile adjustment.\n      $sample_adjustment = 128*1024;\n      print STDERR \"Adjusting heap profiles for 1-in-128KB sampling rate\\n\";\n    } else {\n      printf STDERR (\"Adjusting heap profiles for 1-in-%d sampling rate\\n\",\n                     $sample_adjustment);\n    }\n    if ($sampling_algorithm > 1) {\n      # We don't bother printing anything for the original version (version 1)\n      printf STDERR \"Heap version $sampling_algorithm\\n\";\n    }\n  }\n\n  my $profile = {};\n  my $pcs = {};\n  my $map = \"\";\n\n  while (<PROFILE>) {\n    s/\\r//g;         # turn windows-looking lines into unix-looking lines\n    if (/^MAPPED_LIBRARIES:/) {\n      $map .= ReadMappedLibraries(*PROFILE);\n      last;\n    }\n\n    if (/^--- Memory map:/) {\n      $map .= ReadMemoryMap(*PROFILE);\n      last;\n    }\n\n    # Read entry of the form:\n    #  <count1>: <bytes1> [<count2>: <bytes2>] @ a1 a2 a3 ... an\n    s/^\\s*//;\n    s/\\s*$//;\n    if (m/^\\s*(\\d+):\\s+(\\d+)\\s+\\[\\s*(\\d+):\\s+(\\d+)\\]\\s+@\\s+(.*)$/) {\n      my $stack = $5;\n      my ($n1, $s1, $n2, $s2) = ($1, $2, $3, $4);\n      my @counts = AdjustSamples($sample_adjustment, $sampling_algorithm,\n                                 $n1, $s1, $n2, $s2);\n      AddEntries($profile, $pcs, FixCallerAddresses($stack), $counts[$index]);\n    }\n  }\n\n  my $r = {};\n  $r->{version} = \"heap\";\n  $r->{period} = 1;\n  $r->{profile} = $profile;\n  $r->{libs} = ParseLibraries($prog, $map, $pcs);\n  $r->{pcs} = $pcs;\n  return $r;\n}\n\nsub ReadThreadedHeapProfile {\n  my ($prog, $fname, $header) = @_;\n\n  my $index = HeapProfileIndex();\n  my $sampling_algorithm = 0;\n  my $sample_adjustment = 0;\n  chomp($header);\n  my $type = \"unknown\";\n  # Assuming a very specific type of header for now.\n  if ($header =~ m\"^heap_v2/(\\d+)\") {\n    $type = \"_v2\";\n    $sampling_algorithm = 2;\n    $sample_adjustment = int($1);\n  }\n  if ($type ne \"_v2\" || !defined($sample_adjustment)) {\n    die \"Threaded heap profiles require v2 sampling with a sample rate\\n\";\n  }\n\n  my $profile = {};\n  my $thread_profiles = {};\n  my $pcs = {};\n  my $map = \"\";\n  my $stack = \"\";\n\n  while (<PROFILE>) {\n    s/\\r//g;\n    if (/^MAPPED_LIBRARIES:/) {\n      $map .= ReadMappedLibraries(*PROFILE);\n      last;\n    }\n\n    if (/^--- Memory map:/) {\n      $map .= ReadMemoryMap(*PROFILE);\n      last;\n    }\n\n    # Read entry of the form:\n    # @ a1 a2 ... an\n    #   t*: <count1>: <bytes1> [<count2>: <bytes2>]\n    #   t1: <count1>: <bytes1> [<count2>: <bytes2>]\n    #     ...\n    #   tn: <count1>: <bytes1> [<count2>: <bytes2>]\n    s/^\\s*//;\n    s/\\s*$//;\n    if (m/^@\\s+(.*)$/) {\n      $stack = $1;\n    } elsif (m/^\\s*(t(\\*|\\d+)):\\s+(\\d+):\\s+(\\d+)\\s+\\[\\s*(\\d+):\\s+(\\d+)\\]$/) {\n      if ($stack eq \"\") {\n        # Still in the header, so this is just a per-thread summary.\n        next;\n      }\n      my $thread = $2;\n      my ($n1, $s1, $n2, $s2) = ($3, $4, $5, $6);\n      my @counts = AdjustSamples($sample_adjustment, $sampling_algorithm,\n                                 $n1, $s1, $n2, $s2);\n      if ($thread eq \"*\") {\n        AddEntries($profile, $pcs, FixCallerAddresses($stack), $counts[$index]);\n      } else {\n        if (!exists($thread_profiles->{$thread})) {\n          $thread_profiles->{$thread} = {};\n        }\n        AddEntries($thread_profiles->{$thread}, $pcs,\n                   FixCallerAddresses($stack), $counts[$index]);\n      }\n    }\n  }\n\n  my $r = {};\n  $r->{version} = \"heap\";\n  $r->{period} = 1;\n  $r->{profile} = $profile;\n  $r->{threads} = $thread_profiles;\n  $r->{libs} = ParseLibraries($prog, $map, $pcs);\n  $r->{pcs} = $pcs;\n  return $r;\n}\n\nsub ReadSynchProfile {\n  my $prog = shift;\n  local *PROFILE = shift;\n  my $header = shift;\n\n  my $map = '';\n  my $profile = {};\n  my $pcs = {};\n  my $sampling_period = 1;\n  my $cyclespernanosec = 2.8;   # Default assumption for old binaries\n  my $seen_clockrate = 0;\n  my $line;\n\n  my $index = 0;\n  if ($main::opt_total_delay) {\n    $index = 0;\n  } elsif ($main::opt_contentions) {\n    $index = 1;\n  } elsif ($main::opt_mean_delay) {\n    $index = 2;\n  }\n\n  while ( $line = <PROFILE> ) {\n    $line =~ s/\\r//g;      # turn windows-looking lines into unix-looking lines\n    if ( $line =~ /^\\s*(\\d+)\\s+(\\d+) \\@\\s*(.*?)\\s*$/ ) {\n      my ($cycles, $count, $stack) = ($1, $2, $3);\n\n      # Convert cycles to nanoseconds\n      $cycles /= $cyclespernanosec;\n\n      # Adjust for sampling done by application\n      $cycles *= $sampling_period;\n      $count *= $sampling_period;\n\n      my @values = ($cycles, $count, $cycles / $count);\n      AddEntries($profile, $pcs, FixCallerAddresses($stack), $values[$index]);\n\n    } elsif ( $line =~ /^(slow release).*thread \\d+  \\@\\s*(.*?)\\s*$/ ||\n              $line =~ /^\\s*(\\d+) \\@\\s*(.*?)\\s*$/ ) {\n      my ($cycles, $stack) = ($1, $2);\n      if ($cycles !~ /^\\d+$/) {\n        next;\n      }\n\n      # Convert cycles to nanoseconds\n      $cycles /= $cyclespernanosec;\n\n      # Adjust for sampling done by application\n      $cycles *= $sampling_period;\n\n      AddEntries($profile, $pcs, FixCallerAddresses($stack), $cycles);\n\n    } elsif ( $line =~ m/^([a-z][^=]*)=(.*)$/ ) {\n      my ($variable, $value) = ($1,$2);\n      for ($variable, $value) {\n        s/^\\s+//;\n        s/\\s+$//;\n      }\n      if ($variable eq \"cycles/second\") {\n        $cyclespernanosec = $value / 1e9;\n        $seen_clockrate = 1;\n      } elsif ($variable eq \"sampling period\") {\n        $sampling_period = $value;\n      } elsif ($variable eq \"ms since reset\") {\n        # Currently nothing is done with this value in jeprof\n        # So we just silently ignore it for now\n      } elsif ($variable eq \"discarded samples\") {\n        # Currently nothing is done with this value in jeprof\n        # So we just silently ignore it for now\n      } else {\n        printf STDERR (\"Ignoring unnknown variable in /contention output: \" .\n                       \"'%s' = '%s'\\n\",$variable,$value);\n      }\n    } else {\n      # Memory map entry\n      $map .= $line;\n    }\n  }\n\n  if (!$seen_clockrate) {\n    printf STDERR (\"No cycles/second entry in profile; Guessing %.1f GHz\\n\",\n                   $cyclespernanosec);\n  }\n\n  my $r = {};\n  $r->{version} = 0;\n  $r->{period} = $sampling_period;\n  $r->{profile} = $profile;\n  $r->{libs} = ParseLibraries($prog, $map, $pcs);\n  $r->{pcs} = $pcs;\n  return $r;\n}\n\n# Given a hex value in the form \"0x1abcd\" or \"1abcd\", return either\n# \"0001abcd\" or \"000000000001abcd\", depending on the current (global)\n# address length.\nsub HexExtend {\n  my $addr = shift;\n\n  $addr =~ s/^(0x)?0*//;\n  my $zeros_needed = $address_length - length($addr);\n  if ($zeros_needed < 0) {\n    printf STDERR \"Warning: address $addr is longer than address length $address_length\\n\";\n    return $addr;\n  }\n  return (\"0\" x $zeros_needed) . $addr;\n}\n\n##### Symbol extraction #####\n\n# Aggressively search the lib_prefix values for the given library\n# If all else fails, just return the name of the library unmodified.\n# If the lib_prefix is \"/my/path,/other/path\" and $file is \"/lib/dir/mylib.so\"\n# it will search the following locations in this order, until it finds a file:\n#   /my/path/lib/dir/mylib.so\n#   /other/path/lib/dir/mylib.so\n#   /my/path/dir/mylib.so\n#   /other/path/dir/mylib.so\n#   /my/path/mylib.so\n#   /other/path/mylib.so\n#   /lib/dir/mylib.so              (returned as last resort)\nsub FindLibrary {\n  my $file = shift;\n  my $suffix = $file;\n\n  # Search for the library as described above\n  do {\n    foreach my $prefix (@prefix_list) {\n      my $fullpath = $prefix . $suffix;\n      if (-e $fullpath) {\n        return $fullpath;\n      }\n    }\n  } while ($suffix =~ s|^/[^/]+/|/|);\n  return $file;\n}\n\n# Return path to library with debugging symbols.\n# For libc libraries, the copy in /usr/lib/debug contains debugging symbols\nsub DebuggingLibrary {\n  my $file = shift;\n\n  if ($file !~ m|^/|) {\n    return undef;\n  }\n\n  # Find debug symbol file if it's named after the library's name.\n\n  if (-f \"/usr/lib/debug$file\") {\n    if($main::opt_debug) { print STDERR \"found debug info for $file in /usr/lib/debug$file\\n\"; }\n    return \"/usr/lib/debug$file\";\n  } elsif (-f \"/usr/lib/debug$file.debug\") {\n    if($main::opt_debug) { print STDERR \"found debug info for $file in /usr/lib/debug$file.debug\\n\"; }\n    return \"/usr/lib/debug$file.debug\";\n  }\n\n  if(!$main::opt_debug_syms_by_id) {\n    if($main::opt_debug) { print STDERR \"no debug symbols found for $file\\n\" };\n    return undef;\n  }\n\n  # Find debug file if it's named after the library's build ID.\n\n  my $readelf = '';\n  if (!$main::gave_up_on_elfutils) {\n    $readelf = qx/eu-readelf -n ${file}/;\n    if ($?) {\n      print STDERR \"Cannot run eu-readelf. To use --debug-syms-by-id you must be on Linux, with elfutils installed.\\n\";\n      $main::gave_up_on_elfutils = 1;\n      return undef;\n    }\n    my $buildID = $1 if $readelf =~ /Build ID: ([A-Fa-f0-9]+)/s;\n    if (defined $buildID && length $buildID > 0) {\n      my $symbolFile = '/usr/lib/debug/.build-id/' . substr($buildID, 0, 2) . '/' . substr($buildID, 2) . '.debug';\n      if (-e $symbolFile) {\n        if($main::opt_debug) { print STDERR \"found debug symbol file $symbolFile for $file\\n\" };\n        return $symbolFile;\n      } else {\n        if($main::opt_debug) { print STDERR \"no debug symbol file found for $file, build ID: $buildID\\n\" };\n        return undef;\n      }\n    }\n  }\n\n  if($main::opt_debug) { print STDERR \"no debug symbols found for $file, build ID unknown\\n\" };\n  return undef;\n}\n\n\n# Parse text section header of a library using objdump\nsub ParseTextSectionHeaderFromObjdump {\n  my $lib = shift;\n\n  my $size = undef;\n  my $vma;\n  my $file_offset;\n  # Get objdump output from the library file to figure out how to\n  # map between mapped addresses and addresses in the library.\n  my $cmd = ShellEscape($obj_tool_map{\"objdump\"}, \"-h\", $lib);\n  open(OBJDUMP, \"$cmd |\") || error(\"$cmd: $!\\n\");\n  while (<OBJDUMP>) {\n    s/\\r//g;         # turn windows-looking lines into unix-looking lines\n    # Idx Name          Size      VMA       LMA       File off  Algn\n    #  10 .text         00104b2c  420156f0  420156f0  000156f0  2**4\n    # For 64-bit objects, VMA and LMA will be 16 hex digits, size and file\n    # offset may still be 8.  But AddressSub below will still handle that.\n    my @x = split;\n    if (($#x >= 6) && ($x[1] eq '.text')) {\n      $size = $x[2];\n      $vma = $x[3];\n      $file_offset = $x[5];\n      last;\n    }\n  }\n  close(OBJDUMP);\n\n  if (!defined($size)) {\n    return undef;\n  }\n\n  my $r = {};\n  $r->{size} = $size;\n  $r->{vma} = $vma;\n  $r->{file_offset} = $file_offset;\n\n  return $r;\n}\n\n# Parse text section header of a library using otool (on OS X)\nsub ParseTextSectionHeaderFromOtool {\n  my $lib = shift;\n\n  my $size = undef;\n  my $vma = undef;\n  my $file_offset = undef;\n  # Get otool output from the library file to figure out how to\n  # map between mapped addresses and addresses in the library.\n  my $command = ShellEscape($obj_tool_map{\"otool\"}, \"-l\", $lib);\n  open(OTOOL, \"$command |\") || error(\"$command: $!\\n\");\n  my $cmd = \"\";\n  my $sectname = \"\";\n  my $segname = \"\";\n  foreach my $line (<OTOOL>) {\n    $line =~ s/\\r//g;      # turn windows-looking lines into unix-looking lines\n    # Load command <#>\n    #       cmd LC_SEGMENT\n    # [...]\n    # Section\n    #   sectname __text\n    #    segname __TEXT\n    #       addr 0x000009f8\n    #       size 0x00018b9e\n    #     offset 2552\n    #      align 2^2 (4)\n    # We will need to strip off the leading 0x from the hex addresses,\n    # and convert the offset into hex.\n    if ($line =~ /Load command/) {\n      $cmd = \"\";\n      $sectname = \"\";\n      $segname = \"\";\n    } elsif ($line =~ /Section/) {\n      $sectname = \"\";\n      $segname = \"\";\n    } elsif ($line =~ /cmd (\\w+)/) {\n      $cmd = $1;\n    } elsif ($line =~ /sectname (\\w+)/) {\n      $sectname = $1;\n    } elsif ($line =~ /segname (\\w+)/) {\n      $segname = $1;\n    } elsif (!(($cmd eq \"LC_SEGMENT\" || $cmd eq \"LC_SEGMENT_64\") &&\n               $sectname eq \"__text\" &&\n               $segname eq \"__TEXT\")) {\n      next;\n    } elsif ($line =~ /\\baddr 0x([0-9a-fA-F]+)/) {\n      $vma = $1;\n    } elsif ($line =~ /\\bsize 0x([0-9a-fA-F]+)/) {\n      $size = $1;\n    } elsif ($line =~ /\\boffset ([0-9]+)/) {\n      $file_offset = sprintf(\"%016x\", $1);\n    }\n    if (defined($vma) && defined($size) && defined($file_offset)) {\n      last;\n    }\n  }\n  close(OTOOL);\n\n  if (!defined($vma) || !defined($size) || !defined($file_offset)) {\n     return undef;\n  }\n\n  my $r = {};\n  $r->{size} = $size;\n  $r->{vma} = $vma;\n  $r->{file_offset} = $file_offset;\n\n  return $r;\n}\n\nsub ParseTextSectionHeader {\n  # obj_tool_map(\"otool\") is only defined if we're in a Mach-O environment\n  if (defined($obj_tool_map{\"otool\"})) {\n    my $r = ParseTextSectionHeaderFromOtool(@_);\n    if (defined($r)){\n      return $r;\n    }\n  }\n  # If otool doesn't work, or we don't have it, fall back to objdump\n  return ParseTextSectionHeaderFromObjdump(@_);\n}\n\n# Split /proc/pid/maps dump into a list of libraries\nsub ParseLibraries {\n  return if $main::use_symbol_page;  # We don't need libraries info.\n  my $prog = Cwd::abs_path(shift);\n  my $map = shift;\n  my $pcs = shift;\n\n  my $result = [];\n  my $h = \"[a-f0-9]+\";\n  my $zero_offset = HexExtend(\"0\");\n\n  my $buildvar = \"\";\n  foreach my $l (split(\"\\n\", $map)) {\n    if ($l =~ m/^\\s*build=(.*)$/) {\n      $buildvar = $1;\n    }\n\n    my $start;\n    my $finish;\n    my $offset;\n    my $lib;\n    if ($l =~ /^($h)-($h)\\s+..x.\\s+($h)\\s+\\S+:\\S+\\s+\\d+\\s+(\\S+\\.(so|dll|dylib|bundle)((\\.\\d+)+\\w*(\\.\\d+){0,3})?)$/i) {\n      # Full line from /proc/self/maps.  Example:\n      #   40000000-40015000 r-xp 00000000 03:01 12845071   /lib/ld-2.3.2.so\n      $start = HexExtend($1);\n      $finish = HexExtend($2);\n      $offset = HexExtend($3);\n      $lib = $4;\n      $lib =~ s|\\\\|/|g;     # turn windows-style paths into unix-style paths\n    } elsif ($l =~ /^\\s*($h)-($h):\\s*(\\S+\\.so(\\.\\d+)*)/) {\n      # Cooked line from DumpAddressMap.  Example:\n      #   40000000-40015000: /lib/ld-2.3.2.so\n      $start = HexExtend($1);\n      $finish = HexExtend($2);\n      $offset = $zero_offset;\n      $lib = $3;\n    } elsif (($l =~ /^($h)-($h)\\s+..x.\\s+($h)\\s+\\S+:\\S+\\s+\\d+\\s+(\\S+)$/i) && ($4 eq $prog)) {\n      # PIEs and address space randomization do not play well with our\n      # default assumption that main executable is at lowest\n      # addresses. So we're detecting main executable in\n      # /proc/self/maps as well.\n      $start = HexExtend($1);\n      $finish = HexExtend($2);\n      $offset = HexExtend($3);\n      $lib = $4;\n      $lib =~ s|\\\\|/|g;     # turn windows-style paths into unix-style paths\n    }\n    # FreeBSD 10.0 virtual memory map /proc/curproc/map as defined in\n    # function procfs_doprocmap (sys/fs/procfs/procfs_map.c)\n    #\n    # Example:\n    # 0x800600000 0x80061a000 26 0 0xfffff800035a0000 r-x 75 33 0x1004 COW NC vnode /libexec/ld-elf.s\n    # o.1 NCH -1\n    elsif ($l =~ /^(0x$h)\\s(0x$h)\\s\\d+\\s\\d+\\s0x$h\\sr-x\\s\\d+\\s\\d+\\s0x\\d+\\s(COW|NCO)\\s(NC|NNC)\\svnode\\s(\\S+\\.so(\\.\\d+)*)/) {\n      $start = HexExtend($1);\n      $finish = HexExtend($2);\n      $offset = $zero_offset;\n      $lib = FindLibrary($5);\n\n    } else {\n      next;\n    }\n\n    # Expand \"$build\" variable if available\n    $lib =~ s/\\$build\\b/$buildvar/g;\n\n    $lib = FindLibrary($lib);\n\n    # Check for pre-relocated libraries, which use pre-relocated symbol tables\n    # and thus require adjusting the offset that we'll use to translate\n    # VM addresses into symbol table addresses.\n    # Only do this if we're not going to fetch the symbol table from a\n    # debugging copy of the library.\n    if (!DebuggingLibrary($lib)) {\n      my $text = ParseTextSectionHeader($lib);\n      if (defined($text)) {\n         my $vma_offset = AddressSub($text->{vma}, $text->{file_offset});\n         $offset = AddressAdd($offset, $vma_offset);\n      }\n    }\n\n    if($main::opt_debug) { printf STDERR \"$start:$finish ($offset) $lib\\n\"; }\n    push(@{$result}, [$lib, $start, $finish, $offset]);\n  }\n\n  # Append special entry for additional library (not relocated)\n  if ($main::opt_lib ne \"\") {\n    my $text = ParseTextSectionHeader($main::opt_lib);\n    if (defined($text)) {\n       my $start = $text->{vma};\n       my $finish = AddressAdd($start, $text->{size});\n\n       push(@{$result}, [$main::opt_lib, $start, $finish, $start]);\n    }\n  }\n\n  # Append special entry for the main program.  This covers\n  # 0..max_pc_value_seen, so that we assume pc values not found in one\n  # of the library ranges will be treated as coming from the main\n  # program binary.\n  my $min_pc = HexExtend(\"0\");\n  my $max_pc = $min_pc;          # find the maximal PC value in any sample\n  foreach my $pc (keys(%{$pcs})) {\n    if (HexExtend($pc) gt $max_pc) { $max_pc = HexExtend($pc); }\n  }\n  push(@{$result}, [$prog, $min_pc, $max_pc, $zero_offset]);\n\n  return $result;\n}\n\n# Add two hex addresses of length $address_length.\n# Run jeprof --test for unit test if this is changed.\nsub AddressAdd {\n  my $addr1 = shift;\n  my $addr2 = shift;\n  my $sum;\n\n  if ($address_length == 8) {\n    # Perl doesn't cope with wraparound arithmetic, so do it explicitly:\n    $sum = (hex($addr1)+hex($addr2)) % (0x10000000 * 16);\n    return sprintf(\"%08x\", $sum);\n\n  } else {\n    # Do the addition in 7-nibble chunks to trivialize carry handling.\n\n    if ($main::opt_debug and $main::opt_test) {\n      print STDERR \"AddressAdd $addr1 + $addr2 = \";\n    }\n\n    my $a1 = substr($addr1,-7);\n    $addr1 = substr($addr1,0,-7);\n    my $a2 = substr($addr2,-7);\n    $addr2 = substr($addr2,0,-7);\n    $sum = hex($a1) + hex($a2);\n    my $c = 0;\n    if ($sum > 0xfffffff) {\n      $c = 1;\n      $sum -= 0x10000000;\n    }\n    my $r = sprintf(\"%07x\", $sum);\n\n    $a1 = substr($addr1,-7);\n    $addr1 = substr($addr1,0,-7);\n    $a2 = substr($addr2,-7);\n    $addr2 = substr($addr2,0,-7);\n    $sum = hex($a1) + hex($a2) + $c;\n    $c = 0;\n    if ($sum > 0xfffffff) {\n      $c = 1;\n      $sum -= 0x10000000;\n    }\n    $r = sprintf(\"%07x\", $sum) . $r;\n\n    $sum = hex($addr1) + hex($addr2) + $c;\n    if ($sum > 0xff) { $sum -= 0x100; }\n    $r = sprintf(\"%02x\", $sum) . $r;\n\n    if ($main::opt_debug and $main::opt_test) { print STDERR \"$r\\n\"; }\n\n    return $r;\n  }\n}\n\n\n# Subtract two hex addresses of length $address_length.\n# Run jeprof --test for unit test if this is changed.\nsub AddressSub {\n  my $addr1 = shift;\n  my $addr2 = shift;\n  my $diff;\n\n  if ($address_length == 8) {\n    # Perl doesn't cope with wraparound arithmetic, so do it explicitly:\n    $diff = (hex($addr1)-hex($addr2)) % (0x10000000 * 16);\n    return sprintf(\"%08x\", $diff);\n\n  } else {\n    # Do the addition in 7-nibble chunks to trivialize borrow handling.\n    # if ($main::opt_debug) { print STDERR \"AddressSub $addr1 - $addr2 = \"; }\n\n    my $a1 = hex(substr($addr1,-7));\n    $addr1 = substr($addr1,0,-7);\n    my $a2 = hex(substr($addr2,-7));\n    $addr2 = substr($addr2,0,-7);\n    my $b = 0;\n    if ($a2 > $a1) {\n      $b = 1;\n      $a1 += 0x10000000;\n    }\n    $diff = $a1 - $a2;\n    my $r = sprintf(\"%07x\", $diff);\n\n    $a1 = hex(substr($addr1,-7));\n    $addr1 = substr($addr1,0,-7);\n    $a2 = hex(substr($addr2,-7)) + $b;\n    $addr2 = substr($addr2,0,-7);\n    $b = 0;\n    if ($a2 > $a1) {\n      $b = 1;\n      $a1 += 0x10000000;\n    }\n    $diff = $a1 - $a2;\n    $r = sprintf(\"%07x\", $diff) . $r;\n\n    $a1 = hex($addr1);\n    $a2 = hex($addr2) + $b;\n    if ($a2 > $a1) { $a1 += 0x100; }\n    $diff = $a1 - $a2;\n    $r = sprintf(\"%02x\", $diff) . $r;\n\n    # if ($main::opt_debug) { print STDERR \"$r\\n\"; }\n\n    return $r;\n  }\n}\n\n# Increment a hex addresses of length $address_length.\n# Run jeprof --test for unit test if this is changed.\nsub AddressInc {\n  my $addr = shift;\n  my $sum;\n\n  if ($address_length == 8) {\n    # Perl doesn't cope with wraparound arithmetic, so do it explicitly:\n    $sum = (hex($addr)+1) % (0x10000000 * 16);\n    return sprintf(\"%08x\", $sum);\n\n  } else {\n    # Do the addition in 7-nibble chunks to trivialize carry handling.\n    # We are always doing this to step through the addresses in a function,\n    # and will almost never overflow the first chunk, so we check for this\n    # case and exit early.\n\n    # if ($main::opt_debug) { print STDERR \"AddressInc $addr1 = \"; }\n\n    my $a1 = substr($addr,-7);\n    $addr = substr($addr,0,-7);\n    $sum = hex($a1) + 1;\n    my $r = sprintf(\"%07x\", $sum);\n    if ($sum <= 0xfffffff) {\n      $r = $addr . $r;\n      # if ($main::opt_debug) { print STDERR \"$r\\n\"; }\n      return HexExtend($r);\n    } else {\n      $r = \"0000000\";\n    }\n\n    $a1 = substr($addr,-7);\n    $addr = substr($addr,0,-7);\n    $sum = hex($a1) + 1;\n    $r = sprintf(\"%07x\", $sum) . $r;\n    if ($sum <= 0xfffffff) {\n      $r = $addr . $r;\n      # if ($main::opt_debug) { print STDERR \"$r\\n\"; }\n      return HexExtend($r);\n    } else {\n      $r = \"00000000000000\";\n    }\n\n    $sum = hex($addr) + 1;\n    if ($sum > 0xff) { $sum -= 0x100; }\n    $r = sprintf(\"%02x\", $sum) . $r;\n\n    # if ($main::opt_debug) { print STDERR \"$r\\n\"; }\n    return $r;\n  }\n}\n\n# Extract symbols for all PC values found in profile\nsub ExtractSymbols {\n  my $libs = shift;\n  my $pcset = shift;\n\n  my $symbols = {};\n\n  # Map each PC value to the containing library.  To make this faster,\n  # we sort libraries by their starting pc value (highest first), and\n  # advance through the libraries as we advance the pc.  Sometimes the\n  # addresses of libraries may overlap with the addresses of the main\n  # binary, so to make sure the libraries 'win', we iterate over the\n  # libraries in reverse order (which assumes the binary doesn't start\n  # in the middle of a library, which seems a fair assumption).\n  my @pcs = (sort { $a cmp $b } keys(%{$pcset}));  # pcset is 0-extended strings\n  foreach my $lib (sort {$b->[1] cmp $a->[1]} @{$libs}) {\n    my $libname = $lib->[0];\n    my $start = $lib->[1];\n    my $finish = $lib->[2];\n    my $offset = $lib->[3];\n\n    # Use debug library if it exists\n    my $debug_libname = DebuggingLibrary($libname);\n    if ($debug_libname) {\n        $libname = $debug_libname;\n    }\n\n    # Get list of pcs that belong in this library.\n    my $contained = [];\n    my ($start_pc_index, $finish_pc_index);\n    # Find smallest finish_pc_index such that $finish < $pc[$finish_pc_index].\n    for ($finish_pc_index = $#pcs + 1; $finish_pc_index > 0;\n         $finish_pc_index--) {\n      last if $pcs[$finish_pc_index - 1] le $finish;\n    }\n    # Find smallest start_pc_index such that $start <= $pc[$start_pc_index].\n    for ($start_pc_index = $finish_pc_index; $start_pc_index > 0;\n         $start_pc_index--) {\n      last if $pcs[$start_pc_index - 1] lt $start;\n    }\n    # This keeps PC values higher than $pc[$finish_pc_index] in @pcs,\n    # in case there are overlaps in libraries and the main binary.\n    @{$contained} = splice(@pcs, $start_pc_index,\n                           $finish_pc_index - $start_pc_index);\n    # Map to symbols\n    MapToSymbols($libname, AddressSub($start, $offset), $contained, $symbols);\n  }\n\n  return $symbols;\n}\n\n# Map list of PC values to symbols for a given image\nsub MapToSymbols {\n  my $image = shift;\n  my $offset = shift;\n  my $pclist = shift;\n  my $symbols = shift;\n\n  my $debug = 0;\n\n  # Ignore empty binaries\n  if ($#{$pclist} < 0) { return; }\n\n  # Figure out the addr2line command to use\n  my $addr2line = $obj_tool_map{\"addr2line\"};\n  my $cmd = ShellEscape($addr2line, \"-f\", \"-C\", \"-e\", $image);\n  if (exists $obj_tool_map{\"addr2line_pdb\"}) {\n    $addr2line = $obj_tool_map{\"addr2line_pdb\"};\n    $cmd = ShellEscape($addr2line, \"--demangle\", \"-f\", \"-C\", \"-e\", $image);\n  }\n\n  # If \"addr2line\" isn't installed on the system at all, just use\n  # nm to get what info we can (function names, but not line numbers).\n  if (system(ShellEscape($addr2line, \"--help\") . \" >$dev_null 2>&1\") != 0) {\n    MapSymbolsWithNM($image, $offset, $pclist, $symbols);\n    return;\n  }\n\n  # \"addr2line -i\" can produce a variable number of lines per input\n  # address, with no separator that allows us to tell when data for\n  # the next address starts.  So we find the address for a special\n  # symbol (_fini) and interleave this address between all real\n  # addresses passed to addr2line.  The name of this special symbol\n  # can then be used as a separator.\n  $sep_address = undef;  # May be filled in by MapSymbolsWithNM()\n  my $nm_symbols = {};\n  MapSymbolsWithNM($image, $offset, $pclist, $nm_symbols);\n  if (defined($sep_address)) {\n    # Only add \" -i\" to addr2line if the binary supports it.\n    # addr2line --help returns 0, but not if it sees an unknown flag first.\n    if (system(\"$cmd -i --help >$dev_null 2>&1\") == 0) {\n      $cmd .= \" -i\";\n    } else {\n      $sep_address = undef;   # no need for sep_address if we don't support -i\n    }\n  }\n\n  # Make file with all PC values with intervening 'sep_address' so\n  # that we can reliably detect the end of inlined function list\n  open(ADDRESSES, \">$main::tmpfile_sym\") || error(\"$main::tmpfile_sym: $!\\n\");\n  if ($debug) { print(\"---- $image ---\\n\"); }\n  for (my $i = 0; $i <= $#{$pclist}; $i++) {\n    # addr2line always reads hex addresses, and does not need '0x' prefix.\n    if ($debug) { printf STDERR (\"%s\\n\", $pclist->[$i]); }\n    printf ADDRESSES (\"%s\\n\", AddressSub($pclist->[$i], $offset));\n    if (defined($sep_address)) {\n      printf ADDRESSES (\"%s\\n\", $sep_address);\n    }\n  }\n  close(ADDRESSES);\n  if ($debug) {\n    print(\"----\\n\");\n    system(\"cat\", $main::tmpfile_sym);\n    print(\"----\\n\");\n    system(\"$cmd < \" . ShellEscape($main::tmpfile_sym));\n    print(\"----\\n\");\n  }\n\n  open(SYMBOLS, \"$cmd <\" . ShellEscape($main::tmpfile_sym) . \" |\")\n      || error(\"$cmd: $!\\n\");\n  my $count = 0;   # Index in pclist\n  while (<SYMBOLS>) {\n    # Read fullfunction and filelineinfo from next pair of lines\n    s/\\r?\\n$//g;\n    my $fullfunction = $_;\n    $_ = <SYMBOLS>;\n    s/\\r?\\n$//g;\n    my $filelinenum = $_;\n\n    if (defined($sep_address) && $fullfunction eq $sep_symbol) {\n      # Terminating marker for data for this address\n      $count++;\n      next;\n    }\n\n    $filelinenum =~ s|\\\\|/|g; # turn windows-style paths into unix-style paths\n\n    my $pcstr = $pclist->[$count];\n    my $function = ShortFunctionName($fullfunction);\n    my $nms = $nm_symbols->{$pcstr};\n    if (defined($nms)) {\n      if ($fullfunction eq '??') {\n        # nm found a symbol for us.\n        $function = $nms->[0];\n        $fullfunction = $nms->[2];\n      } else {\n\t# MapSymbolsWithNM tags each routine with its starting address,\n\t# useful in case the image has multiple occurrences of this\n\t# routine.  (It uses a syntax that resembles template parameters,\n\t# that are automatically stripped out by ShortFunctionName().)\n\t# addr2line does not provide the same information.  So we check\n\t# if nm disambiguated our symbol, and if so take the annotated\n\t# (nm) version of the routine-name.  TODO(csilvers): this won't\n\t# catch overloaded, inlined symbols, which nm doesn't see.\n\t# Better would be to do a check similar to nm's, in this fn.\n\tif ($nms->[2] =~ m/^\\Q$function\\E/) {  # sanity check it's the right fn\n\t  $function = $nms->[0];\n\t  $fullfunction = $nms->[2];\n\t}\n      }\n    }\n\n    # Prepend to accumulated symbols for pcstr\n    # (so that caller comes before callee)\n    my $sym = $symbols->{$pcstr};\n    if (!defined($sym)) {\n      $sym = [];\n      $symbols->{$pcstr} = $sym;\n    }\n    unshift(@{$sym}, $function, $filelinenum, $fullfunction);\n    if ($debug) { printf STDERR (\"%s => [%s]\\n\", $pcstr, join(\" \", @{$sym})); }\n    if (!defined($sep_address)) {\n      # Inlining is off, so this entry ends immediately\n      $count++;\n    }\n  }\n  close(SYMBOLS);\n}\n\n# Use nm to map the list of referenced PCs to symbols.  Return true iff we\n# are able to read procedure information via nm.\nsub MapSymbolsWithNM {\n  my $image = shift;\n  my $offset = shift;\n  my $pclist = shift;\n  my $symbols = shift;\n\n  # Get nm output sorted by increasing address\n  my $symbol_table = GetProcedureBoundaries($image, \".\");\n  if (!%{$symbol_table}) {\n    return 0;\n  }\n  # Start addresses are already the right length (8 or 16 hex digits).\n  my @names = sort { $symbol_table->{$a}->[0] cmp $symbol_table->{$b}->[0] }\n    keys(%{$symbol_table});\n\n  if ($#names < 0) {\n    # No symbols: just use addresses\n    foreach my $pc (@{$pclist}) {\n      my $pcstr = \"0x\" . $pc;\n      $symbols->{$pc} = [$pcstr, \"?\", $pcstr];\n    }\n    return 0;\n  }\n\n  # Sort addresses so we can do a join against nm output\n  my $index = 0;\n  my $fullname = $names[0];\n  my $name = ShortFunctionName($fullname);\n  foreach my $pc (sort { $a cmp $b } @{$pclist}) {\n    # Adjust for mapped offset\n    my $mpc = AddressSub($pc, $offset);\n    while (($index < $#names) && ($mpc ge $symbol_table->{$fullname}->[1])){\n      $index++;\n      $fullname = $names[$index];\n      $name = ShortFunctionName($fullname);\n    }\n    if ($mpc lt $symbol_table->{$fullname}->[1]) {\n      $symbols->{$pc} = [$name, \"?\", $fullname];\n    } else {\n      my $pcstr = \"0x\" . $pc;\n      $symbols->{$pc} = [$pcstr, \"?\", $pcstr];\n    }\n  }\n  return 1;\n}\n\nsub ShortFunctionName {\n  my $function = shift;\n  while ($function =~ s/\\([^()]*\\)(\\s*const)?//g) { }   # Argument types\n  while ($function =~ s/<[^<>]*>//g)  { }    # Remove template arguments\n  $function =~ s/^.*\\s+(\\w+::)/$1/;          # Remove leading type\n  return $function;\n}\n\n# Trim overly long symbols found in disassembler output\nsub CleanDisassembly {\n  my $d = shift;\n  while ($d =~ s/\\([^()%]*\\)(\\s*const)?//g) { } # Argument types, not (%rax)\n  while ($d =~ s/(\\w+)<[^<>]*>/$1/g)  { }       # Remove template arguments\n  return $d;\n}\n\n# Clean file name for display\nsub CleanFileName {\n  my ($f) = @_;\n  $f =~ s|^/proc/self/cwd/||;\n  $f =~ s|^\\./||;\n  return $f;\n}\n\n# Make address relative to section and clean up for display\nsub UnparseAddress {\n  my ($offset, $address) = @_;\n  $address = AddressSub($address, $offset);\n  $address =~ s/^0x//;\n  $address =~ s/^0*//;\n  return $address;\n}\n\n##### Miscellaneous #####\n\n# Find the right versions of the above object tools to use.  The\n# argument is the program file being analyzed, and should be an ELF\n# 32-bit or ELF 64-bit executable file.  The location of the tools\n# is determined by considering the following options in this order:\n#   1) --tools option, if set\n#   2) JEPROF_TOOLS environment variable, if set\n#   3) the environment\nsub ConfigureObjTools {\n  my $prog_file = shift;\n\n  # Check for the existence of $prog_file because /usr/bin/file does not\n  # predictably return error status in prod.\n  (-e $prog_file)  || error(\"$prog_file does not exist.\\n\");\n\n  my $file_type = undef;\n  if (-e \"/usr/bin/file\") {\n    # Follow symlinks (at least for systems where \"file\" supports that).\n    my $escaped_prog_file = ShellEscape($prog_file);\n    $file_type = `/usr/bin/file -L $escaped_prog_file 2>$dev_null ||\n                  /usr/bin/file $escaped_prog_file`;\n  } elsif ($^O == \"MSWin32\") {\n    $file_type = \"MS Windows\";\n  } else {\n    print STDERR \"WARNING: Can't determine the file type of $prog_file\";\n  }\n\n  if ($file_type =~ /64-bit/) {\n    # Change $address_length to 16 if the program file is ELF 64-bit.\n    # We can't detect this from many (most?) heap or lock contention\n    # profiles, since the actual addresses referenced are generally in low\n    # memory even for 64-bit programs.\n    $address_length = 16;\n  }\n\n  if ($file_type =~ /MS Windows/) {\n    # For windows, we provide a version of nm and addr2line as part of\n    # the opensource release, which is capable of parsing\n    # Windows-style PDB executables.  It should live in the path, or\n    # in the same directory as jeprof.\n    $obj_tool_map{\"nm_pdb\"} = \"nm-pdb\";\n    $obj_tool_map{\"addr2line_pdb\"} = \"addr2line-pdb\";\n  }\n\n  if ($file_type =~ /Mach-O/) {\n    # OS X uses otool to examine Mach-O files, rather than objdump.\n    $obj_tool_map{\"otool\"} = \"otool\";\n    $obj_tool_map{\"addr2line\"} = \"false\";  # no addr2line\n    $obj_tool_map{\"objdump\"} = \"false\";  # no objdump\n  }\n\n  # Go fill in %obj_tool_map with the pathnames to use:\n  foreach my $tool (keys %obj_tool_map) {\n    $obj_tool_map{$tool} = ConfigureTool($obj_tool_map{$tool});\n  }\n}\n\n# Returns the path of a caller-specified object tool.  If --tools or\n# JEPROF_TOOLS are specified, then returns the full path to the tool\n# with that prefix.  Otherwise, returns the path unmodified (which\n# means we will look for it on PATH).\nsub ConfigureTool {\n  my $tool = shift;\n  my $path;\n\n  # --tools (or $JEPROF_TOOLS) is a comma separated list, where each\n  # item is either a) a pathname prefix, or b) a map of the form\n  # <tool>:<path>.  First we look for an entry of type (b) for our\n  # tool.  If one is found, we use it.  Otherwise, we consider all the\n  # pathname prefixes in turn, until one yields an existing file.  If\n  # none does, we use a default path.\n  my $tools = $main::opt_tools || $ENV{\"JEPROF_TOOLS\"} || \"\";\n  if ($tools =~ m/(,|^)\\Q$tool\\E:([^,]*)/) {\n    $path = $2;\n    # TODO(csilvers): sanity-check that $path exists?  Hard if it's relative.\n  } elsif ($tools ne '') {\n    foreach my $prefix (split(',', $tools)) {\n      next if ($prefix =~ /:/);    # ignore \"tool:fullpath\" entries in the list\n      if (-x $prefix . $tool) {\n        $path = $prefix . $tool;\n        last;\n      }\n    }\n    if (!$path) {\n      error(\"No '$tool' found with prefix specified by \" .\n            \"--tools (or \\$JEPROF_TOOLS) '$tools'\\n\");\n    }\n  } else {\n    # ... otherwise use the version that exists in the same directory as\n    # jeprof.  If there's nothing there, use $PATH.\n    $0 =~ m,[^/]*$,;     # this is everything after the last slash\n    my $dirname = $`;    # this is everything up to and including the last slash\n    if (-x \"$dirname$tool\") {\n      $path = \"$dirname$tool\";\n    } else {\n      $path = $tool;\n    }\n  }\n  if ($main::opt_debug) { print STDERR \"Using '$path' for '$tool'.\\n\"; }\n  return $path;\n}\n\nsub ShellEscape {\n  my @escaped_words = ();\n  foreach my $word (@_) {\n    my $escaped_word = $word;\n    if ($word =~ m![^a-zA-Z0-9/.,_=-]!) {  # check for anything not in whitelist\n      $escaped_word =~ s/'/'\\\\''/;\n      $escaped_word = \"'$escaped_word'\";\n    }\n    push(@escaped_words, $escaped_word);\n  }\n  return join(\" \", @escaped_words);\n}\n\nsub cleanup {\n  unlink($main::tmpfile_sym);\n  unlink(keys %main::tempnames);\n\n  if ((scalar(@main::profile_files) > 0) &&\n      defined($main::collected_profile)) {\n    my @profiles = split(\" \\\\\\n    \", $main::collected_profile);\n    foreach my $profile (@profiles) {\n      unlink($profile);\n    }\n  }\n}\n\nsub sighandler {\n  cleanup();\n  exit(1);\n}\n\nsub error {\n  my $msg = shift;\n  print STDERR $msg;\n  cleanup();\n  exit(1);\n}\n\n\n# Run $nm_command and get all the resulting procedure boundaries whose\n# names match \"$regexp\" and returns them in a hashtable mapping from\n# procedure name to a two-element vector of [start address, end address]\nsub GetProcedureBoundariesViaNm {\n  my $escaped_nm_command = shift;    # shell-escaped\n  my $regexp = shift;\n\n  my $symbol_table = {};\n  open(NM, \"$escaped_nm_command |\") || error(\"$escaped_nm_command: $!\\n\");\n  my $last_start = \"0\";\n  my $routine = \"\";\n  while (<NM>) {\n    s/\\r//g;         # turn windows-looking lines into unix-looking lines\n    if (m/^\\s*([0-9a-f]+) (.) (..*)/) {\n      my $start_val = $1;\n      my $type = $2;\n      my $this_routine = $3;\n\n      # It's possible for two symbols to share the same address, if\n      # one is a zero-length variable (like __start_google_malloc) or\n      # one symbol is a weak alias to another (like __libc_malloc).\n      # In such cases, we want to ignore all values except for the\n      # actual symbol, which in nm-speak has type \"T\".  The logic\n      # below does this, though it's a bit tricky: what happens when\n      # we have a series of lines with the same address, is the first\n      # one gets queued up to be processed.  However, it won't\n      # *actually* be processed until later, when we read a line with\n      # a different address.  That means that as long as we're reading\n      # lines with the same address, we have a chance to replace that\n      # item in the queue, which we do whenever we see a 'T' entry --\n      # that is, a line with type 'T'.  If we never see a 'T' entry,\n      # we'll just go ahead and process the first entry (which never\n      # got touched in the queue), and ignore the others.\n      if ($start_val eq $last_start && $type =~ /t/i) {\n        # We are the 'T' symbol at this address, replace previous symbol.\n        $routine = $this_routine;\n        next;\n      } elsif ($start_val eq $last_start) {\n        # We're not the 'T' symbol at this address, so ignore us.\n        next;\n      }\n\n      if ($this_routine eq $sep_symbol) {\n        $sep_address = HexExtend($start_val);\n      }\n\n      # Tag this routine with the starting address in case the image\n      # has multiple occurrences of this routine.  We use a syntax\n      # that resembles template parameters that are automatically\n      # stripped out by ShortFunctionName()\n      $this_routine .= \"<$start_val>\";\n\n      if (defined($routine) && $routine =~ m/$regexp/) {\n        $symbol_table->{$routine} = [HexExtend($last_start),\n                                     HexExtend($start_val)];\n      }\n      $last_start = $start_val;\n      $routine = $this_routine;\n    } elsif (m/^Loaded image name: (.+)/) {\n      # The win32 nm workalike emits information about the binary it is using.\n      if ($main::opt_debug) { print STDERR \"Using Image $1\\n\"; }\n    } elsif (m/^PDB file name: (.+)/) {\n      # The win32 nm workalike emits information about the pdb it is using.\n      if ($main::opt_debug) { print STDERR \"Using PDB $1\\n\"; }\n    }\n  }\n  close(NM);\n  # Handle the last line in the nm output.  Unfortunately, we don't know\n  # how big this last symbol is, because we don't know how big the file\n  # is.  For now, we just give it a size of 0.\n  # TODO(csilvers): do better here.\n  if (defined($routine) && $routine =~ m/$regexp/) {\n    $symbol_table->{$routine} = [HexExtend($last_start),\n                                 HexExtend($last_start)];\n  }\n  return $symbol_table;\n}\n\n# Gets the procedure boundaries for all routines in \"$image\" whose names\n# match \"$regexp\" and returns them in a hashtable mapping from procedure\n# name to a two-element vector of [start address, end address].\n# Will return an empty map if nm is not installed or not working properly.\nsub GetProcedureBoundaries {\n  my $image = shift;\n  my $regexp = shift;\n\n  # If $image doesn't start with /, then put ./ in front of it.  This works\n  # around an obnoxious bug in our probing of nm -f behavior.\n  # \"nm -f $image\" is supposed to fail on GNU nm, but if:\n  #\n  # a. $image starts with [BbSsPp] (for example, bin/foo/bar), AND\n  # b. you have a.out in your current directory (a not uncommon occurrence)\n  #\n  # then \"nm -f $image\" succeeds because -f only looks at the first letter of\n  # the argument, which looks valid because it's [BbSsPp], and then since\n  # there's no image provided, it looks for a.out and finds it.\n  #\n  # This regex makes sure that $image starts with . or /, forcing the -f\n  # parsing to fail since . and / are not valid formats.\n  $image =~ s#^[^/]#./$&#;\n\n  # For libc libraries, the copy in /usr/lib/debug contains debugging symbols\n  my $debugging = DebuggingLibrary($image);\n  if ($debugging) {\n    $image = $debugging;\n  }\n\n  my $nm = $obj_tool_map{\"nm\"};\n  my $cppfilt = $obj_tool_map{\"c++filt\"};\n\n  # nm can fail for two reasons: 1) $image isn't a debug library; 2) nm\n  # binary doesn't support --demangle.  In addition, for OS X we need\n  # to use the -f flag to get 'flat' nm output (otherwise we don't sort\n  # properly and get incorrect results).  Unfortunately, GNU nm uses -f\n  # in an incompatible way.  So first we test whether our nm supports\n  # --demangle and -f.\n  my $demangle_flag = \"\";\n  my $cppfilt_flag = \"\";\n  my $to_devnull = \">$dev_null 2>&1\";\n  if (system(ShellEscape($nm, \"--demangle\", $image) . $to_devnull) == 0) {\n    # In this mode, we do \"nm --demangle <foo>\"\n    $demangle_flag = \"--demangle\";\n    $cppfilt_flag = \"\";\n  } elsif (system(ShellEscape($cppfilt, $image) . $to_devnull) == 0) {\n    # In this mode, we do \"nm <foo> | c++filt\"\n    $cppfilt_flag = \" | \" . ShellEscape($cppfilt);\n  };\n  my $flatten_flag = \"\";\n  if (system(ShellEscape($nm, \"-f\", $image) . $to_devnull) == 0) {\n    $flatten_flag = \"-f\";\n  }\n\n  # Finally, in the case $imagie isn't a debug library, we try again with\n  # -D to at least get *exported* symbols.  If we can't use --demangle,\n  # we use c++filt instead, if it exists on this system.\n  my @nm_commands = (ShellEscape($nm, \"-n\", $flatten_flag, $demangle_flag,\n                                 $image) . \" 2>$dev_null $cppfilt_flag\",\n                     ShellEscape($nm, \"-D\", \"-n\", $flatten_flag, $demangle_flag,\n                                 $image) . \" 2>$dev_null $cppfilt_flag\",\n                     # 6nm is for Go binaries\n                     ShellEscape(\"6nm\", \"$image\") . \" 2>$dev_null | sort\",\n                     );\n\n  # If the executable is an MS Windows PDB-format executable, we'll\n  # have set up obj_tool_map(\"nm_pdb\").  In this case, we actually\n  # want to use both unix nm and windows-specific nm_pdb, since\n  # PDB-format executables can apparently include dwarf .o files.\n  if (exists $obj_tool_map{\"nm_pdb\"}) {\n    push(@nm_commands,\n         ShellEscape($obj_tool_map{\"nm_pdb\"}, \"--demangle\", $image)\n         . \" 2>$dev_null\");\n  }\n\n  foreach my $nm_command (@nm_commands) {\n    my $symbol_table = GetProcedureBoundariesViaNm($nm_command, $regexp);\n    return $symbol_table if (%{$symbol_table});\n  }\n  my $symbol_table = {};\n  return $symbol_table;\n}\n\n\n# The test vectors for AddressAdd/Sub/Inc are 8-16-nibble hex strings.\n# To make them more readable, we add underscores at interesting places.\n# This routine removes the underscores, producing the canonical representation\n# used by jeprof to represent addresses, particularly in the tested routines.\nsub CanonicalHex {\n  my $arg = shift;\n  return join '', (split '_',$arg);\n}\n\n\n# Unit test for AddressAdd:\nsub AddressAddUnitTest {\n  my $test_data_8 = shift;\n  my $test_data_16 = shift;\n  my $error_count = 0;\n  my $fail_count = 0;\n  my $pass_count = 0;\n  # print STDERR \"AddressAddUnitTest: \", 1+$#{$test_data_8}, \" tests\\n\";\n\n  # First a few 8-nibble addresses.  Note that this implementation uses\n  # plain old arithmetic, so a quick sanity check along with verifying what\n  # happens to overflow (we want it to wrap):\n  $address_length = 8;\n  foreach my $row (@{$test_data_8}) {\n    if ($main::opt_debug and $main::opt_test) { print STDERR \"@{$row}\\n\"; }\n    my $sum = AddressAdd ($row->[0], $row->[1]);\n    if ($sum ne $row->[2]) {\n      printf STDERR \"ERROR: %s != %s + %s = %s\\n\", $sum,\n             $row->[0], $row->[1], $row->[2];\n      ++$fail_count;\n    } else {\n      ++$pass_count;\n    }\n  }\n  printf STDERR \"AddressAdd 32-bit tests: %d passes, %d failures\\n\",\n         $pass_count, $fail_count;\n  $error_count = $fail_count;\n  $fail_count = 0;\n  $pass_count = 0;\n\n  # Now 16-nibble addresses.\n  $address_length = 16;\n  foreach my $row (@{$test_data_16}) {\n    if ($main::opt_debug and $main::opt_test) { print STDERR \"@{$row}\\n\"; }\n    my $sum = AddressAdd (CanonicalHex($row->[0]), CanonicalHex($row->[1]));\n    my $expected = join '', (split '_',$row->[2]);\n    if ($sum ne CanonicalHex($row->[2])) {\n      printf STDERR \"ERROR: %s != %s + %s = %s\\n\", $sum,\n             $row->[0], $row->[1], $row->[2];\n      ++$fail_count;\n    } else {\n      ++$pass_count;\n    }\n  }\n  printf STDERR \"AddressAdd 64-bit tests: %d passes, %d failures\\n\",\n         $pass_count, $fail_count;\n  $error_count += $fail_count;\n\n  return $error_count;\n}\n\n\n# Unit test for AddressSub:\nsub AddressSubUnitTest {\n  my $test_data_8 = shift;\n  my $test_data_16 = shift;\n  my $error_count = 0;\n  my $fail_count = 0;\n  my $pass_count = 0;\n  # print STDERR \"AddressSubUnitTest: \", 1+$#{$test_data_8}, \" tests\\n\";\n\n  # First a few 8-nibble addresses.  Note that this implementation uses\n  # plain old arithmetic, so a quick sanity check along with verifying what\n  # happens to overflow (we want it to wrap):\n  $address_length = 8;\n  foreach my $row (@{$test_data_8}) {\n    if ($main::opt_debug and $main::opt_test) { print STDERR \"@{$row}\\n\"; }\n    my $sum = AddressSub ($row->[0], $row->[1]);\n    if ($sum ne $row->[3]) {\n      printf STDERR \"ERROR: %s != %s - %s = %s\\n\", $sum,\n             $row->[0], $row->[1], $row->[3];\n      ++$fail_count;\n    } else {\n      ++$pass_count;\n    }\n  }\n  printf STDERR \"AddressSub 32-bit tests: %d passes, %d failures\\n\",\n         $pass_count, $fail_count;\n  $error_count = $fail_count;\n  $fail_count = 0;\n  $pass_count = 0;\n\n  # Now 16-nibble addresses.\n  $address_length = 16;\n  foreach my $row (@{$test_data_16}) {\n    if ($main::opt_debug and $main::opt_test) { print STDERR \"@{$row}\\n\"; }\n    my $sum = AddressSub (CanonicalHex($row->[0]), CanonicalHex($row->[1]));\n    if ($sum ne CanonicalHex($row->[3])) {\n      printf STDERR \"ERROR: %s != %s - %s = %s\\n\", $sum,\n             $row->[0], $row->[1], $row->[3];\n      ++$fail_count;\n    } else {\n      ++$pass_count;\n    }\n  }\n  printf STDERR \"AddressSub 64-bit tests: %d passes, %d failures\\n\",\n         $pass_count, $fail_count;\n  $error_count += $fail_count;\n\n  return $error_count;\n}\n\n\n# Unit test for AddressInc:\nsub AddressIncUnitTest {\n  my $test_data_8 = shift;\n  my $test_data_16 = shift;\n  my $error_count = 0;\n  my $fail_count = 0;\n  my $pass_count = 0;\n  # print STDERR \"AddressIncUnitTest: \", 1+$#{$test_data_8}, \" tests\\n\";\n\n  # First a few 8-nibble addresses.  Note that this implementation uses\n  # plain old arithmetic, so a quick sanity check along with verifying what\n  # happens to overflow (we want it to wrap):\n  $address_length = 8;\n  foreach my $row (@{$test_data_8}) {\n    if ($main::opt_debug and $main::opt_test) { print STDERR \"@{$row}\\n\"; }\n    my $sum = AddressInc ($row->[0]);\n    if ($sum ne $row->[4]) {\n      printf STDERR \"ERROR: %s != %s + 1 = %s\\n\", $sum,\n             $row->[0], $row->[4];\n      ++$fail_count;\n    } else {\n      ++$pass_count;\n    }\n  }\n  printf STDERR \"AddressInc 32-bit tests: %d passes, %d failures\\n\",\n         $pass_count, $fail_count;\n  $error_count = $fail_count;\n  $fail_count = 0;\n  $pass_count = 0;\n\n  # Now 16-nibble addresses.\n  $address_length = 16;\n  foreach my $row (@{$test_data_16}) {\n    if ($main::opt_debug and $main::opt_test) { print STDERR \"@{$row}\\n\"; }\n    my $sum = AddressInc (CanonicalHex($row->[0]));\n    if ($sum ne CanonicalHex($row->[4])) {\n      printf STDERR \"ERROR: %s != %s + 1 = %s\\n\", $sum,\n             $row->[0], $row->[4];\n      ++$fail_count;\n    } else {\n      ++$pass_count;\n    }\n  }\n  printf STDERR \"AddressInc 64-bit tests: %d passes, %d failures\\n\",\n         $pass_count, $fail_count;\n  $error_count += $fail_count;\n\n  return $error_count;\n}\n\n\n# Driver for unit tests.\n# Currently just the address add/subtract/increment routines for 64-bit.\nsub RunUnitTests {\n  my $error_count = 0;\n\n  # This is a list of tuples [a, b, a+b, a-b, a+1]\n  my $unit_test_data_8 = [\n    [qw(aaaaaaaa 50505050 fafafafa 5a5a5a5a aaaaaaab)],\n    [qw(50505050 aaaaaaaa fafafafa a5a5a5a6 50505051)],\n    [qw(ffffffff aaaaaaaa aaaaaaa9 55555555 00000000)],\n    [qw(00000001 ffffffff 00000000 00000002 00000002)],\n    [qw(00000001 fffffff0 fffffff1 00000011 00000002)],\n  ];\n  my $unit_test_data_16 = [\n    # The implementation handles data in 7-nibble chunks, so those are the\n    # interesting boundaries.\n    [qw(aaaaaaaa 50505050\n        00_000000f_afafafa 00_0000005_a5a5a5a 00_000000a_aaaaaab)],\n    [qw(50505050 aaaaaaaa\n        00_000000f_afafafa ff_ffffffa_5a5a5a6 00_0000005_0505051)],\n    [qw(ffffffff aaaaaaaa\n        00_000001a_aaaaaa9 00_0000005_5555555 00_0000010_0000000)],\n    [qw(00000001 ffffffff\n        00_0000010_0000000 ff_ffffff0_0000002 00_0000000_0000002)],\n    [qw(00000001 fffffff0\n        00_000000f_ffffff1 ff_ffffff0_0000011 00_0000000_0000002)],\n\n    [qw(00_a00000a_aaaaaaa 50505050\n        00_a00000f_afafafa 00_a000005_a5a5a5a 00_a00000a_aaaaaab)],\n    [qw(0f_fff0005_0505050 aaaaaaaa\n        0f_fff000f_afafafa 0f_ffefffa_5a5a5a6 0f_fff0005_0505051)],\n    [qw(00_000000f_fffffff 01_800000a_aaaaaaa\n        01_800001a_aaaaaa9 fe_8000005_5555555 00_0000010_0000000)],\n    [qw(00_0000000_0000001 ff_fffffff_fffffff\n        00_0000000_0000000 00_0000000_0000002 00_0000000_0000002)],\n    [qw(00_0000000_0000001 ff_fffffff_ffffff0\n        ff_fffffff_ffffff1 00_0000000_0000011 00_0000000_0000002)],\n  ];\n\n  $error_count += AddressAddUnitTest($unit_test_data_8, $unit_test_data_16);\n  $error_count += AddressSubUnitTest($unit_test_data_8, $unit_test_data_16);\n  $error_count += AddressIncUnitTest($unit_test_data_8, $unit_test_data_16);\n  if ($error_count > 0) {\n    print STDERR $error_count, \" errors: FAILED\\n\";\n  } else {\n    print STDERR \"PASS\\n\";\n  }\n  exit ($error_count);\n}"
  },
  {
    "path": "pkg/apiserver/profiling/model.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage profiling\n\nimport (\n\t\"context\"\n\t\"database/sql/driver\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/joomcode/errorx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/model\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n)\n\n// TaskState is used to represent the task/task group state.\ntype TaskState int\n\n// Built-in task state.\nconst (\n\tTaskStateError TaskState = iota\n\tTaskStateRunning\n\tTaskStateFinish\n\tTaskStatePartialFinish // Only valid for task group\n\tTaskStateSkipped\n)\n\ntype TaskRawDataType string\n\nconst (\n\tRawDataTypeJeprof   TaskRawDataType = \"jeprof\"\n\tRawDataTypeProtobuf TaskRawDataType = \"protobuf\"\n\tRawDataTypeText     TaskRawDataType = \"text\"\n)\n\ntype (\n\tTaskProfilingType     string\n\tTaskProfilingTypeList []TaskProfilingType\n)\n\nfunc (r *TaskProfilingTypeList) Scan(src interface{}) error {\n\treturn json.Unmarshal([]byte(src.(string)), r)\n}\n\nfunc (r TaskProfilingTypeList) Value() (driver.Value, error) {\n\tval, err := json.Marshal(r)\n\treturn string(val), err\n}\n\nconst (\n\tProfilingTypeCPU       TaskProfilingType = \"cpu\"\n\tProfilingTypeHeap      TaskProfilingType = \"heap\"\n\tProfilingTypeGoroutine TaskProfilingType = \"goroutine\"\n\tProfilingTypeMutex     TaskProfilingType = \"mutex\"\n)\n\nvar profilingTypeMap = map[TaskProfilingType]struct{}{\n\tProfilingTypeCPU:       {},\n\tProfilingTypeHeap:      {},\n\tProfilingTypeGoroutine: {},\n\tProfilingTypeMutex:     {},\n}\n\ntype TaskModel struct {\n\tID            uint                    `json:\"id\" gorm:\"primary_key\"`\n\tTaskGroupID   uint                    `json:\"task_group_id\" gorm:\"index\"`\n\tState         TaskState               `json:\"state\" gorm:\"index\"`\n\tTarget        model.RequestTargetNode `json:\"target\" gorm:\"embedded;embedded_prefix:target_\"`\n\tFilePath      string                  `json:\"-\" gorm:\"type:text\"`\n\tError         string                  `json:\"error\" gorm:\"type:text\"`\n\tStartedAt     int64                   `json:\"started_at\"` // The start running time, reset when retry. Used to estimate approximate profiling progress.\n\tRawDataType   TaskRawDataType         `json:\"raw_data_type\" gorm:\"raw_data_type\"`\n\tProfilingType TaskProfilingType       `json:\"profiling_type\"`\n}\n\nfunc (TaskModel) TableName() string {\n\treturn \"profiling_tasks\"\n}\n\ntype TaskGroupModel struct {\n\tID                     uint                          `json:\"id\" gorm:\"primary_key\"`\n\tState                  TaskState                     `json:\"state\" gorm:\"index\"`\n\tProfileDurationSecs    uint                          `json:\"profile_duration_secs\"`\n\tTargetStats            model.RequestTargetStatistics `json:\"target_stats\" gorm:\"embedded;embedded_prefix:target_stats_\"`\n\tStartedAt              int64                         `json:\"started_at\"`\n\tRequstedProfilingTypes TaskProfilingTypeList         `json:\"requsted_profiling_types\"`\n}\n\nfunc (TaskGroupModel) TableName() string {\n\treturn \"profiling_task_groups\"\n}\n\nfunc autoMigrate(db *dbstore.DB) error {\n\treturn db.AutoMigrate(&TaskModel{}, &TaskGroupModel{})\n}\n\n// Task is the unit to fetch profiling information.\ntype Task struct {\n\t*TaskModel\n\tctx       context.Context\n\tcancel    context.CancelFunc\n\ttaskGroup *TaskGroup\n\tfetchers  *fetchers\n}\n\n// NewTask creates a new profiling task.\nfunc NewTask(ctx context.Context, taskGroup *TaskGroup, target model.RequestTargetNode, fts *fetchers, profilingType TaskProfilingType) *Task {\n\tctx, cancel := context.WithCancel(ctx)\n\treturn &Task{\n\t\tTaskModel: &TaskModel{\n\t\t\tTaskGroupID:   taskGroup.ID,\n\t\t\tState:         TaskStateRunning,\n\t\t\tTarget:        target,\n\t\t\tStartedAt:     time.Now().Unix(),\n\t\t\tProfilingType: profilingType,\n\t\t},\n\t\tctx:       ctx,\n\t\tcancel:    cancel,\n\t\ttaskGroup: taskGroup,\n\t\tfetchers:  fts,\n\t}\n}\n\nfunc (t *Task) run() {\n\tfileNameWithoutExt := fmt.Sprintf(\"%s_%s\", t.ProfilingType, t.Target.FileName())\n\tprotoFilePath, rawDataType, err := profileAndWritePprof(t.ctx, t.fetchers, &t.Target, fileNameWithoutExt, t.taskGroup.ProfileDurationSecs, t.ProfilingType)\n\tif err != nil {\n\t\tif errorx.IsOfType(err, ErrUnsupportedProfilingType) {\n\t\t\tt.State = TaskStateSkipped\n\t\t} else {\n\t\t\tt.Error = err.Error()\n\t\t\tt.State = TaskStateError\n\t\t}\n\t\tt.taskGroup.db.Save(t.TaskModel)\n\t\treturn\n\t}\n\tt.FilePath = protoFilePath\n\tt.State = TaskStateFinish\n\tt.RawDataType = rawDataType\n\tt.taskGroup.db.Save(t.TaskModel)\n}\n\nfunc (t *Task) stop() {\n\tt.cancel()\n}\n\n// TaskGroup is the collection of tasks.\ntype TaskGroup struct {\n\t*TaskGroupModel\n\tdb *dbstore.DB\n}\n\n// NewTaskGroup create a new profiling task group.\nfunc NewTaskGroup(db *dbstore.DB, profileDurationSecs uint, stats model.RequestTargetStatistics, requestedProfilingTypes TaskProfilingTypeList) *TaskGroup {\n\treturn &TaskGroup{\n\t\tTaskGroupModel: &TaskGroupModel{\n\t\t\tState:                  TaskStateRunning,\n\t\t\tProfileDurationSecs:    profileDurationSecs,\n\t\t\tTargetStats:            stats,\n\t\t\tStartedAt:              time.Now().Unix(),\n\t\t\tRequstedProfilingTypes: requestedProfilingTypes,\n\t\t},\n\t\tdb: db,\n\t}\n}\n"
  },
  {
    "path": "pkg/apiserver/profiling/module.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage profiling\n\nimport \"go.uber.org/fx\"\n\nvar Module = fx.Options(newFetchers, newService)\n"
  },
  {
    "path": "pkg/apiserver/profiling/pprof.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage profiling\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/model\"\n)\n\ntype pprofOptions struct {\n\tduration           uint\n\tfileNameWithoutExt string\n\n\ttarget        *model.RequestTargetNode\n\tfetcher       *profileFetcher\n\tprofilingType TaskProfilingType\n}\n\nfunc fetchPprof(op *pprofOptions) (string, TaskRawDataType, error) {\n\tfetcher := &fetcher{profileFetcher: op.fetcher, target: op.target}\n\ttmpPath, rawDataType, err := fetcher.FetchAndWriteToFile(op.duration, op.fileNameWithoutExt, op.profilingType)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"failed to fetch and write to temp file: %v\", err)\n\t}\n\n\treturn tmpPath, rawDataType, nil\n}\n\ntype fetcher struct {\n\ttarget         *model.RequestTargetNode\n\tprofileFetcher *profileFetcher\n}\n\nfunc (f *fetcher) FetchAndWriteToFile(duration uint, fileNameWithoutExt string, profilingType TaskProfilingType) (string, TaskRawDataType, error) {\n\tvar profilingRawDataType TaskRawDataType\n\tvar fileExtenstion string\n\tsecs := strconv.Itoa(int(duration))\n\tvar url string\n\tswitch profilingType {\n\tcase ProfilingTypeCPU:\n\t\turl = \"/debug/pprof/profile?seconds=\" + secs\n\t\tprofilingRawDataType = RawDataTypeProtobuf\n\t\tfileExtenstion = \"*.proto\"\n\tcase ProfilingTypeHeap:\n\t\turl = \"/debug/pprof/heap\"\n\t\tif f.target.Kind == model.NodeKindTiKV || f.target.Kind == model.NodeKindTiFlash {\n\t\t\tprofilingRawDataType = RawDataTypeJeprof\n\t\t\tfileExtenstion = \"*.prof\"\n\t\t} else {\n\t\t\tprofilingRawDataType = RawDataTypeProtobuf\n\t\t\tfileExtenstion = \"*.proto\"\n\t\t}\n\tcase ProfilingTypeGoroutine:\n\t\t// debug=2 causes STW when collecting the stacks. See https://github.com/pingcap/tidb/issues/48695.\n\t\turl = \"/debug/pprof/goroutine?debug=1\"\n\t\tprofilingRawDataType = RawDataTypeText\n\t\tfileExtenstion = \"*.txt\"\n\tcase ProfilingTypeMutex:\n\t\turl = \"/debug/pprof/mutex?debug=1\"\n\t\tprofilingRawDataType = RawDataTypeText\n\t\tfileExtenstion = \"*.txt\"\n\t}\n\n\ttmpfile, err := os.CreateTemp(\"\", fileNameWithoutExt+\"_\"+fileExtenstion)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"failed to create tmpfile to write profile: %v\", err)\n\t}\n\n\tdefer func() {\n\t\t_ = tmpfile.Close()\n\t}()\n\n\tresp, err := (*f.profileFetcher).fetch(&fetchOptions{ip: f.target.IP, port: f.target.Port, path: url})\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"failed to fetch profile with %v format: %v\", fileExtenstion, err)\n\t}\n\n\t_, err = tmpfile.Write(resp)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"failed to write profile: %v\", err)\n\t}\n\n\treturn tmpfile.Name(), profilingRawDataType, nil\n}\n"
  },
  {
    "path": "pkg/apiserver/profiling/profile.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage profiling\n\nimport (\n\t\"context\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/model\"\n)\n\nfunc profileAndWritePprof(_ context.Context, fts *fetchers, target *model.RequestTargetNode, fileNameWithoutExt string, profileDurationSecs uint, profilingType TaskProfilingType) (string, TaskRawDataType, error) {\n\tswitch target.Kind {\n\tcase model.NodeKindTiKV:\n\t\t// TiKV only supports CPU/heap Profiling\n\t\tif profilingType != ProfilingTypeCPU && profilingType != ProfilingTypeHeap {\n\t\t\treturn \"\", \"\", ErrUnsupportedProfilingType.NewWithNoMessage()\n\t\t}\n\t\treturn fetchPprof(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.tikv, profilingType: profilingType})\n\tcase model.NodeKindTiFlash:\n\t\t// TiFlash only supports CPU/heap Profiling\n\t\tif profilingType != ProfilingTypeCPU && profilingType != ProfilingTypeHeap {\n\t\t\treturn \"\", \"\", ErrUnsupportedProfilingType.NewWithNoMessage()\n\t\t}\n\t\treturn fetchPprof(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.tiflash, profilingType: profilingType})\n\tcase model.NodeKindTiDB:\n\t\treturn fetchPprof(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.tidb, profilingType: profilingType})\n\tcase model.NodeKindPD:\n\t\treturn fetchPprof(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.pd, profilingType: profilingType})\n\tcase model.NodeKindTiCDC:\n\t\treturn fetchPprof(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.ticdc, profilingType: profilingType})\n\tcase model.NodeKindTiProxy:\n\t\treturn fetchPprof(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.tiproxy, profilingType: profilingType})\n\tcase model.NodeKindTSO:\n\t\treturn fetchPprof(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.tso, profilingType: profilingType})\n\tcase model.NodeKindScheduling:\n\t\treturn fetchPprof(&pprofOptions{duration: profileDurationSecs, fileNameWithoutExt: fileNameWithoutExt, target: target, fetcher: &fts.scheduling, profilingType: profilingType})\n\tdefault:\n\t\treturn \"\", \"\", ErrUnsupportedProfilingTarget.New(\"%s\", target.String()) // nolint: vet\n\t}\n}\n"
  },
  {
    "path": "pkg/apiserver/profiling/protobuf_to_svg.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage profiling\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/goccy/go-graphviz\"\n\t\"github.com/google/pprof/driver\"\n\t\"github.com/google/pprof/profile\"\n)\n\nfunc convertProtobufToSVG(content []byte, task TaskModel) ([]byte, error) {\n\tdotContent, err := convertProtobufToDot(content, task)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert protobuf to dot: %v\", err)\n\t}\n\tsvgContent, err := convertDotToSVG(dotContent)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to convert dot to svg: %v\", err)\n\t}\n\n\treturn svgContent, nil\n}\n\nfunc convertProtobufToDot(content []byte, _ TaskModel) ([]byte, error) {\n\targs := []string{ //nolint:prealloc\n\t\t\"-dot\",\n\t\t// prevent printing stdout\n\t\t\"-output\", \"dummy\",\n\t\t\"-seconds\", strconv.Itoa(int(1)),\n\t}\n\t// the addr is required for driver. Pporf but not used here\n\t// since we have fetched proto content and just want to convert it to dot\n\taddress := \"\"\n\targs = append(args, address)\n\tf := &flagSet{\n\t\tFlagSet: flag.NewFlagSet(\"pprof\", flag.PanicOnError),\n\t\targs:    args,\n\t}\n\n\tprotoToDotWriter := &protobufToDotWriter{}\n\tif err := driver.PProf(&driver.Options{\n\t\tFetch:   &dotFetcher{content},\n\t\tFlagset: f,\n\t\tUI:      &blankPprofUI{},\n\t\tWriter:  protoToDotWriter,\n\t}); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn protoToDotWriter.wc.data, nil\n}\n\nfunc convertDotToSVG(dotContent []byte) ([]byte, error) {\n\tg := graphviz.New()\n\tgraph, err := graphviz.ParseBytes(dotContent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsvgWriteCloser := &writeCloser{}\n\tif err := g.Render(graph, graphviz.SVG, svgWriteCloser); err != nil {\n\t\treturn nil, err\n\t}\n\treturn svgWriteCloser.data, nil\n}\n\n// implement a writer to write content to []byte.\ntype protobufToDotWriter struct {\n\twc *writeCloser\n}\n\nfunc (w *protobufToDotWriter) Open(_ string) (io.WriteCloser, error) {\n\tw.wc = &writeCloser{data: make([]byte, 0)}\n\treturn w.wc, nil\n}\n\ntype writeCloser struct {\n\tdata []byte\n}\n\nfunc (wc *writeCloser) Write(p []byte) (n int, err error) {\n\twc.data = make([]byte, len(p))\n\tcopy(wc.data, p)\n\treturn len(p), nil\n}\n\nfunc (wc *writeCloser) Close() error {\n\treturn nil\n}\n\ntype dotFetcher struct {\n\tdata []byte\n}\n\nfunc (f *dotFetcher) Fetch(_ string, _, _ time.Duration) (*profile.Profile, string, error) {\n\tprofile, err := profile.ParseData(f.data)\n\treturn profile, \"\", err\n}\n\ntype flagSet struct {\n\t*flag.FlagSet\n\targs []string\n}\n\nfunc (f *flagSet) StringList(o, d, c string) *[]*string {\n\treturn &[]*string{f.String(o, d, c)}\n}\n\nfunc (f *flagSet) ExtraUsage() string {\n\treturn \"\"\n}\n\nfunc (f *flagSet) Parse(usage func()) []string {\n\tf.Usage = usage\n\t_ = f.FlagSet.Parse(f.args)\n\treturn f.Args()\n}\n\nfunc (f *flagSet) AddExtraUsage(_ string) {}\n\n// blankPprofUI is used to eliminate the pprof logs.\ntype blankPprofUI struct{}\n\nfunc (b blankPprofUI) ReadLine(_ string) (string, error) {\n\tpanic(\"not support\")\n}\n\nfunc (b blankPprofUI) Print(_ ...interface{}) {\n}\n\nfunc (b blankPprofUI) PrintErr(_ ...interface{}) {\n}\n\nfunc (b blankPprofUI) IsTerminal() bool {\n\treturn false\n}\n\nfunc (b blankPprofUI) WantBrowser() bool {\n\treturn false\n}\n\nfunc (b blankPprofUI) SetAutoComplete(_ func(string) string) {\n}\n"
  },
  {
    "path": "pkg/apiserver/profiling/router.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage profiling\n\nimport (\n\t\"archive/zip\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\n// Register register the handlers to the service.\nfunc RegisterRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/profiling\")\n\tendpoint.GET(\"/group/list\", auth.MWAuthRequired(), s.getGroupList)\n\tendpoint.POST(\"/group/start\", auth.MWAuthRequired(), s.handleStartGroup)\n\tendpoint.GET(\"/group/detail/:groupId\", auth.MWAuthRequired(), s.getGroupDetail)\n\tendpoint.POST(\"/group/cancel/:groupId\", auth.MWAuthRequired(), s.handleCancelGroup)\n\tendpoint.DELETE(\"/group/delete/:groupId\", auth.MWAuthRequired(), s.deleteGroup)\n\n\tendpoint.GET(\"/action_token\", auth.MWAuthRequired(), s.getActionToken)\n\tendpoint.GET(\"/group/download\", s.downloadGroup)\n\tendpoint.GET(\"/single/download\", s.downloadSingle)\n\tendpoint.GET(\"/single/view\", s.viewSingle)\n\n\tendpoint.GET(\"/config\", auth.MWAuthRequired(), s.getDynamicConfig)\n\tendpoint.PUT(\"/config\", auth.MWAuthRequired(), auth.MWRequireWritePriv(), s.setDynamicConfig)\n}\n\n// @ID startProfiling\n// @Summary Start profiling\n// @Description Start a profiling task group\n// @Param req body StartRequest true \"profiling request\"\n// @Security JwtAuth\n// @Success 200 {object} TaskGroupModel \"task group\"\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /profiling/group/start [post]\nfunc (s *Service) handleStartGroup(c *gin.Context) {\n\tvar req StartRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tif len(req.Targets) == 0 {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"Expect at least 1 target\"))\n\t\treturn\n\t}\n\n\tif req.DurationSecs == 0 {\n\t\treq.DurationSecs = config.DefaultProfilingAutoCollectionDurationSecs\n\t}\n\tif req.DurationSecs > config.MaxProfilingAutoCollectionDurationSecs {\n\t\treq.DurationSecs = config.MaxProfilingAutoCollectionDurationSecs\n\t}\n\n\tsession := &StartRequestSession{\n\t\treq: req,\n\t\tch:  make(chan struct{}, 1),\n\t}\n\ts.sessionCh <- session\n\tselect {\n\tcase <-session.ch:\n\t\tif session.err != nil {\n\t\t\trest.Error(c, session.err)\n\t\t} else {\n\t\t\tc.JSON(http.StatusOK, session.taskGroup.TaskGroupModel)\n\t\t}\n\tcase <-time.After(Timeout):\n\t\trest.Error(c, ErrTimeout.NewWithNoMessage())\n\t}\n}\n\n// @ID getProfilingGroups\n// @Summary List all profiling groups\n// @Description List all profiling groups\n// @Security JwtAuth\n// @Success 200 {array} TaskGroupModel\n// @Failure 401 {object} rest.ErrorResponse\n// @Router /profiling/group/list [get]\nfunc (s *Service) getGroupList(c *gin.Context) {\n\tvar resp []TaskGroupModel\n\terr := s.params.LocalStore.Order(\"id DESC\").Find(&resp).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, resp)\n}\n\ntype GroupDetailResponse struct {\n\tServerTime int64          `json:\"server_time\"`\n\tTaskGroup  TaskGroupModel `json:\"task_group_status\"`\n\tTasks      []TaskModel    `json:\"tasks_status\"`\n}\n\n// @ID getProfilingGroupDetail\n// @Summary List all tasks with a given group ID\n// @Description List all profiling tasks with a given group ID\n// @Param groupId path string true \"group ID\"\n// @Security JwtAuth\n// @Success 200 {object} GroupDetailResponse\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Router /profiling/group/detail/{groupId} [get]\nfunc (s *Service) getGroupDetail(c *gin.Context) {\n\ttaskGroupID, err := strconv.Atoi(c.Param(\"groupId\"))\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tvar taskGroup TaskGroupModel\n\terr = s.params.LocalStore.Where(\"id = ?\", taskGroupID).Find(&taskGroup).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tvar tasks []TaskModel\n\terr = s.params.LocalStore.Where(\"task_group_id = ?\", taskGroupID).Find(&tasks).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tc.JSON(http.StatusOK, GroupDetailResponse{\n\t\tServerTime: time.Now().Unix(), // Used to estimate task progress\n\t\tTaskGroup:  taskGroup,\n\t\tTasks:      tasks,\n\t})\n}\n\n// @ID cancelProfilingGroup\n// @Summary Cancel all tasks with a given group ID\n// @Description Cancel all profling tasks with a given group ID\n// @Param groupId path string true \"group ID\"\n// @Security JwtAuth\n// @Success 200 {object} rest.EmptyResponse\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Router /profiling/group/cancel/{groupId} [post]\nfunc (s *Service) handleCancelGroup(c *gin.Context) {\n\ttaskGroupID, err := strconv.Atoi(c.Param(\"groupId\"))\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tif err := s.cancelGroup(uint(taskGroupID)); err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, rest.EmptyResponse{})\n}\n\n// @ID getActionToken\n// @Summary Get action token for download or view\n// @Description Get token with a given group ID or task ID and action type\n// @Produce plain\n// @Param id query string false \"group or task ID\"\n// @Param action query string false \"action\"\n// @Security JwtAuth\n// @Success 200 {string} string\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /profiling/action_token [get]\nfunc (s *Service) getActionToken(c *gin.Context) {\n\tid := c.Query(\"id\")\n\taction := c.Query(\"action\") // group_download, single_download, single_view\n\ttoken, err := utils.NewJWTString(\"profiling/\"+action, id)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.String(http.StatusOK, token)\n}\n\n// @ID downloadProfilingGroup\n// @Summary Download all results of a task group\n// @Description Download all finished profiling results of a task group\n// @Produce application/x-gzip\n// @Param token query string true \"download token\"\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /profiling/group/download [get]\nfunc (s *Service) downloadGroup(c *gin.Context) {\n\ttoken := c.Query(\"token\")\n\tstr, err := utils.ParseJWTString(\"profiling/group_download\", token)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\ttaskGroupID, err := strconv.Atoi(str)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tvar tasks []TaskModel\n\terr = s.params.LocalStore.Where(\"task_group_id = ? AND state = ?\", taskGroupID, TaskStateFinish).Find(&tasks).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tfilePathes := make([]string, len(tasks))\n\tfor i, task := range tasks {\n\t\tfilePathes[i] = task.FilePath\n\t}\n\n\tfileName := fmt.Sprintf(\"profiling_%s.zip\", time.Now().Format(\"2006-01-02_15-04-05\"))\n\tc.Writer.Header().Set(\"Content-type\", \"application/octet-stream\")\n\tc.Writer.Header().Set(\"Content-Disposition\", fmt.Sprintf(\"attachment; filename=\\\"%s\\\"\", fileName))\n\tzw := zip.NewWriter(c.Writer)\n\tdefer func() {\n\t\t_ = zw.Close()\n\t}()\n\n\terr = writeZipFromFiles(zw, filePathes, true)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\terr = zipREADME(zw)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n}\n\n// @ID downloadProfilingSingle\n// @Summary Download the result of a task\n// @Description Download the finished profiling result of a task\n// @Produce application/x-gzip\n// @Param token query string true \"download token\"\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /profiling/single/download [get]\nfunc (s *Service) downloadSingle(c *gin.Context) {\n\t// FIXME: We can simply provide only a single file\n\ttoken := c.Query(\"token\")\n\tstr, err := utils.ParseJWTString(\"profiling/single_download\", token)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\ttaskID, err := strconv.Atoi(str)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\ttask := TaskModel{}\n\terr = s.params.LocalStore.Where(\"id = ? AND state = ?\", taskID, TaskStateFinish).First(&task).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tfileName := fmt.Sprintf(\"profiling_%s.zip\", time.Now().Format(\"2006-01-02_15-04-05\"))\n\tc.Writer.Header().Set(\"Content-type\", \"application/octet-stream\")\n\tc.Writer.Header().Set(\"Content-Disposition\", fmt.Sprintf(\"attachment; filename=\\\"%s\\\"\", fileName))\n\tzw := zip.NewWriter(c.Writer)\n\tdefer func() {\n\t\t_ = zw.Close()\n\t}()\n\n\terr = writeZipFromFiles(zw, []string{task.FilePath}, true)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\terr = zipREADME(zw)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n}\n\nfunc writeZipFromFiles(zw *zip.Writer, files []string, compress bool) error {\n\tfor _, file := range files {\n\t\terr := writeZipFromFile(zw, file, compress)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc writeZipFromFile(zw *zip.Writer, file string, compress bool) error {\n\tf, err := os.Open(filepath.Clean(file))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = f.Close()\n\t}()\n\n\tfileInfo, err := f.Stat()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tzipMethod := zip.Store // no compress\n\tif compress {\n\t\tzipMethod = zip.Deflate // compress\n\t}\n\tzipFile, err := zw.CreateHeader(&zip.FileHeader{\n\t\tName:     fileInfo.Name(),\n\t\tMethod:   zipMethod,\n\t\tModified: time.Now(),\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = io.Copy(zipFile, f)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc zipREADME(zw *zip.Writer) error {\n\tconst downloadREADME = `\nTo review the CPU profiling or go heap profiling result interactively:\n$ go tool pprof --http=0.0.0.0:1234 cpu_xxx.proto\n\nTo review the jemalloc profile data whose file name suffix is '.prof' interactively:\n$ jeprof --web profile_xxx.prof\n`\n\tzipFile, err := zw.CreateHeader(&zip.FileHeader{\n\t\tName:     \"README.md\",\n\t\tMethod:   zip.Deflate,\n\t\tModified: time.Now(),\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = zipFile.Write([]byte(downloadREADME))\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\ntype ViewOutputType string\n\nconst (\n\tViewOutputTypeProtobuf ViewOutputType = \"protobuf\"\n\tViewOutputTypeGraph    ViewOutputType = \"graph\"\n\tViewOutputTypeText     ViewOutputType = \"text\"\n)\n\n// @ID viewProfilingSingle\n// @Summary View the result of a task\n// @Description View the finished profiling result of a task\n// @Produce html\n// @Param token query string true \"download token\"\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /profiling/single/view [get]\nfunc (s *Service) viewSingle(c *gin.Context) {\n\ttoken := c.Query(\"token\")\n\toutputType := c.Query(\"output_type\")\n\tstr, err := utils.ParseJWTString(\"profiling/single_view\", token)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\ttaskID, err := strconv.Atoi(str)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\ttask := TaskModel{}\n\terr = s.params.LocalStore.Where(\"id = ? AND state = ?\", taskID, TaskStateFinish).First(&task).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tcontent, err := os.ReadFile(task.FilePath)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\t// set default content-type for legacy profiling content.\n\tcontentType := \"image/svg+xml\"\n\n\tswitch task.RawDataType {\n\tcase RawDataTypeProtobuf:\n\t\tswitch outputType {\n\t\tcase string(ViewOutputTypeGraph):\n\t\t\tsvgContent, err := convertProtobufToSVG(content, task)\n\t\t\tif err != nil {\n\t\t\t\trest.Error(c, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcontent = svgContent\n\t\t\tcontentType = \"image/svg+xml\"\n\t\tcase string(ViewOutputTypeProtobuf):\n\t\t\tcontentType = \"application/protobuf\"\n\t\tdefault:\n\t\t\t// Will not handle converting protobuf to other formats except flamegraph and graph\n\t\t\trest.Error(c, rest.ErrBadRequest.New(\"Cannot output protobuf as %s\", outputType))\n\t\t\treturn\n\t\t}\n\tcase RawDataTypeJeprof:\n\t\t// call jeprof to convert svg\n\t\tswitch outputType {\n\t\tcase string(ViewOutputTypeGraph):\n\t\t\tcmd := exec.Command(\"perl\", \"/dev/stdin\", \"--dot\", task.FilePath) //nolint:gosec\n\t\t\tcmd.Stdin = strings.NewReader(jeprof)\n\t\t\tdotContent, err := cmd.Output()\n\t\t\tif err != nil {\n\t\t\t\trest.Error(c, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tsvgContent, err := convertDotToSVG(dotContent)\n\t\t\tif err != nil {\n\t\t\t\trest.Error(c, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcontent = svgContent\n\t\t\tcontentType = \"image/svg+xml\"\n\t\tcase string(ViewOutputTypeText):\n\t\t\t// Brendan Gregg's collapsed stack format\n\t\t\tcmd := exec.Command(\"perl\", \"/dev/stdin\", \"--collapsed\", task.FilePath) //nolint:gosec\n\t\t\tcmd.Stdin = strings.NewReader(jeprof)\n\t\t\ttextContent, err := cmd.Output()\n\t\t\tif err != nil {\n\t\t\t\trest.Error(c, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tcontent = textContent\n\t\t\tcontentType = \"text/plain\"\n\t\tdefault:\n\t\t\t// Will not handle converting jeprof raw data to other formats except flamegraph and graph\n\t\t\trest.Error(c, rest.ErrBadRequest.New(\"Cannot output jeprof raw data as %s\", outputType))\n\t\t\treturn\n\t\t}\n\tcase RawDataTypeText:\n\t\tswitch outputType {\n\t\tcase string(ViewOutputTypeText):\n\t\t\tcontentType = \"text/plain\"\n\t\tdefault:\n\t\t\t// Will not handle converting text to other formats\n\t\t\trest.Error(c, rest.ErrBadRequest.New(\"Cannot output text as %s\", outputType))\n\t\t\treturn\n\t\t}\n\t}\n\tc.Data(http.StatusOK, contentType, content)\n}\n\n// @ID deleteProfilingGroup\n// @Summary Delete all tasks with a given group ID\n// @Description Delete all finished profiling tasks with a given group ID\n// @Param groupId path string true \"group ID\"\n// @Security JwtAuth\n// @Success 200 {object} rest.EmptyResponse\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\n// @Router /profiling/group/delete/{groupId} [delete]\nfunc (s *Service) deleteGroup(c *gin.Context) {\n\ttaskGroupID, err := strconv.Atoi(c.Param(\"groupId\"))\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tif err := s.cancelGroup(uint(taskGroupID)); err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tif err = s.params.LocalStore.Where(\"task_group_id = ?\", taskGroupID).Delete(&TaskModel{}).Error; err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tif err = s.params.LocalStore.Where(\"id = ?\", taskGroupID).Delete(&TaskGroupModel{}).Error; err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, rest.EmptyResponse{})\n}\n\n// @Summary Get Profiling Dynamic Config\n// @Success 200 {object} config.ProfilingConfig\n// @Router /profiling/config [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) getDynamicConfig(c *gin.Context) {\n\tdc, err := s.params.ConfigManager.Get()\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, dc.Profiling)\n}\n\n// @Summary Set Profiling Dynamic Config\n// @Param request body config.ProfilingConfig true \"Request body\"\n// @Success 200 {object} config.ProfilingConfig\n// @Router /profiling/config [put]\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) setDynamicConfig(c *gin.Context) {\n\tvar req config.ProfilingConfig\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tvar opt config.DynamicConfigOption = func(dc *config.DynamicConfig) {\n\t\tdc.Profiling = req\n\t}\n\tif err := s.params.ConfigManager.Modify(opt); err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, req)\n}\n"
  },
  {
    "path": "pkg/apiserver/profiling/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage profiling\n\nimport (\n\t\"context\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/model\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n)\n\nconst (\n\tTimeout = 5 * time.Second\n)\n\nvar (\n\tErrNS                         = errorx.NewNamespace(\"error.api.profiling\")\n\tErrIgnoredRequest             = ErrNS.NewType(\"ignored_request\")\n\tErrTimeout                    = ErrNS.NewType(\"timeout\")\n\tErrUnsupportedProfilingType   = ErrNS.NewType(\"unsupported_profiling_type\")\n\tErrUnsupportedProfilingTarget = ErrNS.NewType(\"unsupported_profiling_target\")\n)\n\ntype StartRequest struct {\n\tTargets                []model.RequestTargetNode `json:\"targets\"`\n\tDurationSecs           uint                      `json:\"duration_secs\"`\n\tRequstedProfilingTypes TaskProfilingTypeList     `json:\"requsted_profiling_types\"`\n}\n\ntype StartRequestSession struct {\n\treq       StartRequest\n\tch        chan struct{}\n\ttaskGroup *TaskGroup\n\terr       error\n}\n\ntype ServiceParams struct {\n\tfx.In\n\tConfigManager *config.DynamicConfigManager\n\tLocalStore    *dbstore.DB\n\n\tEtcdClient *clientv3.Client\n\tPDClient   *pd.Client\n}\n\ntype Service struct {\n\tparams       ServiceParams\n\tlifecycleCtx context.Context\n\n\twg            sync.WaitGroup\n\tsessionCh     chan *StartRequestSession\n\tlastTaskGroup *TaskGroup\n\ttasks         sync.Map\n\tfetchers      *fetchers\n}\n\nvar newService = fx.Provide(func(lc fx.Lifecycle, p ServiceParams, fts *fetchers) (*Service, error) {\n\tif err := autoMigrate(p.LocalStore); err != nil {\n\t\treturn nil, err\n\t}\n\ts := &Service{params: p, fetchers: fts}\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\ts.lifecycleCtx = ctx\n\t\t\ts.wg.Go(func() {\n\t\t\t\ts.serviceLoop(ctx)\n\t\t\t})\n\t\t\treturn nil\n\t\t},\n\t\tOnStop: func(context.Context) error {\n\t\t\ts.wg.Wait()\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn s, nil\n})\n\nfunc (s *Service) serviceLoop(ctx context.Context) {\n\tcfgCh := s.params.ConfigManager.NewPushChannel()\n\ts.sessionCh = make(chan *StartRequestSession, 1000)\n\tdefer close(s.sessionCh)\n\n\tvar dc *config.DynamicConfig\n\tvar timeCh <-chan time.Time = make(chan time.Time, 1)\n\n\tnewAutoRequest := func() *StartRequest {\n\t\tif dc == nil || dc.Profiling.AutoCollectionDurationSecs == 0 {\n\t\t\ttimeCh = make(chan time.Time, 1)\n\t\t\treturn nil\n\t\t}\n\t\ttimeCh = time.After(time.Duration(dc.Profiling.AutoCollectionIntervalSecs+dc.Profiling.AutoCollectionDurationSecs) * time.Second)\n\t\treturn &StartRequest{\n\t\t\tTargets:      dc.Profiling.AutoCollectionTargets,\n\t\t\tDurationSecs: dc.Profiling.AutoCollectionDurationSecs,\n\t\t}\n\t}\n\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase newDc, ok := <-cfgCh:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdc = newDc\n\t\t\tif req := newAutoRequest(); req != nil {\n\t\t\t\t_, _ = s.exclusiveExecute(ctx, req)\n\t\t\t}\n\t\tcase <-timeCh:\n\t\t\tif req := newAutoRequest(); req != nil {\n\t\t\t\t_, _ = s.exclusiveExecute(ctx, req)\n\t\t\t}\n\t\tcase session := <-s.sessionCh:\n\t\t\ts.handleRequest(ctx, session, dc)\n\t\t}\n\t}\n}\n\nfunc (s *Service) handleRequest(ctx context.Context, session *StartRequestSession, dc *config.DynamicConfig) {\n\tdefer close(session.ch)\n\tif dc.Profiling.AutoCollectionDurationSecs > 0 {\n\t\tsession.err = ErrIgnoredRequest.New(\"automatic collection is enabled\")\n\t\tlog.Warn(\"request is ignored\", zap.Error(session.err))\n\t\treturn\n\t}\n\tsession.taskGroup, session.err = s.exclusiveExecute(ctx, &session.req)\n}\n\nfunc (s *Service) exclusiveExecute(ctx context.Context, req *StartRequest) (*TaskGroup, error) {\n\tif s.lastTaskGroup != nil {\n\t\tif err := s.cancelGroup(s.lastTaskGroup.ID); err != nil {\n\t\t\treturn nil, ErrIgnoredRequest.New(\"failed to cancel last task group: id = %d\", s.lastTaskGroup.ID)\n\t\t}\n\t\ttime.Sleep(500 * time.Millisecond)\n\t}\n\treturn s.startGroup(ctx, req)\n}\n\nfunc (s *Service) startGroup(ctx context.Context, req *StartRequest) (*TaskGroup, error) {\n\ttaskGroup := NewTaskGroup(s.params.LocalStore, req.DurationSecs, model.NewRequestTargetStatisticsFromArray(&req.Targets), req.RequstedProfilingTypes)\n\tif err := s.params.LocalStore.Create(taskGroup.TaskGroupModel).Error; err != nil {\n\t\tlog.Warn(\"failed to start task group\", zap.Error(err))\n\t\treturn nil, err\n\t}\n\n\ttasks := make([]*Task, 0, len(req.Targets))\n\tfor _, target := range req.Targets {\n\t\tprofileTypeList := req.RequstedProfilingTypes\n\t\tfor _, profilingType := range profileTypeList {\n\t\t\t// profilingTypeMap checks the validation of requestedProfilingType.\n\t\t\t_, valid := profilingTypeMap[profilingType]\n\t\t\tif !valid {\n\t\t\t\treturn nil, ErrUnsupportedProfilingType.NewWithNoMessage()\n\t\t\t}\n\n\t\t\tt := NewTask(ctx, taskGroup, target, s.fetchers, profilingType)\n\t\t\ts.params.LocalStore.Create(t.TaskModel)\n\t\t\ts.tasks.Store(t.ID, t)\n\t\t\ttasks = append(tasks, t)\n\t\t}\n\t}\n\n\ts.wg.Go(func() {\n\t\tvar wg sync.WaitGroup\n\t\tfor i := 0; i < len(tasks); i++ {\n\t\t\twg.Add(1)\n\t\t\tgo func(idx int) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\ttasks[idx].run()\n\t\t\t\ts.tasks.Delete(tasks[idx].ID)\n\t\t\t}(i)\n\t\t}\n\t\twg.Wait()\n\t\terrorTasks := 0\n\t\tfinishedTasks := 0\n\t\tfor _, task := range tasks {\n\t\t\tswitch task.State {\n\t\t\tcase TaskStateError:\n\t\t\t\terrorTasks++\n\t\t\tcase TaskStateFinish:\n\t\t\t\tfinishedTasks++\n\t\t\t}\n\t\t}\n\t\tif errorTasks > 0 {\n\t\t\ttaskGroup.State = TaskStateError\n\t\t\tif finishedTasks > 0 {\n\t\t\t\ttaskGroup.State = TaskStatePartialFinish\n\t\t\t}\n\t\t} else {\n\t\t\ttaskGroup.State = TaskStateFinish\n\t\t}\n\t\ts.params.LocalStore.Save(taskGroup.TaskGroupModel)\n\t})\n\n\treturn taskGroup, nil\n}\n\nfunc (s *Service) cancelGroup(taskGroupID uint) error {\n\tvar tasks []TaskModel\n\tif err := s.params.LocalStore.Where(\"task_group_id = ? AND state = ?\", taskGroupID, TaskStateRunning).Find(&tasks).Error; err != nil {\n\t\tlog.Warn(\"failed to cancel task group\", zap.Error(err))\n\t\treturn err\n\t}\n\n\tfor _, task := range tasks {\n\t\tif task, ok := s.tasks.Load(task.ID); ok {\n\t\t\tt := task.(*Task)\n\t\t\tt.stop()\n\t\t}\n\t}\n\n\t// wait for tasks stop\n\tticker := time.NewTicker(100 * time.Millisecond)\n\tdefer ticker.Stop()\n\tfor {\n\t\tvar runningTasks []TaskModel\n\t\tif err := s.params.LocalStore.Where(\"task_group_id = ? AND state = ?\", taskGroupID, TaskStateRunning).Find(&runningTasks).Error; err != nil {\n\t\t\tlog.Warn(\"failed to cancel task group\", zap.Error(err))\n\t\t\treturn err\n\t\t}\n\t\tif len(runningTasks) == 0 {\n\t\t\tbreak\n\t\t}\n\t\t<-ticker.C\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/apiserver/queryeditor/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage queryeditor\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\ntype ServiceParams struct {\n\tfx.In\n\tConfig     *config.Config\n\tTiDBClient *tidb.Client\n}\n\ntype Service struct {\n\tparams       ServiceParams\n\tlifecycleCtx context.Context\n}\n\nfunc NewService(lc fx.Lifecycle, p ServiceParams) *Service {\n\tservice := &Service{params: p}\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tservice.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn service\n}\n\nfunc RegisterRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/query_editor\")\n\tendpoint.Use(auth.MWAuthRequired())\n\tendpoint.Use(utils.MWConnectTiDB(s.params.TiDBClient))\n\tendpoint.Use(utils.MWForbidByExperimentalFlag(s.params.Config.EnableExperimental))\n\tendpoint.POST(\"/run\", auth.MWRequireWritePriv(), s.runHandler)\n}\n\ntype RunRequest struct {\n\tStatements string `json:\"statements\" example:\"show databases;\"`\n\tMaxRows    int    `json:\"max_rows\" example:\"1000\"`\n}\n\ntype RunResponse struct {\n\tErrorMsg    string          `json:\"error_msg\"`\n\tColumnNames []string        `json:\"column_names\"`\n\tRows        [][]interface{} `json:\"rows\"`\n\tExecutionMs int64           `json:\"execution_ms\"`\n\tActualRows  int             `json:\"actual_rows\"`\n}\n\nfunc executeStatements(context context.Context, db *sql.DB, statements string) ([]string, [][]interface{}, error) {\n\trows, err := db.QueryContext(context, statements)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tdefer rows.Close()\n\n\tcolNames, err := rows.Columns()\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tretRows := make([][]interface{}, 0)\n\n\tvalues := make([]sql.RawBytes, len(colNames))\n\tscanArgs := make([]interface{}, len(values))\n\tfor i := range values {\n\t\tscanArgs[i] = &values[i]\n\t}\n\n\tfor rows.Next() {\n\t\terr = rows.Scan(scanArgs...)\n\t\tif err != nil {\n\t\t\treturn nil, nil, err\n\t\t}\n\n\t\tretRow := make([]interface{}, 0, len(values))\n\t\tvar value interface{}\n\t\tfor _, col := range values {\n\t\t\tif col == nil {\n\t\t\t\tvalue = nil\n\t\t\t} else {\n\t\t\t\tvalue = string(col)\n\t\t\t}\n\t\t\tretRow = append(retRow, value)\n\t\t}\n\t\tretRows = append(retRows, retRow)\n\t}\n\n\tif err = rows.Err(); err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\treturn colNames, retRows, nil\n}\n\n// @ID queryEditorRun\n// @Summary Run statements\n// @Param request body RunRequest true \"Request body\"\n// @Success 200 {object} RunResponse\n// @Router /query_editor/run [post]\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 403 {object} rest.ErrorResponse\nfunc (s *Service) runHandler(c *gin.Context) {\n\tvar req RunRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tctx, cancel := context.WithTimeout(s.lifecycleCtx, time.Minute*5)\n\tdefer cancel()\n\n\tstartTime := time.Now()\n\tsqlDB, err := utils.GetTiDBConnection(c).DB()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tcolNames, rows, err := executeStatements(ctx, sqlDB, req.Statements)\n\telapsedTime := time.Since(startTime)\n\n\tif err != nil {\n\t\tlog.Warn(\"Failed to execute user input statements\", zap.String(\"statements\", req.Statements), zap.Error(err))\n\t\tc.JSON(http.StatusOK, RunResponse{\n\t\t\tErrorMsg:    err.Error(),\n\t\t\tColumnNames: nil,\n\t\t\tRows:        nil,\n\t\t\tExecutionMs: elapsedTime.Milliseconds(),\n\t\t\tActualRows:  0,\n\t\t})\n\t\treturn\n\t}\n\n\ttruncatedRows := rows\n\tif len(truncatedRows) > req.MaxRows {\n\t\ttruncatedRows = truncatedRows[:req.MaxRows]\n\t}\n\n\tc.JSON(http.StatusOK, RunResponse{\n\t\tColumnNames: colNames,\n\t\tRows:        truncatedRows,\n\t\tExecutionMs: elapsedTime.Milliseconds(),\n\t\tActualRows:  len(rows),\n\t})\n}\n"
  },
  {
    "path": "pkg/apiserver/resource_manager/module.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage resourcemanager\n\nimport \"go.uber.org/fx\"\n\nvar Module = fx.Options(\n\tfx.Provide(newService),\n\tfx.Invoke(registerRouter),\n)\n"
  },
  {
    "path": "pkg/apiserver/resource_manager/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage resourcemanager\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\t\"github.com/pingcap/tidb-dashboard/util/featureflag\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nvar workloadInjectChecker = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)\n\ntype ServiceParams struct {\n\tfx.In\n\tTiDBClient *tidb.Client\n}\n\ntype Service struct {\n\tFeatureResourceManager *featureflag.FeatureFlag\n\n\tparams ServiceParams\n}\n\nfunc newService(p ServiceParams, ff *featureflag.Registry) *Service {\n\treturn &Service{params: p, FeatureResourceManager: ff.Register(\"resource_manager\", \">= 7.1.0\")}\n}\n\nfunc registerRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/resource_manager\")\n\tendpoint.Use(\n\t\tauth.MWAuthRequired(),\n\t\ts.FeatureResourceManager.VersionGuard(),\n\t\tutils.MWConnectTiDB(s.params.TiDBClient),\n\t)\n\t{\n\t\tendpoint.GET(\"/config\", s.GetConfig)\n\t\tendpoint.GET(\"/information\", s.GetInformation)\n\t\tendpoint.GET(\"/information/group_names\", s.resourceGroupNamesHandler)\n\t\tendpoint.GET(\"/calibrate/hardware\", s.GetCalibrateByHardware)\n\t\tendpoint.GET(\"/calibrate/actual\", s.GetCalibrateByActual)\n\t}\n}\n\ntype GetConfigResponse struct {\n\tEnable bool `json:\"enable\" gorm:\"column:tidb_enable_resource_control\"`\n}\n\n// @Summary Get Resource Control enable config\n// @Router /resource_manager/config [get]\n// @Security JwtAuth\n// @Success 200 {object} GetConfigResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) GetConfig(c *gin.Context) {\n\tdb := utils.GetTiDBConnection(c)\n\tresp := &GetConfigResponse{}\n\terr := db.Raw(\"SELECT @@GLOBAL.tidb_enable_resource_control as tidb_enable_resource_control\").Find(resp).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, resp)\n}\n\ntype ResourceInfoRowDef struct {\n\tName      string `json:\"name\" gorm:\"column:NAME\"`\n\tRuPerSec  string `json:\"ru_per_sec\" gorm:\"column:RU_PER_SEC\"`\n\tPriority  string `json:\"priority\" gorm:\"column:PRIORITY\"`\n\tBurstable string `json:\"burstable\" gorm:\"column:BURSTABLE\"`\n}\n\n// @Summary Get Information of Resource Groups\n// @Router /resource_manager/information [get]\n// @Security JwtAuth\n// @Success 200 {object} []ResourceInfoRowDef\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) GetInformation(c *gin.Context) {\n\tdb := utils.GetTiDBConnection(c)\n\tvar cfg []ResourceInfoRowDef\n\terr := db.Table(\"INFORMATION_SCHEMA.RESOURCE_GROUPS\").Scan(&cfg).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, cfg)\n}\n\n// @Summary List all resource groups\n// @Router /resource_manager/information/group_names [get]\n// @Security JwtAuth\n// @Success 200 {object} []string\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) resourceGroupNamesHandler(c *gin.Context) {\n\ttype groupSchemas struct {\n\t\tGroups string `gorm:\"column:NAME\"`\n\t}\n\tvar result []groupSchemas\n\tdb := utils.GetTiDBConnection(c)\n\terr := db.Raw(\"SELECT NAME FROM INFORMATION_SCHEMA.RESOURCE_GROUPS\").Scan(&result).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tstrs := []string{}\n\tfor _, v := range result {\n\t\tstrs = append(strs, strings.ToLower(v.Groups))\n\t}\n\tsort.Strings(strs)\n\tc.JSON(http.StatusOK, strs)\n}\n\ntype CalibrateResponse struct {\n\tEstimatedCapacity int `json:\"estimated_capacity\" gorm:\"column:QUOTA\"`\n}\n\n// @Summary Get calibrate of Resource Groups by hardware deployment\n// @Router /resource_manager/calibrate/hardware [get]\n// @Param workload query string true \"workload\" default(\"tpcc\")\n// @Security JwtAuth\n// @Success 200 {object} CalibrateResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) GetCalibrateByHardware(c *gin.Context) {\n\tw := c.Query(\"workload\")\n\tif w == \"\" {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"workload cannot be empty\"))\n\t\treturn\n\t}\n\tif !workloadInjectChecker.MatchString(w) {\n\t\trest.Error(c, errors.New(\"invalid workload\"))\n\t\treturn\n\t}\n\n\tdb := utils.GetTiDBConnection(c)\n\tresp := &CalibrateResponse{}\n\terr := db.Raw(fmt.Sprintf(\"calibrate resource workload %s\", w)).Scan(resp).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, resp)\n}\n\ntype GetCalibrateByActualRequest struct {\n\tStartTime int64 `json:\"start_time\" form:\"start_time\"`\n\tEndTime   int64 `json:\"end_time\" form:\"end_time\"`\n}\n\n// @Summary Get calibrate of Resource Groups by actual workload\n// @Router /resource_manager/calibrate/actual [get]\n// @Param q query GetCalibrateByActualRequest true \"Query\"\n// @Security JwtAuth\n// @Success 200 {object} CalibrateResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) GetCalibrateByActual(c *gin.Context) {\n\tvar req GetCalibrateByActualRequest\n\tif err := c.ShouldBindQuery(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tstartTime := time.Unix(req.StartTime, 0).Format(\"2006-01-02 15:04:05\")\n\tendTime := time.Unix(req.EndTime, 0).Format(\"2006-01-02 15:04:05\")\n\n\tdb := utils.GetTiDBConnection(c)\n\tresp := &CalibrateResponse{}\n\terr := db.Raw(fmt.Sprintf(\"calibrate resource start_time '%s' end_time '%s'\", startTime, endTime)).Scan(resp).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, resp)\n}\n"
  },
  {
    "path": "pkg/apiserver/slowquery/model.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage slowquery\n\nimport (\n\t\"strings\"\n\n\t\"gorm.io/datatypes\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/util/reflectutil\"\n)\n\ntype Model struct {\n\tDigest string `gorm:\"column:Digest\" json:\"digest\"`\n\tQuery  string `gorm:\"column:Query\" json:\"query\"`\n\n\tInstance string `gorm:\"column:INSTANCE\" json:\"instance\"`\n\tDB       string `gorm:\"column:DB\" json:\"db\"`\n\t// TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\n\tConnectionID string `gorm:\"column:Conn_ID\" json:\"connection_id\"`\n\tSuccess      int    `gorm:\"column:Succ\" json:\"success\"`\n\n\tTimestamp             float64 `gorm:\"column:timestamp\" proj:\"(UNIX_TIMESTAMP(Time) + 0E0)\" json:\"timestamp\" related:\"time\"` // finish time\n\tQueryTime             float64 `gorm:\"column:Query_time\" json:\"query_time\"`                                                  // latency\n\tParseTime             float64 `gorm:\"column:Parse_time\" json:\"parse_time\"`\n\tCompileTime           float64 `gorm:\"column:Compile_time\" json:\"compile_time\"`\n\tRewriteTime           float64 `gorm:\"column:Rewrite_time\" json:\"rewrite_time\"`\n\tPreprocSubqueriesTime float64 `gorm:\"column:Preproc_subqueries_time\" json:\"preproc_subqueries_time\"`\n\tOptimizeTime          float64 `gorm:\"column:Optimize_time\" json:\"optimize_time\"`\n\tWaitTSTime            float64 `gorm:\"column:Wait_TS\" json:\"wait_ts\"`\n\tCopTime               float64 `gorm:\"column:Cop_time\" json:\"cop_time\"`\n\tLockKeysTime          float64 `gorm:\"column:LockKeys_time\" json:\"lock_keys_time\"`\n\tWriteRespTime         float64 `gorm:\"column:Write_sql_response_total\" json:\"write_sql_response_total\"`\n\tExecRetryTime         float64 `gorm:\"column:Exec_retry_time\" json:\"exec_retry_time\"`\n\n\tMemoryMax      int     `gorm:\"column:Mem_max\" json:\"memory_max\"`\n\tDiskMax        int     `gorm:\"column:Disk_max\" json:\"disk_max\"`\n\tMemArbitration float64 `gorm:\"column:Mem_arbitration\" json:\"mem_arbitration\"`\n\n\t// TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\n\tTxnStartTS string `gorm:\"column:Txn_start_ts\" json:\"txn_start_ts\"`\n\n\t// Detail\n\tPrevStmt   string         `gorm:\"column:Prev_stmt\" json:\"prev_stmt\"`\n\tPlan       string         `gorm:\"column:Plan\" json:\"plan\"` // deprecated, replaced by BinaryPlanText\n\tBinaryPlan string         `gorm:\"column:Binary_plan\" json:\"binary_plan\"`\n\tWarnings   datatypes.JSON `gorm:\"column:Warnings\" json:\"warnings\"`\n\n\t// Basic\n\tIsInternal      int    `gorm:\"column:Is_internal\" json:\"is_internal\"`\n\tIndexNames      string `gorm:\"column:Index_names\" json:\"index_names\"`\n\tStats           string `gorm:\"column:Stats\" json:\"stats\"`\n\tBackoffTypes    string `gorm:\"column:Backoff_types\" json:\"backoff_types\"`\n\tPrepared        int    `gorm:\"column:Prepared\" json:\"prepared\"`\n\tPlanFromCache   int    `gorm:\"column:Plan_from_cache\" json:\"plan_from_cache\"`\n\tPlanFromBinding int    `gorm:\"column:Plan_from_binding\" json:\"plan_from_binding\"`\n\n\t// Connection\n\tUser string `gorm:\"column:User\" json:\"user\"`\n\tHost string `gorm:\"column:Host\" json:\"host\"`\n\n\t// Time\n\tProcessTime            float64 `gorm:\"column:Process_time\" json:\"process_time\"`\n\tWaitTime               float64 `gorm:\"column:Wait_time\" json:\"wait_time\"`\n\tBackoffTime            float64 `gorm:\"column:Backoff_time\" json:\"backoff_time\"`\n\tGetCommitTSTime        float64 `gorm:\"column:Get_commit_ts_time\" json:\"get_commit_ts_time\"`\n\tLocalLatchWaitTime     float64 `gorm:\"column:Local_latch_wait_time\" json:\"local_latch_wait_time\"`\n\tResolveLockTime        float64 `gorm:\"column:Resolve_lock_time\" json:\"resolve_lock_time\"`\n\tPrewriteTime           float64 `gorm:\"column:Prewrite_time\" json:\"prewrite_time\"`\n\tWaitPreWriteBinlogTime float64 `gorm:\"column:Wait_prewrite_binlog_time\" json:\"wait_prewrite_binlog_time\"`\n\tCommitTime             float64 `gorm:\"column:Commit_time\" json:\"commit_time\"`\n\tCommitBackoffTime      float64 `gorm:\"column:Commit_backoff_time\" json:\"commit_backoff_time\"`\n\tCopProcAvg             float64 `gorm:\"column:Cop_proc_avg\" json:\"cop_proc_avg\"`\n\tCopProcP90             float64 `gorm:\"column:Cop_proc_p90\" json:\"cop_proc_p90\"`\n\tCopProcMax             float64 `gorm:\"column:Cop_proc_max\" json:\"cop_proc_max\"`\n\tCopWaitAvg             float64 `gorm:\"column:Cop_wait_avg\" json:\"cop_wait_avg\"`\n\tCopWaitP90             float64 `gorm:\"column:Cop_wait_p90\" json:\"cop_wait_p90\"`\n\tCopWaitMax             float64 `gorm:\"column:Cop_wait_max\" json:\"cop_wait_max\"`\n\n\t// Transaction\n\tWriteKeys      int `gorm:\"column:Write_keys\" json:\"write_keys\"`\n\tWriteSize      int `gorm:\"column:Write_size\" json:\"write_size\"`\n\tPrewriteRegion int `gorm:\"column:Prewrite_region\" json:\"prewrite_region\"`\n\tTxnRetry       int `gorm:\"column:Txn_retry\" json:\"txn_retry\"`\n\n\t// Coprocessor\n\tRequestCount uint   `gorm:\"column:Request_count\" json:\"request_count\"`\n\tProcessKeys  uint   `gorm:\"column:Process_keys\" json:\"process_keys\"`\n\tTotalKeys    uint   `gorm:\"column:Total_keys\" json:\"total_keys\"`\n\tCopProcAddr  string `gorm:\"column:Cop_proc_addr\" json:\"cop_proc_addr\"`\n\tCopWaitAddr  string `gorm:\"column:Cop_wait_addr\" json:\"cop_wait_addr\"`\n\n\t// RocksDB\n\tRocksdbDeleteSkippedCount uint `gorm:\"column:Rocksdb_delete_skipped_count\" json:\"rocksdb_delete_skipped_count\"`\n\tRocksdbKeySkippedCount    uint `gorm:\"column:Rocksdb_key_skipped_count\" json:\"rocksdb_key_skipped_count\"`\n\tRocksdbBlockCacheHitCount uint `gorm:\"column:Rocksdb_block_cache_hit_count\" json:\"rocksdb_block_cache_hit_count\"`\n\tRocksdbBlockReadCount     uint `gorm:\"column:Rocksdb_block_read_count\" json:\"rocksdb_block_read_count\"`\n\tRocksdbBlockReadByte      uint `gorm:\"column:Rocksdb_block_read_byte\" json:\"rocksdb_block_read_byte\"`\n\n\t// Computed fields\n\tBinaryPlanJSON string `json:\"binary_plan_json\"` // binary plan json format\n\tBinaryPlanText string `json:\"binary_plan_text\"` // binary plan plain text\n\n\t// Resource Control\n\tRU            float64 `gorm:\"column:RU\" json:\"ru\" proj:\"(Request_unit_write + Request_unit_read)\" related:\"Request_unit_write,Request_unit_read\"`\n\tQueuedTime    float64 `gorm:\"column:Time_queued_by_rc\" json:\"time_queued_by_rc\"`\n\tResourceGroup string  `gorm:\"column:Resource_group\" json:\"resource_group\"`\n\n\t// Network fields\n\tUnpackedBytesSentTiKVTotal            uint `gorm:\"column:Unpacked_bytes_sent_tikv_total\" json:\"unpacked_bytes_sent_tikv_total\"`\n\tUnpackedBytesReceivedTiKVTotal        uint `gorm:\"column:Unpacked_bytes_received_tikv_total\" json:\"unpacked_bytes_received_tikv_total\"`\n\tUnpackedBytesSentTiKVCrossZone        uint `gorm:\"column:Unpacked_bytes_sent_tikv_cross_zone\" json:\"unpacked_bytes_sent_tikv_cross_zone\"`\n\tUnpackedBytesReceivedTiKVCrossZone    uint `gorm:\"column:Unpacked_bytes_received_tikv_cross_zone\" json:\"unpacked_bytes_received_tikv_cross_zone\"`\n\tUnpackedBytesSentTiFlashTotal         uint `gorm:\"column:Unpacked_bytes_sent_tiflash_total\" json:\"unpacked_bytes_sent_tiflash_total\"`\n\tUnpackedBytesReceivedTiFlashTotal     uint `gorm:\"column:Unpacked_bytes_received_tiflash_total\" json:\"unpacked_bytes_received_tiflash_total\"`\n\tUnpackedBytesSentTiFlashCrossZone     uint `gorm:\"column:Unpacked_bytes_sent_tiflash_cross_zone\" json:\"unpacked_bytes_sent_tiflash_cross_zone\"`\n\tUnpackedBytesReceivedTiFlashCrossZone uint `gorm:\"column:Unpacked_bytes_received_tiflash_cross_zone\" json:\"unpacked_bytes_received_tiflash_cross_zone\"`\n\n\t// IA remote read\n\tIARemoteReadSegmentSize     uint64  `gorm:\"column:IA_remote_read_segment_size\" json:\"ia_remote_read_segment_size\"`\n\tIARemoteReadSegmentWaitTime float64 `gorm:\"column:IA_remote_read_segment_wait_time\" json:\"ia_remote_read_segment_wait_time\"`\n}\n\ntype Field struct {\n\tColumnName string\n\tJSONName   string\n\tProjection string\n\t// `related` tag is used to verify a non-existent column, which is aggregated/projection from the columns represented by related.\n\tRelated []string\n}\n\nfunc getFieldsAndTags() (slowQueryFields []Field) {\n\tfields := reflectutil.GetFieldsAndTags(Model{}, []string{\"gorm\", \"proj\", \"json\", \"related\"})\n\n\tfor _, f := range fields {\n\t\tsqf := Field{\n\t\t\tColumnName: utils.GetGormColumnName(f.Tags[\"gorm\"]),\n\t\t\tJSONName:   f.Tags[\"json\"],\n\t\t\tProjection: f.Tags[\"proj\"],\n\t\t}\n\n\t\tif f.Tags[\"related\"] != \"\" {\n\t\t\tsqf.Related = strings.Split(f.Tags[\"related\"], \",\")\n\t\t}\n\n\t\tslowQueryFields = append(slowQueryFields, sqf)\n\t}\n\n\treturn\n}\n\nfunc filterFieldsByColumns(fields []Field, columns []string) []Field {\n\tcolMap := map[string]struct{}{}\n\tfor _, c := range columns {\n\t\tcolMap[strings.ToLower(c)] = struct{}{}\n\t}\n\n\tfilteredFields := []Field{}\n\tfor _, f := range fields {\n\t\t_, ok := colMap[strings.ToLower(f.ColumnName)]\n\t\tif ok || (f.Projection != \"\") {\n\t\t\tfilteredFields = append(filteredFields, f)\n\t\t}\n\t}\n\n\treturn filteredFields\n}\n"
  },
  {
    "path": "pkg/apiserver/slowquery/module.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage slowquery\n\nimport \"go.uber.org/fx\"\n\nvar Module = fx.Options(\n\tfx.Provide(newService),\n\tfx.Invoke(registerRouter),\n)\n"
  },
  {
    "path": "pkg/apiserver/slowquery/queries.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage slowquery\n\nimport (\n\t\"strings\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils\"\n)\n\nconst (\n\tSlowQueryTable = \"INFORMATION_SCHEMA.CLUSTER_SLOW_QUERY\"\n)\n\ntype GetListRequest struct {\n\tBeginTime     int      `json:\"begin_time\" form:\"begin_time\"`\n\tEndTime       int      `json:\"end_time\" form:\"end_time\"`\n\tDB            []string `json:\"db\" form:\"db\"`\n\tResourceGroup []string `json:\"resource_group\" form:\"resource_group\"`\n\tLimit         int      `json:\"limit\" form:\"limit\"`\n\tText          string   `json:\"text\" form:\"text\"`\n\tOrderBy       string   `json:\"orderBy\" form:\"orderBy\"`\n\tIsDesc        bool     `json:\"desc\" form:\"desc\"`\n\n\t// for showing slow queries in the statement detail page\n\tPlans  []string `json:\"plans\" form:\"plans\"`\n\tDigest string   `json:\"digest\" form:\"digest\"`\n\n\tFields string `json:\"fields\" form:\"fields\"` // example: \"Query,Digest\"\n}\n\ntype GetDetailRequest struct {\n\tDigest    string  `json:\"digest\" form:\"digest\"`\n\tTimestamp float64 `json:\"timestamp\" form:\"timestamp\"`\n\t// TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\n\tConnectID string `json:\"connect_id\" form:\"connect_id\"`\n}\n\nfunc QuerySlowLogList(req *GetListRequest, sysSchema *utils.SysSchema, db *gorm.DB) ([]Model, error) {\n\tslowQueryColumns, err := sysSchema.GetTableColumnNames(db, SlowQueryTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treqFields := strings.Split(req.Fields, \",\")\n\tselectStmt, err := genSelectStmt(slowQueryColumns, reqFields)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx := db.\n\t\tSelect(selectStmt)\n\n\tif req.BeginTime != 0 && req.EndTime != 0 {\n\t\ttx = tx.Where(\"Time BETWEEN FROM_UNIXTIME(?) AND FROM_UNIXTIME(?)\", req.BeginTime, req.EndTime)\n\t}\n\n\tif req.Limit <= 0 {\n\t\treq.Limit = 100\n\t}\n\ttx = tx.Limit(req.Limit)\n\n\tif req.Text != \"\" {\n\t\tlowerStr := strings.ToLower(req.Text)\n\t\tarr := strings.FieldsSeq(lowerStr)\n\t\tfor v := range arr {\n\t\t\ttx = tx.Where(\n\t\t\t\t`Txn_start_ts REGEXP ?\n\t\t\t\t OR LOWER(Digest) REGEXP ?\n\t\t\t\t OR LOWER(CONVERT(Prev_stmt USING utf8)) REGEXP ?\n\t\t\t\t OR LOWER(CONVERT(Query USING utf8)) REGEXP ?`,\n\t\t\t\tv, v, v, v,\n\t\t\t)\n\t\t}\n\t}\n\n\tif len(req.DB) > 0 {\n\t\ttx = tx.Where(\"DB IN (?)\", req.DB)\n\t}\n\n\tif len(req.ResourceGroup) > 0 {\n\t\ttx = tx.Where(\"RESOURCE_GROUP IN (?)\", req.ResourceGroup)\n\t}\n\n\t// more robust\n\tif req.OrderBy == \"\" {\n\t\treq.OrderBy = \"timestamp\"\n\t}\n\torderStmt, err := genOrderStmt(slowQueryColumns, req.OrderBy, req.IsDesc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ttx = tx.Order(orderStmt)\n\n\t// in TiDB Dashboard SQL Statements detail page, we can get multiple plan digests for a certain SQL statement.\n\t// we can get related slow queries for these plan digests.\n\tif len(req.Plans) > 0 {\n\t\ttx = tx.Where(\"Plan_digest IN (?)\", req.Plans)\n\t}\n\n\t// in TiDB Dashboard SQL Statements detail page, we can get related slow queries by its SQL statement digest.\n\tif req.Digest != \"\" {\n\t\ttx = tx.Where(\"Digest = ?\", req.Digest)\n\t}\n\n\tvar results []Model\n\terr = tx.Find(&results).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\t// truncate each row's query, keep the start 1000 characters to avoid too long text\n\t// if user want to see the full query, they can access the detail api\n\tfor i := range results {\n\t\tif len(results[i].Query) > 1000 {\n\t\t\tresults[i].Query = results[i].Query[:1000] + \"...\"\n\t\t}\n\t}\n\treturn results, nil\n}\n\nfunc QuerySlowLogDetail(req *GetDetailRequest, sysSchema *utils.SysSchema, db *gorm.DB) (*Model, error) {\n\tvar result Model\n\tslowQueryColumns, err := sysSchema.GetTableColumnNames(db, SlowQueryTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tselectStmt, err := genSelectStmt(slowQueryColumns, []string{\"*\"})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = db.\n\t\tSelect(selectStmt).\n\t\tWhere(\"Digest = ?\", req.Digest).\n\t\tWhere(\"Time = FROM_UNIXTIME(?)\", req.Timestamp).\n\t\tWhere(\"Conn_id = ?\", req.ConnectID).\n\t\tFirst(&result).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &result, nil\n}\n\nfunc GetAvailableFields(sysSchema *utils.SysSchema, db *gorm.DB) ([]string, error) {\n\tcs, err := sysSchema.GetTableColumnNames(db, SlowQueryTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfields := filterFieldsByColumns(getFieldsAndTags(), cs)\n\tjsonNames := make([]string, 0, len(fields))\n\tfor _, f := range fields {\n\t\tjsonNames = append(jsonNames, f.JSONName)\n\t}\n\n\treturn jsonNames, nil\n}\n"
  },
  {
    "path": "pkg/apiserver/slowquery/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage slowquery\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joomcode/errorx\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\tcommonUtils \"github.com/pingcap/tidb-dashboard/pkg/utils\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nvar (\n\tErrNS     = errorx.NewNamespace(\"error.api.slow_query\")\n\tErrNoData = ErrNS.NewType(\"export_no_data\")\n)\n\ntype ServiceParams struct {\n\tfx.In\n\tTiDBClient *tidb.Client\n\tSysSchema  *commonUtils.SysSchema\n}\n\ntype Service struct {\n\tparams ServiceParams\n}\n\nfunc newService(p ServiceParams) *Service {\n\treturn &Service{params: p}\n}\n\nfunc registerRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/slow_query\")\n\t{\n\t\tendpoint.GET(\"/download\", s.downloadHandler)\n\n\t\tendpoint.Use(auth.MWAuthRequired())\n\t\tendpoint.Use(utils.MWConnectTiDB(s.params.TiDBClient))\n\t\t{\n\t\t\tendpoint.GET(\"/list\", s.getList)\n\t\t\tendpoint.GET(\"/detail\", s.getDetails)\n\n\t\t\tendpoint.POST(\"/download/token\", s.downloadTokenHandler)\n\n\t\t\tendpoint.GET(\"/available_fields\", s.getAvailableFields)\n\t\t}\n\t}\n}\n\n// @Summary List all slow queries\n// @Param q query GetListRequest true \"Query\"\n// @Success 200 {array} Model\n// @Router /slow_query/list [get]\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getList(c *gin.Context) {\n\tvar req GetListRequest\n\tif err := c.ShouldBindQuery(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tdb := utils.GetTiDBConnection(c)\n\tresults, err := QuerySlowLogList(&req, s.params.SysSchema, db.Table(SlowQueryTable))\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tc.JSON(http.StatusOK, results)\n}\n\n// @Summary Get details of a slow query\n// @Param q query GetDetailRequest true \"Query\"\n// @Success 200 {object} Model\n// @Router /slow_query/detail [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) getDetails(c *gin.Context) {\n\tvar req GetDetailRequest\n\tif err := c.ShouldBindQuery(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tdb := utils.GetTiDBConnection(c)\n\tresult, err := QuerySlowLogDetail(&req, s.params.SysSchema, db.Table(SlowQueryTable))\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\t// generate binary plan json\n\t//\n\t// Due to a kernel bug, the binary plan may fail to parse due to\n\t// encoding issues. Additionally, since the binary plan field is\n\t// not a required field, we can mask this error.\n\t//\n\t// See: https://github.com/pingcap/tidb-dashboard/issues/1515\n\tif result.BinaryPlan != \"\" {\n\t\t// may failed but it's ok\n\t\tresult.BinaryPlanText, err = utils.GenerateBinaryPlanText(db, result.BinaryPlan)\n\t\t// may failed but it's ok\n\t\tresult.BinaryPlanJSON, _ = utils.GenerateBinaryPlanJSON(result.BinaryPlan)\n\n\t\tif err == nil {\n\t\t\t// reduce response size\n\t\t\tresult.BinaryPlan = \"\"\n\t\t\tresult.Plan = \"\"\n\t\t}\n\t}\n\n\tc.JSON(http.StatusOK, *result)\n}\n\n// @Router /slow_query/download/token [post]\n// @Summary Generate a download token for exported slow query statements\n// @Produce plain\n// @Param request body GetListRequest true \"Request body\"\n// @Success 200 {string} string \"xxx\"\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) downloadTokenHandler(c *gin.Context) {\n\tvar req GetListRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tfields := []string{}\n\tif strings.TrimSpace(req.Fields) != \"\" {\n\t\tfields = strings.Split(req.Fields, \",\")\n\t}\n\tdb := utils.GetTiDBConnection(c)\n\tlist, err := QuerySlowLogList(&req, s.params.SysSchema, db.Table(SlowQueryTable))\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tif len(list) == 0 {\n\t\trest.Error(c, ErrNoData.NewWithNoMessage())\n\t\treturn\n\t}\n\n\t// interface{} tricky\n\trawData := make([]interface{}, len(list))\n\tfor i, v := range list {\n\t\trawData[i] = v\n\t}\n\n\t// convert data\n\tcsvData := utils.GenerateCSVFromRaw(rawData, fields, []string{})\n\n\t// generate temp file that persist encrypted data\n\ttimeLayout := \"0102150405\"\n\tbeginTime := time.Unix(int64(req.BeginTime), 0).Format(timeLayout)\n\tendTime := time.Unix(int64(req.EndTime), 0).Format(timeLayout)\n\ttoken, err := utils.ExportCSV(csvData,\n\t\tfmt.Sprintf(\"slowquery_%s_%s_*.csv\", beginTime, endTime),\n\t\t\"slowquery/download\")\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.String(http.StatusOK, token)\n}\n\n// @Router /slow_query/download [get]\n// @Summary Download slow query statements\n// @Produce text/csv\n// @Param token query string true \"download token\"\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) downloadHandler(c *gin.Context) {\n\ttoken := c.Query(\"token\")\n\tutils.DownloadByToken(token, \"slowquery/download\", c)\n}\n\n// @Summary Get available field names\n// @Description Get available field names by slowquery table columns\n// @Success 200 {array} string\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Security JwtAuth\n// @Router /slow_query/available_fields [get]\nfunc (s *Service) getAvailableFields(c *gin.Context) {\n\tdb := utils.GetTiDBConnection(c)\n\tjsonNames, err := GetAvailableFields(s.params.SysSchema, db)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tc.JSON(http.StatusOK, jsonNames)\n}\n"
  },
  {
    "path": "pkg/apiserver/slowquery/statement_gen.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage slowquery\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/samber/lo\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n)\n\nvar ErrUnknownColumn = ErrNS.NewType(\"unknown_column\")\n\nfunc genSelectStmt(tableColumns []string, reqJSONColumns []string) (string, error) {\n\tfields := getFieldsAndTags()\n\n\t// use required fields filter when not all fields are requested\n\tif reqJSONColumns[0] != \"*\" {\n\t\t// These three fields are the most basic information of a slow query record and should contain them\n\t\trequiredFields := lo.Uniq(append(reqJSONColumns, \"digest\", \"connection_id\", \"timestamp\"))\n\t\tfields = lo.Filter(fields, func(f Field, _ int) bool {\n\t\t\treturn lo.Contains(requiredFields, f.JSONName)\n\t\t})\n\t}\n\n\t// We have both TiDB 4.x and TiDB 5.x columns listed in the model. Filter out columns that do not exist in current version TiDB schema.\n\tfields = lo.Filter(fields, func(f Field, _ int) bool {\n\t\tvar representedColumns []string\n\t\tif len(f.Related) != 0 {\n\t\t\trepresentedColumns = f.Related\n\t\t} else {\n\t\t\trepresentedColumns = []string{f.ColumnName}\n\t\t}\n\t\t// For compatibility with old TiDB, we need to check if the column exists in the table.\n\t\t// Dependent columns of the requested field must exist in the db schema. Otherwise, the requested field will be ignored.\n\t\treturn utils.IsSubsetICaseInsensitive(tableColumns, representedColumns)\n\t})\n\n\tif len(fields) == 0 {\n\t\treturn \"\", ErrUnknownColumn.New(\"all columns are not included in the current version TiDB schema, columns: %q\", reqJSONColumns)\n\t}\n\n\tstmt := lo.Map(fields, func(f Field, _ int) string {\n\t\tif f.Projection == \"\" {\n\t\t\treturn f.ColumnName\n\t\t}\n\t\treturn fmt.Sprintf(\"%s AS %s\", f.Projection, f.ColumnName)\n\t})\n\treturn strings.Join(stmt, \", \"), nil\n}\n\nfunc genOrderStmt(tableColumns []string, orderBy string, isDesc bool) (string, error) {\n\tvar order string\n\t// to handle the special case: timestamp\n\t// Order by column instead of expression, see related optimization in TiDB: https://github.com/pingcap/tidb/pull/20750\n\tif orderBy == \"timestamp\" {\n\t\torder = \"Time\"\n\t} else {\n\t\t// We have both TiDB 4.x and TiDB 5.x columns listed in the model. Filter out columns that do not exist in current version TiDB schema.\n\t\tfields := lo.Filter(getFieldsAndTags(), func(f Field, _ int) bool {\n\t\t\tvar representedColumns []string\n\t\t\tif len(f.Related) != 0 {\n\t\t\t\trepresentedColumns = f.Related\n\t\t\t} else {\n\t\t\t\trepresentedColumns = []string{f.ColumnName}\n\t\t\t}\n\t\t\t// For compatibility with old TiDB, we need to check if the column exists in the table.\n\t\t\t// Dependent columns of the requested field must exist in the db schema. Otherwise, the requested field will be ignored.\n\t\t\treturn utils.IsSubsetICaseInsensitive(tableColumns, representedColumns)\n\t\t})\n\t\torderField, ok := lo.Find(fields, func(f Field) bool {\n\t\t\treturn f.JSONName == orderBy\n\t\t})\n\t\tif !ok {\n\t\t\treturn \"\", ErrUnknownColumn.New(\"unknown order by %s\", orderBy)\n\t\t}\n\n\t\torder = orderField.ColumnName\n\t}\n\n\tif isDesc {\n\t\torder = fmt.Sprintf(\"%s DESC\", order)\n\t} else {\n\t\torder = fmt.Sprintf(\"%s ASC\", order)\n\t}\n\n\treturn order, nil\n}\n"
  },
  {
    "path": "pkg/apiserver/statement/config.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage statement\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\n\t\"github.com/samber/lo\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n)\n\n// incoming configuration field should have the gorm tag `column` used to specify global variables\n// sql will be built like this,\n// struct { FieldName `gorm:\"column:some_global_var\"` } -> @@GLOBAL.some_global_var AS some_global_var.\nfunc buildGlobalConfigProjectionSelectSQL(config interface{}) string {\n\tstr := buildStringByStructField(config, func(f reflect.StructField) (string, bool) {\n\t\tgormTag, ok := f.Tag.Lookup(\"gorm\")\n\t\tif !ok {\n\t\t\treturn \"\", false\n\t\t}\n\t\tcolumn := utils.GetGormColumnName(gormTag)\n\t\treturn fmt.Sprintf(\"@@GLOBAL.%s AS %s\", column, column), true\n\t}, \", \")\n\treturn \"SELECT \" + str // #nosec\n}\n\n// sql will be built like this,\n// struct { FieldName `gorm:\"column:some_global_var\"` } -> @@GLOBAL.some_global_var = @FieldName\n// `allowedFields` means only allowed fields can be kept in built SQL.\nfunc buildGlobalConfigNamedArgsUpdateSQL(config interface{}, allowedFields ...string) string {\n\tstr := buildStringByStructField(config, func(f reflect.StructField) (string, bool) {\n\t\t// extract fields on demand\n\t\tif len(allowedFields) != 0 && !lo.Contains(allowedFields, f.Name) {\n\t\t\treturn \"\", false\n\t\t}\n\n\t\tgormTag, ok := f.Tag.Lookup(\"gorm\")\n\t\tif !ok {\n\t\t\treturn \"\", false\n\t\t}\n\t\tcolumn := utils.GetGormColumnName(gormTag)\n\t\treturn fmt.Sprintf(\"@@GLOBAL.%s = @%s\", column, f.Name), true\n\t}, \", \")\n\treturn \"SET \" + str // #nosec\n}\n\nfunc buildStringByStructField(i interface{}, buildFunc func(f reflect.StructField) (string, bool), sep string) string {\n\tvar t reflect.Type\n\tif reflect.ValueOf(i).Kind() == reflect.Pointer {\n\t\tt = reflect.TypeOf(i).Elem()\n\t} else {\n\t\tt = reflect.TypeOf(i)\n\t}\n\n\tstrs := []string{}\n\tfNum := t.NumField()\n\tfor i := range fNum {\n\t\tstr, ok := buildFunc(t.Field(i))\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tstrs = append(strs, str)\n\t}\n\treturn strings.Join(strs, sep)\n}\n"
  },
  {
    "path": "pkg/apiserver/statement/config_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage statement\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/check\"\n)\n\nfunc TestT(t *testing.T) {\n\tcheck.CustomVerboseFlag = true\n\tcheck.TestingT(t)\n}\n\nvar _ = check.Suite(&testConfigSuite{})\n\ntype testConfigSuite struct{}\n\ntype testConfig struct {\n\tEnable          bool `json:\"enable\" gorm:\"column:tidb_enable_stmt_summary\"`\n\tRefreshInterval int  `json:\"refresh_interval\" gorm:\"column:tidb_stmt_summary_refresh_interval\"`\n}\n\nfunc (t *testConfigSuite) Test_buildGlobalConfigProjectionSelectSQL_struct_success(c *check.C) {\n\ttestConfigStmt := \"SELECT @@GLOBAL.tidb_enable_stmt_summary AS tidb_enable_stmt_summary, @@GLOBAL.tidb_stmt_summary_refresh_interval AS tidb_stmt_summary_refresh_interval\"\n\tc.Assert(buildGlobalConfigProjectionSelectSQL(testConfig{}), check.Equals, testConfigStmt)\n}\n\nfunc (t *testConfigSuite) Test_buildGlobalConfigProjectionSelectSQL_ptr_success(c *check.C) {\n\ttestConfigStmt := \"SELECT @@GLOBAL.tidb_enable_stmt_summary AS tidb_enable_stmt_summary, @@GLOBAL.tidb_stmt_summary_refresh_interval AS tidb_stmt_summary_refresh_interval\"\n\tc.Assert(buildGlobalConfigProjectionSelectSQL(&testConfig{}), check.Equals, testConfigStmt)\n}\n\ntype testConfig2 struct {\n\tEnable          bool `json:\"enable\" gorm:\"column:tidb_enable_stmt_summary\"`\n\tRefreshInterval int  `json:\"refresh_interval\"`\n}\n\nfunc (t *testConfigSuite) Test_buildGlobalConfigProjectionSelectSQL_without_gorm_tag(c *check.C) {\n\ttestConfigStmt := \"SELECT @@GLOBAL.tidb_enable_stmt_summary AS tidb_enable_stmt_summary\"\n\tc.Assert(buildGlobalConfigProjectionSelectSQL(&testConfig2{}), check.Equals, testConfigStmt)\n}\n\nfunc (t *testConfigSuite) Test_buildGlobalConfigNamedArgsUpdateSQL_struct_success(c *check.C) {\n\ttestConfigStmt := \"SET @@GLOBAL.tidb_enable_stmt_summary = @Enable, @@GLOBAL.tidb_stmt_summary_refresh_interval = @RefreshInterval\"\n\tc.Assert(buildGlobalConfigNamedArgsUpdateSQL(testConfig{Enable: true, RefreshInterval: 1800}), check.Equals, testConfigStmt)\n}\n\nfunc (t *testConfigSuite) Test_buildGlobalConfigNamedArgsUpdateSQL_ptr_success(c *check.C) {\n\ttestConfigStmt := \"SET @@GLOBAL.tidb_enable_stmt_summary = @Enable, @@GLOBAL.tidb_stmt_summary_refresh_interval = @RefreshInterval\"\n\tc.Assert(buildGlobalConfigNamedArgsUpdateSQL(&testConfig{Enable: true, RefreshInterval: 1800}), check.Equals, testConfigStmt)\n}\n\nfunc (t *testConfigSuite) Test_buildGlobalConfigNamedArgsUpdateSQL_without_gorm_tag(c *check.C) {\n\ttestConfigStmt := \"SET @@GLOBAL.tidb_enable_stmt_summary = @Enable\"\n\tc.Assert(buildGlobalConfigNamedArgsUpdateSQL(&testConfig2{Enable: true, RefreshInterval: 1800}), check.Equals, testConfigStmt)\n}\n\nfunc (t *testConfigSuite) Test_buildGlobalConfigNamedArgsUpdateSQL_extract_fields(c *check.C) {\n\ttestConfigStmt := \"SET @@GLOBAL.tidb_enable_stmt_summary = @Enable\"\n\tc.Assert(buildGlobalConfigNamedArgsUpdateSQL(&testConfig{Enable: true, RefreshInterval: 1800}, \"Enable\"), check.Equals, testConfigStmt)\n}\n"
  },
  {
    "path": "pkg/apiserver/statement/models.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage statement\n\nimport (\n\t\"strings\"\n\n\t\"github.com/samber/lo\"\n\t\"gorm.io/gorm\"\n\t\"gorm.io/gorm/schema\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/util/reflectutil\"\n)\n\ntype Model struct {\n\tAggBeginTime             int     `json:\"summary_begin_time\" agg:\"FLOOR(UNIX_TIMESTAMP(MIN(summary_begin_time)))\"`\n\tAggEndTime               int     `json:\"summary_end_time\" agg:\"FLOOR(UNIX_TIMESTAMP(MAX(summary_end_time)))\"`\n\tAggDigestText            string  `json:\"digest_text\" agg:\"ANY_VALUE(digest_text)\"`\n\tAggDigest                string  `json:\"digest\" agg:\"ANY_VALUE(digest)\"`\n\tAggExecCount             int     `json:\"exec_count\" agg:\"SUM(exec_count)\"`\n\tAggStmtType              string  `json:\"stmt_type\" agg:\"ANY_VALUE(stmt_type)\"`\n\tAggSumErrors             int     `json:\"sum_errors\" agg:\"SUM(sum_errors)\"`\n\tAggSumWarnings           int     `json:\"sum_warnings\" agg:\"SUM(sum_warnings)\"`\n\tAggSumLatency            int     `json:\"sum_latency\" agg:\"SUM(sum_latency)\"`\n\tAggMaxLatency            int     `json:\"max_latency\" agg:\"MAX(max_latency)\"`\n\tAggMinLatency            int     `json:\"min_latency\" agg:\"MIN(min_latency)\"`\n\tAggAvgLatency            int     `json:\"avg_latency\" agg:\"CAST(SUM(exec_count * avg_latency) / SUM(exec_count) AS SIGNED)\"`\n\tAggAvgParseLatency       int     `json:\"avg_parse_latency\" agg:\"CAST(SUM(exec_count * avg_parse_latency) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxParseLatency       int     `json:\"max_parse_latency\" agg:\"MAX(max_parse_latency)\"`\n\tAggAvgCompileLatency     int     `json:\"avg_compile_latency\" agg:\"CAST(SUM(exec_count * avg_compile_latency) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxCompileLatency     int     `json:\"max_compile_latency\" agg:\"MAX(max_compile_latency)\"`\n\tAggSumCopTaskNum         int     `json:\"sum_cop_task_num\" agg:\"SUM(sum_cop_task_num)\"`\n\tAggAvgCopProcessTime     int     `json:\"avg_cop_process_time\" agg:\"CAST(SUM(exec_count * avg_process_time) / SUM(sum_cop_task_num) AS SIGNED)\"` // avg process time per copr task\n\tAggMaxCopProcessTime     int     `json:\"max_cop_process_time\" agg:\"MAX(max_cop_process_time)\"`                                                  // max process time per copr task\n\tAggAvgCopWaitTime        int     `json:\"avg_cop_wait_time\" agg:\"CAST(SUM(exec_count * avg_wait_time) / SUM(sum_cop_task_num) AS SIGNED)\"`       // avg wait time per copr task\n\tAggMaxCopWaitTime        int     `json:\"max_cop_wait_time\" agg:\"MAX(max_cop_wait_time)\"`                                                        // max wait time per copr task\n\tAggAvgProcessTime        int     `json:\"avg_process_time\" agg:\"CAST(SUM(exec_count * avg_process_time) / SUM(exec_count) AS SIGNED)\"`           // avg total process time per sql\n\tAggMaxProcessTime        int     `json:\"max_process_time\" agg:\"MAX(max_process_time)\"`                                                          // max process time per sql\n\tAggAvgWaitTime           int     `json:\"avg_wait_time\" agg:\"CAST(SUM(exec_count * avg_wait_time) / SUM(exec_count) AS SIGNED)\"`                 // avg total wait time per sql\n\tAggMaxWaitTime           int     `json:\"max_wait_time\" agg:\"MAX(max_wait_time)\"`                                                                // max wait time per sql\n\tAggAvgBackoffTime        int     `json:\"avg_backoff_time\" agg:\"CAST(SUM(exec_count * avg_backoff_time) / SUM(exec_count) AS SIGNED)\"`           // avg total back off time per sql\n\tAggMaxBackoffTime        int     `json:\"max_backoff_time\" agg:\"MAX(max_backoff_time)\"`                                                          // max back off time per sql\n\tAggAvgTotalKeys          int     `json:\"avg_total_keys\" agg:\"CAST(SUM(exec_count * avg_total_keys) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxTotalKeys          int     `json:\"max_total_keys\" agg:\"MAX(max_total_keys)\"`\n\tAggAvgProcessedKeys      int     `json:\"avg_processed_keys\" agg:\"CAST(SUM(exec_count * avg_processed_keys) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxProcessedKeys      int     `json:\"max_processed_keys\" agg:\"MAX(max_processed_keys)\"`\n\tAggAvgPrewriteTime       int     `json:\"avg_prewrite_time\" agg:\"CAST(SUM(exec_count * avg_prewrite_time) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxPrewriteTime       int     `json:\"max_prewrite_time\" agg:\"MAX(max_prewrite_time)\"`\n\tAggAvgCommitTime         int     `json:\"avg_commit_time\" agg:\"CAST(SUM(exec_count * avg_commit_time) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxCommitTime         int     `json:\"max_commit_time\" agg:\"MAX(max_commit_time)\"`\n\tAggAvgGetCommitTsTime    int     `json:\"avg_get_commit_ts_time\" agg:\"CAST(SUM(exec_count * avg_get_commit_ts_time) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxGetCommitTsTime    int     `json:\"max_get_commit_ts_time\" agg:\"MAX(max_get_commit_ts_time)\"`\n\tAggAvgCommitBackoffTime  int     `json:\"avg_commit_backoff_time\" agg:\"CAST(SUM(exec_count * avg_commit_backoff_time) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxCommitBackoffTime  int     `json:\"max_commit_backoff_time\" agg:\"MAX(max_commit_backoff_time)\"`\n\tAggAvgResolveLockTime    int     `json:\"avg_resolve_lock_time\" agg:\"CAST(SUM(exec_count * avg_resolve_lock_time) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxResolveLockTime    int     `json:\"max_resolve_lock_time\" agg:\"MAX(max_resolve_lock_time)\"`\n\tAggAvgLocalLatchWaitTime int     `json:\"avg_local_latch_wait_time\" agg:\"CAST(SUM(exec_count * avg_local_latch_wait_time) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxLocalLatchWaitTime int     `json:\"max_local_latch_wait_time\" agg:\"MAX(max_local_latch_wait_time)\"`\n\tAggAvgWriteKeys          int     `json:\"avg_write_keys\" agg:\"CAST(SUM(exec_count * avg_write_keys) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxWriteKeys          int     `json:\"max_write_keys\" agg:\"MAX(max_write_keys)\"`\n\tAggAvgWriteSize          int     `json:\"avg_write_size\" agg:\"CAST(SUM(exec_count * avg_write_size) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxWriteSize          int     `json:\"max_write_size\" agg:\"MAX(max_write_size)\"`\n\tAggAvgPrewriteRegions    int     `json:\"avg_prewrite_regions\" agg:\"CAST(SUM(exec_count * avg_prewrite_regions) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxPrewriteRegions    int     `json:\"max_prewrite_regions\" agg:\"MAX(max_prewrite_regions)\"`\n\tAggAvgTxnRetry           int     `json:\"avg_txn_retry\" agg:\"CAST(SUM(exec_count * avg_txn_retry) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxTxnRetry           int     `json:\"max_txn_retry\" agg:\"MAX(max_txn_retry)\"`\n\tAggSumBackoffTimes       int     `json:\"sum_backoff_times\" agg:\"SUM(sum_backoff_times)\"`\n\tAggAvgMem                int     `json:\"avg_mem\" agg:\"CAST(SUM(exec_count * avg_mem) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxMem                int     `json:\"max_mem\" agg:\"MAX(max_mem)\"`\n\tAggAvgMemArbitration     float64 `json:\"avg_mem_arbitration\" agg:\"SUM(exec_count * avg_mem_arbitration) / SUM(exec_count)\"`\n\tAggMaxMemArbitration     float64 `json:\"max_mem_arbitration\" agg:\"MAX(max_mem_arbitration)\"`\n\tAggAvgDisk               int     `json:\"avg_disk\" agg:\"CAST(SUM(exec_count * avg_disk) / SUM(exec_count) AS SIGNED)\"`\n\tAggMaxDisk               int     `json:\"max_disk\" agg:\"MAX(max_disk)\"`\n\tAggAvgAffectedRows       int     `json:\"avg_affected_rows\" agg:\"CAST(SUM(exec_count * avg_affected_rows) / SUM(exec_count) AS SIGNED)\"`\n\tAggFirstSeen             int     `json:\"first_seen\" agg:\"UNIX_TIMESTAMP(MIN(first_seen))\"`\n\tAggLastSeen              int     `json:\"last_seen\" agg:\"UNIX_TIMESTAMP(MAX(last_seen))\"`\n\tAggSampleUser            string  `json:\"sample_user\" agg:\"ANY_VALUE(sample_user)\"`\n\tAggQuerySampleText       string  `json:\"query_sample_text\" agg:\"ANY_VALUE(query_sample_text)\"`\n\tAggPrevSampleText        string  `json:\"prev_sample_text\" agg:\"ANY_VALUE(prev_sample_text)\"`\n\tAggSchemaName            string  `json:\"schema_name\" agg:\"ANY_VALUE(schema_name)\"`\n\tAggTableNames            string  `json:\"table_names\" agg:\"ANY_VALUE(table_names)\"`\n\tAggIndexNames            string  `json:\"index_names\" agg:\"ANY_VALUE(index_names)\"`\n\tAggPlanCount             int     `json:\"plan_count\" agg:\"COUNT(DISTINCT plan_digest)\" related:\"plan_digest\"`\n\tAggPlan                  string  `json:\"plan\" agg:\"ANY_VALUE(plan)\"` // deprecated, replaced by BinaryPlanText\n\tAggBinaryPlan            string  `json:\"binary_plan\" agg:\"ANY_VALUE(binary_plan)\"`\n\tAggPlanDigest            string  `json:\"plan_digest\" agg:\"ANY_VALUE(plan_digest)\"`\n\tAggPlanHint              *string `json:\"plan_hint\" agg:\"ANY_VALUE(plan_hint)\"`\n\tAggPlanCacheHits         int     `json:\"plan_cache_hits\" agg:\"SUM(plan_cache_hits)\"`\n\n\t// RocksDB\n\tAggMaxRocksdbDeleteSkippedCount uint `json:\"max_rocksdb_delete_skipped_count\" agg:\"MAX(max_rocksdb_delete_skipped_count)\"`\n\tAggAvgRocksdbDeleteSkippedCount uint `json:\"avg_rocksdb_delete_skipped_count\" agg:\"CAST(SUM(exec_count * avg_rocksdb_delete_skipped_count) / SUM(exec_count) as SIGNED)\"`\n\tAggMaxRocksdbKeySkippedCount    uint `json:\"max_rocksdb_key_skipped_count\" agg:\"MAX(max_rocksdb_key_skipped_count)\"`\n\tAggAvgRocksdbKeySkippedCount    uint `json:\"avg_rocksdb_key_skipped_count\" agg:\"CAST(SUM(exec_count * avg_rocksdb_key_skipped_count) / SUM(exec_count) as SIGNED)\"`\n\tAggMaxRocksdbBlockCacheHitCount uint `json:\"max_rocksdb_block_cache_hit_count\" agg:\"MAX(max_rocksdb_block_cache_hit_count)\"`\n\tAggAvgRocksdbBlockCacheHitCount uint `json:\"avg_rocksdb_block_cache_hit_count\" agg:\"CAST(SUM(exec_count * avg_rocksdb_block_cache_hit_count) / SUM(exec_count) as SIGNED)\"`\n\tAggMaxRocksdbBlockReadCount     uint `json:\"max_rocksdb_block_read_count\" agg:\"MAX(max_rocksdb_block_read_count)\"`\n\tAggAvgRocksdbBlockReadCount     uint `json:\"avg_rocksdb_block_read_count\" agg:\"CAST(SUM(exec_count * avg_rocksdb_block_read_count) / SUM(exec_count) as SIGNED)\"`\n\tAggMaxRocksdbBlockReadByte      uint `json:\"max_rocksdb_block_read_byte\" agg:\"MAX(max_rocksdb_block_read_byte)\"`\n\tAggAvgRocksdbBlockReadByte      uint `json:\"avg_rocksdb_block_read_byte\" agg:\"CAST(SUM(exec_count * avg_rocksdb_block_read_byte) / SUM(exec_count) as SIGNED)\"`\n\t// Computed fields\n\tRelatedSchemas string `json:\"related_schemas\"`\n\tPlanCanBeBound bool   `json:\"plan_can_be_bound\"`\n\tBinaryPlanJSON string `json:\"binary_plan_json\"`\n\tBinaryPlanText string `json:\"binary_plan_text\"`\n\n\t// Resource Control\n\tAggResourceGroup string  `json:\"resource_group\" agg:\"ANY_VALUE(resource_group)\"`\n\tAggAvgRU         float64 `json:\"avg_ru\" agg:\"CAST(AVG(avg_request_unit_write + avg_request_unit_read) AS DECIMAL(64, 2))\" related:\"avg_request_unit_write,avg_request_unit_read\"`\n\tAggMaxRU         float64 `json:\"max_ru\" agg:\"MAX(max_request_unit_write + max_request_unit_read)\" related:\"max_request_unit_write,max_request_unit_read\"`\n\tAggSumRU         float64 `json:\"sum_ru\" agg:\"CAST(SUM(exec_count * (avg_request_unit_write + avg_request_unit_read)) AS DECIMAL(64, 2))\" related:\"avg_request_unit_write,avg_request_unit_read\"`\n\tAvgQueuedTime    float64 `json:\"avg_time_queued_by_rc\" agg:\"CAST(AVG(AVG_QUEUED_RC_TIME) AS DECIMAL(64, 2))\" related:\"AVG_QUEUED_RC_TIME\"`\n\tMaxQueuedTime    float64 `json:\"max_time_queued_by_rc\" agg:\"Max(MAX_QUEUED_RC_TIME)\" related:\"MAX_QUEUED_RC_TIME\"`\n\n\t// Network Fields\n\tSumUnpackedBytesSentTiKVTotal            uint `json:\"sum_unpacked_bytes_sent_tikv_total\" agg:\"SUM(sum_unpacked_bytes_sent_tikv_total)\"`\n\tSumUnpackedBytesReceivedTiKVTotal        uint `json:\"sum_unpacked_bytes_received_tikv_total\" agg:\"SUM(sum_unpacked_bytes_received_tikv_total)\"`\n\tSumUnpackedBytesSentTiKVCrossZone        uint `json:\"sum_unpacked_bytes_sent_tikv_cross_zone\" agg:\"SUM(sum_unpacked_bytes_sent_tikv_cross_zone)\"`\n\tSumUnpackedBytesReceivedTiKVCrossZone    uint `json:\"sum_unpacked_bytes_received_tikv_cross_zone\" agg:\"SUM(sum_unpacked_bytes_received_tikv_cross_zone)\"`\n\tSumUnpackedBytesSentTiFlashTotal         uint `json:\"sum_unpacked_bytes_sent_tiflash_total\" agg:\"SUM(sum_unpacked_bytes_sent_tiflash_total)\"`\n\tSumUnpackedBytesReceivedTiFlashTotal     uint `json:\"sum_unpacked_bytes_received_tiflash_total\" agg:\"SUM(sum_unpacked_bytes_received_tiflash_total)\"`\n\tSumUnpackedBytesSentTiFlashCrossZone     uint `json:\"sum_unpacked_bytes_sent_tiflash_cross_zone\" agg:\"SUM(sum_unpacked_bytes_sent_tiflash_cross_zone)\"`\n\tSumUnpackedBytesReceivedTiFlashCrossZone uint `json:\"sum_unpacked_bytes_received_tiflash_cross_zone\" agg:\"SUM(sum_unpacked_bytes_received_tiflash_cross_zone)\"`\n\n\t// IA remote read segment metrics\n\tSumIaReadSegmentCount          int64   `json:\"sum_ia_read_segment_count\" agg:\"SUM(sum_ia_read_segment_count)\"`\n\tSumIaRemoteReadSegmentSize     int64   `json:\"sum_ia_remote_read_segment_size\" agg:\"SUM(sum_ia_remote_read_segment_size)\"`\n\tSumIaRemoteReadSegmentWaitTime float64 `json:\"sum_ia_remote_read_segment_wait_time\" agg:\"SUM(sum_ia_remote_read_segment_wait_time)\"`\n\tAvgIaReadSegmentCount          float64 `json:\"avg_ia_read_segment_count\" agg:\"SUM(exec_count * avg_ia_read_segment_count) / SUM(exec_count)\"`\n\tAvgIaRemoteReadSegmentSize     float64 `json:\"avg_ia_remote_read_segment_size\" agg:\"SUM(exec_count * avg_ia_remote_read_segment_size) / SUM(exec_count)\"`\n\tAvgIaRemoteReadSegmentWaitTime float64 `json:\"avg_ia_remote_read_segment_wait_time\" agg:\"SUM(exec_count * avg_ia_remote_read_segment_wait_time) / SUM(exec_count)\"`\n\tMaxIaReadSegmentCount          int64   `json:\"max_ia_read_segment_count\" agg:\"MAX(max_ia_read_segment_count)\"`\n\tMaxIaRemoteReadSegmentSize     int64   `json:\"max_ia_remote_read_segment_size\" agg:\"MAX(max_ia_remote_read_segment_size)\"`\n\tMaxIaRemoteReadSegmentWaitTime float64 `json:\"max_ia_remote_read_segment_wait_time\" agg:\"MAX(max_ia_remote_read_segment_wait_time)\"`\n}\n\n// tableNames example: \"d1.a1,d2.a2,d1.a1,d3.a3\"\n// return \"d1, d2, d3\".\nfunc extractSchemasFromTableNames(tableNames string) string {\n\tschemas := make(map[string]bool)\n\ttables := strings.SplitSeq(tableNames, \",\")\n\tfor v := range tables {\n\t\tschema := strings.Trim(strings.Split(v, \".\")[0], \" \")\n\t\tif len(schema) > 0 {\n\t\t\tschemas[schema] = true\n\t\t}\n\t}\n\tkeys := make([]string, 0, len(schemas))\n\tfor k := range schemas {\n\t\tkeys = append(keys, k)\n\t}\n\treturn strings.Join(keys, \", \")\n}\n\n// checkSupportPlanBinding checks if whether the plan can be bound manually with sql `CREATE GLOBAL BINDING FROM HISTORY USING PLAN DIGEST '%s'`.\nfunc (m *Model) checkSupportPlanBinding() bool {\n\tif !lo.Contains([]string{\"SELECT\", \"DELETE\", \"UPDATE\", \"INSERT\", \"REPLACE\"}, strings.ToUpper(m.AggStmtType)) {\n\t\treturn false\n\t}\n\tif m.AggPlanHint != nil && *m.AggPlanHint == \"\" {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc (m *Model) AfterFind(_ *gorm.DB) error {\n\tif len(m.AggTableNames) > 0 {\n\t\tm.RelatedSchemas = extractSchemasFromTableNames(m.AggTableNames)\n\t}\n\tm.PlanCanBeBound = m.checkSupportPlanBinding()\n\treturn nil\n}\n\ntype Field struct {\n\tColumnName string\n\tJSONName   string\n\t// `related` tag is used to verify a non-existent column, which is aggregated from the columns represented by related.\n\tRelated     []string\n\tAggregation string\n}\n\nvar gormDefaultNamingStrategy = schema.NamingStrategy{}\n\nfunc getFieldsAndTags() (stmtFields []Field) {\n\tfields := reflectutil.GetFieldsAndTags(Model{}, []string{\"related\", \"agg\", \"json\"})\n\n\tfor _, f := range fields {\n\t\tsf := Field{\n\t\t\tColumnName:  gormDefaultNamingStrategy.ColumnName(\"\", f.Name),\n\t\t\tJSONName:    f.Tags[\"json\"],\n\t\t\tRelated:     []string{},\n\t\t\tAggregation: f.Tags[\"agg\"],\n\t\t}\n\n\t\tif f.Tags[\"related\"] != \"\" {\n\t\t\tsf.Related = strings.Split(f.Tags[\"related\"], \",\")\n\t\t}\n\n\t\tstmtFields = append(stmtFields, sf)\n\t}\n\n\treturn\n}\n\nfunc filterFieldsByColumns(fields []Field, columns []string) []Field {\n\tcolMap := map[string]struct{}{}\n\tfor _, c := range columns {\n\t\tcolMap[strings.ToLower(c)] = struct{}{}\n\t}\n\n\tfilteredFields := []Field{}\n\tfor _, f := range fields {\n\t\t// The json name of Statement is currently exactly the same as the table column name\n\t\t// TODO: use util.VirtualView instead of the convention in the comment\n\t\t_, ok := colMap[strings.ToLower(f.JSONName)]\n\t\tif ok || (len(f.Related) != 0 && utils.IsSubsetICaseInsensitive(columns, f.Related)) {\n\t\t\tfilteredFields = append(filteredFields, f)\n\t\t}\n\t}\n\treturn filteredFields\n}\n\n// Binding struct maps to the response of `SHOW BINDINGS` query.\ntype Binding struct {\n\tStatus     string `json:\"status\" example:\"enabled\" enums:\"enabled,using,disabled,deleted,invalid,rejected,pending verify\"`\n\tSource     string `json:\"source\" example:\"manual\" enums:\"manual,history,capture,evolve\"`\n\tSQLDigest  string `json:\"-\" gorm:\"column:Sql_digest\"`\n\tPlanDigest string `json:\"plan_digest\" gorm:\"column:Plan_digest\"`\n}\n"
  },
  {
    "path": "pkg/apiserver/statement/module.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage statement\n\nimport \"go.uber.org/fx\"\n\nvar Module = fx.Options(\n\tfx.Provide(newService),\n\tfx.Invoke(registerRouter),\n)\n"
  },
  {
    "path": "pkg/apiserver/statement/queries.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage statement\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pingcap/errors\"\n\t\"gorm.io/gorm\"\n)\n\nconst (\n\tstatementsTable = \"INFORMATION_SCHEMA.CLUSTER_STATEMENTS_SUMMARY_HISTORY\"\n)\n\nvar digestInjectChecker = regexp.MustCompile(`^[a-zA-Z0-9]+$`)\n\nfunc queryStmtTypes(db *gorm.DB) (result []string, err error) {\n\t// why should put DISTINCT inside the `Pluck()` method, see here:\n\t// https://github.com/jinzhu/gorm/issues/496\n\terr = db.\n\t\tTable(statementsTable).\n\t\tOrder(\"stmt_type ASC\").\n\t\tPluck(\"DISTINCT stmt_type\", &result).\n\t\tError\n\treturn\n}\n\n// sample params:\n// beginTime: 1586844000\n// endTime: 1586845800\n// schemas: [\"tpcc\", \"test\"]\n// stmtTypes: [\"select\", \"update\"]\n// fields: [\"digest_text\", \"sum_latency\"]\nfunc (s *Service) queryStatements(\n\tdb *gorm.DB,\n\tbeginTime, endTime int,\n\tschemas, resourceGroups, stmtTypes []string,\n\ttext string,\n\treqFields []string,\n) (result []Model, err error) {\n\ttableColumns, err := s.params.SysSchema.GetTableColumnNames(db, statementsTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tselectStmt, err := s.genSelectStmt(tableColumns, reqFields)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tquery := db.\n\t\tSelect(selectStmt).\n\t\tTable(statementsTable).\n\t\t// https://stackoverflow.com/questions/3269434/whats-the-most-efficient-way-to-test-if-two-ranges-overlap\n\t\tWhere(\"summary_begin_time <= FROM_UNIXTIME(?) AND summary_end_time >= FROM_UNIXTIME(?)\", endTime, beginTime).\n\t\tGroup(\"schema_name, digest\").\n\t\tOrder(\"agg_sum_latency DESC\")\n\n\tif len(schemas) > 0 {\n\t\tregex := make([]string, 0, len(schemas))\n\t\tfor _, schema := range schemas {\n\t\t\tregex = append(regex, fmt.Sprintf(\"\\\\b%s\\\\.\", regexp.QuoteMeta(schema)))\n\t\t}\n\t\tregexAll := strings.Join(regex, \"|\")\n\t\tquery = query.Where(\"table_names REGEXP ?\", regexAll)\n\t}\n\n\tif len(resourceGroups) > 0 {\n\t\tquery = query.Where(\"resource_group in (?)\", resourceGroups)\n\t}\n\n\tif len(stmtTypes) > 0 {\n\t\tquery = query.Where(\"stmt_type in (?)\", stmtTypes)\n\t}\n\n\tif len(text) > 0 {\n\t\tlowerText := strings.ToLower(text)\n\t\tarr := strings.FieldsSeq(lowerText)\n\t\tfor v := range arr {\n\t\t\tquery = query.Where(\n\t\t\t\t`LOWER(digest_text) REGEXP ?\n\t\t\t\t OR LOWER(digest) REGEXP ?\n\t\t\t\t OR LOWER(schema_name) REGEXP ?\n\t\t\t\t OR LOWER(table_names) REGEXP ?`,\n\t\t\t\tv, v, v, v,\n\t\t\t)\n\t\t}\n\t}\n\tctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)\n\tdefer cancel()\n\terr = query.WithContext(ctx).Find(&result).Error\n\tif err == nil {\n\t\t// truncate each row's digest_text, keep the start 1000 characters to avoid too long text\n\t\t// if user want to see the full digest_text, they can access the detail api\n\t\tfor i := range result {\n\t\t\tif len(result[i].AggDigestText) > 1000 {\n\t\t\t\tresult[i].AggDigestText = result[i].AggDigestText[:1000] + \"...\"\n\t\t\t}\n\t\t}\n\t}\n\treturn\n}\n\nfunc (s *Service) queryPlans(\n\tdb *gorm.DB,\n\tbeginTime, endTime int,\n\tschemaName, digest string,\n) (result []Model, err error) {\n\ttableColumns, err := s.params.SysSchema.GetTableColumnNames(db, statementsTable)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tselectStmt, err := s.genSelectStmt(tableColumns, []string{\n\t\t\"plan_digest\",\n\t\t\"schema_name\",\n\t\t\"digest_text\",\n\t\t\"digest\",\n\t\t\"sum_latency\",\n\t\t\"max_latency\",\n\t\t\"min_latency\",\n\t\t\"avg_latency\",\n\t\t\"exec_count\",\n\t\t\"avg_mem\",\n\t\t\"max_mem\",\n\t\t\"stmt_type\", // required by quick plan binding\n\t\t\"plan_hint\", // required by quick plan binding, only available in TiDB 6.6.0+, could be filter out by `tableColumns`\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tquery := db.\n\t\tSelect(selectStmt).\n\t\tTable(statementsTable).\n\t\tWhere(\"summary_begin_time <= FROM_UNIXTIME(?) AND summary_end_time >= FROM_UNIXTIME(?)\", endTime, beginTime).\n\t\tGroup(\"plan_digest\")\n\n\tif digest == \"\" {\n\t\t// the evicted record's digest will be NULL\n\t\tquery.Where(\"digest IS NULL\")\n\t} else {\n\t\tif schemaName != \"\" {\n\t\t\tquery.Where(\"schema_name = ?\", schemaName)\n\t\t}\n\t\tquery.Where(\"digest = ?\", digest)\n\t}\n\n\terr = query.Find(&result).Error\n\n\treturn\n}\n\nfunc (s *Service) queryPlanDetail(\n\tdb *gorm.DB,\n\tbeginTime, endTime int,\n\tschemaName, digest string,\n\tplans []string,\n) (result Model, err error) {\n\ttableColumns, err := s.params.SysSchema.GetTableColumnNames(db, statementsTable)\n\tif err != nil {\n\t\treturn\n\t}\n\n\tselectStmt, err := s.genSelectStmt(tableColumns, []string{\"*\"})\n\tif err != nil {\n\t\treturn\n\t}\n\n\tquery := db.\n\t\tSelect(selectStmt).\n\t\tTable(statementsTable).\n\t\tWhere(\"summary_begin_time <= FROM_UNIXTIME(?) AND summary_end_time >= FROM_UNIXTIME(?)\", endTime, beginTime)\n\n\tif digest == \"\" {\n\t\t// the evicted record's digest will be NULL\n\t\tquery.Where(\"digest IS NULL\")\n\t} else {\n\t\tif schemaName != \"\" {\n\t\t\tquery.Where(\"schema_name = ?\", schemaName)\n\t\t}\n\t\tif len(plans) > 0 {\n\t\t\tquery = query.Where(\"plan_digest in (?)\", plans)\n\t\t}\n\t\tquery.Where(\"digest = ?\", digest)\n\t}\n\n\terr = query.Scan(&result).Error\n\treturn\n}\n\nfunc (s *Service) queryPlanBinding(db *gorm.DB, sqlDigest string, beginTime, endTime int) (bindings []Binding, err error) {\n\t// The binding sql digest is newly generated and different from the original sql digest,\n\t// we have to do one more query here.\n\n\t// First, get plan digests by sql digest.\n\tq1 := db.\n\t\tTable(statementsTable).\n\t\tSelect(\"plan_digest\").\n\t\tWhere(\"digest = ? AND summary_begin_time <= FROM_UNIXTIME(?) AND summary_end_time > FROM_UNIXTIME(?)\", sqlDigest, endTime, beginTime)\n\tq1Res := make([]map[string]any, 0)\n\tif err := q1.Find(&q1Res).Error; err != nil {\n\t\treturn nil, err\n\t}\n\tplanDigests := make([]string, 0, len(q1Res))\n\tfor _, row := range q1Res {\n\t\ts, ok := row[\"plan_digest\"].(string)\n\t\tif !ok {\n\t\t\treturn nil, errors.New(\"invalid plan digest value\")\n\t\t}\n\t\tplanDigests = append(planDigests, s)\n\t}\n\n\t// Second, get bindings.\n\tquery := db.Raw(\"SHOW GLOBAL BINDINGS WHERE plan_digest IN (?) AND source = ? AND status IN (?)\", planDigests, \"history\", []string{\"enabled\", \"using\"})\n\treturn bindings, query.Scan(&bindings).Error\n}\n\nfunc (s *Service) createPlanBinding(db *gorm.DB, planDigest string) (err error) {\n\t// Caution! SQL injection vulnerability!\n\t// We have to interpolate sql string here, since plan binding stmt does not support session level prepare.\n\t// go-sql-driver can enable interpolation globally. Refer to https://github.com/go-sql-driver/mysql#interpolateparams.\n\tif !digestInjectChecker.MatchString(planDigest) {\n\t\treturn errors.New(\"invalid planDigest\")\n\t}\n\n\tquery := db.Exec(fmt.Sprintf(\"CREATE GLOBAL BINDING FROM HISTORY USING PLAN DIGEST '%s'\", planDigest))\n\treturn query.Error\n}\n\nfunc (s *Service) dropPlanBinding(db *gorm.DB, sqlDigest string) (err error) {\n\t// The binding sql digest is newly generated and different from the original sql digest,\n\t// we have to do one more query here.\n\tbindings, err := s.queryPlanBinding(db, sqlDigest, 0, int(time.Now().Unix()))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif len(bindings) <= 0 {\n\t\treturn errors.New(\"no binding found\")\n\t}\n\n\tfor _, binding := range bindings {\n\t\t// No SQL injection vulnerability here.\n\t\tquery := db.Exec(fmt.Sprintf(\"DROP GLOBAL BINDING FOR SQL DIGEST '%s'\", binding.SQLDigest))\n\t\tif query.Error != nil {\n\t\t\treturn query.Error\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/apiserver/statement/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage statement\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/pingcap/errors\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\tcommonUtils \"github.com/pingcap/tidb-dashboard/pkg/utils\"\n\t\"github.com/pingcap/tidb-dashboard/util/featureflag\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nvar (\n\tErrNS     = errorx.NewNamespace(\"error.api.statement\")\n\tErrNoData = ErrNS.NewType(\"export_no_data\")\n)\n\ntype ServiceParams struct {\n\tfx.In\n\tTiDBClient *tidb.Client\n\tSysSchema  *commonUtils.SysSchema\n}\n\ntype Service struct {\n\tparams                 ServiceParams\n\tplanBindingFeatureFlag *featureflag.FeatureFlag\n}\n\nfunc newService(p ServiceParams, ff *featureflag.Registry) *Service {\n\treturn &Service{params: p, planBindingFeatureFlag: ff.Register(\"plan_binding\", \">= 6.5.0\")}\n}\n\nfunc registerRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/statements\")\n\t{\n\t\tendpoint.GET(\"/download\", s.downloadHandler)\n\n\t\tendpoint.Use(auth.MWAuthRequired())\n\t\tendpoint.Use(utils.MWConnectTiDB(s.params.TiDBClient))\n\t\t{\n\t\t\tendpoint.POST(\"/download/token\", s.downloadTokenHandler)\n\n\t\t\tendpoint.GET(\"/config\", s.configHandler)\n\t\t\tendpoint.POST(\"/config\", auth.MWRequireWritePriv(), s.modifyConfigHandler)\n\t\t\tendpoint.GET(\"/stmt_types\", s.stmtTypesHandler)\n\t\t\tendpoint.GET(\"/list\", s.listHandler)\n\t\t\tendpoint.GET(\"/plans\", s.plansHandler)\n\t\t\tendpoint.GET(\"/plan/detail\", s.planDetailHandler)\n\n\t\t\tendpoint.GET(\"/available_fields\", s.getAvailableFields)\n\n\t\t\tbinding := endpoint.Group(\"/plan/binding\")\n\t\t\tbinding.Use(s.planBindingFeatureFlag.VersionGuard())\n\t\t\t{\n\t\t\t\tbinding.GET(\"\", s.getPlanBindingHandler)\n\t\t\t\tbinding.POST(\"\", s.createPlanBindingHandler)\n\t\t\t\tbinding.DELETE(\"\", s.dropPlanBindingHandler)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype EditableConfig struct {\n\tEnable          bool `json:\"enable\" gorm:\"column:tidb_enable_stmt_summary\"`\n\tRefreshInterval int  `json:\"refresh_interval\" gorm:\"column:tidb_stmt_summary_refresh_interval\"`\n\tHistorySize     int  `json:\"history_size\" gorm:\"column:tidb_stmt_summary_history_size\"`\n\tMaxSize         int  `json:\"max_size\" gorm:\"column:tidb_stmt_summary_max_stmt_count\"`\n\tInternalQuery   bool `json:\"internal_query\" gorm:\"column:tidb_stmt_summary_internal_query\"`\n}\n\n// @Summary Get statement configurations\n// @Success 200 {object} statement.EditableConfig\n// @Router /statements/config [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) configHandler(c *gin.Context) {\n\tdb := utils.GetTiDBConnection(c)\n\tcfg := &EditableConfig{}\n\terr := db.Raw(buildGlobalConfigProjectionSelectSQL(cfg)).Find(cfg).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, cfg)\n}\n\n// @Summary Update statement configurations\n// @Param request body statement.EditableConfig true \"Request body\"\n// @Success 204 {object} string\n// @Router /statements/config [post]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) modifyConfigHandler(c *gin.Context) {\n\tvar config EditableConfig\n\tif err := c.ShouldBindJSON(&config); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tdb := utils.GetTiDBConnection(c)\n\n\tvar sqlWithNamedArgument string\n\tif !config.Enable {\n\t\tsqlWithNamedArgument = buildGlobalConfigNamedArgsUpdateSQL(&config, \"Enable\")\n\t} else {\n\t\tsqlWithNamedArgument = buildGlobalConfigNamedArgsUpdateSQL(&config)\n\t}\n\terr := db.Exec(sqlWithNamedArgument, &config).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tc.Status(http.StatusNoContent)\n}\n\n// @Summary Get all statement types\n// @Success 200 {array} string\n// @Router /statements/stmt_types [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) stmtTypesHandler(c *gin.Context) {\n\tdb := utils.GetTiDBConnection(c)\n\tstmtTypes, err := queryStmtTypes(db)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, stmtTypes)\n}\n\ntype GetStatementsRequest struct {\n\tSchemas        []string `json:\"schemas\" form:\"schemas\"`\n\tResourceGroups []string `json:\"resource_groups\" form:\"resource_groups\"`\n\tStmtTypes      []string `json:\"stmt_types\" form:\"stmt_types\"`\n\tBeginTime      int      `json:\"begin_time\" form:\"begin_time\"`\n\tEndTime        int      `json:\"end_time\" form:\"end_time\"`\n\tText           string   `json:\"text\" form:\"text\"`\n\tFields         string   `json:\"fields\" form:\"fields\"`\n}\n\n// @Summary Get a list of statements\n// @Param q query GetStatementsRequest true \"Query\"\n// @Success 200 {array} Model\n// @Router /statements/list [get]\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) listHandler(c *gin.Context) {\n\tvar req GetStatementsRequest\n\tif err := c.ShouldBindQuery(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tdb := utils.GetTiDBConnection(c)\n\tfields := []string{}\n\tif strings.TrimSpace(req.Fields) != \"\" {\n\t\tfields = strings.Split(req.Fields, \",\")\n\t}\n\toverviews, err := s.queryStatements(\n\t\tdb,\n\t\treq.BeginTime, req.EndTime,\n\t\treq.Schemas,\n\t\treq.ResourceGroups,\n\t\treq.StmtTypes,\n\t\treq.Text,\n\t\tfields)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, overviews)\n}\n\ntype GetPlansRequest struct {\n\tSchemaName string `json:\"schema_name\" form:\"schema_name\"`\n\tDigest     string `json:\"digest\" form:\"digest\"`\n\tBeginTime  int    `json:\"begin_time\" form:\"begin_time\"`\n\tEndTime    int    `json:\"end_time\" form:\"end_time\"`\n}\n\n// @Summary Get execution plans of a statement\n// @Param q query GetPlansRequest true \"Query\"\n// @Success 200 {array} Model\n// @Router /statements/plans [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) plansHandler(c *gin.Context) {\n\tvar req GetPlansRequest\n\tif err := c.ShouldBindQuery(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tdb := utils.GetTiDBConnection(c)\n\tplans, err := s.queryPlans(db, req.BeginTime, req.EndTime, req.SchemaName, req.Digest)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, plans)\n}\n\ntype GetPlanDetailRequest struct {\n\tGetPlansRequest\n\tPlans []string `json:\"plans\" form:\"plans\"`\n}\n\n// @Summary Get details of a statement in an execution plan\n// @Param q query GetPlanDetailRequest true \"Query\"\n// @Success 200 {object} Model\n// @Router /statements/plan/detail [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) planDetailHandler(c *gin.Context) {\n\tvar req GetPlanDetailRequest\n\tif err := c.ShouldBindQuery(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tdb := utils.GetTiDBConnection(c)\n\tresult, err := s.queryPlanDetail(db, req.BeginTime, req.EndTime, req.SchemaName, req.Digest, req.Plans)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tif result.AggBinaryPlan != \"\" {\n\t\t// may failed but it's ok\n\t\tresult.BinaryPlanText, err = utils.GenerateBinaryPlanText(db, result.AggBinaryPlan)\n\t\t// may failed but it's ok\n\t\tresult.BinaryPlanJSON, _ = utils.GenerateBinaryPlanJSON(result.AggBinaryPlan)\n\n\t\tif err == nil {\n\t\t\t// reduce response size\n\t\t\tresult.AggBinaryPlan = \"\"\n\t\t\tresult.AggPlan = \"\"\n\t\t}\n\t}\n\n\tc.JSON(http.StatusOK, result)\n}\n\n// @Summary\tGet the bound plan digest (if exists) of a statement\n// @Param\tsql_digest\tquery\tstring\ttrue\t\"query template id\"\n// @Param\tbegin_time\tquery\tint\ttrue\t\"begin time\"\n// @Param\tend_time\tquery\tint\ttrue\t\"end time\"\n// @Success\t200\t{object}\tBinding\n// @Router\t/statements/plan/binding\t[get]\n// @Security\tJwtAuth\n// @Failure\t401\t{object}\trest.ErrorResponse\nfunc (s *Service) getPlanBindingHandler(c *gin.Context) {\n\tdigest := c.Query(\"sql_digest\")\n\tif digest == \"\" {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"sql_digest cannot be empty\"))\n\t\treturn\n\t}\n\tbTimeS := c.Query(\"begin_time\")\n\tbTime, err := strconv.Atoi(bTimeS)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"begin_time is not a valid timestamp second int\"))\n\t\treturn\n\t}\n\teTimeS := c.Query(\"end_time\")\n\teTime, err := strconv.Atoi(eTimeS)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"end_time is not a valid timestamp second int\"))\n\t\treturn\n\t}\n\n\tdb := utils.GetTiDBConnection(c)\n\tresults, err := s.queryPlanBinding(db, digest, bTime, eTime)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\t// Creating binding with the same plan digest will override the previous one.\n\t// Therefore, we only need to return the first result.\n\tvar result *Binding\n\tif len(results) >= 1 {\n\t\tresult = &results[0]\n\t}\n\n\tc.JSON(http.StatusOK, result)\n}\n\n// @Summary\tCreate a binding for a statement and a plan\n// @Param\tplan_digest\tquery\tstring\ttrue\t\"plan digest id\"\n// @Success\t200\t{string}\tstring\t\"success\"\n// @Router\t/statements/plan/binding\t[post]\n// @Security\tJwtAuth\n// @Failure\t401\t{object}\trest.ErrorResponse\nfunc (s *Service) createPlanBindingHandler(c *gin.Context) {\n\tdigest := c.Query(\"plan_digest\")\n\tif digest == \"\" {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"plan_digest cannot be empty\"))\n\t\treturn\n\t}\n\n\tdb := utils.GetTiDBConnection(c)\n\terr := s.createPlanBinding(db, digest)\n\tif err != nil {\n\t\trest.Error(c, errors.Annotate(err, \"create plan binding failed due to internal failure, please refer to https://docs.pingcap.com/tidb/stable/sql-plan-management\"))\n\t\treturn\n\t}\n\n\tc.String(http.StatusOK, \"success\")\n}\n\n// @Summary\tDrop all manually created bindings for a statement\n// @Param\tsql_digest\tquery\tstring\ttrue\t\"query template ID (a.k.a. sql digest)\"\n// @Success\t200\t{string}\tstring\t\"success\"\n// @Router\t/statements/plan/binding\t[delete]\n// @Security\tJwtAuth\n// @Failure\t401\t{object}\trest.ErrorResponse\nfunc (s *Service) dropPlanBindingHandler(c *gin.Context) {\n\tdigest := c.Query(\"sql_digest\")\n\tif digest == \"\" {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"sql_digest cannot be empty\"))\n\t\treturn\n\t}\n\n\tdb := utils.GetTiDBConnection(c)\n\terr := s.dropPlanBinding(db, digest)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tc.String(http.StatusOK, \"success\")\n}\n\n// @Router /statements/download/token [post]\n// @Summary Generate a download token for exported statements\n// @Produce plain\n// @Param request body GetStatementsRequest true \"Request body\"\n// @Success 200 {string} string \"xxx\"\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) downloadTokenHandler(c *gin.Context) {\n\tvar req GetStatementsRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tdb := utils.GetTiDBConnection(c)\n\tfields := []string{}\n\tif strings.TrimSpace(req.Fields) != \"\" {\n\t\tfields = strings.Split(req.Fields, \",\")\n\t}\n\toverviews, err := s.queryStatements(\n\t\tdb,\n\t\treq.BeginTime, req.EndTime,\n\t\treq.Schemas,\n\t\treq.ResourceGroups,\n\t\treq.StmtTypes,\n\t\treq.Text,\n\t\tfields)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tif len(overviews) == 0 {\n\t\trest.Error(c, ErrNoData.NewWithNoMessage())\n\t\treturn\n\t}\n\n\t// interface{} tricky\n\trawData := make([]interface{}, len(overviews))\n\tfor i, v := range overviews {\n\t\trawData[i] = v\n\t}\n\n\t// convert data\n\tcsvData := utils.GenerateCSVFromRaw(rawData, fields, []string{\"first_seen\", \"last_seen\"})\n\n\t// generate temp file that persist encrypted data\n\ttimeLayout := \"01021504\"\n\tbeginTime := time.Unix(int64(req.BeginTime), 0).Format(timeLayout)\n\tendTime := time.Unix(int64(req.EndTime), 0).Format(timeLayout)\n\ttoken, err := utils.ExportCSV(csvData,\n\t\tfmt.Sprintf(\"statements_%s_%s_*.csv\", beginTime, endTime),\n\t\t\"statements/download\")\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.String(http.StatusOK, token)\n}\n\n// @Router /statements/download [get]\n// @Summary Download statements\n// @Produce text/csv\n// @Param token query string true \"download token\"\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) downloadHandler(c *gin.Context) {\n\ttoken := c.Query(\"token\")\n\tutils.DownloadByToken(token, \"statements/download\", c)\n}\n\n// @Summary Get available field names\n// @Description Get available field names by statements table columns\n// @Success 200 {array} string\n// @Failure 401 {object} rest.ErrorResponse\n// @Security JwtAuth\n// @Router /statements/available_fields [get]\nfunc (s *Service) getAvailableFields(c *gin.Context) {\n\tdb := utils.GetTiDBConnection(c)\n\tcs, err := s.params.SysSchema.GetTableColumnNames(db, statementsTable)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tfields := filterFieldsByColumns(getFieldsAndTags(), cs)\n\tjsonNames := make([]string, 0, len(fields))\n\tfor _, f := range fields {\n\t\tjsonNames = append(jsonNames, f.JSONName)\n\t}\n\n\tc.JSON(http.StatusOK, jsonNames)\n}\n"
  },
  {
    "path": "pkg/apiserver/statement/statement_gen.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage statement\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/samber/lo\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nvar ErrUnknownColumn = ErrNS.NewType(\"unknown_column\")\n\nfunc (s *Service) genSelectStmt(tableColumns []string, reqJSONColumns []string) (string, error) {\n\tfields := getFieldsAndTags()\n\n\t// use required fields filter when not all fields are requested\n\tif reqJSONColumns[0] != \"*\" {\n\t\trequiredFields := lo.Uniq(append(reqJSONColumns,\n\t\t\t\"schema_name\", \"digest\", // required by group by\n\t\t\t\"sum_latency\", // required by order\n\t\t\t\"summary_begin_time\", \"summary_end_time\",\n\t\t))\n\t\tfields = lo.Filter(fields, func(f Field, _ int) bool {\n\t\t\treturn lo.Contains(requiredFields, f.JSONName)\n\t\t})\n\t}\n\n\t// Filter out columns that do not exist in current version TiDB schema.\n\t// Current version TiDB schema columns are passed in by `tableColumns`.\n\tfields = lo.Filter(fields, func(f Field, _ int) bool {\n\t\tvar representedColumns []string\n\t\tif len(f.Related) != 0 {\n\t\t\trepresentedColumns = f.Related\n\t\t} else {\n\t\t\trepresentedColumns = []string{f.JSONName}\n\t\t}\n\n\t\t// Dependent columns of the requested field must exist in the db schema. Otherwise, the requested field will be ignored.\n\t\treturn utils.IsSubsetICaseInsensitive(tableColumns, representedColumns)\n\t})\n\n\tif len(fields) == 0 {\n\t\treturn \"\", ErrUnknownColumn.New(\"all columns are not included in the current version %s schema, columns: %q\", distro.R().TiDB, reqJSONColumns)\n\t}\n\n\tstmt := lo.Map(fields, func(f Field, _ int) string {\n\t\tif f.Aggregation == \"\" {\n\t\t\treturn f.JSONName\n\t\t}\n\t\treturn fmt.Sprintf(\"%s AS %s\", f.Aggregation, f.ColumnName)\n\t})\n\treturn strings.Join(stmt, \", \"), nil\n}\n"
  },
  {
    "path": "pkg/apiserver/topsql/module.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topsql\n\nimport \"go.uber.org/fx\"\n\nvar Module = fx.Options(\n\tfx.Provide(newService),\n\tfx.Invoke(registerRouter),\n)\n"
  },
  {
    "path": "pkg/apiserver/topsql/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topsql\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"net\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joomcode/errorx\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tikv\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/topology\"\n\t\"github.com/pingcap/tidb-dashboard/util/featureflag\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nvar ErrNS = errorx.NewNamespace(\"error.api.topsql\")\n\ntype ServiceParams struct {\n\tfx.In\n\tTiDBClient *tidb.Client\n\tNgmProxy   *utils.NgmProxy\n\tPDClient   *pd.Client\n\tTiKVClient *tikv.Client\n}\n\ntype Service struct {\n\tFeatureTopSQL *featureflag.FeatureFlag\n\n\tparams ServiceParams\n}\n\nfunc newService(p ServiceParams, ff *featureflag.Registry) *Service {\n\treturn &Service{params: p, FeatureTopSQL: ff.Register(\"topsql\", \">= 5.4.0\")}\n}\n\nfunc registerRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/topsql\")\n\tendpoint.Use(\n\t\tauth.MWAuthRequired(),\n\t\ts.FeatureTopSQL.VersionGuard(),\n\t\tutils.MWConnectTiDB(s.params.TiDBClient),\n\t)\n\t{\n\t\tendpoint.GET(\"/config\", s.GetConfig)\n\t\tendpoint.POST(\"/config\", auth.MWRequireWritePriv(), s.UpdateConfig)\n\t\tendpoint.GET(\"/tikv_network_io_collection\", s.GetTiKVNetworkIOCollection)\n\t\tendpoint.POST(\n\t\t\t\"/tikv_network_io_collection\",\n\t\t\tauth.MWRequireWritePriv(),\n\t\t\ts.UpdateTiKVNetworkIOCollection,\n\t\t)\n\t\tendpoint.GET(\"/instances\", s.params.NgmProxy.Route(\"/topsql/v1/instances\"))\n\t\tendpoint.GET(\"/summary\", s.params.NgmProxy.Route(\"/topsql/v1/summary\"))\n\t}\n}\n\ntype GetInstancesRequest struct {\n\tStart      string `json:\"start\"`\n\tEnd        string `json:\"end\"`\n\tDataSource string `json:\"data_source\"`\n}\n\ntype InstanceResponse struct {\n\tData []InstanceItem `json:\"data\"`\n}\n\ntype InstanceItem struct {\n\tInstance     string `json:\"instance\"`\n\tInstanceType string `json:\"instance_type\"`\n}\n\n// @Summary Get available instances\n// @Router /topsql/instances [get]\n// @Security JwtAuth\n// @Param q query GetInstancesRequest true \"Query\"\n// @Success 200 {object} InstanceResponse \"ok\"\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) GetInstance(_ *gin.Context) {\n\t// dummy, for generate open api\n}\n\ntype GetSummaryRequest struct {\n\tInstance     string `json:\"instance\"`\n\tInstanceType string `json:\"instance_type\"`\n\tStart        string `json:\"start\"`\n\tEnd          string `json:\"end\"`\n\tTop          string `json:\"top\"`\n\tGroupBy      string `json:\"group_by\"`\n\tOrderBy      string `json:\"order_by\"`\n\tWindow       string `json:\"window\"`\n\tDataSource   string `json:\"data_source\"`\n}\n\ntype SummaryResponse struct {\n\tData   []SummaryItem   `json:\"data\"`\n\tDataBy []SummaryByItem `json:\"data_by\"`\n}\n\ntype SummaryItem struct {\n\tSQLDigest         string            `json:\"sql_digest\"`\n\tSQLText           string            `json:\"sql_text\"`\n\tIsOther           bool              `json:\"is_other\"`\n\tCPUTimeMs         uint64            `json:\"cpu_time_ms\"`\n\tExecCountPerSec   float64           `json:\"exec_count_per_sec\"`\n\tDurationPerExecMs float64           `json:\"duration_per_exec_ms\"`\n\tScanRecordsPerSec float64           `json:\"scan_records_per_sec\"`\n\tScanIndexesPerSec float64           `json:\"scan_indexes_per_sec\"`\n\tNetworkBytes      uint64            `json:\"network_bytes\"`\n\tLogicalIoBytes    uint64            `json:\"logical_io_bytes\"`\n\tPlans             []SummaryPlanItem `json:\"plans\"`\n}\n\ntype SummaryByItem struct {\n\tText              string   `json:\"text\"`\n\tTimestampSec      []uint64 `json:\"timestamp_sec\"`\n\tCPUTimeMs         []uint64 `json:\"cpu_time_ms,omitempty\"`\n\tCPUTimeMsSum      uint64   `json:\"cpu_time_ms_sum\"`\n\tNetworkBytes      []uint64 `json:\"network_bytes,omitempty\"`\n\tNetworkBytesSum   uint64   `json:\"network_bytes_sum\"`\n\tLogicalIoBytes    []uint64 `json:\"logical_io_bytes,omitempty\"`\n\tLogicalIoBytesSum uint64   `json:\"logical_io_bytes_sum\"`\n}\n\ntype SummaryPlanItem struct {\n\tPlanDigest        string   `json:\"plan_digest\"`\n\tPlanText          string   `json:\"plan_text\"`\n\tTimestampSec      []uint64 `json:\"timestamp_sec\"`\n\tCPUTimeMs         []uint64 `json:\"cpu_time_ms,omitempty\"`\n\tExecCountPerSec   float64  `json:\"exec_count_per_sec\"`\n\tDurationPerExecMs float64  `json:\"duration_per_exec_ms\"`\n\tScanRecordsPerSec float64  `json:\"scan_records_per_sec\"`\n\tScanIndexesPerSec float64  `json:\"scan_indexes_per_sec\"`\n\tNetworkBytes      []uint64 `json:\"network_bytes\"`\n\tLogicalIoBytes    []uint64 `json:\"logical_io_bytes\"`\n}\n\n// @Summary Get summaries\n// @Router /topsql/summary [get]\n// @Security JwtAuth\n// @Param q query GetSummaryRequest true \"Query\"\n// @Success 200 {object} SummaryResponse \"ok\"\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) GetSummary(_ *gin.Context) {\n\t// dummy, for generate open api\n}\n\ntype EditableConfig struct {\n\tEnable bool `json:\"enable\" gorm:\"column:tidb_enable_top_sql\"`\n}\n\n// @Summary Get Top SQL config\n// @Router /topsql/config [get]\n// @Security JwtAuth\n// @Success 200 {object} EditableConfig \"ok\"\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) GetConfig(c *gin.Context) {\n\tdb := utils.GetTiDBConnection(c)\n\tcfg := &EditableConfig{}\n\terr := db.Raw(\"SELECT @@GLOBAL.tidb_enable_top_sql as tidb_enable_top_sql\").Find(cfg).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, cfg)\n}\n\n// @Summary Update Top SQL config\n// @Router /topsql/config [post]\n// @Param request body EditableConfig true \"Request body\"\n// @Security JwtAuth\n// @Success 204 {object} string\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) UpdateConfig(c *gin.Context) {\n\tvar cfg EditableConfig\n\tif err := c.ShouldBindJSON(&cfg); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tdb := utils.GetTiDBConnection(c)\n\terr := db.Exec(\"SET @@GLOBAL.tidb_enable_top_sql = @Enable\", &cfg).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tc.Status(http.StatusNoContent)\n}\n\nconst (\n\ttikvNetworkIoCollectionKey = \"resource-metering.enable-network-io-collection\"\n\n\ttikvNetworkIoCollectionNodeTimeout    = 3 * time.Second\n\ttikvNetworkIoCollectionMaxConcurrency = 10\n)\n\ntype TikvNetworkIoCollectionConfig struct {\n\tEnable       bool `json:\"enable\"`\n\tIsMultiValue bool `json:\"is_multi_value,omitempty\"`\n}\n\ntype UpdateTikvNetworkIoCollectionResponse struct {\n\tWarnings []rest.ErrorResponse `json:\"warnings\"`\n}\n\n// @ID topsqlGetTiKVNetworkIOCollection\n// @Summary Get TiKV network IO collection config\n// @Router /topsql/tikv_network_io_collection [get]\n// @Security JwtAuth\n// @Success 200 {object} TikvNetworkIoCollectionConfig \"ok\"\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) GetTiKVNetworkIOCollection(c *gin.Context) {\n\ttikvInfo, _, err := topology.FetchStoreTopology(s.params.PDClient)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tif len(tikvInfo) == 0 {\n\t\tc.JSON(http.StatusOK, &TikvNetworkIoCollectionConfig{Enable: false})\n\t\treturn\n\t}\n\n\ttype getResult struct {\n\t\tvalue bool\n\t\tfound bool\n\t\terr   error\n\t}\n\n\tconcurrency := getTiKVNetworkIoCollectionConcurrency(len(tikvInfo))\n\ttaskChan := make(chan topology.StoreInfo, len(tikvInfo))\n\tresultChan := make(chan getResult, len(tikvInfo))\n\tvar wg sync.WaitGroup\n\n\tfor range concurrency {\n\t\twg.Go(func() {\n\t\t\tfor kvStore := range taskChan {\n\t\t\t\tdata, err := s.params.TiKVClient.\n\t\t\t\t\tWithTimeout(tikvNetworkIoCollectionNodeTimeout).\n\t\t\t\t\tSendGetRequest(kvStore.IP, int(kvStore.StatusPort), \"/config\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tresultChan <- getResult{err: err}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tv, found, err := parseNestedBoolByDotPath(data, tikvNetworkIoCollectionKey)\n\t\t\t\tif err != nil {\n\t\t\t\t\tresultChan <- getResult{err: err}\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tresultChan <- getResult{value: v, found: found}\n\t\t\t}\n\t\t})\n\t}\n\n\tfor _, kvStore := range tikvInfo {\n\t\ttaskChan <- kvStore\n\t}\n\tclose(taskChan)\n\twg.Wait()\n\tclose(resultChan)\n\n\tsuccesses := 0\n\tfailures := 0\n\ttrueCount := 0\n\tfalseCount := 0\n\thasMissing := false\n\tfor result := range resultChan {\n\t\tif result.err != nil {\n\t\t\tfailures++\n\t\t\tcontinue\n\t\t}\n\n\t\tsuccesses++\n\t\t// Keep existing semantics: missing key is treated as \"false\".\n\t\tif !result.found {\n\t\t\thasMissing = true\n\t\t\tfalseCount++\n\t\t\tcontinue\n\t\t}\n\t\tif result.value {\n\t\t\ttrueCount++\n\t\t} else {\n\t\t\tfalseCount++\n\t\t}\n\t}\n\n\tif successes == 0 {\n\t\trest.Error(c, errorx.IllegalState.New(\"Failed to fetch config from any TiKV node\"))\n\t\treturn\n\t}\n\n\t// Keep existing semantics:\n\t// 1. Any failed request means \"not enabled on all nodes\".\n\t// 2. Missing config key is treated as false and marks multi-value.\n\tallTrue := failures == 0 && !hasMissing && falseCount == 0\n\tisMulti := failures > 0 || hasMissing || (trueCount > 0 && falseCount > 0)\n\n\tc.JSON(http.StatusOK, &TikvNetworkIoCollectionConfig{\n\t\tEnable:       allTrue,\n\t\tIsMultiValue: isMulti,\n\t})\n}\n\n// @ID topsqlUpdateTiKVNetworkIOCollection\n// @Summary Update TiKV network IO collection config\n// @Param request body TikvNetworkIoCollectionConfig true \"Request body\"\n// @Router /topsql/tikv_network_io_collection [post]\n// @Security JwtAuth\n// @Success 200 {object} UpdateTikvNetworkIoCollectionResponse \"ok\"\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) UpdateTiKVNetworkIOCollection(c *gin.Context) {\n\tvar cfg TikvNetworkIoCollectionConfig\n\tif err := c.ShouldBindJSON(&cfg); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\ttikvInfo, _, err := topology.FetchStoreTopology(s.params.PDClient)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tbody := map[string]interface{}{\n\t\ttikvNetworkIoCollectionKey: cfg.Enable,\n\t}\n\tbodyJSON, err := json.Marshal(&body)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\ttype postResult struct {\n\t\ttarget string\n\t\terr    error\n\t}\n\n\tconcurrency := getTiKVNetworkIoCollectionConcurrency(len(tikvInfo))\n\ttaskChan := make(chan topology.StoreInfo, len(tikvInfo))\n\tresultChan := make(chan postResult, len(tikvInfo))\n\tvar wg sync.WaitGroup\n\n\tfor range concurrency {\n\t\twg.Go(func() {\n\t\t\tfor kvStore := range taskChan {\n\t\t\t\ttarget := net.JoinHostPort(kvStore.IP, strconv.Itoa(int(kvStore.Port)))\n\t\t\t\t_, err := s.params.TiKVClient.\n\t\t\t\t\tWithTimeout(tikvNetworkIoCollectionNodeTimeout).\n\t\t\t\t\tSendPostRequest(\n\t\t\t\t\t\tkvStore.IP,\n\t\t\t\t\t\tint(kvStore.StatusPort),\n\t\t\t\t\t\t\"/config\",\n\t\t\t\t\t\tbytes.NewBuffer(bodyJSON),\n\t\t\t\t\t)\n\t\t\t\tresultChan <- postResult{target: target, err: err}\n\t\t\t}\n\t\t})\n\t}\n\n\tfor _, kvStore := range tikvInfo {\n\t\ttaskChan <- kvStore\n\t}\n\tclose(taskChan)\n\twg.Wait()\n\tclose(resultChan)\n\n\tfailures := make([]error, 0)\n\tfailedStores := make([]string, 0)\n\tfor result := range resultChan {\n\t\tif result.err == nil {\n\t\t\tcontinue\n\t\t}\n\t\tfailedStores = append(failedStores, result.target)\n\t\tfailures = append(\n\t\t\tfailures,\n\t\t\terrorx.Decorate(result.err, \"Failed to edit config for TiKV instance `%s`\", result.target),\n\t\t)\n\t}\n\n\tif len(failures) == len(tikvInfo) && len(failures) > 0 {\n\t\tsort.Strings(failedStores)\n\t\trest.Error(c, errorx.Decorate(\n\t\t\tfailures[0],\n\t\t\t\"Failed to edit config for all TiKV instances: %s\",\n\t\t\tstrings.Join(failedStores, \", \"),\n\t\t))\n\t\treturn\n\t}\n\n\tsort.Slice(failures, func(i, j int) bool {\n\t\treturn failures[i].Error() < failures[j].Error()\n\t})\n\n\twarnings := make([]rest.ErrorResponse, 0)\n\tfor _, err := range failures {\n\t\twarnings = append(warnings, rest.NewErrorResponse(err))\n\t}\n\tc.JSON(http.StatusOK, &UpdateTikvNetworkIoCollectionResponse{Warnings: warnings})\n}\n\nfunc getTiKVNetworkIoCollectionConcurrency(tikvCount int) int {\n\tconcurrency := max(tikvCount/10, 1)\n\tif concurrency > tikvNetworkIoCollectionMaxConcurrency {\n\t\tconcurrency = tikvNetworkIoCollectionMaxConcurrency\n\t}\n\treturn concurrency\n}\n\nfunc parseNestedBoolByDotPath(data []byte, dotPath string) (value bool, found bool, err error) {\n\tvar m map[string]interface{}\n\tif err := json.Unmarshal(data, &m); err != nil {\n\t\treturn false, false, err\n\t}\n\tcur := interface{}(m)\n\tfor _, p := range splitDotPath(dotPath) {\n\t\tobj, ok := cur.(map[string]interface{})\n\t\tif !ok {\n\t\t\treturn false, false, nil\n\t\t}\n\t\tnext, ok := obj[p]\n\t\tif !ok {\n\t\t\treturn false, false, nil\n\t\t}\n\t\tcur = next\n\t}\n\n\tswitch v := cur.(type) {\n\tcase bool:\n\t\treturn v, true, nil\n\tcase string:\n\t\t// Be tolerant if TiKV returns \"true\"/\"false\"\n\t\tif v == \"true\" {\n\t\t\treturn true, true, nil\n\t\t}\n\t\tif v == \"false\" {\n\t\t\treturn false, true, nil\n\t\t}\n\t\treturn false, true, nil\n\tdefault:\n\t\treturn false, true, nil\n\t}\n}\n\nfunc splitDotPath(dotPath string) []string {\n\t// Avoid importing strings for a single split; keep consistent with other packages.\n\tparts := make([]string, 0, 4)\n\tlast := 0\n\tfor i := 0; i < len(dotPath); i++ {\n\t\tif dotPath[i] == '.' {\n\t\t\tparts = append(parts, dotPath[last:i])\n\t\t\tlast = i + 1\n\t\t}\n\t}\n\tparts = append(parts, dotPath[last:])\n\treturn parts\n}\n"
  },
  {
    "path": "pkg/apiserver/user/auth.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage user\n\nimport (\n\t\"crypto/rsa\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"net/http\"\n\t\"os\"\n\t\"sort\"\n\t\"time\"\n\n\tjwt \"github.com/appleboy/gin-jwt/v2\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gtank/cryptopasta\"\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/util/featureflag\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nvar (\n\tErrNS                  = errorx.NewNamespace(\"error.api.user\")\n\tErrUnsupportedAuthType = ErrNS.NewType(\"unsupported_auth_type\")\n\tErrNSSignIn            = ErrNS.NewSubNamespace(\"signin\")\n\tErrSignInOther         = ErrNSSignIn.NewType(\"other\")\n)\n\ntype AuthService struct {\n\tFeatureFlagNonRootLogin *featureflag.FeatureFlag\n\n\tmiddleware     *jwt.GinJWTMiddleware\n\tauthenticators map[utils.AuthType]Authenticator\n\n\tRsaPublicKey  *rsa.PublicKey\n\tRsaPrivateKey *rsa.PrivateKey\n}\n\ntype AuthenticateForm struct {\n\tType     utils.AuthType `json:\"type\" example:\"0\"`\n\tUsername string         `json:\"username\" example:\"root\"` // Does not present for AuthTypeSharingCode\n\tPassword string         `json:\"password\"`\n\tExtra    string         `json:\"extra\"` // FIXME: Use strong type\n}\n\ntype TokenResponse struct {\n\tToken  string    `json:\"token\"`\n\tExpire time.Time `json:\"expire\"`\n}\n\ntype SignOutInfo struct {\n\tEndSessionURL string `json:\"end_session_url\"`\n}\n\ntype Authenticator interface {\n\tIsEnabled() (bool, error)\n\tAuthenticate(form AuthenticateForm) (*utils.SessionUser, error)\n\tProcessSession(u *utils.SessionUser) bool\n\tSignOutInfo(u *utils.SessionUser, redirectURL string) (*SignOutInfo, error)\n}\n\ntype BaseAuthenticator struct{}\n\nfunc (a BaseAuthenticator) IsEnabled() (bool, error) {\n\treturn true, nil\n}\n\nfunc (a BaseAuthenticator) ProcessSession(_ *utils.SessionUser) bool {\n\treturn true\n}\n\nfunc (a BaseAuthenticator) SignOutInfo(_ *utils.SessionUser, _ string) (*SignOutInfo, error) {\n\treturn &SignOutInfo{}, nil\n}\n\nfunc NewAuthService(featureFlags *featureflag.Registry) *AuthService {\n\tvar secret *[32]byte\n\n\tsecretStr := os.Getenv(\"DASHBOARD_SESSION_SECRET\")\n\tswitch len(secretStr) {\n\tcase 0:\n\t\tsecret = cryptopasta.NewEncryptionKey()\n\tcase 32:\n\t\tlog.Info(\"DASHBOARD_SESSION_SECRET is overridden from env var\")\n\t\tsecret = &[32]byte{}\n\t\tcopy(secret[:], secretStr)\n\tdefault:\n\t\tlog.Warn(\"DASHBOARD_SESSION_SECRET does not meet the 32 byte size requirement, ignored\")\n\t\tsecret = cryptopasta.NewEncryptionKey()\n\t}\n\n\tprivateKey, publicKey, err := GenerateKey()\n\tif err != nil {\n\t\tlog.Fatal(\"Failed to generate rsa key pairs\", zap.Error(err))\n\t}\n\n\tservice := &AuthService{\n\t\tFeatureFlagNonRootLogin: featureFlags.Register(\"nonRootLogin\", \">= 5.3.0\"),\n\t\tmiddleware:              nil,\n\t\tauthenticators:          map[utils.AuthType]Authenticator{},\n\t\tRsaPrivateKey:           privateKey,\n\t\tRsaPublicKey:            publicKey,\n\t}\n\n\tmiddleware, err := jwt.New(&jwt.GinJWTMiddleware{\n\t\tIdentityKey: utils.SessionUserKey,\n\t\tRealm:       \"dashboard\",\n\t\tKey:         secret[:],\n\t\tTimeout:     time.Hour * 24,\n\t\tMaxRefresh:  time.Hour * 24,\n\t\tAuthenticator: func(c *gin.Context) (interface{}, error) {\n\t\t\tvar form AuthenticateForm\n\t\t\tif err := c.ShouldBindJSON(&form); err != nil {\n\t\t\t\treturn nil, rest.ErrBadRequest.WrapWithNoMessage(err)\n\t\t\t}\n\t\t\tu, err := service.authForm(form)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, errorx.Decorate(err, \"authenticate failed\")\n\t\t\t}\n\t\t\t// TODO: uncomment it after thinking clearly\n\t\t\t// if form.Type == 0 {\n\t\t\t// \t// generate new rsa key pair for each sql auth login\n\t\t\t// \tprivateKey, publicKey, err := GenerateKey()\n\t\t\t// \t// if generate successfully, replace the old key pair\n\t\t\t// \tif err == nil {\n\t\t\t// \t\tservice.RsaPrivateKey = privateKey\n\t\t\t// \t\tservice.RsaPublicKey = publicKey\n\t\t\t// \t}\n\t\t\t// }\n\t\t\treturn u, nil\n\t\t},\n\t\tPayloadFunc: func(data interface{}) jwt.MapClaims {\n\t\t\tuser, ok := data.(*utils.SessionUser)\n\t\t\tif !ok {\n\t\t\t\treturn jwt.MapClaims{}\n\t\t\t}\n\t\t\t// `user` contains sensitive information, thus it is encrypted in the token.\n\t\t\t// In order to be simple, we keep using JWS instead of JWE for thus scenario.\n\t\t\tplain, err := json.Marshal(user)\n\t\t\tif err != nil {\n\t\t\t\treturn jwt.MapClaims{}\n\t\t\t}\n\t\t\tencrypted, err := cryptopasta.Encrypt(plain, secret)\n\t\t\tif err != nil {\n\t\t\t\treturn jwt.MapClaims{}\n\t\t\t}\n\t\t\treturn jwt.MapClaims{\n\t\t\t\t\"p\": base64.StdEncoding.EncodeToString(encrypted),\n\t\t\t}\n\t\t},\n\t\tIdentityHandler: func(c *gin.Context) interface{} {\n\t\t\tclaims := jwt.ExtractClaims(c)\n\n\t\t\tencoded, ok := claims[\"p\"].(string)\n\t\t\tif !ok {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tdecoded, err := base64.StdEncoding.DecodeString(encoded)\n\t\t\tif err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tdecrypted, err := cryptopasta.Decrypt(decoded, secret)\n\t\t\tif err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tvar user utils.SessionUser\n\t\t\tif err := json.Unmarshal(decrypted, &user); err != nil {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\t// Force expire schema outdated sessions.\n\t\t\tif user.Version != utils.SessionVersion {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\ta, ok := service.authenticators[user.AuthFrom]\n\t\t\tif !ok {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\tif !a.ProcessSession(&user) {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\treturn &user\n\t\t},\n\t\tAuthorizator: func(data interface{}, _ *gin.Context) bool {\n\t\t\t// Ensure identity is valid\n\t\t\tif data == nil {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tuser := data.(*utils.SessionUser)\n\t\t\treturn user != nil\n\t\t},\n\t\tHTTPStatusMessageFunc: func(e error, c *gin.Context) string {\n\t\t\tvar err error\n\t\t\tif errorxErr := errorx.Cast(e); errorxErr != nil {\n\t\t\t\t// If the error is an errorx, use it directly.\n\t\t\t\terr = e\n\t\t\t} else if errors.Is(e, jwt.ErrFailedTokenCreation) {\n\t\t\t\t// Try to catch other sign in failure errors.\n\t\t\t\terr = ErrSignInOther.WrapWithNoMessage(e)\n\t\t\t} else {\n\t\t\t\t// The remaining error comes from checking tokens for protected endpoints.\n\t\t\t\terr = rest.ErrUnauthenticated.NewWithNoMessage()\n\t\t\t}\n\t\t\trest.Error(c, err)\n\t\t\treturn err.Error()\n\t\t},\n\t\tUnauthorized: func(c *gin.Context, code int, _ string) {\n\t\t\tc.Status(code)\n\t\t},\n\t\tLoginResponse: func(c *gin.Context, _ int, token string, expire time.Time) {\n\t\t\tc.JSON(http.StatusOK, TokenResponse{\n\t\t\t\tToken:  token,\n\t\t\t\tExpire: expire,\n\t\t\t})\n\t\t},\n\t})\n\tif err != nil {\n\t\t// Error only comes from configuration errors. Fatal is fine.\n\t\tlog.Fatal(\"Failed to configure auth service\", zap.Error(err))\n\t}\n\n\tservice.middleware = middleware\n\n\treturn service\n}\n\nfunc (s *AuthService) authForm(f AuthenticateForm) (*utils.SessionUser, error) {\n\ta, ok := s.authenticators[f.Type]\n\tif !ok {\n\t\treturn nil, ErrUnsupportedAuthType.NewWithNoMessage()\n\t}\n\tu, err := a.Authenticate(f)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tu.AuthFrom = f.Type\n\treturn u, nil\n}\n\nfunc registerRouter(r *gin.RouterGroup, s *AuthService) {\n\tendpoint := r.Group(\"/user\")\n\tendpoint.GET(\"/login_info\", s.GetLoginInfoHandler)\n\tendpoint.POST(\"/login\", s.LoginHandler)\n\tendpoint.GET(\"/sign_out_info\", s.MWAuthRequired(), s.getSignOutInfoHandler)\n}\n\n// MWAuthRequired creates a middleware that verifies the authentication token (JWT) in the request. If the token\n// is valid, identity information will be attached in the context. If there is no authentication token, or the\n// token is invalid, subsequent handlers will be skipped and errors will be generated.\nfunc (s *AuthService) MWAuthRequired() gin.HandlerFunc {\n\treturn s.middleware.MiddlewareFunc()\n}\n\n// TODO: Make these MWRequireXxxPriv more general to use.\nfunc (s *AuthService) MWRequireSharePriv() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tu := utils.GetSession(c)\n\t\tif u == nil {\n\t\t\trest.Error(c, rest.ErrUnauthenticated.NewWithNoMessage())\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\t\tif !u.IsShareable {\n\t\t\trest.Error(c, rest.ErrForbidden.NewWithNoMessage())\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\t\tc.Next()\n\t}\n}\n\nfunc (s *AuthService) MWRequireWritePriv() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tu := utils.GetSession(c)\n\t\tif u == nil {\n\t\t\trest.Error(c, rest.ErrUnauthenticated.NewWithNoMessage())\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\t\tif !u.IsWriteable {\n\t\t\trest.Error(c, rest.ErrForbidden.NewWithNoMessage())\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\t\tc.Next()\n\t}\n}\n\n// RegisterAuthenticator registers an authenticator in the authenticate pipeline.\nfunc (s *AuthService) RegisterAuthenticator(typeID utils.AuthType, a Authenticator) {\n\ts.authenticators[typeID] = a\n}\n\ntype GetLoginInfoResponse struct {\n\tSupportedAuthTypes []int  `json:\"supported_auth_types\"`\n\tSQLAuthPublicKey   string `json:\"sql_auth_public_key\"`\n}\n\n// @ID userGetLoginInfo\n// @Summary Get log in information, like supported authenticate types\n// @Success 200 {object} GetLoginInfoResponse\n// @Router /user/login_info [get]\nfunc (s *AuthService) GetLoginInfoHandler(c *gin.Context) {\n\tsupportedAuth := make([]int, 0)\n\tfor typeID, a := range s.authenticators {\n\t\tenabled, err := a.IsEnabled()\n\t\tif err != nil {\n\t\t\trest.Error(c, err)\n\t\t\treturn\n\t\t}\n\t\tif enabled {\n\t\t\tsupportedAuth = append(supportedAuth, int(typeID))\n\t\t}\n\t}\n\tsort.Ints(supportedAuth)\n\t// both work\n\t// publicKeyStr, err := ExportPublicKeyAsString(s.rsaPublicKey)\n\tpublicKeyStr, err := DumpPublicKeyBase64(s.RsaPublicKey)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tresp := GetLoginInfoResponse{\n\t\tSupportedAuthTypes: supportedAuth,\n\t\tSQLAuthPublicKey:   publicKeyStr,\n\t}\n\tc.JSON(http.StatusOK, resp)\n}\n\n// @ID userLogin\n// @Summary Log in\n// @Param message body AuthenticateForm true \"Credentials\"\n// @Success 200 {object} TokenResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Router /user/login [post]\nfunc (s *AuthService) LoginHandler(c *gin.Context) {\n\ts.middleware.LoginHandler(c)\n}\n\ntype GetSignOutInfoRequest struct {\n\tRedirectURL string `json:\"redirect_url\" form:\"redirect_url\"`\n}\n\n// @ID userGetSignOutInfo\n// @Summary Get sign out info\n// @Success 200 {object} SignOutInfo\n// @Param q query GetSignOutInfoRequest true \"Query\"\n// @Router /user/sign_out_info [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *AuthService) getSignOutInfoHandler(c *gin.Context) {\n\tvar req GetSignOutInfoRequest\n\tif err := c.ShouldBindQuery(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tu := utils.GetSession(c)\n\ta, ok := s.authenticators[u.AuthFrom]\n\tif !ok {\n\t\trest.Error(c, ErrUnsupportedAuthType.NewWithNoMessage())\n\t\treturn\n\t}\n\tsi, err := a.SignOutInfo(u, req.RedirectURL)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, si)\n}\n"
  },
  {
    "path": "pkg/apiserver/user/code/codeauth/auth.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage codeauth\n\nimport (\n\t\"time\"\n\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user/code\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n)\n\nconst typeID utils.AuthType = 1\n\nvar ErrSignInInvalidCode = user.ErrNSSignIn.NewType(\"invalid_code\") // Invalid or expired\n\ntype Authenticator struct {\n\tuser.BaseAuthenticator\n\tsharingCodeService *code.Service\n}\n\nfunc newAuthenticator(sharingCodeService *code.Service) *Authenticator {\n\treturn &Authenticator{\n\t\tsharingCodeService: sharingCodeService,\n\t}\n}\n\nfunc registerAuthenticator(a *Authenticator, authService *user.AuthService) {\n\tauthService.RegisterAuthenticator(typeID, a)\n}\n\nvar Module = fx.Options(\n\tfx.Provide(newAuthenticator),\n\tfx.Invoke(registerAuthenticator),\n)\n\nfunc (a *Authenticator) Authenticate(f user.AuthenticateForm) (*utils.SessionUser, error) {\n\tsession := a.sharingCodeService.NewSessionFromSharingCode(f.Password)\n\tif session == nil {\n\t\treturn nil, ErrSignInInvalidCode.NewWithNoMessage()\n\t}\n\treturn session, nil\n}\n\nfunc (a *Authenticator) ProcessSession(user *utils.SessionUser) bool {\n\treturn !time.Now().After(user.SharedSessionExpireAt)\n}\n"
  },
  {
    "path": "pkg/apiserver/user/code/router.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage code\n\nimport (\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nfunc registerRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/user/share\")\n\tendpoint.Use(auth.MWAuthRequired())\n\tendpoint.POST(\"/code\", auth.MWRequireSharePriv(), s.ShareHandler)\n\tendpoint.POST(\"/revoke\", auth.MWRequireSharePriv(), s.RevokeHandler)\n}\n\ntype ShareRequest struct {\n\tExpireInSeconds int64 `json:\"expire_in_sec\"`\n\tRevokeWritePriv bool  `json:\"revoke_write_priv\"`\n}\n\ntype ShareResponse struct {\n\tCode string `json:\"code\"`\n}\n\n// @ID userShareSession\n// @Summary Share current session and generate a sharing code\n// @Param request body ShareRequest true \"Request body\"\n// @Security JwtAuth\n// @Success 200 {object} ShareResponse\n// @Router /user/share/code [post]\nfunc (s *Service) ShareHandler(c *gin.Context) {\n\tvar req ShareRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\texpiry := time.Second * time.Duration(req.ExpireInSeconds)\n\n\t// after allow user customize the expiration\n\t// we should remove the following check\n\t//\n\t// if expiry > MaxSessionShareExpiry || expiry < 0 {\n\t// \trest.Error(c, rest.ErrBadRequest.New(\"Invalid share expiry\"))\n\t// \treturn\n\t// }\n\tif expiry < 0 {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"Invalid share expiry\"))\n\t\treturn\n\t}\n\n\tsessionUser := utils.GetSession(c)\n\tcode := s.SharingCodeFromSession(sessionUser, expiry, req.RevokeWritePriv)\n\tif code == nil {\n\t\trest.Error(c, ErrShareFailed.New(\"Share session failed\"))\n\t\treturn\n\t}\n\n\tc.JSON(http.StatusOK, ShareResponse{Code: *code})\n}\n\n// @ID userRevokeSession\n// @Summary Reset encryption key to revoke all authorized codes\n// @Security JwtAuth\n// @Success 200\n// @Router /user/share/revoke [post]\nfunc (s *Service) RevokeHandler(c *gin.Context) {\n\ts.ResetEncryptionKey()\n\tc.JSON(http.StatusOK, nil)\n}\n"
  },
  {
    "path": "pkg/apiserver/user/code/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage code\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"sync/atomic\"\n\t\"time\"\n\t\"unsafe\"\n\n\t\"github.com/gtank/cryptopasta\"\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/vmihailenco/msgpack/v5\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n)\n\nvar (\n\tErrNS          = errorx.NewNamespace(\"error.api.user.code\")\n\tErrShareFailed = ErrNS.NewType(\"share_failed\")\n)\n\n// after allow user customize the expiration and support no expiration\n// we should remove the following check\n//\n// const (\n// \t// Max permitted lifetime of a shared session.\n// \tMaxSessionShareExpiry = time.Hour * 24 * 30\n// )\n\ntype Service struct {\n\tsharingSecret *[32]byte\n}\n\ntype sharedSession struct {\n\tSession         *utils.SessionUser\n\tExpireAt        time.Time\n\tRevokeWritePriv bool\n}\n\nfunc NewService() *Service {\n\treturn &Service{\n\t\tsharingSecret: cryptopasta.NewEncryptionKey(),\n\t}\n}\n\nvar Module = fx.Options(\n\tfx.Provide(NewService),\n\tfx.Invoke(registerRouter),\n)\n\nfunc (s *Service) NewSessionFromSharingCode(codeInHex string) *utils.SessionUser {\n\tencrypted, err := hex.DecodeString(codeInHex)\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tb, err := cryptopasta.Decrypt(encrypted, s.loadShareingSecret())\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tvar shared sharedSession\n\tif err := msgpack.Unmarshal(b, &shared); err != nil {\n\t\treturn nil\n\t}\n\n\tif time.Now().After(shared.ExpireAt) {\n\t\treturn nil\n\t}\n\n\tshared.Session.SharedSessionExpireAt = shared.ExpireAt\n\tshared.Session.DisplayName = fmt.Sprintf(\"Shared from %s\", shared.Session.DisplayName)\n\tshared.Session.IsShareable = false\n\tif shared.RevokeWritePriv {\n\t\tshared.Session.IsWriteable = false\n\t}\n\n\treturn shared.Session\n}\n\nfunc (s *Service) SharingCodeFromSession(session *utils.SessionUser, expireIn time.Duration, revokeWritePriv bool) *string {\n\tif !session.IsShareable {\n\t\treturn nil\n\t}\n\n\t// after allow user customize the expiration and support no expiration\n\t// we should remove the following check\n\t//\n\t// if expireIn > MaxSessionShareExpiry {\n\t// \treturn nil\n\t// }\n\tif expireIn < 0 {\n\t\treturn nil\n\t}\n\n\tshared := sharedSession{\n\t\tSession:         session,\n\t\tExpireAt:        time.Now().Add(expireIn),\n\t\tRevokeWritePriv: revokeWritePriv,\n\t}\n\n\tb, err := msgpack.Marshal(&shared)\n\tif err != nil {\n\t\t// Do not output anything about how serialization is failed to avoid potential leaks.\n\t\treturn nil\n\t}\n\n\tencrypted, err := cryptopasta.Encrypt(b, s.loadShareingSecret())\n\tif err != nil {\n\t\treturn nil\n\t}\n\n\tcodeInHex := hex.EncodeToString(encrypted)\n\treturn &codeInHex\n}\n\nfunc (s *Service) ResetEncryptionKey() {\n\t//nolint:gosec // Using unsafe is necessary because atomic pointer operations are required.\n\tatomic.StorePointer((*unsafe.Pointer)(unsafe.Pointer(&s.sharingSecret)), unsafe.Pointer(cryptopasta.NewEncryptionKey()))\n}\n\nfunc (s *Service) loadShareingSecret() *[32]byte {\n\t//nolint:gosec // Using unsafe is necessary because atomic pointer operations are required.\n\treturn (*[32]byte)(atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&s.sharingSecret))))\n}\n"
  },
  {
    "path": "pkg/apiserver/user/module.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage user\n\nimport (\n\t\"go.uber.org/fx\"\n)\n\nvar Module = fx.Options(\n\tfx.Provide(NewAuthService),\n\tfx.Invoke(registerRouter),\n)\n"
  },
  {
    "path": "pkg/apiserver/user/rsa_utils.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage user\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/rsa\"\n\t\"crypto/x509\"\n\t\"encoding/base64\"\n\t\"encoding/pem\"\n)\n\n// Generate RSA private/public key.\nfunc GenerateKey() (*rsa.PrivateKey, *rsa.PublicKey, error) {\n\tprivateKey, err := rsa.GenerateKey(rand.Reader, 2048)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\tpublicKey := &privateKey.PublicKey\n\treturn privateKey, publicKey, nil\n}\n\n// Export public key to string\n// Output format:\n// -----BEGIN PUBLIC KEY-----\n// MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA67F1RPMUO4SjARRe4UfX\n// J7ZOCbcysna0jx2Av14KteGo6AWFHhuIxZwgp83GDqFv0Dhc/be7n+9V5vfq0Ob4\n// fUtdjBio5ciF4pcqzVGbddfJ0R2e52DF6TI2pDgUFdN+1bmGDwZOCyrwBvVh0wW2\n// jAI+QfQyRimZOMqFeX97XjW32vGk7cxNYMys9ExyJcfzfLanbzOwp6kdNbPXnYtU\n// Y2nmp+evlPKrRzBPnmO0bpZhYHklrRxLo/u/mThysMEttLkgzCare+JPQyb3z3Si\n// Q2E7WG4yz6+6L/wB4etHDfRljMOtqEwv9z4inUfh5716Mg23Div/AbwqGPiKPZf7\n// cQIDAQAB\n// -----END PUBLIC KEY-----.\nfunc ExportPublicKeyAsString(publicKey *rsa.PublicKey) (string, error) {\n\tpublicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tpublicKeyPEM := &pem.Block{\n\t\tType:  \"PUBLIC KEY\",\n\t\tBytes: publicKeyBytes,\n\t}\n\n\tpublicKeyString := string(pem.EncodeToMemory(publicKeyPEM))\n\n\treturn publicKeyString, nil\n}\n\n// Dump public key to base64 string\n//  1. Have no header/tailer line\n//  2. Key content is merged into one-line format\n//\n// The output is:\n//\n//\tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2y8mEdCRE8siiI7udpge......2QIDAQAB\nfunc DumpPublicKeyBase64(publicKey *rsa.PublicKey) (string, error) {\n\tkeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tkeyBase64 := base64.StdEncoding.EncodeToString(keyBytes)\n\treturn keyBase64, nil\n}\n\n// Dump private key to base64 string\n//  1. Have no header/tailer line\n//  2. Key content is merged into one-line format\n//\n// The output is:\n//\n//\tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2y8mEdCRE8siiI7udpge......2QIDAQAB\nfunc DumpPrivateKeyBase64(privatekey *rsa.PrivateKey) (string, error) {\n\tkeyBytes := x509.MarshalPKCS1PrivateKey(privatekey)\n\n\tkeyBase64 := base64.StdEncoding.EncodeToString(keyBytes)\n\treturn keyBase64, nil\n}\n\n// Encrypt by public key.\nfunc Encrypt(plainText string, publicKey *rsa.PublicKey) (string, error) {\n\tencryptedText, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, []byte(plainText))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// the encryptedText is encoded by base64 in the frontend by jsEncrypt\n\tencodedText := base64.StdEncoding.EncodeToString(encryptedText)\n\treturn encodedText, nil\n}\n\n// Decrypt by private key.\nfunc Decrypt(cipherText string, privateKey *rsa.PrivateKey) (string, error) {\n\t// the cipherText is encoded by base64 in the frontend by jsEncrypt\n\tdecodedText, err := base64.StdEncoding.DecodeString(cipherText)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tdecryptedText, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, decodedText)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(decryptedText), nil\n}\n"
  },
  {
    "path": "pkg/apiserver/user/sqlauth/sqlauth.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage sqlauth\n\nimport (\n\t\"github.com/joomcode/errorx\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n)\n\nconst typeID utils.AuthType = 0\n\ntype Authenticator struct {\n\tuser.BaseAuthenticator\n\ttidbClient  *tidb.Client\n\tauthService *user.AuthService\n}\n\nfunc NewAuthenticator(tidbClient *tidb.Client) *Authenticator {\n\treturn &Authenticator{\n\t\ttidbClient: tidbClient,\n\t}\n}\n\nfunc registerAuthenticator(a *Authenticator, authService *user.AuthService) {\n\tauthService.RegisterAuthenticator(typeID, a)\n\ta.authService = authService\n}\n\nvar Module = fx.Options(\n\tfx.Provide(NewAuthenticator),\n\tfx.Invoke(registerAuthenticator),\n)\n\nfunc (a *Authenticator) Authenticate(f user.AuthenticateForm) (*utils.SessionUser, error) {\n\tplainPwd, err := user.Decrypt(f.Password, a.authService.RsaPrivateKey)\n\tif err != nil {\n\t\treturn nil, user.ErrSignInOther.WrapWithNoMessage(err)\n\t}\n\n\twriteable, err := user.VerifySQLUser(a.tidbClient, f.Username, plainPwd)\n\tif err != nil {\n\t\tif errorx.Cast(err) == nil {\n\t\t\treturn nil, user.ErrSignInOther.WrapWithNoMessage(err)\n\t\t}\n\t\t// Possible errors could be:\n\t\t// tidb.ErrNoAliveTiDB\n\t\t// tidb.ErrPDAccessFailed\n\t\t// tidb.ErrTiDBConnFailed\n\t\t// tidb.ErrTiDBAuthFailed\n\t\t// user.ErrInsufficientPrivs\n\t\treturn nil, err\n\t}\n\n\treturn &utils.SessionUser{\n\t\tVersion:      utils.SessionVersion,\n\t\tHasTiDBAuth:  true,\n\t\tTiDBUsername: f.Username,\n\t\tTiDBPassword: plainPwd,\n\t\tDisplayName:  f.Username,\n\t\tIsShareable:  true,\n\t\tIsWriteable:  writeable,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/apiserver/user/sso/models.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage sso\n\nimport (\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n)\n\ntype ImpersonateStatus string\n\nconst (\n\tImpersonateStatusSuccess           ImpersonateStatus = \"success\"\n\tImpersonateStatusAuthFail          ImpersonateStatus = \"auth_fail\"\n\tImpersonateStatusInsufficientPrivs ImpersonateStatus = \"insufficient_privileges\"\n)\n\ntype SSOImpersonationModel struct { // nolint\n\tSQLUser string `gorm:\"primary_key;size:128\" json:\"sql_user\"`\n\t// The encryption key is placed somewhere else in the FS, to avoid being collected by diagnostics collecting tools.\n\tEncryptedPass         string             `gorm:\"type:text\" json:\"-\"`\n\tLastImpersonateStatus *ImpersonateStatus `gorm:\"size:32\" json:\"last_impersonate_status\"`\n}\n\nfunc (SSOImpersonationModel) TableName() string {\n\treturn \"sso_impersonation\"\n}\n\nfunc autoMigrate(db *dbstore.DB) error {\n\treturn db.AutoMigrate(&SSOImpersonationModel{})\n}\n"
  },
  {
    "path": "pkg/apiserver/user/sso/router.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage sso\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joomcode/errorx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nfunc registerRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/user/sso\")\n\tendpoint.GET(\"/auth_url\", s.getAuthURLHandler)\n\tendpoint.Use(auth.MWAuthRequired())\n\t// TODO: Forbid modifying config when signed in as SSO.\n\tendpoint.GET(\"/impersonations/list\", s.listImpersonationHandler)\n\tendpoint.POST(\"/impersonation\", auth.MWRequireWritePriv(), s.createImpersonationHandler)\n\tendpoint.GET(\"/config\", s.getConfig)\n\tendpoint.PUT(\"/config\", auth.MWRequireWritePriv(), s.setConfig)\n}\n\ntype GetAuthURLRequest struct {\n\tRedirectURL  string `json:\"redirect_url\" form:\"redirect_url\"`\n\tCodeVerifier string `json:\"code_verifier\" form:\"code_verifier\"`\n\tState        string `json:\"state\" form:\"state\"`\n}\n\n// @ID userSSOGetAuthURL\n// @Summary Get SSO Auth URL\n// @Param q query GetAuthURLRequest true \"Query\"\n// @Success 200 {string} string\n// @Router /user/sso/auth_url [get]\nfunc (s *Service) getAuthURLHandler(c *gin.Context) {\n\tvar req GetAuthURLRequest\n\tif err := c.ShouldBindQuery(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tauthURL, err := s.buildOAuthURL(req.RedirectURL, req.State, req.CodeVerifier)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.String(http.StatusOK, authURL)\n}\n\n// @ID userSSOListImpersonations\n// @Summary List all impersonations\n// @Success 200 {array} SSOImpersonationModel\n// @Router /user/sso/impersonations/list [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) listImpersonationHandler(c *gin.Context) {\n\tvar resp []SSOImpersonationModel\n\terr := s.params.LocalStore.Find(&resp).Error\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, resp)\n}\n\ntype CreateImpersonationRequest struct {\n\tSQLUser  string `json:\"sql_user\"`\n\tPassword string `json:\"password\"`\n}\n\n// @ID userSSOCreateImpersonation\n// @Summary Create an impersonation\n// @Param request body CreateImpersonationRequest true \"Request body\"\n// @Success 200 {object} SSOImpersonationModel\n// @Router /user/sso/impersonation [post]\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) createImpersonationHandler(c *gin.Context) {\n\tvar req CreateImpersonationRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\trec, err := s.createImpersonation(req.SQLUser, req.Password)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\tif errorx.IsOfType(err, ErrUnsupportedUser) || errorx.IsOfType(err, ErrInvalidImpersonateCredential) {\n\t\t\tc.Status(http.StatusBadRequest)\n\t\t}\n\t\treturn\n\t}\n\n\tc.JSON(http.StatusOK, rec)\n}\n\n// @ID userSSOGetConfig\n// @Summary Get SSO config\n// @Success 200 {object} config.SSOCoreConfig\n// @Router /user/sso/config [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) getConfig(c *gin.Context) {\n\tdc, err := s.params.ConfigManager.Get()\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\t// Hide client secret for security\n\tdc.SSO.CoreConfig.ClientSecret = \"\"\n\tc.JSON(http.StatusOK, dc.SSO.CoreConfig)\n}\n\ntype SetConfigRequest struct {\n\tConfig config.SSOCoreConfig `json:\"config\"`\n}\n\n// @ID userSSOSetConfig\n// @Summary Set SSO config\n// @Param request body SetConfigRequest true \"Request body\"\n// @Success 200 {object} config.SSOCoreConfig\n// @Router /user/sso/config [put]\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) setConfig(c *gin.Context) {\n\tvar req SetConfigRequest\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\n\tdConfig := config.SSOConfig{CoreConfig: req.Config}\n\tif req.Config.Enabled {\n\t\twellKnownConfig, err := s.discoverOIDC(req.Config.DiscoveryURL)\n\t\tif err != nil {\n\t\t\trest.Error(c, rest.ErrBadRequest.WrapWithNoMessage(err))\n\t\t\treturn\n\t\t}\n\t\tdConfig.AuthURL = wellKnownConfig.AuthURL\n\t\tdConfig.TokenURL = wellKnownConfig.TokenURL\n\t\tdConfig.UserInfoURL = wellKnownConfig.UserInfoURL\n\t\tdConfig.SignOutURL = wellKnownConfig.EndSessionURL // This is optional\n\t} else {\n\t\terr := s.revokeAllImpersonations()\n\t\tif err != nil {\n\t\t\trest.Error(c, err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tvar opt config.DynamicConfigOption = func(dc *config.DynamicConfig) {\n\t\tdc.SSO = dConfig\n\t}\n\tif err := s.params.ConfigManager.Modify(opt); err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, req.Config)\n}\n"
  },
  {
    "path": "pkg/apiserver/user/sso/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage sso\n\nimport (\n\t\"context\"\n\t\"crypto/sha256\"\n\t\"encoding/base64\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/go-resty/resty/v2\"\n\t\"github.com/gtank/cryptopasta\"\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/zap\"\n\t\"golang.org/x/oauth2\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n)\n\nvar (\n\tErrNS                           = errorx.NewNamespace(\"error.api.user.sso\")\n\tErrUnsupportedUser              = ErrNS.NewType(\"unsupported_user\")\n\tErrInvalidImpersonateCredential = ErrNS.NewType(\"invalid_impersonate_credential\")\n\tErrDiscoverFailed               = ErrNS.NewType(\"discover_failed\")\n\tErrBadConfig                    = ErrNS.NewType(\"bad_config\")\n\tErrOIDCInternalErr              = ErrNS.NewType(\"oidc_internal_err\")\n)\n\nconst (\n\tdiscoveryTimeout = time.Second * 30\n\texchangeTimeout  = time.Second * 30\n\tuserInfoTimeout  = time.Second * 30\n)\n\ntype ServiceParams struct {\n\tfx.In\n\tLocalStore    *dbstore.DB\n\tTiDBClient    *tidb.Client\n\tConfigManager *config.DynamicConfigManager\n}\n\ntype Service struct {\n\tparams           ServiceParams\n\tlifecycleCtx     context.Context\n\toauthStateSecret []byte\n\n\tencKeyPath string\n\tencKeyLock sync.Mutex\n\n\tcreateImpersonationLock sync.Mutex\n}\n\nfunc NewService(p ServiceParams, lc fx.Lifecycle, config *config.Config) (*Service, error) {\n\tif err := autoMigrate(p.LocalStore); err != nil {\n\t\treturn nil, err\n\t}\n\ts := &Service{\n\t\tparams:                  p,\n\t\toauthStateSecret:        cryptopasta.NewHMACKey()[:],\n\t\tencKeyPath:              path.Join(config.DataDir, \"dbek.bin\"),\n\t\tencKeyLock:              sync.Mutex{},\n\t\tcreateImpersonationLock: sync.Mutex{},\n\t}\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\ts.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\treturn s, nil\n}\n\nvar Module = fx.Options(\n\tfx.Provide(NewService),\n\tfx.Invoke(registerRouter),\n)\n\nfunc (s *Service) getMasterEncKey() (*[32]byte, error) {\n\tb, err := os.ReadFile(s.encKeyPath)\n\tif err != nil {\n\t\t// Key does not exist\n\t\tif os.IsNotExist(err) {\n\t\t\treturn nil, nil\n\t\t}\n\t\treturn nil, err\n\t}\n\tif len(b) != 32 {\n\t\treturn nil, fmt.Errorf(\"encryption key is broken\")\n\t}\n\n\tvar fixedLenKey [32]byte\n\tcopy(fixedLenKey[:], b)\n\n\treturn &fixedLenKey, nil\n}\n\n// This function is thread-safe.\nfunc (s *Service) getOrCreateMasterEncKey() (*[32]byte, error) {\n\ts.encKeyLock.Lock()\n\tdefer s.encKeyLock.Unlock()\n\n\tkey, _ := s.getMasterEncKey()\n\tif key != nil {\n\t\treturn key, nil\n\t}\n\n\t// Try to create a key otherwise\n\tkey = cryptopasta.NewEncryptionKey()\n\terr := os.WriteFile(s.encKeyPath, key[:], 0o400) // read only for owner\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"persist key failed: %v\", err)\n\t}\n\treturn key, nil\n}\n\n// getAndDecryptImpersonation reads the impersonation record from local Sqlite and decrypt the record to get the\n// plain SQL password. Currently this function only reads `root` user impersonation.\nfunc (s *Service) getAndDecryptImpersonation() (string, string, error) {\n\tvar imp SSOImpersonationModel\n\terr := s.params.LocalStore.\n\t\tFirst(&imp).Error\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"bad record: %v\", err)\n\t}\n\tkey, err := s.getMasterEncKey()\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"bad encryption key: %v\", err)\n\t}\n\tif key == nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"encryption key is missing\")\n\t}\n\tencrypted, err := hex.DecodeString(imp.EncryptedPass)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"bad record: %v\", err)\n\t}\n\tdecryptedPass, err := cryptopasta.Decrypt(encrypted, key)\n\tif err != nil {\n\t\treturn \"\", \"\", fmt.Errorf(\"bad record: %v\", err)\n\t}\n\treturn imp.SQLUser, string(decryptedPass), nil\n}\n\nfunc (s *Service) updateImpersonationStatus(user string, status ImpersonateStatus) error {\n\treturn s.params.LocalStore.\n\t\tModel(&SSOImpersonationModel{}).\n\t\tWhere(\"sql_user = ?\", user).\n\t\tUpdate(\"last_impersonate_status\", status).\n\t\tError\n}\n\n// newSessionFromImpersonation creates a new session from the impersonation records.\nfunc (s *Service) newSessionFromImpersonation(userInfo *oAuthUserInfo, idToken string) (*utils.SessionUser, error) {\n\tdc, err := s.params.ConfigManager.Get()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tuserName, password, err := s.getAndDecryptImpersonation()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Check whether this user can access dashboard\n\twriteable, err := user.VerifySQLUser(s.params.TiDBClient, userName, password)\n\tif err != nil {\n\t\tif errorx.IsOfType(err, tidb.ErrTiDBAuthFailed) {\n\t\t\t_ = s.updateImpersonationStatus(userName, ImpersonateStatusAuthFail)\n\t\t\treturn nil, ErrInvalidImpersonateCredential.Wrap(err, \"Invalid SQL credential\")\n\t\t}\n\t\tif errorx.IsOfType(err, user.ErrInsufficientPrivs) {\n\t\t\t_ = s.updateImpersonationStatus(userName, ImpersonateStatusInsufficientPrivs)\n\t\t\treturn nil, ErrInvalidImpersonateCredential.Wrap(err, \"Insufficient privileges\")\n\t\t}\n\t\treturn nil, err\n\t}\n\t_ = s.updateImpersonationStatus(userName, ImpersonateStatusSuccess)\n\n\treturn &utils.SessionUser{\n\t\tVersion:      utils.SessionVersion,\n\t\tHasTiDBAuth:  true,\n\t\tTiDBUsername: userName,\n\t\tTiDBPassword: password,\n\t\tDisplayName:  userInfo.Email,\n\t\tIsShareable:  true,\n\t\tIsWriteable:  writeable && !dc.SSO.CoreConfig.IsReadOnly,\n\t\tOIDCIDToken:  idToken,\n\t}, nil\n}\n\nfunc (s *Service) createImpersonation(userName string, password string) (*SSOImpersonationModel, error) {\n\t{\n\t\t// Check whether this user can access dashboard\n\t\t_, err := user.VerifySQLUser(s.params.TiDBClient, userName, password)\n\t\tif err != nil {\n\t\t\tif errorx.IsOfType(err, tidb.ErrTiDBAuthFailed) {\n\t\t\t\treturn nil, ErrInvalidImpersonateCredential.Wrap(err, \"Invalid SQL credential\")\n\t\t\t}\n\t\t\tif errorx.IsOfType(err, user.ErrInsufficientPrivs) {\n\t\t\t\treturn nil, ErrInvalidImpersonateCredential.Wrap(err, \"Insufficient privileges\")\n\t\t\t}\n\t\t\treturn nil, err\n\t\t}\n\t}\n\tkey, err := s.getOrCreateMasterEncKey()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tencrypted, err := cryptopasta.Encrypt([]byte(password), key)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tencryptedInHex := hex.EncodeToString(encrypted)\n\n\trecord := &SSOImpersonationModel{\n\t\tSQLUser:               userName,\n\t\tEncryptedPass:         encryptedInHex,\n\t\tLastImpersonateStatus: nil,\n\t}\n\t// currently, we only support to authorize one sql user\n\ts.createImpersonationLock.Lock()\n\tdefer s.createImpersonationLock.Unlock()\n\n\terr = s.revokeAllImpersonations()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = s.params.LocalStore.Create(&record).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn record, nil\n}\n\nfunc (s *Service) revokeAllImpersonations() error {\n\tsqlStr := fmt.Sprintf(\"DELETE FROM `%s`\", SSOImpersonationModel{}.TableName()) // #nosec\n\treturn s.params.LocalStore.\n\t\tExec(sqlStr).\n\t\tError\n}\n\ntype oidcWellKnownConfig struct {\n\tIssuer                           string   `json:\"issuer\"`\n\tAuthURL                          string   `json:\"authorization_endpoint\"`\n\tTokenURL                         string   `json:\"token_endpoint\"`\n\tUserInfoURL                      string   `json:\"userinfo_endpoint\"`\n\tEndSessionURL                    string   `json:\"end_session_endpoint\"`\n\tJWKSURI                          string   `json:\"jwks_uri\"`\n\tResponseTypesSupported           []string `json:\"response_types_supported\"`\n\tSubjectTypesSupported            []string `json:\"subject_types_supported\"`\n\tIDTokenSigningAlgValuesSupported []string `json:\"id_token_signing_alg_values_supported\"`\n}\n\nfunc (s *Service) discoverOIDC(issuer string) (*oidcWellKnownConfig, error) {\n\tissuer = strings.TrimSuffix(issuer, \"/\")\n\tif !strings.HasPrefix(issuer, \"http://\") && !strings.HasPrefix(issuer, \"https://\") {\n\t\tissuer = \"https://\" + issuer\n\t}\n\t_, err := url.Parse(issuer)\n\tif err != nil {\n\t\treturn nil, ErrDiscoverFailed.Wrap(err, \"Invalid URL format\")\n\t}\n\n\tctx, cancel := context.WithTimeout(s.lifecycleCtx, discoveryTimeout)\n\tdefer cancel()\n\n\twellKnownURL := issuer + \"/.well-known/openid-configuration\"\n\tresp, err := resty.New().R().SetContext(ctx).SetResult(&oidcWellKnownConfig{}).Get(wellKnownURL)\n\tif err != nil {\n\t\treturn nil, ErrDiscoverFailed.Wrap(err, \"Failed to discover OIDC endpoints\")\n\t}\n\twellKnownConfig := resp.Result().(*oidcWellKnownConfig)\n\tif strings.TrimSuffix(wellKnownConfig.Issuer, \"/\") != issuer {\n\t\treturn nil, ErrDiscoverFailed.New(\"Issuer did not match in the OIDC provider, expect %s, got %s\", issuer, wellKnownConfig.Issuer)\n\t}\n\tif len(wellKnownConfig.TokenURL) == 0 {\n\t\treturn nil, ErrDiscoverFailed.New(\"token_endpoint is not provided in the OIDC provider\")\n\t}\n\tif len(wellKnownConfig.AuthURL) == 0 {\n\t\treturn nil, ErrDiscoverFailed.New(\"authorization_endpoint is not provided in the OIDC provider\")\n\t}\n\tif len(wellKnownConfig.UserInfoURL) == 0 {\n\t\treturn nil, ErrDiscoverFailed.New(\"userinfo_endpoint is not provided in the OIDC provider\")\n\t}\n\tif len(wellKnownConfig.JWKSURI) == 0 {\n\t\treturn nil, ErrDiscoverFailed.New(\"jwks_uri is not provided in the OIDC provider\")\n\t}\n\tif len(wellKnownConfig.ResponseTypesSupported) == 0 {\n\t\treturn nil, ErrDiscoverFailed.New(\"response_types_supported is not provided in the OIDC provider\")\n\t}\n\tif len(wellKnownConfig.SubjectTypesSupported) == 0 {\n\t\treturn nil, ErrDiscoverFailed.New(\"subject_types_supported is not provided in the OIDC provider\")\n\t}\n\tif len(wellKnownConfig.IDTokenSigningAlgValuesSupported) == 0 {\n\t\treturn nil, ErrDiscoverFailed.New(\"id_token_signing_alg_values_supported is not provided in the OIDC provider\")\n\t}\n\treturn wellKnownConfig, nil\n}\n\nfunc (s *Service) IsEnabled() (bool, error) {\n\tdc, err := s.params.ConfigManager.Get()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn dc.SSO.CoreConfig.Enabled, nil\n}\n\nfunc (s *Service) buildOAuth2Config(redirectURL string) (*oauth2.Config, error) {\n\tdc, err := s.params.ConfigManager.Get()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !dc.SSO.CoreConfig.Enabled {\n\t\treturn nil, ErrBadConfig.New(\"SSO is not enabled\")\n\t}\n\tscopes := []string{\"openid\", \"profile\", \"email\"}\n\tif len(dc.SSO.CoreConfig.Scopes) > 0 {\n\t\tuserSupplied := strings.Split(dc.SSO.CoreConfig.Scopes, \" \")\n\t\tscopes = append(scopes, userSupplied...)\n\t}\n\treturn &oauth2.Config{\n\t\tClientID:     dc.SSO.CoreConfig.ClientID,\n\t\tClientSecret: dc.SSO.CoreConfig.ClientSecret,\n\t\tRedirectURL:  redirectURL,\n\t\tEndpoint: oauth2.Endpoint{\n\t\t\tAuthURL:  dc.SSO.AuthURL,\n\t\t\tTokenURL: dc.SSO.TokenURL,\n\t\t},\n\t\tScopes: scopes,\n\t}, nil\n}\n\n// buildOAuthURL builds an OAuth URL (to be redirected by the browser) if OIDC SSO is enabled.\n// Returns nil if OIDC SSO is not enabled.\n//\n// `state` is generated by the browser, persisted in local storage and to be verified later before exchange.\n//\n//\tBrowser uses this to ensure that the auth callback is not replayed (by an CSRF attacker that use another state).\n//\n// `codeVerifier` is also generated by the browser, persisted in local storage and will be presented to the RP at exchange.\n//\n//\tRP uses this to ensure that the exchange request is indeed issued by the same client (browser instance).\nfunc (s *Service) buildOAuthURL(redirectURL string, state string, codeVerifier string) (string, error) {\n\toauthConfig, err := s.buildOAuth2Config(redirectURL)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// generate PKCE code challenge, which is base64(sha256(codeVerifier)).\n\th := sha256.New()\n\t_, _ = h.Write([]byte(codeVerifier))\n\tcodeChallenge := base64.RawURLEncoding.EncodeToString(h.Sum(nil))\n\n\tauthURL := oauthConfig.AuthCodeURL(state,\n\t\toauth2.SetAuthURLParam(\"code_challenge\", codeChallenge),\n\t\toauth2.SetAuthURLParam(\"code_challenge_method\", \"S256\"))\n\treturn authURL, nil\n}\n\nfunc (s *Service) exchangeOAuthCode(redirectURL string, code string, codeVerifier string) (string, string, error) {\n\toauthConfig, err := s.buildOAuth2Config(redirectURL)\n\tif err != nil {\n\t\treturn \"\", \"\", err\n\t}\n\n\tctx, cancel := context.WithTimeout(s.lifecycleCtx, exchangeTimeout)\n\tdefer cancel()\n\ttoken, err := oauthConfig.Exchange(ctx, code, oauth2.SetAuthURLParam(\"code_verifier\", codeVerifier))\n\tif err != nil {\n\t\treturn \"\", \"\", ErrOIDCInternalErr.Wrap(err, \"oidc: exchange failed\")\n\t}\n\n\tidToken, ok := token.Extra(\"id_token\").(string)\n\tif !ok {\n\t\treturn \"\", \"\", ErrOIDCInternalErr.Wrap(err, \"oidc: id_token not exist\")\n\t}\n\n\treturn token.AccessToken, idToken, nil\n}\n\ntype oAuthUserInfo struct {\n\tName  string `json:\"name\"`\n\tEmail string `json:\"email\"`\n}\n\nfunc (s *Service) oAuthGetUserInfo(accessToken string) (*oAuthUserInfo, error) {\n\tdc, err := s.params.ConfigManager.Get()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif !dc.SSO.CoreConfig.Enabled {\n\t\treturn nil, ErrBadConfig.New(\"SSO is not enabled\")\n\t}\n\n\tctx, cancel := context.WithTimeout(s.lifecycleCtx, userInfoTimeout)\n\tdefer cancel()\n\n\tresp, err := resty.New().R().SetContext(ctx).\n\t\tSetResult(&oAuthUserInfo{}).\n\t\tSetAuthToken(accessToken).\n\t\tGet(dc.SSO.UserInfoURL)\n\tif err != nil {\n\t\treturn nil, ErrOIDCInternalErr.Wrap(err, \"Failed to read user info\")\n\t}\n\tinfo := resp.Result().(*oAuthUserInfo)\n\treturn info, nil\n}\n\nfunc (s *Service) NewSessionFromOAuthExchange(redirectURL string, code string, codeVerifier string) (*utils.SessionUser, error) {\n\tak, idToken, err := s.exchangeOAuthCode(redirectURL, code, codeVerifier)\n\tif err != nil {\n\t\treturn nil, ErrBadConfig.Wrap(err, \"SSO is not configured correctly\")\n\t}\n\n\tinfo, err := s.oAuthGetUserInfo(ak)\n\tif err != nil {\n\t\t// This is likely not a configuration error\n\t\treturn nil, err\n\t}\n\n\tlog.Info(\"New session via SSO\", zap.Any(\"userinfo\", info))\n\n\tu, err := s.newSessionFromImpersonation(info, idToken)\n\tif err != nil {\n\t\treturn nil, ErrBadConfig.Wrap(err, \"SSO is not configured correctly\")\n\t}\n\treturn u, nil\n}\n\nfunc (s *Service) BuildEndSessionURL(user *utils.SessionUser, redirectURL string) (string, error) {\n\tdc, err := s.params.ConfigManager.Get()\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !dc.SSO.CoreConfig.Enabled {\n\t\treturn \"\", ErrBadConfig.New(\"SSO is not enabled\")\n\t}\n\tu, err := url.Parse(dc.SSO.SignOutURL)\n\tif err != nil {\n\t\treturn \"\", ErrBadConfig.Wrap(err, \"Bad end session URL\")\n\t}\n\tq := u.Query()\n\tq.Add(\"client_id\", dc.SSO.CoreConfig.ClientID)\n\tq.Add(\"id_token_hint\", user.OIDCIDToken)\n\tif len(redirectURL) > 0 {\n\t\tq.Add(\"post_logout_redirect_uri\", redirectURL)\n\t}\n\tu.RawQuery = q.Encode()\n\n\treturn u.String(), nil\n}\n"
  },
  {
    "path": "pkg/apiserver/user/sso/ssoauth/auth.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage ssoauth\n\nimport (\n\t\"encoding/json\"\n\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user/sso\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nconst typeID utils.AuthType = 2\n\ntype Authenticator struct {\n\tuser.BaseAuthenticator\n\tssoService *sso.Service\n}\n\nfunc newAuthenticator(ssoService *sso.Service) *Authenticator {\n\treturn &Authenticator{\n\t\tssoService: ssoService,\n\t}\n}\n\nfunc registerAuthenticator(a *Authenticator, authService *user.AuthService) {\n\tauthService.RegisterAuthenticator(typeID, a)\n}\n\nvar Module = fx.Options(\n\tfx.Provide(newAuthenticator),\n\tfx.Invoke(registerAuthenticator),\n)\n\ntype SSOExtra struct {\n\tCode         string `json:\"code\"`\n\tCodeVerifier string `json:\"code_verifier\"`\n\tRedirectURL  string `json:\"redirect_url\"`\n}\n\nfunc (a *Authenticator) Authenticate(f user.AuthenticateForm) (*utils.SessionUser, error) {\n\tvar extra SSOExtra\n\terr := json.Unmarshal([]byte(f.Extra), &extra)\n\tif err != nil {\n\t\treturn nil, rest.ErrBadRequest.Wrap(err, \"Invalid extra payload\")\n\t}\n\tu, err := a.ssoService.NewSessionFromOAuthExchange(extra.RedirectURL, extra.Code, extra.CodeVerifier)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn u, nil\n}\n\nfunc (a *Authenticator) IsEnabled() (bool, error) {\n\treturn a.ssoService.IsEnabled()\n}\n\nfunc (a *Authenticator) SignOutInfo(u *utils.SessionUser, redirectURL string) (*user.SignOutInfo, error) {\n\tesURL, err := a.ssoService.BuildEndSessionURL(u, redirectURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &user.SignOutInfo{\n\t\tEndSessionURL: esURL,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/apiserver/user/verify_sql_user.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage user\n\nimport (\n\t\"encoding/json\"\n\t\"regexp\"\n\t\"strings\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n)\n\nvar ErrInsufficientPrivs = ErrNSSignIn.NewType(\"insufficient_priv\")\n\n// TiDB config response\n//\n//\t\"security\": {\n//\t  ...\n//\t  \"enable-sem\": true/false,\n//\t  ...\n//\t},.\ntype tidbSecurityConfig struct {\n\tSecurity tidbSEMConfig `json:\"security\"`\n}\n\ntype tidbSEMConfig struct {\n\tEnableSEM      bool `json:\"enable-sem\"`\n\tSkipGrantTable bool `json:\"skip-grant-table\"`\n}\n\nfunc VerifySQLUser(tidbClient *tidb.Client, userName, password string) (writeable bool, err error) {\n\tdb, err := tidbClient.OpenSQLConn(userName, password)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tdefer utils.CloseTiDBConnection(db) //nolint:errcheck\n\n\t// Check dashboard privileges\n\t// 1. Get TiDB config\n\tresData, err := tidbClient.SendGetRequest(\"/config\")\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tvar config tidbSecurityConfig\n\terr = json.Unmarshal(resData, &config)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\t// 2. Check SkipGrantTable\n\t// Note: Currently, if TiDB enable the skip-grant-table, running `show grants` will get error.\n\t// So this is a workaround before the above bug is fixed.\n\tif config.Security.SkipGrantTable {\n\t\treturn true, nil\n\t}\n\t// 3. Get grants\n\tvar grantRows []string\n\terr = db.Raw(\"show grants for current_user()\").Find(&grantRows).Error\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tgrants := parseUserGrants(grantRows)\n\t// 4. Check grants\n\tif !checkDashboardPriv(grants, config.Security.EnableSEM) {\n\t\treturn false, ErrInsufficientPrivs.NewWithNoMessage()\n\t}\n\n\treturn checkWriteablePriv(grants), nil\n}\n\nvar grantRegex = regexp.MustCompile(`GRANT (.+) ON`)\n\n// Currently, There are 2 kinds of grant output format in TiDB:\n// - GRANT [grants] ON [db.table] TO [user]\n// - GRANT [roles] TO [user]\n// Examples:\n// - GRANT PROCESS,SHOW DATABASES,CONFIG ON *.* TO 'dashboardAdmin'@'%'\n// - GRANT SYSTEM_VARIABLES_ADMIN,RESTRICTED_VARIABLES_ADMIN,RESTRICTED_STATUS_ADMIN,RESTRICTED_TABLES_ADMIN ON *.* TO 'dashboardAdmin'@'%'\n// - GRANT ALL PRIVILEGES ON *.* TO 'dashboardAdmin'@'%'\n// - GRANT `app_read`@`%` TO `test`@`%`.\nfunc parseUserGrants(grantRows []string) map[string]struct{} {\n\tgrants := map[string]struct{}{}\n\n\tfor _, row := range grantRows {\n\t\tm := grantRegex.FindStringSubmatch(row)\n\t\tif len(m) == 2 {\n\t\t\tcurRowGrants := strings.SplitSeq(m[1], \",\")\n\t\t\tfor grant := range curRowGrants {\n\t\t\t\tgrants[grant] = struct{}{}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn grants\n}\n\n// To access TiDB Dashboard, following base privileges are required\n// - ALL PRIVILEGES\n// - or\n// - PROCESS\n// - SHOW DATABASES\n// - CONFIG\n// - DASHBOARD_CLIENT or SUPER (SUPER includes DASHBOARD_CLIENT)\n// When TiDB SEM is enabled, following extra privileges are required\n// - RESTRICTED_VARIABLES_ADMIN\n// - RESTRICTED_TABLES_ADMIN\n// - RESTRICTED_STATUS_ADMIN.\nfunc checkDashboardPriv(privs map[string]struct{}, enableSEM bool) bool {\n\tif enableSEM {\n\t\t// Note: When SEM is enabled, these additional privileges need to be checked even if \"ALL PRIVILEGES\" is granted.\n\t\tif !hasPriv(\"RESTRICTED_VARIABLES_ADMIN\", privs) {\n\t\t\treturn false\n\t\t}\n\t\tif !hasPriv(\"RESTRICTED_TABLES_ADMIN\", privs) {\n\t\t\treturn false\n\t\t}\n\t\tif !hasPriv(\"RESTRICTED_STATUS_ADMIN\", privs) {\n\t\t\treturn false\n\t\t}\n\t}\n\n\tif hasPriv(\"ALL PRIVILEGES\", privs) {\n\t\t// ALL PRIVILEGES contains privileges below. If it is set, privilege requirement is met.\n\t\treturn true\n\t}\n\tif !hasPriv(\"PROCESS\", privs) {\n\t\treturn false\n\t}\n\tif !hasPriv(\"SHOW DATABASES\", privs) {\n\t\treturn false\n\t}\n\tif !hasPriv(\"CONFIG\", privs) {\n\t\treturn false\n\t}\n\n\tif hasPriv(\"SUPER\", privs) {\n\t\t// SUPER contains privileges below. If it is set, privilege requirement is met.\n\t\treturn true\n\t}\n\tif !hasPriv(\"DASHBOARD_CLIENT\", privs) {\n\t\treturn false\n\t}\n\n\treturn true\n}\n\nfunc checkWriteablePriv(privs map[string]struct{}) bool {\n\tif hasPriv(\"ALL PRIVILEGES\", privs) {\n\t\treturn true\n\t}\n\tif hasPriv(\"SUPER\", privs) {\n\t\treturn true\n\t}\n\tif hasPriv(\"SYSTEM_VARIABLES_ADMIN\", privs) {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc hasPriv(priv string, privs map[string]struct{}) bool {\n\t_, ok := privs[priv]\n\treturn ok\n}\n"
  },
  {
    "path": "pkg/apiserver/user/verify_sql_user_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage user\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/check\"\n)\n\nfunc TestT(t *testing.T) {\n\tcheck.CustomVerboseFlag = true\n\tcheck.TestingT(t)\n}\n\nvar _ = check.Suite(&testVerifySQLUserSuite{})\n\ntype testVerifySQLUserSuite struct{}\n\nfunc (t *testVerifySQLUserSuite) Test_parseUserGrants(c *check.C) {\n\tcases := []struct {\n\t\tdesc     string\n\t\tinput    []string\n\t\texpected map[string]struct{}\n\t}{\n\t\t// 0\n\t\t{\n\t\t\tdesc: \"all privileges\",\n\t\t\tinput: []string{\n\t\t\t\t\"GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION\",\n\t\t\t},\n\t\t\texpected: map[string]struct{}{\n\t\t\t\t\"ALL PRIVILEGES\": {},\n\t\t\t},\n\t\t},\n\t\t// 1\n\t\t{\n\t\t\tdesc: \"table privileges\",\n\t\t\tinput: []string{\n\t\t\t\t\"GRANT SELECT,INSERT ON mysql.* TO 'dashboardAdmin'@'%'\",\n\t\t\t},\n\t\t\texpected: map[string]struct{}{\n\t\t\t\t\"SELECT\": {},\n\t\t\t\t\"INSERT\": {},\n\t\t\t},\n\t\t},\n\t\t// 2\n\t\t{\n\t\t\tdesc: \"global privileges\",\n\t\t\tinput: []string{\n\t\t\t\t\"GRANT PROCESS,SHOW DATABASES,CONFIG ON *.* TO 'dashboardAdmin'@'%'\",\n\t\t\t\t\"GRANT SYSTEM_VARIABLES_ADMIN ON *.* TO 'dashboardAdmin'@'%'\",\n\t\t\t},\n\t\t\texpected: map[string]struct{}{\n\t\t\t\t\"PROCESS\":                {},\n\t\t\t\t\"SHOW DATABASES\":         {},\n\t\t\t\t\"CONFIG\":                 {},\n\t\t\t\t\"SYSTEM_VARIABLES_ADMIN\": {},\n\t\t\t},\n\t\t},\n\t\t// 3\n\t\t{\n\t\t\tdesc: \"role privileges\",\n\t\t\tinput: []string{\n\t\t\t\t\"GRANT `app_read`@`%` TO `test`@`%`\",\n\t\t\t},\n\t\t\texpected: map[string]struct{}{},\n\t\t},\n\t}\n\n\tfor i, v := range cases {\n\t\tactual := parseUserGrants(v.input)\n\t\tc.Assert(actual, check.DeepEquals, v.expected, check.Commentf(\"parse %s (index: %d) failed\", v.desc, i))\n\t}\n}\n\nfunc (t *testVerifySQLUserSuite) Test_checkDashboardPriv(c *check.C) {\n\tcases := []struct {\n\t\tdesc      string\n\t\tgrants    []string\n\t\tenableSEM bool\n\t\texpected  bool\n\t}{\n\t\t// 0\n\t\t{\n\t\t\tdesc:      \"all privileges with enableSEM false\",\n\t\t\tgrants:    []string{\"ALL PRIVILEGES\"},\n\t\t\tenableSEM: false,\n\t\t\texpected:  true,\n\t\t},\n\t\t// 1\n\t\t{\n\t\t\tdesc:      \"all privileges with enableSEM true\",\n\t\t\tgrants:    []string{\"ALL PRIVILEGES\"},\n\t\t\tenableSEM: true,\n\t\t\texpected:  false,\n\t\t},\n\t\t// 2\n\t\t{\n\t\t\tdesc:      \"super privileges with enableSEM false\",\n\t\t\tgrants:    []string{\"PROCESS\", \"SHOW DATABASES\", \"CONFIG\", \"SUPER\"},\n\t\t\tenableSEM: false,\n\t\t\texpected:  true,\n\t\t},\n\t\t// 3\n\t\t{\n\t\t\tdesc:      \"super privileges with enableSEM true\",\n\t\t\tgrants:    []string{\"PROCESS\", \"SHOW DATABASES\", \"CONFIG\", \"SUPER\"},\n\t\t\tenableSEM: true,\n\t\t\texpected:  false,\n\t\t},\n\t\t// 4\n\t\t{\n\t\t\tdesc:      \"base privileges with enableSEM false\",\n\t\t\tgrants:    []string{\"PROCESS\", \"SHOW DATABASES\", \"CONFIG\", \"DASHBOARD_CLIENT\"},\n\t\t\tenableSEM: false,\n\t\t\texpected:  true,\n\t\t},\n\t\t// 5\n\t\t{\n\t\t\tdesc:      \"base privileges with enableSEM true\",\n\t\t\tgrants:    []string{\"PROCESS\", \"SHOW DATABASES\", \"CONFIG\", \"DASHBOARD_CLIENT\"},\n\t\t\tenableSEM: true,\n\t\t\texpected:  false,\n\t\t},\n\t\t// 6\n\t\t{\n\t\t\tdesc:      \"lack PROCESS privilege\",\n\t\t\tgrants:    []string{\"SHOW DATABASES\", \"CONFIG\", \"DASHBOARD_CLIENT\"},\n\t\t\tenableSEM: false,\n\t\t\texpected:  false,\n\t\t},\n\t\t// 7\n\t\t{\n\t\t\tdesc:      \"lack DASHBOARD_CLIENT privilege\",\n\t\t\tgrants:    []string{\"PROCESS\", \"SHOW DATABASES\", \"CONFIG\"},\n\t\t\tenableSEM: false,\n\t\t\texpected:  false,\n\t\t},\n\t\t// 8\n\t\t{\n\t\t\tdesc:      \"extra privileges\",\n\t\t\tgrants:    []string{\"PROCESS\", \"SHOW DATABASES\", \"CONFIG\", \"DASHBOARD_CLIENT\", \"RESTRICTED_VARIABLES_ADMIN\", \"RESTRICTED_TABLES_ADMIN\", \"RESTRICTED_STATUS_ADMIN\"},\n\t\t\tenableSEM: true,\n\t\t\texpected:  true,\n\t\t},\n\t\t// 9\n\t\t{\n\t\t\tdesc:      \"lack RESTRICTED_VARIABLES_ADMIN extra privileges\",\n\t\t\tgrants:    []string{\"PROCESS\", \"SHOW DATABASES\", \"CONFIG\", \"DASHBOARD_CLIENT\", \"RESTRICTED_TABLES_ADMIN\", \"RESTRICTED_STATUS_ADMIN\"},\n\t\t\tenableSEM: true,\n\t\t\texpected:  false,\n\t\t},\n\t}\n\tfor i, v := range cases {\n\t\tgrants := map[string]struct{}{}\n\t\tfor _, grant := range v.grants {\n\t\t\tgrants[grant] = struct{}{}\n\t\t}\n\t\tactual := checkDashboardPriv(grants, v.enableSEM)\n\t\tc.Assert(actual, check.DeepEquals, v.expected, check.Commentf(\"check %s (index: %d) failed\", v.desc, i))\n\t}\n}\n\nfunc (t *testVerifySQLUserSuite) Test_checkWriteablePriv(c *check.C) {\n\tcases := []struct {\n\t\tdesc     string\n\t\tgrants   []string\n\t\texpected bool\n\t}{\n\t\t// 0\n\t\t{\n\t\t\tdesc: \"ALL privileges\",\n\t\t\tgrants: []string{\n\t\t\t\t\"ALL PRIVILEGES\",\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t// 1\n\t\t{\n\t\t\tdesc: \"SUPER privileges\",\n\t\t\tgrants: []string{\n\t\t\t\t\"SUPER\",\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t// 2\n\t\t{\n\t\t\tdesc: \"SYSTEM_VARIABLES_ADMIN privileges\",\n\t\t\tgrants: []string{\n\t\t\t\t\"SYSTEM_VARIABLES_ADMIN\",\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t// 3\n\t\t{\n\t\t\tdesc: \"all privileges\",\n\t\t\tgrants: []string{\n\t\t\t\t\"ALL PRIVILEGES\", \"SUPER\", \"SYSTEM_VARIABLES_ADMIN\",\n\t\t\t},\n\t\t\texpected: true,\n\t\t},\n\t\t// 4\n\t\t{\n\t\t\tdesc: \"other privileges\",\n\t\t\tgrants: []string{\n\t\t\t\t\"PROCESS\", \"CONFIG\",\n\t\t\t},\n\t\t\texpected: false,\n\t\t},\n\t}\n\n\tfor i, v := range cases {\n\t\tgrants := map[string]struct{}{}\n\t\tfor _, grant := range v.grants {\n\t\t\tgrants[grant] = struct{}{}\n\t\t}\n\t\tactual := checkWriteablePriv(grants)\n\t\tc.Assert(actual, check.DeepEquals, v.expected, check.Commentf(\"check %s (index: %d) failed\", v.desc, i))\n\t}\n}\n"
  },
  {
    "path": "pkg/apiserver/utils/auth.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\ntype AuthType int\n\nconst SessionVersion = 2\n\n// The content of this structure will be encrypted and stored as both Session Token and Sharing Token.\n// For fields that don't need to be cloned during session sharing, mark fields as `msgpack:\"-\"`.\ntype SessionUser struct {\n\t// Must be 2. This field is used to invalidate outdated sessions after schema change.\n\tVersion int\n\n\tDisplayName string\n\n\tHasTiDBAuth  bool\n\tTiDBUsername string\n\tTiDBPassword string\n\n\t// This field only exists for CodeAuth.\n\tSharedSessionExpireAt time.Time `msgpack:\"-\"`\n\n\t// This field only exists for SSOAuth\n\tOIDCIDToken string `json:\",omitempty\"`\n\n\t// These fields should not be updated by individual authenticators.\n\tAuthFrom AuthType `msgpack:\"-\" json:\",omitempty\"`\n\n\t// TODO: Make them table fields\n\tIsShareable bool\n\tIsWriteable bool\n}\n\nconst (\n\t// The key that attached the SessionUser in the gin Context.\n\tSessionUserKey = \"user\"\n)\n\nfunc GetSession(c *gin.Context) *SessionUser {\n\ti, ok := c.Get(SessionUserKey)\n\tif !ok {\n\t\treturn nil\n\t}\n\treturn i.(*SessionUser)\n}\n"
  },
  {
    "path": "pkg/apiserver/utils/binary_plan.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\tsimplejson \"github.com/bitly/go-simplejson\"\n\t\"github.com/golang/snappy\"\n\t\"github.com/pingcap/tipb/go-tipb\"\n\tjson \"google.golang.org/protobuf/encoding/protojson\"\n\t\"google.golang.org/protobuf/runtime/protoimpl\"\n\t\"gorm.io/gorm\"\n)\n\nconst (\n\tMainTree                = \"main\"\n\tCteTrees                = \"ctes\"\n\tChildren                = \"children\"\n\tDuration                = \"duration\"\n\tTime                    = \"time\"\n\tDiagnosis               = \"diagnosis\"\n\tRootGroupExecInfo       = \"rootGroupExecInfo\"\n\tRootBasicExecInfo       = \"rootBasicExecInfo\"\n\tOperatorInfo            = \"operatorInfo\"\n\tOperatorName            = \"name\"\n\tCopExecInfo             = \"copExecInfo\"\n\tCacheHitRatio           = \"cacheHitRatio\"\n\tTaskType                = \"taskType\"\n\tStoreType               = \"storeType\"\n\tDiskBytes               = \"diskBytes\"\n\tMemoryBytes             = \"memoryBytes\"\n\tActRows                 = \"actRows\"\n\tEstRows                 = \"estRows\"\n\tAccessObjects           = \"accessObjects\"\n\tScanObject              = \"scanObject\"\n\tDynamicpartitionObjects = \"dynamicpartitionObjects\"\n\tOtherObject             = \"otherObject\"\n\tDiscardedDueToTooLong   = \"discardedDueToTooLong\"\n\tBuildSide               = \"buildSide\"\n\tProbeSide               = \"probeSide\"\n\n\tJoinTaskThreshold    = 10000\n\treturnTableThreshold = 0.7\n\n\t// role tag.\n\tHighEstError               = \"high_est_error\"\n\tDiskSpill                  = \"disk_spill\"\n\tPseudoEst                  = \"pseudo_est\"\n\tGoodFilterOnTableFullScan  = \"good_filter_on_table_fullscan\"\n\tBadIndexForIndexLookUp     = \"bad_index_for_index_lookup\"\n\tIndexJoinBuildSideTooLarge = \"index_join_build_side_too_large\"\n\tTiKVHugeTableScan          = \"tikv_huge_table_scan\"\n)\n\n// operator.\ntype operator int\n\nconst (\n\tDefault operator = iota\n\tIndexJoin\n\tIndexMergeJoin\n\tIndexHashJoin\n\tApply\n\tShuffle\n\tShuffleReceiver\n\tIndexLookUpReader\n\tIndexMergeReader\n\tIndexFullScan\n\tIndexRangeScan\n\tTableFullScan\n\tTableRangeScan\n\tTableRowIDScan\n\tSelection\n)\n\ntype concurrency struct {\n\tjoinConcurrency    int\n\tcopConcurrency     int\n\ttableConcurrency   int\n\tapplyConcurrency   int\n\tshuffleConcurrency int\n}\n\ntype diagnosticOperation struct {\n\tneedUdateStatistics bool\n}\n\nvar (\n\tneedJSONFormat = []string{\n\t\tRootGroupExecInfo,\n\t\tRootBasicExecInfo,\n\t\tCopExecInfo,\n\t}\n\n\tneedSetNA = []string{\n\t\tMemoryBytes,\n\t\tDiskBytes,\n\t}\n\n\tneedCheckOperator = []string{\n\t\t\"eq\", \"ge\", \"gt\", \"le\", \"lt\", \"isnull\", \"in\",\n\t}\n)\n\nfunc newConcurrency() concurrency {\n\treturn concurrency{\n\t\tjoinConcurrency:    1,\n\t\tcopConcurrency:     1,\n\t\ttableConcurrency:   1,\n\t\tapplyConcurrency:   1,\n\t\tshuffleConcurrency: 1,\n\t}\n}\n\nfunc newDiagnosticOperation() diagnosticOperation {\n\treturn diagnosticOperation{}\n}\n\n// GenerateBinaryPlan generate visual plan from raw data.\nfunc GenerateBinaryPlan(v string) (*tipb.ExplainData, error) {\n\tif v == \"\" {\n\t\treturn nil, nil\n\t}\n\n\t// base64 decode\n\tcompressVPBytes, err := base64.StdEncoding.DecodeString(v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// snappy uncompress\n\tbpBytes, err := snappy.Decode(nil, compressVPBytes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// proto unmarshal\n\tbp := &tipb.ExplainData{}\n\terr = bp.Unmarshal(bpBytes)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bp, nil\n}\n\nfunc GenerateBinaryPlanJSON(b string) (string, error) {\n\t// generate bp\n\tbp, err := GenerateBinaryPlan(b)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tif bp == nil {\n\t\treturn \"\", nil\n\t}\n\t// json marshal\n\tbpJSON, err := json.Marshal(protoimpl.X.ProtoMessageV2Of(bp))\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tbpJSON, err = formatBinaryPlanJSON(bpJSON)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbpJSON, err = analyzeDuration(bpJSON)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tbpJSON, err = diagnosticOperator(bpJSON)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn string(bpJSON), nil\n}\n\nfunc diagnosticOperator(bp []byte) ([]byte, error) {\n\t// new simple json\n\tvp, err := simplejson.NewJson(bp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif vp.Get(DiscardedDueToTooLong).MustBool() {\n\t\treturn vp.MarshalJSON()\n\t}\n\n\t// main\n\t_, err = diagnosticOperatorNode(vp.Get(MainTree), newDiagnosticOperation())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// ctes\n\t_, err = diagnosticOperatorNodes((vp.Get(CteTrees)), newDiagnosticOperation())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn vp.MarshalJSON()\n}\n\n// diagnosticOperatorNode set node.diagnosis.\nfunc diagnosticOperatorNode(node *simplejson.Json, diagOp diagnosticOperation) (diagnosticOperation, error) {\n\toperator := getOperatorType(node)\n\toperatorInfo := node.Get(OperatorInfo).MustString()\n\tdiagnosis := []string{}\n\n\t// filter system table\n\tswitch strings.ToLower(getScanDatabase(node)) {\n\tcase \"information_schema\", \"metrics_schema\", \"performance_schema\", \"mysql\":\n\t\tdiagOp, err := diagnosticOperatorNodes(node.Get(Children), diagOp)\n\t\tif err != nil {\n\t\t\treturn diagOp, nil\n\t\t}\n\t\t// set diagnosis\n\t\tnode.Set(Diagnosis, diagnosis)\n\t\treturn diagOp, nil\n\t}\n\n\t// pseudo stats\n\tif strings.Contains(operatorInfo, \"stats:pseudo\") {\n\t\tdiagnosis = append(diagnosis, PseudoEst)\n\t}\n\n\t// use disk\n\tdiskBytes := node.Get(DiskBytes).MustString()\n\tif diskBytes != \"N/A\" {\n\t\tdiagnosis = append(diagnosis, DiskSpill)\n\t}\n\n\tdiagOp, err := diagnosticOperatorNodes(node.Get(Children), diagOp)\n\tif err != nil {\n\t\treturn diagOp, nil\n\t}\n\n\t// marked rows estimation error too high\n\tif diagOp.needUdateStatistics {\n\t\tswitch operator {\n\t\tcase IndexFullScan, IndexRangeScan, TableFullScan, TableRangeScan, TableRowIDScan, Selection:\n\t\t\tactRows := node.Get(ActRows).MustFloat64()\n\t\t\testRows := node.Get(EstRows).MustFloat64()\n\t\t\tif actRows == 0 || estRows == 0 {\n\t\t\t\tactRows = actRows + 1\n\t\t\t\testRows = estRows + 1\n\t\t\t}\n\t\t\tif actRows/estRows > 100 || estRows/actRows > 100 {\n\t\t\t\tdiagnosis = append(diagnosis, HighEstError)\n\t\t\t}\n\t\tdefault:\n\t\t\tdiagOp.needUdateStatistics = false\n\t\t}\n\t}\n\n\tswitch operator {\n\t// index join\n\tcase IndexJoin, IndexMergeJoin, IndexHashJoin:\n\t\t// only use in build\n\t\trootGroupInfo := node.Get(RootGroupExecInfo)\n\t\trootGroupInfoCount := len(rootGroupInfo.MustArray())\n\t\tif rootGroupInfoCount > 0 {\n\t\t\tfor i := range rootGroupInfoCount {\n\t\t\t\tjoinTaskCountStr := rootGroupInfo.GetIndex(i).GetPath(\"inner\", \"task\").MustString()\n\t\t\t\tjoinTaskCount, _ := strconv.Atoi(joinTaskCountStr)\n\t\t\t\tif joinTaskCount > JoinTaskThreshold {\n\t\t\t\t\tdiagnosis = append(diagnosis, IndexJoinBuildSideTooLarge)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t// unreasonable index return table plan\n\tcase IndexLookUpReader:\n\t\tcNode := getBuildChildrenWithDriverSide(node)\n\t\tif getOperatorType(cNode) != Selection {\n\t\t\tbreak\n\t\t}\n\t\tif len(cNode.Get(Children).MustArray()) != 1 {\n\t\t\tbreak\n\t\t}\n\t\tgNode := cNode.Get(Children).GetIndex(0)\n\n\t\tif getOperatorType(gNode) != IndexFullScan {\n\t\t\tbreak\n\t\t}\n\n\t\tif !strings.Contains(gNode.Get(OperatorInfo).MustString(), \"keep order:false\") {\n\t\t\tbreak\n\t\t}\n\n\t\tcNodeActRows := cNode.Get(ActRows).MustFloat64()\n\t\tgNodeActRows := gNode.Get(ActRows).MustFloat64()\n\t\tif cNodeActRows/gNodeActRows > returnTableThreshold {\n\t\t\tdiagnosis = append(diagnosis, BadIndexForIndexLookUp)\n\t\t}\n\tcase Selection:\n\t\tif len(node.Get(Children).MustArray()) != 1 {\n\t\t\tbreak\n\t\t}\n\t\tcNode := node.Get(Children).GetIndex(0)\n\n\t\tif getOperatorType(cNode) != TableFullScan {\n\t\t\tbreak\n\t\t}\n\t\tif node.Get(StoreType).MustString() != \"tikv\" {\n\t\t\tbreak\n\t\t}\n\n\t\tif !useComparisonOperator(operatorInfo) {\n\t\t\tbreak\n\t\t}\n\n\t\tif node.Get(ActRows).MustFloat64() < 10000 && cNode.Get(ActRows).MustFloat64() > 5000000 {\n\t\t\tdiagnosis = append(diagnosis, GoodFilterOnTableFullScan)\n\t\t}\n\tcase TableFullScan:\n\t\tif node.Get(StoreType).MustString() == \"tikv\" && node.Get(ActRows).MustFloat64() > 1000000000 {\n\t\t\tdiagnosis = append(diagnosis, TiKVHugeTableScan)\n\t\t}\n\t}\n\n\t// set diagnosis\n\tnode.Set(Diagnosis, diagnosis)\n\n\treturn diagOp, nil\n}\n\nfunc diagnosticOperatorNodes(nodes *simplejson.Json, diagOp diagnosticOperation) (diagnosticOperation, error) {\n\tlength := len(nodes.MustArray())\n\n\t// no children nodes\n\tif length == 0 {\n\t\tdiagOp.needUdateStatistics = true\n\t\treturn diagOp, nil\n\t}\n\tvar needUdateStatistics bool\n\tfor i := range length {\n\t\tc := nodes.GetIndex(i)\n\t\tn, err := diagnosticOperatorNode(c, diagOp)\n\t\tif err != nil {\n\t\t\treturn diagOp, err\n\t\t}\n\t\tif n.needUdateStatistics {\n\t\t\tneedUdateStatistics = n.needUdateStatistics\n\t\t}\n\t}\n\n\tdiagOp.needUdateStatistics = needUdateStatistics\n\treturn diagOp, nil\n}\n\n// cut go 1.18 strings.Cut.\nfunc cut(s, sep string) (before, after string, found bool) {\n\tif before0, after0, ok := strings.Cut(s, sep); ok {\n\t\treturn before0, after0, true\n\t}\n\treturn s, \"\", false\n}\n\nfunc getScanDatabase(node *simplejson.Json) string {\n\taccessObjects := node.Get(AccessObjects)\n\n\tlength := len(accessObjects.MustArray())\n\n\tif length == 0 {\n\t\treturn \"\"\n\t}\n\n\tfor i := range length {\n\t\tdatabase := accessObjects.GetIndex(i).GetPath(ScanObject, \"database\").MustString()\n\t\tif database != \"\" {\n\t\t\treturn database\n\t\t}\n\t}\n\n\treturn \"\"\n}\n\n// useComparisonOperator\n// matching rules: only match the eq/ge/gt/le/lt/isnull/in functions on a single column\n// for example:\n// eq(test.t.a, 1)  ture\n// eq(minus(test.t1.b, 1), 1) false\n// eq(test.t.a, 1), eq(test.t.a, 2)  ture\n// eq(test.t.a, 1), eq(test.t.b, 1) false\n// in(test.t.a, 1, 2, 3, 4) ture\n// in(test.t.a, 1, 2, 3, 4), in(test.t.b, 1, 2, 3, 4) false.\nfunc useComparisonOperator(operatorInfo string) bool {\n\tuseComparisonOperator := false\n\tcolumnSet := make(map[string]bool)\n\tfor _, op := range needCheckOperator {\n\t\tif strings.Contains(operatorInfo, op) {\n\t\t\tuseComparisonOperator = true\n\t\t\tn := strings.Count(operatorInfo, op+\"(\")\n\t\t\tfor range n {\n\t\t\t\tcolumn := \"\"\n\t\t\t\ts1, s, _ := cut(operatorInfo, op+\"(\")\n\t\t\t\ts, s2, _ := cut(s, \")\")\n\n\t\t\t\tif strings.Contains(s, \"(\") {\n\t\t\t\t\treturn false\n\t\t\t\t}\n\t\t\t\tswitch op {\n\t\t\t\tcase \"isnull\":\n\t\t\t\t\t// not(isnull(test2.t1.a)) true\n\t\t\t\t\tif strings.HasSuffix(s1, \"not(\") && strings.HasPrefix(s2, \")\") {\n\t\t\t\t\t\ts1 = strings.TrimRight(s1, \"not(\")\n\t\t\t\t\t\ts2 = strings.TrimLeft(s2, \")\")\n\t\t\t\t\t}\n\t\t\t\t\t// isnull(test.t.a)\n\t\t\t\t\tcolumn = strings.TrimSpace(s)\n\t\t\t\t\t// if strings.\n\t\t\t\tcase \"in\":\n\t\t\t\t\t// in(test.t.a, 1, 2, 3, 4) true\n\t\t\t\t\tslist := strings.Split(s, \",\")\n\t\t\t\t\tcolumn = strings.TrimSpace(slist[0])\n\t\t\t\t\t// in(test.t.a, 1, 2, test.t.b, 4) false\n\t\t\t\t\tfor _, c := range slist[1:] {\n\t\t\t\t\t\tc = strings.TrimSpace(c)\n\t\t\t\t\t\tif strings.Count(c, \".\") == 2 && !strings.Contains(c, `\"`) {\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\tdefault:\n\t\t\t\t\t// eq(test.t.a, 1) true\n\t\t\t\t\tslist := strings.Split(s, \",\")\n\t\t\t\t\tif len(slist) != 2 {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t\tc := \"\"\n\t\t\t\t\tc1, c2 := strings.TrimSpace(slist[0]), strings.TrimSpace(slist[1])\n\t\t\t\t\tif strings.Count(c1, \".\") == 2 && !strings.Contains(c1, `\"`) {\n\t\t\t\t\t\tcolumn = c1\n\t\t\t\t\t\tc = c2\n\t\t\t\t\t} else if strings.Count(c2, \".\") == 2 && !strings.Contains(c2, `\"`) {\n\t\t\t\t\t\tcolumn = c2\n\t\t\t\t\t\tc = c1\n\t\t\t\t\t}\n\t\t\t\t\t// eq(test2.t1.a, test2.t2.a) false\n\t\t\t\t\tdatabase := strings.Split(column, \".\")[0]\n\t\t\t\t\tif len(slist) > 1 && strings.HasPrefix(c, database) {\n\t\t\t\t\t\treturn false\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif column != \"\" {\n\t\t\t\t\tcolumnSet[column] = true\n\t\t\t\t}\n\t\t\t\toperatorInfo = s1 + s2\n\t\t\t}\n\t\t}\n\t}\n\tif useComparisonOperator {\n\t\tif strings.Count(operatorInfo, \"(\") == strings.Count(operatorInfo, \")\") && strings.Count(operatorInfo, \"(\") > 0 {\n\t\t\treturn false\n\t\t}\n\t\t// single column\n\t\tif len(columnSet) != 1 {\n\t\t\treturn false\n\t\t}\n\t}\n\n\treturn useComparisonOperator\n}\n\nfunc analyzeDuration(bp []byte) ([]byte, error) {\n\t// new simple json\n\tvp, err := simplejson.NewJson(bp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif vp.Get(DiscardedDueToTooLong).MustBool() {\n\t\treturn vp.MarshalJSON()\n\t}\n\n\trootTs := vp.Get(MainTree).GetPath(RootBasicExecInfo, Time).MustString()\n\t// main\n\tmainConcurrency := newConcurrency()\n\t_, err = analyzeDurationNode(vp.Get(MainTree), mainConcurrency)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvp.Get(MainTree).Set(Duration, rootTs)\n\n\t// ctes\n\tctesConcurrency := newConcurrency()\n\t_, err = analyzeDurationNodes(vp.Get(CteTrees), Default, ctesConcurrency)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vp.MarshalJSON()\n}\n\n// analyzeDurationNode set node.duration.\nfunc analyzeDurationNode(node *simplejson.Json, concurrency concurrency) (time.Duration, error) {\n\t// get duration time\n\tts := node.GetPath(RootBasicExecInfo, Time).MustString()\n\n\t// cop task\n\tif ts == \"\" {\n\t\tts = getCopTaskDuration(node, concurrency)\n\t} else {\n\t\tts = getOperatorDuration(ts, concurrency)\n\t}\n\n\toperator := getOperatorType(node)\n\tduration, err := time.ParseDuration(ts)\n\tif err != nil {\n\t\tduration = 0\n\t}\n\t// get current_node concurrency\n\tconcurrency = getConcurrency(node, operator, concurrency)\n\n\tsubDuration, err := analyzeDurationNodes(node.Get(Children), operator, concurrency)\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tif duration < subDuration {\n\t\tduration = subDuration\n\t}\n\n\t// set\n\tnode.Set(Duration, duration.String())\n\n\treturn duration, nil\n}\n\n// analyzeDurationNodes return max(node.duration).\nfunc analyzeDurationNodes(nodes *simplejson.Json, operator operator, concurrency concurrency) (time.Duration, error) {\n\tlength := len(nodes.MustArray())\n\n\t// no children nodes\n\tif length == 0 {\n\t\treturn 0, nil\n\t}\n\tvar durations []time.Duration\n\n\tif operator == Apply {\n\t\tfor i := range length {\n\t\t\tn := nodes.GetIndex(i)\n\t\t\tif isBuildSide(n) {\n\t\t\t\tnewConcurrency := concurrency\n\t\t\t\tnewConcurrency.applyConcurrency = 1\n\t\t\t\td, err := analyzeDurationNode(n, newConcurrency)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\tdurations = append(durations, d)\n\n\t\t\t\t// get probe concurrency\n\t\t\t\tvar cacheHitRatio, actRows float64\n\t\t\t\trootGroupInfo := n.Get(RootGroupExecInfo)\n\t\t\t\tfor i := 0; i < len(rootGroupInfo.MustArray()); i++ {\n\t\t\t\t\tcacheHitRatioStr := strings.TrimRight(rootGroupInfo.GetIndex(i).Get(CacheHitRatio).MustString(), \"%\")\n\t\t\t\t\tif cacheHitRatioStr == \"\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tcacheHitRatio, err = strconv.ParseFloat(cacheHitRatioStr, 64)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn 0, err\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tactRowsStr := n.Get(ActRows).MustString()\n\t\t\t\tif actRowsStr == \"\" {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tactRows, err = strconv.ParseFloat(n.Get(ActRows).MustString(), 64)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\n\t\t\t\ttaskCount := int(actRows * (1 - cacheHitRatio/100))\n\t\t\t\ttaskCountAvg := taskCount / concurrency.joinConcurrency * concurrency.shuffleConcurrency * concurrency.tableConcurrency\n\t\t\t\tif taskCountAvg < concurrency.applyConcurrency {\n\t\t\t\t\tconcurrency.applyConcurrency = taskCountAvg\n\t\t\t\t}\n\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\n\t\tfor i := range length {\n\t\t\tn := nodes.GetIndex(i)\n\t\t\tif isProbeSide(n) {\n\t\t\t\td, err := analyzeDurationNode(n, concurrency)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn 0, err\n\t\t\t\t}\n\t\t\t\tdurations = append(durations, d)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t} else {\n\t\tfor i := range length {\n\t\t\tvar d time.Duration\n\t\t\tvar err error\n\t\t\tn := nodes.GetIndex(i)\n\n\t\t\tswitch operator {\n\t\t\tcase IndexJoin, IndexMergeJoin, IndexHashJoin:\n\t\t\t\tif isProbeSide(n) {\n\t\t\t\t\td, err = analyzeDurationNode(n, concurrency)\n\t\t\t\t} else {\n\t\t\t\t\t// build: set joinConcurrency == 1\n\t\t\t\t\tnewConcurrency := concurrency\n\t\t\t\t\tnewConcurrency.joinConcurrency = 1\n\t\t\t\t\td, err = analyzeDurationNode(n, newConcurrency)\n\t\t\t\t}\n\t\t\tcase IndexLookUpReader, IndexMergeReader:\n\t\t\t\tif isProbeSide(n) {\n\t\t\t\t\td, err = analyzeDurationNode(n, concurrency)\n\t\t\t\t} else {\n\t\t\t\t\t// build: set joinConcurrency == 1\n\t\t\t\t\tnewConcurrency := concurrency\n\t\t\t\t\tnewConcurrency.tableConcurrency = 1\n\t\t\t\t\td, err = analyzeDurationNode(n, newConcurrency)\n\t\t\t\t}\n\t\t\t// concurrency:  suffle -> StreamAgg/Window/MergeJoin ->  Sort -> ShuffleReceiver\n\t\t\tcase ShuffleReceiver:\n\t\t\t\tnewConcurrency := concurrency\n\t\t\t\tnewConcurrency.shuffleConcurrency = 1\n\t\t\t\td, err = analyzeDurationNode(n, newConcurrency)\n\t\t\tdefault:\n\t\t\t\td, err = analyzeDurationNode(n, concurrency)\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\treturn 0, err\n\t\t\t}\n\t\t\tdurations = append(durations, d)\n\t\t}\n\t}\n\n\t// get max duration\n\tsort.Slice(durations, func(p, q int) bool {\n\t\treturn durations[p] > durations[q]\n\t})\n\n\treturn durations[0], nil\n}\n\nfunc isProbeSide(node *simplejson.Json) bool {\n\treturn labelsContains(node, ProbeSide)\n}\n\nfunc isBuildSide(node *simplejson.Json) bool {\n\treturn labelsContains(node, BuildSide)\n}\n\nfunc labelsContains(node *simplejson.Json, label string) bool {\n\tlabels := node.Get(\"labels\")\n\tlength := len(labels.MustArray())\n\tif length == 0 {\n\t\treturn false\n\t}\n\n\tfor i := range length {\n\t\tif labels.GetIndex(i).MustString() == label {\n\t\t\treturn true\n\t\t}\n\t}\n\n\treturn false\n}\n\nfunc getOperatorType(node *simplejson.Json) operator {\n\toperator := node.Get(OperatorName).MustString()\n\n\tswitch {\n\tcase strings.HasPrefix(operator, \"IndexJoin\"):\n\t\treturn IndexJoin\n\tcase strings.HasPrefix(operator, \"IndexMergeJoin\"):\n\t\treturn IndexMergeJoin\n\tcase strings.HasPrefix(operator, \"IndexHashJoin\"):\n\t\treturn IndexHashJoin\n\tcase strings.HasPrefix(operator, \"Apply\"):\n\t\treturn Apply\n\tcase strings.HasPrefix(operator, \"Shuffle\") && !strings.Contains(operator, \"ShuffleReceiver\"):\n\t\treturn Shuffle\n\tcase strings.HasPrefix(operator, \"ShuffleReceiver\"):\n\t\treturn ShuffleReceiver\n\tcase strings.HasPrefix(operator, \"IndexLookUp\"):\n\t\treturn IndexLookUpReader\n\tcase strings.HasPrefix(operator, \"IndexMerge\"):\n\t\treturn IndexMergeReader\n\tcase strings.HasPrefix(operator, \"IndexFullScan\"):\n\t\treturn IndexFullScan\n\tcase strings.HasPrefix(operator, \"IndexRangeScan\"):\n\t\treturn IndexRangeScan\n\tcase strings.HasPrefix(operator, \"TableFullScan\"):\n\t\treturn TableFullScan\n\tcase strings.HasPrefix(operator, \"TableRangeScan\"):\n\t\treturn TableRangeScan\n\tcase strings.HasPrefix(operator, \"TableRowIDScan\"):\n\t\treturn TableRowIDScan\n\tcase strings.HasPrefix(operator, \"Selection\"):\n\t\treturn Selection\n\tdefault:\n\t\treturn Default\n\t}\n}\n\nfunc getBuildChildrenWithDriverSide(node *simplejson.Json) *simplejson.Json {\n\tnodes := node.Get(Children)\n\tlength := len(nodes.MustArray())\n\n\t// no children nodes\n\tif length == 0 {\n\t\treturn nil\n\t}\n\n\tfor i := range length {\n\t\tn := nodes.GetIndex(i)\n\t\tif isBuildSide(n) {\n\t\t\treturn n\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc getConcurrency(node *simplejson.Json, operator operator, concurrency concurrency) concurrency {\n\t// concurrency, copConcurrency\n\trootGroupInfo := node.Get(RootGroupExecInfo)\n\trootGroupInfoCount := len(rootGroupInfo.MustArray())\n\tif rootGroupInfoCount > 0 {\n\t\tfor i := range rootGroupInfoCount {\n\t\t\tswitch operator {\n\t\t\tcase IndexJoin, IndexMergeJoin, IndexHashJoin:\n\t\t\t\ttmpJoinConcurrencyStr := rootGroupInfo.GetIndex(i).GetPath(\"inner\", \"concurrency\").MustString()\n\t\t\t\ttmpJoinConcurrency, _ := strconv.Atoi(tmpJoinConcurrencyStr)\n\n\t\t\t\tjoinTaskCountStr := rootGroupInfo.GetIndex(i).GetPath(\"inner\", \"task\").MustString()\n\t\t\t\tjoinTaskCount, _ := strconv.Atoi(joinTaskCountStr)\n\t\t\t\tjoinTaskCountAvg := joinTaskCount / concurrency.applyConcurrency * concurrency.shuffleConcurrency * concurrency.tableConcurrency\n\t\t\t\t// task count as concurrency\n\t\t\t\tif joinTaskCountAvg < tmpJoinConcurrency {\n\t\t\t\t\ttmpJoinConcurrency = joinTaskCountAvg\n\t\t\t\t}\n\n\t\t\t\tif tmpJoinConcurrency > 0 {\n\t\t\t\t\tconcurrency.joinConcurrency = tmpJoinConcurrency * concurrency.joinConcurrency\n\t\t\t\t}\n\n\t\t\tcase Apply:\n\t\t\t\ttmpApplyConcurrencyStr := rootGroupInfo.GetIndex(i).GetPath(\"Concurrency\").MustString()\n\t\t\t\ttmpApplyConcurrency, _ := strconv.Atoi(tmpApplyConcurrencyStr)\n\t\t\t\tif tmpApplyConcurrency > 0 {\n\t\t\t\t\tconcurrency.applyConcurrency = tmpApplyConcurrency * concurrency.applyConcurrency\n\t\t\t\t}\n\n\t\t\tcase IndexLookUpReader, IndexMergeReader:\n\t\t\t\ttmpTableConcurrencyStr := rootGroupInfo.GetIndex(i).GetPath(\"table_task\", \"concurrency\").MustString()\n\t\t\t\ttmpTableConcurrency, _ := strconv.Atoi(tmpTableConcurrencyStr)\n\n\t\t\t\ttableTaskNumStr := rootGroupInfo.GetIndex(i).GetPath(\"table_task\", \"num\").MustString()\n\t\t\t\ttableTaskNum, _ := strconv.Atoi(tableTaskNumStr)\n\t\t\t\ttableTaskNumAvg := tableTaskNum / concurrency.joinConcurrency * concurrency.applyConcurrency * concurrency.shuffleConcurrency\n\t\t\t\tif tableTaskNumAvg < tmpTableConcurrency {\n\t\t\t\t\ttmpTableConcurrency = tableTaskNumAvg\n\t\t\t\t}\n\n\t\t\t\tif tmpTableConcurrency > 0 {\n\t\t\t\t\tconcurrency.tableConcurrency = tmpTableConcurrency * concurrency.copConcurrency\n\t\t\t\t}\n\n\t\t\tcase Shuffle:\n\t\t\t\ttmpShuffleConcurrencyStr := rootGroupInfo.GetIndex(i).Get(\"ShuffleConcurrency\").MustString()\n\t\t\t\ttmpShuffleConcurrency, _ := strconv.Atoi(tmpShuffleConcurrencyStr)\n\n\t\t\t\tif tmpShuffleConcurrency > 0 {\n\t\t\t\t\tconcurrency.shuffleConcurrency = tmpShuffleConcurrency * concurrency.shuffleConcurrency\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttmpCopConcurrencyStr := rootGroupInfo.GetIndex(i).GetPath(\"cop_task\", \"distsql_concurrency\").MustString()\n\t\t\ttmpCopConcurrency, _ := strconv.Atoi(tmpCopConcurrencyStr)\n\t\t\tif tmpCopConcurrency > 0 {\n\t\t\t\tconcurrency.copConcurrency = tmpCopConcurrency * concurrency.copConcurrency\n\t\t\t}\n\t\t}\n\t}\n\n\treturn concurrency\n}\n\nfunc getCopTaskDuration(node *simplejson.Json, concurrency concurrency) string {\n\tstoreType := node.GetPath(StoreType).MustString()\n\t// task == 1\n\tts := node.GetPath(CopExecInfo, fmt.Sprintf(\"%s_task\", storeType), \"time\").MustString()\n\tif ts == \"\" {\n\t\tswitch node.GetPath(TaskType).MustString() {\n\t\tcase \"cop\":\n\t\t\t// cop task count\n\t\t\ttaskCountStr := node.GetPath(CopExecInfo, fmt.Sprintf(\"%s_task\", storeType), \"tasks\").MustString()\n\t\t\ttaskCount, _ := strconv.Atoi(taskCountStr)\n\t\t\tmaxTS := node.GetPath(CopExecInfo, fmt.Sprintf(\"%s_task\", storeType), \"proc max\").MustString()\n\t\t\tmaxDuration, err := time.ParseDuration(maxTS)\n\t\t\tif err != nil {\n\t\t\t\tts = maxTS\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tavgTS := node.GetPath(CopExecInfo, fmt.Sprintf(\"%s_task\", storeType), \"avg\").MustString()\n\t\t\tavgDuration, err := time.ParseDuration(avgTS)\n\t\t\tif err != nil {\n\t\t\t\tts = maxTS\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\tvar tsDuration time.Duration\n\t\t\tn := float64(taskCount) / float64(\n\t\t\t\tconcurrency.joinConcurrency*concurrency.tableConcurrency*concurrency.applyConcurrency*concurrency.shuffleConcurrency)\n\n\t\t\tif n > float64(concurrency.copConcurrency) {\n\t\t\t\ttsDuration = time.Duration(float64(avgDuration) * n / float64(concurrency.copConcurrency))\n\t\t\t} else {\n\t\t\t\ttsDuration = time.Duration(float64(avgDuration) * n)\n\t\t\t}\n\n\t\t\tif tsDuration < maxDuration {\n\t\t\t\tts = maxTS\n\t\t\t} else {\n\t\t\t\tts = tsDuration.String()\n\t\t\t}\n\n\t\t// tiflash\n\t\tcase \"batchCop\", \"mpp\":\n\t\t\tts = node.GetPath(CopExecInfo, fmt.Sprintf(\"%s_task\", storeType), \"proc max\").MustString()\n\t\tdefault:\n\t\t\tts = \"0s\"\n\t\t}\n\t}\n\n\treturn ts\n}\n\nfunc getOperatorDuration(ts string, concurrency concurrency) string {\n\tt, err := time.ParseDuration(ts)\n\tif err != nil {\n\t\treturn \"0s\"\n\t}\n\n\treturn time.Duration(float64(t) /\n\t\tfloat64(concurrency.joinConcurrency*concurrency.tableConcurrency*concurrency.applyConcurrency*concurrency.shuffleConcurrency)).\n\t\tString()\n}\n\nfunc formatBinaryPlanJSON(bp []byte) ([]byte, error) {\n\t// new simple json\n\tvp, err := simplejson.NewJson(bp)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif vp.Get(DiscardedDueToTooLong).MustBool() {\n\t\treturn vp.MarshalJSON()\n\t}\n\tvp.Set(DiscardedDueToTooLong, false)\n\n\t// main\n\terr = formatNode(vp.Get(MainTree))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\t// ctes\n\terr = formatChildrenNodes(vp.Get(CteTrees))\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn vp.MarshalJSON()\n}\n\n// formatNode\n// format diskBytes memoryByte to string\n// format rootBasicExecInfo rootGroupExecInfo copExecInfo field to json\n// for example:\n// {\"copExecInfo\" : \"tikv_task:{time:0s, loops:1}, scan_detail: {total_process_keys: 8, total_process_keys_size: 360, total_keys: 9, rocksdb: {delete_skipped_count: 0, key_skipped_count: 8, block: {cache_hit_count: 1, read_count: 0, read_byte: 0 Bytes}}}\"}.\nfunc formatNode(node *simplejson.Json) error {\n\tvar err error\n\tfor _, key := range needSetNA {\n\t\tif node.Get(key).MustString() == \"-1\" || node.Get(key).MustString() == \"\" {\n\t\t\tnode.Set(key, \"N/A\")\n\t\t}\n\t}\n\n\tif len(node.Get(\"labels\").MustArray()) == 0 {\n\t\tnode.Set(\"labels\", []string{})\n\t}\n\n\tif len(node.Get(AccessObjects).MustArray()) == 0 {\n\t\tnode.Set(AccessObjects, []interface{}{})\n\t}\n\n\t// actRows string -> uint64\n\tactRows, err := strconv.ParseUint(node.Get(ActRows).MustString(), 10, 64)\n\tif err != nil {\n\t\tactRows = 0\n\t}\n\tnode.Set(ActRows, float64(actRows))\n\n\tif node.Get(EstRows).MustFloat64() == 0 {\n\t\tnode.Set(EstRows, 0)\n\t}\n\n\tfor _, key := range needJSONFormat {\n\t\tif key == RootGroupExecInfo {\n\t\t\tslist := node.Get(key).MustStringArray()\n\t\t\tnewSlist := []interface{}{}\n\t\t\tfor _, s := range slist {\n\t\t\t\tsJSON, err := formatJSON(s)\n\t\t\t\tif err != nil {\n\t\t\t\t\tnewSlist = append(newSlist, s)\n\t\t\t\t}\n\t\t\t\tnewSlist = append(newSlist, sJSON)\n\t\t\t}\n\t\t\tnode.Set(key, newSlist)\n\t\t} else {\n\t\t\ts := node.Get(key).MustString()\n\t\t\tsJSON, err := formatJSON(s)\n\t\t\tif err != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tnode.Set(key, sJSON)\n\t\t}\n\t}\n\n\tc := node.Get(Children)\n\terr = formatChildrenNodes(c)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc formatChildrenNodes(nodes *simplejson.Json) error {\n\tlength := len(nodes.MustArray())\n\n\t// no children nodes\n\tif length == 0 {\n\t\treturn nil\n\t}\n\n\tfor i := range length {\n\t\tc := nodes.GetIndex(i)\n\t\terr := formatNode(c)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\treturn nil\n}\n\nfunc formatJSON(s string) (*simplejson.Json, error) {\n\ts = `{` + s + `}`\n\ts = strings.ReplaceAll(s, \"{\", `{\"`)\n\ts = strings.ReplaceAll(s, \"}\", `\"}`)\n\ts = strings.ReplaceAll(s, \":\", `\":\"`)\n\ts = strings.ReplaceAll(s, \",\", `\",\"`)\n\ts = strings.ReplaceAll(s, `\" `, `\"`)\n\ts = strings.ReplaceAll(s, `}\"`, `}`)\n\ts = strings.ReplaceAll(s, `\"{`, `{`)\n\ts = strings.ReplaceAll(s, `{\"\"}`, \"{}\")\n\n\treturn simplejson.NewJson([]byte(s))\n}\n\n/////////////////\n\nfunc GenerateBinaryPlanText(db *gorm.DB, b string) (string, error) {\n\ttype binaryPlanText struct {\n\t\tText string `gorm:\"column:binary_plan_text\"`\n\t}\n\tret := &binaryPlanText{}\n\terr := db.Raw(fmt.Sprintf(\"select tidb_decode_binary_plan('%s') as binary_plan_text\", b)).Find(ret).Error\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn ret.Text, err\n}\n"
  },
  {
    "path": "pkg/apiserver/utils/binary_plan_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/bitly/go-simplejson\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nvar bpTestStr = \"SiwKRgoGU2hvd18yKQAFAYjwPzAFOAFAAWoVdGltZTozNC44wrVzLCBsb29wczoygAH//w0COAGIAf///////////wEYAQ==\"\n\nfunc TestGenerateBinaryPlan(t *testing.T) {\n\t_, err := GenerateBinaryPlan(bpTestStr)\n\tif err != nil {\n\t\tt.Fatalf(\"generate Visual plan failed: %v\", err)\n\t}\n}\n\nfunc TestGenerateBinaryPlanJson(t *testing.T) {\n\t_, err := GenerateBinaryPlanJSON(bpTestStr)\n\tif err != nil {\n\t\tt.Fatalf(\"generate Visual plan failed: %v\", err)\n\t}\n}\n\nfunc TestUseComparisonOperator(t *testing.T) {\n\tassert.True(t, useComparisonOperator(\"eq(test.t.a, 1)\"))\n\tassert.False(t, useComparisonOperator(\"eq(minus(test.t1.b, 1), 1)\"))\n\tassert.True(t, useComparisonOperator(\"eq(test.t.a, 1), eq(test.t.a, 2)\"))\n\tassert.False(t, useComparisonOperator(\"eq(test.t.a, 1), eq(test.t.b, 1)\"))\n\tassert.True(t, useComparisonOperator(\"in(test.t.a, 1, 2, 3, 4)\"))\n\tassert.False(t, useComparisonOperator(\"in(test.t.a, 1, 2, 3, 4), in(test.t.b, 1, 2, 3, 4)\"))\n\tassert.False(t, useComparisonOperator(\"in(test.t.a, 1, 2, 3, 4, test.t.b)\"))\n\tassert.True(t, useComparisonOperator(\"isnull(test2.t1.a)\"))\n\tassert.True(t, useComparisonOperator(\"not(isnull(test2.t1.a))\"))\n\tassert.False(t, useComparisonOperator(\"eq(test2.t1.a, test2.t2.a)\"))\n\tassert.True(t, useComparisonOperator(\"eq(1, test2.t2.a)\"))\n\tassert.False(t, useComparisonOperator(\"in(test.t.a, 1, 2, test.t.b, 4)\"))\n\tassert.False(t, useComparisonOperator(\"in(test.t.a, 1, 2, 3, 4), eq(1, test2.t2.a), eq(test.t.a, 1), eq(test.t.a, 2), isnull(test2.t1.a)\"))\n\tassert.True(t, useComparisonOperator(\"in(test.t.a, 1, 2, 3, 4), eq(1, test.t.a), eq(test.t.a, 1), eq(test.t.a, 2), isnull(test.t.a)\"))\n\tassert.True(t, useComparisonOperator(\"not(isnull(test2.table1.a))\"))\n\tassert.False(t, useComparisonOperator(`eq(information_schema.cluster_slow_query.conn_id, 4842609848539415539), eq(information_schema.cluster_slow_query.digest, \"6e88a1c8f7bfb008e6ead01c1ad9e47922c145e95a3a037381ac8a088e25e60b\")`))\n\tassert.True(t, useComparisonOperator(\"lt(imdbload.movie_info.movie_id, 1000)\"))\n}\n\nfunc TestFormatJSON(t *testing.T) {\n\t_, err := formatJSON(`tikv_task:{time:0s, loops:1}, scan_detail: {total_process_keys: 8, total_process_keys_size: 360, total_keys: 9, rocksdb: {delete_skipped_count: 0, key_skipped_count: 8, block: {cache_hit_count: 1, read_count: 0, read_byte: 0 Bytes}}}`)\n\n\tassert.Nil(t, err)\n}\n\nfunc TestTooLong(t *testing.T) {\n\tbp := \"AgQgAQ==\"\n\tvp, err := GenerateBinaryPlanJSON(bp)\n\tassert.Nil(t, err)\n\tvpJSON, err := simplejson.NewJson([]byte(vp))\n\tassert.Nil(t, err)\n\n\tassert.True(t, vpJSON.Get(\"discardedDueToTooLong\").MustBool())\n}\n\nfunc TestBinaryPlanIsNil(t *testing.T) {\n\tvp, err := GenerateBinaryPlanJSON(\"\")\n\tassert.Nil(t, err)\n\tassert.Len(t, vp, 0)\n}\n\nfunc TestHighEstError(t *testing.T) {\n\tvp, err := GenerateBinaryPlanJSON(\"1gmQCtEJCgdMaW1pdF84Eu0ICg5UYWJsZVJlYWRlcl8xMxKpBgoITAUfXDEyEpsDCgxTZWxlY3Rpb25fMTESxwEKEAUx8IZGdWxsU2Nhbl8xMCHw694zF1ZHQSlZHdfaNxuEQDDTuA44AkACShUKEwoIaW1kYmxvYWQSB2tleXdvcmRSEGtlZXAgb3JkZXI6ZmFsc2VqWnRpa3ZfdGFzazp7cHJvYyBtYXg6MjAxbXMsIG1pbjowcywgYXZnOiAxMDAuNW1zLCBwODA6MjAFIRRwOTU6MjAFC1BpdGVyczoyMzcsIHRhc2tzOjJ9cP8RAQQBeBEKLP8BIUc/mTzKe0dBKQUUCD9/QAGxJFI7Z3QoY2FzdCgRtQAuDbSQLnBob25ldGljX2NvZGUsIGRvdWJsZSBCSU5BUlkpLCAyMClqWFLFABA2Mm1zLDrFAAAzBa0AcAHDCR8FwwkL/sMAWBNvZmZzZXQ6MCwgY291bnQ6NTAwar0CVpwAADNOnAAELjUBFgmeBSEJngULSp4AoCwgc2Nhbl9kZXRhaWw6IHt0b3RhbF9wcm9jZXNzX2tleXM6IDIzNjYyIZVCHABAX3NpemU6IDE0MzIzNTkxLCAJIx03bDksIHJvY2tzZGI6IHtkZWxldGVfc2tpcHBlZF8J4hggMCwga2V5PhYAADINdUBibG9jazoge2NhY2hlX2hpdBE3HDI3OCwgcmVhLkgABQ84Ynl0ZTogMCBCeXRlc319bkQCDHcjSEFZRCgBQAFSDWRhdGE6TG1ELFoVdGltZToyNzIuNyFXNGxvb3BzOjFi5AFjb3BfQaUkOiB7bnVtOiAyLEX2DCAyNjkpgiBtaW46IDIuODQBOCBhdmc6IDEzNi5FTwhwOTUuKQAIYXhfIXA5Ngw1OTYsASVOFwAIdG90BRcBQQWTAREUd2FpdDogJeMMcnBjXxGRAQwFwAAgAcEFexBjb3ByXyVLEDogZGlzgT4EZCwBClh0c3FsX2NvbmN1cnJlbmN5OiAxfXCFND51AyUxAAABAQhAf0AlMU6yAlo3AVbNAwQYAQ==\")\n\tassert.Nil(t, err)\n\tassert.True(t, strings.Contains(vp, HighEstError))\n}\n\nfunc TestDiskSpill(t *testing.T) {\n\tvp, err := GenerateBinaryPlanJSON(\"6Q5ICuQOCglIYXNoQWdnXzgS8w0KCgEOdEpvaW5fORL6BQoOSW5kZXhSZWFkZXJfMTQSrQMKEAUTUEZ1bGxTY2FuXzEzIQAAAADndopBKQEJ8HmAhC5BMIAOOAJAAkonCiUKBHRlc3QSAnQxGhkKB1BSSU1BUlkSBnNfd19pZBIGc19pX2lkUhBrZWVwIG9yZGVyOmZhbHNlaq4CdGlrdl90YXNrOntwcm9jIG1heDoxbXMsIG1pbjowcywgYXZnOiA3NTDCtXMsIHA4MAkeCHA5NQkJUGl0ZXJzOjE0LCB0YXNrczo0fSwgcwGyiGRldGFpbDoge3RvdGFsX3Byb2Nlc3Nfa2V5czogMTc5MiwgRhoALF9zaXplOiA5NDk3NhEgDTK4ODEwLCByb2Nrc2RiOiB7ZGVsZXRlX3NraXBwZWRfY291bnQ6IDMyODgsIGtleV86GQBYNTE1MCwgYmxvY2s6IHtjYWNoZV9oaXQROBgxNywgcmVhFUgBawEPRGJ5dGU6IDAgQnl0ZXN9fX1w/xEBBAF4EQo0/wEaAQIhq6qqqjk/UkE9nhwIOAFAAVIWaSHcADo+zwFoWhN0aW1lOjcuMm1zLCBsb29wczoxYt8BY29wKY8kIHtudW06IDQsICGTECAyLjMzASogbWluOiAxLjM3AQ0lnAwxLjc5AQ0hkxknBGF4JXMtPgA5IW8IcDk1QhQACHRvdAUUBDogBV4BDxh3YWl0OiAxAVMMcnBjXxGGAQwFsxAgNy4wNQEeEGNvcHJfOUugcmF0aW86IDAuMDAsIGRpc3RzcWxfY29uY3VycmVuY3k6IDE1fXCShxM9OggSjgY6/QIIOBK1Rv0CADdF/Qhtc5A9XwjAhD1C/gIAMrb+Agi1AnRO/gIAOAHdIWhZ/wQuNQXyDHA4MDo1bAAxJTJxAAQzN3ECCDExNZIEAwwzODA4UeQuHgNtBBAyMDE4MmFXZT9pBQgzODZBmnoFAwQ0MWErSgUDEDgwMzUsZgUDBDIy9gUDIAEhVVVVVRq/VzqmAWYGAww3WhZ0YQYIMTcuRXBpBxA5ODJi5kIJAwwxMTUsaQsIMy4wBS4hoxQgNDEyLjSFnGUNEDYyOC4yBQ9lDwgxLjMpzjYPAxAyMDE2LAEiJXktXQAwJY0FEgg6IDVBBHkOZW5hAmUOBYsBDgW9DCA3MC4FL14QAwQ5NmoQAwztgPICfREgIYlXpqxvjrhCgUggopQabUIwgJB6JUNQFENBUlRFU0lBTiBpbm5lciBqb2luLUEQNTQuNnOVR1g5NTRihAFidWlsZF9oYXNoX3RhYmxlOqmKEDozNjAuRcUcZmV0Y2g6MjEJvgUvRDozMzkuM21zfSwgcHJvYmU6ey7BAwAxJSIQYWw6NTXBIQRheAkJCSoUNTQuN3MsDVUBZFQ4bXN9cOT51wd4gOy4CyHlutab7Ui7BdkQAABC+EAF1SQ1Z3JvdXAgYnk6wcFQLnN0b2NrLnNfaV9pZCwgZnVuY3M6pds8KDEpLT5Db2x1bW4jMzdaESV6BYcsbG9vcHM6MXD0URgB\")\n\tassert.Nil(t, err)\n\tassert.True(t, strings.Contains(vp, DiskSpill))\n}\n\nfunc TestGoodFilterOnTableFullScan(t *testing.T) {\n\tvp, err := GenerateBinaryPlanJSON(\"zQeYCsgHCg1UYWJsZVJlYWRlcl83Ev8ECgtTZWxlY3Rpb25fNhLLAQoPBSLwfUZ1bGxTY2FuXzUhAADA0p/kTUIpAAAAgEhlfEEwiKmZDjgCQAJKGAoWCghpbWRibG9hZBIKbW92aWVfaW5mb1IQa2VlcCBvcmRlcjpmYWxzZWpbdGlrdl90YXNrOntwcm9jIG1heDo2NDVtcywgbWluOjBzLCBhdmc6IDQyMwEUGHA4MDo0ODYFCxA5NTo1NQULXGl0ZXJzOjI5MzY0LCB0YXNrczo2Nn1w/xEBBAF4EQpY/wEhAAC4xj7/TUIp3lAMtdp/qUAw7Q8BuBBSJmx0KBG3AC4ZthELKGQsIDEwMDApasoCUrUABDcyAZY2tQAMNDEuNwEWAbcENTEFIQW3BDg4ARZWtwCoLCBzY2FuX2RldGFpbDoge3RvdGFsX3Byb2Nlc3Nfa2V5czogMjk3NzQ5OAHuQh4ASF9zaXplOiAyNjU1OTgyODQ4LCAJJRk7sDUwNTAsIHJvY2tzZGI6IHtkZWxldGVfc2tpcHBlZF9jb3VudDogMCwga2V5XzoWAGgzMTUwMzIzMCwgYmxvY2s6IHtjYWNoZV9oaXQROSQ0NzgzNiwgcmVhLkwABQ84Ynl0ZTogMCBCeXRlc319XqQBGF15cUA6/w82pAEkAUABUhBkYXRhOl2dbFoTdGltZToyLjQxcywgbG9vcHM6M2LlAWNvcF9BCig6IHtudW06IDY2LEVcFCA3MDMuNiF+IaoQIDEuMTEBDUlkBDU3La8EOTUBMgg4LjQBHAhtYXglfy1BGDcyMjA3NCwhyS4XABA1NjQ0NimRBRcIOiAzQb0wdG90X3dhaXQ6IDE3MAFODHJwY18VkgENBcAwIDMwLjJzLCBjb3ByXyVREDogZGlzYZcEZCwBCpR0c3FsX2NvbmN1cnJlbmN5OiAxNX1w/r0DeP///////////wEYAQ==\")\n\tassert.Nil(t, err)\n\tassert.True(t, strings.Contains(vp, GoodFilterOnTableFullScan))\n}\n\nfunc TestBadIndexForIndexLookUp(t *testing.T) {\n\tvp, err := GenerateBinaryPlanJSON(\"uA6YCrMOCg1JbmRleExvb2tVcF84EpYHCgtTZWxlY3Rpb25fNxLqAQoPBSLwQEZ1bGxTY2FuXzUhAACAxaCT8kEpAAAAABEGNEEwkYxQOAJAAko4CjYKCGltZGJsb2FkEghha2FfbmFtZRogChNhDQwwX2lkeF9wZXJzb24SCQkI8FhfaWRSEGtlZXAgb3JkZXI6ZmFsc2VqW3Rpa3ZfdGFzazp7cHJvYyBtYXg6NTA1bXMsIG1pbjowcywgYXZnOiAyMzguN21zLCBwODA6NTA1bXMsIHA5NTo1MAUsLGl0ZXJzOjEyOTIsIAFOFHM6M31w/xEBBAF4EQo8/wEaAQEpZ2ZmZtoEMEEwignTKFIsZ3QocGx1cyhpDdcELmENygQucAXGXF9pZCwgMSksIDEwKVoYdGltZToyNTUuNgGSDGxvb3ABfBw4NWLlAWNvcAnPJCB7bnVtOiAzLCAB0xQgNjI2LjcBLhxtaW46IDQuMAUNAGEF2ww5NC45ARsB0B0pUGF4X3Byb2Nfa2V5czogOTYwMDAwLAElThcACHRvdAUXEDogODQyAU0BERh3YWl0OiAzAQ8McnBjXxGRAQwFwwwgODg0CZZQY29wcl9jYWNoZTogZGlzYWJsZWQsAQpcdHNxbF9jb25jdXJyZW5jeTogMTV9ar8CUrgBADQljAHiADA1uAA1BekMcDgwOg0fAeYNC062AVAsIHNjYW5fZGV0YWlsOiB7dG90YWwF2whlc3MtDBwxMzEyMjczLAHlOh0AOF9zaXplOiA2MDM2NDU1OBEjKUQJOKA2LCByb2Nrc2RiOiB7ZGVsZXRlX3NraXBwZWRfY291bnQ6IDAsIGtleT4WAAk/KDMsIGJsb2NrOiB7JTEMX2hpdBE4HDkzMiwgcmVhLkkABQ84Ynl0ZTogMCBCeXRlc319WpwCFBK3BQoQVCF9EFJvd0lEZYoMNhoBAnmNProCEEoWChQKSo0DRmsDBFoVJfMQOC45cyxRswg0MjhKswIIMTU5TbUQMjQxLjFBPiHTCCAxLingRbUEODJJOSHOECAxOTEuRWY6tAIQMjAwMDNCswIQMTcyMjAlmSXXGDogMTEuN3MJEUmyADJFwkGnRbMFkQEOBcIYIDEzLjFzLL6zAgDGUrMCBDE5RZ8B4FWzBDY3adgUcDgwOjEwdZoIMTU2IQtNtBA5OTUsIIlqCDE1Oaa2AgQ2NgXpVrYCHDE0NjkxOTM4PrcCEDU4ODU0QVrStwIUOTIxMTU1arYCGDM4MTYxOTHuugIgIT4xsa60gddBNqICEAFAAVoWJbYQOS44NXNVeRgyODNijAFpwZWxLmnoBSxgIDkuNjhzLCBmZXRjaF9oYW5kbGU6IDM1NCmYOGJ1aWxkOiAzNjPCtXMsIEkzFDkuMzNzfSGTCGJsZVZUAAA1QfpFUQQ2OEE/LtQESH1wi/2MCHj///////////8BGAE=\")\n\tassert.Nil(t, err)\n\tassert.True(t, strings.Contains(vp, BadIndexForIndexLookUp))\n}\n\nfunc TestIndexJoinBuildSideTooLarge(t *testing.T) {\n\tvp, err := GenerateBinaryPlanJSON(\"tQ+gCrAPCgxJbmRleEpvaW5fMTISjAYKDlRhYmxlUmVhZGVyXzE4ErUDChAFE/SoAUZ1bGxTY2FuXzE3IQAAwNKf5E1CKQAAAIBIZXxBMIipmQ44AkACShAKDgoIaW1kYmxvYWQSAm1pUhBrZWVwIG9yZGVyOmZhbHNlassCdGlrdl90YXNrOntwcm9jIG1heDo2MzVtcywgbWluOjJtcywgYXZnOiA0MzguNG1zLCBwODA6NDk0bXMsIHA5NTo1OTRtcywgaXRlcnM6MjkzNjQsIHRhc2tzOjY2fSwgc2Nhbl9kZXRhaWw6IHt0b3RhbF9wcm9jZXNzX2tleXM6IDI5Nzc0OTg0LCB0b3RhbF9wcm9jZXNzX2tleXNfc2l6ZTogMjY1NTk4Mjg0OCwgdG90YWxfa2V5czogMjk3NzUwNTAsIHJvY2tzZGI6IHtkZWxldGVfc2tpcHBlZF9jb3VudDogMCwga2V5X3NraXBwZWRfY291bnQ6IDMxNTAzMjMwLCBibG9jazoge2NhY2hlX2hpdF9jb3VudDogNDc4MzYsIHJlYWRfY291bnQ6IDAsIHJlYWRfYnl0ZTogMCBCeXRlc319fXD///////////8BeP//////AQ0sARoBASGpOPsATIYQPqYBJAFAAVIVZGF0YTo+2AEsWhl0aW1lOjc0Ni45IV4UbG9vcHM6JV5UYuABY29wX3Rhc2s6IHtudW06IDY2LCW4FCAxLjQycym5ECA1LjQzATsAYSG9BDcwKbsEOTUBJQQzNgElCGF4XyFrJWgwOiA3MjIwNzQsIHA5NS4XACg1NjQ0NjQsIHRvdAUXGDogMzcuOXMJEQx3YWl0AToBZwxycGNfFY0BDQXBWCA0Ni41cywgY29wcl9jYWNoZTogZGlzQdkEZCwBCmx0c3FsX2NvbmN1cnJlbmN5OiAxNX1wl4a/igR4IT8FARQBEqoGCg0lJG0PDDkSzwNtDhBSYW5nZWUPADhhBSQAAADwPzCP9gI4SgQDEGl0UkZyATBAOiBkZWNpZGVkIGJ5IFtpbWRlJSwubW92aWVfaW5mby4BBSBfdHlwZV9pZF1BPQhlcCAuOgMEuQJOOgMEMW0hWgxpbjowcTcMNjPCtWEqBDgwBRQhggUlAGltMAg1NjFxMCHfBDN9jjMDEDQ3ODg3JYQEYWwllwhlc3MlyAhfc2llMBQyNDcyMTIRIgBrZWgoNTI4NDksIHJvY2vGKgMYMjA5NzMsIGInAxgyODQzOTcsaRkcY291bnQ6IDANDxhieXRlOiAwgigDJAIhpOLsw2cYb0A2yQE6JwM98QRaF0VmFDM4LjFzLG0lGDU4OTIwYuNCJQMlXm0oEDc3NC43MasUIDQ0MC42JaYAYWEsCDEuMmU5IasAICWsOisDBDQzQicDADMlayWLEDogMi41QfRhM2kiCDIuOWFyYRZlIxQ1ODcyNSwFEAXAECAxbTEwzigDZR4BAQABfS0gIU4beDyqUxhCIUYAgNkUIUc0tAFpbm5lciBqb2luLCAFDIl8cVgsLCBvdXRlciBrZXk6fhcDDUE2LAAIaW5maThILmlkLCBlcXVhbCBjb25kOmVxKIpRAHGKLkcACClaGCUnBDI3PeccOTMwNDcwYmgJxAB7xRYQOjU5LjSFbplQBDMwaV9pXoGKKG5zdHJ1Y3Q6MTkuoS0cZmV0Y2g6NDAFXKxidWlsZDo4OS40bXN9LCBwcm9iZToyMS41c3CVm6wMeP///////////wEYAQ==\")\n\tassert.Nil(t, err)\n\tassert.True(t, strings.Contains(vp, IndexJoinBuildSideTooLarge))\n}\n\nfunc TestPseudoEst(t *testing.T) {\n\tvp, err := GenerateBinaryPlanJSON(\"nhHwdQqABQoLSGFzaEpvaW5fMjUSugEKDFNlbGVjdGlvbl8yNhJPCg5DVEVGdWxsU2Nhbl8yNykAAAAAAAAQQDAJOAFAAUoKGghDVEU6Y3RlMVIKZGF0YTpDVEVfMFoTdGltZTo2LjJtcywgbG9vcHM6M3DwCxoBASEFQRTQVUApmpkBAQAJDUt4Uhtub3QoaXNudWxsKHRlc3QyLnRhYmxlMS5hKSlaE0ZQABQycPgFeP8RAQgBErw2vQAIOBJQOr0AADkJvRRAv0AwAjg2vQAIMlIKFb0IMVoUDW0MMTZtcza+ACQCIVZVVVVllQBBCUwEALkNTF6+ABAyLmMpKRlRADgZUTq/ABghEQ7jWw+ZFVkMEEAwBgGlWFI2aW5uZXIgam9pbiwgZXF1YWw6W2VxOiIBBCwgPTIBdABdDXQMMTkuMx10NGKKAWJ1aWxkX2hhc2hfJWAoOnt0b3RhbDoxOC4FniRmZXRjaDo2LjI5ATgFLnw6MTIuNW1zfSwgcHJvYmU6e2NvbmN1cnJlbmN5OjUsIAlBBDk0ASUQLCBtYXgBdwAxAT8JMCAzMjIuNMK1cywNXUA5NC4ybXN9cNSNARKJBgoFQ0EzCBLFBS59AhwxMxLYBAoOVEEDNFJlYWRlcl8xNhLhAgoQBRMARlGTCDE1IUVRCHh+QC2TABRBUSACQAJKEQoPCgUlGAQSBgXruDFSHmtlZXAgb3JkZXI6ZmFsc2UsIHN0YXRzOnBzZXVkb2rrAXRpa3ZfdGFzazp7RQYFxwRsb0HBEDF9LCBzYQccZGV0YWlsOiApOjxfcHJvY2Vzc19rZXlzOiA5LRAyFwAkX3NpemU6IDQ3NxEeCS2oMTAsIHJvY2tzZGI6IHtkZWxldGVfc2tpcHBlZF9jb3VudDogMCwga2V5XzoWAEw5LCBibG9jazoge2NhY2hlX2hpdB0yCHJlYS5BAAUPQGJ5dGU6IDAgQnl0ZXN9fX1wcTwE/wF9SClPBKBDMk8BEAFAAVIVZSI+fgFRaAQuMl1oHDNijgFjb3BfJUgcIHtudW06IDFJGgwgMS40TRoAYy0FKTIlMAQ6ICVwDHJwY18RNQEMJYoMIDEuM2WREGNvcHJfGfVQcmF0aW86IDAuMDAsIGRpc3RzcWxfLqICGCAxNX1wmwMd5AgaAQMJ5wTQUhHnABBBNgHncocElRplHUkXADI2iARNhhVQRBFOb24tUmVjdXJzaXZlIENURU7NBKEdBBKKbQwAMT4MAwA4QgwDDDIxEuBCDAMEMjAF1ggwwS2NnwSIw4GfUgwDADKCDAMA6u4MA2kMADJR7i4jA20MCDEwNhEeaQwAM94LAwAy/gsDYgsDGKuqqqqqg/JNJClOQSROCwMEMjBNHgAxrTtJHggzYo9qCwNJ0SEeLQUpMiUwfgwDZXDSDAMEqAI6DAMB6AjKTvsN6PUhXgwDwWHd1WXQCe5SDAMZUEoMA3VSZdssbG9vcHM6M3DwCxgB\")\n\tassert.Nil(t, err)\n\tassert.True(t, strings.Contains(vp, PseudoEst))\n}\n\nfunc TestTiKVHugeTableScan(t *testing.T) {\n\tvp, err := GenerateBinaryPlanJSON(\"2hTwWArVFAoNUHJvamVjdGlvbl8xOBLAEgoIQXBwbHlfMjASvQUKCExpbWl0XzIzEtUECg5UYWJsZVJlYWRlcl8zMRKkAwoRRXhjaGFuZ2VTZW5kZXJfMzASlAIKETYUORKVAQoQBTZQRnVsbFNjYW5fMjghAAAAAIA17EApAQnwbQAAEEAwgIAEOARAA0oOCgwKBHRwY2gSBHBhcnRSEGtlZXAgb3JkZXI6ZmFsc2VqL3RpZmxhc2hfdGFzazp7dGltZTo0NTguMW1zLCBsb29wczoxLCB0aHJlYWRzOjF9cP///////////wF4////CQwAAUqDAAWBTFIRb2Zmc2V0OjAsIGNvdW50OjRq/nIAenIAABkxQkBUeXBlOiBQYXNzVGhyb3VnaP56ABF6GKuqqqqq3qI9bywEOAFAAVIWZGF0YToRfwBTMcEIWhV0JVUUOTEuM21zNVUQYj9jb3ApdnQge251bTogMSwgbWF4OiAwcywgcHJvY19rZXlzOiAlO0hwcl9jYWNoZTogZGlzYWJsZWR9VooBCBoBAWKhAEaNAVacAAAzVlsAHBKFDAoMU2VsTd4UMzISowsKTZtUMzUSvgoKDFN0cmVhbUFnZ181MhLPCTbiAhA1MxL8Bi4kABA0MRKqAy5TAAw1MRLPQuECKDUwIQCUrdq20pRCQeFIs7nhsUEwzM2bvAQ4AkACShIKEE3jIAhsaW5laXRlbUrnAhhjdGlrdl90ReQhXCVqNDQuMjVzLCBtaW46Mzc1IZwkYXZnOiA5MDUuOAEOGHA4MDoxLjIhj2A5NToxLjc2cywgaXRlcnM6MTE3MzA2OCwgAVMUczoyNjIwWpEBUCEAfLmpGNuUQimamZlRXJysQTChyw29EFIwZ3QoYZ8ALhG7CC5sX2GpEGtleSwgBRkBDgQucBEVCClqZFrJAAA3EckAOAW7CckIMjUuZcgNyR3KADjSygAQnFxPzeEphxwAAADwPzC8FCGEKFIuZnVuY3M6c3VtQs4AWHF1YW50aXR5KS0+Q29sdW1uIzI5at8CVsYABDMzDcYINDI5IYEJxgQ4Nmk5DcYBuymQADghvmaQAQgsIHOhIThkZXRhaWw6IHt0b3RhbF8h/ghlc3NtXSwxMjAwMDIzMjQ0LCBGIABEX3NpemU6IDIzODM4MTcyMDcyIfIFR2mcCT94NTg2NCwgcm9ja3NkYjoge2RlbGV0ZV9za2lwcGVkX4n5YcgIa2V5PhYACUIJgRxibG9jazoge2XqDF9oaXQROyQxNTEwNCwgcmVhFU4YMzg5NjI4MA0VOGJ5dGU6IDYxLjcgR0J9fV6LAhjN7KdUMEZWZUg1wYG7ABGFu3m+DDQxWhSFtggzbTdlASRsb29wczo4Yu0BPrYEQfuNuQg1LjVhImFQECA0MzEueVMIMS4wARohtxAgMi4zMSHnBGF4JYwtShg0Njc5NDgsASMyFwAQNTgzNTMlnAUXGDogNDVtMS5BKDR0b3Rfd2FpdDogMW0xNAWmDHJwY1+lSxA0NTg1LAUPBcYYIDFoMTZtMqFZAGNKSwVwLCBkaXN0c3FsX2NvbmN1cnJlbmN5OiAxNX1wpgMu5gYIzWyvOkABqfoAH3kAVeVd8QA3Uk0BEDRwyOIBjl4AQrcFADGttzadAQQxMla3BRgaAQIhzey2CbkEmpkBAQTpPw25GBBsdCgwLCARtQQ3KVKqAAw4cJwLLqkAGCLiTVUwRnZRRwQQQA1OUBRDQVJURVNJQU4gaW5uZXIgam9pbh1SgaRJSQwyYhpDPWsQT0ZGLCBF9gELPm4ABIJQUm4ABLMBSiEFNjYFDG5hbWU2EgAMbWZncjYSABBicmFuZDYTAAh0eXA6NwCBCDYkABxjb250YWluZTpOAAByhW0McHJpY0IwABBtbWVudFoOAQAPOg4BPHCYOnj///////////8BGAE=\")\n\n\tfmt.Println(vp)\n\tassert.Nil(t, err)\n\tassert.True(t, strings.Contains(vp, TiKVHugeTableScan))\n}\n\nfunc TestCopTasksDuration(t *testing.T) {\n\tvp, err := GenerateBinaryPlanJSON(\"1QfICtAHCgxQcm9qZWN0aW9uXzUS2wYKBlNvcnRfNhKABgoNVGFibGVSZWFkZXJfORKsAwoPBRL0tgFGdWxsU2Nhbl84IQAAACAXuMBBKQAAAACAhC5BMMCEPTgCQAJKDwoNCgR0ZXN0EgVzdG9ja1IQa2VlcCBvcmRlcjpmYWxzZWrFAnRpa3ZfdGFzazp7cHJvYyBtYXg6NjFtcywgbWluOjBzLCBhdmc6IDE5LjNtcywgcDgwOjMwbXMsIHA5NTo0MW1zLCBpdGVyczoxNTQ2LCB0YXNrczoxNDZ9LCBzY2FuX2RldGFpbDoge3RvdGFsX3Byb2Nlc3Nfa2V5czogMTAwMDAwMCwgdG90YWxfcHJvY2Vzc19rZXlzX3NpemU6IDM3MDM0MzczMywgdG90YWxfa2V5czogMTAyNjM3NSwgcm9ja3NkYjoge2RlbGV0ZV9za2lwcGVkX2NvdW50OiAxNjY1Mywga2V5X3NraXBwZWRfY291bnQ6IDIwNjkzNTAsIGJsb2NrOiB7Y2FjaGVfaGl0X2NvdW50OiA3MTQ2LCByZWFkX2NvdW50OiAwLCByZWFkX2J5dGU6IDAgQnl0ZXN9fX1w////////////AXj///////////8BIVVVVTWsWYJBKQAAAACAhC5BJZskAUABUhRkYXRhOjrLAYBaF3RpbWU6NzI0LjhtcywgbG9vcHM6OTg1YucBY29wX3Qhpigge251bTogMTQ2LCWsECA2My42AS4gbWluOiAxLjEyAQ0cYXZnOiAyMi4FDSBwOTU6IDQ2LjcBGkxtYXhfcHJvY19rZXlzOiA5MTg0LAEiRhUACHRvdAUVHDogMi45N3MsBREYd2FpdDogMgVLDHJwY18ZjQEOBcAwIDMuMjRzLCBjb3ByXzlVsHJhdGlvOiAwLjAwLCBkaXN0c3FsX2NvbmN1cnJlbmN5OiAxNX1wu/8aeP///wkCIAEhaqF/AXpulzrdAigBQAFSFnRlc3Quc0HYDC5zX29B1AxfY250LUQMOTQ1LkXFLUQYNzhw0IzvCwFQFBmVJZhBKTItAwFQABEyUAAMaV9pZBVLADQpYRVLCGINQx23RDFw6OMBeP///////////wEYAQ==\")\n\tassert.Nil(t, err)\n\tassert.True(t, strings.Contains(vp, \"187.85333\"))\n}\n\nfunc TestIndexJoinAndLookUPDuration(t *testing.T) {\n\tvp, err := GenerateBinaryPlanJSON(\"sB/YCqsfCgtTdHJlYW1BZ2dfORKrHgoMSW5kZXhKb2luXzM1EuANCgxTZWxlY3Rpb25fMzASkQwKDgUiNExvb2tVcF8yORK+BQoRBRPwUlJhbmdlU2Nhbl8yNxoBASF6g4vNd8oQQSlOr2RnSmOmQDDZATgCQAJKRQpDCgR0ZXN0EgpvcmRlcl9saW5lGi8KB1BSSU1BUlkSB29sX3dfaWQSAQkEZF8NCQBvAQkwCW9sX251bWJlclIrcgFxKDpbNiA4IDMwMTksCQkoMzkpLCBrZWVwIG8BXfBYOmZhbHNlWhR0aW1lOjEuOTRtcywgbG9vcHM6M2LJAWNvcF90YXNrOiB7bnVtOiAyLCBtYXg6IDk0OC44wrVzLCBtaW46IDg1MC43wrVzLCBhdmc6IDg5OS4JDwhwOTUyLQBEYXhfcHJvY19rZXlzOiAxMjEsASNCFAAIcnBjAccFcAEMBZ4QIDEuNzUBn/BMY29wcl9jYWNoZV9oaXRfcmF0aW86IDAuMDAsIGRpc3RzcWxfY29uY3VycmVuY3k6IDE1fWqgAnRpa3ZfdGFzazp7cHJvYyBtYXg6MHMJwgAwEbsBCQhwODAFEQG9ARAgaXRlcnM6NSwgIQoYczoyfSwgcyHINGRldGFpbDoge3RvdGFsBcUIZXNzDdwQMjE3LCBGGQAQX3NpemUBkgQ0MBUgKQ2YMjE5LCByb2Nrc2RiOiB7ZGVsZXRlX3NraXBwZWRfY291bnQ6IDI4Ib0AeUIXACw0NSwgYmxvY2s6IHs5Gg01FDgsIHJlYRVEADAND0RieXRlOiAwIEJ5dGVzfX19cP8RAQQBeBEKQP8BEu0EChFUYWJsZVJvd0lEScEQOBoBAiFiwQIMFAoSCkLBAgRSEFp1AgwyLjIzIdZJdQgyYsNidQIMMS4xMQEqRXMUOTUyLjnCQYJFcwgxLjAFRiG0GSnybwIMMi4wMgFvAGPObwIEoQL+bwL+bwIubwIQMjE1NTJRj01vADNBF3pvAgQxNFZvAgQzMWpvAgQ0Mk1hTX+2cAIgIdUN7z4KnvZAMhgFCAFAAY2kDDQuNTYhlkkvEDRijwFpoZNp2XGLkTAEOTgBKyhmZXRjaF9oYW5kbGF7BC45BUEQYnVpbGRBLAA4ickcd2FpdDogMTlFVgB9Ye0IYmxlTlcACDIuMwVBIG51bTogMSwgYy5rBBA1fXDwly5AAwgaAQE+ygAoUoQBZXEodGVzdC6lhKXhCC5vbKXMFCwgOCksIFYgACx3X2lkLCA2KSwgZ2VOQADBAwAspe0QKSwgbHRuIwAIMzkpNVEAN2VWKVEMNXDgRC7+AwTwDTbSBgwzNBLqRtIGBDMxAecAAAUBDPA/MNjJyQgqCiiNCCAFc3RvY2saGQrVxAQGcwXOAQgUaV9pZFJtya4wIGRlY2lkZWQgYnkgWzEFBT0ELnMFKQQsIEo4AQFCACk1NBEvKS4IOCldQcRW8AYEMy4lygn7CDlizkJ7BAQ0LMnwFDYuMDltc8ksDCAxLjUFDYV5CDMuMyU/AHDB6hUnNnkEBDk2QucGARMIdG90xTUIOiA1AVzB6UVUBDQsBQxNxQQzLgVg0oYEAKlShgSF1qFFwd0FxQgxLjIFg8H6BR4BzgUJyfwANmEPEHNrczo0mvwGATFCFQfR/AwxNDQ4No0EBDc0ivwGWo0EADWBcGL8Bgg0MzXujgQMEp8GCi7jCQwzErEBQg8HBDMyQvsCCA8KDTL7Akb+BgRqU04hBsXEMpsBEDgzMy4zxeghnUULIZ0FKSmdADkxnQA2WsMHCBoBAhmhGAw4AkACUh2RHnEtLHF1YW50aXR5LCAxNpEaCDkuOAVnaR8INmLSQh8DADZtHwgzLjZFLgG6FCA1NDUuMgWyTVypsQG2GSk2IQMIMTI3QiIDBRReIwMANi4jAwA5GkII0iMDAKhSIwMF+AHOdSOyiAGWIQpuJQMQNzk5ODE2JQOSIQoAMIHtQiAKADBqrwcAMxJ4CuIgChAaAQIhIgEBCPJQQC5mAvmyCDI0LiVPUUdusgcIMy43oQs6sgetp+2yCDYuOBb5CemyFDQzLjXCtXKyBwQxMAmCpV4AM0ayBwT0UN3zTCGQ1iKAmZ4TQSn4TwpLQircQDAMAc44UpsBaW5uZXIgam9pbiwgBQwEOkkOfAga7Q00MzQsIG91dGVyIGtleTpapwYNORkjcYEAaeXcEGVxdWFsDlIIBGQ60f42IwgJJwVdLjoAcbMIMTcuxZspbAgyYmoJpxaiDAA6qeUusAgANYk8ADopFSRzdHJ1Y3Q6MjAxEgUJACwpiCHBJZYpgAAxCR0wfSwgcHJvYmU6MTEyLg5CCQxwpMgMLkcBHHheyDji5hhBWRUAASVHGDJmdW5jczoSHQoAKA6vDQxpbmN0StIAKC0+Q29sdW1uIzMwGqoIOt0APHCUBXj///////////8BGAE=\")\n\tassert.Nil(t, err)\n\tassert.True(t, strings.Contains(vp, \"8.1ms\"))\n\tassert.True(t, strings.Contains(vp, \"2.23\"))\n}\n\nfunc TestApplyAndLookUPDuration(t *testing.T) {\n\tvp, err := GenerateBinaryPlanJSON(\"/haICvkWCg1Qcm9qZWN0aW9uXzEwEoYWCghBcHBseV8xMhL9BQoyHwBgMxLTBAoOVGFibGVSZWFkZXJfMTUS3AIKEAUTUEZ1bGxTY2FuXzE0IQAAAAAgZSFBKQEJ8FsAiMNAMAM4AkACSg0KCwoFdGVzdDISAnR0Uh5rZWVwIG9yZGVyOmZhbHNlLCBzdGF0czpwc2V1ZG9q6gF0aWt2X3Rhc2s6e3RpbWU6MW1zLCBsb29wczoxfSwgcwFwfGRldGFpbDoge3RvdGFsX3Byb2Nlc3Nfa2V5czogMywgRhcAIF9zaXplOiAxMhUeCS2gNCwgcm9ja3NkYjoge2RlbGV0ZV9za2lwcGVkX2NvdW50OiAwLCBrZXk+FgBMMywgYmxvY2s6IHtjYWNoZV9oaXQdMghyZWEuQQAFD0RieXRlOiAwIEJ5dGVzfX19cP8RAQQBeBEKDP8BIVUBAQjY50AuSgEkAUABUhVkYXRhOj55AQRaFCkmCC41OTkpHDJijgFjb3BfJUdIIHtudW06IDEsIG1heDogMS41NAEqITUtBSkUJUcIOiAxAR0McnBjXxE2AQwFZAE7AR0QY29wcl8Z9aByYXRpbzogMC4wMCwgZGlzdHNxbF9jb25jdXJyZW5jeTogMTV9cLcCeBnkKBoBASGqqqqqij/zQucAAEhFLRgudHQuYSwgFQwcYiwgY2FzdCgVEbBhLCBkZWNpbWFsKDIwLDApIEJJTkFSWSktPkNvbHVtbiM4WhR0aW1lOjEuNjcBtgRsb0FDDDJiDUMdmwwxcIwXPXyoEuUOCgpIYXNoQWdnXzI0EoIOCg5JbmRleExvb2tVcF8yNRKTBgoMU2VsZWlAGDIzEsUBChAFJHUOBDIxSg4DAAZlDggXChVxDjADdHQxGgcKAmlhEgFhghgDBEp0eRchuiXMGDBzLCBtaW4FCBBhdmc6IAERCHA4MAURCHA5NQUIFGl0ZXJzOiHjGGFza3M6M31WdwIIGgEBSXEIQL9AabsIUhtnOXAAMSFxOX0AKU13CDIuNl13CDdixUJ3AgAzTXcYOTU2LjTCtQ2yFCA2MjkuNgUPBbkIODA4DQ8Bty4tAAhheF8B8E2qBDIsASE6EgAAclGtBDUsBQxJrQgzLjdF6M6uAgxqlwJ0/nABNXCOtAQANmq0BAQyNxUeibQEOSzatAQANv60BF60BAgSiwZ5OBgxNxK9AQoRhakQUm93SUTFIwQyMjoVA1VoDEoOCgwuFQP+DAP+DANhDAACaQwQAPA/MAJhuShSIWZ1bmNzOnN1bZWDCDEuYpluBDExdRIENTaZbwg0YsFiEgMIODYyZQFhwhQgNjk3LjMFD2UQCDc5NW0uDHA5NToZK+4OA2Wp/g4D/g4Dkg4DADNqDgNCwgdh9dLCBwAy/g4DXg4DICH/lVB1tWEZQS5FAggBQAFNIgw1Ljg4WSIQNWKPAWnBY/GtFrwIicQMMi4wMgErMGZldGNoX2hhbmRsZToBFuWlEGJ1aWxkAQ8ANqlKDHdhaXQBDzA5OMK1c30sIHRhYmxlVlcAADdFpOXiCDIsIDatBwx9cLxL/RQgGgECIdy6VnbBMtEAAAMB0QRSHnkW7XYEMTF5FAA3FfEAOQWw6YIsNnC0ESFvNAp7AvzuLSIIiMNABU9QKXNlbWkgam9pbiwgZXF1YWw6W2VxEVoIOCwgDWQQNyldWhMlIQQ3Lj1KCDFiLy7bBwhPRkYONAgUY2hlOk9ODQoMSGl0UhKuCBwwLjAwMCVwGx3oFCFvNMohEUKUAAAW2fUujwgAWhZdCAw3Ljg3IaEJ2wAxcl0IBBgB\")\n\tassert.Nil(t, err)\n\tassert.True(t, strings.Contains(vp, \"5.91ms\"))\n\tassert.True(t, strings.Contains(vp, \"2.56ms\"))\n}\n\nfunc TestShuffleDuration(t *testing.T) {\n\tvp, err := GenerateBinaryPlanJSON(\"6xhYCuYYCg1JbmRleE1lcmdlXzE2ErQEChAFEjxSYW5nZVNjYW5fOBoBASEAAQEM0IFAKQEI8J8AACRAOAJAAkoXChUKBXRlc3QyEgN0dDMaBwoCaWESAWFSK3JhbmdlOlsxLDFdLCBrZWVwIG9yZGVyOmZhbHNlLCBzdGF0czpwc2V1ZG9aFHRpbWU6NS4yOG1zLCBsb29wczoxYqkBY29wX3Rhc2s6IHtudW06IDEsIG1heDogMS4wM21zLCBwcm9jX2tleXM6IDAsIHRvdF9wcm9jOiAxAUcMcnBjXwU2BDIsBQwFZBAgMi4zOQEe8Ftjb3ByX2NhY2hlX2hpdF9yYXRpbzogMC4wMCwgZGlzdHNxbF9jb25jdXJyZW5jeTogMTV9LCBiYWNrb2Zme3JlZ2lvbk1pc3M6IDJtc31q6AF0aWt2X3Rhc2s6ewVqADEBZgRsbwXLDH0sIHMhSThkZXRhaWw6IHt0b3RhbF8Bvwxlc3NfLsIAOhcAJF9zaXplOiAwLCAJMwkrgDEsIHJvY2tzZGI6IHtkZWxldGVfc2tpcHBlZF9jb3VudAUyCGtleUoWAAxibG9jIVEZ/BkyCHJlYS5BAAUPCGJ5dAGBKCBCeXRlc319fXD/EQEEAXgRCgz/ARKjRjcCADm2NwIMYhIBYlU3CDIsMqY3AgQ3Nj1sBGKZajcCADIBKSF3LUkAMEkbTicCBDg4ASwAY/4nAjYnAgDnQicCCDBzLP4mAv4mAv4mAqomAlCoBQoMU2VsZWN0aW9uXzExEqYBChE6bgQEMTCFbCyAMQdBKauqqqqqCqpmbAQUYxIBY1IuiWwYKDMsK2luZoI4AgxqHHRpNqwDLoUBVuACKBoBASlWVVVVVdWkBY0kUhxsdChwbHVzKIX9QC50dDMuYywgMSksIDEwKVoUhQMIOC4xMpoCAKlimgIIMy43hZY2mwIAdI7RBAg1LjP+qgJOqgIA6EKqAv7RBP7RBP7RBLrRBASlBEaaAgAyttIEDGQSAWSV0gg0LDSCmgJNOAg1LjkyOAIAmmI4AgwxLjE1gac2OAJe+gYAOAUs/tME/tME/tME/tME/tMEOtMEAKRKKAIAM7YoAgxlEgFlVSgINSw1higCABOFYAQ1LmX6GmUIdicCSjAJYicCADdBU/4nAv4nAv4nAv4nAv4nAjonAgTDATL6Bjg1EmYKEVRhYmxlUm93SUQSZwskMTQpYQKtfOkPpcVjDEoOCgwuXAsIUh5rckYLXrkGIAIptM7wlofZoAVWCFIlZ0K5BghhLCDZxhRiKSwgMilWTQAgIaHrVV42sONAGVMIAUABjbYENzAWXwlNjzxwggR4////////////ARgB\")\n\tassert.Nil(t, err)\n\tassert.True(t, strings.Contains(vp, \"8.16ms\"))\n}\n"
  },
  {
    "path": "pkg/apiserver/utils/error.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"github.com/joomcode/errorx\"\n)\n\nvar ErrNS = errorx.NewNamespace(\"error.api\")\n\nvar ErrExpNotEnabled = ErrNS.NewType(\"experimental_feature_not_enabled\")\n"
  },
  {
    "path": "pkg/apiserver/utils/export.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"encoding/base64\"\n\t\"encoding/csv\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/Xeoncross/go-aesctr-with-hmac\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gtank/cryptopasta\"\n\t\"github.com/oleiade/reflections\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\n// TODO: Better to be a streaming interface.\nfunc GenerateCSVFromRaw(rawData []interface{}, fields []string, timeFields []string) (data [][]string) {\n\ttimeFieldsMap := make(map[string]struct{})\n\tfor _, f := range timeFields {\n\t\ttimeFieldsMap[f] = struct{}{}\n\t}\n\n\tfieldsMap := make(map[string]string)\n\tt := reflect.TypeOf(rawData[0])\n\tfieldsNum := t.NumField()\n\tallFields := make([]string, fieldsNum)\n\tfor i := range fieldsNum {\n\t\tfield := t.Field(i)\n\t\tallFields[i] = strings.ToLower(field.Tag.Get(\"json\"))\n\t\tfieldsMap[allFields[i]] = field.Name\n\t}\n\tif len(fields) == 1 && fields[0] == \"*\" {\n\t\tfields = allFields\n\t}\n\n\tdata = [][]string{fields} //nolint:prealloc\n\ttimeLayout := \"01-02 15:04:05\"\n\tfor _, overview := range rawData {\n\t\trow := make([]string, 0, len(fields))\n\t\tfor _, field := range fields {\n\t\t\tfieldName := fieldsMap[field]\n\t\t\ts, _ := reflections.GetField(overview, fieldName)\n\t\t\tvar val string\n\t\t\tswitch t := s.(type) {\n\t\t\tcase int:\n\t\t\t\tif _, ok := timeFieldsMap[field]; ok {\n\t\t\t\t\tval = time.Unix(int64(t), 0).Format(timeLayout)\n\t\t\t\t} else {\n\t\t\t\t\tval = fmt.Sprintf(\"%d\", t)\n\t\t\t\t}\n\t\t\tcase uint:\n\t\t\t\tval = fmt.Sprintf(\"%d\", t)\n\t\t\tcase float64:\n\t\t\t\tval = fmt.Sprintf(\"%f\", t)\n\t\t\tdefault:\n\t\t\t\tval = fmt.Sprintf(\"%s\", t)\n\t\t\t}\n\t\t\trow = append(row, val)\n\t\t}\n\t\tdata = append(data, row)\n\t}\n\treturn\n}\n\n// TODO: Better to be a streaming interface.\nfunc ExportCSV(data [][]string, filename, tokenNamespace string) (token string, err error) {\n\tcsvFile, err := os.CreateTemp(\"\", filename)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer csvFile.Close() // #nosec\n\n\t// generate encryption key\n\tsecretKey := *cryptopasta.NewEncryptionKey()\n\n\tpr, pw := io.Pipe()\n\tgo func() {\n\t\tcsvwriter := csv.NewWriter(pw)\n\t\t_ = csvwriter.WriteAll(data)\n\t\t_ = pw.Close()\n\t}()\n\terr = aesctr.Encrypt(pr, csvFile, secretKey[0:16], secretKey[16:])\n\tif err != nil {\n\t\treturn\n\t}\n\n\t// generate token by filepath and secretKey\n\tsecretKeyStr := base64.StdEncoding.EncodeToString(secretKey[:])\n\ttoken, err = NewJWTString(tokenNamespace, secretKeyStr+\" \"+csvFile.Name())\n\treturn\n}\n\n// FIXME: Remove or refine this function, as it is not general.\nfunc DownloadByToken(token, tokenNamespace string, c *gin.Context) {\n\ttokenPlain, err := ParseJWTString(tokenNamespace, token)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.WrapWithNoMessage(err))\n\t\treturn\n\t}\n\tarr := strings.Fields(tokenPlain)\n\tif len(arr) != 2 {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"invalid token\"))\n\t\treturn\n\t}\n\tsecretKey, err := base64.StdEncoding.DecodeString(arr[0])\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.WrapWithNoMessage(err))\n\t\treturn\n\t}\n\n\tfilePath := arr[1]\n\tfileInfo, err := os.Stat(filePath)\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tf, err := os.Open(filepath.Clean(filePath))\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\n\tc.Writer.Header().Set(\"Content-type\", \"text/csv\")\n\tc.Writer.Header().Set(\"Content-Disposition\", fmt.Sprintf(`attachment; filename=\"%s\"`, fileInfo.Name()))\n\terr = aesctr.Decrypt(f, c.Writer, secretKey[0:16], secretKey[16:])\n\tif err != nil {\n\t\tlog.Error(\"decrypt csv failed\", zap.Error(err))\n\t}\n\t// delete it anyway\n\t_ = f.Close()\n\t_ = os.Remove(filePath)\n}\n"
  },
  {
    "path": "pkg/apiserver/utils/gorm.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport \"gorm.io/gorm/schema\"\n\nfunc GetGormColumnName(gormStr string) string {\n\tgormStrMap := schema.ParseTagSetting(gormStr, \";\")\n\t// The key will be converted to uppercase in:\n\t// https://github.com/go-gorm/gorm/blob/master/schema/utils.go#L33\n\treturn gormStrMap[\"COLUMN\"]\n}\n"
  },
  {
    "path": "pkg/apiserver/utils/gorm_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/check\"\n)\n\nfunc TestT(t *testing.T) {\n\tcheck.CustomVerboseFlag = true\n\tcheck.TestingT(t)\n}\n\nvar _ = check.Suite(&testGormSuite{})\n\ntype testGormSuite struct{}\n\nfunc (t *testGormSuite) Test_GetGormColumnName(c *check.C) {\n\tc.Assert(GetGormColumnName(`column:db`), check.Equals, `db`)\n\tc.Assert(GetGormColumnName(`primaryKey;index`), check.Equals, ``)\n\tc.Assert(GetGormColumnName(`column:db;primaryKey;index`), check.Equals, `db`)\n}\n"
  },
  {
    "path": "pkg/apiserver/utils/jwt.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\tjwt \"github.com/golang-jwt/jwt/v4\"\n\t\"github.com/gtank/cryptopasta\"\n)\n\nvar hmacSampleSecret = cryptopasta.NewEncryptionKey()\n\n// Claims is a struct that will be encoded to a JWT.\ntype Claims struct {\n\tData string `json:\"data\"`\n\tjwt.StandardClaims\n}\n\nfunc newClaims(issuer string, data string, expireIn time.Duration) *Claims {\n\treturn &Claims{\n\t\tData: data,\n\t\tStandardClaims: jwt.StandardClaims{ //nolint:staticcheck // StandardClaims is deprecated, but we use it here temporarily\n\t\t\tExpiresAt: time.Now().Add(expireIn).Unix(),\n\t\t\tIssuer:    issuer,\n\t\t},\n\t}\n}\n\n// NewJWTString create a JWT string by given data, expire in 24 hours.\nfunc NewJWTString(issuer string, data string) (string, error) {\n\treturn NewJWTStringWithExpire(issuer, data, 24*time.Hour)\n}\n\nfunc NewJWTStringWithExpire(issuer string, data string, expireIn time.Duration) (string, error) {\n\tclaims := newClaims(issuer, data, expireIn)\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)\n\ttokenString, err := token.SignedString(hmacSampleSecret[:])\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn tokenString, nil\n}\n\n// ParseJWTString parse the JWT string and return the raw data.\nfunc ParseJWTString(requiredIssuer string, tokenStr string) (string, error) {\n\tclaims := &Claims{}\n\ttoken, err := jwt.ParseWithClaims(tokenStr, claims, func(_ *jwt.Token) (interface{}, error) {\n\t\treturn hmacSampleSecret[:], nil\n\t})\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tif !token.Valid {\n\t\treturn \"\", fmt.Errorf(\"token is invalid or expired\")\n\t}\n\tif claims.Issuer != requiredIssuer {\n\t\treturn \"\", fmt.Errorf(\"token is invalid (invalid issuer)\")\n\t}\n\treturn claims.Data, nil\n}\n"
  },
  {
    "path": "pkg/apiserver/utils/mw_experimental.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nfunc MWForbidByExperimentalFlag(enableExp bool) gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tif !enableExp {\n\t\t\tc.Status(http.StatusForbidden)\n\t\t\trest.Error(c, ErrExpNotEnabled.NewWithNoMessage())\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tc.Next()\n\t}\n}\n"
  },
  {
    "path": "pkg/apiserver/utils/ngm.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httputil\"\n\t\"net/url\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joomcode/errorx\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/fx\"\n\t\"golang.org/x/sync/singleflight\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/topology\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nvar (\n\tNgmErrNS       = errorx.NewNamespace(\"ngm\")\n\tErrNgmNotStart = NgmErrNS.NewType(\"ngm_not_started\")\n)\n\ntype NgmState string\n\nconst (\n\tNgmStateNotSupported NgmState = \"not_supported\"\n\tNgmStateNotStarted   NgmState = \"not_started\"\n\tNgmStateStarted      NgmState = \"started\"\n)\n\nconst (\n\tngmCacheTTL = time.Second * 5\n)\n\ntype ngmAddrCacheEntity struct {\n\taddress string\n\terr     error\n\tcacheAt time.Time\n}\n\ntype NgmProxy struct {\n\tlifecycleCtx context.Context\n\tetcdClient   *clientv3.Client\n\tngmReqGroup  singleflight.Group\n\tngmAddrCache atomic.Value\n\ttimeout      time.Duration\n}\n\nfunc NewNgmProxy(lc fx.Lifecycle, etcdClient *clientv3.Client, config *config.Config) (*NgmProxy, error) {\n\ts := &NgmProxy{\n\t\tetcdClient: etcdClient,\n\t\ttimeout:    time.Duration(config.NgmTimeout) * time.Second,\n\t}\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\ts.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn s, nil\n}\n\nfunc (n *NgmProxy) Route(targetPath string) gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tngmAddr, err := n.getNgmAddrFromCache()\n\t\tif err != nil {\n\t\t\trest.Error(c, err)\n\t\t\treturn\n\t\t}\n\n\t\tc.Request.URL.Path = targetPath\n\n\t\tngmURL, _ := url.Parse(ngmAddr)\n\t\tproxy := httputil.NewSingleHostReverseProxy(ngmURL)\n\t\tproxy.Transport = &http.Transport{\n\t\t\tProxy: http.ProxyFromEnvironment,\n\t\t\tDialContext: defaultTransportDialContext(&net.Dialer{\n\t\t\t\tTimeout:   n.timeout,\n\t\t\t\tKeepAlive: n.timeout,\n\t\t\t}),\n\t\t\tForceAttemptHTTP2:     true,\n\t\t\tMaxIdleConns:          100,\n\t\t\tIdleConnTimeout:       90 * time.Second,\n\t\t\tTLSHandshakeTimeout:   10 * time.Second,\n\t\t\tExpectContinueTimeout: 1 * time.Second,\n\t\t}\n\t\tproxy.ServeHTTP(c.Writer, c.Request)\n\t}\n}\n\nfunc (n *NgmProxy) getNgmAddrFromCache() (string, error) {\n\tfn := func() (string, error) {\n\t\t// Check whether cache is valid, and use the cache if possible.\n\t\tif v := n.ngmAddrCache.Load(); v != nil {\n\t\t\tentity := v.(*ngmAddrCacheEntity)\n\t\t\tif entity.cacheAt.Add(ngmCacheTTL).After(time.Now()) {\n\t\t\t\treturn entity.address, entity.err\n\t\t\t}\n\t\t}\n\n\t\taddr, err := n.resolveNgmAddress()\n\n\t\tn.ngmAddrCache.Store(&ngmAddrCacheEntity{\n\t\t\taddress: addr,\n\t\t\terr:     err,\n\t\t\tcacheAt: time.Now(),\n\t\t})\n\n\t\treturn addr, err\n\t}\n\n\tresolveResult, err, _ := n.ngmReqGroup.Do(\"any_key\", func() (interface{}, error) {\n\t\treturn fn()\n\t})\n\treturn resolveResult.(string), err\n}\n\nfunc (n *NgmProxy) resolveNgmAddress() (string, error) {\n\taddr, err := topology.FetchNgMonitoringTopology(n.lifecycleCtx, n.etcdClient)\n\tif err == nil && addr != \"\" {\n\t\treturn fmt.Sprintf(\"http://%s\", addr), nil\n\t}\n\treturn \"\", ErrNgmNotStart.Wrap(err, \"NgMonitoring component is not started\")\n}\n\nfunc defaultTransportDialContext(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) {\n\treturn dialer.DialContext\n}\n"
  },
  {
    "path": "pkg/apiserver/utils/subset.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"strings\"\n\n\t\"github.com/samber/lo\"\n)\n\nfunc IsSubsetICaseInsensitive(a []string, b []string) bool {\n\tlowercaseA := lo.Map(a, func(x string, _ int) string {\n\t\treturn strings.ToLower(x)\n\t})\n\tlowercaseB := lo.Map(b, func(x string, _ int) string {\n\t\treturn strings.ToLower(x)\n\t})\n\n\treturn len(lo.Intersect(lowercaseA, lowercaseB)) == len(lowercaseB)\n}\n"
  },
  {
    "path": "pkg/apiserver/utils/tidb_conn.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joomcode/errorx\"\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nconst (\n\t// The key that attached the TiDB connection in the gin Context.\n\ttiDBConnectionKey = \"tidb\"\n)\n\n// MWConnectTiDB creates a middleware that attaches TiDB connection to the context, according to the identity\n// information attached in the context. If a connection cannot be established, subsequent handlers will be skipped\n// and errors will be generated.\n//\n// This middleware must be placed after the `MWAuthRequired()` middleware, otherwise it will panic.\nfunc MWConnectTiDB(tidbClient *tidb.Client) gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tsessionUser := GetSession(c)\n\t\tif sessionUser == nil {\n\t\t\tpanic(\"invalid sessionUser\")\n\t\t}\n\n\t\tif !sessionUser.HasTiDBAuth {\n\t\t\t// Only TiDBAuth is able to access. Raise error in this case.\n\t\t\t// The error is privilege error instead of authorization error so that user will not be redirected.\n\t\t\trest.Error(c, rest.ErrForbidden.NewWithNoMessage())\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tdb, err := tidbClient.OpenSQLConn(sessionUser.TiDBUsername, sessionUser.TiDBPassword)\n\t\tif err != nil {\n\t\t\tif errorx.IsOfType(err, tidb.ErrTiDBAuthFailed) {\n\t\t\t\t// If TiDB conn is ok when login but fail this time, it means TiDB credential has been changed since\n\t\t\t\t// login. In this case, we return unauthorized error, so that the front-end can let user to login again.\n\t\t\t\trest.Error(c, rest.ErrUnauthenticated.NewWithNoMessage())\n\t\t\t} else {\n\t\t\t\t// For other kind of connection errors, for example, PD goes away, return these errors directly.\n\t\t\t\t// In front-end we will simply display these errors but not ask user to login again.\n\t\t\t\trest.Error(c, err)\n\t\t\t}\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tdefer func() {\n\t\t\t// We allow tiDBConnectionKey to be cleared by `TakeTiDBConnection`.\n\t\t\tdbInContext := c.MustGet(tiDBConnectionKey)\n\t\t\tif dbInContext != nil {\n\t\t\t\tdbInContext2 := dbInContext.(*gorm.DB)\n\t\t\t\tif dbInContext2 != nil {\n\t\t\t\t\t_ = CloseTiDBConnection(dbInContext2)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\tc.Set(tiDBConnectionKey, db)\n\t\tc.Next()\n\t}\n}\n\n// TakeTiDBConnection takes out the TiDB connection stored in the gin context by `MWConnectTiDB` middleware.\n// Subsequent handlers in this context cannot access the TiDB connection any more.\n//\n// The TiDB connection will no longer be closed automatically after all handlers are finished. You must manually\n// close the taken out connection.\nfunc TakeTiDBConnection(c *gin.Context) *gorm.DB {\n\tdb := GetTiDBConnection(c)\n\tc.Set(tiDBConnectionKey, nil)\n\treturn db\n}\n\nfunc CloseTiDBConnection(db *gorm.DB) error {\n\tsqlDB, err := db.DB()\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn sqlDB.Close()\n}\n\n// GetTiDBConnection gets the TiDB connection stored in the gin context by `MWConnectTiDB` middleware.\n//\n// The connection will be closed automatically after all handlers are finished. Thus you must not use it outside\n// the request lifetime. If you want to extend the lifetime, use `TakeTiDBConnection`.\nfunc GetTiDBConnection(c *gin.Context) *gorm.DB {\n\tdb := c.MustGet(tiDBConnectionKey).(*gorm.DB)\n\treturn db\n}\n"
  },
  {
    "path": "pkg/apiserver/visualplan/module.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage visualplan\n\nimport \"go.uber.org/fx\"\n\nvar Module = fx.Options(\n\tfx.Provide(newService),\n\tfx.Invoke(registerRouter),\n)\n"
  },
  {
    "path": "pkg/apiserver/visualplan/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage visualplan\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joomcode/errorx\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/util/featureflag\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nvar ErrNS = errorx.NewNamespace(\"error.api.visualplan\")\n\ntype ServiceParams struct {\n\tfx.In\n}\n\ntype Service struct {\n\tFeatureVisualPlan *featureflag.FeatureFlag\n\tparams            ServiceParams\n}\n\nfunc newService(p ServiceParams, ff *featureflag.Registry) *Service {\n\treturn &Service{params: p, FeatureVisualPlan: ff.Register(\"visualplan\", \">= 6.2.0\")}\n}\n\nfunc registerRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/visualplan\")\n\tendpoint.Use(\n\t\tauth.MWAuthRequired(),\n\t\ts.FeatureVisualPlan.VersionGuard(),\n\t)\n\t{\n\t\tendpoint.POST(\"/generate\", s.GenerateVisualPlan)\n\t}\n}\n\ntype GenerateVisualPlanRequest struct {\n\tBinaryPlan string `json:\"binary_plan\"`\n}\n\n// // @Summary Generate VisualPlan\n// // @Router /visualplan/generate [post]\n// // @Security JwtAuth\n// // @Param request body GenerateVisualPlanRequest true \"Request body\"\n// // @Success 200 {object} string\n// // @Failure 401 {object} rest.ErrorResponse\n// // @Failure 500 {object} rest.ErrorResponse.\nfunc (s *Service) GenerateVisualPlan(c *gin.Context) {\n\tvar bp GenerateVisualPlanRequest\n\tif err := c.ShouldBindJSON(&bp); err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tvp, err := utils.GenerateBinaryPlanJSON(bp.BinaryPlan)\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.New(\"generate visual plan failed: %v\", err))\n\t\treturn\n\t}\n\tc.Data(http.StatusOK, gin.MIMEJSON, []byte(vp))\n}\n"
  },
  {
    "path": "pkg/config/config.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage config\n\nimport (\n\t\"crypto/tls\"\n\t\"net/url\"\n\t\"strings\"\n\n\t\"go.etcd.io/etcd/client/pkg/v3/transport\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/version\"\n)\n\nconst (\n\tdefaultPublicPathPrefix = \"/dashboard\"\n\n\tUIPathPrefix      = \"/dashboard/\"\n\tAPIPathPrefix     = \"/dashboard/api/\"\n\tSwaggerPathPrefix = \"/dashboard/api/swagger/\"\n)\n\ntype Config struct {\n\tDataDir          string\n\tTempDir          string\n\tPDEndPoint       string\n\tPublicPathPrefix string\n\n\tClusterTLSConfig *tls.Config        // TLS config for mTLS authentication between TiDB components.\n\tClusterTLSInfo   *transport.TLSInfo // TLS info for mTLS authentication between TiDB components.\n\tTiDBTLSConfig    *tls.Config        // TLS config for mTLS authentication between TiDB and MySQL client.\n\n\tEnableTelemetry       bool\n\tEnableExperimental    bool\n\tEnableKeyVisualizer   bool\n\tDisableCustomPromAddr bool\n\tFeatureVersion        string // assign the target TiDB version when running TiDB Dashboard as standalone mode\n\n\tNgmTimeout int // in seconds\n}\n\nfunc Default() *Config {\n\treturn &Config{\n\t\tDataDir:               \"/tmp/dashboard-data\",\n\t\tTempDir:               \"\",\n\t\tPDEndPoint:            \"http://127.0.0.1:2379\",\n\t\tPublicPathPrefix:      defaultPublicPathPrefix,\n\t\tClusterTLSConfig:      nil,\n\t\tClusterTLSInfo:        nil,\n\t\tTiDBTLSConfig:         nil,\n\t\tEnableTelemetry:       false,\n\t\tEnableExperimental:    false,\n\t\tEnableKeyVisualizer:   true,\n\t\tDisableCustomPromAddr: false,\n\t\tFeatureVersion:        version.PDVersion,\n\t\tNgmTimeout:            30, // s\n\t}\n}\n\nfunc (c *Config) GetClusterHTTPScheme() string {\n\tif c.ClusterTLSConfig != nil {\n\t\treturn \"https\"\n\t}\n\treturn \"http\"\n}\n\nfunc (c *Config) NormalizePDEndPoint() error {\n\tif !strings.HasPrefix(c.PDEndPoint, \"http://\") && !strings.HasPrefix(c.PDEndPoint, \"https://\") {\n\t\tc.PDEndPoint = \"http://\" + c.PDEndPoint\n\t}\n\n\tpdEndPoint, err := url.Parse(c.PDEndPoint)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpdEndPoint.Scheme = c.GetClusterHTTPScheme()\n\tc.PDEndPoint = pdEndPoint.String()\n\treturn nil\n}\n\nfunc (c *Config) NormalizePublicPathPrefix() {\n\tif c.PublicPathPrefix == \"\" {\n\t\tc.PublicPathPrefix = defaultPublicPathPrefix\n\t}\n\tc.PublicPathPrefix = strings.TrimRight(c.PublicPathPrefix, \"/\")\n}\n"
  },
  {
    "path": "pkg/config/dynamic_config.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage config\n\nimport (\n\t\"slices\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/model\"\n)\n\nconst (\n\tKeyVisualDBPolicy = \"db\"\n\tKeyVisualKVPolicy = \"kv\"\n\n\tDefaultKeyVisualPolicy = KeyVisualDBPolicy\n\n\tDefaultProfilingAutoCollectionDurationSecs = 30\n\tMaxProfilingAutoCollectionDurationSecs     = 120\n\tDefaultProfilingAutoCollectionIntervalSecs = 3600\n)\n\nvar (\n\tKeyVisualPolicies = []string{KeyVisualDBPolicy, KeyVisualKVPolicy}\n\n\tErrVerificationFailed = ErrorNS.NewType(\"verification failed\")\n)\n\ntype KeyVisualConfig struct {\n\tAutoCollectionDisabled bool   `json:\"auto_collection_disabled\"`\n\tPolicy                 string `json:\"policy\"`\n\tPolicyKVSeparator      string `json:\"policy_kv_separator\"`\n}\n\nfunc (c *KeyVisualConfig) validatePolicy() error {\n\tif slices.Contains(KeyVisualPolicies, c.Policy) {\n\t\treturn nil\n\t}\n\treturn ErrVerificationFailed.New(\"policy must be in %v\", KeyVisualPolicies)\n}\n\ntype ProfilingConfig struct {\n\tAutoCollectionTargets      []model.RequestTargetNode `json:\"auto_collection_targets\"`\n\tAutoCollectionDurationSecs uint                      `json:\"auto_collection_duration_secs\"`\n\tAutoCollectionIntervalSecs uint                      `json:\"auto_collection_interval_secs\"`\n}\n\ntype SSOCoreConfig struct {\n\tEnabled      bool   `json:\"enabled\"`\n\tClientID     string `json:\"client_id\"`\n\tClientSecret string `json:\"client_secret\"`\n\tDiscoveryURL string `json:\"discovery_url\"`\n\tScopes       string `json:\"scopes\"`\n\tIsReadOnly   bool   `json:\"is_read_only\"`\n}\n\ntype SSOConfig struct {\n\tCoreConfig  SSOCoreConfig `json:\"core_config\"`\n\tAuthURL     string        `json:\"auth_url\"`\n\tTokenURL    string        `json:\"token_url\"`\n\tUserInfoURL string        `json:\"user_info_url\"`\n\tSignOutURL  string        `json:\"sign_out_url\"`\n}\n\ntype DynamicConfig struct {\n\tKeyVisual KeyVisualConfig `json:\"keyvisual\"`\n\tProfiling ProfilingConfig `json:\"profiling\"`\n\tSSO       SSOConfig       `json:\"sso\"`\n}\n\nfunc (c *DynamicConfig) Clone() *DynamicConfig {\n\tnewCfg := *c\n\tnewCfg.Profiling.AutoCollectionTargets = make([]model.RequestTargetNode, len(c.Profiling.AutoCollectionTargets))\n\tcopy(newCfg.Profiling.AutoCollectionTargets, c.Profiling.AutoCollectionTargets)\n\treturn &newCfg\n}\n\nfunc (c *DynamicConfig) Validate() error {\n\tif !c.KeyVisual.AutoCollectionDisabled {\n\t\tif err := c.KeyVisual.validatePolicy(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif len(c.Profiling.AutoCollectionTargets) > 0 {\n\t\tif c.Profiling.AutoCollectionDurationSecs == 0 {\n\t\t\treturn ErrVerificationFailed.New(\"auto_collection_duration_secs cannot be 0\")\n\t\t}\n\t\tif c.Profiling.AutoCollectionDurationSecs > MaxProfilingAutoCollectionDurationSecs {\n\t\t\treturn ErrVerificationFailed.New(\"auto_collection_duration_secs cannot be greater than %d\", MaxProfilingAutoCollectionDurationSecs)\n\t\t}\n\t\tif c.Profiling.AutoCollectionIntervalSecs == 0 {\n\t\t\treturn ErrVerificationFailed.New(\"auto_collection_interval_secs cannot be 0\")\n\t\t}\n\t} else {\n\t\tif c.Profiling.AutoCollectionDurationSecs != 0 {\n\t\t\treturn ErrVerificationFailed.New(\"auto_collection_duration_secs must be 0\")\n\t\t}\n\t\tif c.Profiling.AutoCollectionIntervalSecs != 0 {\n\t\t\treturn ErrVerificationFailed.New(\"auto_collection_interval_secs must be 0\")\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Adjust is used to fill the default config for the existing config of the old version.\nfunc (c *DynamicConfig) Adjust() {\n\tif !c.KeyVisual.AutoCollectionDisabled {\n\t\tif err := c.KeyVisual.validatePolicy(); err != nil {\n\t\t\tc.KeyVisual.Policy = DefaultKeyVisualPolicy\n\t\t}\n\t}\n\n\tif len(c.Profiling.AutoCollectionTargets) > 0 {\n\t\tif c.Profiling.AutoCollectionDurationSecs == 0 {\n\t\t\tc.Profiling.AutoCollectionDurationSecs = DefaultProfilingAutoCollectionDurationSecs\n\t\t}\n\t\tif c.Profiling.AutoCollectionDurationSecs > MaxProfilingAutoCollectionDurationSecs {\n\t\t\tc.Profiling.AutoCollectionDurationSecs = MaxProfilingAutoCollectionDurationSecs\n\t\t}\n\t\tif c.Profiling.AutoCollectionIntervalSecs == 0 {\n\t\t\tc.Profiling.AutoCollectionIntervalSecs = DefaultProfilingAutoCollectionIntervalSecs\n\t\t}\n\t} else {\n\t\tc.Profiling.AutoCollectionDurationSecs = 0\n\t\tc.Profiling.AutoCollectionIntervalSecs = 0\n\t}\n}\n"
  },
  {
    "path": "pkg/config/dynamic_config_manager.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage config\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/zap\"\n)\n\nconst (\n\tDynamicConfigPath = \"/dashboard/dynamic_config\"\n\tTimeout           = time.Second\n\tMaxCheckInterval  = 30 * time.Second\n\tMaxElapsedTime    = 0 // never stop if MaxElapsedTime == 0\n)\n\nvar (\n\tErrorNS         = errorx.NewNamespace(\"error.dynamic_config\")\n\tErrUnableToLoad = ErrorNS.NewType(\"unable_to_load\")\n\tErrNotReady     = ErrorNS.NewType(\"not_ready\")\n)\n\ntype DynamicConfigOption func(dc *DynamicConfig)\n\ntype DynamicConfigManager struct {\n\tmu sync.RWMutex\n\n\tlifecycleCtx context.Context\n\tconfig       *Config\n\tetcdClient   *clientv3.Client\n\n\tdynamicConfig *DynamicConfig\n\tpushChannels  []chan *DynamicConfig\n}\n\nfunc NewDynamicConfigManager(lc fx.Lifecycle, config *Config, etcdClient *clientv3.Client) *DynamicConfigManager {\n\tm := &DynamicConfigManager{\n\t\tconfig:     config,\n\t\tetcdClient: etcdClient,\n\t}\n\tlc.Append(fx.Hook{\n\t\tOnStart: m.Start,\n\t\tOnStop:  m.Stop,\n\t})\n\treturn m\n}\n\nfunc (m *DynamicConfigManager) Start(ctx context.Context) error {\n\tm.lifecycleCtx = ctx\n\n\tgo func() {\n\t\tvar dc *DynamicConfig\n\t\tebo := backoff.NewExponentialBackOff()\n\t\tebo.MaxInterval = MaxCheckInterval\n\t\tebo.MaxElapsedTime = MaxElapsedTime\n\t\tbo := backoff.WithContext(ebo, ctx)\n\n\t\tif err := backoff.Retry(func() error {\n\t\t\tvar err error\n\t\t\tdc, err = m.load()\n\t\t\treturn err\n\t\t}, bo); err != nil {\n\t\t\tlog.Error(\"Failed to start DynamicConfigManager\", zap.Error(err))\n\t\t\treturn\n\t\t}\n\n\t\tif dc == nil {\n\t\t\tdc = &DynamicConfig{}\n\t\t}\n\t\tdc.Adjust()\n\t\tdc.KeyVisual.AutoCollectionDisabled = !m.config.EnableKeyVisualizer\n\n\t\tif err := backoff.Retry(func() error { return m.Set(dc) }, bo); err != nil {\n\t\t\tlog.Error(\"Failed to start DynamicConfigManager\", zap.Error(err))\n\t\t}\n\t}()\n\n\treturn nil\n}\n\nfunc (m *DynamicConfigManager) Stop(_ context.Context) error {\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\tfor _, ch := range m.pushChannels {\n\t\tclose(ch)\n\t}\n\treturn nil\n}\n\nfunc (m *DynamicConfigManager) NewPushChannel() <-chan *DynamicConfig {\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\n\tch := make(chan *DynamicConfig, 1000)\n\tm.pushChannels = append(m.pushChannels, ch)\n\n\tif m.dynamicConfig != nil {\n\t\tch <- m.dynamicConfig.Clone()\n\t}\n\n\treturn ch\n}\n\nfunc (m *DynamicConfigManager) Get() (*DynamicConfig, error) {\n\tm.mu.RLock()\n\tdefer m.mu.RUnlock()\n\tif m.dynamicConfig == nil {\n\t\treturn nil, ErrNotReady.NewWithNoMessage()\n\t}\n\treturn m.dynamicConfig.Clone(), nil\n}\n\nfunc (m *DynamicConfigManager) Set(newDc *DynamicConfig) error {\n\tif err := m.store(newDc); err != nil {\n\t\treturn err\n\t}\n\n\tm.mu.Lock()\n\tdefer m.mu.Unlock()\n\n\tm.dynamicConfig = newDc\n\n\tfor _, ch := range m.pushChannels {\n\t\tch <- m.dynamicConfig.Clone()\n\t}\n\n\treturn nil\n}\n\nfunc (m *DynamicConfigManager) Modify(opts ...DynamicConfigOption) error {\n\tnewDc, err := m.Get()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(newDc)\n\t}\n\tif err := newDc.Validate(); err != nil {\n\t\treturn err\n\t}\n\n\treturn m.Set(newDc)\n}\n\nfunc (m *DynamicConfigManager) load() (*DynamicConfig, error) {\n\tctx, cancel := context.WithTimeout(m.lifecycleCtx, Timeout)\n\tdefer cancel()\n\tresp, err := m.etcdClient.Get(ctx, DynamicConfigPath)\n\tif err != nil {\n\t\tlog.Warn(\"Failed to load dynamic config from etcd\", zap.Error(err))\n\t\treturn nil, ErrUnableToLoad.WrapWithNoMessage(err)\n\t}\n\tswitch len(resp.Kvs) {\n\tcase 0:\n\t\tlog.Warn(\"Dynamic config does not exist in etcd\")\n\t\treturn nil, nil\n\tcase 1:\n\t\t// the log contains the sso client secret, so we should not log it\n\t\t// log.Info(\"Load dynamic config from etcd\", zap.ByteString(\"json\", resp.Kvs[0].Value))\n\t\tvar dc DynamicConfig\n\t\tif err = json.Unmarshal(resp.Kvs[0].Value, &dc); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn &dc, nil\n\tdefault:\n\t\tlog.Error(\"etcd is unreachable\")\n\t\treturn nil, backoff.Permanent(ErrUnableToLoad.New(\"unreachable\"))\n\t}\n}\n\nfunc (m *DynamicConfigManager) store(dc *DynamicConfig) error {\n\tbs, err := json.Marshal(dc)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tctx, cancel := context.WithTimeout(m.lifecycleCtx, Timeout)\n\tdefer cancel()\n\t_, err = m.etcdClient.Put(ctx, DynamicConfigPath, string(bs))\n\t// the log contains the sso client secret, so we should not log it\n\t// log.Info(\"Save dynamic config to etcd\", zap.ByteString(\"json\", bs))\n\n\treturn err\n}\n"
  },
  {
    "path": "pkg/dbstore/dbstore.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage dbstore\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/zap\"\n\t\"gorm.io/driver/sqlite\"\n\t\"gorm.io/gorm\"\n\t\"moul.io/zapgorm2\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/utils\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n)\n\ntype DB struct {\n\t*gorm.DB\n}\n\nfunc NewDBStore(lc fx.Lifecycle, config *config.Config) (*DB, error) {\n\terr := os.MkdirAll(config.DataDir, 0o777) // #nosec\n\tif err != nil {\n\t\tlog.Error(\"Failed to create Dashboard storage directory\", zap.Error(err))\n\t\treturn nil, err\n\t}\n\n\tp := path.Join(config.DataDir, \"dashboard.sqlite.db\")\n\tlog.Info(\"Dashboard initializing local storage file\", zap.String(\"path\", p))\n\tgormDB, err := gorm.Open(sqlite.Open(p), &gorm.Config{\n\t\tLogger: zapgorm2.New(log.L()),\n\t})\n\tif err != nil {\n\t\tlog.Error(\"Failed to open Dashboard storage file\", zap.Error(err))\n\t\treturn nil, err\n\t}\n\n\tdb := &DB{gormDB}\n\n\tlc.Append(fx.Hook{\n\t\tOnStop: func(context.Context) error {\n\t\t\treturn utils.CloseTiDBConnection(db.DB)\n\t\t},\n\t})\n\n\treturn db, nil\n}\n"
  },
  {
    "path": "pkg/httpc/client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage httpc\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"time\"\n\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n)\n\nconst (\n\tdefaultTimeout = time.Second * 10\n)\n\ntype Client struct {\n\thttp.Client\n\n\theader http.Header\n}\n\nfunc NewHTTPClient(lc fx.Lifecycle, config *config.Config) *Client {\n\tcli := http.Client{\n\t\tTransport: &http.Transport{\n\t\t\tDialTLS: func(network, addr string) (net.Conn, error) {\n\t\t\t\tconn, err := tls.Dial(network, addr, config.ClusterTLSConfig)\n\t\t\t\treturn conn, err\n\t\t\t},\n\t\t\tTLSClientConfig: config.ClusterTLSConfig,\n\t\t},\n\t\tTimeout: defaultTimeout,\n\t}\n\n\tlc.Append(fx.Hook{\n\t\tOnStop: func(context.Context) error {\n\t\t\tcli.CloseIdleConnections()\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn &Client{\n\t\tClient: cli,\n\t}\n}\n\n// Clone is a temporary solution to the unexpected shared pointer field and race problem\n// TODO: use latest `/util/client` for better api experience.\nfunc (c *Client) Clone() *Client {\n\treturn &Client{\n\t\tClient: c.Client,\n\t\theader: c.header.Clone(),\n\t}\n}\n\nfunc (c Client) WithTimeout(timeout time.Duration) *Client {\n\tc.Timeout = timeout\n\treturn &c\n}\n\nfunc (c *Client) CloneAndAddRequestHeader(key, value string) *Client {\n\tcc := c.Clone()\n\tif cc.header == nil {\n\t\tcc.header = http.Header{}\n\t}\n\tcc.header.Add(key, value)\n\treturn cc\n}\n\n// TODO: Replace using go-resty.\nfunc (c *Client) SendRequest(\n\tctx context.Context,\n\turi string,\n\tmethod string,\n\tbody io.Reader,\n\terrType *errorx.Type,\n\terrOriginComponent string,\n) ([]byte, error) {\n\tres, err := c.Send(ctx, uri, method, body, errType, errOriginComponent)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer res.Response.Body.Close()\n\treturn io.ReadAll(res.Response.Body)\n}\n\nfunc (c *Client) Send(\n\tctx context.Context,\n\turi string,\n\tmethod string,\n\tbody io.Reader,\n\terrType *errorx.Type,\n\terrOriginComponent string,\n) (*Response, error) {\n\treq, err := http.NewRequestWithContext(ctx, method, uri, body)\n\tif err != nil {\n\t\te := errType.Wrap(err, \"Failed to build %s API request\", errOriginComponent)\n\t\tlog.Warn(\"SendRequest failed\", zap.String(\"uri\", uri), zap.Error(err))\n\t\treturn nil, e\n\t}\n\treq.Header = c.header\n\n\tresp, err := c.Do(req)\n\tif err != nil {\n\t\te := errType.Wrap(err, \"Failed to send %s API request\", errOriginComponent)\n\t\tlog.Warn(\"SendRequest failed\", zap.String(\"uri\", uri), zap.Error(err))\n\t\treturn nil, e\n\t}\n\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\tdefer resp.Body.Close()\n\t\tdata, _ := io.ReadAll(resp.Body)\n\t\te := errType.New(\"Request failed with status code %d from %s API: %s\", resp.StatusCode, errOriginComponent, string(data))\n\t\tlog.Warn(\"SendRequest failed\", zap.String(\"uri\", uri), zap.Error(err))\n\t\treturn nil, e\n\t}\n\n\treturn &Response{resp}, nil\n}\n\ntype Response struct {\n\t*http.Response\n}\n\nfunc (r *Response) Body() ([]byte, error) {\n\tdefer r.Response.Body.Close()\n\treturn io.ReadAll(r.Response.Body)\n}\n"
  },
  {
    "path": "pkg/httpc/client_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage httpc\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\t\"go.uber.org/fx/fxtest\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n)\n\nfunc newTestClient(t *testing.T) *Client {\n\tlc := fxtest.NewLifecycle(t)\n\tconfig := &config.Config{}\n\treturn NewHTTPClient(lc, config)\n}\n\nfunc Test_Clone(t *testing.T) {\n\tc := newTestClient(t)\n\tcc := c.Clone()\n\n\trequire.NotSame(t, c, cc)\n\n\trequire.Nil(t, c.header)\n\trequire.Nil(t, cc.header)\n}\n\nfunc Test_CloneAndAddRequestHeader(t *testing.T) {\n\tc := newTestClient(t)\n\tcc := c.CloneAndAddRequestHeader(\"1\", \"11\")\n\n\trequire.Nil(t, c.header)\n\trequire.Equal(t, \"11\", cc.header.Get(\"1\"))\n\n\tcc2 := cc.CloneAndAddRequestHeader(\"2\", \"22\")\n\trequire.Equal(t, \"11\", cc.header.Get(\"1\"))\n\trequire.Equal(t, \"\", cc.header.Get(\"2\"))\n\trequire.Equal(t, \"11\", cc2.header.Get(\"1\"))\n\trequire.Equal(t, \"22\", cc2.header.Get(\"2\"))\n}\n\nfunc Test_Send_withHeader(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = w.Write([]byte(r.Header.Get(\"1\")))\n\t}))\n\tdefer ts.Close()\n\n\tc := newTestClient(t)\n\tresp1, _ := c.Send(context.Background(), ts.URL, http.MethodGet, nil, nil, \"\")\n\td1, _ := resp1.Body()\n\trequire.Equal(t, \"\", string(d1))\n\n\tcc := c.CloneAndAddRequestHeader(\"1\", \"11\")\n\tresp2, _ := cc.Send(context.Background(), ts.URL, http.MethodGet, nil, nil, \"\")\n\td2, _ := resp2.Body()\n\trequire.Equal(t, \"11\", string(d2))\n\n\tresp3, _ := c.Send(context.Background(), ts.URL, http.MethodGet, nil, nil, \"\")\n\td3, _ := resp3.Body()\n\trequire.Equal(t, \"\", string(d3))\n}\n"
  },
  {
    "path": "pkg/keyvisual/decorator/decorator.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package decorator contains all implementations of LabelStrategy.\npackage decorator\n\nimport (\n\t\"encoding/hex\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n)\n\n// LabelKey is the decoration key.\ntype LabelKey struct {\n\tKey    string   `json:\"key\" binding:\"required\"`\n\tLabels []string `json:\"labels\" binding:\"required\"`\n}\n\n// LabelStrategy requires cross-border determination and key decoration scheme.\n// It supports dynamic reload configuration and generation of an actuator.\ntype LabelStrategy interface {\n\tReloadConfig(cfg *config.KeyVisualConfig)\n\tNewLabeler() Labeler\n}\n\n// Labeler is an executor of LabelStrategy, and its functions should not be called concurrently.\ntype Labeler interface {\n\t// CrossBorder determines whether two keys not belong to the same logical range.\n\tCrossBorder(startKey, endKey string) bool\n\t// Label returns the Label information of the keys.\n\tLabel(keys []string) []LabelKey\n}\n\n// NaiveLabelStrategy is one of the simplest LabelStrategy.\nfunc NaiveLabelStrategy() LabelStrategy {\n\treturn naiveLabelStrategy{}\n}\n\ntype naiveLabelStrategy struct{}\n\ntype naiveLabeler struct{}\n\nfunc (s naiveLabelStrategy) ReloadConfig(_ *config.KeyVisualConfig) {}\n\nfunc (s naiveLabelStrategy) NewLabeler() Labeler {\n\treturn naiveLabeler{}\n}\n\n// CrossBorder always returns false. So naiveLabelStrategy believes that there are no cross-border situations.\nfunc (e naiveLabeler) CrossBorder(_, _ string) bool {\n\treturn false\n}\n\n// Label only encodes the keys.\nfunc (e naiveLabeler) Label(keys []string) []LabelKey {\n\tlabelKeys := make([]LabelKey, len(keys))\n\tfor i, key := range keys {\n\t\tstr := hex.EncodeToString([]byte(key))\n\t\tlabelKeys[i] = LabelKey{\n\t\t\tKey:    str,\n\t\t\tLabels: []string{str},\n\t\t}\n\t}\n\treturn labelKeys\n}\n"
  },
  {
    "path": "pkg/keyvisual/decorator/decorator_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage decorator\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/check\"\n)\n\nfunc TestDecorator(t *testing.T) {\n\tcheck.TestingT(t)\n}\n"
  },
  {
    "path": "pkg/keyvisual/decorator/separator.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage decorator\n\nimport (\n\t\"strings\"\n\t\"sync/atomic\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n)\n\n// SeparatorLabelStrategy implements the LabelStrategy interface. It obtains label information after splitting the key.\nfunc SeparatorLabelStrategy(cfg *config.KeyVisualConfig) LabelStrategy {\n\ts := &separatorLabelStrategy{}\n\ts.Separator.Store(cfg.PolicyKVSeparator)\n\treturn s\n}\n\ntype separatorLabelStrategy struct {\n\tSeparator atomic.Value\n}\n\ntype separatorLabeler struct {\n\tSeparator string\n}\n\n// ReloadConfig reset separator.\nfunc (s *separatorLabelStrategy) ReloadConfig(cfg *config.KeyVisualConfig) {\n\ts.Separator.Store(cfg.PolicyKVSeparator)\n\tlog.Debug(\"Reload config\", zap.String(\"separator\", cfg.PolicyKVSeparator))\n}\n\nfunc (s *separatorLabelStrategy) NewLabeler() Labeler {\n\treturn &separatorLabeler{\n\t\tSeparator: s.Separator.Load().(string),\n\t}\n}\n\n// CrossBorder is temporarily not considering cross-border logic.\nfunc (e *separatorLabeler) CrossBorder(_, _ string) bool {\n\treturn false\n}\n\n// Label uses separator to split key.\nfunc (e *separatorLabeler) Label(keys []string) []LabelKey {\n\tlabelKeys := make([]LabelKey, len(keys))\n\tfor i, key := range keys {\n\t\tvar labels []string\n\t\tif e.Separator == \"\" {\n\t\t\tlabels = []string{key}\n\t\t} else {\n\t\t\tlabels = strings.Split(key, e.Separator)\n\t\t}\n\t\tlabelKeys[i] = LabelKey{\n\t\t\tKey:    key,\n\t\t\tLabels: labels,\n\t\t}\n\t}\n\treturn labelKeys\n}\n"
  },
  {
    "path": "pkg/keyvisual/decorator/tidb.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage decorator\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/region\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb/model\"\n)\n\n// TiDBLabelStrategy implements the LabelStrategy interface. It obtains Label Information from TiDB.\nfunc TiDBLabelStrategy(lc fx.Lifecycle, wg *sync.WaitGroup, etcdClient *clientv3.Client, tidbClient *tidb.Client) LabelStrategy {\n\ts := &tidbLabelStrategy{\n\t\tEtcdClient:    etcdClient,\n\t\ttidbClient:    tidbClient,\n\t\tSchemaVersion: -1,\n\t}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\twg.Go(func() {\n\t\t\t\ts.Background(ctx)\n\t\t\t})\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn s\n}\n\ntype tableDetail struct {\n\tName    string\n\tDB      string\n\tID      int64\n\tIndices map[int64]string\n}\n\ntype tidbLabelStrategy struct {\n\tConfig     *config.Config\n\tEtcdClient *clientv3.Client\n\n\tTableMap      sync.Map\n\ttidbClient    *tidb.Client\n\tSchemaVersion int64\n\tTidbAddress   []string\n}\n\ntype tidbLabeler struct {\n\tTableMap *sync.Map\n\tBuffer   model.KeyInfoBuffer\n}\n\nfunc (s *tidbLabelStrategy) ReloadConfig(_ *config.KeyVisualConfig) {}\n\nfunc (s *tidbLabelStrategy) Background(ctx context.Context) {\n\tticker := time.NewTicker(time.Minute)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase <-ticker.C:\n\t\t\ts.updateMap(ctx)\n\t\t}\n\t}\n}\n\nfunc (s *tidbLabelStrategy) NewLabeler() Labeler {\n\treturn &tidbLabeler{\n\t\tTableMap: &s.TableMap,\n\t}\n}\n\n// CrossBorder does not allow cross tables or cross indexes within a table.\nfunc (e *tidbLabeler) CrossBorder(startKey, endKey string) bool {\n\tstartInfo, _ := e.Buffer.DecodeKey(region.Bytes(startKey))\n\tstartIsMeta, startTableID := startInfo.MetaOrTable()\n\tstartIndex := startInfo.IndexInfo()\n\n\tendInfo, _ := e.Buffer.DecodeKey(region.Bytes(endKey))\n\tendIsMeta, endTableID := endInfo.MetaOrTable()\n\tendIndex := endInfo.IndexInfo()\n\n\tif startIsMeta || endIsMeta {\n\t\treturn startIsMeta != endIsMeta\n\t}\n\tif startTableID != endTableID {\n\t\treturn true\n\t}\n\treturn startIndex != endIndex\n}\n\n// Label will parse the ID information of the table and index.\nfunc (e *tidbLabeler) Label(keys []string) []LabelKey {\n\tlabelKeys := make([]LabelKey, len(keys))\n\tfor i, key := range keys {\n\t\tlabelKeys[i] = e.label(key)\n\t}\n\n\tif keys[0] == \"\" {\n\t\tlabelKeys[0] = globalStart\n\t}\n\tendIndex := len(keys) - 1\n\tif keys[endIndex] == \"\" {\n\t\tlabelKeys[endIndex] = globalEnd\n\t}\n\n\treturn labelKeys\n}\n\nfunc (e *tidbLabeler) label(key string) (label LabelKey) {\n\tkeyBytes := region.Bytes(key)\n\tlabel.Key = hex.EncodeToString(keyBytes)\n\tkeyInfo, _ := e.Buffer.DecodeKey(keyBytes)\n\n\tisMeta, tableID := keyInfo.MetaOrTable()\n\tif isMeta {\n\t\tlabel.Labels = append(label.Labels, \"meta\")\n\t\treturn\n\t}\n\n\tvar detail *tableDetail\n\tif v, ok := e.TableMap.Load(tableID); ok {\n\t\tdetail = v.(*tableDetail)\n\t\tlabel.Labels = append(label.Labels, detail.DB, detail.Name)\n\t} else {\n\t\tlabel.Labels = append(label.Labels, fmt.Sprintf(\"table_%d\", tableID))\n\t}\n\n\tif isCommonHandle, rowID := keyInfo.RowInfo(); isCommonHandle {\n\t\tlabel.Labels = append(label.Labels, \"row\")\n\t} else if rowID != 0 {\n\t\tlabel.Labels = append(label.Labels, fmt.Sprintf(\"row_%d\", rowID))\n\t} else if indexID := keyInfo.IndexInfo(); indexID != 0 {\n\t\tif detail == nil {\n\t\t\tlabel.Labels = append(label.Labels, fmt.Sprintf(\"index_%d\", indexID))\n\t\t} else if name, ok := detail.Indices[indexID]; ok {\n\t\t\tlabel.Labels = append(label.Labels, name)\n\t\t} else {\n\t\t\tlabel.Labels = append(label.Labels, fmt.Sprintf(\"index_%d\", indexID))\n\t\t}\n\t}\n\treturn\n}\n\nvar globalStart = LabelKey{\n\tKey:    \"\",\n\tLabels: []string{\"meta\"},\n}\n\nvar globalEnd = LabelKey{\n\tKey:    \"\",\n\tLabels: []string{},\n}\n"
  },
  {
    "path": "pkg/keyvisual/decorator/tidb_requests.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage decorator\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb/model\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nconst (\n\tschemaVersionPath   = \"/tidb/ddl/global_schema_version\"\n\tetcdGetTimeout      = time.Second\n\ttableInfosBatchSize = 512\n)\n\nvar (\n\tErrNS          = errorx.NewNamespace(\"error.keyvisual\")\n\tErrNSDecorator = ErrNS.NewSubNamespace(\"decorator\")\n\tErrInvalidData = ErrNSDecorator.NewType(\"invalid_data\")\n)\n\nfunc (s *tidbLabelStrategy) updateMap(ctx context.Context) {\n\t// check schema version\n\tectx, cancel := context.WithTimeout(ctx, etcdGetTimeout)\n\tresp, err := s.EtcdClient.Get(ectx, schemaVersionPath)\n\tcancel()\n\tif err != nil || len(resp.Kvs) != 1 {\n\t\tif s.SchemaVersion != -1 {\n\t\t\tlog.Warn(\"failed to get tidb schema version\", zap.Error(err))\n\t\t} else {\n\t\t\tlog.Debug(\"failed to get tidb schema version, maybe not a db cluster\", zap.Error(err))\n\t\t}\n\t\treturn\n\t}\n\tschemaVersion, err := strconv.ParseInt(string(resp.Kvs[0].Value), 10, 64)\n\tif err != nil {\n\t\tif s.SchemaVersion != -1 {\n\t\t\tlog.Warn(\"failed to get tidb schema version\", zap.Error(err))\n\t\t} else {\n\t\t\tlog.Debug(\"failed to get tidb schema version, maybe not a db cluster\", zap.Error(err))\n\t\t}\n\t\treturn\n\t}\n\tif schemaVersion == s.SchemaVersion {\n\t\tlog.Debug(\"schema version has not changed, skip this update\")\n\t\treturn\n\t}\n\n\tlog.Debug(\"schema version has changed\", zap.Int64(\"old\", s.SchemaVersion), zap.Int64(\"new\", schemaVersion))\n\n\t// get all database info\n\tvar dbInfos []*model.DBInfo\n\tif err := s.request(\"/schema\", &dbInfos); err != nil {\n\t\tlog.Error(\"fail to send schema request\", zap.String(\"component\", distro.R().TiDB), zap.Error(err))\n\t\treturn\n\t}\n\n\t// get all table info\n\tupdateSuccess := true\n\tfor _, db := range dbInfos {\n\t\tif db.State == model.StateNone {\n\t\t\tcontinue\n\t\t}\n\t\tvar tableInfos []*model.TableInfo\n\t\tencodeName := url.PathEscape(db.Name.O)\n\t\tif err := s.request(fmt.Sprintf(\"/schema/%s?id_name_only=true\", encodeName), &tableInfos); err != nil {\n\t\t\tlog.Error(\"fail to send schema request\", zap.String(\"component\", distro.R().TiDB), zap.Error(err))\n\t\t\tupdateSuccess = false\n\t\t\tcontinue\n\t\t}\n\t\tif len(tableInfos) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif tableInfos[0].Version != nil {\n\t\t\t// ?id_name_only=true doesn't work, fallback.\n\t\t\tlog.Debug(\"use fallback\")\n\t\t\ts.updateTableMap(db.Name.O, tableInfos)\n\t\t\tcontinue\n\t\t}\n\n\t\t/* Split into small batches */\n\t\tlog.Debug(\"use batch\")\n\n\t\tvar tableIDBatches [][]string\n\t\tbatch := make([]string, 0, tableInfosBatchSize)\n\t\tn := 0\n\t\tfor _, info := range tableInfos {\n\t\t\tbatch = append(batch, strconv.FormatInt(info.ID, 10))\n\t\t\tn++\n\t\t\tif n == tableInfosBatchSize {\n\t\t\t\ttableIDBatches = append(tableIDBatches, batch)\n\t\t\t\tbatch = make([]string, 0, tableInfosBatchSize)\n\t\t\t\tn = 0\n\t\t\t}\n\t\t}\n\t\tif len(batch) > 0 {\n\t\t\ttableIDBatches = append(tableIDBatches, batch)\n\t\t}\n\t\tfor _, batch := range tableIDBatches {\n\t\t\tvar tableInfoBatch map[string]*model.TableInfo\n\t\t\tif err := s.request(fmt.Sprintf(\"/schema?table_ids=%s\", strings.Join(batch, \",\")), &tableInfoBatch); err != nil {\n\t\t\t\tlog.Error(\"fail to send schema request\", zap.String(\"component\", distro.R().TiDB), zap.Error(err))\n\t\t\t\tupdateSuccess = false\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(tableInfoBatch) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttableInfoBatchSlice := make([]*model.TableInfo, 0, len(tableInfoBatch))\n\t\t\tfor _, info := range tableInfoBatch {\n\t\t\t\ttableInfoBatchSlice = append(tableInfoBatchSlice, info)\n\t\t\t}\n\t\t\ts.updateTableMap(db.Name.O, tableInfoBatchSlice)\n\t\t}\n\t}\n\n\t// update schema version\n\tif updateSuccess {\n\t\ts.SchemaVersion = schemaVersion\n\t}\n}\n\nfunc (s *tidbLabelStrategy) updateTableMap(dbname string, tableInfos []*model.TableInfo) {\n\tif len(tableInfos) == 0 {\n\t\treturn\n\t}\n\tfor _, table := range tableInfos {\n\t\tindices := make(map[int64]string, len(table.Indices))\n\t\tfor _, index := range table.Indices {\n\t\t\tindices[index.ID] = index.Name.O\n\t\t}\n\t\tdetail := &tableDetail{\n\t\t\tName:    table.Name.O,\n\t\t\tDB:      dbname,\n\t\t\tID:      table.ID,\n\t\t\tIndices: indices,\n\t\t}\n\t\ts.TableMap.Store(table.ID, detail)\n\t\tif partition := table.GetPartitionInfo(); partition != nil {\n\t\t\tfor _, partitionDef := range partition.Definitions {\n\t\t\t\tdetail := &tableDetail{\n\t\t\t\t\tName:    fmt.Sprintf(\"%s/%s\", table.Name.O, partitionDef.Name.O),\n\t\t\t\t\tDB:      dbname,\n\t\t\t\t\tID:      partitionDef.ID,\n\t\t\t\t\tIndices: indices,\n\t\t\t\t}\n\t\t\t\ts.TableMap.Store(partitionDef.ID, detail)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *tidbLabelStrategy) request(path string, v interface{}) error {\n\tdata, err := s.tidbClient.SendGetRequest(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err = json.Unmarshal(data, v); err != nil {\n\t\treturn ErrInvalidData.Wrap(err, \"%s schema API unmarshal failed\", distro.R().TiDB)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/keyvisual/decorator/tidb_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage decorator\n\nimport (\n\t\"github.com/pingcap/check\"\n)\n\nvar _ = check.Suite(&testTiDBSuite{})\n\ntype testTiDBSuite struct{}\n"
  },
  {
    "path": "pkg/keyvisual/input/api.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage input\n\nimport (\n\t\"encoding/hex\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"sort\"\n\n\t\"github.com/joomcode/errorx\"\n\n\tregionpkg \"github.com/pingcap/tidb-dashboard/pkg/keyvisual/region\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nconst ScanRegionsLimit = 51200\n\nvar (\n\tErrNS          = errorx.NewNamespace(\"error.keyvisual\")\n\tErrNSInput     = ErrNS.NewSubNamespace(\"input\")\n\tErrInvalidData = ErrNSInput.NewType(\"invalid_data\")\n)\n\n// RegionInfo records detail region info for api usage.\ntype RegionInfo struct {\n\tID              uint64 `json:\"id\"`\n\tStartKey        string `json:\"start_key\"`\n\tEndKey          string `json:\"end_key\"`\n\tWrittenBytes    uint64 `json:\"written_bytes\"`\n\tReadBytes       uint64 `json:\"read_bytes\"`\n\tWrittenKeys     uint64 `json:\"written_keys\"`\n\tReadKeys        uint64 `json:\"read_keys\"`\n\tApproximateSize int64  `json:\"approximate_size\"`\n\tApproximateKeys int64  `json:\"approximate_keys\"`\n}\n\n// RegionsInfo contains some regions with the detailed region info.\ntype RegionsInfo struct {\n\tCount   int           `json:\"count\"`\n\tRegions []*RegionInfo `json:\"regions\"`\n}\n\nfunc (rs *RegionsInfo) Len() int {\n\treturn rs.Count\n}\n\nfunc (rs *RegionsInfo) GetKeys() []string {\n\tkeys := make([]string, rs.Count+1)\n\tkeys[0] = rs.Regions[0].StartKey\n\tendKeys := keys[1:]\n\tfor i, region := range rs.Regions {\n\t\tendKeys[i] = region.EndKey\n\t}\n\treturn keys\n}\n\nfunc (rs *RegionsInfo) GetValues(tag regionpkg.StatTag) []uint64 {\n\tvalues := make([]uint64, rs.Count)\n\tswitch tag {\n\tcase regionpkg.WrittenBytes:\n\t\tfor i, region := range rs.Regions {\n\t\t\tvalues[i] = region.WrittenBytes\n\t\t}\n\tcase regionpkg.ReadBytes:\n\t\tfor i, region := range rs.Regions {\n\t\t\tvalues[i] = region.ReadBytes\n\t\t}\n\tcase regionpkg.WrittenKeys:\n\t\tfor i, region := range rs.Regions {\n\t\t\tvalues[i] = region.WrittenKeys\n\t\t}\n\tcase regionpkg.ReadKeys:\n\t\tfor i, region := range rs.Regions {\n\t\t\tvalues[i] = region.ReadKeys\n\t\t}\n\tcase regionpkg.Integration:\n\t\tfor i, region := range rs.Regions {\n\t\t\tvalues[i] = region.WrittenBytes + region.ReadBytes\n\t\t}\n\tdefault:\n\t\tpanic(\"unreachable\")\n\t}\n\treturn values\n}\n\nfunc read(data []byte) (*RegionsInfo, error) {\n\tregions := &RegionsInfo{}\n\tif err := json.Unmarshal(data, regions); err != nil {\n\t\treturn nil, ErrInvalidData.Wrap(err, \"%s regions API unmarshal failed\", distro.R().PD)\n\t}\n\n\tfor _, region := range regions.Regions {\n\t\tstartBytes, err := hex.DecodeString(region.StartKey)\n\t\tif err != nil {\n\t\t\treturn nil, ErrInvalidData.Wrap(err, \"%s regions API unmarshal failed\", distro.R().PD)\n\t\t}\n\t\tregion.StartKey = regionpkg.String(startBytes)\n\t\tendBytes, err := hex.DecodeString(region.EndKey)\n\t\tif err != nil {\n\t\t\treturn nil, ErrInvalidData.Wrap(err, \"%s regions API unmarshal failed\", distro.R().PD)\n\t\t}\n\t\tregion.EndKey = regionpkg.String(endBytes)\n\t}\n\n\tsort.Slice(regions.Regions, func(i, j int) bool {\n\t\treturn regions.Regions[i].StartKey < regions.Regions[j].StartKey\n\t})\n\n\treturn regions, nil\n}\n\nfunc NewAPIPeriodicGetter(pdClient *pd.Client) regionpkg.RegionsInfoGenerator {\n\treturn func() (regionpkg.RegionsInfo, error) {\n\t\tvar mergedRegionsInfo RegionsInfo\n\t\tstartKey := \"\"\n\t\tfor {\n\t\t\tregionsInfo, err := scanRegions(pdClient, startKey, \"\", ScanRegionsLimit)\n\t\t\tif err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\t// Decode the the hex encode code start key and end key.\n\t\t\tfor _, region := range regionsInfo.Regions {\n\t\t\t\tstartBytes, err := hex.DecodeString(region.StartKey)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, ErrInvalidData.Wrap(err, \"%s regions API unmarshal failed\", distro.R().PD)\n\t\t\t\t}\n\t\t\t\tregion.StartKey = regionpkg.String(startBytes)\n\t\t\t\tendBytes, err := hex.DecodeString(region.EndKey)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nil, ErrInvalidData.Wrap(err, \"%s regions API unmarshal failed\", distro.R().PD)\n\t\t\t\t}\n\t\t\t\tregion.EndKey = regionpkg.String(endBytes)\n\t\t\t}\n\t\t\tmergedRegionsInfo.Regions = append(mergedRegionsInfo.Regions, regionsInfo.Regions...)\n\t\t\tmergedRegionsInfo.Count += regionsInfo.Count\n\t\t\tif regionsInfo.Count == 0 || regionsInfo.Regions[len(regionsInfo.Regions)-1].EndKey == \"\" {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tstartKey = regionsInfo.Regions[len(regionsInfo.Regions)-1].EndKey\n\t\t}\n\t\treturn &mergedRegionsInfo, nil\n\t}\n}\n\nfunc scanRegions(pdclient *pd.Client, key, endKey string, limit int) (*RegionsInfo, error) {\n\tvalues := url.Values{\n\t\t\"key\":     {key},\n\t\t\"end_key\": {endKey},\n\t\t\"limit\":   {fmt.Sprintf(\"%d\", limit)},\n\t}\n\n\turl := \"/regions/key\" + \"?\" + values.Encode()\n\n\tdata, err := pdclient.SendGetRequest(url)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar regionsInfo RegionsInfo\n\terr = json.Unmarshal(data, &regionsInfo)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &regionsInfo, nil\n}\n"
  },
  {
    "path": "pkg/keyvisual/input/file.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage input\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/storage\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\ntype fileInput struct {\n\tStartTime time.Time\n\tEndTime   time.Time\n\tNow       time.Time\n}\n\n// FileInput reads files in the specified time range from the ./data directory.\nfunc FileInput(startTime, endTime time.Time) StatInput {\n\treturn &fileInput{\n\t\tStartTime: startTime,\n\t\tEndTime:   endTime,\n\t\tNow:       time.Now(),\n\t}\n}\n\nfunc (input *fileInput) GetStartTime() time.Time {\n\treturn input.Now.Add(input.StartTime.Sub(input.EndTime))\n}\n\nfunc (input *fileInput) Background(_ context.Context, stat *storage.Stat) {\n\tlog.Info(\"keyvisual load files from\", zap.Time(\"start-time\", input.StartTime))\n\tfileTime := input.StartTime\n\tfor !fileTime.After(input.EndTime) {\n\t\tregions, err := readFile(fileTime)\n\t\tfileTime = fileTime.Add(time.Minute)\n\t\tif err == nil {\n\t\t\tstat.Append(regions, input.Now.Add(fileTime.Sub(input.EndTime)))\n\t\t}\n\t}\n\tlog.Info(\"keyvisual load files to\", zap.Time(\"end-time\", input.EndTime))\n}\n\nfunc readFile(fileTime time.Time) (*RegionsInfo, error) {\n\tfileName := fileTime.Format(\"./data/20060102-15-04.json\")\n\tfile, err := os.Open(filepath.Clean(fileName))\n\tif err != nil {\n\t\treturn nil, ErrInvalidData.Wrap(err, \"%s regions API unmarshal failed, from file %s\", distro.R().PD, fileName)\n\t}\n\tdefer file.Close() // #nosec\n\tdata, err := io.ReadAll(file)\n\tif err != nil {\n\t\treturn nil, ErrInvalidData.Wrap(err, \"%s regions API unmarshal failed, from file %s\", distro.R().PD, fileName)\n\t}\n\treturn read(data)\n}\n"
  },
  {
    "path": "pkg/keyvisual/input/input.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package input defines several different data inputs.\npackage input\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/pingcap/log\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/region\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/storage\"\n)\n\n// StatInput is the interface that different data inputs need to implement.\ntype StatInput interface {\n\tGetStartTime() time.Time\n\tBackground(ctx context.Context, stat *storage.Stat)\n}\n\nfunc NewStatInput(provider *region.DataProvider) StatInput {\n\tif provider.FileStartTime == 0 && provider.FileEndTime == 0 {\n\t\tif provider.PeriodicGetter == nil {\n\t\t\tlog.Fatal(\"Empty DataProvider is not allowed\")\n\t\t}\n\t\treturn PeriodicInput(provider.PeriodicGetter)\n\t}\n\tstartTime := time.Unix(provider.FileStartTime, 0)\n\tendTime := time.Unix(provider.FileEndTime, 0)\n\treturn FileInput(startTime, endTime)\n}\n"
  },
  {
    "path": "pkg/keyvisual/input/periodic.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage input\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/region\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/storage\"\n)\n\ntype periodicInput struct {\n\tPeriodicGetter region.RegionsInfoGenerator\n}\n\nfunc PeriodicInput(periodicGetter region.RegionsInfoGenerator) StatInput {\n\treturn &periodicInput{\n\t\tPeriodicGetter: periodicGetter,\n\t}\n}\n\nfunc (input *periodicInput) GetStartTime() time.Time {\n\treturn time.Now()\n}\n\nfunc (input *periodicInput) Background(ctx context.Context, stat *storage.Stat) {\n\tticker := time.NewTicker(time.Minute)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase <-ticker.C:\n\t\t\tregions, err := input.PeriodicGetter()\n\t\t\tif err != nil {\n\t\t\t\tlog.Warn(\"can not get RegionsInfo\", zap.Error(err))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tendTime := time.Now()\n\t\t\tstat.Append(regions, endTime)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/keyvisual/manager.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage keyvisual\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"sync\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nfunc (s *Service) managerHook() fx.Hook {\n\tvar wg sync.WaitGroup\n\treturn fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\twg.Go(func() {\n\t\t\t\ts.managerLoop(ctx)\n\t\t\t})\n\t\t\treturn nil\n\t\t},\n\t\tOnStop: func(context.Context) error {\n\t\t\twg.Wait()\n\t\t\treturn nil\n\t\t},\n\t}\n}\n\nfunc (s *Service) managerLoop(ctx context.Context) {\n\tch := s.cfgManager.NewPushChannel()\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\ts.stopService()\n\t\t\treturn\n\t\tcase cfg, ok := <-ch:\n\t\t\tif !ok {\n\t\t\t\ts.stopService()\n\t\t\t\treturn\n\t\t\t}\n\t\t\ts.resetKeyVisualConfig(ctx, cfg)\n\t\t}\n\t}\n}\n\nfunc (s *Service) resetKeyVisualConfig(ctx context.Context, cfg *config.DynamicConfig) {\n\tif !cfg.KeyVisual.AutoCollectionDisabled {\n\t\tif s.keyVisualCfg != nil && s.keyVisualCfg.Policy != cfg.KeyVisual.Policy {\n\t\t\ts.stopService()\n\t\t}\n\t\ts.reloadKeyVisualConfig(&cfg.KeyVisual)\n\t\ts.startService(ctx)\n\t} else {\n\t\ts.stopService()\n\t\ts.reloadKeyVisualConfig(&cfg.KeyVisual)\n\t}\n}\n\nfunc (s *Service) startService(ctx context.Context) {\n\tif s.IsRunning() {\n\t\treturn\n\t}\n\tif err := s.Start(ctx); err != nil {\n\t\tlog.Error(\"Can not start key visual service\", zap.Error(err))\n\t} else {\n\t\tlog.Info(\"Key visual service is started\")\n\t}\n}\n\nfunc (s *Service) stopService() {\n\tif !s.IsRunning() {\n\t\treturn\n\t}\n\tif err := s.Stop(context.Background()); err != nil {\n\t\tlog.Error(\"Can not stop key visual service\", zap.Error(err))\n\t} else {\n\t\tlog.Info(\"Key visual service is stopped\")\n\t}\n}\n\n// @Summary Get Key Visual Dynamic Config\n// @Success 200 {object} config.KeyVisualConfig\n// @Router /keyvisual/config [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) getDynamicConfig(c *gin.Context) {\n\tdc, err := s.cfgManager.Get()\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, dc.KeyVisual)\n}\n\n// @Summary Set Key Visual Dynamic Config\n// @Param request body config.KeyVisualConfig true \"Request body\"\n// @Success 200 {object} config.KeyVisualConfig\n// @Router /keyvisual/config [put]\n// @Security JwtAuth\n// @Failure 400 {object} rest.ErrorResponse\n// @Failure 401 {object} rest.ErrorResponse\n// @Failure 500 {object} rest.ErrorResponse\nfunc (s *Service) setDynamicConfig(c *gin.Context) {\n\tvar req config.KeyVisualConfig\n\tif err := c.ShouldBindJSON(&req); err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.NewWithNoMessage())\n\t\treturn\n\t}\n\tvar opt config.DynamicConfigOption = func(dc *config.DynamicConfig) {\n\t\tdc.KeyVisual = req\n\t}\n\tif err := s.cfgManager.Modify(opt); err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n\tc.JSON(http.StatusOK, req)\n}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/average.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\n// AverageSplitStrategy adopts the strategy of equal distribution when buckets are split.\nfunc AverageSplitStrategy() SplitStrategy {\n\treturn averageSplitStrategy{}\n}\n\ntype averageSplitStrategy struct{}\n\ntype averageSplitter struct{}\n\nfunc (averageSplitStrategy) NewSplitter(_ []chunk, _ []string) Splitter {\n\treturn averageSplitter{}\n}\n\nfunc (averageSplitter) Split(dst, src chunk, tag splitTag, _ int) {\n\tCheckPartOf(dst.Keys, src.Keys)\n\n\tif len(dst.Keys) == len(src.Keys) {\n\t\tswitch tag {\n\t\tcase splitTo:\n\t\t\tcopy(dst.Values, src.Values)\n\t\tcase splitAdd:\n\t\t\tfor i, v := range src.Values {\n\t\t\t\tdst.Values[i] += v\n\t\t\t}\n\t\tdefault:\n\t\t\tpanic(\"unreachable\")\n\t\t}\n\t\treturn\n\t}\n\n\tstart := 0\n\tfor startKey := src.Keys[0]; !equal(dst.Keys[start], startKey); {\n\t\tstart++\n\t}\n\tend := start + 1\n\n\tswitch tag {\n\tcase splitTo:\n\t\tfor i, key := range src.Keys[1:] {\n\t\t\tfor !equal(dst.Keys[end], key) {\n\t\t\t\tend++\n\t\t\t}\n\t\t\tvalue := src.Values[i] / uint64(end-start)\n\t\t\tfor ; start < end; start++ {\n\t\t\t\tdst.Values[start] = value\n\t\t\t}\n\t\t\tend++\n\t\t}\n\tcase splitAdd:\n\t\tfor i, key := range src.Keys[1:] {\n\t\t\tfor !equal(dst.Keys[end], key) {\n\t\t\t\tend++\n\t\t\t}\n\t\t\tvalue := src.Values[i] / uint64(end-start)\n\t\t\tfor ; start < end; start++ {\n\t\t\t\tdst.Values[start] += value\n\t\t\t}\n\t\t\tend++\n\t\t}\n\tdefault:\n\t\tpanic(\"unreachable\")\n\t}\n}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/average_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\nimport (\n\t\"github.com/pingcap/check\"\n)\n\nvar _ = check.Suite(&testAverageSuite{})\n\ntype testAverageSuite struct{}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/axis.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\nimport (\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/decorator\"\n)\n\n// Axis stores consecutive buckets. Each bucket has StartKey, EndKey, and some statistics. The EndKey of each bucket is\n// the StartKey of its next bucket. The actual data structure is stored in columns. Therefore satisfies:\n// len(Keys) == len(ValuesList[i]) + 1. In particular, ValuesList[0] is the base column.\ntype Axis struct {\n\tKeys       []string\n\tValuesList [][]uint64\n}\n\n// CreateAxis checks the given parameters and uses them to build the Axis.\nfunc CreateAxis(keys []string, valuesList [][]uint64) Axis {\n\tkeysLen := len(keys)\n\tif keysLen <= 1 {\n\t\tpanic(\"Keys length must be greater than 1\")\n\t}\n\tif len(valuesList) == 0 {\n\t\tpanic(\"ValuesList length must be greater than 0\")\n\t}\n\tfor _, values := range valuesList {\n\t\tif keysLen != len(values)+1 {\n\t\t\tpanic(\"Keys length must be equal to Values length + 1\")\n\t\t}\n\t}\n\treturn Axis{\n\t\tKeys:       keys,\n\t\tValuesList: valuesList,\n\t}\n}\n\n// CreateEmptyAxis constructs a minimal empty Axis with the given parameters.\nfunc CreateEmptyAxis(startKey, endKey string, valuesListLen int) Axis {\n\tkeys := []string{startKey, endKey}\n\tvalues := []uint64{0}\n\tvaluesList := make([][]uint64, valuesListLen)\n\tfor i := range valuesList {\n\t\tvaluesList[i] = values\n\t}\n\treturn CreateAxis(keys, valuesList)\n}\n\n// Shrink reduces all statistical values.\nfunc (axis *Axis) Shrink(ratio uint64) {\n\tfor _, values := range axis.ValuesList {\n\t\tfor i := range values {\n\t\t\tvalues[i] /= ratio\n\t\t}\n\t}\n}\n\n// Range returns a sub Axis with specified range.\nfunc (axis *Axis) Range(startKey string, endKey string) Axis {\n\tstart, end, ok := KeysRange(axis.Keys, startKey, endKey)\n\tif !ok {\n\t\treturn CreateEmptyAxis(startKey, endKey, len(axis.ValuesList))\n\t}\n\tkeys := axis.Keys[start:end]\n\tvaluesList := make([][]uint64, len(axis.ValuesList))\n\tfor i := range valuesList {\n\t\tvaluesList[i] = axis.ValuesList[i][start : end-1]\n\t}\n\treturn CreateAxis(keys, valuesList)\n}\n\n// Focus uses the base column as the chunk for the Focus operation to obtain the partitioning scheme, and uses this to\n// reduce other columns.\nfunc (axis *Axis) Focus(labeler decorator.Labeler, threshold uint64, ratio int, target int) Axis {\n\tif target >= len(axis.Keys)-1 {\n\t\treturn *axis\n\t}\n\n\tbaseChunk := createChunk(axis.Keys, axis.ValuesList[0])\n\tnewChunk := baseChunk.Focus(labeler, threshold, ratio, target, MergeColdLogicalRange)\n\tvaluesListLen := len(axis.ValuesList)\n\tnewValuesList := make([][]uint64, valuesListLen)\n\tnewValuesList[0] = newChunk.Values\n\tfor i := 1; i < valuesListLen; i++ {\n\t\tbaseChunk.SetValues(axis.ValuesList[i])\n\t\tnewValuesList[i] = baseChunk.Reduce(newChunk.Keys).Values\n\t}\n\treturn CreateAxis(newChunk.Keys, newValuesList)\n}\n\n// Divide uses the base column as the chunk for the Divide operation to obtain the partitioning scheme, and uses this to\n// reduce other columns.\nfunc (axis *Axis) Divide(labeler decorator.Labeler, target int) Axis {\n\tif target >= len(axis.Keys)-1 {\n\t\treturn *axis\n\t}\n\n\tbaseChunk := createChunk(axis.Keys, axis.ValuesList[0])\n\tnewChunk := baseChunk.Divide(labeler, target, MergeColdLogicalRange)\n\tvaluesListLen := len(axis.ValuesList)\n\tnewValuesList := make([][]uint64, valuesListLen)\n\tnewValuesList[0] = newChunk.Values\n\tfor i := 1; i < valuesListLen; i++ {\n\t\tbaseChunk.SetValues(axis.ValuesList[i])\n\t\tnewValuesList[i] = baseChunk.Reduce(newChunk.Keys).Values\n\t}\n\treturn CreateAxis(newChunk.Keys, newValuesList)\n}\n\ntype FocusMode int\n\nconst (\n\tNotMergeLogicalRange FocusMode = iota\n\tMergeColdLogicalRange\n)\n\ntype chunk struct {\n\t// Keys and ValuesList[i] from Axis\n\tKeys   []string\n\tValues []uint64\n}\n\nfunc createChunk(keys []string, values []uint64) chunk {\n\tkeysLen := len(keys)\n\tif keysLen <= 1 {\n\t\tpanic(\"Keys length must be greater than 1\")\n\t}\n\tif keysLen != len(values)+1 {\n\t\tpanic(\"Keys length must be equal to Values length + 1\")\n\t}\n\treturn chunk{\n\t\tKeys:   keys,\n\t\tValues: values,\n\t}\n}\n\nfunc createZeroChunk(keys []string) chunk {\n\tkeysLen := len(keys)\n\tif keysLen <= 1 {\n\t\tpanic(\"Keys length must be greater than 1\")\n\t}\n\treturn createChunk(keys, make([]uint64, keysLen-1))\n}\n\nfunc (c *chunk) SetValues(values []uint64) {\n\tif len(values)+1 != len(c.Keys) {\n\t\tpanic(\"Keys length must be equal to Values length + 1\")\n\t}\n\tc.Values = values\n}\n\nfunc (c *chunk) SetZeroValues() {\n\tnewValues := make([]uint64, len(c.Values))\n\tc.SetValues(newValues)\n}\n\n// Set all values to 0.\nfunc (c *chunk) Clear() {\n\tMemsetUint64(c.Values, 0)\n}\n\n// Calculation\n\n// Reduce generates new chunks based on the more sparse newKeys.\nfunc (c *chunk) Reduce(newKeys []string) chunk {\n\tkeys := c.Keys\n\tCheckReduceOf(keys, newKeys)\n\n\tnewValues := make([]uint64, len(newKeys)-1)\n\n\tif len(keys) == len(newKeys) {\n\t\tcopy(newValues, c.Values)\n\t\treturn createChunk(newKeys, newValues)\n\t}\n\n\tendKeys := newKeys[1:]\n\tj := 0\n\tfor i, value := range c.Values {\n\t\tif i > 0 && equal(keys[i], endKeys[j]) {\n\t\t\tj++\n\t\t}\n\t\tnewValues[j] += value\n\t}\n\treturn createChunk(newKeys, newValues)\n}\n\n// GetFocusRows estimates the number of rows generated by executing a Focus with a specified threshold.\nfunc (c *chunk) GetFocusRows(threshold uint64) (count int) {\n\tstart := 0\n\tvar bucketSum uint64\n\tgenerateBucket := func(end int) {\n\t\tif end > start {\n\t\t\tcount++\n\t\t\tstart = end\n\t\t\tbucketSum = 0\n\t\t}\n\t}\n\n\tfor i, value := range c.Values {\n\t\tif value >= threshold || bucketSum >= threshold {\n\t\t\tgenerateBucket(i)\n\t\t}\n\t\tbucketSum += value\n\t}\n\tgenerateBucket(len(c.Values))\n\n\treturn\n}\n\n// Given a `threshold`, merge the rows with less traffic,\n// and merge the most `ratio` rows at a time.\n// `target` is the estimated final number of rows.\nfunc (c *chunk) Focus(labeler decorator.Labeler, threshold uint64, ratio int, target int, mode FocusMode) chunk {\n\tnewKeys := make([]string, 0, target)\n\tnewValues := make([]uint64, 0, target)\n\tnewKeys = append(newKeys, c.Keys[0])\n\n\tstart := 0\n\tvar bucketSum uint64\n\tgenerateBucket := func(end int) {\n\t\tif end > start {\n\t\t\tnewKeys = append(newKeys, c.Keys[end])\n\t\t\tnewValues = append(newValues, bucketSum)\n\t\t\tstart = end\n\t\t\tbucketSum = 0\n\t\t}\n\t}\n\n\tfor i, value := range c.Values {\n\t\tif value >= threshold ||\n\t\t\tbucketSum >= threshold ||\n\t\t\ti-start >= ratio ||\n\t\t\tlabeler.CrossBorder(c.Keys[start], c.Keys[i]) {\n\t\t\tgenerateBucket(i)\n\t\t}\n\t\tbucketSum += value\n\t}\n\tgenerateBucket(len(c.Values))\n\n\tnewChunk := createChunk(newKeys, newValues)\n\tif mode == MergeColdLogicalRange && len(newValues) >= target {\n\t\tnewChunk = newChunk.MergeColdLogicalRange(labeler, threshold, target)\n\t}\n\treturn newChunk\n}\n\nfunc (c *chunk) MergeColdLogicalRange(labeler decorator.Labeler, threshold uint64, target int) chunk {\n\tthreshold /= 4 // TODO: This var can be adjusted\n\n\tnewKeys := make([]string, 0, target)\n\tnewValues := make([]uint64, 0, target)\n\tnewKeys = append(newKeys, c.Keys[0])\n\n\tcoldStart := 0\n\tcoldEnd := 0\n\tvar coldRangeSum uint64\n\tmergeColdRange := func() {\n\t\tif coldEnd <= coldStart {\n\t\t\treturn\n\t\t}\n\t\tnewKeys = append(newKeys, c.Keys[coldEnd])\n\t\tnewValues = append(newValues, coldRangeSum)\n\t\tcoldStart = coldEnd\n\t\tcoldRangeSum = 0\n\t}\n\tgenerateRange := func(end int) {\n\t\tif end <= coldEnd {\n\t\t\treturn\n\t\t}\n\t\tvar rangeSum uint64\n\t\tfor i := coldEnd; i < end; i++ {\n\t\t\trangeSum += c.Values[i]\n\t\t}\n\t\tif coldRangeSum > threshold || rangeSum > threshold {\n\t\t\tmergeColdRange()\n\t\t}\n\t\tif rangeSum > threshold {\n\t\t\tnewKeys = append(newKeys, c.Keys[coldEnd+1:end+1]...)\n\t\t\tnewValues = append(newValues, c.Values[coldEnd:end]...)\n\t\t\tcoldStart = end\n\t\t} else {\n\t\t\tcoldRangeSum += rangeSum\n\t\t}\n\t\tcoldEnd = end\n\t}\n\n\tfor i := range c.Values {\n\t\tif labeler.CrossBorder(c.Keys[i], c.Keys[i+1]) {\n\t\t\tgenerateRange(i + 1)\n\t\t}\n\t}\n\tgenerateRange(len(c.Values))\n\tmergeColdRange()\n\n\treturn createChunk(newKeys, newValues)\n}\n\n// Divide uses binary search to find a suitable threshold, which can reduce the number of buckets of Axis to near the target.\nfunc (c *chunk) Divide(labeler decorator.Labeler, target int, mode FocusMode) chunk {\n\tif target >= len(c.Values) {\n\t\treturn *c\n\t}\n\t// get upperThreshold\n\tvar upperThreshold uint64 = 1\n\tfor _, value := range c.Values {\n\t\tupperThreshold += value\n\t}\n\t// search threshold\n\tvar lowerThreshold uint64 = 1\n\ttargetFocusRows := target * 2 / 3 // TODO: This var can be adjusted\n\tfor lowerThreshold < upperThreshold {\n\t\tmid := (lowerThreshold + upperThreshold) >> 1\n\t\tif c.GetFocusRows(mid) > targetFocusRows {\n\t\t\tlowerThreshold = mid + 1\n\t\t} else {\n\t\t\tupperThreshold = mid\n\t\t}\n\t}\n\n\tthreshold := lowerThreshold\n\tfocusRows := c.GetFocusRows(threshold)\n\tratio := len(c.Values)/(target-focusRows) + 1\n\treturn c.Focus(labeler, threshold, ratio, target, mode)\n}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/axis_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\nimport (\n\t\"github.com/pingcap/check\"\n)\n\nvar _ = check.Suite(&testAxisSuite{})\n\ntype testAxisSuite struct{}\n\nfunc (s *testAxisSuite) TestChunkReduce(c *check.C) {\n\ttestcases := []struct {\n\t\tkeys      []string\n\t\tvalues    []uint64\n\t\tnewKeys   []string\n\t\tnewValues []uint64\n\t}{\n\t\t{\n\t\t\t[]string{\"\", \"a\", \"b\", \"c\", \"\"},\n\t\t\t[]uint64{1, 10, 100, 1000},\n\t\t\t[]string{\"\", \"b\", \"\"},\n\t\t\t[]uint64{11, 1100},\n\t\t},\n\t\t{\n\t\t\t[]string{\"\", \"a\", \"b\", \"c\", \"\"},\n\t\t\t[]uint64{1, 10, 100, 1000},\n\t\t\t[]string{\"\", \"\"},\n\t\t\t[]uint64{1111},\n\t\t},\n\t}\n\n\tfor _, testcase := range testcases {\n\t\toriginChunk := createChunk(testcase.keys, testcase.values)\n\t\treduceChunk := originChunk.Reduce(testcase.newKeys)\n\t\tc.Assert(reduceChunk.Values, check.DeepEquals, testcase.newValues)\n\t}\n}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/distance.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\nimport (\n\t\"context\"\n\t\"math\"\n\t\"runtime\"\n\t\"sort\"\n\t\"sync\"\n\n\t\"go.uber.org/fx\"\n)\n\n// TODO:\n// * Multiplexing data between requests\n// * Limit memory usage\n\nfunc DistanceSplitStrategy(lc fx.Lifecycle, wg *sync.WaitGroup, ratio float64, level int, count int) SplitStrategy {\n\tpow := make([]float64, level)\n\tfor i := range pow {\n\t\tpow[i] = math.Pow(ratio, float64(i))\n\t}\n\ts := &distanceSplitStrategy{\n\t\tSplitRatio:    ratio,\n\t\tSplitLevel:    level,\n\t\tSplitCount:    count,\n\t\tSplitRatioPow: pow,\n\t}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\ts.StartWorkers(ctx, wg)\n\t\t\treturn nil\n\t\t},\n\t\tOnStop: func(context.Context) error {\n\t\t\ts.StopWorkers()\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn s\n}\n\ntype distanceSplitStrategy struct {\n\tSplitRatio float64\n\tSplitLevel int\n\tSplitCount int\n\n\tSplitRatioPow []float64\n\n\tScaleWorkerCh chan *scaleTask\n}\n\ntype distanceSplitter struct {\n\tScale [][]float64\n}\n\nfunc (s *distanceSplitStrategy) NewSplitter(chunks []chunk, compactKeys []string) Splitter {\n\taxesLen := len(chunks)\n\tkeysLen := len(compactKeys)\n\n\t// generate key distance matrix\n\tdis := make([][]int, axesLen)\n\tfor i := range axesLen {\n\t\tdis[i] = make([]int, keysLen)\n\t}\n\n\t// a column with the maximum value is virtualized on the right and left\n\tvirtualColumn := make([]int, keysLen)\n\tMemsetInt(virtualColumn, axesLen)\n\n\t// calculate left distance\n\tupdateLeftDis(dis[0], virtualColumn, chunks[0].Keys, compactKeys)\n\tfor i := 1; i < axesLen; i++ {\n\t\tupdateLeftDis(dis[i], dis[i-1], chunks[i].Keys, compactKeys)\n\t}\n\t// calculate the nearest distance on both sides\n\tend := axesLen - 1\n\tupdateRightDis(dis[end], virtualColumn, chunks[end].Keys, compactKeys)\n\tfor i := end - 1; i >= 0; i-- {\n\t\tupdateRightDis(dis[i], dis[i+1], chunks[i].Keys, compactKeys)\n\t}\n\n\treturn &distanceSplitter{\n\t\tScale: s.GenerateScale(chunks, compactKeys, dis),\n\t}\n}\n\nfunc (e *distanceSplitter) Split(dst, src chunk, tag splitTag, axesIndex int) {\n\tCheckPartOf(dst.Keys, src.Keys)\n\n\tif len(dst.Keys) == len(src.Keys) {\n\t\tswitch tag {\n\t\tcase splitTo:\n\t\t\tcopy(dst.Values, src.Values)\n\t\tcase splitAdd:\n\t\t\tfor i, v := range src.Values {\n\t\t\t\tdst.Values[i] += v\n\t\t\t}\n\t\tdefault:\n\t\t\tpanic(\"unreachable\")\n\t\t}\n\t\treturn\n\t}\n\n\tstart := 0\n\tfor startKey := src.Keys[0]; !equal(dst.Keys[start], startKey); {\n\t\tstart++\n\t}\n\tend := start + 1\n\n\tswitch tag {\n\tcase splitTo:\n\t\tfor i, key := range src.Keys[1:] {\n\t\t\tfor !equal(dst.Keys[end], key) {\n\t\t\t\tend++\n\t\t\t}\n\t\t\tvalue := src.Values[i]\n\t\t\tfor ; start < end; start++ {\n\t\t\t\tdst.Values[start] = uint64(float64(value) * e.Scale[axesIndex][start])\n\t\t\t}\n\t\t\tend++\n\t\t}\n\tcase splitAdd:\n\t\tfor i, key := range src.Keys[1:] {\n\t\t\tfor !equal(dst.Keys[end], key) {\n\t\t\t\tend++\n\t\t\t}\n\t\t\tvalue := src.Values[i]\n\t\t\tfor ; start < end; start++ {\n\t\t\t\tdst.Values[start] += uint64(float64(value) * e.Scale[axesIndex][start])\n\t\t\t}\n\t\t\tend++\n\t\t}\n\tdefault:\n\t\tpanic(\"unreachable\")\n\t}\n}\n\n// multi-threaded calculate scale matrix.\nvar workerCount int\n\nfunc init() {\n\tworkerCount = min(runtime.NumCPU(), 20)\n}\n\ntype scaleTask struct {\n\t*sync.WaitGroup\n\tDis         []int\n\tKeys        []string\n\tCompactKeys []string\n\tScale       *[]float64\n}\n\nfunc (s *distanceSplitStrategy) StartWorkers(ctx context.Context, wg *sync.WaitGroup) {\n\ts.ScaleWorkerCh = make(chan *scaleTask, workerCount*100)\n\twg.Add(workerCount)\n\tfor i := 0; i < workerCount; i++ {\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\ts.GenerateScaleColumnWork(ctx, s.ScaleWorkerCh)\n\t\t}()\n\t}\n}\n\nfunc (s *distanceSplitStrategy) StopWorkers() {\n\tclose(s.ScaleWorkerCh)\n}\n\nfunc (s *distanceSplitStrategy) GenerateScale(chunks []chunk, compactKeys []string, dis [][]int) [][]float64 {\n\tvar wg sync.WaitGroup\n\taxesLen := len(chunks)\n\tscale := make([][]float64, axesLen)\n\twg.Add(axesLen)\n\tfor i := range axesLen {\n\t\ts.ScaleWorkerCh <- &scaleTask{\n\t\t\tWaitGroup:   &wg,\n\t\t\tDis:         dis[i],\n\t\t\tKeys:        chunks[i].Keys,\n\t\t\tCompactKeys: compactKeys,\n\t\t\tScale:       &scale[i],\n\t\t}\n\t}\n\twg.Wait()\n\treturn scale\n}\n\nfunc (s *distanceSplitStrategy) GenerateScaleColumnWork(ctx context.Context, ch <-chan *scaleTask) {\n\tvar maxDis int\n\t// Each split interval needs to be sorted after copying to tempDis\n\tvar tempDis []int\n\t// Used as a mapping from distance to scale\n\ttempMapCap := 256\n\ttempMap := make([]float64, tempMapCap)\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase task, ok := <-ch:\n\t\t\tif !ok {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdis := task.Dis\n\t\t\tkeys := task.Keys\n\t\t\tcompactKeys := task.CompactKeys\n\n\t\t\t// The maximum distance between the StartKey and EndKey of a bucket\n\t\t\t// is considered the bucket distance.\n\t\t\tdis, maxDis = toBucketDis(dis)\n\t\t\tscale := make([]float64, len(dis))\n\t\t\t*task.Scale = scale\n\n\t\t\t// When it is not enough to accommodate maxDis, expand the capacity.\n\t\t\tfor tempMapCap <= maxDis {\n\t\t\t\ttempMapCap *= 2\n\t\t\t\ttempMap = make([]float64, tempMapCap)\n\t\t\t}\n\n\t\t\t// generate scale column\n\t\t\tstart := 0\n\t\t\tfor startKey := keys[0]; !equal(compactKeys[start], startKey); {\n\t\t\t\tstart++\n\t\t\t}\n\t\t\tend := start + 1\n\n\t\t\tfor _, key := range keys[1:] {\n\t\t\t\tfor !equal(compactKeys[end], key) {\n\t\t\t\t\tend++\n\t\t\t\t}\n\n\t\t\t\tif start+1 == end {\n\t\t\t\t\t// Optimize calculation when splitting into 1\n\t\t\t\t\tscale[start] = 1.0\n\t\t\t\t\tstart++\n\t\t\t\t} else {\n\t\t\t\t\t// Copy tempDis and calculate the top n levels\n\t\t\t\t\ttempDis = append(tempDis[:0], dis[start:end]...)\n\t\t\t\t\ttempLen := len(tempDis)\n\t\t\t\t\tsort.Ints(tempDis)\n\t\t\t\t\t// Calculate distribution factors and sums based on distance ordering\n\t\t\t\t\tlevel := 0\n\t\t\t\t\ttempMap[tempDis[0]] = 1.0\n\t\t\t\t\ttempValue := 1.0\n\t\t\t\t\ttempSum := 1.0\n\t\t\t\t\tfor i := 1; i < tempLen; i++ {\n\t\t\t\t\t\td := tempDis[i]\n\t\t\t\t\t\tif d != tempDis[i-1] {\n\t\t\t\t\t\t\tlevel++\n\t\t\t\t\t\t\tif level >= s.SplitLevel || i >= s.SplitCount {\n\t\t\t\t\t\t\t\ttempMap[d] = 0\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t// tempValue = math.Pow(s.SplitRatio, float64(level))\n\t\t\t\t\t\t\t\ttempValue = s.SplitRatioPow[level]\n\t\t\t\t\t\t\t\ttempMap[d] = tempValue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttempSum += tempValue\n\t\t\t\t\t}\n\t\t\t\t\t// Calculate scale\n\t\t\t\t\tfor ; start < end; start++ {\n\t\t\t\t\t\tscale[start] = tempMap[dis[start]] / tempSum\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tend++\n\t\t\t}\n\t\t\t// task finish\n\t\t\ttask.Done()\n\t\t}\n\t}\n}\n\nfunc updateLeftDis(dis, leftDis []int, keys, compactKeys []string) {\n\tCheckPartOf(compactKeys, keys)\n\tj := 0\n\tkeysLen := len(keys)\n\tfor i := range dis {\n\t\tif j < keysLen && equal(compactKeys[i], keys[j]) {\n\t\t\tdis[i] = 0\n\t\t\tj++\n\t\t} else {\n\t\t\tdis[i] = leftDis[i] + 1\n\t\t}\n\t}\n}\n\nfunc updateRightDis(dis, rightDis []int, keys, compactKeys []string) {\n\tj := 0\n\tkeysLen := len(keys)\n\tfor i := range dis {\n\t\tif j < keysLen && equal(compactKeys[i], keys[j]) {\n\t\t\tdis[i] = 0\n\t\t\tj++\n\t\t} else {\n\t\t\tdis[i] = Min(dis[i], rightDis[i]+1)\n\t\t}\n\t}\n}\n\nfunc toBucketDis(dis []int) ([]int, int) {\n\tmaxDis := 0\n\tfor i := len(dis) - 1; i > 0; i-- {\n\t\tdis[i] = Max(dis[i], dis[i-1])\n\t\tmaxDis = Max(maxDis, dis[i])\n\t}\n\treturn dis[1:], maxDis\n}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/distance_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\nimport (\n\t\"compress/gzip\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math\"\n\t\"os\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/pingcap/check\"\n\t\"go.uber.org/fx\"\n)\n\nvar _ = check.Suite(&testDistanceSuite{})\n\ntype testDistanceSuite struct{}\n\ntype testDisData struct {\n\tDis            []int    `json:\"dis\"`\n\tKeys           []string `json:\"keys\"`\n\tCompactKeysLen int      `json:\"compact_keys_len\"`\n}\n\nfunc BenchmarkGenerateScale(b *testing.B) {\n\tperr := func(err error) {\n\t\tif err != nil {\n\t\t\tpanic(\"Can not load test data!\")\n\t\t}\n\t}\n\n\tvar data testDisData\n\tfin, err := os.Open(\"../testdata/dis.json.gzip\")\n\tperr(err)\n\tdefer func() {\n\t\t_ = fin.Close()\n\t}()\n\tifs, err := gzip.NewReader(fin)\n\tperr(err)\n\terr = json.NewDecoder(ifs).Decode(&data)\n\tperr(err)\n\n\tn := 300\n\tchunks := make([]chunk, n)\n\tdisOrig := make([][]int, n)\n\tdis := make([][]int, n)\n\tfor i := range chunks {\n\t\tchunks[i] = createZeroChunk(data.Keys)\n\t\tdisOrig[i] = make([]int, len(data.Dis))\n\t}\n\trollbackDis := func() {\n\t\tcopy(dis, disOrig)\n\t\tfor i := range dis {\n\t\t\tcopy(dis[i], data.Dis)\n\t\t}\n\t}\n\n\tcompactKeys := []string{\"\"}\n\tfor i := 1; i < data.CompactKeysLen; i++ {\n\t\tcompactKeys = append(compactKeys, fmt.Sprintf(\"t%05d\", i))\n\t}\n\tcompactKeys = append(compactKeys, \"\")\n\n\tkeymap := KeyMap{}\n\tkeymap.SaveKeys(compactKeys)\n\tkeymap.SaveKeys(data.Keys)\n\n\tvar strategy SplitStrategy\n\twg := &sync.WaitGroup{}\n\tapp := fx.New(\n\t\tfx.Provide(func(lc fx.Lifecycle) SplitStrategy {\n\t\t\treturn DistanceSplitStrategy(lc, wg, 1.0/math.Phi, 15, 50)\n\t\t}),\n\t\tfx.Populate(&strategy),\n\t)\n\t_ = app.Start(context.Background())\n\ts := strategy.(*distanceSplitStrategy)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tb.StopTimer()\n\t\trollbackDis()\n\t\tb.StartTimer()\n\t\t_ = s.GenerateScale(chunks, compactKeys, dis)\n\t}\n\tb.StopTimer()\n\t_ = app.Stop(context.Background())\n\twg.Wait()\n}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/interface.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\nimport (\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/decorator\"\n)\n\ntype splitTag int\n\nconst (\n\tsplitTo  splitTag = iota // Direct assignment after split\n\tsplitAdd                 // Add to original value after split\n)\n\n// SplitStrategy is an allocation scheme. It is used to generate a Splitter for a plane to split a chunk of columns.\ntype SplitStrategy interface {\n\tNewSplitter(chunks []chunk, compactKeys []string) Splitter\n}\n\ntype Splitter interface {\n\t// Split a chunk of columns.\n\tSplit(dst, src chunk, tag splitTag, axesIndex int)\n}\n\n// Strategy is part of the customizable strategy in Matrix generation.\ntype Strategy struct {\n\tdecorator.LabelStrategy\n\tSplitStrategy\n}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/key.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\nimport (\n\t\"sync\"\n)\n\n// KeyMap is used for string intern.\ntype KeyMap struct {\n\tsync.RWMutex\n\tsync.Map\n}\n\n// SaveKey interns a string.\nfunc (km *KeyMap) SaveKey(key *string) {\n\tuniqueKey, _ := km.LoadOrStore(*key, *key)\n\t*key = uniqueKey.(string)\n}\n\n// SaveKeys interns all strings without using mutex.\nfunc (km *KeyMap) SaveKeys(keys []string) {\n\tfor i, key := range keys {\n\t\tuniqueKey, _ := km.LoadOrStore(key, key)\n\t\tkeys[i] = uniqueKey.(string)\n\t}\n}\n\nfunc equal(keyA, keyB string) bool {\n\treturn keyA == keyB\n}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/key_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\nimport (\n\t\"github.com/pingcap/check\"\n)\n\nvar _ = check.Suite(&testKeySuite{})\n\ntype testKeySuite struct{}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/matrix.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package matrix abstracts the source data as Plane, and then pixelates it into a matrix for display on the front end.\npackage matrix\n\nimport (\n\t\"time\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/decorator\"\n)\n\n// Matrix is the front end displays the required data.\ntype Matrix struct {\n\tKeys     []string              `json:\"-\"`\n\tDataMap  map[string][][]uint64 `json:\"data\" binding:\"required\"`\n\tKeyAxis  []decorator.LabelKey  `json:\"keyAxis\" binding:\"required\"`\n\tTimeAxis []int64               `json:\"timeAxis\" binding:\"required\"`\n}\n\n// CreateMatrix uses the specified times and keys to build an initial matrix with no data.\nfunc CreateMatrix(labeler decorator.Labeler, times []time.Time, keys []string, valuesListLen int) Matrix {\n\tdataMap := make(map[string][][]uint64, valuesListLen)\n\t// collect label keys\n\tkeyAxis := labeler.Label(keys)\n\t// collect unix times\n\ttimeAxis := make([]int64, len(times))\n\tfor i, t := range times {\n\t\ttimeAxis[i] = t.Unix()\n\t}\n\treturn Matrix{\n\t\tKeys:     keys,\n\t\tDataMap:  dataMap,\n\t\tKeyAxis:  keyAxis,\n\t\tTimeAxis: timeAxis,\n\t}\n}\n\n// Range returns a sub Matrix with specified range.\nfunc (mx *Matrix) Range(startKey, endKey string) {\n\tstart, end, ok := KeysRange(mx.Keys, startKey, endKey)\n\tif !ok {\n\t\tpanic(\"unreachable\")\n\t}\n\tmx.Keys = mx.Keys[start:end]\n\tmx.KeyAxis = mx.KeyAxis[start:end]\n\tfor _, data := range mx.DataMap {\n\t\tfor i, axis := range data {\n\t\t\tdata[i] = axis[start : end-1]\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/matrix_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/check\"\n)\n\nfunc TestMatrix(t *testing.T) {\n\tcheck.TestingT(t)\n}\n\nvar _ = check.Suite(&testMatrixSuite{})\n\ntype testMatrixSuite struct{}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/plane.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\nimport (\n\t\"sync\"\n\t\"time\"\n)\n\n// Plane stores consecutive axes. Each axis has StartTime, EndTime. The EndTime of each axis is the StartTime of its\n// next axis. Therefore satisfies:\n// len(Times) == len(Axes) + 1.\ntype Plane struct {\n\tTimes []time.Time\n\tAxes  []Axis\n}\n\n// CreatePlane checks the given parameters and uses them to build the Plane.\nfunc CreatePlane(times []time.Time, axes []Axis) Plane {\n\tif len(times) <= 1 {\n\t\tpanic(\"Times length must be greater than 1\")\n\t}\n\treturn Plane{\n\t\tTimes: times,\n\t\tAxes:  axes,\n\t}\n}\n\n// CreateEmptyPlane constructs a minimal empty Plane with the given parameters.\nfunc CreateEmptyPlane(startTime, endTime time.Time, startKey, endKey string, valuesListLen int) Plane {\n\treturn CreatePlane([]time.Time{startTime, endTime}, []Axis{CreateEmptyAxis(startKey, endKey, valuesListLen)})\n}\n\n// Compact compacts Plane into an axis.\nfunc (plane *Plane) Compact(strategy SplitStrategy) Axis {\n\tchunks := make([]chunk, len(plane.Axes))\n\tfor i, axis := range plane.Axes {\n\t\tchunks[i] = createChunk(axis.Keys, axis.ValuesList[0])\n\t}\n\tcompactChunk, splitter := compact(strategy, chunks)\n\tvaluesListLen := len(plane.Axes[0].ValuesList)\n\tvaluesList := make([][]uint64, valuesListLen)\n\tvaluesList[0] = compactChunk.Values\n\tfor j := 1; j < valuesListLen; j++ {\n\t\tcompactChunk.SetZeroValues()\n\t\tfor i, axis := range plane.Axes {\n\t\t\tchunks[i].SetValues(axis.ValuesList[j])\n\t\t\tsplitter.Split(compactChunk, chunks[i], splitAdd, i)\n\t\t}\n\t\tvaluesList[j] = compactChunk.Values\n\t}\n\treturn CreateAxis(compactChunk.Keys, valuesList)\n}\n\n// Pixel pixelates Plane into a matrix with a number of rows close to the target.\nfunc (plane *Plane) Pixel(strategy *Strategy, target int, displayTags []string) Matrix {\n\tvaluesListLen := len(plane.Axes[0].ValuesList)\n\tif valuesListLen != len(displayTags) {\n\t\tpanic(\"the length of displayTags and valuesList should be equal\")\n\t}\n\taxesLen := len(plane.Axes)\n\tchunks := make([]chunk, axesLen)\n\tfor i, axis := range plane.Axes {\n\t\tchunks[i] = createChunk(axis.Keys, axis.ValuesList[0])\n\t}\n\tcompactChunk, splitter := compact(strategy, chunks)\n\tlabeler := strategy.NewLabeler()\n\tbaseKeys := compactChunk.Divide(labeler, target, NotMergeLogicalRange).Keys\n\tmatrix := CreateMatrix(labeler, plane.Times, baseKeys, valuesListLen)\n\n\tvar wg sync.WaitGroup\n\tvar mutex sync.Mutex\n\tgenerateFunc := func(j int) {\n\t\tdefer wg.Done()\n\t\tdata := make([][]uint64, axesLen)\n\t\tgoCompactChunk := createZeroChunk(compactChunk.Keys)\n\t\tfor i, axis := range plane.Axes {\n\t\t\tgoCompactChunk.Clear()\n\t\t\tsplitter.Split(goCompactChunk, createChunk(chunks[i].Keys, axis.ValuesList[j]), splitTo, i)\n\t\t\tdata[i] = goCompactChunk.Reduce(baseKeys).Values\n\t\t}\n\t\tmutex.Lock()\n\t\tdefer mutex.Unlock()\n\t\tmatrix.DataMap[displayTags[j]] = data\n\t}\n\n\twg.Add(valuesListLen)\n\tfor j := range valuesListLen {\n\t\tgo generateFunc(j)\n\t}\n\twg.Wait()\n\n\treturn matrix\n}\n\nfunc compact(strategy SplitStrategy, chunks []chunk) (compactChunk chunk, splitter Splitter) {\n\t// get compact chunk keys\n\tkeySet := make(map[string]struct{})\n\tunlimitedEnd := false\n\tfor _, c := range chunks {\n\t\tend := len(c.Keys) - 1\n\t\tendKey := c.Keys[end]\n\t\tif endKey == \"\" {\n\t\t\tunlimitedEnd = true\n\t\t} else {\n\t\t\tkeySet[endKey] = struct{}{}\n\t\t}\n\t\tfor _, key := range c.Keys[:end] {\n\t\t\tkeySet[key] = struct{}{}\n\t\t}\n\t}\n\n\tvar compactKeys []string\n\tif unlimitedEnd {\n\t\tcompactKeys = MakeKeysWithUnlimitedEnd(keySet)\n\t} else {\n\t\tcompactKeys = MakeKeys(keySet)\n\t}\n\tcompactChunk = createZeroChunk(compactKeys)\n\n\tsplitter = strategy.NewSplitter(chunks, compactChunk.Keys)\n\tfor i, c := range chunks {\n\t\tsplitter.Split(compactChunk, c, splitAdd, i)\n\t}\n\treturn\n}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/plane_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\nimport (\n\t\"github.com/pingcap/check\"\n)\n\nvar _ = check.Suite(&testPlaneSuite{})\n\ntype testPlaneSuite struct{}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/util.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\nimport (\n\t\"sort\"\n)\n\n// MemsetUint64 sets all elements of the uint64 slice to v.\nfunc MemsetUint64(slice []uint64, v uint64) {\n\tsliceLen := len(slice)\n\tif sliceLen == 0 {\n\t\treturn\n\t}\n\tslice[0] = v\n\tfor bp := 1; bp < sliceLen; bp <<= 1 {\n\t\tcopy(slice[bp:], slice[:bp])\n\t}\n}\n\n// MemsetInt sets all elements of the int slice to v.\nfunc MemsetInt(slice []int, v int) {\n\tsliceLen := len(slice)\n\tif sliceLen == 0 {\n\t\treturn\n\t}\n\tslice[0] = v\n\tfor bp := 1; bp < sliceLen; bp <<= 1 {\n\t\tcopy(slice[bp:], slice[:bp])\n\t}\n}\n\n// GetLastKey gets the last element of keys.\nfunc GetLastKey(keys []string) string {\n\treturn keys[len(keys)-1]\n}\n\n// CheckPartOf checks that part keys are a subset of src keys.\nfunc CheckPartOf(src, part []string) {\n\terr := src[0] > part[0] || len(src) < len(part)\n\tsrcLastKey := GetLastKey(src)\n\tpartLastKey := GetLastKey(part)\n\tif srcLastKey != \"\" && (partLastKey == \"\" || srcLastKey < partLastKey) {\n\t\terr = true\n\t}\n\tif err {\n\t\tpanic(\"The inclusion relationship is not satisfied between keys\")\n\t}\n}\n\n// CheckReduceOf checks that part keys are a subset of src keys and have the same StartKey and EndKey.\nfunc CheckReduceOf(src, part []string) {\n\tif src[0] != part[0] || GetLastKey(src) != GetLastKey(part) || len(src) < len(part) {\n\t\tpanic(\"The inclusion relationship is not satisfied between keys\")\n\t}\n}\n\n// MakeKeys uses a key set to build a new Key-Axis.\nfunc MakeKeys(keySet map[string]struct{}) []string {\n\tkeysLen := len(keySet)\n\tkeys := make([]string, keysLen, keysLen+1)\n\ti := 0\n\tfor key := range keySet {\n\t\tkeys[i] = key\n\t\ti++\n\t}\n\tsort.Strings(keys)\n\treturn keys\n}\n\n// MakeKeysWithUnlimitedEnd uses a key set to build a new Key-Axis, then add a \"\" to the keys, indicating that the last\n// bucket has an unlimited end.\nfunc MakeKeysWithUnlimitedEnd(keySet map[string]struct{}) []string {\n\tkeys := MakeKeys(keySet)\n\treturn append(keys, \"\")\n}\n\n// KeysRange finds a range that intersects [startKey, endKey) in keys.\nfunc KeysRange(keys []string, startKey string, endKey string) (start, end int, ok bool) {\n\tif endKey != \"\" && startKey >= endKey {\n\t\tpanic(\"StartKey must be less than EndKey\")\n\t}\n\n\t// ensure intersection\n\tif endKey != \"\" && endKey <= keys[0] {\n\t\treturn -1, -1, false\n\t}\n\taxisEndKey := GetLastKey(keys)\n\tif axisEndKey != \"\" && startKey >= axisEndKey {\n\t\treturn -1, -1, false\n\t}\n\n\tkeysLen := len(keys)\n\tsortedKeysLen := keysLen\n\tif axisEndKey == \"\" {\n\t\tsortedKeysLen--\n\t}\n\n\t// start index (contain)\n\tstart = sort.Search(sortedKeysLen, func(i int) bool {\n\t\treturn keys[i] > startKey\n\t})\n\tif start > 0 {\n\t\tstart--\n\t}\n\n\t// end index (contain)\n\tend = keysLen\n\tif endKey != \"\" {\n\t\tend = sort.Search(sortedKeysLen, func(i int) bool {\n\t\t\treturn keys[i] >= endKey\n\t\t})\n\t\tif end < keysLen {\n\t\t\tend++\n\t\t}\n\t}\n\n\treturn start, end, true\n}\n\n// Max returns the larger of a and b.\nfunc Max(a, b int) int {\n\tif a > b {\n\t\treturn a\n\t}\n\treturn b\n}\n\n// Min returns the smaller of a and b.\nfunc Min(a, b int) int {\n\tif a < b {\n\t\treturn a\n\t}\n\treturn b\n}\n"
  },
  {
    "path": "pkg/keyvisual/matrix/util_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage matrix\n\nimport (\n\t\"github.com/pingcap/check\"\n)\n\nvar _ = check.Suite(&testUtilSuite{})\n\ntype testUtilSuite struct{}\n\nfunc (s *testUtilSuite) TestMemset(c *check.C) {\n\ts1 := []uint64{3, 3, 3, 3, 3}\n\ts2 := []uint64{0, 0, 0, 0, 0}\n\ts3 := []int{6, 6, 6, 6}\n\ts4 := []int{9, 9, 9, 9}\n\n\tMemsetUint64(s1, 0)\n\tMemsetInt(s3, 9)\n\n\tc.Assert(s1, check.DeepEquals, s2)\n\tc.Assert(s3, check.DeepEquals, s4)\n}\n"
  },
  {
    "path": "pkg/keyvisual/region/interface.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage region\n\ntype RegionsInfo interface {\n\tLen() int\n\tGetKeys() []string\n\tGetValues(tag StatTag) []uint64\n}\n\ntype RegionsInfoGenerator func() (RegionsInfo, error)\n\ntype DataProvider struct {\n\t// File mode (debug)\n\tFileStartTime int64\n\tFileEndTime   int64\n\t// API or Core mode\n\t// This item takes effect only when both FileStartTime and FileEndTime are 0.\n\tPeriodicGetter RegionsInfoGenerator\n}\n"
  },
  {
    "path": "pkg/keyvisual/region/tag.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage region\n\n// StatTag is a tag for statistics of different dimensions.\ntype StatTag int\n\nconst (\n\t// Integration is The overall value of all other dimension statistics.\n\tIntegration StatTag = iota\n\t// WrittenBytes is the size of the data written per minute.\n\tWrittenBytes\n\t// ReadBytes is the size of the data read per minute.\n\tReadBytes\n\t// WrittenKeys is the number of keys written to the data per minute.\n\tWrittenKeys\n\t// ReadKeys is the number of keys read to the data per minute.\n\tReadKeys\n)\n\n// IntoTag converts a string into a StatTag.\nfunc IntoTag(typ string) StatTag {\n\tswitch typ {\n\tcase \"\":\n\t\treturn Integration\n\tcase \"integration\":\n\t\treturn Integration\n\tcase \"written_bytes\":\n\t\treturn WrittenBytes\n\tcase \"read_bytes\":\n\t\treturn ReadBytes\n\tcase \"written_keys\":\n\t\treturn WrittenKeys\n\tcase \"read_keys\":\n\t\treturn ReadKeys\n\tdefault:\n\t\treturn WrittenBytes\n\t}\n}\n\nfunc (tag StatTag) String() string {\n\tswitch tag {\n\tcase Integration:\n\t\treturn \"integration\"\n\tcase WrittenBytes:\n\t\treturn \"written_bytes\"\n\tcase ReadBytes:\n\t\treturn \"read_bytes\"\n\tcase WrittenKeys:\n\t\treturn \"written_keys\"\n\tcase ReadKeys:\n\t\treturn \"read_keys\"\n\tdefault:\n\t\tpanic(\"unreachable\")\n\t}\n}\n\n// StorageTags is the order of tags during storage.\nvar StorageTags = []StatTag{WrittenBytes, ReadBytes, WrittenKeys, ReadKeys}\n\n// ResponseTags is the order of tags when responding.\nvar ResponseTags = append([]StatTag{Integration}, StorageTags...)\n\n// GetDisplayTags returns the actual order of the ResponseTags under the specified baseTag.\nfunc GetDisplayTags(baseTag StatTag) []string {\n\tdisplayTags := make([]string, len(ResponseTags))\n\tfor i, tag := range ResponseTags {\n\t\tdisplayTags[i] = tag.String()\n\t\tif tag == baseTag {\n\t\t\tdisplayTags[0], displayTags[i] = displayTags[i], displayTags[0]\n\t\t}\n\t}\n\treturn displayTags\n}\n"
  },
  {
    "path": "pkg/keyvisual/region/utils.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage region\n\nimport (\n\t\"unsafe\"\n)\n\n// String converts slice of bytes to string without copy.\nfunc String(b []byte) string {\n\tif len(b) == 0 {\n\t\treturn \"\"\n\t}\n\treturn unsafe.String(&b[0], len(b)) // #nosec\n}\n\n// Bytes converts a string into a byte slice. Need to make sure that the byte slice is not modified.\nfunc Bytes(s string) []byte {\n\tif len(s) == 0 {\n\t\treturn nil\n\t}\n\treturn unsafe.Slice(unsafe.StringData(s), len(s)) // #nosec\n}\n"
  },
  {
    "path": "pkg/keyvisual/service.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage keyvisual\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"math\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/decorator\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/input\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/matrix\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/region\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/storage\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils\"\n)\n\nconst (\n\theatmapsMaxDisplayY = 1536\n\n\tdistanceStrategyRatio = 1.0 / math.Phi\n\tdistanceStrategyLevel = 15\n\tdistanceStrategyCount = 50\n)\n\nvar (\n\tErrNS             = errorx.NewNamespace(\"error.keyvisual\")\n\tErrServiceStopped = ErrNS.NewType(\"service_stopped\")\n\n\tdefaultStatConfig = storage.StatConfig{\n\t\tLayersConfig: []storage.LayerConfig{\n\t\t\t{Len: 60, Ratio: 2 / 1},                     // step 1 minutes, total 60, 1 hours (sum: 1 hours)\n\t\t\t{Len: 60 / 2 * 7, Ratio: 6 / 2},             // step 2 minutes, total 210, 7 hours (sum: 8 hours)\n\t\t\t{Len: 60 / 6 * 16, Ratio: 30 / 6},           // step 6 minutes, total 160, 16 hours (sum: 1 days)\n\t\t\t{Len: 60 / 30 * 24 * 6, Ratio: 4 * 60 / 30}, // step 30 minutes, total 288, 6 days (sum: 1 weeks)\n\t\t\t{Len: 24 / 4 * 28, Ratio: 0},                // step 4 hours, total 168, 4 weeks (sum: 5 weeks)\n\t\t},\n\t}\n)\n\ntype Service struct {\n\tapp    *fx.App\n\tstatus *utils.ServiceStatus\n\n\tctx    context.Context\n\tcancel context.CancelFunc\n\n\tconfig         *config.Config\n\tkeyVisualCfg   *config.KeyVisualConfig\n\tcfgManager     *config.DynamicConfigManager\n\tcustomProvider *region.DataProvider\n\tetcdClient     *clientv3.Client\n\tpdClient       *pd.Client\n\tdb             *dbstore.DB\n\ttidbClient     *tidb.Client\n\n\tstat          *storage.Stat\n\tstrategy      *matrix.Strategy\n\tlabelStrategy decorator.LabelStrategy\n}\n\n// FIXME: Simplify these things.\nfunc NewService(\n\tlc fx.Lifecycle,\n\tcfg *config.Config,\n\tcfgManager *config.DynamicConfigManager,\n\tcustomProvider *region.DataProvider,\n\tetcdClient *clientv3.Client,\n\tpdClient *pd.Client,\n\tdb *dbstore.DB,\n\ttidbClient *tidb.Client,\n) *Service {\n\ts := &Service{\n\t\tstatus:         utils.NewServiceStatus(),\n\t\tconfig:         cfg,\n\t\tcfgManager:     cfgManager,\n\t\tcustomProvider: customProvider,\n\t\tetcdClient:     etcdClient,\n\t\tpdClient:       pdClient,\n\t\tdb:             db,\n\t\ttidbClient:     tidbClient,\n\t}\n\n\tlc.Append(s.managerHook())\n\n\treturn s\n}\n\nfunc RegisterRouter(r *gin.RouterGroup, auth *user.AuthService, s *Service) {\n\tendpoint := r.Group(\"/keyvisual\")\n\tendpoint.Use(auth.MWAuthRequired())\n\n\tendpoint.GET(\"/config\", s.getDynamicConfig)\n\tendpoint.PUT(\"/config\", auth.MWRequireWritePriv(), s.setDynamicConfig)\n\n\tendpoint.Use(s.status.MWHandleStopped(stoppedHandler))\n\tendpoint.GET(\"/heatmaps\", s.heatmaps)\n}\n\nfunc (s *Service) IsRunning() bool {\n\treturn s.status.IsRunning()\n}\n\nfunc (s *Service) Start(ctx context.Context) error {\n\tif s.IsRunning() {\n\t\treturn nil\n\t}\n\n\ts.ctx, s.cancel = context.WithCancel(ctx)\n\n\ts.app = fx.New(\n\t\tfx.Logger(utils.NewFxPrinter()),\n\t\tfx.Provide(\n\t\t\tnewWaitGroup,\n\t\t\tnewStrategy,\n\t\t\tnewStat,\n\t\t\ts.provideLocals,\n\t\t\ts.newProvider,\n\t\t\tinput.NewStatInput,\n\t\t\ts.newLabelStrategy,\n\t\t),\n\t\tfx.Populate(&s.stat, &s.strategy, &s.labelStrategy),\n\t\tfx.Invoke(\n\t\t\t// Must be at the end\n\t\t\ts.status.Register,\n\t\t),\n\t)\n\n\tif err := s.app.Start(s.ctx); err != nil {\n\t\ts.cleanAfterError()\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (s *Service) newLabelStrategy(\n\tlc fx.Lifecycle,\n\twg *sync.WaitGroup,\n\tetcdClient *clientv3.Client,\n\ttidbClient *tidb.Client,\n) decorator.LabelStrategy {\n\tswitch s.keyVisualCfg.Policy {\n\tcase config.KeyVisualDBPolicy:\n\t\tlog.Debug(\"New LabelStrategy\", zap.String(\"policy\", s.keyVisualCfg.Policy))\n\t\treturn decorator.TiDBLabelStrategy(lc, wg, etcdClient, tidbClient)\n\tcase config.KeyVisualKVPolicy:\n\t\tlog.Debug(\"New LabelStrategy\", zap.String(\"policy\", s.keyVisualCfg.Policy),\n\t\t\tzap.String(\"separator\", s.keyVisualCfg.PolicyKVSeparator))\n\t\treturn decorator.SeparatorLabelStrategy(s.keyVisualCfg)\n\tdefault:\n\t\tpanic(\"unreachable\")\n\t}\n}\n\nfunc (s *Service) newProvider(pdClient *pd.Client) *region.DataProvider {\n\tif s.customProvider != nil {\n\t\treturn s.customProvider\n\t}\n\treturn &region.DataProvider{\n\t\tPeriodicGetter: input.NewAPIPeriodicGetter(pdClient),\n\t}\n}\n\nfunc (s *Service) reloadKeyVisualConfig(cfg *config.KeyVisualConfig) {\n\ts.keyVisualCfg = cfg\n\tif s.labelStrategy != nil {\n\t\ts.labelStrategy.ReloadConfig(s.keyVisualCfg)\n\t}\n}\n\nfunc (s *Service) cleanAfterError() {\n\ts.cancel()\n\n\t// drop\n\ts.app = nil\n\ts.stat = nil\n\ts.strategy = nil\n\ts.labelStrategy = nil\n\ts.ctx = nil\n\ts.cancel = nil\n}\n\nfunc (s *Service) Stop(ctx context.Context) error {\n\tif !s.IsRunning() || s.app == nil {\n\t\treturn nil\n\t}\n\n\ts.cancel()\n\terr := s.app.Stop(ctx)\n\n\t// drop\n\ts.app = nil\n\ts.stat = nil\n\ts.strategy = nil\n\ts.labelStrategy = nil\n\ts.ctx = nil\n\ts.cancel = nil\n\n\treturn err\n}\n\n// @Summary Key Visual Heatmaps\n// @Description Heatmaps in a given range to visualize TiKV usage\n// @Param startkey query string false \"The start of the key range\"\n// @Param endkey query string false \"The end of the key range\"\n// @Param starttime query int false \"The start of the time range (Unix)\"\n// @Param endtime query int false \"The end of the time range (Unix)\"\n// @Param type query string false \"Main types of data\" Enums(written_bytes, read_bytes, written_keys, read_keys, integration)\n// @Success 200 {object} matrix.Matrix\n// @Router /keyvisual/heatmaps [get]\n// @Security JwtAuth\n// @Failure 401 {object} rest.ErrorResponse\nfunc (s *Service) heatmaps(c *gin.Context) {\n\tstartKey := c.Query(\"startkey\")\n\tendKey := c.Query(\"endkey\")\n\tstartTimeString := c.Query(\"starttime\")\n\tendTimeString := c.Query(\"endtime\")\n\ttyp := c.Query(\"type\")\n\n\tendTime := time.Now()\n\tstartTime := endTime.Add(-360 * time.Minute)\n\tif startTimeString != \"\" {\n\t\ttsSec, err := strconv.ParseInt(startTimeString, 10, 64)\n\t\tif err != nil {\n\t\t\tlog.Error(\"parse ts failed\", zap.Error(err))\n\t\t\tc.JSON(http.StatusBadRequest, \"bad request\")\n\t\t\treturn\n\t\t}\n\t\tstartTime = time.Unix(tsSec, 0)\n\t}\n\tif endTimeString != \"\" {\n\t\ttsSec, err := strconv.ParseInt(endTimeString, 10, 64)\n\t\tif err != nil {\n\t\t\tlog.Error(\"parse ts failed\", zap.Error(err))\n\t\t\tc.JSON(http.StatusBadRequest, \"bad request\")\n\t\t\treturn\n\t\t}\n\t\tendTime = time.Unix(tsSec, 0)\n\t}\n\tif !startTime.Before(endTime) || (endKey != \"\" && startKey >= endKey) {\n\t\tc.JSON(http.StatusBadRequest, \"bad request\")\n\t\treturn\n\t}\n\n\tlog.Debug(\"Request matrix\",\n\t\tzap.Time(\"start-time\", startTime),\n\t\tzap.Time(\"end-time\", endTime),\n\t\tzap.String(\"start-key\", startKey),\n\t\tzap.String(\"end-key\", endKey),\n\t\tzap.String(\"type\", typ),\n\t)\n\n\tif startKeyBytes, err := hex.DecodeString(startKey); err == nil {\n\t\tstartKey = string(startKeyBytes)\n\t} else {\n\t\tc.JSON(http.StatusBadRequest, \"bad request\")\n\t\treturn\n\t}\n\tif endKeyBytes, err := hex.DecodeString(endKey); err == nil {\n\t\tendKey = string(endKeyBytes)\n\t} else {\n\t\tc.JSON(http.StatusBadRequest, \"bad request\")\n\t\treturn\n\t}\n\tbaseTag := region.IntoTag(typ)\n\tplane := s.stat.Range(startTime, endTime, startKey, endKey, baseTag)\n\tresp := plane.Pixel(s.strategy, heatmapsMaxDisplayY, region.GetDisplayTags(baseTag))\n\tresp.Range(startKey, endKey)\n\t// TODO: An expedient to reduce data transmission, which needs to be deleted later.\n\tresp.DataMap = map[string][][]uint64{\n\t\ttyp: resp.DataMap[typ],\n\t}\n\t// ----------\n\tc.JSON(http.StatusOK, resp)\n}\n\nfunc (s *Service) provideLocals() (*config.Config, *clientv3.Client, *pd.Client, *dbstore.DB, *tidb.Client) {\n\treturn s.config, s.etcdClient, s.pdClient, s.db, s.tidbClient\n}\n\nfunc newWaitGroup(lc fx.Lifecycle) *sync.WaitGroup {\n\twg := &sync.WaitGroup{}\n\tlc.Append(fx.Hook{\n\t\tOnStop: func(_ context.Context) error {\n\t\t\twg.Wait()\n\t\t\treturn nil\n\t\t},\n\t})\n\treturn wg\n}\n\nfunc newStrategy(lc fx.Lifecycle, wg *sync.WaitGroup, labelStrategy decorator.LabelStrategy) *matrix.Strategy {\n\treturn &matrix.Strategy{\n\t\tLabelStrategy: labelStrategy,\n\t\tSplitStrategy: matrix.DistanceSplitStrategy(\n\t\t\tlc, wg,\n\t\t\tdistanceStrategyRatio,\n\t\t\tdistanceStrategyLevel,\n\t\t\tdistanceStrategyCount,\n\t\t),\n\t}\n}\n\nfunc newStat(\n\tlc fx.Lifecycle,\n\twg *sync.WaitGroup,\n\t_ *clientv3.Client,\n\tdb *dbstore.DB,\n\tin input.StatInput,\n\tstrategy *matrix.Strategy,\n) *storage.Stat {\n\tstat := storage.NewStat(lc, wg, db, defaultStatConfig, strategy, in.GetStartTime())\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\twg.Go(func() {\n\t\t\t\tin.Background(ctx, stat)\n\t\t\t})\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn stat\n}\n\nfunc stoppedHandler(c *gin.Context) {\n\t_ = c.AbortWithError(http.StatusNotFound, ErrServiceStopped.NewWithNoMessage())\n}\n"
  },
  {
    "path": "pkg/keyvisual/storage/model.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage storage\n\nimport (\n\t\"bytes\"\n\t\"encoding/gob\"\n\t\"time\"\n\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/matrix\"\n)\n\nconst tableAxisModelName = \"keyviz_axis\"\n\ntype AxisModel struct {\n\tLayerNum uint8     `gorm:\"unique_index:index_layer_time\"`\n\tTime     time.Time `gorm:\"unique_index:index_layer_time\"`\n\tAxis     []byte\n}\n\nfunc (AxisModel) TableName() string {\n\treturn tableAxisModelName\n}\n\nfunc NewAxisModel(layerNum uint8, time time.Time, axis matrix.Axis) (*AxisModel, error) {\n\tvar buf bytes.Buffer\n\tenc := gob.NewEncoder(&buf)\n\terr := enc.Encode(axis)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &AxisModel{\n\t\tlayerNum,\n\t\ttime,\n\t\tbuf.Bytes(),\n\t}, nil\n}\n\nfunc (a *AxisModel) UnmarshalAxis() (matrix.Axis, error) {\n\tbuf := bytes.NewBuffer(a.Axis)\n\tdec := gob.NewDecoder(buf)\n\tvar axis matrix.Axis\n\terr := dec.Decode(&axis)\n\treturn axis, err\n}\n\nfunc (a *AxisModel) Insert(db *dbstore.DB) error {\n\treturn db.Create(a).Error\n}\n\nfunc (a *AxisModel) Delete(db *dbstore.DB) error {\n\treturn db.\n\t\tWhere(\"layer_num = ? AND time = ?\", a.LayerNum, a.Time).\n\t\tDelete(&AxisModel{}).\n\t\tError\n}\n\n// If the table `AxisModel` exists, return true, nil\n// or create table `AxisModel`.\nfunc CreateTableAxisModelIfNotExists(db *dbstore.DB) (bool, error) {\n\tif db.Migrator().HasTable(&AxisModel{}) {\n\t\treturn true, nil\n\t}\n\treturn false, db.Migrator().CreateTable(&AxisModel{})\n}\n\nfunc ClearTableAxisModel(db *dbstore.DB) error {\n\treturn db.Session(&gorm.Session{AllowGlobalUpdate: true}).\n\t\tDelete(&AxisModel{}).\n\t\tError\n}\n\nfunc FindAxisModelsOrderByTime(db *dbstore.DB, layerNum uint8) ([]*AxisModel, error) {\n\tvar axisModels []*AxisModel\n\terr := db.\n\t\tWhere(\"layer_num = ?\", layerNum).\n\t\tOrder(\"time\").\n\t\tFind(&axisModels).\n\t\tError\n\treturn axisModels, err\n}\n\nfunc DeleteAxisModelsByLayerNum(db *dbstore.DB, layerNum uint8) error {\n\treturn db.\n\t\tWhere(\"layer_num = ?\", layerNum).\n\t\tDelete(&AxisModel{}).\n\t\tError\n}\n"
  },
  {
    "path": "pkg/keyvisual/storage/model_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage storage\n\nimport (\n\t\"path\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/pingcap/check\"\n\t\"gorm.io/driver/sqlite\"\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/matrix\"\n)\n\nfunc TestDbstore(t *testing.T) {\n\tcheck.TestingT(t)\n}\n\nvar _ = check.Suite(&testDbstoreSuite{})\n\ntype testDbstoreSuite struct {\n\tdir string\n\tdb  *dbstore.DB\n}\n\nfunc (t *testDbstoreSuite) SetUpTest(c *check.C) {\n\tt.dir = c.MkDir()\n\tgormDB, err := gorm.Open(sqlite.Open(path.Join(t.dir, \"test.sqlite.db\")))\n\tif err != nil {\n\t\tc.Errorf(\"Open %s error: %v\", path.Join(t.dir, \"test.sqlite.db\"), err)\n\t}\n\tt.db = &dbstore.DB{DB: gormDB}\n}\n\nfunc (t *testDbstoreSuite) TestCreateTableAxisModelIfNotExists(c *check.C) {\n\tisExist, err := CreateTableAxisModelIfNotExists(t.db)\n\tc.Assert(isExist, check.Equals, false)\n\tc.Assert(err, check.IsNil)\n\tisExist, err = CreateTableAxisModelIfNotExists(t.db)\n\tc.Assert(isExist, check.Equals, true)\n\tc.Assert(err, check.IsNil)\n}\n\nfunc (t *testDbstoreSuite) TestClearTableAxisModel(c *check.C) {\n\t_, err := CreateTableAxisModelIfNotExists(t.db)\n\tif err != nil {\n\t\tc.Fatalf(\"Create table AxisModel error: %v\", err)\n\t}\n\taxisModel, err := NewAxisModel(0, time.Now(), matrix.Axis{})\n\tif err != nil {\n\t\tc.Fatalf(\"NewAxisModel error: %v\", err)\n\t}\n\terr = axisModel.Insert(t.db)\n\tif err != nil {\n\t\tc.Fatalf(\"AxisModel Insert error: %v\", err)\n\t}\n\tvar count int64\n\n\terr = t.db.Table(tableAxisModelName).Count(&count).Error\n\tif err != nil {\n\t\tc.Fatalf(\"Count table AxisModel error: %v\", err)\n\t}\n\tc.Assert(count, check.Equals, int64(1))\n\n\terr = ClearTableAxisModel(t.db)\n\tc.Assert(err, check.IsNil)\n\n\terr = t.db.Table(tableAxisModelName).Count(&count).Error\n\tif err != nil {\n\t\tc.Fatalf(\"Count table AxisModel error: %v\", err)\n\t}\n\tc.Assert(count, check.Equals, int64(0))\n}\n\nfunc (t *testDbstoreSuite) TestAxisModelFunc(c *check.C) {\n\t_, err := CreateTableAxisModelIfNotExists(t.db)\n\tif err != nil {\n\t\tc.Fatalf(\"Create table AxisModel error: %v\", err)\n\t}\n\tvar layerNum uint8\n\tendTime := time.Now()\n\taxis := matrix.Axis{\n\t\tKeys:       []string{\"a\", \"b\"},\n\t\tValuesList: [][]uint64{{1}, {1}, {1}, {1}},\n\t}\n\taxisModel, err := NewAxisModel(layerNum, endTime, axis)\n\tif err != nil {\n\t\tc.Fatalf(\"NewAxisModel error: %v\", err)\n\t}\n\terr = axisModel.Insert(t.db)\n\tc.Assert(err, check.IsNil)\n\taxisModels, err := FindAxisModelsOrderByTime(t.db, layerNum)\n\tif err != nil {\n\t\tc.Fatalf(\"FindAxisModelOrderByTime error: %v\", err)\n\t}\n\tc.Assert(len(axisModels), check.Equals, 1)\n\taxisModelDeepEqual(axisModels[0], axisModel, c)\n\tobtainedAxis, err := axisModels[0].UnmarshalAxis()\n\tif err != nil {\n\t\tc.Fatalf(\"UnmarshalAxis error: %v\", err)\n\t}\n\tc.Assert(obtainedAxis, check.DeepEquals, axis)\n\n\terr = axisModel.Delete(t.db)\n\tc.Assert(err, check.IsNil)\n\n\tvar count int64\n\terr = t.db.Table(tableAxisModelName).Count(&count).Error\n\tif err != nil {\n\t\tc.Fatalf(\"Count table AxisModel error: %v\", err)\n\t}\n\tc.Assert(count, check.Equals, int64(0))\n\n\terr = axisModel.Delete(t.db)\n\tc.Assert(err, check.IsNil)\n}\n\nfunc (t *testDbstoreSuite) TestAxisModelsFindAndDelete(c *check.C) {\n\t_, err := CreateTableAxisModelIfNotExists(t.db)\n\tif err != nil {\n\t\tc.Fatalf(\"Create table AxisModel error: %v\", err)\n\t}\n\n\tvar maxLayerNum uint8 = 2\n\taxisModelNumEachLayer := 3\n\taxisModelList := make([][]*AxisModel, maxLayerNum)\n\tfor layerNum := range maxLayerNum {\n\t\taxisModelList[layerNum] = make([]*AxisModel, axisModelNumEachLayer)\n\t\tfor i := range axisModelNumEachLayer {\n\t\t\taxisModelList[layerNum][i], err = NewAxisModel(layerNum, time.Now(), matrix.Axis{})\n\t\t\tif err != nil {\n\t\t\t\tc.Fatalf(\"NewAxisModel error: %v\", err)\n\t\t\t}\n\t\t\terr = axisModelList[layerNum][i].Insert(t.db)\n\t\t\tif err != nil {\n\t\t\t\tc.Fatalf(\"NewAxisModel error: %v\", err)\n\t\t\t}\n\t\t}\n\t}\n\n\tvar count int64\n\terr = t.db.Table(tableAxisModelName).Count(&count).Error\n\tif err != nil {\n\t\tc.Fatalf(\"Count table AxisModel error: %v\", err)\n\t}\n\tc.Assert(count, check.Equals, int64(int(maxLayerNum)*axisModelNumEachLayer))\n\n\tfindLayerNum := maxLayerNum - 1\n\taxisModels, err := FindAxisModelsOrderByTime(t.db, findLayerNum)\n\tc.Assert(err, check.IsNil)\n\taxisModelsDeepEqual(axisModels, axisModelList[findLayerNum], c)\n\n\terr = DeleteAxisModelsByLayerNum(t.db, findLayerNum)\n\tc.Assert(err, check.IsNil)\n\n\taxisModels, err = FindAxisModelsOrderByTime(t.db, findLayerNum)\n\tc.Assert(err, check.IsNil)\n\tc.Assert(axisModels, check.HasLen, 0)\n\n\terr = t.db.Table(tableAxisModelName).Count(&count).Error\n\tif err != nil {\n\t\tc.Fatalf(\"Count table AxisModel error: %v\", err)\n\t}\n\tc.Assert(count, check.Equals, int64(int(maxLayerNum-1)*axisModelNumEachLayer))\n}\n\nfunc axisModelsDeepEqual(obtainedAxisModels []*AxisModel, expectedAxisModels []*AxisModel, c *check.C) {\n\tc.Assert(len(obtainedAxisModels), check.Equals, len(expectedAxisModels))\n\tfor i := range obtainedAxisModels {\n\t\taxisModelDeepEqual(obtainedAxisModels[i], expectedAxisModels[i], c)\n\t}\n}\n\nfunc axisModelDeepEqual(obtainedAxisModel *AxisModel, expectedAxisModel *AxisModel, c *check.C) {\n\tc.Assert(obtainedAxisModel.Time.Unix(), check.Equals, expectedAxisModel.Time.Unix())\n\tobtainedAxisModel.Time = expectedAxisModel.Time\n\tc.Assert(obtainedAxisModel, check.DeepEquals, expectedAxisModel)\n}\n"
  },
  {
    "path": "pkg/keyvisual/storage/region.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage storage\n\nimport (\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/decorator\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/matrix\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/region\"\n)\n\n// Source data pre processing parameters.\nconst (\n\t// preThreshold   = 128\n\t// preRatioTarget = 512.\n\tpreTarget = 3072\n\n\tdirtyWrittenBytes uint64 = 1 << 32\n)\n\n// CreateStorageAxis converts the RegionsInfo to a StorageAxis.\nfunc CreateStorageAxis(regions region.RegionsInfo, labeler decorator.Labeler) matrix.Axis {\n\tregionsLen := regions.Len()\n\tif regionsLen <= 0 {\n\t\tpanic(\"At least one RegionInfo\")\n\t}\n\n\tkeys := regions.GetKeys()\n\tvaluesList := make([][]uint64, len(region.ResponseTags))\n\tfor i, tag := range region.ResponseTags {\n\t\tvaluesList[i] = regions.GetValues(tag)\n\t}\n\n\tpreAxis := matrix.CreateAxis(keys, valuesList)\n\twash(&preAxis)\n\n\taxis := IntoStorageAxis(preAxis, labeler)\n\tlog.Debug(\"New StorageAxis\", zap.Int(\"region length\", regionsLen), zap.Int(\"focus keys length\", len(axis.Keys)))\n\treturn axis\n}\n\n// IntoStorageAxis converts ResponseAxis to StorageAxis.\nfunc IntoStorageAxis(responseAxis matrix.Axis, labeler decorator.Labeler) matrix.Axis {\n\t// axis := preAxis.Focus(strategy, preThreshold, len(keys)/preRatioTarget, preTarget)\n\taxis := responseAxis.Divide(labeler, preTarget)\n\tstorageValuesList := make([][]uint64, 0, len(axis.ValuesList))\n\tstorageValuesList = append(storageValuesList, axis.ValuesList[1:]...)\n\treturn matrix.CreateAxis(axis.Keys, storageValuesList)\n}\n\n// IntoResponseAxis converts StorageAxis to ResponseAxis.\nfunc IntoResponseAxis(storageAxis matrix.Axis, baseTag region.StatTag) matrix.Axis {\n\t// add integration values\n\tvaluesList := make([][]uint64, 1, len(region.ResponseTags))\n\twrittenBytes := storageAxis.ValuesList[0]\n\treadBytes := storageAxis.ValuesList[1]\n\tintegration := make([]uint64, len(writtenBytes))\n\tfor i := range integration {\n\t\tintegration[i] = writtenBytes[i] + readBytes[i]\n\t}\n\tvaluesList[0] = integration\n\tvaluesList = append(valuesList, storageAxis.ValuesList...)\n\t// swap baseTag\n\tfor i, tag := range region.ResponseTags {\n\t\tif tag == baseTag {\n\t\t\tvaluesList[0], valuesList[i] = valuesList[i], valuesList[0]\n\t\t\treturn matrix.CreateAxis(storageAxis.Keys, valuesList)\n\t\t}\n\t}\n\tpanic(\"unreachable\")\n}\n\n// TODO: Temporary solution, need to trace the source of dirty data.\nfunc wash(axis *matrix.Axis) {\n\tfor i, value := range axis.ValuesList[1] {\n\t\tif value >= dirtyWrittenBytes {\n\t\t\tfor j := range region.ResponseTags {\n\t\t\t\taxis.ValuesList[j][i] = 0\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/keyvisual/storage/region_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage storage\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/check\"\n)\n\nfunc TestRegion(t *testing.T) {\n\tcheck.TestingT(t)\n}\n\nvar _ = check.Suite(&testRegionSuite{})\n\ntype testRegionSuite struct{}\n"
  },
  {
    "path": "pkg/keyvisual/storage/stat.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package storage stores the input axes in order, and can get a Plane by time interval.\npackage storage\n\nimport (\n\t\"context\"\n\t\"sort\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/dbstore\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/decorator\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/matrix\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/region\"\n)\n\n// LayerConfig is the configuration of layerStat.\ntype LayerConfig struct {\n\tLen   int\n\tRatio int\n}\n\n// layerStat is a layer in Stat. It uses a circular queue structure and can store up to Len Axes. Whenever the data is\n// full, the Ratio Axes will be compacted into an Axis and added to the next layer.\ntype layerStat struct {\n\tStartTime time.Time\n\tEndTime   time.Time\n\tRingAxes  []matrix.Axis\n\tRingTimes []time.Time\n\n\tLayerNum uint8\n\tHead     int\n\tTail     int\n\tEmpty    bool\n\tLen      int\n\n\tDb *dbstore.DB\n\t// Hierarchical mechanism\n\tSplitStrategy matrix.SplitStrategy\n\tRatio         int\n\tNext          *layerStat\n}\n\nfunc newLayerStat(\n\tlayerNum uint8,\n\tconf LayerConfig,\n\tsplitStrategy matrix.SplitStrategy,\n\tstartTime time.Time,\n\tdb *dbstore.DB,\n) *layerStat {\n\treturn &layerStat{\n\t\tStartTime:     startTime,\n\t\tEndTime:       startTime,\n\t\tRingAxes:      make([]matrix.Axis, conf.Len),\n\t\tRingTimes:     make([]time.Time, conf.Len),\n\t\tLayerNum:      layerNum,\n\t\tHead:          0,\n\t\tTail:          0,\n\t\tEmpty:         true,\n\t\tLen:           conf.Len,\n\t\tDb:            db,\n\t\tSplitStrategy: splitStrategy,\n\t\tRatio:         conf.Ratio,\n\t\tNext:          nil,\n\t}\n}\n\n// Reduce merges ratio axes and append to next layerStat.\nfunc (s *layerStat) Reduce(labeler decorator.Labeler) {\n\tif s.Ratio == 0 || s.Next == nil {\n\t\t_ = s.DeleteFirstAxisFromDb()\n\n\t\ts.StartTime = s.RingTimes[s.Head]\n\t\ts.RingAxes[s.Head] = matrix.Axis{}\n\t\ts.Head = (s.Head + 1) % s.Len\n\t\treturn\n\t}\n\n\ttimes := make([]time.Time, 0, s.Ratio+1)\n\ttimes = append(times, s.StartTime)\n\taxes := make([]matrix.Axis, 0, s.Ratio)\n\n\tfor i := 0; i < s.Ratio; i++ {\n\t\t_ = s.DeleteFirstAxisFromDb()\n\n\t\ts.StartTime = s.RingTimes[s.Head]\n\t\ttimes = append(times, s.StartTime)\n\t\taxes = append(axes, s.RingAxes[s.Head])\n\t\ts.RingAxes[s.Head] = matrix.Axis{}\n\t\ts.Head = (s.Head + 1) % s.Len\n\t}\n\n\tplane := matrix.CreatePlane(times, axes)\n\tnewAxis := plane.Compact(s.SplitStrategy)\n\tnewAxis = IntoResponseAxis(newAxis, region.Integration)\n\tnewAxis = IntoStorageAxis(newAxis, labeler)\n\tnewAxis.Shrink(uint64(s.Ratio))\n\ts.Next.Append(newAxis, s.StartTime, labeler)\n}\n\n// Append appends a key axis to layerStat.\nfunc (s *layerStat) Append(axis matrix.Axis, endTime time.Time, labeler decorator.Labeler) {\n\tif s.Head == s.Tail && !s.Empty {\n\t\ts.Reduce(labeler)\n\t}\n\n\t_ = s.InsertLastAxisToDb(axis, endTime)\n\n\ts.RingAxes[s.Tail] = axis\n\ts.RingTimes[s.Tail] = endTime\n\ts.Empty = false\n\ts.EndTime = endTime\n\ts.Tail = (s.Tail + 1) % s.Len\n}\n\n// Range gets the specify plane in the time range.\nfunc (s *layerStat) Range(startTime, endTime time.Time) (times []time.Time, axes []matrix.Axis) {\n\tif s.Next != nil {\n\t\ttimes, axes = s.Next.Range(startTime, endTime)\n\t}\n\n\tif s.Empty || (!startTime.Before(s.EndTime) || !endTime.After(s.StartTime)) {\n\t\treturn times, axes\n\t}\n\n\tsize := s.Tail - s.Head\n\tif size <= 0 {\n\t\tsize += s.Len\n\t}\n\n\tstart := sort.Search(size, func(i int) bool {\n\t\treturn s.RingTimes[(s.Head+i)%s.Len].After(startTime)\n\t})\n\tend := sort.Search(size, func(i int) bool {\n\t\treturn !s.RingTimes[(s.Head+i)%s.Len].Before(endTime)\n\t})\n\tif end != size {\n\t\tend++\n\t}\n\n\tn := end - start\n\tstart = (s.Head + start) % s.Len\n\n\t// add StartTime\n\tif len(times) == 0 {\n\t\tif start == s.Head {\n\t\t\ttimes = append(times, s.StartTime)\n\t\t} else {\n\t\t\ttimes = append(times, s.RingTimes[(start-1+s.Len)%s.Len])\n\t\t}\n\t}\n\n\tif start+n <= s.Len {\n\t\ttimes = append(times, s.RingTimes[start:start+n]...)\n\t\taxes = append(axes, s.RingAxes[start:start+n]...)\n\t} else {\n\t\ttimes = append(times, s.RingTimes[start:s.Len]...)\n\t\ttimes = append(times, s.RingTimes[0:start+n-s.Len]...)\n\t\taxes = append(axes, s.RingAxes[start:s.Len]...)\n\t\taxes = append(axes, s.RingAxes[0:start+n-s.Len]...)\n\t}\n\n\treturn times, axes\n}\n\n// StatConfig is the configuration of Stat.\ntype StatConfig struct {\n\tLayersConfig []LayerConfig\n}\n\n// Stat is composed of multiple layerStats.\ntype Stat struct {\n\tmutex  sync.RWMutex\n\tlayers []*layerStat\n\n\tkeyMap   matrix.KeyMap\n\tstrategy *matrix.Strategy\n\n\tdb *dbstore.DB\n}\n\n// NewStat generates a Stat based on the configuration.\nfunc NewStat(\n\tlc fx.Lifecycle,\n\twg *sync.WaitGroup,\n\tdb *dbstore.DB,\n\tcfg StatConfig,\n\tstrategy *matrix.Strategy,\n\tstartTime time.Time,\n) *Stat {\n\tlayers := make([]*layerStat, len(cfg.LayersConfig))\n\tfor i, c := range cfg.LayersConfig {\n\t\tlayers[i] = newLayerStat(uint8(i), c, strategy, startTime, db)\n\t\tif i > 0 {\n\t\t\tlayers[i-1].Next = layers[i]\n\t\t}\n\t}\n\ts := &Stat{\n\t\tlayers:   layers,\n\t\tstrategy: strategy,\n\t\tdb:       db,\n\t}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tif err := s.Restore(); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\twg.Go(func() {\n\t\t\t\ts.rebuildRegularly(ctx)\n\t\t\t})\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn s\n}\n\nfunc (s *Stat) rebuildKeyMap() {\n\ts.keyMap.Lock()\n\tdefer s.keyMap.Unlock()\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\ts.keyMap.Map = sync.Map{}\n\n\tfor _, layer := range s.layers {\n\t\tfor _, axis := range layer.RingAxes {\n\t\t\tif len(axis.Keys) > 0 {\n\t\t\t\ts.keyMap.SaveKeys(axis.Keys)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *Stat) rebuildRegularly(ctx context.Context) {\n\tticker := time.NewTicker(time.Hour * 24)\n\tdefer ticker.Stop()\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase <-ticker.C:\n\t\t\ts.rebuildKeyMap()\n\t\t}\n\t}\n}\n\n// Append adds the latest full statistics.\nfunc (s *Stat) Append(regions region.RegionsInfo, endTime time.Time) {\n\tif regions.Len() == 0 {\n\t\treturn\n\t}\n\tlabeler := s.strategy.NewLabeler()\n\taxis := CreateStorageAxis(regions, labeler)\n\n\ts.keyMap.RLock()\n\tdefer s.keyMap.RUnlock()\n\ts.keyMap.SaveKeys(axis.Keys)\n\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\ts.layers[0].Append(axis, endTime, labeler)\n}\n\nfunc (s *Stat) rangeRoot(startTime, endTime time.Time) ([]time.Time, []matrix.Axis) {\n\ts.mutex.RLock()\n\tdefer s.mutex.RUnlock()\n\treturn s.layers[0].Range(startTime, endTime)\n}\n\n// Range returns a sub Plane with specified range.\nfunc (s *Stat) Range(startTime, endTime time.Time, startKey, endKey string, baseTag region.StatTag) matrix.Plane {\n\ts.keyMap.RLock()\n\tdefer s.keyMap.RUnlock()\n\ts.keyMap.SaveKey(&startKey)\n\ts.keyMap.SaveKey(&endKey)\n\n\ttimes, axes := s.rangeRoot(startTime, endTime)\n\n\tif len(times) <= 1 {\n\t\treturn matrix.CreateEmptyPlane(startTime, endTime, startKey, endKey, len(region.ResponseTags))\n\t}\n\n\tfor i, axis := range axes {\n\t\taxis = axis.Range(startKey, endKey)\n\t\taxis = IntoResponseAxis(axis, baseTag)\n\t\taxes[i] = axis\n\t}\n\treturn matrix.CreatePlane(times, axes)\n}\n"
  },
  {
    "path": "pkg/keyvisual/storage/stat_persist.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage storage\n\nimport (\n\t\"time\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/keyvisual/matrix\"\n)\n\nfunc (s *layerStat) InsertLastAxisToDb(axis matrix.Axis, endTime time.Time) error {\n\tlog.Debug(\"Insert Axis\", zap.Uint8(\"layer num\", s.LayerNum), zap.Time(\"time\", endTime))\n\taxisModel, err := NewAxisModel(s.LayerNum, endTime, axis)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn axisModel.Insert(s.Db)\n}\n\nfunc (s *layerStat) DeleteFirstAxisFromDb() error {\n\tlog.Debug(\"Delete Axis\", zap.Uint8(\"layer num\", s.LayerNum), zap.Time(\"time\", s.StartTime))\n\taxisModel, err := NewAxisModel(s.LayerNum, s.StartTime, matrix.Axis{})\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn axisModel.Delete(s.Db)\n}\n\n// Restore data from db the first time service starts.\nfunc (s *Stat) Restore() error {\n\ts.keyMap.Lock()\n\tdefer s.keyMap.Unlock()\n\ts.mutex.Lock()\n\tdefer s.mutex.Unlock()\n\n\t// insert start `AxisModel` for each layer\n\tcreateStartAxisModels := func() error {\n\t\tlog.Debug(\"Create start axisModel for each layer\")\n\t\tfor i, layer := range s.layers {\n\t\t\tstartAxisModel, err := NewAxisModel(uint8(i), layer.StartTime, matrix.Axis{})\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tif err := startAxisModel.Insert(s.db); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\t// table `AxisModel` preprocess\n\tisExist, err := CreateTableAxisModelIfNotExists(s.db)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif !isExist {\n\t\treturn createStartAxisModels()\n\t}\n\n\t// load data from db\n\tfor layerNum := uint8(0); ; layerNum++ {\n\t\taxisModels, err := FindAxisModelsOrderByTime(s.db, layerNum)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif len(axisModels) == 0 {\n\t\t\tbreak\n\t\t}\n\t\tif layerNum >= uint8(len(s.layers)) {\n\t\t\tlog.Warn(\"Layer num is too large. Ignore and delete the redundant axisModels\", zap.Uint8(\"layer num\", layerNum), zap.Int(\"layers len\", len(s.layers)))\n\t\t\t_ = DeleteAxisModelsByLayerNum(s.db, layerNum)\n\t\t\tcontinue\n\t\t}\n\n\t\tif len(axisModels) > 1 {\n\t\t\ts.layers[layerNum].Empty = false\n\t\t} else if layerNum == 0 {\n\t\t\t// no valid data was stored，clear\n\t\t\tlog.Debug(\"Clear table AxisModel\")\n\t\t\tif err := ClearTableAxisModel(s.db); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn createStartAxisModels()\n\t\t}\n\t\tlog.Debug(\"Load axisModels\", zap.Uint8(\"layer num\", layerNum), zap.Int(\"len\", len(axisModels)-1))\n\n\t\t// the first axisModel is only used to save starttime\n\t\ts.layers[layerNum].StartTime = axisModels[0].Time\n\t\ts.layers[layerNum].Head = 0\n\t\tn := len(axisModels) - 1\n\t\tif n > s.layers[layerNum].Len {\n\t\t\tlog.Warn(\"The number of axisModel is longer than layer's len\", zap.Int(\"number\", n), zap.Int(\"layer len\", s.layers[layerNum].Len), zap.Uint8(\"layer num\", layerNum))\n\t\t\tfor _, p := range axisModels[s.layers[layerNum].Len+1:] {\n\t\t\t\t_ = p.Delete(s.db)\n\t\t\t}\n\t\t\tn = s.layers[layerNum].Len\n\t\t}\n\t\ts.layers[layerNum].EndTime = axisModels[n].Time\n\t\ts.layers[layerNum].Tail = (s.layers[layerNum].Head + n) % s.layers[layerNum].Len\n\t\tfor i, axisModel := range axisModels[1 : n+1] {\n\t\t\ts.layers[layerNum].RingTimes[i] = axisModel.Time\n\t\t\taxis, err := axisModel.UnmarshalAxis()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\ts.keyMap.SaveKeys(axis.Keys)\n\t\t\ts.layers[layerNum].RingAxes[i] = axis\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/keyvisual/storage/stat_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage storage\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/check\"\n)\n\nfunc TestStat(t *testing.T) {\n\tcheck.TestingT(t)\n}\n\nvar _ = check.Suite(&testStatSuite{})\n\ntype testStatSuite struct{}\n"
  },
  {
    "path": "pkg/pd/client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/httpc\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nvar ErrPDClientRequestFailed = ErrNS.NewType(\"client_request_failed\")\n\nconst (\n\tdefaultPDTimeout = time.Second * 10\n)\n\ntype Client struct {\n\thttpScheme    string\n\tbaseURL       string\n\twithoutPrefix bool\n\thttpClient    *httpc.Client\n\tlifecycleCtx  context.Context\n\ttimeout       time.Duration\n}\n\nfunc NewPDClient(lc fx.Lifecycle, httpClient *httpc.Client, config *config.Config) *Client {\n\tclient := &Client{\n\t\thttpClient:   httpClient,\n\t\thttpScheme:   config.GetClusterHTTPScheme(),\n\t\tbaseURL:      config.PDEndPoint,\n\t\tlifecycleCtx: nil,\n\t\ttimeout:      defaultPDTimeout,\n\t}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tclient.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn client\n}\n\nfunc (c Client) WithBaseURL(baseURL string) *Client {\n\tc.baseURL = baseURL\n\treturn &c\n}\n\nfunc (c Client) WithAddress(host string, port int) *Client {\n\tc.baseURL = fmt.Sprintf(\"%s://%s\", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(port)))\n\treturn &c\n}\n\nfunc (c Client) WithTimeout(timeout time.Duration) *Client {\n\tc.timeout = timeout\n\treturn &c\n}\n\nfunc (c Client) WithoutPrefix() *Client {\n\tc.withoutPrefix = true\n\treturn &c\n}\n\nfunc (c Client) getPrefix() string {\n\tif c.withoutPrefix {\n\t\treturn \"\"\n\t}\n\treturn \"/pd/api/v1\"\n}\n\nfunc (c Client) AddRequestHeader(key, value string) *Client {\n\tc.httpClient = c.httpClient.CloneAndAddRequestHeader(key, value)\n\treturn &c\n}\n\nfunc (c *Client) Get(relativeURI string) (*httpc.Response, error) {\n\turi := fmt.Sprintf(\"%s%s%s\", c.baseURL, c.getPrefix(), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).Send(c.lifecycleCtx, uri, http.MethodGet, nil, ErrPDClientRequestFailed, distro.R().PD)\n}\n\nfunc (c *Client) SendGetRequest(relativeURI string) ([]byte, error) {\n\tres, err := c.Get(relativeURI)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn res.Body()\n}\n\nfunc (c *Client) SendPostRequest(relativeURI string, body io.Reader) ([]byte, error) {\n\turi := fmt.Sprintf(\"%s%s%s\", c.baseURL, c.getPrefix(), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).SendRequest(c.lifecycleCtx, uri, http.MethodPost, body, ErrPDClientRequestFailed, distro.R().PD)\n}\n"
  },
  {
    "path": "pkg/pd/client_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pd\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\t\"go.uber.org/fx/fxtest\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/httpc\"\n)\n\nfunc newTestClient(t *testing.T) *Client {\n\tlc := fxtest.NewLifecycle(t)\n\tconfig := &config.Config{}\n\tc := NewPDClient(lc, httpc.NewHTTPClient(lc, config), config)\n\tc.lifecycleCtx = context.Background()\n\treturn c\n}\n\nfunc Test_AddRequestHeader_returnDifferentHTTPClient(t *testing.T) {\n\tc := newTestClient(t)\n\tcc := c.AddRequestHeader(\"1\", \"11\")\n\n\trequire.NotSame(t, c.httpClient, cc.httpClient)\n}\n\nfunc Test_Get_withHeader(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = w.Write([]byte(r.Header.Get(\"1\")))\n\t}))\n\tdefer ts.Close()\n\n\tc := newTestClient(t).WithBaseURL(ts.URL)\n\tresp1, _ := c.Get(\"\")\n\td1, _ := resp1.Body()\n\trequire.Equal(t, \"\", string(d1))\n\n\tcc := c.AddRequestHeader(\"1\", \"11\")\n\tresp2, _ := cc.Get(\"\")\n\td2, _ := resp2.Body()\n\trequire.Equal(t, \"11\", string(d2))\n\n\tresp3, _ := c.Get(\"\")\n\td3, _ := resp3.Body()\n\trequire.Equal(t, \"\", string(d3))\n}\n"
  },
  {
    "path": "pkg/pd/etcd.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pd\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils\"\n)\n\nfunc NewEtcdClient(lc fx.Lifecycle, config *config.Config) (*clientv3.Client, error) {\n\tzapCfg := zap.NewProductionConfig()\n\tzapCfg.Encoding = log.ZapEncodingName\n\n\tcli, err := clientv3.New(clientv3.Config{\n\t\tEndpoints:            []string{config.PDEndPoint},\n\t\tAutoSyncInterval:     30 * time.Second,\n\t\tDialTimeout:          5 * time.Second,\n\t\tDialKeepAliveTime:    utils.DefaultGRPCKeepaliveParams.Time,\n\t\tDialKeepAliveTimeout: utils.DefaultGRPCKeepaliveParams.Timeout,\n\t\tPermitWithoutStream:  utils.DefaultGRPCKeepaliveParams.PermitWithoutStream,\n\t\tDialOptions:          utils.DefaultGRPCDialOptions,\n\t\tTLS:                  config.ClusterTLSConfig,\n\t\tLogConfig:            &zapCfg,\n\t})\n\n\tlc.Append(fx.Hook{\n\t\tOnStop: func(context.Context) error {\n\t\t\treturn cli.Close()\n\t\t},\n\t})\n\n\treturn cli, err\n}\n"
  },
  {
    "path": "pkg/pd/pd.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pd\n\nimport (\n\t\"github.com/joomcode/errorx\"\n)\n\nvar ErrNS = errorx.NewNamespace(\"error.pd\")\n"
  },
  {
    "path": "pkg/scheduling/client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage scheduling\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/httpc\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nvar ErrSchedulingClientRequestFailed = ErrNS.NewType(\"client_request_failed\")\n\nconst (\n\tdefaultSchedulingStatusAPITimeout = time.Second * 10\n)\n\ntype Client struct {\n\thttpClient   *httpc.Client\n\thttpScheme   string\n\tlifecycleCtx context.Context\n\ttimeout      time.Duration\n}\n\nfunc NewSchedulingClient(lc fx.Lifecycle, httpClient *httpc.Client, config *config.Config) *Client {\n\tclient := &Client{\n\t\thttpClient:   httpClient,\n\t\thttpScheme:   config.GetClusterHTTPScheme(),\n\t\tlifecycleCtx: nil,\n\t\ttimeout:      defaultSchedulingStatusAPITimeout,\n\t}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tclient.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn client\n}\n\nfunc (c Client) WithTimeout(timeout time.Duration) *Client {\n\tc.timeout = timeout\n\treturn &c\n}\n\nfunc (c Client) AddRequestHeader(key, value string) *Client {\n\tc.httpClient = c.httpClient.CloneAndAddRequestHeader(key, value)\n\treturn &c\n}\n\nfunc (c *Client) Get(host string, port int, relativeURI string) (*httpc.Response, error) {\n\turi := fmt.Sprintf(\"%s://%s%s\", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(port)), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).Send(c.lifecycleCtx, uri, http.MethodGet, nil, ErrSchedulingClientRequestFailed, distro.R().Scheduling)\n}\n\nfunc (c *Client) SendGetRequest(host string, port int, relativeURI string) ([]byte, error) {\n\tres, err := c.Get(host, port, relativeURI)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn res.Body()\n}\n\nfunc (c *Client) SendPostRequest(host string, port int, relativeURI string, body io.Reader) ([]byte, error) {\n\turi := fmt.Sprintf(\"%s://%s%s\", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(port)), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).SendRequest(c.lifecycleCtx, uri, http.MethodPost, body, ErrSchedulingClientRequestFailed, distro.R().Scheduling)\n}\n"
  },
  {
    "path": "pkg/scheduling/scheduling.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage scheduling\n\nimport (\n\t\"github.com/joomcode/errorx\"\n)\n\nvar ErrNS = errorx.NewNamespace(\"error.scheduling\")\n"
  },
  {
    "path": "pkg/swaggerserver/handler.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage swaggerserver\n\nimport (\n\t\"net/http\"\n\n\thttpSwagger \"github.com/swaggo/http-swagger\"\n\t// Swagger doc.\n\t_ \"github.com/pingcap/tidb-dashboard/swaggerspec\"\n)\n\nfunc Handler() http.Handler {\n\treturn httpSwagger.Handler()\n}\n"
  },
  {
    "path": "pkg/ticdc/client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage ticdc\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/httpc\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nvar ErrTiCDCClientRequestFailed = ErrNS.NewType(\"client_request_failed\")\n\nconst (\n\tdefaultTiCDCStatusAPITimeout = time.Second * 10\n)\n\ntype Client struct {\n\thttpClient   *httpc.Client\n\thttpScheme   string\n\tlifecycleCtx context.Context\n\ttimeout      time.Duration\n}\n\nfunc NewTiCDCClient(lc fx.Lifecycle, httpClient *httpc.Client, config *config.Config) *Client {\n\tclient := &Client{\n\t\thttpClient:   httpClient,\n\t\thttpScheme:   config.GetClusterHTTPScheme(),\n\t\tlifecycleCtx: nil,\n\t\ttimeout:      defaultTiCDCStatusAPITimeout,\n\t}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tclient.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn client\n}\n\nfunc (c Client) WithTimeout(timeout time.Duration) *Client {\n\tc.timeout = timeout\n\treturn &c\n}\n\nfunc (c Client) AddRequestHeader(key, value string) *Client {\n\tc.httpClient = c.httpClient.CloneAndAddRequestHeader(key, value)\n\treturn &c\n}\n\nfunc (c *Client) Get(host string, statusPort int, relativeURI string) (*httpc.Response, error) {\n\turi := fmt.Sprintf(\"%s://%s%s\", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(statusPort)), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).Send(c.lifecycleCtx, uri, http.MethodGet, nil, ErrTiCDCClientRequestFailed, distro.R().TiCDC)\n}\n\nfunc (c *Client) SendGetRequest(host string, statusPort int, relativeURI string) ([]byte, error) {\n\tres, err := c.Get(host, statusPort, relativeURI)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn res.Body()\n}\n\nfunc (c *Client) SendPostRequest(host string, statusPort int, relativeURI string, body io.Reader) ([]byte, error) {\n\turi := fmt.Sprintf(\"%s://%s%s\", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(statusPort)), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).SendRequest(c.lifecycleCtx, uri, http.MethodPost, body, ErrTiCDCClientRequestFailed, distro.R().TiCDC)\n}\n"
  },
  {
    "path": "pkg/ticdc/ticdc.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage ticdc\n\nimport (\n\t\"github.com/joomcode/errorx\"\n)\n\nvar ErrNS = errorx.NewNamespace(\"error.ticdc\")\n"
  },
  {
    "path": "pkg/tidb/client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tidb\n\nimport (\n\t\"context\"\n\t\"database/sql/driver\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/VividCortex/mysqlerr\"\n\t\"github.com/go-sql-driver/mysql\"\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/zap\"\n\tmysqlDriver \"gorm.io/driver/mysql\"\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/httpc\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nvar (\n\tErrTiDBConnFailed          = ErrNS.NewType(\"tidb_conn_failed\")\n\tErrTiDBAuthFailed          = ErrNS.NewType(\"tidb_auth_failed\")\n\tErrTiDBClientRequestFailed = ErrNS.NewType(\"client_request_failed\")\n)\n\nconst (\n\tdefaultTiDBStatusAPITimeout      = time.Second * 10\n\tdefaultTiDBSQLExecutionTimeoutMs = 600000 // 600s\n\n\t// When this environment variable is set, SQL requests will be always sent to this specific TiDB instance.\n\t// Calling `WithSQLAPIAddress` to enforce a SQL request endpoint will fail when opening the connection.\n\ttidbOverrideSQLEndpointEnvVar = \"TIDB_OVERRIDE_ENDPOINT\"\n\t// When this environment variable is set, status requests will be always sent to this specific TiDB instance.\n\t// Calling `WithStatusAPIAddress` to enforce a status API request endpoint will fail when opening the connection.\n\ttidbOverrideStatusEndpointEnvVar = \"TIDB_OVERRIDE_STATUS_ENDPOINT\"\n)\n\ntype Client struct {\n\tlifecycleCtx             context.Context\n\tforwarder                *Forwarder\n\tstatusAPIHTTPScheme      string\n\tstatusAPIAddress         string // Empty means to use address provided by forwarder\n\tenforceStatusAPIAddresss bool   // enforced status api address and ignore env override config\n\tstatusAPIHTTPClient      *httpc.Client\n\tstatusAPITimeout         time.Duration\n\tsqlAPITLSKey             string // Non empty means use this key as MySQL TLS config\n\tsqlAPIAddress            string // Empty means to use address provided by forwarder\n}\n\nfunc NewTiDBClient(lc fx.Lifecycle, config *config.Config, etcdClient *clientv3.Client, httpClient *httpc.Client) *Client {\n\tsqlAPITLSKey := \"\"\n\tif config.TiDBTLSConfig != nil {\n\t\tsqlAPITLSKey = \"tidb\"\n\t\t_ = mysql.RegisterTLSConfig(sqlAPITLSKey, config.TiDBTLSConfig)\n\t}\n\n\tclient := &Client{\n\t\tlifecycleCtx:             nil,\n\t\tforwarder:                newForwarder(lc, etcdClient),\n\t\tstatusAPIHTTPScheme:      config.GetClusterHTTPScheme(),\n\t\tstatusAPIAddress:         \"\",\n\t\tenforceStatusAPIAddresss: false,\n\t\tstatusAPIHTTPClient:      httpClient,\n\t\tstatusAPITimeout:         defaultTiDBStatusAPITimeout,\n\t\tsqlAPITLSKey:             sqlAPITLSKey,\n\t\tsqlAPIAddress:            \"\",\n\t}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tclient.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn client\n}\n\nfunc (c Client) WithStatusAPITimeout(timeout time.Duration) *Client {\n\tc.statusAPITimeout = timeout\n\treturn &c\n}\n\nfunc (c Client) WithStatusAPIAddress(host string, statusPort int) *Client {\n\tc.statusAPIAddress = net.JoinHostPort(host, strconv.Itoa(statusPort))\n\treturn &c\n}\n\nfunc (c Client) WithEnforcedStatusAPIAddress(host string, statusPort int) *Client {\n\tc.enforceStatusAPIAddresss = true\n\tc.statusAPIAddress = net.JoinHostPort(host, strconv.Itoa(statusPort))\n\treturn &c\n}\n\nfunc (c Client) WithSQLAPIAddress(host string, sqlPort int) *Client {\n\tc.sqlAPIAddress = net.JoinHostPort(host, strconv.Itoa(sqlPort))\n\treturn &c\n}\n\nfunc (c *Client) OpenSQLConn(user string, pass string) (*gorm.DB, error) {\n\tvar err error\n\n\toverrideEndpoint := os.Getenv(tidbOverrideSQLEndpointEnvVar)\n\t// the `tidbOverrideSQLEndpointEnvVar` and the `Client.sqlAPIAddress` have the same override priority, if both exist, an error is returned\n\tif overrideEndpoint != \"\" && c.sqlAPIAddress != \"\" {\n\t\tlog.Warn(fmt.Sprintf(\"Reject to establish a target specified %s SQL connection since `%s` is set\", distro.R().TiDB, tidbOverrideSQLEndpointEnvVar))\n\t\treturn nil, ErrTiDBConnFailed.New(\"%s Dashboard is configured to only connect to specified %s host\", distro.R().TiDB, distro.R().TiDB)\n\t}\n\n\tvar addr string\n\tswitch {\n\tcase overrideEndpoint != \"\":\n\t\taddr = overrideEndpoint\n\tdefault:\n\t\taddr = c.sqlAPIAddress\n\t}\n\tif addr == \"\" {\n\t\tif addr, err = c.forwarder.getEndpointAddr(c.forwarder.sqlPort); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tdsnConfig := mysql.NewConfig()\n\tdsnConfig.Net = \"tcp\"\n\tdsnConfig.Addr = addr\n\tdsnConfig.User = user\n\tdsnConfig.Passwd = pass\n\tdsnConfig.Timeout = time.Second\n\tdsnConfig.ParseTime = true\n\tdsnConfig.Loc = time.Local\n\tdsnConfig.MultiStatements = true\n\tdsnConfig.TLSConfig = c.sqlAPITLSKey\n\tdsn := dsnConfig.FormatDSN()\n\n\tdb, err := gorm.Open(mysqlDriver.Open(dsn))\n\tif err != nil {\n\t\tif _, ok := err.(*net.OpError); ok || err == driver.ErrBadConn {\n\t\t\tif strings.HasPrefix(addr, \"0.0.0.0:\") {\n\t\t\t\tlog.Warn(fmt.Sprintf(\"%s reported its address to be 0.0.0.0. Please specify `-advertise-address` command line parameter when running %s\", distro.R().TiDB, distro.R().TiDB))\n\t\t\t}\n\t\t\tif c.forwarder.sqlProxy.noAliveRemote.Load() {\n\t\t\t\treturn nil, ErrNoAliveTiDB.NewWithNoMessage()\n\t\t\t}\n\t\t\treturn nil, ErrTiDBConnFailed.Wrap(err, \"failed to connect to %s\", distro.R().TiDB)\n\t\t} else if mysqlErr, ok := err.(*mysql.MySQLError); ok {\n\t\t\tif mysqlErr.Number == mysqlerr.ER_ACCESS_DENIED_ERROR {\n\t\t\t\treturn nil, ErrTiDBAuthFailed.New(\"bad %s username or password\", distro.R().TiDB)\n\t\t\t}\n\t\t}\n\t\tlog.Warn(fmt.Sprintf(\"Unknown error occurred while opening %s connection\", distro.R().TiDB), zap.Error(err))\n\t\treturn nil, err\n\t}\n\n\tif err := db.Exec(fmt.Sprintf(\"SET SESSION max_execution_time = '%d'\", defaultTiDBSQLExecutionTimeoutMs)).Error; err != nil {\n\t\tlog.Error(\"Failed to set max_execution_time\", zap.Error(err))\n\t\tif d, err := db.DB(); err == nil && db != nil {\n\t\t\tif cerr := d.Close(); cerr != nil {\n\t\t\t\tlog.Error(\"Failed to close database after setting max_execution_time\", zap.Error(cerr))\n\t\t\t}\n\t\t}\n\t\treturn nil, ErrTiDBClientRequestFailed.Wrap(err, \"failed to set max_execution_time\")\n\t}\n\n\treturn db, nil\n}\n\nfunc (c *Client) Get(relativeURI string) (*httpc.Response, error) {\n\tvar err error\n\n\toverrideEndpoint := os.Getenv(tidbOverrideStatusEndpointEnvVar)\n\t// the `tidbOverrideStatusEndpointEnvVar` and the `Client.statusAPIAddress` have the same override priority, if both exist and have not enforced `Client.statusAPIAddress` then an error is returned\n\tif overrideEndpoint != \"\" && c.statusAPIAddress != \"\" && !c.enforceStatusAPIAddresss {\n\t\tlog.Warn(fmt.Sprintf(\"Reject to establish a target specified %s status connection since `%s` is set\", distro.R().TiDB, tidbOverrideStatusEndpointEnvVar))\n\t\treturn nil, ErrTiDBConnFailed.New(\"%s Dashboard is configured to only connect to specified %s host\", distro.R().TiDB, distro.R().TiDB)\n\t}\n\n\tvar addr string\n\tswitch {\n\tcase c.enforceStatusAPIAddresss:\n\t\taddr = c.statusAPIAddress\n\tcase overrideEndpoint != \"\":\n\t\taddr = overrideEndpoint\n\tdefault:\n\t\taddr = c.statusAPIAddress\n\t}\n\tif addr == \"\" {\n\t\tif addr, err = c.forwarder.getEndpointAddr(c.forwarder.statusPort); err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\turi := fmt.Sprintf(\"%s://%s%s\", c.statusAPIHTTPScheme, addr, relativeURI)\n\tres, err := c.statusAPIHTTPClient.\n\t\tWithTimeout(c.statusAPITimeout).\n\t\tSend(c.lifecycleCtx, uri, http.MethodGet, nil, ErrTiDBClientRequestFailed, distro.R().TiDB)\n\tif err != nil && c.forwarder.statusProxy.noAliveRemote.Load() {\n\t\treturn nil, ErrNoAliveTiDB.NewWithNoMessage()\n\t}\n\treturn res, err\n}\n\n// FIXME: SendGetRequest should be extracted, as a common method.\nfunc (c *Client) SendGetRequest(relativeURI string) ([]byte, error) {\n\tres, err := c.Get(relativeURI)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn res.Body()\n}\n"
  },
  {
    "path": "pkg/tidb/forwarder.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tidb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/cenkalti/backoff/v4\"\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils/topology\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nvar ErrNoAliveTiDB = ErrNS.NewType(\"no_alive_tidb\")\n\ntype forwarderConfig struct {\n\tTiDBRetrieveTimeout time.Duration\n\tTiDBPollInterval    time.Duration\n\tProxyTimeout        time.Duration\n\tProxyCheckInterval  time.Duration\n}\n\ntype Forwarder struct {\n\tlifecycleCtx context.Context\n\n\tconfig     *forwarderConfig\n\tetcdClient *clientv3.Client\n\n\tsqlProxy    *proxy\n\tsqlPort     int\n\tstatusProxy *proxy\n\tstatusPort  int\n}\n\nfunc (f *Forwarder) Start(ctx context.Context) error {\n\tf.lifecycleCtx = ctx\n\n\tvar err error\n\tif f.sqlProxy, err = f.createProxy(); err != nil {\n\t\treturn err\n\t}\n\tif f.statusProxy, err = f.createProxy(); err != nil {\n\t\treturn err\n\t}\n\n\tf.sqlPort = f.sqlProxy.port()\n\tf.statusPort = f.statusProxy.port()\n\n\tgo f.pollingForTiDB()\n\tgo f.sqlProxy.run(ctx)\n\tgo f.statusProxy.run(ctx)\n\n\treturn nil\n}\n\nfunc (f *Forwarder) createProxy() (*proxy, error) {\n\tl, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tproxy := newProxy(l, nil, f.config.ProxyCheckInterval, f.config.ProxyTimeout)\n\treturn proxy, nil\n}\n\nfunc (f *Forwarder) pollingForTiDB() {\n\tebo := backoff.NewExponentialBackOff()\n\tebo.MaxInterval = f.config.TiDBPollInterval\n\tbo := backoff.WithContext(ebo, f.lifecycleCtx)\n\n\tfor {\n\t\tvar allTiDB []topology.TiDBInfo\n\t\terr := backoff.Retry(func() error {\n\t\t\tvar err error\n\t\t\tallTiDB, err = topology.FetchTiDBTopology(bo.Context(), f.etcdClient)\n\t\t\treturn err\n\t\t}, bo)\n\t\tif err == nil {\n\t\t\tstatusEndpoints := make(map[string]struct{}, len(allTiDB))\n\t\t\ttidbEndpoints := make(map[string]struct{}, len(allTiDB))\n\t\t\tfor _, server := range allTiDB {\n\t\t\t\tif server.Status == topology.ComponentStatusUp {\n\t\t\t\t\ttidbEndpoints[net.JoinHostPort(server.IP, strconv.Itoa(int(server.Port)))] = struct{}{}\n\t\t\t\t\tstatusEndpoints[net.JoinHostPort(server.IP, strconv.Itoa(int(server.StatusPort)))] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t\tf.sqlProxy.updateRemotes(tidbEndpoints)\n\t\t\tf.statusProxy.updateRemotes(statusEndpoints)\n\t\t}\n\n\t\tselect {\n\t\tcase <-f.lifecycleCtx.Done():\n\t\t\treturn\n\t\tcase <-time.After(f.config.TiDBPollInterval):\n\t\t}\n\t}\n}\n\nfunc (f *Forwarder) getEndpointAddr(port int) (string, error) {\n\tif f.statusProxy.noAliveRemote.Load() {\n\t\tlog.Warn(fmt.Sprintf(\"Unable to resolve connection address since no alive %s instance\", distro.R().TiDB))\n\t\treturn \"\", ErrNoAliveTiDB.NewWithNoMessage()\n\t}\n\treturn fmt.Sprintf(\"127.0.0.1:%d\", port), nil\n}\n\nfunc newForwarder(lc fx.Lifecycle, etcdClient *clientv3.Client) *Forwarder {\n\tf := &Forwarder{\n\t\tconfig: &forwarderConfig{\n\t\t\tTiDBRetrieveTimeout: time.Second,\n\t\t\tTiDBPollInterval:    5 * time.Second,\n\t\t\tProxyTimeout:        3 * time.Second,\n\t\t\tProxyCheckInterval:  2 * time.Second,\n\t\t},\n\t\tetcdClient: etcdClient,\n\t}\n\tlc.Append(fx.Hook{\n\t\tOnStart: f.Start,\n\t})\n\treturn f\n}\n"
  },
  {
    "path": "pkg/tidb/model/codec.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage model\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\n\t\"github.com/pingcap/errors\"\n)\n\nvar (\n\ttablePrefix  = []byte{'t'}\n\tmetaPrefix   = []byte{'m'}\n\trecordPrefix = []byte{'r'}\n)\n\nconst (\n\tsignMask uint64 = 0x8000000000000000\n\n\tencGroupSize = 8\n\tencMarker    = byte(0xFF)\n\tencPad       = byte(0x0)\n)\n\n// Key represents high-level TiDB Key type.\ntype Key []byte\n\n// KeyInfoBuffer can obtain the meta information of the TiDB Key.\n// It can be reused, thereby reducing memory applications.\ntype KeyInfoBuffer []byte\n\n// DecodeKey obtains the KeyInfoBuffer from a TiDB Key.\nfunc (buf *KeyInfoBuffer) DecodeKey(key Key) (KeyInfoBuffer, error) {\n\t_, result, err := decodeBytes(key, *buf)\n\tif err != nil {\n\t\t*buf = (*buf)[:0]\n\t\treturn nil, err\n\t}\n\n\t*buf = result\n\treturn result, nil\n}\n\n// MetaOrTable checks if the key is a meta key or table key.\n// If the key is a meta key, it returns true and 0.\n// If the key is a table key, it returns false and table ID.\n// Otherwise, it returns false and 0.\nfunc (buf KeyInfoBuffer) MetaOrTable() (isMeta bool, tableID int64) {\n\tif bytes.HasPrefix(buf, metaPrefix) {\n\t\treturn true, 0\n\t}\n\tif bytes.HasPrefix(buf, tablePrefix) {\n\t\t_, tableID, _ := decodeInt(buf[len(tablePrefix):])\n\t\treturn false, tableID\n\t}\n\treturn false, 0\n}\n\n// RowInfo returns the row ID of the key, if the key is not table key, returns 0.\nfunc (buf KeyInfoBuffer) RowInfo() (isCommonHandle bool, rowID int64) {\n\tif !bytes.HasPrefix(buf, tablePrefix) || len(buf) < 19 || (buf[9] != '_' || buf[10] != 'r') {\n\t\treturn\n\t}\n\tisCommonHandle = len(buf) != 19\n\tif !isCommonHandle {\n\t\t_, rowID, _ = decodeInt(buf[11:19])\n\t}\n\treturn\n}\n\n// IndexInfo returns the row ID of the key, if the key is not table key, returns 0.\nfunc (buf KeyInfoBuffer) IndexInfo() (indexID int64) {\n\tif !bytes.HasPrefix(buf, tablePrefix) || len(buf) < 19 || (buf[9] != '_' || buf[10] != 'i') {\n\t\treturn\n\t}\n\t_, indexID, _ = decodeInt(buf[11:19])\n\treturn\n}\n\n// GenerateTableKey generates a table split key.\nfunc (buf *KeyInfoBuffer) GenerateKey(tableID, rowID int64) Key {\n\tif tableID == 0 {\n\t\treturn nil\n\t}\n\n\tdata := *buf\n\tif data == nil {\n\t\tlength := len(tablePrefix) + 8\n\t\tif rowID != 0 {\n\t\t\tlength = len(tablePrefix) + len(recordPrefix) + 8*2\n\t\t}\n\t\tdata = make([]byte, 0, length)\n\t} else {\n\t\tdata = data[:0]\n\t}\n\n\tdata = append(data, tablePrefix...)\n\tdata = encodeInt(data, tableID)\n\tif rowID != 0 {\n\t\tdata = append(data, recordPrefix...)\n\t\tdata = encodeInt(data, rowID)\n\t}\n\n\t*buf = data\n\n\treturn encodeBytes(data)\n}\n\nvar pads = make([]byte, encGroupSize)\n\n// decodeBytes decodes bytes which is encoded by encodeBytes before,\n// returns the leftover bytes and decoded value if no error.\nfunc decodeBytes(b []byte, buf []byte) (rest []byte, result []byte, err error) {\n\tif buf == nil {\n\t\tbuf = make([]byte, 0, len(b))\n\t}\n\tbuf = buf[:0]\n\n\tfor {\n\t\tif len(b) < encGroupSize+1 {\n\t\t\treturn nil, nil, errors.New(\"insufficient bytes to decode value\")\n\t\t}\n\n\t\tgroupBytes := b[:encGroupSize+1]\n\n\t\tgroup := groupBytes[:encGroupSize]\n\t\tmarker := groupBytes[encGroupSize]\n\n\t\tpadCount := encMarker - marker\n\t\tif padCount > encGroupSize {\n\t\t\treturn nil, nil, errors.Errorf(\"invalid marker byte, group bytes %q\", groupBytes)\n\t\t}\n\n\t\trealGroupSize := encGroupSize - padCount\n\t\tbuf = append(buf, group[:realGroupSize]...)\n\t\tb = b[encGroupSize+1:]\n\n\t\tif padCount != 0 {\n\t\t\t// Check validity of padding bytes.\n\t\t\tfor _, v := range group[realGroupSize:] {\n\t\t\t\tif v != encPad {\n\t\t\t\t\treturn nil, nil, errors.Errorf(\"invalid padding byte, group bytes %q\", groupBytes)\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn b, buf, nil\n}\n\n// encodeBytes guarantees the encoded value is in ascending order for comparison,\n// encoding with the following rule:\n//\n//\t[group1][marker1]...[groupN][markerN]\n//\tgroup is 8 bytes slice which is padding with 0.\n//\tmarker is `0xFF - padding 0 count`\n//\n// For example:\n//\n//\t[] -> [0, 0, 0, 0, 0, 0, 0, 0, 247]\n//\t[1, 2, 3] -> [1, 2, 3, 0, 0, 0, 0, 0, 250]\n//\t[1, 2, 3, 0] -> [1, 2, 3, 0, 0, 0, 0, 0, 251]\n//\t[1, 2, 3, 4, 5, 6, 7, 8] -> [1, 2, 3, 4, 5, 6, 7, 8, 255, 0, 0, 0, 0, 0, 0, 0, 0, 247]\n//\n// Refer: https://github.com/facebook/mysql-5.6/wiki/MyRocks-record-format#memcomparable-format\nfunc encodeBytes(data []byte) []byte {\n\t// Allocate more space to avoid unnecessary slice growing.\n\t// Assume that the byte slice size is about `(len(data) / encGroupSize + 1) * (encGroupSize + 1)` bytes,\n\t// that is `(len(data) / 8 + 1) * 9` in our implement.\n\tdLen := len(data)\n\tresult := make([]byte, 0, (dLen/encGroupSize+1)*(encGroupSize+1))\n\tfor idx := 0; idx <= dLen; idx += encGroupSize {\n\t\tremain := dLen - idx\n\t\tpadCount := 0\n\t\tif remain >= encGroupSize {\n\t\t\tresult = append(result, data[idx:idx+encGroupSize]...)\n\t\t} else {\n\t\t\tpadCount = encGroupSize - remain\n\t\t\tresult = append(result, data[idx:]...)\n\t\t\tresult = append(result, pads[:padCount]...)\n\t\t}\n\n\t\tmarker := encMarker - byte(padCount)\n\t\tresult = append(result, marker)\n\t}\n\treturn result\n}\n\n// decodeInt decodes value encoded by EncodeInt before.\n// It returns the leftover un-decoded slice, decoded value if no error.\nfunc decodeInt(b []byte) ([]byte, int64, error) {\n\tif len(b) < 8 {\n\t\treturn nil, 0, errors.New(\"insufficient bytes to decode value\")\n\t}\n\n\tu := binary.BigEndian.Uint64(b[:8])\n\tv := decodeCmpUintToInt(u)\n\tb = b[8:]\n\treturn b, v, nil\n}\n\n// encodeInt appends the encoded value to slice b and returns the appended slice.\n// encodeInt guarantees that the encoded value is in ascending order for comparison.\nfunc encodeInt(b []byte, v int64) []byte {\n\tvar data [8]byte\n\tu := encodeIntToCmpUint(v)\n\tbinary.BigEndian.PutUint64(data[:], u)\n\treturn append(b, data[:]...)\n}\n\nfunc decodeCmpUintToInt(u uint64) int64 {\n\treturn int64(u ^ signMask)\n}\n\nfunc encodeIntToCmpUint(v int64) uint64 {\n\treturn uint64(v) ^ signMask\n}\n"
  },
  {
    "path": "pkg/tidb/model/codec_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage model\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/check\"\n)\n\nfunc TestTable(t *testing.T) {\n\tcheck.TestingT(t)\n}\n\nvar _ = check.Suite(&testCodecSuite{})\n\ntype testCodecSuite struct{}\n\nfunc (s *testCodecSuite) TestDecodeBytes(c *check.C) {\n\tkey := \"abcdefghijklmnopqrstuvwxyz\"\n\tfor i := 0; i < len(key); i++ {\n\t\t_, k, err := decodeBytes(encodeBytes([]byte(key[:i])), nil)\n\t\tc.Assert(err, check.IsNil)\n\t\tc.Assert(string(k), check.Equals, key[:i])\n\t}\n}\n\nfunc (s *testCodecSuite) TestTiDBInfo(c *check.C) {\n\tbuf := new(KeyInfoBuffer)\n\n\t// no encode\n\t_, err := buf.DecodeKey([]byte(\"t\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\xff\"))\n\tc.Assert(err, check.NotNil)\n\n\ttestcases := []struct {\n\t\tKey            string\n\t\tIsMeta         bool\n\t\tTableID        int64\n\t\tIsCommonHandle bool\n\t\tRowID          int64\n\t\tIndexID        int64\n\t}{\n\t\t{\n\t\t\t\"T\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xff\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"t\\x80\\x00\\x00\\x00\\x00\\x00\\xff\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"t\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\xff\",\n\t\t\tfalse,\n\t\t\t0xff,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"t\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\xff_i\\x01\\x02\",\n\t\t\tfalse,\n\t\t\t0xff,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"t\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\xff_i\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x02\",\n\t\t\tfalse,\n\t\t\t0xff,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t2,\n\t\t},\n\t\t{\n\t\t\t\"t\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\xff_r\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x02\",\n\t\t\tfalse,\n\t\t\t0xff,\n\t\t\tfalse,\n\t\t\t2,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"t\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\xff_r\\x03\\x80\\x00\\x00\\x00\\x00\\x02\\r\\xaf\\x03\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x03\\x80\\x00\\x00\\x00\\x00\\x00\\b%\",\n\t\t\tfalse,\n\t\t\t0xff,\n\t\t\ttrue,\n\t\t\t0,\n\t\t\t0,\n\t\t},\n\t}\n\n\tfor _, t := range testcases {\n\t\tkey := encodeBytes([]byte(t.Key))\n\t\t_, err := buf.DecodeKey(key)\n\t\tc.Assert(err, check.IsNil)\n\t\tisMeta, tableID := buf.MetaOrTable()\n\t\tc.Assert(isMeta, check.Equals, t.IsMeta)\n\t\tc.Assert(tableID, check.Equals, t.TableID)\n\t\tisCommonHandle, rowID := buf.RowInfo()\n\t\tc.Assert(isCommonHandle, check.Equals, t.IsCommonHandle)\n\t\tc.Assert(rowID, check.Equals, t.RowID)\n\t\tindexID := buf.IndexInfo()\n\t\tc.Assert(indexID, check.Equals, t.IndexID)\n\t}\n}\n"
  },
  {
    "path": "pkg/tidb/model/model.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage model\n\n// SchemaState is the state for schema elements.\ntype SchemaState byte\n\nconst (\n\t// StateNone means this schema element is absent and can't be used.\n\tStateNone SchemaState = iota\n\t// StateDeleteOnly means we can only delete items for this schema element.\n\tStateDeleteOnly\n\t// StateWriteOnly means we can use any write operation on this schema element,\n\t// but outer can't read the changed data.\n\tStateWriteOnly\n\t// StateWriteReorganization means we are re-organizing whole data after write only state.\n\tStateWriteReorganization\n\t// StateDeleteReorganization means we are re-organizing whole data after delete only state.\n\tStateDeleteReorganization\n\t// StatePublic means this schema element is ok for all write and read operations.\n\tStatePublic\n)\n\n// CIStr is case insensitive string.\ntype CIStr struct {\n\tO string `json:\"O\"` // Original string.\n\tL string `json:\"L\"` // Lower case string.\n}\n\n// DBInfo provides meta data describing a DB.\ntype DBInfo struct {\n\tID    int64       `json:\"id\"`\n\tName  CIStr       `json:\"db_name\"`\n\tState SchemaState `json:\"state\"`\n}\n\n// IndexInfo provides meta data describing a DB index.\n// It corresponds to the statement `CREATE INDEX Name ON Table (Column);`\n// See https://dev.mysql.com/doc/refman/5.7/en/create-index.html\ntype IndexInfo struct {\n\tID   int64 `json:\"id\"`\n\tName CIStr `json:\"idx_name\"`\n}\n\n// PartitionDefinition defines a single partition.\ntype PartitionDefinition struct {\n\tID   int64 `json:\"id\"`\n\tName CIStr `json:\"name\"`\n}\n\n// PartitionInfo provides table partition info.\ntype PartitionInfo struct {\n\t// User may already creates table with partition but table partition is not\n\t// yet supported back then. When Enable is true, write/read need use tid\n\t// rather than pid.\n\tEnable      bool                   `json:\"enable\"`\n\tDefinitions []*PartitionDefinition `json:\"definitions\"`\n}\n\n// TableInfo provides meta data describing a DB table.\ntype TableInfo struct {\n\tID        int64          `json:\"id\"`\n\tName      CIStr          `json:\"name\"`\n\tIndices   []*IndexInfo   `json:\"index_info\"`\n\tPartition *PartitionInfo `json:\"partition\"`\n\tVersion   *int64         `json:\"version\"`\n}\n\n// GetPartitionInfo returns the partition information.\nfunc (t *TableInfo) GetPartitionInfo() *PartitionInfo {\n\tif t.Partition != nil && t.Partition.Enable {\n\t\treturn t.Partition\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/tidb/proxy.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tidb\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/atomic\"\n\t\"go.uber.org/zap\"\n)\n\ntype remote struct {\n\taddr     string\n\tinactive *atomic.Bool\n}\n\nfunc (r *remote) isActive() bool {\n\treturn !r.inactive.Load()\n}\n\nfunc (r *remote) becomeInactive() {\n\tr.inactive.Store(true)\n\tlog.Debug(\"remote become inactive\", zap.String(\"remote\", r.addr))\n}\n\nfunc (r *remote) checkAlive(timeout time.Duration) error {\n\tconn, err := net.DialTimeout(\"tcp\", r.addr, timeout)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_ = conn.Close()\n\tr.inactive.Store(false)\n\treturn nil\n}\n\ntype proxy struct {\n\tlistener      net.Listener\n\tcheckInterval time.Duration\n\tdialTimeout   time.Duration\n\n\tnoAliveRemote *atomic.Bool\n\tremotes       sync.Map\n\tcurrent       *atomic.String\n}\n\nfunc newProxy(l net.Listener, endpoints map[string]string, checkInterval time.Duration, timeout time.Duration) *proxy {\n\tif checkInterval <= 0 {\n\t\tcheckInterval = 2 * time.Second\n\t}\n\tif timeout <= 0 {\n\t\ttimeout = 3 * time.Second\n\t}\n\tp := &proxy{\n\t\tlistener:      l,\n\t\tremotes:       sync.Map{},\n\t\tdialTimeout:   timeout,\n\t\tcheckInterval: checkInterval,\n\t\tnoAliveRemote: atomic.NewBool(len(endpoints) == 0),\n\t\tcurrent:       atomic.NewString(\"\"),\n\t}\n\tfor key, e := range endpoints {\n\t\tp.remotes.Store(key, &remote{addr: e, inactive: atomic.NewBool(true)})\n\t}\n\treturn p\n}\n\nfunc (p *proxy) port() int {\n\treturn p.listener.Addr().(*net.TCPAddr).Port\n}\n\nfunc (p *proxy) updateRemotes(remotes map[string]struct{}) {\n\tif len(remotes) == 0 {\n\t\tp.remotes.Range(func(key, _ interface{}) bool {\n\t\t\tp.remotes.Delete(key)\n\t\t\treturn true\n\t\t})\n\t\tp.noAliveRemote.Store(true)\n\t\treturn\n\t}\n\t// update or create new remote\n\tfor addr := range remotes {\n\t\tif _, ok := p.remotes.Load(addr); !ok {\n\t\t\tlog.Debug(\"proxy adds new remote\", zap.String(\"remote\", addr))\n\t\t\tp.remotes.Store(addr, &remote{\n\t\t\t\taddr:     addr,\n\t\t\t\tinactive: atomic.NewBool(true),\n\t\t\t})\n\t\t}\n\t}\n\t// remove old remote\n\tp.remotes.Range(func(key, _ interface{}) bool {\n\t\taddr := key.(string)\n\t\tif _, ok := remotes[addr]; !ok {\n\t\t\tlog.Debug(\"proxy discards remote\", zap.String(\"remote\", addr))\n\t\t\tp.remotes.Delete(key)\n\t\t}\n\t\treturn true\n\t})\n\tp.noAliveRemote.Store(false)\n}\n\nfunc (p *proxy) serve(in net.Conn) {\n\tout := p.pickActiveConn()\n\tif out == nil {\n\t\tlog.Warn(\"no alive remote, drop incoming conn\")\n\t\t_ = in.Close()\n\t\treturn\n\t}\n\tdeadline := time.Now().Add(10 * time.Minute)\n\tif err := in.SetDeadline(deadline); err != nil {\n\t\tlog.Warn(\"input set deadline failed\", zap.Error(err))\n\t\t_ = in.Close()\n\t\treturn\n\t}\n\tif err := out.SetReadDeadline(deadline); err != nil {\n\t\tlog.Warn(\"output set deadline failed\", zap.Error(err))\n\t\t_ = in.Close()\n\t\treturn\n\t}\n\tdone := make(chan struct{})\n\t// bidirectional copy\n\tgo func() {\n\t\tdefer func() {\n\t\t\tdone <- struct{}{}\n\t\t}()\n\t\t// nolint\n\t\tio.Copy(in, out)\n\t}()\n\t// nolint\n\tio.Copy(out, in)\n\t<-done\n\t_ = out.Close()\n\t_ = in.Close()\n}\n\nfunc (p *proxy) pickActiveConn() (out net.Conn) {\n\tvar (\n\t\terr    error\n\t\tpicked *remote\n\t)\n\tfor {\n\t\tpicked = p.pick()\n\t\tif picked == nil {\n\t\t\tbreak\n\t\t}\n\t\tout, err = net.DialTimeout(\"tcp\", picked.addr, p.dialTimeout)\n\t\tif err == nil {\n\t\t\tbreak\n\t\t}\n\t\tp.current.Store(\"\")\n\t\tpicked.becomeInactive()\n\t\tlog.Warn(\"remote become inactive\", zap.String(\"remote\", picked.addr))\n\t}\n\tp.noAliveRemote.Store(out == nil)\n\treturn\n}\n\n// pick returns an active remote if there is any.\nfunc (p *proxy) pick() *remote {\n\tvar picked *remote\n\tif p.current.Load() == \"\" {\n\t\tp.remotes.Range(func(key, value interface{}) bool {\n\t\t\tid := key.(string)\n\t\t\tr := value.(*remote)\n\t\t\tif r.isActive() {\n\t\t\t\tp.current.Store(id)\n\t\t\t\tpicked = r\n\t\t\t\treturn false\n\t\t\t}\n\t\t\treturn true\n\t\t})\n\t}\n\tif picked != nil {\n\t\treturn picked\n\t}\n\tcurRemote := p.current.Load()\n\tif curRemote != \"\" {\n\t\tr, ok := p.remotes.Load(curRemote)\n\t\tif ok {\n\t\t\tpicked = r.(*remote)\n\t\t} else {\n\t\t\tp.current.Store(\"\")\n\t\t}\n\t}\n\treturn picked\n}\n\nfunc (p *proxy) doCheck(ctx context.Context) {\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase <-time.After(p.checkInterval):\n\t\t\tp.remotes.Range(func(_, value interface{}) bool {\n\t\t\t\trmt := value.(*remote)\n\t\t\t\tif rmt.isActive() {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\tgo func(r *remote) {\n\t\t\t\t\tlog.Debug(\"run remote check\", zap.String(\"remote\", r.addr))\n\t\t\t\t\tif err := r.checkAlive(p.dialTimeout); err != nil {\n\t\t\t\t\t\tlog.Warn(\"fail to recv activity from remote, stay inactive and wait to next checking round\", zap.String(\"remote\", r.addr), zap.Duration(\"interval\", p.checkInterval), zap.Error(err))\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlog.Debug(\"remote become active\", zap.String(\"remote\", r.addr))\n\t\t\t\t\t}\n\t\t\t\t}(rmt)\n\t\t\t\treturn true\n\t\t\t})\n\t\t}\n\t}\n}\n\nfunc (p *proxy) run(ctx context.Context) {\n\tendpoints := make([]string, 0)\n\tp.remotes.Range(func(_, value interface{}) bool {\n\t\tr := value.(*remote)\n\t\tendpoints = append(endpoints, r.addr)\n\t\treturn true\n\t})\n\tlog.Info(\"start serve requests to remotes\", zap.String(\"endpoint\", p.listener.Addr().String()), zap.Strings(\"remotes\", endpoints))\n\tgo p.doCheck(ctx)\n\n\tdefer p.listener.Close()\n\t// wait a check round before serve connections\n\tselect {\n\tcase <-ctx.Done():\n\t\treturn\n\tcase <-time.After(p.checkInterval + time.Second):\n\t}\n\t// serve\n\tfor {\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t\tincoming, err := p.listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\tlog.Warn(\"got err from listener\", zap.Error(err), zap.String(\"from\", p.listener.Addr().String()))\n\t\t\t} else {\n\t\t\t\tgo p.serve(incoming)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "pkg/tidb/proxy_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tidb\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestProxy(t *testing.T) {\n\tl, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer l.Close()\n\twant := \"hello proxy\"\n\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\t_, err := w.Write([]byte(want))\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}))\n\tdefer server.Close()\n\tu, err := url.Parse(server.URL)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tp := newProxy(l, map[string]string{\"test\": fmt.Sprintf(\"%s:%s\", u.Hostname(), u.Port())}, 0, 0)\n\tctx, cancel := context.WithCancel(context.Background())\n\tgo p.run(ctx)\n\tdefer cancel()\n\n\tu.Host = l.Addr().String()\n\tres, err := http.Get(u.String())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgot, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.Equal(t, want, string(got))\n}\n\nfunc TestProxyPick(t *testing.T) {\n\tl, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer l.Close()\n\tn := 3\n\tresponseData := \"test\"\n\tendpoints := make(map[string]string)\n\tpicked := make(map[int]bool)\n\tservers := make(map[int]*httptest.Server)\n\tvar currentPicked int\n\tfor i := range n {\n\t\tidx := i\n\t\tserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\t\tpicked[idx] = true\n\t\t\tcurrentPicked = idx\n\t\t\t_, err := w.Write([]byte(responseData))\n\t\t\tif err != nil {\n\t\t\t\tt.Fatal(err)\n\t\t\t}\n\t\t}))\n\t\tdefer server.Close()\n\t\tu, err := url.Parse(server.URL)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\tkey := strconv.Itoa(i)\n\t\tendpoint := fmt.Sprintf(\"%s:%s\", u.Hostname(), u.Port())\n\t\tendpoints[key] = endpoint\n\t\tservers[idx] = server\n\t}\n\tp := newProxy(l, endpoints, 0, 0)\n\tctx, cancel := context.WithCancel(context.Background())\n\tgo p.run(ctx)\n\tdefer cancel()\n\n\tfor range n {\n\t\tclient := &http.Client{}\n\t\tres, err := client.Get(\"http://\" + l.Addr().String())\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\t_, err = io.ReadAll(res.Body)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t\t// close conn manually to force proxy re-pick remote\n\t\tclient.CloseIdleConnections()\n\t\ttime.Sleep(time.Second)\n\t}\n\t// Always pick the same active remote\n\tassert.Equal(t, 1, len(picked))\n\tps := servers[currentPicked]\n\tif ps == nil {\n\t\tt.Fatal(\"Fail to get current picked server\")\n\t}\n\t// Shutdown current server to see if we can pick a new one\n\tps.Close()\n\tclient := &http.Client{}\n\ttarget := \"http://\" + l.Addr().String()\n\tassertRespData(t, client, responseData, target)\n\n\t// Remove current picked from remotes and test out picking\n\tp.remotes.Delete(strconv.Itoa(currentPicked))\n\tps = servers[currentPicked]\n\tif ps == nil {\n\t\tt.Fatal(\"Fail to get current picked server\")\n\t}\n\tps.Close()\n\t// First conn will be dropped as the current picked remote is deleted\n\t_, err = client.Get(target)\n\tassert.NotNil(t, err)\n\t// Then pick a new remote\n\tassertRespData(t, client, responseData, target)\n}\n\nfunc assertRespData(t *testing.T, client *http.Client, expect string, target string) {\n\tres, err := client.Get(target)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdata, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tassert.Equal(t, expect, string(data))\n}\n"
  },
  {
    "path": "pkg/tidb/tidb.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tidb\n\nimport (\n\t\"github.com/joomcode/errorx\"\n)\n\nvar ErrNS = errorx.NewNamespace(\"error.tidb\")\n"
  },
  {
    "path": "pkg/tiflash/client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tiflash\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/httpc\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nvar ErrFlashClientRequestFailed = ErrNS.NewType(\"client_request_failed\")\n\nconst (\n\tdefaultTiFlashStatusAPITimeout = time.Second * 10\n)\n\ntype Client struct {\n\thttpClient   *httpc.Client\n\thttpScheme   string\n\tlifecycleCtx context.Context\n\ttimeout      time.Duration\n}\n\nfunc NewTiFlashClient(lc fx.Lifecycle, httpClient *httpc.Client, config *config.Config) *Client {\n\tclient := &Client{\n\t\thttpClient:   httpClient,\n\t\thttpScheme:   config.GetClusterHTTPScheme(),\n\t\tlifecycleCtx: nil,\n\t\ttimeout:      defaultTiFlashStatusAPITimeout,\n\t}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tclient.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn client\n}\n\nfunc (c Client) GetHTTPScheme() string {\n\treturn c.httpScheme\n}\n\nfunc (c Client) WithTimeout(timeout time.Duration) *Client {\n\tc.timeout = timeout\n\treturn &c\n}\n\nfunc (c Client) AddRequestHeader(key, value string) *Client {\n\tc.httpClient = c.httpClient.CloneAndAddRequestHeader(key, value)\n\treturn &c\n}\n\nfunc (c *Client) Get(host string, statusPort int, relativeURI string) (*httpc.Response, error) {\n\turi := fmt.Sprintf(\"%s://%s%s\", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(statusPort)), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).Send(c.lifecycleCtx, uri, http.MethodGet, nil, ErrFlashClientRequestFailed, distro.R().TiFlash)\n}\n\nfunc (c *Client) SendGetRequest(host string, statusPort int, relativeURI string) ([]byte, error) {\n\tres, err := c.Get(host, statusPort, relativeURI)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn res.Body()\n}\n\nfunc (c *Client) SendPostRequest(host string, statusPort int, relativeURI string, body io.Reader) ([]byte, error) {\n\turi := fmt.Sprintf(\"%s://%s%s\", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(statusPort)), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).SendRequest(c.lifecycleCtx, uri, http.MethodPost, body, ErrFlashClientRequestFailed, distro.R().TiFlash)\n}\n"
  },
  {
    "path": "pkg/tiflash/tiflash.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tiflash\n\nimport (\n\t\"github.com/joomcode/errorx\"\n)\n\nvar ErrNS = errorx.NewNamespace(\"error.tiflash\")\n"
  },
  {
    "path": "pkg/tikv/client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tikv\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"go.etcd.io/etcd/client/pkg/v3/transport\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/httpc\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nvar ErrTiKVClientRequestFailed = ErrNS.NewType(\"client_request_failed\")\n\nconst (\n\tdefaultTiKVStatusAPITimeout = time.Second * 10\n)\n\ntype Client struct {\n\thttpClient   *httpc.Client\n\thttpScheme   string\n\tlifecycleCtx context.Context\n\ttimeout      time.Duration\n\ttlsInfo      *transport.TLSInfo\n}\n\nfunc NewTiKVClient(lc fx.Lifecycle, httpClient *httpc.Client, config *config.Config) *Client {\n\tclient := &Client{\n\t\thttpClient:   httpClient,\n\t\thttpScheme:   config.GetClusterHTTPScheme(),\n\t\tlifecycleCtx: nil,\n\t\ttimeout:      defaultTiKVStatusAPITimeout,\n\t\ttlsInfo:      config.ClusterTLSInfo,\n\t}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tclient.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn client\n}\n\nfunc (c Client) WithTimeout(timeout time.Duration) *Client {\n\tc.timeout = timeout\n\treturn &c\n}\n\nfunc (c Client) AddRequestHeader(key, value string) *Client {\n\tc.httpClient = c.httpClient.CloneAndAddRequestHeader(key, value)\n\treturn &c\n}\n\nfunc (c *Client) GetHTTPScheme() string {\n\treturn c.httpScheme\n}\n\nfunc (c *Client) GetTLSInfo() *transport.TLSInfo {\n\treturn c.tlsInfo\n}\n\nfunc (c *Client) Get(host string, statusPort int, relativeURI string) (*httpc.Response, error) {\n\turi := fmt.Sprintf(\"%s://%s%s\", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(statusPort)), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).Send(c.lifecycleCtx, uri, http.MethodGet, nil, ErrTiKVClientRequestFailed, distro.R().TiKV)\n}\n\nfunc (c *Client) SendGetRequest(host string, statusPort int, relativeURI string) ([]byte, error) {\n\tres, err := c.Get(host, statusPort, relativeURI)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn res.Body()\n}\n\nfunc (c *Client) SendPostRequest(host string, statusPort int, relativeURI string, body io.Reader) ([]byte, error) {\n\turi := fmt.Sprintf(\"%s://%s%s\", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(statusPort)), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).SendRequest(c.lifecycleCtx, uri, http.MethodPost, body, ErrTiKVClientRequestFailed, distro.R().TiKV)\n}\n"
  },
  {
    "path": "pkg/tikv/tikv.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tikv\n\nimport (\n\t\"github.com/joomcode/errorx\"\n)\n\nvar ErrNS = errorx.NewNamespace(\"error.tikv\")\n"
  },
  {
    "path": "pkg/tiproxy/client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tiproxy\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/httpc\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nvar ErrTiProxyClientRequestFailed = ErrNS.NewType(\"client_request_failed\")\n\nconst (\n\tdefaultTiProxyStatusAPITimeout = time.Second * 10\n)\n\ntype Client struct {\n\thttpClient   *httpc.Client\n\thttpScheme   string\n\tlifecycleCtx context.Context\n\ttimeout      time.Duration\n}\n\nfunc NewTiProxyClient(lc fx.Lifecycle, httpClient *httpc.Client, config *config.Config) *Client {\n\tclient := &Client{\n\t\thttpClient:   httpClient,\n\t\thttpScheme:   config.GetClusterHTTPScheme(),\n\t\tlifecycleCtx: nil,\n\t\ttimeout:      defaultTiProxyStatusAPITimeout,\n\t}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tclient.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn client\n}\n\nfunc (c Client) WithTimeout(timeout time.Duration) *Client {\n\tc.timeout = timeout\n\treturn &c\n}\n\nfunc (c Client) AddRequestHeader(key, value string) *Client {\n\tc.httpClient = c.httpClient.CloneAndAddRequestHeader(key, value)\n\treturn &c\n}\n\nfunc (c *Client) Get(host string, statusPort int, relativeURI string) (*httpc.Response, error) {\n\turi := fmt.Sprintf(\"%s://%s%s\", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(statusPort)), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).Send(c.lifecycleCtx, uri, http.MethodGet, nil, ErrTiProxyClientRequestFailed, distro.R().TiProxy)\n}\n\nfunc (c *Client) SendGetRequest(host string, statusPort int, relativeURI string) ([]byte, error) {\n\tres, err := c.Get(host, statusPort, relativeURI)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn res.Body()\n}\n\nfunc (c *Client) SendPostRequest(host string, statusPort int, relativeURI string, body io.Reader) ([]byte, error) {\n\turi := fmt.Sprintf(\"%s://%s%s\", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(statusPort)), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).SendRequest(c.lifecycleCtx, uri, http.MethodPost, body, ErrTiProxyClientRequestFailed, distro.R().TiProxy)\n}\n"
  },
  {
    "path": "pkg/tiproxy/tiproxy.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tiproxy\n\nimport (\n\t\"github.com/joomcode/errorx\"\n)\n\nvar ErrNS = errorx.NewNamespace(\"error.tiproxy\")\n"
  },
  {
    "path": "pkg/tso/client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tso\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/httpc\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nvar ErrTSOClientRequestFailed = ErrNS.NewType(\"client_request_failed\")\n\nconst (\n\tdefaultTSOStatusAPITimeout = time.Second * 10\n)\n\ntype Client struct {\n\thttpClient   *httpc.Client\n\thttpScheme   string\n\tlifecycleCtx context.Context\n\ttimeout      time.Duration\n}\n\nfunc NewTSOClient(lc fx.Lifecycle, httpClient *httpc.Client, config *config.Config) *Client {\n\tclient := &Client{\n\t\thttpClient:   httpClient,\n\t\thttpScheme:   config.GetClusterHTTPScheme(),\n\t\tlifecycleCtx: nil,\n\t\ttimeout:      defaultTSOStatusAPITimeout,\n\t}\n\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tclient.lifecycleCtx = ctx\n\t\t\treturn nil\n\t\t},\n\t})\n\n\treturn client\n}\n\nfunc (c Client) WithTimeout(timeout time.Duration) *Client {\n\tc.timeout = timeout\n\treturn &c\n}\n\nfunc (c Client) AddRequestHeader(key, value string) *Client {\n\tc.httpClient = c.httpClient.CloneAndAddRequestHeader(key, value)\n\treturn &c\n}\n\nfunc (c *Client) Get(host string, port int, relativeURI string) (*httpc.Response, error) {\n\turi := fmt.Sprintf(\"%s://%s%s\", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(port)), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).Send(c.lifecycleCtx, uri, http.MethodGet, nil, ErrTSOClientRequestFailed, distro.R().TSO)\n}\n\nfunc (c *Client) SendGetRequest(host string, port int, relativeURI string) ([]byte, error) {\n\tres, err := c.Get(host, port, relativeURI)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn res.Body()\n}\n\nfunc (c *Client) SendPostRequest(host string, port int, relativeURI string, body io.Reader) ([]byte, error) {\n\turi := fmt.Sprintf(\"%s://%s%s\", c.httpScheme, net.JoinHostPort(host, strconv.Itoa(port)), relativeURI)\n\treturn c.httpClient.WithTimeout(c.timeout).SendRequest(c.lifecycleCtx, uri, http.MethodPost, body, ErrTSOClientRequestFailed, distro.R().TSO)\n}\n"
  },
  {
    "path": "pkg/tso/tso.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tso\n\nimport (\n\t\"github.com/joomcode/errorx\"\n)\n\nvar ErrNS = errorx.NewNamespace(\"error.tso\")\n"
  },
  {
    "path": "pkg/uiserver/.gitignore",
    "content": "/embedded_assets_handler.go\n"
  },
  {
    "path": "pkg/uiserver/embedded_assets_rewriter.go",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\n//go:build ui_server\n\npackage uiserver\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"sync\"\n\t\"time\"\n\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/log\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n)\n\nvar once sync.Once\n\nfunc Assets(cfg *config.Config) http.FileSystem {\n\tonce.Do(func() {\n\t\texePath, err := os.Executable()\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"Failed to get executable path\", zap.Error(err))\n\t\t}\n\n\t\tdistroResFolderPath := path.Join(path.Dir(exePath), distroResFolderName)\n\t\tRewriteAssets(assets, cfg, distroResFolderPath, func(fs http.FileSystem, f http.File, path, newContent string, bs []byte) {\n\t\t\tm := fs.(vfsgen۰FS)\n\t\t\tfi := f.(os.FileInfo)\n\t\t\tm[path] = &vfsgen۰CompressedFileInfo{\n\t\t\t\tname:              fi.Name(),\n\t\t\t\tmodTime:           time.Now(),\n\t\t\t\tuncompressedSize:  int64(len(newContent)),\n\t\t\t\tcompressedContent: bs,\n\t\t\t}\n\t\t})\n\t})\n\treturn assets\n}\n"
  },
  {
    "path": "pkg/uiserver/empty_assets_handler.go",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\n//go:build !ui_server\n\npackage uiserver\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n)\n\nfunc Assets(*config.Config) http.FileSystem {\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/uiserver/uiserver.go",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage uiserver\n\nimport (\n\t\"bytes\"\n\t\"compress/gzip\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"html\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pingcap/log\"\n\t\"github.com/shurcooL/httpgzip\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nconst (\n\tdistroResFolderName = \"distro-res\"\n)\n\ntype UpdateContentFunc func(fs http.FileSystem, oldFile http.File, path, newContent string, zippedBytes []byte)\n\nfunc RewriteAssets(fs http.FileSystem, cfg *config.Config, distroResFolderPath string, updater UpdateContentFunc) {\n\tif fs == nil {\n\t\treturn\n\t}\n\n\trewrite := func(assetPath string) {\n\t\tf, err := fs.Open(assetPath)\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"Asset not found\", zap.String(\"path\", assetPath), zap.Error(err))\n\t\t}\n\t\tdefer f.Close()\n\n\t\tbs, err := ioutil.ReadAll(f)\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"Failed to read asset\", zap.String(\"path\", assetPath), zap.Error(err))\n\t\t}\n\t\ttmplText := string(bs)\n\t\tupdated := strings.ReplaceAll(tmplText, \"__PUBLIC_PATH_PREFIX__\", html.EscapeString(cfg.PublicPathPrefix))\n\n\t\tdistroStrings, _ := json.Marshal(distro.R()) // this will never fail\n\t\tupdated = strings.ReplaceAll(updated, \"__DISTRO_STRINGS_RES__\", base64.StdEncoding.EncodeToString(distroStrings))\n\t\tupdated = strings.ReplaceAll(updated, \"__DISTRO_ASSETS_RES_TIMESTAMP__\", fmt.Sprintf(\"%d\", time.Now().Unix()))\n\n\t\tvar b bytes.Buffer\n\t\tw := gzip.NewWriter(&b)\n\t\tif _, err := w.Write([]byte(updated)); err != nil {\n\t\t\tlog.Fatal(\"Failed to zip asset\", zap.Error(err))\n\t\t}\n\t\tif err := w.Close(); err != nil {\n\t\t\tlog.Fatal(\"Failed to zip asset\", zap.Error(err))\n\t\t}\n\n\t\tupdater(fs, f, assetPath, updated, b.Bytes())\n\t}\n\n\trewrite(\"/index.html\")\n\trewrite(\"/diagnoseReport.html\")\n\n\tif err := overrideDistroAssetsRes(fs, distroResFolderPath, updater); err != nil {\n\t\tlog.Fatal(\"Failed to load distro assets res\", zap.Error(err))\n\t}\n}\n\nfunc overrideDistroAssetsRes(fs http.FileSystem, distroResFolderPath string, updater UpdateContentFunc) error {\n\tinfo, err := os.Stat(distroResFolderPath)\n\tif errors.Is(err, os.ErrNotExist) || !info.IsDir() {\n\t\t// just ignore if the folder doesn't exist or it's not a folder\n\t\treturn nil\n\t}\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// traverse\n\tfiles, err := ioutil.ReadDir(distroResFolderPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfor _, file := range files {\n\t\tif err := overrideSingleDistroAsset(fs, distroResFolderPath, file.Name(), updater); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc overrideSingleDistroAsset(fs http.FileSystem, distroResFolderPath, assetName string, updater UpdateContentFunc) error {\n\tassetPath := path.Join(\"/\", distroResFolderName, assetName)\n\ttargetFile, err := fs.Open(assetPath)\n\tif err != nil {\n\t\t// has no target asset to be overried, skip\n\t\treturn nil\n\t}\n\tdefer targetFile.Close()\n\n\tassetFullPath := path.Join(distroResFolderPath, assetName)\n\tsourceFile, err := os.Open(assetFullPath)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer sourceFile.Close()\n\n\tdata, err := ioutil.ReadAll(sourceFile)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar b bytes.Buffer\n\tw := gzip.NewWriter(&b)\n\tif _, err := w.Write(data); err != nil {\n\t\treturn err\n\t}\n\tif err := w.Close(); err != nil {\n\t\treturn err\n\t}\n\n\tupdater(fs, targetFile, assetPath, string(data), b.Bytes())\n\treturn nil\n}\n\nfunc Handler(root http.FileSystem) http.Handler {\n\tif root != nil {\n\t\treturn httpgzip.FileServer(root, httpgzip.FileServerOptions{IndexHTML: true})\n\t}\n\n\treturn http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\t_, _ = io.WriteString(w, \"Dashboard UI is not built. Use `UI=1 make`.\\n\")\n\t})\n}\n"
  },
  {
    "path": "pkg/utils/fx.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/fx\"\n)\n\ntype FxPrinter func(string, ...interface{})\n\nfunc (p FxPrinter) Printf(format string, args ...interface{}) {\n\tp(format, args...)\n}\n\nfunc NewFxPrinter() fx.Printer {\n\treturn FxPrinter(log.S().Debugf)\n}\n"
  },
  {
    "path": "pkg/utils/grpc.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"time\"\n\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/backoff\"\n\t\"google.golang.org/grpc/keepalive\"\n)\n\nvar (\n\tDefaultGRPCConnectParams = grpc.ConnectParams{\n\t\tBackoff: backoff.Config{\n\t\t\tBaseDelay:  100 * time.Millisecond, // Default was 1 second\n\t\t\tMultiplier: 1.6,                    // Default\n\t\t\tJitter:     0.2,                    // Default\n\t\t\tMaxDelay:   3 * time.Second,        // Default was 120 seconds\n\t\t},\n\t\tMinConnectTimeout: 5 * time.Second, // Default was 20 seconds\n\t}\n\tDefaultGRPCKeepaliveParams = keepalive.ClientParameters{\n\t\tTime:                10 * time.Second,\n\t\tTimeout:             3 * time.Second,\n\t\tPermitWithoutStream: false,\n\t}\n)\n\nvar DefaultGRPCDialOptions = []grpc.DialOption{\n\tgrpc.WithConnectParams(DefaultGRPCConnectParams),\n}\n"
  },
  {
    "path": "pkg/utils/service_status.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"sync/atomic\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"go.uber.org/fx\"\n)\n\ntype ServiceStatus int32\n\nfunc NewServiceStatus() *ServiceStatus {\n\treturn new(ServiceStatus)\n}\n\nfunc (s *ServiceStatus) IsRunning() bool {\n\treturn atomic.LoadInt32((*int32)(s)) != 0\n}\n\nfunc (s *ServiceStatus) Start() {\n\tatomic.StoreInt32((*int32)(s), 1)\n}\n\nfunc (s *ServiceStatus) Stop() {\n\tatomic.StoreInt32((*int32)(s), 0)\n}\n\nfunc (s *ServiceStatus) Register(lc fx.Lifecycle) {\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():\n\t\t\t\treturn ctx.Err()\n\t\t\tdefault:\n\t\t\t\ts.Start()\n\t\t\t\treturn nil\n\t\t\t}\n\t\t},\n\t\tOnStop: func(context.Context) error {\n\t\t\ts.Stop()\n\t\t\treturn nil\n\t\t},\n\t})\n}\n\nfunc (s *ServiceStatus) MWHandleStopped(stoppedHandler gin.HandlerFunc) gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tif !s.IsRunning() {\n\t\t\tstoppedHandler(c)\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\t\tc.Next()\n\t}\n}\n\nfunc (s *ServiceStatus) NewStatusAwareHandler(handler http.Handler, stoppedHandler http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tif !s.IsRunning() {\n\t\t\tstoppedHandler.ServeHTTP(w, r)\n\t\t\treturn\n\t\t}\n\t\thandler.ServeHTTP(w, r)\n\t})\n}\n"
  },
  {
    "path": "pkg/utils/sys_schema.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage utils\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/ReneKroon/ttlcache/v2\"\n\t\"go.uber.org/fx\"\n\t\"gorm.io/gorm\"\n)\n\nconst (\n\tcacheTTL = 1 * time.Minute\n)\n\ntype SysSchema struct {\n\tcache *ttlcache.Cache\n}\n\nfunc ProvideSysSchema(lc fx.Lifecycle) *SysSchema {\n\ts := NewSysSchema()\n\n\tlc.Append(fx.Hook{\n\t\tOnStop: func(_ context.Context) error {\n\t\t\treturn s.Close()\n\t\t},\n\t})\n\n\treturn s\n}\n\nfunc NewSysSchema() *SysSchema {\n\tc := ttlcache.NewCache()\n\tc.SkipTTLExtensionOnHit(true)\n\treturn &SysSchema{\n\t\tcache: c,\n\t}\n}\n\nfunc (c *SysSchema) Close() error {\n\treturn c.cache.Close()\n}\n\nfunc (c *SysSchema) GetTableColumnNames(db *gorm.DB, tableName string) ([]string, error) {\n\tcnsCache, _ := c.cache.Get(tableName)\n\tif cnsCache != nil {\n\t\treturn cnsCache.([]string), nil\n\t}\n\n\tcns := []string{}\n\tcs, err := fetchTableSchema(db, tableName)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tfor _, c := range cs {\n\t\tcns = append(cns, c.Field)\n\t}\n\n\terr = c.cache.SetWithTTL(tableName, cns, cacheTTL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cns, nil\n}\n\ntype columnInfo struct {\n\tField string `gorm:\"column:Field\" json:\"field\"`\n}\n\nfunc fetchTableSchema(db *gorm.DB, table string) ([]columnInfo, error) {\n\tvar cs []columnInfo\n\terr := db.Raw(fmt.Sprintf(\"DESC %s\", table)).Scan(&cs).Error\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn cs, nil\n}\n"
  },
  {
    "path": "pkg/utils/topology/models.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topology\n\ntype ComponentStatus uint\n\nconst (\n\tComponentStatusUnreachable ComponentStatus = 0\n\tComponentStatusUp          ComponentStatus = 1\n\tComponentStatusTombstone   ComponentStatus = 2\n\tComponentStatusOffline     ComponentStatus = 3\n\tComponentStatusDown        ComponentStatus = 4\n)\n\ntype PDInfo struct {\n\tGitHash        string          `json:\"git_hash\"`\n\tVersion        string          `json:\"version\"`\n\tIP             string          `json:\"ip\"`\n\tPort           uint            `json:\"port\"`\n\tDeployPath     string          `json:\"deploy_path\"`\n\tStatus         ComponentStatus `json:\"status\"`\n\tStartTimestamp int64           `json:\"start_timestamp\"` // Ts = 0 means unknown\n}\n\ntype TiDBInfo struct {\n\tGitHash        string          `json:\"git_hash\"`\n\tVersion        string          `json:\"version\"`\n\tIP             string          `json:\"ip\"`\n\tPort           uint            `json:\"port\"`\n\tDeployPath     string          `json:\"deploy_path\"`\n\tStatus         ComponentStatus `json:\"status\"`\n\tStatusPort     uint            `json:\"status_port\"`\n\tStartTimestamp int64           `json:\"start_timestamp\"`\n}\n\ntype TiCDCInfo struct {\n\tClusterName    string          `json:\"cluster_name\"`\n\tGitHash        string          `json:\"git_hash\"`\n\tVersion        string          `json:\"version\"`\n\tIP             string          `json:\"ip\"`\n\tPort           uint            `json:\"port\"`\n\tDeployPath     string          `json:\"deploy_path\"`\n\tStatus         ComponentStatus `json:\"status\"`\n\tStatusPort     uint            `json:\"status_port\"`\n\tStartTimestamp int64           `json:\"start_timestamp\"`\n}\n\ntype TiProxyInfo struct {\n\tGitHash        string          `json:\"git_hash\"`\n\tVersion        string          `json:\"version\"`\n\tIP             string          `json:\"ip\"`\n\tPort           uint            `json:\"port\"`\n\tDeployPath     string          `json:\"deploy_path\"`\n\tStatus         ComponentStatus `json:\"status\"`\n\tStatusPort     uint            `json:\"status_port\"`\n\tStartTimestamp int64           `json:\"start_timestamp\"`\n}\n\ntype TSOInfo struct {\n\tGitHash        string          `json:\"git_hash\"`\n\tVersion        string          `json:\"version\"`\n\tIP             string          `json:\"ip\"`\n\tPort           uint            `json:\"port\"`\n\tDeployPath     string          `json:\"deploy_path\"`\n\tStatus         ComponentStatus `json:\"status\"`\n\tStartTimestamp int64           `json:\"start_timestamp\"`\n}\n\ntype SchedulingInfo struct {\n\tGitHash        string          `json:\"git_hash\"`\n\tVersion        string          `json:\"version\"`\n\tIP             string          `json:\"ip\"`\n\tPort           uint            `json:\"port\"`\n\tDeployPath     string          `json:\"deploy_path\"`\n\tStatus         ComponentStatus `json:\"status\"`\n\tStartTimestamp int64           `json:\"start_timestamp\"`\n}\n\n// Store may be a TiKV store or TiFlash store.\ntype StoreInfo struct {\n\tGitHash        string            `json:\"git_hash\"`\n\tVersion        string            `json:\"version\"`\n\tIP             string            `json:\"ip\"`\n\tPort           uint              `json:\"port\"`\n\tDeployPath     string            `json:\"deploy_path\"`\n\tStatus         ComponentStatus   `json:\"status\"`\n\tStatusPort     uint              `json:\"status_port\"`\n\tLabels         map[string]string `json:\"labels\"`\n\tStartTimestamp int64             `json:\"start_timestamp\"`\n}\n\ntype StoreLabels struct {\n\tAddress string            `json:\"address\"`\n\tLabels  map[string]string `json:\"labels\"`\n}\n\ntype StoreLocation struct {\n\tLocationLabels []string      `json:\"location_labels\"`\n\tStores         []StoreLabels `json:\"stores\"`\n}\n\ntype StandardComponentInfo struct {\n\tIP   string `json:\"ip\"`\n\tPort uint   `json:\"port\"`\n}\n\ntype AlertManagerInfo struct {\n\tStandardComponentInfo\n}\n\ntype GrafanaInfo struct {\n\tStandardComponentInfo\n}\n\ntype PrometheusInfo struct {\n\tStandardComponentInfo\n}\n"
  },
  {
    "path": "pkg/utils/topology/monitor.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topology\n\nimport (\n\t\"context\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nfunc FetchAlertManagerTopology(ctx context.Context, etcdClient *clientv3.Client) (*AlertManagerInfo, error) {\n\ti, err := fetchStandardComponentTopology(ctx, \"alertmanager\", etcdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif i == nil {\n\t\treturn nil, nil\n\t}\n\treturn &AlertManagerInfo{StandardComponentInfo: *i}, nil\n}\n\nfunc FetchGrafanaTopology(ctx context.Context, etcdClient *clientv3.Client) (*GrafanaInfo, error) {\n\ti, err := fetchStandardComponentTopology(ctx, \"grafana\", etcdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif i == nil {\n\t\treturn nil, nil\n\t}\n\treturn &GrafanaInfo{StandardComponentInfo: *i}, nil\n}\n\nfunc FetchPrometheusTopology(ctx context.Context, etcdClient *clientv3.Client) (*PrometheusInfo, error) {\n\ti, err := fetchStandardComponentTopology(ctx, \"prometheus\", etcdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif i == nil {\n\t\treturn nil, nil\n\t}\n\treturn &PrometheusInfo{StandardComponentInfo: *i}, nil\n}\n\nconst ngMonitoringKeyPrefix = \"/topology/ng-monitoring/\"\n\nfunc FetchNgMonitoringTopology(ctx context.Context, etcdClient *clientv3.Client) (string, error) {\n\tctx2, cancel := context.WithTimeout(ctx, defaultFetchTimeout)\n\tdefer cancel()\n\n\tresp, err := etcdClient.Get(ctx2, ngMonitoringKeyPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn \"\", ErrEtcdRequestFailed.Wrap(err, \"failed to get key %s from %s etcd\", ngMonitoringKeyPrefix, distro.R().PD)\n\t}\n\n\tfor _, kv := range resp.Kvs {\n\t\tkey := string(kv.Key)\n\t\tif !strings.HasPrefix(key, ngMonitoringKeyPrefix) {\n\t\t\tcontinue\n\t\t}\n\t\t// RemainingKey looks like `ip:port/info` or `ip:port/ttl`.\n\t\t// Currently it only has `ip:port/ttl`.\n\t\tremainingKey := key[len(ngMonitoringKeyPrefix):]\n\t\tkeyParts := strings.Split(remainingKey, \"/\")\n\t\tif len(keyParts) != 2 {\n\t\t\tlog.Warn(\"Ignored invalid topology key\", zap.String(\"component\", \"ng-monitoring\"), zap.String(\"key\", key))\n\t\t\tcontinue\n\t\t}\n\t\tif keyParts[1] == \"ttl\" {\n\t\t\t_, err := parseNgMontioringAliveness(kv.Value)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warn(\"Ignored invalid NgMonitoring topology TTL entry\",\n\t\t\t\t\tzap.String(\"key\", key),\n\t\t\t\t\tzap.String(\"value\", string(kv.Value)),\n\t\t\t\t\tzap.Error(err))\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\t// Currently ttl value is not refreshed periodically in the NgMonitoring side\n\t\t\t// if !alive {\n\t\t\t// \tlog.Warn(\"Alive of NgMonitoring has expired, maybe local time in different hosts are not synchronized\",\n\t\t\t// \t\tzap.String(\"key\", key),\n\t\t\t// \t\tzap.String(\"value\", string(kv.Value)))\n\t\t\t// \treturn \"\", ErrInstanceNotAlive.NewWithNoMessage()\n\t\t\t// }\n\t\t\treturn keyParts[0], nil\n\t\t}\n\t}\n\treturn \"\", nil\n}\n\nfunc parseNgMontioringAliveness(value []byte) (bool, error) {\n\tunixTimestampNano, err := strconv.ParseUint(string(value), 10, 64)\n\tif err != nil {\n\t\treturn false, ErrInvalidTopologyData.Wrap(err, \"NgMonitoring TTL info parse failed\")\n\t}\n\tt := time.Unix(0, int64(unixTimestampNano))\n\tif time.Since(t) > time.Second*90 {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "pkg/utils/topology/pd.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topology\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n\t\"github.com/pingcap/tidb-dashboard/util/netutil\"\n)\n\nfunc FetchPDTopology(pdClient *pd.Client) ([]PDInfo, error) {\n\tnodes := make([]PDInfo, 0)\n\thealthMap, err := fetchPDHealth(pdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tdata, err := pdClient.SendGetRequest(\"/members\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tds := struct {\n\t\tCount   int `json:\"count\"`\n\t\tMembers []struct {\n\t\t\tGitHash       string   `json:\"git_hash\"`\n\t\t\tClientUrls    []string `json:\"client_urls\"`\n\t\t\tDeployPath    string   `json:\"deploy_path\"`\n\t\t\tBinaryVersion string   `json:\"binary_version\"`\n\t\t\tMemberID      uint64   `json:\"member_id\"`\n\t\t} `json:\"members\"`\n\t}{}\n\n\terr = json.Unmarshal(data, &ds)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"%s members API unmarshal failed\", distro.R().PD)\n\t}\n\n\tfor _, ds := range ds.Members {\n\t\tu := ds.ClientUrls[0]\n\t\thostname, port, err := netutil.ParseHostAndPortFromAddressURL(u)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tts, err := fetchPDStartTimestamp(pdClient)\n\t\tif err != nil {\n\t\t\tlog.Warn(fmt.Sprintf(\"Failed to fetch %s start timestamp\", distro.R().PD), zap.String(\"targetPdNode\", u), zap.Error(err))\n\t\t\tts = 0\n\t\t}\n\n\t\tvar storeStatus ComponentStatus\n\t\tif _, ok := healthMap[ds.MemberID]; ok {\n\t\t\tstoreStatus = ComponentStatusUp\n\t\t} else {\n\t\t\tstoreStatus = ComponentStatusUnreachable\n\t\t}\n\n\t\tnodes = append(nodes, PDInfo{\n\t\t\tGitHash:        ds.GitHash,\n\t\t\tVersion:        ds.BinaryVersion,\n\t\t\tIP:             hostname,\n\t\t\tPort:           port,\n\t\t\tDeployPath:     ds.DeployPath,\n\t\t\tStatus:         storeStatus,\n\t\t\tStartTimestamp: ts,\n\t\t})\n\t}\n\n\tsort.Slice(nodes, func(i, j int) bool {\n\t\tif nodes[i].IP < nodes[j].IP {\n\t\t\treturn true\n\t\t}\n\t\tif nodes[i].IP > nodes[j].IP {\n\t\t\treturn false\n\t\t}\n\t\treturn nodes[i].Port < nodes[j].Port\n\t})\n\n\treturn nodes, nil\n}\n\nfunc fetchPDStartTimestamp(pdClient *pd.Client) (int64, error) {\n\tdata, err := pdClient.SendGetRequest(\"/status\")\n\tif err != nil {\n\t\treturn 0, err\n\t}\n\n\tds := struct {\n\t\tStartTimestamp int64 `json:\"start_timestamp\"`\n\t}{}\n\terr = json.Unmarshal(data, &ds)\n\tif err != nil {\n\t\treturn 0, ErrInvalidTopologyData.Wrap(err, \"%s status API unmarshal failed\", distro.R().PD)\n\t}\n\n\treturn ds.StartTimestamp, nil\n}\n\nfunc fetchPDHealth(pdClient *pd.Client) (map[uint64]struct{}, error) {\n\tdata, err := pdClient.SendGetRequest(\"/health\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar healths []struct {\n\t\tMemberID uint64 `json:\"member_id\"`\n\t\tHealth   bool   `json:\"health\"`\n\t}\n\n\terr = json.Unmarshal(data, &healths)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"%s health API unmarshal failed\", distro.R().PD)\n\t}\n\n\tmemberHealth := map[uint64]struct{}{}\n\tfor _, v := range healths {\n\t\tif v.Health {\n\t\t\tmemberHealth[v.MemberID] = struct{}{}\n\t\t}\n\t}\n\treturn memberHealth, nil\n}\n\nfunc fetchLocationLabels(pdClient *pd.Client) ([]string, error) {\n\tdata, err := pdClient.SendGetRequest(\"/config/replicate\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar replicateConfig struct {\n\t\tLocationLabels string `json:\"location-labels\"`\n\t}\n\terr = json.Unmarshal(data, &replicateConfig)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"%s config/replicate API unmarshal failed\", distro.R().PD)\n\t}\n\tlabels := strings.Split(replicateConfig.LocationLabels, \",\")\n\treturn labels, nil\n}\n"
  },
  {
    "path": "pkg/utils/topology/scheduling.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topology\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"sort\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n\t\"github.com/pingcap/tidb-dashboard/util/netutil\"\n)\n\nfunc FetchSchedulingTopology(_ context.Context, pdClient *pd.Client) ([]SchedulingInfo, error) {\n\tnodes := make([]SchedulingInfo, 0)\n\tdata, err := pdClient.WithoutPrefix().SendGetRequest(\"/pd/api/v2/ms/members/scheduling\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tds := []struct {\n\t\tServiceAddr    string `json:\"service-addr\"`\n\t\tVersion        string `json:\"version\"`\n\t\tGitHash        string `json:\"git-hash\"`\n\t\tDeployPath     string `json:\"deploy-path\"`\n\t\tStartTimestamp int64  `json:\"start-timestamp\"`\n\t}{}\n\n\terr = json.Unmarshal(data, &ds)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"%s members API unmarshal failed\", distro.R().Scheduling)\n\t}\n\n\tfor _, ds := range ds {\n\t\tu := ds.ServiceAddr\n\t\thostname, port, err := netutil.ParseHostAndPortFromAddressURL(u)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tnodes = append(nodes, SchedulingInfo{\n\t\t\tGitHash:        ds.GitHash,\n\t\t\tVersion:        ds.Version,\n\t\t\tIP:             hostname,\n\t\t\tPort:           port,\n\t\t\tDeployPath:     ds.DeployPath,\n\t\t\tStatus:         ComponentStatusUp,\n\t\t\tStartTimestamp: ds.StartTimestamp,\n\t\t})\n\t}\n\n\tsort.Slice(nodes, func(i, j int) bool {\n\t\tif nodes[i].IP < nodes[j].IP {\n\t\t\treturn true\n\t\t}\n\t\tif nodes[i].IP > nodes[j].IP {\n\t\t\treturn false\n\t\t}\n\t\treturn nodes[i].Port < nodes[j].Port\n\t})\n\n\treturn nodes, nil\n}\n"
  },
  {
    "path": "pkg/utils/topology/store.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topology\n\nimport (\n\t\"encoding/json\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n\t\"github.com/pingcap/tidb-dashboard/util/netutil\"\n)\n\n// FetchStoreTopology returns TiKV info and TiFlash info.\nfunc FetchStoreTopology(pdClient *pd.Client) ([]StoreInfo, []StoreInfo, error) {\n\tstores, err := fetchStores(pdClient)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\ttiKVStores := make([]store, 0, len(stores))\n\ttiFlashStores := make([]store, 0, len(stores))\n\tfor _, store := range stores {\n\t\tisTiFlash := false\n\t\tfor _, label := range store.Labels {\n\t\t\tif label.Key == \"engine\" && (label.Value == \"tiflash\" || label.Value == \"tiflash_compute\") {\n\t\t\t\tisTiFlash = true\n\t\t\t}\n\t\t}\n\t\tif isTiFlash {\n\t\t\ttiFlashStores = append(tiFlashStores, store)\n\t\t} else {\n\t\t\ttiKVStores = append(tiKVStores, store)\n\t\t}\n\t}\n\n\treturn buildStoreTopology(tiKVStores), buildStoreTopology(tiFlashStores), nil\n}\n\nfunc FetchStoreLocation(pdClient *pd.Client) (*StoreLocation, error) {\n\tlocationLabels, err := fetchLocationLabels(pdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstores, err := fetchStores(pdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnodes := make([]StoreLabels, 0, len(stores))\n\tfor _, s := range stores {\n\t\tnode := StoreLabels{\n\t\t\tAddress: s.Address,\n\t\t\tLabels:  map[string]string{},\n\t\t}\n\t\tfor _, l := range s.Labels {\n\t\t\tnode.Labels[l.Key] = l.Value\n\t\t}\n\t\tnodes = append(nodes, node)\n\t}\n\n\tstoreLocation := StoreLocation{\n\t\tLocationLabels: locationLabels,\n\t\tStores:         nodes,\n\t}\n\n\treturn &storeLocation, nil\n}\n\nfunc buildStoreTopology(stores []store) []StoreInfo {\n\tnodes := make([]StoreInfo, 0, len(stores))\n\tfor _, v := range stores {\n\t\thostname, port, err := netutil.ParseHostAndPortFromAddress(v.Address)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"Failed to parse store address\", zap.Any(\"store\", v))\n\t\t\tcontinue\n\t\t}\n\t\t_, statusPort, err := netutil.ParseHostAndPortFromAddress(v.StatusAddress)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"Failed to parse store status address\", zap.Any(\"store\", v))\n\t\t\tcontinue\n\t\t}\n\t\t// In current TiKV, it's version may not start with 'v',\n\t\t// so we may need to add a prefix 'v' for it.\n\t\tversion := strings.Trim(v.Version, \"\\n \")\n\t\tif !strings.HasPrefix(version, \"v\") {\n\t\t\tversion = \"v\" + version\n\t\t}\n\t\tnode := StoreInfo{\n\t\t\tVersion:        version,\n\t\t\tIP:             hostname,\n\t\t\tPort:           port,\n\t\t\tGitHash:        v.GitHash,\n\t\t\tDeployPath:     v.DeployPath,\n\t\t\tStatus:         parseStoreState(v.StateName),\n\t\t\tStatusPort:     statusPort,\n\t\t\tLabels:         map[string]string{},\n\t\t\tStartTimestamp: v.StartTimestamp,\n\t\t}\n\t\tfor _, v := range v.Labels {\n\t\t\tnode.Labels[v.Key] = v.Value\n\t\t}\n\t\tnodes = append(nodes, node)\n\t}\n\n\treturn nodes\n}\n\ntype store struct {\n\tAddress string `json:\"address\"`\n\tID      int    `json:\"id\"`\n\tLabels  []struct {\n\t\tKey   string `json:\"key\"`\n\t\tValue string `json:\"value\"`\n\t} `json:\"labels\"`\n\tStateName      string `json:\"state_name\"`\n\tVersion        string `json:\"version\"`\n\tStatusAddress  string `json:\"status_address\"`\n\tGitHash        string `json:\"git_hash\"`\n\tDeployPath     string `json:\"deploy_path\"`\n\tStartTimestamp int64  `json:\"start_timestamp\"`\n}\n\nfunc fetchStores(pdClient *pd.Client) ([]store, error) {\n\tdata, err := pdClient.SendGetRequest(\"/stores\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstoreResp := struct {\n\t\tCount  int `json:\"count\"`\n\t\tStores []struct {\n\t\t\tStore store\n\t\t} `json:\"stores\"`\n\t}{}\n\terr = json.Unmarshal(data, &storeResp)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"%s stores API unmarshal failed\", distro.R().PD)\n\t}\n\n\tret := make([]store, 0, storeResp.Count)\n\tfor _, s := range storeResp.Stores {\n\t\tret = append(ret, s.Store)\n\t}\n\n\tsort.Slice(ret, func(i, j int) bool {\n\t\treturn ret[i].Address < ret[j].Address\n\t})\n\n\treturn ret, nil\n}\n\nfunc parseStoreState(state string) ComponentStatus {\n\tstate = strings.Trim(strings.ToLower(state), \"\\n \")\n\tswitch state {\n\tcase \"up\":\n\t\treturn ComponentStatusUp\n\tcase \"tombstone\":\n\t\treturn ComponentStatusTombstone\n\tcase \"offline\":\n\t\treturn ComponentStatusOffline\n\tcase \"down\":\n\t\treturn ComponentStatusDown\n\tcase \"disconnected\":\n\t\treturn ComponentStatusUnreachable\n\tdefault:\n\t\treturn ComponentStatusUnreachable\n\t}\n}\n"
  },
  {
    "path": "pkg/utils/topology/ticdc.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topology\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n\t\"github.com/pingcap/tidb-dashboard/util/netutil\"\n)\n\n// TODO: refactor this with topology prefix since it is compatible with other components.\nconst (\n\tticdcTopologyKeyPrefix = \"/topology/ticdc/\"\n)\n\nfunc FetchTiCDCTopology(ctx context.Context, etcdClient *clientv3.Client) ([]TiCDCInfo, error) {\n\tctx2, cancel := context.WithTimeout(ctx, defaultFetchTimeout)\n\tdefer cancel()\n\n\tresp, err := etcdClient.Get(ctx2, ticdcTopologyKeyPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn nil, ErrEtcdRequestFailed.Wrap(err, \"failed to get key %s from %s etcd\", ticdcTopologyKeyPrefix, distro.R().PD)\n\t}\n\n\tnodes := make([]TiCDCInfo, 0)\n\tfor _, kv := range resp.Kvs {\n\t\tkey := string(kv.Key)\n\t\tif !strings.HasPrefix(key, ticdcTopologyKeyPrefix) {\n\t\t\tcontinue\n\t\t}\n\n\t\t// parse default\n\t\tkeys := strings.TrimPrefix(key, ticdcTopologyKeyPrefix)\n\t\tclusterName := strings.Split(keys, \"/\")[0]\n\n\t\tnodeInfo, err := parseTiCDCInfo(clusterName, kv.Value)\n\t\tif err != nil {\n\t\t\tlog.Warn(fmt.Sprintf(\"Ignored invalid %s topology info entry\", distro.R().TiCDC),\n\t\t\t\tzap.String(\"key\", key),\n\t\t\t\tzap.String(\"value\", string(kv.Value)),\n\t\t\t\tzap.Error(err))\n\t\t\tcontinue\n\t\t}\n\n\t\tnodes = append(nodes, *nodeInfo)\n\t}\n\n\tsort.Slice(nodes, func(i, j int) bool {\n\t\tif nodes[i].IP < nodes[j].IP {\n\t\t\treturn true\n\t\t}\n\t\tif nodes[i].IP > nodes[j].IP {\n\t\t\treturn false\n\t\t}\n\t\treturn nodes[i].Port < nodes[j].Port\n\t})\n\n\treturn nodes, nil\n}\n\nfunc parseTiCDCInfo(clusterName string, value []byte) (*TiCDCInfo, error) {\n\tds := struct {\n\t\tID             string `json:\"id\"`\n\t\tAddress        string `json:\"address\"`\n\t\tVersion        string `json:\"version\"`\n\t\tGitHash        string `json:\"git-hash\"`\n\t\tDeployPath     string `json:\"deploy-path\"`\n\t\tStartTimestamp int64  `json:\"start-timestamp\"`\n\t}{}\n\n\terr := json.Unmarshal(value, &ds)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"%s info unmarshal failed\", distro.R().TiCDC)\n\t}\n\thostname, port, err := netutil.ParseHostAndPortFromAddress(ds.Address)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"%s info address parse failed\", distro.R().TiCDC)\n\t}\n\n\treturn &TiCDCInfo{\n\t\tClusterName:    clusterName,\n\t\tGitHash:        ds.GitHash,\n\t\tVersion:        ds.Version,\n\t\tIP:             hostname,\n\t\tPort:           port,\n\t\tDeployPath:     ds.DeployPath,\n\t\tStatus:         ComponentStatusUp,\n\t\tStatusPort:     port,\n\t\tStartTimestamp: ds.StartTimestamp,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/utils/topology/tidb.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topology\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n\t\"github.com/pingcap/tidb-dashboard/util/netutil\"\n)\n\nconst (\n\ttidbTopologyKeyPrefix = \"/topology/tidb/\"\n\tkeyspaceNameKeyPrefix = \"/keyspaces/tidb\"\n)\n\nfunc getAliveNodesAndInfos(ctx context.Context, etcdClient *clientv3.Client, keyPrefix string) (map[string]struct{}, map[string]*TiDBInfo, error) {\n\tctx2, cancel := context.WithTimeout(ctx, defaultFetchTimeout)\n\tdefer cancel()\n\n\tresp, err := etcdClient.Get(ctx2, keyPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn nil, nil, ErrEtcdRequestFailed.Wrap(err, \"failed to get key %s from %s etcd\", keyPrefix, distro.R().PD)\n\t}\n\n\tnodesAlive := make(map[string]struct{}, len(resp.Kvs))\n\tnodesInfo := make(map[string]*TiDBInfo, len(resp.Kvs))\n\tfor _, kv := range resp.Kvs {\n\t\tkey := string(kv.Key)\n\t\tif !strings.HasPrefix(key, keyPrefix) {\n\t\t\tcontinue\n\t\t}\n\t\t// remainingKey looks like `ip:port/info` or `ip:port/ttl`.\n\t\tremainingKey := key[len(keyPrefix):]\n\t\tkeyParts := strings.Split(remainingKey, \"/\")\n\t\tif len(keyParts) != 2 {\n\t\t\tlog.Warn(\"Ignored invalid topology key\", zap.String(\"component\", distro.R().TiDB), zap.String(\"key\", key))\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch keyParts[1] {\n\t\tcase \"info\":\n\t\t\tnode, err := parseTiDBInfo(keyParts[0], kv.Value)\n\t\t\tif err == nil {\n\t\t\t\tnodesInfo[keyParts[0]] = node\n\t\t\t} else {\n\t\t\t\tlog.Warn(fmt.Sprintf(\"Ignored invalid %s topology info entry\", distro.R().TiDB),\n\t\t\t\t\tzap.String(\"key\", key),\n\t\t\t\t\tzap.String(\"value\", string(kv.Value)),\n\t\t\t\t\tzap.Error(err))\n\t\t\t}\n\t\tcase \"ttl\":\n\t\t\talive, err := parseTiDBAliveness(kv.Value)\n\t\t\tif err == nil {\n\t\t\t\tnodesAlive[keyParts[0]] = struct{}{}\n\t\t\t\tif !alive {\n\t\t\t\t\tlog.Warn(fmt.Sprintf(\"Alive of %s has expired, maybe local time in different hosts are not synchronized\", distro.R().TiDB),\n\t\t\t\t\t\tzap.String(\"key\", key),\n\t\t\t\t\t\tzap.String(\"value\", string(kv.Value)))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog.Warn(fmt.Sprintf(\"Ignored invalid %s topology TTL entry\", distro.R().TiDB),\n\t\t\t\t\tzap.String(\"key\", key),\n\t\t\t\t\tzap.String(\"value\", string(kv.Value)),\n\t\t\t\t\tzap.Error(err))\n\t\t\t}\n\t\t}\n\t}\n\n\treturn nodesAlive, nodesInfo, nil\n}\n\nfunc getAliveNodesAndInfoWithPrefix(ctx context.Context, etcdClient *clientv3.Client) (map[string]struct{}, map[string]*TiDBInfo, error) {\n\tchildCtx, cancel := context.WithTimeout(ctx, defaultFetchTimeout)\n\tdefer cancel()\n\n\tresp, err := etcdClient.Get(childCtx, keyspaceNameKeyPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn nil, nil, ErrEtcdRequestFailed.Wrap(err, \"failed to get key %s from %s etcd\", keyspaceNameKeyPrefix, distro.R().PD)\n\t}\n\n\tidMap := make(map[string]struct{})\n\tfor _, kv := range resp.Kvs {\n\t\t// layout: /keyspaces/tidb/<id>/topology/tidb/...\n\t\trest := strings.TrimPrefix(string(kv.Key), keyspaceNameKeyPrefix)\n\t\t// rest: <id>/topology/tidb/...\n\t\tparts := strings.SplitN(rest, \"/\", 3)\n\t\tif len(parts) >= 2 && strings.HasPrefix(parts[2], \"topology/tidb/\") {\n\t\t\tid := parts[1]\n\t\t\tidMap[id] = struct{}{}\n\t\t}\n\t}\n\n\tretryTimes := 3\n\tnodesAlive := make(map[string]struct{})\n\tnodesInfo := make(map[string]*TiDBInfo)\n\tfor id := range idMap {\n\t\tkeyPrefix := fmt.Sprintf(\"%s/%s/topology/tidb/\", keyspaceNameKeyPrefix, id)\n\n\t\tvar (\n\t\t\tnodesAlive0 map[string]struct{}\n\t\t\tnodesInfo0  map[string]*TiDBInfo\n\t\t\terr         error\n\t\t)\n\t\tfor i := 1; i <= retryTimes; i++ {\n\t\t\tnodesAlive0, nodesInfo0, err = getAliveNodesAndInfos(childCtx, etcdClient, keyPrefix)\n\t\t\tif err != nil {\n\t\t\t\tlog.Warn(\"Failed to get TiDB topology nodes\", zap.String(\"keyPrefix\", keyPrefix), zap.Int(\"retry\", i), zap.Error(err))\n\t\t\t\tif i == retryTimes {\n\t\t\t\t\treturn nil, nil, err\n\t\t\t\t}\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\n\t\tfor addr := range nodesAlive0 {\n\t\t\tnodesAlive[addr] = struct{}{}\n\t\t}\n\t\tfor addr, info := range nodesInfo0 {\n\t\t\tif _, exists := nodesInfo[addr]; exists {\n\t\t\t\t// If the same address appears, we keep the first one.\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tnodesInfo[addr] = info\n\t\t}\n\t}\n\n\treturn nodesAlive, nodesInfo, nil\n}\n\nfunc FetchTiDBTopology(ctx context.Context, etcdClient *clientv3.Client) ([]TiDBInfo, error) {\n\tnodesAlive, nodesInfo, err := getAliveNodesAndInfos(ctx, etcdClient, tidbTopologyKeyPrefix)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(nodesAlive) == 0 {\n\t\tnodesAlive, nodesInfo, err = getAliveNodesAndInfoWithPrefix(ctx, etcdClient)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\n\tnodes := make([]TiDBInfo, 0)\n\tfor addr, info := range nodesInfo {\n\t\tif _, ok := nodesAlive[addr]; ok {\n\t\t\tinfo.Status = ComponentStatusUp\n\t\t}\n\t\tnodes = append(nodes, *info)\n\t}\n\n\tsort.Slice(nodes, func(i, j int) bool {\n\t\tif nodes[i].IP < nodes[j].IP {\n\t\t\treturn true\n\t\t}\n\t\tif nodes[i].IP > nodes[j].IP {\n\t\t\treturn false\n\t\t}\n\t\treturn nodes[i].Port < nodes[j].Port\n\t})\n\n\treturn nodes, nil\n}\n\nfunc parseTiDBInfo(address string, value []byte) (*TiDBInfo, error) {\n\tds := struct {\n\t\tVersion        string `json:\"version\"`\n\t\tGitHash        string `json:\"git_hash\"`\n\t\tStatusPort     uint   `json:\"status_port\"`\n\t\tDeployPath     string `json:\"deploy_path\"`\n\t\tStartTimestamp int64  `json:\"start_timestamp\"`\n\t}{}\n\n\terr := json.Unmarshal(value, &ds)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"%s info unmarshal failed\", distro.R().TiDB)\n\t}\n\thostname, port, err := netutil.ParseHostAndPortFromAddress(address)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"%s info address parse failed\", distro.R().TiDB)\n\t}\n\n\treturn &TiDBInfo{\n\t\tGitHash:        ds.GitHash,\n\t\tVersion:        ds.Version,\n\t\tIP:             hostname,\n\t\tPort:           port,\n\t\tDeployPath:     ds.DeployPath,\n\t\tStatus:         ComponentStatusUnreachable,\n\t\tStatusPort:     ds.StatusPort,\n\t\tStartTimestamp: ds.StartTimestamp,\n\t}, nil\n}\n\nfunc parseTiDBAliveness(value []byte) (bool, error) {\n\tunixTimestampNano, err := strconv.ParseUint(string(value), 10, 64)\n\tif err != nil {\n\t\treturn false, ErrInvalidTopologyData.Wrap(err, \"%s TTL info parse failed\", distro.R().TiDB)\n\t}\n\tt := time.Unix(0, int64(unixTimestampNano))\n\tif time.Since(t) > time.Second*45 {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "pkg/utils/topology/tiproxy.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topology\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nconst tiproxyTopologyKeyPrefix = \"/topology/tiproxy/\"\n\nfunc FetchTiProxyTopology(ctx context.Context, etcdClient *clientv3.Client) ([]TiProxyInfo, error) {\n\tctx2, cancel := context.WithTimeout(ctx, defaultFetchTimeout)\n\tdefer cancel()\n\n\tresp, err := etcdClient.Get(ctx2, tiproxyTopologyKeyPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn nil, ErrEtcdRequestFailed.Wrap(err, \"failed to get key %s from %s etcd\", tiproxyTopologyKeyPrefix, distro.R().PD)\n\t}\n\n\tnodesAlive := make(map[string]struct{}, len(resp.Kvs))\n\tnodesInfo := make(map[string]*TiProxyInfo, len(resp.Kvs))\n\n\tfor _, kv := range resp.Kvs {\n\t\tkey := string(kv.Key)\n\t\tif !strings.HasPrefix(key, tiproxyTopologyKeyPrefix) {\n\t\t\tcontinue\n\t\t}\n\t\t// remainingKey looks like `ip:port/info` or `ip:port/ttl`.\n\t\tremainingKey := key[len(tiproxyTopologyKeyPrefix):]\n\t\tkeyParts := strings.Split(remainingKey, \"/\")\n\t\tif len(keyParts) != 2 {\n\t\t\tlog.Warn(\"Ignored invalid topology key\", zap.String(\"component\", distro.R().TiProxy), zap.String(\"key\", key))\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch keyParts[1] {\n\t\tcase \"info\":\n\t\t\tnode, err := parseTiProxyInfo(keyParts[0], kv.Value)\n\t\t\tif err == nil {\n\t\t\t\tnodesInfo[keyParts[0]] = node\n\t\t\t} else {\n\t\t\t\tlog.Warn(fmt.Sprintf(\"Ignored invalid %s topology info entry\", distro.R().TiProxy),\n\t\t\t\t\tzap.String(\"key\", key),\n\t\t\t\t\tzap.String(\"value\", string(kv.Value)),\n\t\t\t\t\tzap.Error(err))\n\t\t\t}\n\t\tcase \"ttl\":\n\t\t\talive, err := parseTiDBAliveness(kv.Value)\n\t\t\tif err == nil {\n\t\t\t\tnodesAlive[keyParts[0]] = struct{}{}\n\t\t\t\tif !alive {\n\t\t\t\t\tlog.Warn(fmt.Sprintf(\"Alive of %s has expired, maybe local time in different hosts are not synchronized\", distro.R().TiProxy),\n\t\t\t\t\t\tzap.String(\"key\", key),\n\t\t\t\t\t\tzap.String(\"value\", string(kv.Value)))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog.Warn(fmt.Sprintf(\"Ignored invalid %s topology TTL entry\", distro.R().TiProxy),\n\t\t\t\t\tzap.String(\"key\", key),\n\t\t\t\t\tzap.String(\"value\", string(kv.Value)),\n\t\t\t\t\tzap.Error(err))\n\t\t\t}\n\t\t}\n\t}\n\n\tnodes := make([]TiProxyInfo, 0)\n\n\tfor addr, info := range nodesInfo {\n\t\tif _, ok := nodesAlive[addr]; ok {\n\t\t\tinfo.Status = ComponentStatusUp\n\t\t}\n\t\tnodes = append(nodes, *info)\n\t}\n\n\tsort.Slice(nodes, func(i, j int) bool {\n\t\tif nodes[i].IP < nodes[j].IP {\n\t\t\treturn true\n\t\t}\n\t\tif nodes[i].IP > nodes[j].IP {\n\t\t\treturn false\n\t\t}\n\t\treturn nodes[i].Port < nodes[j].Port\n\t})\n\n\treturn nodes, nil\n}\n\nfunc parseTiProxyInfo(_ string, value []byte) (*TiProxyInfo, error) {\n\tds := struct {\n\t\tGitHash        string `json:\"git_hash\"`\n\t\tVersion        string `json:\"version\"`\n\t\tIP             string `json:\"ip\"`\n\t\tPort           string `json:\"port\"`\n\t\tDeployPath     string `json:\"deploy_path\"`\n\t\tStatusPort     string `json:\"status_port\"`\n\t\tStartTimestamp int64  `json:\"start_timestamp\"`\n\t}{}\n\terr := json.Unmarshal(value, &ds)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"%s info unmarshal failed\", distro.R().TiProxy)\n\t}\n\tport, err := strconv.ParseUint(ds.Port, 10, 64)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"%s port parse failed\", distro.R().TiProxy)\n\t}\n\tstatusPort, err := strconv.ParseUint(ds.StatusPort, 10, 64)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"%s port parse failed\", distro.R().TiProxy)\n\t}\n\treturn &TiProxyInfo{\n\t\tGitHash:        ds.GitHash,\n\t\tVersion:        ds.Version,\n\t\tIP:             ds.IP,\n\t\tPort:           uint(port),\n\t\tDeployPath:     ds.DeployPath,\n\t\tStatus:         ComponentStatusUnreachable,\n\t\tStatusPort:     uint(statusPort),\n\t\tStartTimestamp: ds.StartTimestamp,\n\t}, nil\n}\n"
  },
  {
    "path": "pkg/utils/topology/topology.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topology\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"time\"\n\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nvar (\n\tErrNS                  = errorx.NewNamespace(\"error.topology\")\n\tErrEtcdRequestFailed   = ErrNS.NewType(\"pd_etcd_request_failed\")\n\tErrInvalidTopologyData = ErrNS.NewType(\"invalid_topology_data\")\n\tErrInstanceNotAlive    = ErrNS.NewType(\"instance_not_alive\")\n)\n\nconst defaultFetchTimeout = 2 * time.Second\n\nfunc fetchStandardComponentTopology(ctx context.Context, componentName string, etcdClient *clientv3.Client) (*StandardComponentInfo, error) {\n\tctx2, cancel := context.WithTimeout(ctx, defaultFetchTimeout)\n\tdefer cancel()\n\n\tkey := \"/topology/\" + componentName\n\tresp, err := etcdClient.Get(ctx2, key, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn nil, ErrEtcdRequestFailed.Wrap(err, \"failed to get key %s from %s etcd\", key, distro.R().PD)\n\t}\n\tif resp.Count == 0 {\n\t\treturn nil, nil\n\t}\n\tinfo := StandardComponentInfo{}\n\tkv := resp.Kvs[0]\n\tif err = json.Unmarshal(kv.Value, &info); err != nil {\n\t\tlog.Warn(\"Failed to unmarshal topology value\",\n\t\t\tzap.String(\"key\", string(kv.Key)),\n\t\t\tzap.String(\"value\", string(kv.Value)))\n\t\treturn nil, nil\n\t}\n\treturn &info, nil\n}\n"
  },
  {
    "path": "pkg/utils/topology/tso.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topology\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"sort\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/pd\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n\t\"github.com/pingcap/tidb-dashboard/util/netutil\"\n)\n\nfunc FetchTSOTopology(_ context.Context, pdClient *pd.Client) ([]TSOInfo, error) {\n\tnodes := make([]TSOInfo, 0)\n\tdata, err := pdClient.WithoutPrefix().SendGetRequest(\"/pd/api/v2/ms/members/tso\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tds := []struct {\n\t\tServiceAddr    string `json:\"service-addr\"`\n\t\tVersion        string `json:\"version\"`\n\t\tGitHash        string `json:\"git-hash\"`\n\t\tDeployPath     string `json:\"deploy-path\"`\n\t\tStartTimestamp int64  `json:\"start-timestamp\"`\n\t}{}\n\n\terr = json.Unmarshal(data, &ds)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"%s members API unmarshal failed\", distro.R().TSO)\n\t}\n\n\tfor _, ds := range ds {\n\t\tu := ds.ServiceAddr\n\t\thostname, port, err := netutil.ParseHostAndPortFromAddressURL(u)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\tnodes = append(nodes, TSOInfo{\n\t\t\tGitHash:        ds.GitHash,\n\t\t\tVersion:        ds.Version,\n\t\t\tIP:             hostname,\n\t\t\tPort:           port,\n\t\t\tDeployPath:     ds.DeployPath,\n\t\t\tStatus:         ComponentStatusUp,\n\t\t\tStartTimestamp: ds.StartTimestamp,\n\t\t})\n\t}\n\n\tsort.Slice(nodes, func(i, j int) bool {\n\t\tif nodes[i].IP < nodes[j].IP {\n\t\t\treturn true\n\t\t}\n\t\tif nodes[i].IP > nodes[j].IP {\n\t\t\treturn false\n\t\t}\n\t\treturn nodes[i].Port < nodes[j].Port\n\t})\n\n\treturn nodes, nil\n}\n"
  },
  {
    "path": "pkg/utils/version/fips.go",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\n//go:build boringcrypto\n\npackage version\n\nimport _ \"crypto/tls/fipsonly\"\n"
  },
  {
    "path": "pkg/utils/version/version.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage version\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\ntype Info struct {\n\tInternalVersion string `json:\"internal_version\"`\n\tStandalone      string `json:\"standalone\"`\n\tPDVersion       string `json:\"pd_version\"`\n\tBuildTime       string `json:\"build_time\"`\n\tBuildGitHash    string `json:\"build_git_hash\"`\n}\n\n// Zero-value version information. It will be overwritten by LDFLAGS.\nvar (\n\tInternalVersion = \"0.0.0\"\n\tStandalone      = \"Yes\" // Unknown, Yes or No\n\tPDVersion       = \"0.0.0\"\n\tBuildTime       = \"1970-01-01 00:00:00\"\n\tBuildGitHash    = \"Unknown\"\n)\n\nfunc Print() {\n\tlog.Info(fmt.Sprintf(\"%s Dashboard started\", distro.R().TiDB),\n\t\tzap.String(\"internal-version\", InternalVersion),\n\t\tzap.String(\"standalone\", Standalone),\n\t\tzap.String(fmt.Sprintf(\"%s-version\", strings.ToLower(distro.R().PD)), PDVersion),\n\t\tzap.String(\"build-time\", BuildTime),\n\t\tzap.String(\"build-git-hash\", BuildGitHash))\n}\n\nfunc GetInfo() *Info {\n\treturn &Info{\n\t\tInternalVersion: InternalVersion,\n\t\tStandalone:      Standalone,\n\t\tPDVersion:       PDVersion,\n\t\tBuildTime:       BuildTime,\n\t\tBuildGitHash:    BuildGitHash,\n\t}\n}\n\nfunc PrintStandaloneModeInfo() {\n\tfmt.Println(\"Internal Version: \", InternalVersion)\n\tfmt.Println(\"Git Commit Hash:  \", BuildGitHash)\n\tfmt.Println(\"UTC Build Time:   \", BuildTime)\n}\n"
  },
  {
    "path": "release-version",
    "content": "# This file specifies the TiDB Dashboard internal version, which will be printed in `--version`\n# and UI. In release branch, changing this file will result in publishing a new version and tag.\nnightly\n"
  },
  {
    "path": "scripts/_inc/download_tools.sh",
    "content": "#!/usr/bin/env bash\n\n# Download tools for running the integration test\n\nset -euo pipefail\n\nPROJECT_DIR=\"$(dirname \"$0\")/..\"\nBIN=\"${PROJECT_DIR}/bin\"\n\ndownload_tools() {\n  echo \"+ Download tools\"\n\n  download_tiup\n\n  mkdir -p $BIN\n\n  if [ ! -e \"$BIN/toolkit.tar.gz\" ]; then\n    echo \"  - Downloading toolkit...\"\n    curl -L -f -o \"$BIN/toolkit.tar.gz\" \"https://download.pingcap.org/tidb-toolkit-v6.0.0-linux-amd64.tar.gz\"\n  fi\n\n  if [ ! -e \"$BIN/dumpling\" ]; then\n    tar -x -f \"$BIN/toolkit.tar.gz\" -C \"$BIN/\" tidb-toolkit-v6.0.0-linux-amd64/bin/dumpling\n    mv \"$BIN\"/tidb-toolkit-v6.0.0-linux-amd64/bin/dumpling \"$BIN/dumpling\"\n  fi\n\n  echo \"+ All binaries are now available.\"\n}\n\ndownload_tiup() {\n  if ! command -v tiup >/dev/null 2>&1; then\n    echo \"  - Downloading tiup...\"\n    curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh\n  fi\n}\n"
  },
  {
    "path": "scripts/_inc/run_services.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nINTEGRATION_LOG_PATH=/tmp/dashboard-integration-test.log\nINTEGRATION_PID_LOG_PATH=/tmp/dashboard-integration-test-pid.log\nTIUP_BIN_DIR=$HOME/.tiup/bin\n\nPROJECT_DIR=\"$(dirname \"$0\")/..\"\nBIN=\"${PROJECT_DIR}/bin\"\n\nstart_tidb() {\n  echo \"+ Waiting for TiDB start, for at most 15 min...\"\n\n  rm -rf $INTEGRATION_LOG_PATH\n  TIDB_VERSION=${1:-latest}\n  $TIUP_BIN_DIR/tiup playground $TIDB_VERSION > $INTEGRATION_LOG_PATH &\n  echo $! > $INTEGRATION_PID_LOG_PATH\n  ensure_tidb\n\n  echo \"  - Start TiDB@$TIDB_VERSION Success!\"\n}\n\nstop_tidb() {\n  echo \"+ Waiting for TiDB teardown...\"\n  kill `cat $INTEGRATION_PID_LOG_PATH`\n}\n\nensure_tidb() {\n  i=1\n  while ! grep \"TiDB Playground Cluster is started\" $INTEGRATION_LOG_PATH; do\n    i=$((i+1))\n    if [ \"$i\" -gt 90 ]; then\n      echo 'Failed to start TiDB'\n      return 1\n    fi\n    sleep 10\n  done\n}\n\ndump_schema() {\n  if [ ${1:-\"\"} = \"\" ]; then\n    echo \"Please specify the 'database-name.table-name' to dump\"\n    echo \"Usage: tests/dump.sh database-name.table-name\"\n    return 1\n  fi\n\n  if [ -e \"$BIN/dumpling\" ]; then\n    echo \"+ Start dump schema...\"\n    $BIN/dumpling -u root -P 4000 -h 127.0.0.1 --filetype sql --no-data -o \"${PROJECT_DIR}/tests/schema\" -T $1\n    echo \"  - Dump success!\"\n  else\n    echo \"Tool $BIN/dumpling not exist\"\n    return 1\n  fi\n}\n"
  },
  {
    "path": "scripts/create_release_tag.js",
    "content": "// This scripts is used to create a new release tag for the current release branch\n// when we need to submit a PR to PD for updating the tidb-dashboard.\n// After this tag is pushed, it will trigger the CI to build a new tidb-dashboard release version.\n// Then we can run the manual-create-pd-pr.yaml workflow in the GitHub to create a PR to PD.\n\nconst { execSync } = require('child_process');\n\nconst readline = require('readline');\n\nconst rl = readline.createInterface({\n  input: process.stdin,\n  output: process.stdout\n});\n\n// rl.on('close', () => {\n//   console.log('exit')\n//   process.exit(0);\n// });\n\nfunction getGitBranch() {\n  // master, release-7.6\n  return execSync('git rev-parse --abbrev-ref HEAD').toString().trim();\n}\n\nfunction getGitShortSha() {\n  // eb69e4fd\n  return execSync('git rev-parse --short HEAD').toString().trim();\n}\n\nfunction getGitLatestTag() {\n  // v7.6.0-alpha, v7.6.0-<sha>, v7.6.0, v7.6.1-<sha>, v7.6.1\n  // v7.6.0-alpha-3-g383cf602, v7.6.0-<sha>-3-g383cf602, v7.6.0-3-g383cf602, v7.6.1-<sha>-3-g383cf602, v7.6.1-3-g383cf602\n  return execSync('git describe --tags --dirty --always').toString().trim();\n}\n\nfunction question(nextTag) {\n  rl.question(`Do you want to create tag ${nextTag}? (y or enter continue, others exit): `, (answer) => {\n    if (answer.toLowerCase() === 'y' || answer.toLowerCase() === '') {\n      execSync(`git tag ${nextTag}`);\n      console.log(`Created tag ${nextTag}`)\n      process.exit(0);\n    } else {\n      console.log('Cancel create tag')\n      process.exit(0);\n    }\n  })\n}\n\nfunction createReleaseTag() {\n  const branch = getGitBranch();\n  const branchVer = branch.replace('release-', '');\n  const latestTag = getGitLatestTag().replace('-fips', '');\n\n  if (!latestTag.startsWith(`v${branchVer}.`)) {\n    console.error(`Err: latest tag ${latestTag} doesn't match the branch ${branch}, you need to add the new tag manually`);\n    process.exit(1)\n  }\n\n  const shortSha = getGitShortSha();\n  const splitPos = latestTag.indexOf('-');\n  let nextTag = ''\n  if (splitPos === -1) {\n    // the latest tag likes v7.6.0, v7.6.1\n    // then the next tag should be v7.6.1-<sha> for v7.6.0, v7.6.2-<sha> for v7.6.1\n    const suffix = latestTag.replace(`v${branchVer}.`, '');\n    nextTag = `v${branchVer}.${parseInt(suffix) + 1}-${shortSha}`;\n  } else if (latestTag.match(/^v\\d+\\.\\d+\\.\\d+-\\d+-[0-9a-z]{9}/)) {\n    // the latest tag likes v7.6.0-3-g383cf602, v7.6.1-3-g383cf602\n    // then the next tag should be v7.6.1-<sha> for v7.6.0, v7.6.2-<sha> for v7.6.1\n    const suffix = latestTag.substring(0, splitPos).replace(`v${branchVer}.`, '');\n    nextTag = `v${branchVer}.${parseInt(suffix) + 1}-${shortSha}`;\n  } else {\n    // the latest tag likes v7.6.0-<sha>, v7.6.1-<sha>\n    // then the next tag should be v7.6.0-<sha> for v7.6.0-<sha>, v7.6.1-<sha> for v7.6.1-<sha>\n    const prefix = latestTag.substring(0, splitPos);\n    nextTag = `${prefix}-${shortSha}`;\n  }\n\n  question(nextTag)\n}\n\nfunction createMasterTag() {\n  const latestTag = getGitLatestTag();\n  // the latest tag must like vX.Y.0-alpha-3-g383cf602, likes `v8.4.0-alpha-3-g383cf602`\n  // or like vX.Y.0-<sha>-3-g383cf602\n  const shortSha = getGitShortSha();\n  if (!latestTag.match(/^v\\d+\\.\\d+.0-alpha/) && !latestTag.match(/^v\\d+\\.\\d+.0-[0-9a-z]{8}/)) {\n    console.error(`Err: latest tag ${latestTag} is not a valid tag, please add the tag manually, currently sha is ${shortSha}, the tag should be vX.Y.Z-${shortSha}`)\n    process.exit(1)\n  }\n  const splitPos = latestTag.indexOf('-');\n  const prefix = latestTag.substring(0, splitPos);\n  const nextTag = `${prefix}-${shortSha}`\n\n  question(nextTag)\n}\n\nfunction createTag() {\n  const branch = getGitBranch();\n\n  if (branch.match(/^release-\\d+\\.\\d+$/)) {\n    createReleaseTag()\n  } else if (branch === 'master') {\n    createMasterTag()\n  } else {\n    console.error('Err: this is not a valid branch that can be tagged');\n    process.exit(1)\n  }\n}\n\ncreateTag()\n"
  },
  {
    "path": "scripts/distro/write_strings.go",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"flag\"\n\t\"io/ioutil\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nfunc main() {\n\toutputPath := flag.String(\"o\", \"\", \"Output json file path\")\n\tflag.Parse()\n\n\td, _ := json.Marshal(distro.R())\n\tif err := ioutil.WriteFile(*outputPath, d, 0o600); err != nil {\n\t\tlog.Fatal(\"Write output failed\", zap.Error(err))\n\t}\n}\n"
  },
  {
    "path": "scripts/distro/write_strings.sh",
    "content": "#!/usr/bin/env bash\n\n# This script outputs distribution strings to json which is required for the frontend.\n\nset -euo pipefail\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\nPROJECT_DIR=$(cd \"$DIR/../..\"; pwd)\n\nTARGET=\"${PROJECT_DIR}/ui/packages/tidb-dashboard-for-op/src/utils/distro/strings_res.json\"\n\necho \"+ Write distro strings\"\ncd \"$PROJECT_DIR\"\ngo run scripts/distro/write_strings.go -o=\"${TARGET}\"\n\necho \"  - Success! Distro strings:\"\ncat \"${TARGET}\"\n\necho\n"
  },
  {
    "path": "scripts/embed_ui_assets.sh",
    "content": "#!/usr/bin/env bash\n\n# This script embeds UI assets into Golang source file. UI assets must be already built\n# before calling this script.\n#\n# Available flags:\n# NO_ASSET_BUILD_TAG=1\n#   No build tags will be included in the generated source code.\n# ASSET_BUILD_TAG=X\n#   Customize the build tag of the generated source code. If unspecified, build tag will be \"ui_server\".\n\nset -euo pipefail\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\nPROJECT_DIR=$(cd \"$DIR/..\"; pwd)\n\nUI_ASSETS_DIR=$PROJECT_DIR/ui/packages/tidb-dashboard-for-op/dist\n\nexport GOBIN=$PROJECT_DIR/bin\nexport PATH=$GOBIN:$PATH\n\necho \"+ Preflight check\"\nif [ ! -d \"$UI_ASSETS_DIR\" ]; then\n  echo \"  - Error: UI assets must be built first\"\n  exit 1\nfi\n\nif [ \"${NO_ASSET_BUILD_TAG:-}\" = \"1\" ]; then\n  BUILD_TAG_PARAMETER=\"\"\nelse\n  BUILD_TAG_PARAMETER=${ASSET_BUILD_TAG:-ui_server}\nfi\n\necho \"+ Embed UI assets\"\n\ncd \"$PROJECT_DIR/scripts\"\ngo run generate_assets.go \"$UI_ASSETS_DIR\" \"$BUILD_TAG_PARAMETER\"\n\nHANDLER_PATH=$PROJECT_DIR/pkg/uiserver/embedded_assets_handler.go\nmv assets_vfsdata.go $HANDLER_PATH\necho \"  - Assets handler written to $HANDLER_PATH\"\n"
  },
  {
    "path": "scripts/generate_assets.go",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage main\n\nimport (\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/pingcap/log\"\n\t\"github.com/shurcooL/vfsgen\"\n\t\"go.uber.org/zap\"\n)\n\nfunc main() {\n\tif len(os.Args) < 3 {\n\t\tlog.Fatal(\"Require 2 args\")\n\t}\n\tdirectory := os.Args[1]\n\tbuildTag := os.Args[2]\n\tvar fs http.FileSystem = http.Dir(directory)\n\terr := vfsgen.Generate(fs, vfsgen.Options{\n\t\tBuildTags:    buildTag,\n\t\tPackageName:  \"uiserver\",\n\t\tVariableName: \"assets\",\n\t})\n\tif err != nil {\n\t\tlog.Fatal(\"Generate vfs failed\", zap.Error(err))\n\t}\n}\n"
  },
  {
    "path": "scripts/generate_swagger_spec.sh",
    "content": "#!/usr/bin/env bash\n\n# This script generates API client from the swagger annotation in the Golang server code.\n\nset -euo pipefail\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\nPROJECT_DIR=$(cd \"$DIR/..\"; pwd)\n\ncd $PROJECT_DIR\n\necho \"+ Generate swagger spec\"\nbin/swag init --parseDependency --parseDepth 1 --generalInfo cmd/tidb-dashboard/main.go --propertyStrategy snakecase \\\n  --exclude ui --output swaggerspec\n"
  },
  {
    "path": "scripts/go.mod",
    "content": "module scripts\n\ngo 1.25.7\n\nrequire (\n\tgithub.com/codeskyblue/go-sh v0.0.0-20200712050446-30169cf553fe\n\tgithub.com/olekukonko/tablewriter v0.0.5\n\tgithub.com/pingcap/log v0.0.0-20210906054005-afc726e70354\n\tgithub.com/pingcap/tidb-dashboard v0.0.0-00010101000000-000000000000\n\tgithub.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546\n\tgithub.com/swaggo/swag v1.7.9\n\tgithub.com/vektra/mockery/v2 v2.40.3\n\tgo.uber.org/zap v1.21.0\n\tgolang.org/x/mod v0.29.0\n)\n\nrequire (\n\tgithub.com/KyleBanks/depth v1.2.1 // indirect\n\tgithub.com/PuerkitoBio/purell v1.1.1 // indirect\n\tgithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect\n\tgithub.com/benbjohnson/clock v1.1.0 // indirect\n\tgithub.com/chigopher/pathlib v0.19.1 // indirect\n\tgithub.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 // indirect\n\tgithub.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect\n\tgithub.com/fsnotify/fsnotify v1.6.0 // indirect\n\tgithub.com/ghodss/yaml v1.0.0 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.19.5 // indirect\n\tgithub.com/go-openapi/jsonreference v0.19.6 // indirect\n\tgithub.com/go-openapi/spec v0.20.4 // indirect\n\tgithub.com/go-openapi/swag v0.19.15 // indirect\n\tgithub.com/hashicorp/hcl v1.0.0 // indirect\n\tgithub.com/huandu/xstrings v1.4.0 // indirect\n\tgithub.com/iancoleman/strcase v0.2.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/jinzhu/copier v0.3.5 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/magiconair/properties v1.8.7 // indirect\n\tgithub.com/mailru/easyjson v0.7.6 // 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.9 // indirect\n\tgithub.com/mitchellh/go-homedir v1.1.0 // indirect\n\tgithub.com/mitchellh/mapstructure v1.5.0 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.4 // indirect\n\tgithub.com/rs/zerolog v1.29.0 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.1.0 // indirect\n\tgithub.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect\n\tgithub.com/spf13/afero v1.9.3 // indirect\n\tgithub.com/spf13/cast v1.5.0 // indirect\n\tgithub.com/spf13/cobra v1.6.1 // indirect\n\tgithub.com/spf13/jwalterweatherman v1.1.0 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/spf13/viper v1.15.0 // indirect\n\tgithub.com/subosito/gotenv v1.4.2 // indirect\n\tgithub.com/urfave/cli/v2 v2.3.0 // indirect\n\tgo.uber.org/atomic v1.9.0 // indirect\n\tgo.uber.org/multierr v1.8.0 // indirect\n\tgolang.org/x/net v0.47.0 // indirect\n\tgolang.org/x/sync v0.18.0 // indirect\n\tgolang.org/x/sys v0.38.0 // indirect\n\tgolang.org/x/term v0.37.0 // indirect\n\tgolang.org/x/text v0.31.0 // indirect\n\tgolang.org/x/tools v0.38.0 // indirect\n\tgopkg.in/ini.v1 v1.67.0 // indirect\n\tgopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n\nreplace github.com/pingcap/tidb-dashboard => ../\n"
  },
  {
    "path": "scripts/go.sum",
    "content": "cloud.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.44.3/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 v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=\ncloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=\ncloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=\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=\ncloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=\ndmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=\ngithub.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=\ngithub.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=\ngithub.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=\ngithub.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=\ngithub.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=\ngithub.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=\ngithub.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs=\ngithub.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=\ngithub.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=\ngithub.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\ngithub.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=\ngithub.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=\ngithub.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=\ngithub.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=\ngithub.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=\ngithub.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/chigopher/pathlib v0.19.1 h1:RoLlUJc0CqBGwq239cilyhxPNLXTK+HXoASGyGznx5A=\ngithub.com/chigopher/pathlib v0.19.1/go.mod h1:tzC1dZLW8o33UQpWkNkhvPwL5n4yyFRFm/jL1YGWFvY=\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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=\ngithub.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q=\ngithub.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=\ngithub.com/codeskyblue/go-sh v0.0.0-20200712050446-30169cf553fe h1:69JI97HlzP+PH5Mi1thcGlDoBr6PS2Oe+l3mNmAkbs4=\ngithub.com/codeskyblue/go-sh v0.0.0-20200712050446-30169cf553fe/go.mod h1:VQx0hjo2oUeQkQUET7wRwradO6f+fN5jzXgB/zROxxE=\ngithub.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\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/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=\ngithub.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=\ngithub.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=\ngithub.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=\ngithub.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=\ngithub.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=\ngithub.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=\ngithub.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=\ngithub.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=\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-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=\ngithub.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=\ngithub.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=\ngithub.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=\ngithub.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=\ngithub.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=\ngithub.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=\ngithub.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=\ngithub.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=\ngithub.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=\ngithub.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=\ngithub.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=\ngithub.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=\ngithub.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=\ngithub.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=\ngithub.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=\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.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=\ngithub.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\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/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-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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=\ngithub.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\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/martian/v3 v3.1.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-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=\ngithub.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\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/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=\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/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=\ngithub.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=\ngithub.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=\ngithub.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=\ngithub.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=\ngithub.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=\ngithub.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=\ngithub.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=\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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=\ngithub.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=\ngithub.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=\ngithub.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=\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/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=\ngithub.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=\ngithub.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=\ngithub.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=\ngithub.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=\ngithub.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=\ngithub.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=\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.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=\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.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=\ngithub.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=\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.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=\ngithub.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=\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 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=\ngithub.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=\ngithub.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=\ngithub.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=\ngithub.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE=\ngithub.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=\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/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=\ngithub.com/pingcap/errors v0.11.5-0.20200917111840-a15ef68f753d h1:TH18wFO5Nq/zUQuWu9ms2urgZnLP69XJYiI2JZAkUGc=\ngithub.com/pingcap/errors v0.11.5-0.20200917111840-a15ef68f753d/go.mod h1:g4vx//d6VakjJ0mk7iLBlKA8LFavV/sAVINT/1PFxeQ=\ngithub.com/pingcap/log v0.0.0-20210906054005-afc726e70354 h1:SvWCbCPh1YeHd9yQLksvJYAgft6wLTY1aNG81tpyscQ=\ngithub.com/pingcap/log v0.0.0-20210906054005-afc726e70354/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=\ngithub.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=\ngithub.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk=\ngithub.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U=\ngithub.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=\ngithub.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=\ngithub.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=\ngithub.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=\ngithub.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=\ngithub.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\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/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=\ngithub.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 h1:pXY9qYc/MP5zdvqWEUH6SjNiu7VhSjuVFTFiTcphaLU=\ngithub.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=\ngithub.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=\ngithub.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=\ngithub.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=\ngithub.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=\ngithub.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=\ngithub.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=\ngithub.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=\ngithub.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=\ngithub.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=\ngithub.com/stretchr/objx v0.1.0/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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\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.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=\ngithub.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=\ngithub.com/swaggo/swag v1.7.9 h1:6vCG5mm43ebDzGlZPMGYrYI4zKFfOr5kicQX8qjeDwc=\ngithub.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU=\ngithub.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=\ngithub.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=\ngithub.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=\ngithub.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=\ngithub.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=\ngithub.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=\ngithub.com/vektra/mockery/v2 v2.40.3 h1:IZ2lydSDFsY0khnEsbSu13VLcqSsa6UYSS/8F+uOJmo=\ngithub.com/vektra/mockery/v2 v2.40.3/go.mod h1:KYBZF/7sqOa86BaOZPYsoCZWEWLS90a5oBLg2pVudxY=\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=\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.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=\ngo.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=\ngo.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=\ngo.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=\ngo.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=\ngo.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=\ngo.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=\ngo.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=\ngo.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=\ngolang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=\ngolang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=\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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=\ngolang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=\ngolang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=\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/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/lint v0.0.0-20201208152925-83fdc39ff7b5/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.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.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=\ngolang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=\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-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-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-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\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-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=\ngolang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=\ngolang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=\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-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=\ngolang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/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.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=\ngolang.org/x/sync v0.18.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-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-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-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-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/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-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=\ngolang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=\ngolang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=\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.4/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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=\ngolang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=\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-20191108193012-7d206e10da11/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-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-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.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=\ngolang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=\ngolang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=\ngolang.org/x/tools/godoc v0.1.0-deprecated h1:o+aZ1BOj6Hsx/GBdJO/s815sqftjSnrZZwyYTHODvtk=\ngolang.org/x/tools/godoc v0.1.0-deprecated/go.mod h1:qM63CriJ961IHWmnWa9CjZnBndniPt4a3CK0PVB9bIg=\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/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=\ngoogle.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=\ngoogle.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=\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/appengine v1.6.7/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/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=\ngoogle.golang.org/genproto v0.0.0-20210226172003-ab064af71705/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/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=\ngoogle.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\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.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=\ngoogle.golang.org/protobuf v1.36.10/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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=\ngopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\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/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=\ngopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/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.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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=\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=\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": "scripts/install_go_tools.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\nPROJECT_DIR=$(cd \"$DIR/..\"; pwd)\n\n# See https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module\n\ncd $PROJECT_DIR/scripts\n\nexport GOBIN=$PROJECT_DIR/bin\nexport PATH=$GOBIN:$PATH\n\necho \"+ Install go tools\"\ngrep '_' tools.go | sed 's/\"//g' | awk '{print $2}' | xargs -t -L 1 go install\n\necho \"+ Clean up go mod\"\ngo mod tidy\n"
  },
  {
    "path": "scripts/lint.sh",
    "content": "#!/usr/bin/env bash\n\n# This script run lints.\n\nset -euo pipefail\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\nPROJECT_DIR=\"$(dirname \"$DIR\")\"\n\ncd $PROJECT_DIR\n\nLINT_BIN=./bin/golangci-lint\nREQUIRED_VERSION=2.11.3\nNEED_DOWNLOAD=true\n\necho \"+ Check golangci-lint binary\"\nif [[ -f \"${LINT_BIN}\" ]]; then\n  if ${LINT_BIN} --version | grep -qF ${REQUIRED_VERSION}; then\n    NEED_DOWNLOAD=false\n  fi\nfi\n\nif [ \"${NEED_DOWNLOAD}\" = true ]; then\n  echo \"  - Download golangci-lint binary\"\n  curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v${REQUIRED_VERSION}\nfi\n\necho \"+ Run lints for Go source code\"\n${LINT_BIN} run --fix --timeout=10m\n\necho \"+ Clean up go mod\"\ngo mod tidy\n"
  },
  {
    "path": "scripts/pd_version_matrix.go",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\n// This proram can be used to generate a version relationship matrix for TiDB Dashboard and PD.\n\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/codeskyblue/go-sh\"\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\t\"golang.org/x/mod/modfile\"\n\t\"golang.org/x/mod/semver\"\n)\n\nvar pdGitDir string\nvar dashboardGitDir = flag.String(\"dashboard\", \"\", \"TiDB Dashboard git path\")\nvar outputFormat = flag.String(\"format\", \"mdtable\", \"Output format, accept values: text, mdtable, mdtable-link\")\n\nfunc mustSuccess(err error) {\n\tif err != nil {\n\t\tlog.Fatal(\"Execute failed\", zap.Error(err))\n\t}\n}\n\nfunc listPDTags() []string {\n\toutput, err := sh.Command(\"git\", \"tag\", sh.Dir(pdGitDir)).Output()\n\tmustSuccess(err)\n\tvalidTags := make([]string, 0)\n\tpdTags := strings.Split(string(output), \"\\n\")\n\tfor _, pdTag := range pdTags {\n\t\tif !semver.IsValid(pdTag) {\n\t\t\tcontinue\n\t\t}\n\t\tvalidTags = append(validTags, pdTag)\n\t}\n\tsort.SliceStable(validTags, func(i, j int) bool {\n\t\treturn semver.Compare(validTags[i], validTags[j]) < 0\n\t})\n\treturn validTags\n}\n\nfunc lookupDashboardCommit(pdTag string) string {\n\toutput, err := sh.Command(\n\t\t\"git\", \"show\", fmt.Sprintf(\"%s:go.mod\", pdTag),\n\t\tsh.Dir(pdGitDir)).Output()\n\tmustSuccess(err)\n\n\tf, err := modfile.Parse(\"go.mod\", output, nil)\n\tmustSuccess(err)\n\tfor _, r := range f.Require {\n\t\tif r.Mod.Path != \"github.com/pingcap-incubator/tidb-dashboard\" &&\n\t\t\tr.Mod.Path != \"github.com/pingcap/tidb-dashboard\" {\n\t\t\tcontinue\n\t\t}\n\t\tif !semver.IsValid(r.Mod.Version) {\n\t\t\tcontinue\n\t\t}\n\t\tversionSegments := strings.Split(r.Mod.Version, \"-\")\n\t\tgitHash := versionSegments[2]\n\t\treturn gitHash\n\t}\n\n\treturn \"\"\n}\n\nfunc lookupDashboardRelease(gitCommit string) string {\n\tsess := sh.NewSession()\n\tsess.Stderr = nil\n\toutput, err := sess.\n\t\tCommand(\"git\", \"show\", fmt.Sprintf(\"%s:release-version\", gitCommit), sh.Dir(*dashboardGitDir)).\n\t\tOutput()\n\tif err != nil {\n\t\t// It might be possible that the TiDB Dashboard is not using a calver.\n\t\treturn \"\"\n\t}\n\n\tlines := strings.Split(string(output), \"\\n\")\n\tfor _, line := range lines {\n\t\tif len(line) > 0 && !strings.HasPrefix(line, \"#\") {\n\t\t\treturn line\n\t\t}\n\t}\n\n\treturn \"\"\n}\n\nfunc lookupPDTagUpdateTime(pdTag string) string {\n\toutput, err := sh.Command(\n\t\t\"git\", \"log\", \"-1\", \"--format=%cI\", pdTag,\n\t\tsh.Dir(pdGitDir)).Output()\n\tmustSuccess(err)\n\n\tt, err := time.Parse(time.RFC3339, strings.TrimSpace(string(output)))\n\tmustSuccess(err)\n\n\treturn t.UTC().Format(\"2006-01-02\")\n}\n\nfunc main() {\n\tflag.Parse()\n\n\tif flag.NArg() < 1 {\n\t\tfmt.Println(\"Usage: go run pd_version_matrix.go <pd_git_path>\")\n\t\tos.Exit(1)\n\t\treturn\n\t}\n\n\tpdGitDir = flag.Arg(0)\n\toutput := make([][]string, 0)\n\n\tpdTags := listPDTags()\n\tfor _, pdTag := range pdTags {\n\t\tif strings.Count(pdTag, \"-\") > 0 {\n\t\t\tcontinue\n\t\t}\n\t\tif semver.Compare(pdTag, \"v4.0.0\") < 0 {\n\t\t\tcontinue\n\t\t}\n\t\ttagAt := lookupPDTagUpdateTime(pdTag)\n\t\tdashboardCommit := lookupDashboardCommit(pdTag)\n\t\tif dashboardCommit == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tdashboardRelease := lookupDashboardRelease(dashboardCommit)\n\t\tif dashboardRelease == \"\" {\n\t\t\tcontinue\n\t\t}\n\n\t\toutput = append(output, []string{pdTag, tagAt, dashboardRelease})\n\t}\n\n\tswitch *outputFormat {\n\tcase \"mdtable\", \"mdtable-link\":\n\t\tif *outputFormat == \"mdtable-link\" {\n\t\t\tfor _, row := range output {\n\t\t\t\trow[2] = fmt.Sprintf(\"[%s](https://github.com/pingcap/tidb-dashboard/releases/tag/v%s)\", row[2], row[2])\n\t\t\t}\n\t\t}\n\t\ttable := tablewriter.NewWriter(os.Stdout)\n\t\ttable.SetHeader([]string{\"PD Version\", \"PD Commit At\", \"TiDB Dashboard Version\"})\n\t\ttable.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})\n\t\ttable.SetCenterSeparator(\"|\")\n\t\ttable.AppendBulk(output)\n\t\ttable.Render()\n\tdefault:\n\t\tfor _, row := range output {\n\t\t\tfmt.Printf(\"%s: %s\\n\", row[0], row[1], row[2])\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "scripts/start_tiup.sh",
    "content": "#!/usr/bin/env bash\nset -ex\ntidb_version=$1\nwithout_ngm=${2:-false}\nmode=${3:-\"start\"}\n\n# TIUP_BIN_DIR=$TIUP_HOME/bin/tiup\nTIUP_BIN_DIR=$HOME/.tiup/bin/tiup\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\n\nif [ $mode = \"restart\" ]; then\n    # get process id\n    pid=$(ps -ef | grep -v start_tiup | grep tiup | grep -v grep | awk '{print $2}')\n\n    for id in $pid\n    do\n        # kill tiup-playground\n        echo \"killing $id\"\n        kill -9 $id;\n    done\n\n    # Run Tiup\n    $TIUP_BIN_DIR playground ${tidb_version} --db.config=$DIR/tiup.config.toml --tiflash=0 &> start_tiup.log &\nelse\n    echo \"install tiup\"\n    # Install TiUP\n    curl --proto '=https' --tlsv1.2 -sSf https://tiup-mirrors.pingcap.com/install.sh | sh\n    $TIUP_BIN_DIR update playground\n\n    # Run Tiup\n    $TIUP_BIN_DIR playground ${tidb_version} --without-monitor=${without_ngm} --tiflash=0 &> ~/start_tiup.log &\nfi\n"
  },
  {
    "path": "scripts/tiup.config.toml",
    "content": "[log]\nslow-threshold = 800\n"
  },
  {
    "path": "scripts/tools.go",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage scripts\n\n// This file lists tools that need to be installed via `go install`.\n\nimport (\n\t_ \"github.com/swaggo/swag/cmd/swag\"\n\t_ \"github.com/vektra/mockery/v2\"\n)\n"
  },
  {
    "path": "scripts/wait_tiup_playground.sh",
    "content": "#!/usr/bin/env bash\n# Wait unitl `tiup playground` command runs success\n\nINTERVAL=$1\nMAX_TIMES=$2\n\nif ([ -z \"${INTERVAL}\" ] || [ -z \"${MAX_TIMES}\" ]); then\n  echo \"Usage: command <interval> <max_times>\"\n  exit 1\nfi\n\nsource /home/runner/.profile\n\nfor ((i=0; i<${MAX_TIMES}; i++)); do\n  tiup playground display\n  if [ $? -eq 0 ]; then\n    exit 0\n  fi\n  cat ~/start_tiup.log\n  sleep ${INTERVAL}\ndone\n\nexit 1\n"
  },
  {
    "path": "swaggerspec/.gitignore",
    "content": "*\n!.gitignore\n!placeholder.go\n"
  },
  {
    "path": "swaggerspec/placeholder.go",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\n// This file only ensures `swaggerspec` package exist even if swagger is not enabled. This is required for `go mod tidy`.\n\npackage swaggerspec\n\nimport (\n\t// Make sure that go mod tidy won't clean up necessary dependencies.\n\t_ \"github.com/alecthomas/template\"\n\t_ \"github.com/swaggo/swag\"\n)\n"
  },
  {
    "path": "tests/create_table.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\necho \"+ Create test tables\"\ncat tests/schema/*.sql | mysql --host 127.0.0.1 --port 4000 -u root test || true\n\necho \"  - Success!\"\n"
  },
  {
    "path": "tests/dump.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nPROJECT_DIR=\"$(dirname \"$0\")/..\"\n\nsource scripts/_inc/download_tools.sh >/dev/null\nsource scripts/_inc/run_services.sh >/dev/null\n\ndownload_tools\ndump_schema $@\ngo run $PROJECT_DIR/tests/util/dump/dump.go $@\n"
  },
  {
    "path": "tests/fixtures/CLUSTER_SLOW_QUERY.yml",
    "content": "- INSTANCE: \"1\"\n  Warnings: null\n  Time: 2021-12-19T23:45:30.786521+08:00\n  Txn_start_ts: 1\n  User: \"1\"\n  Host: \"1\"\n  Conn_ID: 1\n  Exec_retry_count: 1\n  Exec_retry_time: 1\n  Query_time: 0.462883101\n  Parse_time: 1\n  Compile_time: 3.2932e-05\n  Rewrite_time: 1.943e-06\n  Preproc_subqueries: 1\n  Preproc_subqueries_time: 1\n  Optimize_time: 1\n  Wait_TS: 1\n  Prewrite_time: 1\n  Wait_prewrite_binlog_time: 1\n  Commit_time: 1\n  Get_commit_ts_time: 1\n  Commit_backoff_time: 1\n  Backoff_types: \"1\"\n  Resolve_lock_time: 1\n  Local_latch_wait_time: 1\n  Write_keys: 1\n  Write_size: 1\n  Prewrite_region: 1\n  Txn_retry: 1\n  Cop_time: 1\n  Process_time: 1\n  Wait_time: 1\n  Backoff_time: 1\n  LockKeys_time: 1\n  Request_count: 1\n  Total_keys: 1\n  Process_keys: 1\n  Rocksdb_delete_skipped_count: 1\n  Rocksdb_key_skipped_count: 1\n  Rocksdb_block_cache_hit_count: 1\n  Rocksdb_block_read_count: 1\n  Rocksdb_block_read_byte: 1\n  DB: \"1\"\n  Index_names: \"1\"\n  Is_internal: 1\n  Digest: TEST_ALL_FIELDS\n  Stats: \"1\"\n  Cop_proc_avg: 1\n  Cop_proc_p90: 1\n  Cop_proc_max: 1\n  Cop_proc_addr: \"1\"\n  Cop_wait_avg: 1\n  Cop_wait_p90: 1\n  Cop_wait_max: 1\n  Cop_wait_addr: \"1\"\n  Mem_max: 1\n  Disk_max: 1\n  KV_total: 1\n  PD_total: 1\n  Backoff_total: 1\n  Write_sql_response_total: 1\n  Result_rows: 1\n  Backoff_Detail: \"1\"\n  Prepared: 1\n  Succ: 1\n  IsExplicitTxn: 1\n  IsWriteCacheTable: 1\n  Plan_from_cache: 1\n  Plan_from_binding: 1\n  Plan: \"1\"\n  Plan_digest: \"1\"\n  Prev_stmt: \"test prev stmt\"\n  Query: CREATE DATABASE IF NOT EXISTS `mysql`;\n- INSTANCE: \"\"\n  Warnings: null\n  Time: 2021-12-19T23:49:47.79658+08:00\n  Txn_start_ts: 0\n  User: root\n  Host: 127.0.0.1\n  Conn_ID: 7\n  Exec_retry_count: 0\n  Exec_retry_time: 0\n  Query_time: 6.3039e-05\n  Parse_time: 1.5559e-05\n  Compile_time: 3.4865e-05\n  Rewrite_time: 5.851e-06\n  Preproc_subqueries: 0\n  Preproc_subqueries_time: 0\n  Optimize_time: 2.18e-05\n  Wait_TS: 0\n  Prewrite_time: 0\n  Wait_prewrite_binlog_time: 0\n  Commit_time: 0\n  Get_commit_ts_time: 0\n  Commit_backoff_time: 0\n  Backoff_types: \"\"\n  Resolve_lock_time: 0\n  Local_latch_wait_time: 0\n  Write_keys: 0\n  Write_size: 0\n  Prewrite_region: 0\n  Txn_retry: 0\n  Cop_time: 0\n  Process_time: 0\n  Wait_time: 0\n  Backoff_time: 0\n  LockKeys_time: 0\n  Request_count: 0\n  Total_keys: 0\n  Process_keys: 0\n  Rocksdb_delete_skipped_count: 1\n  Rocksdb_key_skipped_count: 1\n  Rocksdb_block_cache_hit_count: 1\n  Rocksdb_block_read_count: 1\n  Rocksdb_block_read_byte: 1\n  DB: test\n  Index_names: \"\"\n  Is_internal: 0\n  Digest: 911916530273cbddb6cee664f22d8f9d1ddf457ca0b6baeb28d3fad06b376a07\n  Stats: \"\"\n  Cop_proc_avg: 0\n  Cop_proc_p90: 0\n  Cop_proc_max: 0\n  Cop_proc_addr: \"\"\n  Cop_wait_avg: 0\n  Cop_wait_p90: 0\n  Cop_wait_max: 0\n  Cop_wait_addr: \"\"\n  Mem_max: 0\n  Disk_max: 0\n  KV_total: 0\n  PD_total: 8.677e-06\n  Backoff_total: 0\n  Write_sql_response_total: 0\n  Result_rows: 0\n  Backoff_Detail: \"\"\n  Prepared: 0\n  Succ: 1\n  IsExplicitTxn: 0\n  IsWriteCacheTable: 0\n  Plan_from_cache: 0\n  Plan_from_binding: 0\n  Plan: \"\"\n  Plan_digest: \"\"\n  Prev_stmt: \"\"\n  Query: SET tidb_slow_log_threshold = 0;\n- INSTANCE: \"\"\n  Warnings: null\n  Time: 2021-12-19T23:49:47.797894+08:00\n  Txn_start_ts: 0\n  User: root\n  Host: 127.0.0.1\n  Conn_ID: 9\n  Exec_retry_count: 0\n  Exec_retry_time: 0\n  Query_time: 0.000101329\n  Parse_time: 9.748e-06\n  Compile_time: 1.7342e-05\n  Rewrite_time: 4.298e-06\n  Preproc_subqueries: 0\n  Preproc_subqueries_time: 0\n  Optimize_time: 0\n  Wait_TS: 0\n  Prewrite_time: 0\n  Wait_prewrite_binlog_time: 0\n  Commit_time: 0\n  Get_commit_ts_time: 0\n  Commit_backoff_time: 0\n  Backoff_types: \"\"\n  Resolve_lock_time: 0\n  Local_latch_wait_time: 0\n  Write_keys: 0\n  Write_size: 0\n  Prewrite_region: 0\n  Txn_retry: 0\n  Cop_time: 0\n  Process_time: 0\n  Wait_time: 0\n  Backoff_time: 0\n  LockKeys_time: 0\n  Request_count: 0\n  Total_keys: 0\n  Process_keys: 0\n  Rocksdb_delete_skipped_count: 1\n  Rocksdb_key_skipped_count: 1\n  Rocksdb_block_cache_hit_count: 1\n  Rocksdb_block_read_count: 1\n  Rocksdb_block_read_byte: 1\n  DB: test\n  Index_names: \"\"\n  Is_internal: 0\n  Digest: a78efa0dfe9c9aeeace75326c12fa30cca3bdec7ae9449de21e1eefc7a46720b\n  Stats: \"\"\n  Cop_proc_avg: 0\n  Cop_proc_p90: 0\n  Cop_proc_max: 0\n  Cop_proc_addr: \"\"\n  Cop_wait_avg: 0\n  Cop_wait_p90: 0\n  Cop_wait_max: 0\n  Cop_wait_addr: \"\"\n  Mem_max: 0\n  Disk_max: 0\n  KV_total: 0\n  PD_total: 1.502e-06\n  Backoff_total: 0\n  Write_sql_response_total: 0\n  Result_rows: 0\n  Backoff_Detail: \"\"\n  Prepared: 0\n  Succ: 1\n  IsExplicitTxn: 0\n  IsWriteCacheTable: 0\n  Plan_from_cache: 0\n  Plan_from_binding: 0\n  Plan: \"\"\n  Plan_digest: \"\"\n  Prev_stmt: \"\"\n  Query: SET time_zone='+00:00';\n- INSTANCE: \"\"\n  Warnings: null\n  Time: 2021-12-19T23:49:47.798101+08:00\n  Txn_start_ts: 0\n  User: root\n  Host: 127.0.0.1\n  Conn_ID: 13\n  Exec_retry_count: 0\n  Exec_retry_time: 0\n  Query_time: 9.7543e-05\n  Parse_time: 1.1542e-05\n  Compile_time: 1.628e-05\n  Rewrite_time: 3.166e-06\n  Preproc_subqueries: 0\n  Preproc_subqueries_time: 0\n  Optimize_time: 0\n  Wait_TS: 0\n  Prewrite_time: 0\n  Wait_prewrite_binlog_time: 0\n  Commit_time: 0\n  Get_commit_ts_time: 0\n  Commit_backoff_time: 0\n  Backoff_types: \"\"\n  Resolve_lock_time: 0\n  Local_latch_wait_time: 0\n  Write_keys: 0\n  Write_size: 0\n  Prewrite_region: 0\n  Txn_retry: 0\n  Cop_time: 0\n  Process_time: 0\n  Wait_time: 0\n  Backoff_time: 0\n  LockKeys_time: 0\n  Request_count: 0\n  Total_keys: 0\n  Process_keys: 0\n  Rocksdb_delete_skipped_count: 1\n  Rocksdb_key_skipped_count: 1\n  Rocksdb_block_cache_hit_count: 1\n  Rocksdb_block_read_count: 1\n  Rocksdb_block_read_byte: 1\n  DB: test\n  Index_names: \"\"\n  Is_internal: 0\n  Digest: a78efa0dfe9c9aeeace75326c12fa30cca3bdec7ae9449de21e1eefc7a46720b\n  Stats: \"\"\n  Cop_proc_avg: 0\n  Cop_proc_p90: 0\n  Cop_proc_max: 0\n  Cop_proc_addr: \"\"\n  Cop_wait_avg: 0\n  Cop_wait_p90: 0\n  Cop_wait_max: 0\n  Cop_wait_addr: \"\"\n  Mem_max: 0\n  Disk_max: 0\n  KV_total: 0\n  PD_total: 1.533e-06\n  Backoff_total: 0\n  Write_sql_response_total: 0\n  Result_rows: 0\n  Backoff_Detail: \"\"\n  Prepared: 0\n  Succ: 1\n  IsExplicitTxn: 0\n  IsWriteCacheTable: 0\n  Plan_from_cache: 0\n  Plan_from_binding: 0\n  Plan: \"\"\n  Plan_digest: \"\"\n  Prev_stmt: \"\"\n  Query: SET time_zone='+00:00';\n- INSTANCE: \"\"\n  Warnings: null\n  Time: 2021-12-19T23:49:47.799045+08:00\n  Txn_start_ts: 0\n  User: root\n  Host: 127.0.0.1\n  Conn_ID: 11\n  Exec_retry_count: 0\n  Exec_retry_time: 0\n  Query_time: 0.000119424\n  Parse_time: 1.1973e-05\n  Compile_time: 2.1259e-05\n  Rewrite_time: 4.338e-06\n  Preproc_subqueries: 0\n  Preproc_subqueries_time: 0\n  Optimize_time: 0\n  Wait_TS: 0\n  Prewrite_time: 0\n  Wait_prewrite_binlog_time: 0\n  Commit_time: 0\n  Get_commit_ts_time: 0\n  Commit_backoff_time: 0\n  Backoff_types: \"\"\n  Resolve_lock_time: 0\n  Local_latch_wait_time: 0\n  Write_keys: 0\n  Write_size: 0\n  Prewrite_region: 0\n  Txn_retry: 0\n  Cop_time: 0\n  Process_time: 0\n  Wait_time: 0\n  Backoff_time: 0\n  LockKeys_time: 0\n  Request_count: 0\n  Total_keys: 0\n  Process_keys: 0\n  Rocksdb_delete_skipped_count: 1\n  Rocksdb_key_skipped_count: 1\n  Rocksdb_block_cache_hit_count: 1\n  Rocksdb_block_read_count: 1\n  Rocksdb_block_read_byte: 1\n  DB: test\n  Index_names: \"\"\n  Is_internal: 0\n  Digest: a78efa0dfe9c9aeeace75326c12fa30cca3bdec7ae9449de21e1eefc7a46720b\n  Stats: \"\"\n  Cop_proc_avg: 0\n  Cop_proc_p90: 0\n  Cop_proc_max: 0\n  Cop_proc_addr: \"\"\n  Cop_wait_avg: 0\n  Cop_wait_p90: 0\n  Cop_wait_max: 0\n  Cop_wait_addr: \"\"\n  Mem_max: 0\n  Disk_max: 0\n  KV_total: 0\n  PD_total: 1.282e-06\n  Backoff_total: 0\n  Write_sql_response_total: 0\n  Result_rows: 0\n  Backoff_Detail: \"\"\n  Prepared: 0\n  Succ: 1\n  IsExplicitTxn: 0\n  IsWriteCacheTable: 0\n  Plan_from_cache: 0\n  Plan_from_binding: 0\n  Plan: \"\"\n  Plan_digest: \"\"\n  Prev_stmt: \"\"\n  Query: SET time_zone='+00:00';\n- INSTANCE: \"\"\n  Warnings: null\n  Time: 2021-12-19T23:49:47.802016+08:00\n  Txn_start_ts: 429897544566046725\n  User: root\n  Host: 127.0.0.1\n  Conn_ID: 7\n  Exec_retry_count: 0\n  Exec_retry_time: 0\n  Query_time: 0.005136275\n  Parse_time: 4.252e-05\n  Compile_time: 0.001705726\n  Rewrite_time: 0.000206389\n  Preproc_subqueries: 0\n  Preproc_subqueries_time: 0\n  Optimize_time: 0.001440608\n  Wait_TS: 1.4577e-05\n  Prewrite_time: 0\n  Wait_prewrite_binlog_time: 0\n  Commit_time: 0\n  Get_commit_ts_time: 0\n  Commit_backoff_time: 0\n  Backoff_types: \"\"\n  Resolve_lock_time: 0\n  Local_latch_wait_time: 0\n  Write_keys: 0\n  Write_size: 0\n  Prewrite_region: 0\n  Txn_retry: 0\n  Cop_time: 0.002414267\n  Process_time: 0\n  Wait_time: 0\n  Backoff_time: 0\n  LockKeys_time: 0\n  Request_count: 1\n  Total_keys: 0\n  Process_keys: 0\n  Rocksdb_delete_skipped_count: 1\n  Rocksdb_key_skipped_count: 1\n  Rocksdb_block_cache_hit_count: 1\n  Rocksdb_block_read_count: 1\n  Rocksdb_block_read_byte: 1\n  DB: test\n  Index_names: \"\"\n  Is_internal: 0\n  Digest: 2375da6810d9c5a0d1c84875b1376bfd469ad952c1884f5dc1d6f36fc953b5df\n  Stats: CLUSTER_SLOW_QUERY:pseudo\n  Cop_proc_avg: 0\n  Cop_proc_p90: 0\n  Cop_proc_max: 0\n  Cop_proc_addr: 127.0.0.1:10080\n  Cop_wait_avg: 0\n  Cop_wait_p90: 0\n  Cop_wait_max: 0\n  Cop_wait_addr: 127.0.0.1:10080\n  Mem_max: 10670\n  Disk_max: 0\n  KV_total: 0.002446018\n  PD_total: 1.3976e-05\n  Backoff_total: 0\n  Write_sql_response_total: 5.11e-07\n  Result_rows: 1\n  Backoff_Detail: \"\"\n  Prepared: 0\n  Succ: 1\n  IsExplicitTxn: 0\n  IsWriteCacheTable: 0\n  Plan_from_cache: 0\n  Plan_from_binding: 0\n  Plan:\n    \"\\tid                 \\ttask     \\testRows\\toperator info                                           \\tactRows\\texecution\n    info                                                                                                                                                                                                                                                                                                                              \\tmemory\n    \\  \\tdisk\\n\\tHashAgg_5          \\troot     \\t1      \\tfuncs:count(1)->Column#73\n    \\                              \\t1      \\ttime:2.74ms, loops:2, partial_worker:{wall_time:2.714742ms,\n    concurrency:5, task_num:1, tot_wait:12.22074ms, tot_exec:2.325µs, tot_time:12.225638ms,\n    max:2.448372ms, p95:2.448372ms}, final_worker:{wall_time:0s, concurrency:5, task_num:1,\n    tot_wait:12.288149ms, tot_exec:14.748µs, tot_time:12.305432ms, max:2.469603ms,\n    p95:2.469603ms}\\t10.2 KB  \\tN/A\\n\\t└─TableReader_8    \\troot     \\t10000  \\tdata:TableFullScan_7\n    \\                                   \\t5      \\ttime:2.44ms, loops:2, cop_task:\n    {num: 1, max: 2.64ms, proc_keys: 0, rpc_num: 1, rpc_time: 2.63ms, copr_cache_hit_ratio:\n    0.00}                                                                                                                                                                                                               \\t190\n    Bytes\\tN/A\\n\\t  └─TableFullScan_7\\tcop[tidb]\\t10000  \\ttable:CLUSTER_SLOW_QUERY,\n    keep order:false, stats:pseudo\\t0      \\t                                                                                                                                                                                                                                                                                                                                            \\tN/A\n    \\     \\tN/A\"\n  Plan_digest: a5e33155313418557311d13039dbf20aa54df3b825d062bdca92f1a271e5778a\n  Prev_stmt: \"\"\n  Query: SELECT count(*) FROM INFORMATION_SCHEMA.CLUSTER_SLOW_QUERY;\n- INSTANCE: \"\"\n  Warnings: null\n  Time: 2021-12-19T23:49:47.802039+08:00\n  Txn_start_ts: 429897544566046730\n  User: root\n  Host: 127.0.0.1\n  Conn_ID: 9\n  Exec_retry_count: 0\n  Exec_retry_time: 0\n  Query_time: 0.003925498\n  Parse_time: 2.1069e-05\n  Compile_time: 0.000539934\n  Rewrite_time: 0.000118383\n  Preproc_subqueries: 0\n  Preproc_subqueries_time: 0\n  Optimize_time: 0.000369585\n  Wait_TS: 0.000127581\n  Prewrite_time: 0\n  Wait_prewrite_binlog_time: 0\n  Commit_time: 0\n  Get_commit_ts_time: 0\n  Commit_backoff_time: 0\n  Backoff_types: \"\"\n  Resolve_lock_time: 0\n  Local_latch_wait_time: 0\n  Write_keys: 0\n  Write_size: 0\n  Prewrite_region: 0\n  Txn_retry: 0\n  Cop_time: 0.002487686\n  Process_time: 0\n  Wait_time: 0\n  Backoff_time: 0\n  LockKeys_time: 0\n  Request_count: 1\n  Total_keys: 0\n  Process_keys: 0\n  Rocksdb_delete_skipped_count: 1\n  Rocksdb_key_skipped_count: 1\n  Rocksdb_block_cache_hit_count: 1\n  Rocksdb_block_read_count: 1\n  Rocksdb_block_read_byte: 1\n  DB: test\n  Index_names: \"\"\n  Is_internal: 0\n  Digest: 2375da6810d9c5a0d1c84875b1376bfd469ad952c1884f5dc1d6f36fc953b5df\n  Stats: CLUSTER_SLOW_QUERY:pseudo\n  Cop_proc_avg: 0\n  Cop_proc_p90: 0\n  Cop_proc_max: 0\n  Cop_proc_addr: 127.0.0.1:10080\n  Cop_wait_avg: 0\n  Cop_wait_p90: 0\n  Cop_wait_max: 0\n  Cop_wait_addr: 127.0.0.1:10080\n  Mem_max: 10670\n  Disk_max: 0\n  KV_total: 0.002366699\n  PD_total: 0.000122611\n  Backoff_total: 0\n  Write_sql_response_total: 1.002e-06\n  Result_rows: 1\n  Backoff_Detail: \"\"\n  Prepared: 0\n  Succ: 1\n  IsExplicitTxn: 0\n  IsWriteCacheTable: 0\n  Plan_from_cache: 0\n  Plan_from_binding: 0\n  Plan:\n    \"\\tid                 \\ttask     \\testRows\\toperator info                                           \\tactRows\\texecution\n    info                                                                                                                                                                                                                                                                                                                             \\tmemory\n    \\  \\tdisk\\n\\tHashAgg_5          \\troot     \\t1      \\tfuncs:count(1)->Column#73\n    \\                              \\t1      \\ttime:2.65ms, loops:2, partial_worker:{wall_time:2.62302ms,\n    concurrency:5, task_num:1, tot_wait:12.675366ms, tot_exec:3.887µs, tot_time:12.682497ms,\n    max:2.539994ms, p95:2.539994ms}, final_worker:{wall_time:0s, concurrency:5, task_num:1,\n    tot_wait:12.769694ms, tot_exec:17.533µs, tot_time:12.79023ms, max:2.568939ms,\n    p95:2.568939ms}\\t10.2 KB  \\tN/A\\n\\t└─TableReader_8    \\troot     \\t10000  \\tdata:TableFullScan_7\n    \\                                   \\t5      \\ttime:2.52ms, loops:2, cop_task:\n    {num: 1, max: 2.49ms, proc_keys: 0, rpc_num: 1, rpc_time: 2.48ms, copr_cache_hit_ratio:\n    0.00}                                                                                                                                                                                                              \\t190\n    Bytes\\tN/A\\n\\t  └─TableFullScan_7\\tcop[tidb]\\t10000  \\ttable:CLUSTER_SLOW_QUERY,\n    keep order:false, stats:pseudo\\t0      \\t                                                                                                                                                                                                                                                                                                                                           \\tN/A\n    \\     \\tN/A\"\n  Plan_digest: a5e33155313418557311d13039dbf20aa54df3b825d062bdca92f1a271e5778a\n  Prev_stmt: \"\"\n  Query: SELECT count(*) FROM INFORMATION_SCHEMA.CLUSTER_SLOW_QUERY;\n- INSTANCE: \"\"\n  Warnings: null\n  Time: 2021-12-19T23:49:47.802044+08:00\n  Txn_start_ts: 429897544566046731\n  User: root\n  Host: 127.0.0.1\n  Conn_ID: 13\n  Exec_retry_count: 0\n  Exec_retry_time: 0\n  Query_time: 0.003768796\n  Parse_time: 1.7675e-05\n  Compile_time: 0.00056407\n  Rewrite_time: 0.000102121\n  Preproc_subqueries: 0\n  Preproc_subqueries_time: 0\n  Optimize_time: 0.000430239\n  Wait_TS: 2.0288e-05\n  Prewrite_time: 0\n  Wait_prewrite_binlog_time: 0\n  Commit_time: 0\n  Get_commit_ts_time: 0\n  Commit_backoff_time: 0\n  Backoff_types: \"\"\n  Resolve_lock_time: 0\n  Local_latch_wait_time: 0\n  Write_keys: 0\n  Write_size: 0\n  Prewrite_region: 0\n  Txn_retry: 0\n  Cop_time: 0.002568317\n  Process_time: 0\n  Wait_time: 0\n  Backoff_time: 0\n  LockKeys_time: 0\n  Request_count: 1\n  Total_keys: 0\n  Process_keys: 0\n  Rocksdb_delete_skipped_count: 1\n  Rocksdb_key_skipped_count: 1\n  Rocksdb_block_cache_hit_count: 1\n  Rocksdb_block_read_count: 1\n  Rocksdb_block_read_byte: 1\n  DB: test\n  Index_names: \"\"\n  Is_internal: 0\n  Digest: 2375da6810d9c5a0d1c84875b1376bfd469ad952c1884f5dc1d6f36fc953b5df\n  Stats: CLUSTER_SLOW_QUERY:pseudo\n  Cop_proc_avg: 0\n  Cop_proc_p90: 0\n  Cop_proc_max: 0\n  Cop_proc_addr: 127.0.0.1:10080\n  Cop_wait_avg: 0\n  Cop_wait_p90: 0\n  Cop_wait_max: 0\n  Cop_wait_addr: 127.0.0.1:10080\n  Mem_max: 10670\n  Disk_max: 0\n  KV_total: 0.00214943\n  PD_total: 2.886e-06\n  Backoff_total: 0\n  Write_sql_response_total: 3.41e-07\n  Result_rows: 1\n  Backoff_Detail: \"\"\n  Prepared: 0\n  Succ: 1\n  IsExplicitTxn: 0\n  IsWriteCacheTable: 0\n  Plan_from_cache: 0\n  Plan_from_binding: 0\n  Plan:\n    \"\\tid                 \\ttask     \\testRows\\toperator info                                           \\tactRows\\texecution\n    info                                                                                                                                                                                                                                                                                                                               \\tmemory\n    \\  \\tdisk\\n\\tHashAgg_5          \\troot     \\t1      \\tfuncs:count(1)->Column#73\n    \\                              \\t1      \\ttime:2.77ms, loops:2, partial_worker:{wall_time:2.730291ms,\n    concurrency:5, task_num:1, tot_wait:12.990317ms, tot_exec:2.415µs, tot_time:12.996318ms,\n    max:2.601721ms, p95:2.601721ms}, final_worker:{wall_time:0s, concurrency:5, task_num:1,\n    tot_wait:13.109715ms, tot_exec:14.026µs, tot_time:13.126607ms, max:2.635896ms,\n    p95:2.635896ms}\\t10.2 KB  \\tN/A\\n\\t└─TableReader_8    \\troot     \\t10000  \\tdata:TableFullScan_7\n    \\                                   \\t5      \\ttime:2.59ms, loops:2, cop_task:\n    {num: 1, max: 2.58ms, proc_keys: 0, rpc_num: 1, rpc_time: 2.57ms, copr_cache_hit_ratio:\n    0.00}                                                                                                                                                                                                                \\t190\n    Bytes\\tN/A\\n\\t  └─TableFullScan_7\\tcop[tidb]\\t10000  \\ttable:CLUSTER_SLOW_QUERY,\n    keep order:false, stats:pseudo\\t0      \\t                                                                                                                                                                                                                                                                                                                                             \\tN/A\n    \\     \\tN/A\"\n  Plan_digest: a5e33155313418557311d13039dbf20aa54df3b825d062bdca92f1a271e5778a\n  Prev_stmt: \"\"\n  Query: SELECT count(*) FROM INFORMATION_SCHEMA.CLUSTER_SLOW_QUERY;\n- INSTANCE: \"\"\n  Warnings: null\n  Time: 2021-12-19T23:49:48.802373+08:00\n  Txn_start_ts: 429897544566046734\n  User: root\n  Host: 127.0.0.1\n  Conn_ID: 11\n  Exec_retry_count: 0\n  Exec_retry_time: 0\n  Query_time: 0.00292619\n  Parse_time: 2.2352e-05\n  Compile_time: 0.000229712\n  Rewrite_time: 0.000125655\n  Preproc_subqueries: 0\n  Preproc_subqueries_time: 0\n  Optimize_time: 6.339e-05\n  Wait_TS: 0.000181109\n  Prewrite_time: 0\n  Wait_prewrite_binlog_time: 0\n  Commit_time: 0\n  Get_commit_ts_time: 0\n  Commit_backoff_time: 0\n  Backoff_types: \"\"\n  Resolve_lock_time: 0\n  Local_latch_wait_time: 0\n  Write_keys: 0\n  Write_size: 0\n  Prewrite_region: 0\n  Txn_retry: 0\n  Cop_time: 0.001871728\n  Process_time: 0\n  Wait_time: 0\n  Backoff_time: 0\n  LockKeys_time: 0\n  Request_count: 1\n  Total_keys: 0\n  Process_keys: 0\n  Rocksdb_delete_skipped_count: 1\n  Rocksdb_key_skipped_count: 1\n  Rocksdb_block_cache_hit_count: 1\n  Rocksdb_block_read_count: 1\n  Rocksdb_block_read_byte: 1\n  DB: test\n  Index_names: \"\"\n  Is_internal: 0\n  Digest: 2375da6810d9c5a0d1c84875b1376bfd469ad952c1884f5dc1d6f36fc953b5df\n  Stats: CLUSTER_SLOW_QUERY:pseudo\n  Cop_proc_avg: 0\n  Cop_proc_p90: 0\n  Cop_proc_max: 0\n  Cop_proc_addr: 127.0.0.1:10080\n  Cop_wait_avg: 0\n  Cop_wait_p90: 0\n  Cop_wait_max: 0\n  Cop_wait_addr: 127.0.0.1:10080\n  Mem_max: 10670\n  Disk_max: 0\n  KV_total: 0.001912876\n  PD_total: 0.000176561\n  Backoff_total: 0\n  Write_sql_response_total: 7.01e-07\n  Result_rows: 1\n  Backoff_Detail: \"\"\n  Prepared: 0\n  Succ: 1\n  IsExplicitTxn: 0\n  IsWriteCacheTable: 0\n  Plan_from_cache: 0\n  Plan_from_binding: 0\n  Plan:\n    \"\\tid                 \\ttask     \\testRows\\toperator info                                           \\tactRows\\texecution\n    info                                                                                                                                                                                                                                                                                                                                   \\tmemory\n    \\  \\tdisk\\n\\tHashAgg_5          \\troot     \\t1      \\tfuncs:count(1)->Column#73\n    \\                              \\t1      \\ttime:2.03ms, loops:2, partial_worker:{wall_time:2.014637ms,\n    concurrency:5, task_num:1, tot_wait:9.451746ms, tot_exec:2.094µs, tot_time:9.456433ms,\n    max:1.900011ms, p95:1.900011ms}, final_worker:{wall_time:2.044704ms, concurrency:5,\n    task_num:1, tot_wait:9.422482ms, tot_exec:11.863µs, tot_time:9.437038ms, max:1.903117ms,\n    p95:1.903117ms}\\t10.2 KB  \\tN/A\\n\\t└─TableReader_8    \\troot     \\t10000  \\tdata:TableFullScan_7\n    \\                                   \\t5      \\ttime:1.89ms, loops:2, cop_task:\n    {num: 1, max: 1.92ms, proc_keys: 0, rpc_num: 1, rpc_time: 1.92ms, copr_cache_hit_ratio:\n    0.00}                                                                                                                                                                                                                    \\t190\n    Bytes\\tN/A\\n\\t  └─TableFullScan_7\\tcop[tidb]\\t10000  \\ttable:CLUSTER_SLOW_QUERY,\n    keep order:false, stats:pseudo\\t0      \\t                                                                                                                                                                                                                                                                                                                                                 \\tN/A\n    \\     \\tN/A\"\n  Plan_digest: a5e33155313418557311d13039dbf20aa54df3b825d062bdca92f1a271e5778a\n  Prev_stmt: \"\"\n  Query: SELECT count(*) FROM INFORMATION_SCHEMA.CLUSTER_SLOW_QUERY;\n"
  },
  {
    "path": "tests/integration/diagnose_report_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage integration\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/check\"\n)\n\nfunc TestT(t *testing.T) {\n\tcheck.CustomVerboseFlag = true\n\tcheck.TestingT(t)\n}\n\nvar _ = check.Suite(&testReportSuite{})\n\ntype testReportSuite struct{}\n\n// func (t *testReportSuite) TestReport(c *C) {\n//\tcli, err := gorm.Open(\"mysql\", \"root:@tcp(172.16.5.40:4009)/test?charset=utf8&parseTime=True&loc=Local\")\n//\tc.Assert(err, IsNil)\n//\tdefer cli.Close()\n//\n//\tstartTime := \"2020-03-03 17:18:00\"\n//\tendTime := \"2020-03-03 17:21:00\"\n//\n//\ttables := GetReportTablesForDisplay(startTime, endTime, cli)\n//\tfor _, tbl := range tables {\n//\t\tprintRows(tbl)\n//\t}\n// }\n\n// func (t *testReportSuite) TestGetTable(c *C) {\n// \tcli, err := gorm.Open(mysql.Open(\"root:@tcp(172.16.5.40:4009)/test?charset=utf8&parseTime=True&loc=Local\"))\n// \tc.Assert(err, IsNil)\n\n// \tstartTime := \"2020-03-25 23:00:00\"\n// \tendTime := \"2020-03-25 23:05:00\"\n\n// \tvar table diagnose.TableDef\n// \ttable, err = diagnose.GetLoadTable(startTime, endTime, cli)\n// \tc.Assert(err, IsNil)\n// \tprintRows(&table)\n// }\n\n// func (t *testReportSuite) TestGetCompareTable(c *C) {\n// \tcli, err := gorm.Open(mysql.Open(\"root:@tcp(172.16.5.40:4009)/test?charset=utf8&parseTime=True&loc=Local\"))\n// \tc.Assert(err, IsNil)\n\n// \t//startTime1 := \"2020-03-12 20:17:00\"\n// \t//endTime1 := \"2020-03-12 20:39:00\"\n// \t//\n// \t//startTime2 := \"2020-03-12 20:17:00\"\n// \t//endTime2 := \"2020-03-12 20:39:00\"\n\n// \tstartTime1 := \"2020-04-02 12:13:00\"\n// \tendTime1 := \"2020-04-02 12:15:00\"\n\n// \tstartTime2 := \"2020-04-02 12:15:00\"\n// \tendTime2 := \"2020-04-02 12:17:00\"\n\n// \ttables := diagnose.GetCompareReportTablesForDisplay(startTime1, endTime1, startTime2, endTime2, cli, nil, \"ID\")\n// \tfor _, tbl := range tables {\n// \t\tprintRows(tbl)\n// \t}\n// }\n\n// func (t *testReportSuite) TestInspection(c *C) {\n// \tcli, err := gorm.Open(mysql.Open(\"root:@tcp(172.16.5.40:4009)/test?charset=utf8&parseTime=True&loc=Local\"))\n// \tc.Assert(err, IsNil)\n\n// \t// affect by big query join\n// \tstartTime1 := \"2020-03-08 01:36:00\"\n// \tendTime1 := \"2020-03-08 01:41:00\"\n\n// \tstartTime2 := \"2020-03-08 01:46:30\"\n// \tendTime2 := \"2020-03-08 01:51:30\"\n\n// \t// affect by big write with conflict\n// \t//startTime1 := \"2020-03-10 12:35:00\"\n// \t//endTime1 := \"2020-03-10 12:39:00\"\n// \t//\n// \t//startTime2 := \"2020-03-10 12:41:00\"\n// \t//endTime2 := \"2020-03-10 12:45:00\"\n\n// \t// affect by big write without conflict\n// \t//startTime1 := \"\t2020-03-10 13:20:00\"\n// \t//endTime1 := \"\t2020-03-10 13:23:00\"\n// \t//\n// \t//startTime2 := \"2020-03-10 13:24:00\"\n// \t//endTime2 := \"2020-03-10 13:27:00\"\n\n// \t// diagnose for server down\n// \t// startTime1 := \"2020-03-09 20:35:00\"\n// \t// endTime1 := \"2020-03-09 21:20:00\"\n// \t// startTime2 := \"2020-03-08 20:35:00\"\n// \t// endTime2 := \"2020-03-09 21:20:00\"\n\n// \t// diagnose for disk slow , need more disk metric.\n// \t//startTime1 := \"2020-03-10 12:48:00\"\n// \t//endTime1 := \"2020-03-10 12:50:00\"\n// \t//\n// \t//startTime2 := \"2020-03-10 12:54:30\"\n// \t//endTime2 := \"2020-03-10 12:56:30\"\n\n// \ttable, errRow := diagnose.CompareDiagnose(startTime1, endTime1, startTime2, endTime2, cli)\n// \tc.Assert(errRow, IsNil)\n// \tprintRows(&table)\n// }\n\n// func printRows(t *diagnose.TableDef) {\n// \tif t == nil {\n// \t\tfmt.Println(\"table is nil\")\n// \t\treturn\n// \t}\n\n// \tfmt.Println(strings.Join(t.Category, \" - \"))\n// \tfmt.Println(t.Title)\n// \tfmt.Println(t.Comment)\n// \tif len(t.Rows) == 0 {\n// \t\tfmt.Println(\"table rows is 0\")\n// \t\treturn\n// \t}\n\n// \tfieldLen := t.ColumnWidth()\n// \t// fmt.Println(fieldLen)\n// \tprintLine := func(values []string, comment string) {\n// \t\tline := \"\"\n// \t\tfor i, s := range values {\n// \t\t\tfor k := len(s); k < fieldLen[i]; k++ {\n// \t\t\t\ts += \" \"\n// \t\t\t}\n// \t\t\tif i > 0 {\n// \t\t\t\tline += \"    |    \"\n// \t\t\t}\n// \t\t\tline += s\n// \t\t}\n// \t\tif len(comment) != 0 {\n// \t\t\tline = line + \"    |    \" + comment\n// \t\t}\n// \t\tfmt.Println(line)\n// \t}\n\n// \tprintLine(t.Column, \"\")\n\n// \tfor _, row := range t.Rows {\n// \t\tprintLine(row.Values, row.Comment)\n// \t\tfor i := range row.SubValues {\n// \t\t\tprintLine(row.SubValues[i], \"\")\n// \t\t}\n// \t}\n// \tfmt.Println(\"\")\n// }\n"
  },
  {
    "path": "tests/integration/info/info_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage info\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/suite\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/info\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user/code\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/tests/util\"\n\t\"github.com/pingcap/tidb-dashboard/util/testutil\"\n)\n\ntype testInfoSuite struct {\n\tsuite.Suite\n\tdb          *testutil.TestDB\n\tauthService *user.AuthService\n\tinfoService *info.Service\n\tcodeService *code.Service\n}\n\nfunc TestInfoSuite(t *testing.T) {\n\tdb := testutil.OpenTestDB(t)\n\ttidbVersion := util.GetTiDBVersion(t, db)\n\n\tauthService := &user.AuthService{}\n\tinfoService := &info.Service{}\n\tcodeService := &code.Service{}\n\n\tapp := util.NewMockApp(t,\n\t\ttidbVersion,\n\t\tconfig.Default(),\n\t\tfx.Populate(&authService),\n\t\tfx.Populate(&infoService),\n\t\tfx.Populate(&codeService),\n\t)\n\tapp.RequireStart()\n\n\tsuite.Run(t, &testInfoSuite{\n\t\tdb:          db,\n\t\tauthService: authService,\n\t\tinfoService: infoService,\n\t\tcodeService: codeService,\n\t})\n\n\tapp.RequireStop()\n}\n\nfunc (s *testInfoSuite) TestWithNotLoginUser() {\n\treq, _ := http.NewRequest(http.MethodGet, \"/info/whoami\", nil)\n\tc, w := util.TestReqWithHandlers(req, s.authService.MWAuthRequired(), s.infoService.WhoamiHandler)\n\n\ts.Require().Contains(c.Errors.Last().Err.Error(), \"common.unauthenticated\")\n\ts.Require().Equal(401, w.Code)\n}\n\nfunc (s *testInfoSuite) TestWithSQLLoginUser() {\n\ttoken := s.getTokenBySQLRoot()\n\tres := s.requestWhoami(token)\n\ts.Require().Equal(res.DisplayName, \"root\")\n\ts.Require().Equal(res.IsWriteable, true)\n\ts.Require().Equal(res.IsShareable, true)\n}\n\nfunc (s *testInfoSuite) TestWithShareCodeLoginUser() {\n\trootUserToken := s.getTokenBySQLRoot()\n\tshareCode := s.shareCode(rootUserToken, false)\n\n\tshareCodeUserToken := s.getTokenByShareCode(shareCode)\n\tres := s.requestWhoami(shareCodeUserToken)\n\ts.Require().Equal(res.DisplayName, \"Shared from root\")\n\ts.Require().Equal(res.IsWriteable, false)\n\ts.Require().Equal(res.IsShareable, false)\n}\n\nfunc (s *testInfoSuite) TestWithShareCodeAndWritePrivLoginUser() {\n\trootUserToken := s.getTokenBySQLRoot()\n\tshareCode := s.shareCode(rootUserToken, true)\n\n\tshareCodeUserToken := s.getTokenByShareCode(shareCode)\n\tres := s.requestWhoami(shareCodeUserToken)\n\ts.Require().Equal(res.DisplayName, \"Shared from root\")\n\ts.Require().Equal(res.IsWriteable, true)\n\ts.Require().Equal(res.IsShareable, false)\n}\n\nfunc (s *testInfoSuite) getTokenBySQLRoot() string {\n\tparam := make(map[string]interface{})\n\tparam[\"type\"] = 0\n\tparam[\"username\"] = \"root\"\n\tpwd, _ := user.Encrypt(\"\", s.authService.RsaPublicKey)\n\tparam[\"password\"] = pwd\n\n\tjsonByte, _ := json.Marshal(param)\n\treq, _ := http.NewRequest(http.MethodPost, \"/user/login\", bytes.NewReader(jsonByte))\n\tc, w := util.TestReqWithHandlers(req, s.authService.LoginHandler)\n\n\ts.Require().Len(c.Errors, 0)\n\ts.Require().Equal(200, w.Code)\n\n\tres := struct {\n\t\tToken string\n\t}{}\n\terr := json.Unmarshal(w.Body.Bytes(), &res)\n\ts.Require().Nil(err)\n\n\treturn res.Token\n}\n\nfunc (s *testInfoSuite) getTokenByShareCode(shareCode string) string {\n\tparam := make(map[string]interface{})\n\tparam[\"type\"] = 1\n\tparam[\"password\"] = shareCode\n\n\tjsonByte, _ := json.Marshal(param)\n\treq, _ := http.NewRequest(http.MethodPost, \"/user/login\", bytes.NewReader(jsonByte))\n\tc, w := util.TestReqWithHandlers(req, s.authService.LoginHandler)\n\n\ts.Require().Len(c.Errors, 0)\n\ts.Require().Equal(200, w.Code)\n\n\tres := struct {\n\t\tToken string\n\t}{}\n\terr := json.Unmarshal(w.Body.Bytes(), &res)\n\ts.Require().Nil(err)\n\n\treturn res.Token\n}\n\nfunc (s *testInfoSuite) shareCode(token string, grantWritePriv bool) string {\n\t// request /user/share/code\n\tparam := make(map[string]interface{})\n\tparam[\"expire_in_sec\"] = 10800\n\tparam[\"revoke_write_priv\"] = !grantWritePriv\n\n\tjsonByte, _ := json.Marshal(param)\n\treq, _ := http.NewRequest(http.MethodPost, \"/user/share/code\", bytes.NewReader(jsonByte))\n\treq.Header.Add(\"Authorization\", \"Bearer \"+token)\n\tc, w := util.TestReqWithHandlers(req, s.authService.MWAuthRequired(), s.codeService.ShareHandler)\n\n\ts.Require().Len(c.Errors, 0)\n\ts.Require().Equal(200, w.Code)\n\n\tres := struct {\n\t\tCode string\n\t}{}\n\terr := json.Unmarshal(w.Body.Bytes(), &res)\n\ts.Require().Nil(err)\n\n\treturn res.Code\n}\n\nfunc (s *testInfoSuite) requestWhoami(token string) info.WhoAmIResponse {\n\treq, _ := http.NewRequest(http.MethodPost, \"/info/whoami\", nil)\n\treq.Header.Add(\"Authorization\", \"Bearer \"+token)\n\t_, w := util.TestReqWithHandlers(req, s.authService.MWAuthRequired(), s.infoService.WhoamiHandler)\n\n\ts.Require().Equal(200, w.Code)\n\n\tres := info.WhoAmIResponse{}\n\terr := json.Unmarshal(w.Body.Bytes(), &res)\n\ts.Require().Nil(err)\n\n\treturn res\n}\n"
  },
  {
    "path": "tests/integration/slowquery/compatibility_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage slowquery\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/suite\"\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/slowquery\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils\"\n\t\"github.com/pingcap/tidb-dashboard/tests/util\"\n\t\"github.com/pingcap/tidb-dashboard/util/testutil\"\n)\n\ntype testCompatibilitySuite struct {\n\tsuite.Suite\n\tdb        *testutil.TestDB\n\tsysSchema *utils.SysSchema\n}\n\nfunc TestCompatibilitySuite(t *testing.T) {\n\tdb := testutil.OpenTestDB(t)\n\tsysSchema := utils.NewSysSchema()\n\n\tsuite.Run(t, &testCompatibilitySuite{\n\t\tdb:        db,\n\t\tsysSchema: sysSchema,\n\t})\n}\n\nfunc (s *testCompatibilitySuite) SetupSuite() {\n\ts.db.MustExec(\"SET tidb_slow_log_threshold = 0\")\n\tvar wg sync.WaitGroup\n\tfor i := 1; i < 5; i++ {\n\t\twg.Go(func() {\n\t\t\ts.db.MustExec(fmt.Sprintf(\"SELECT count(*) FROM %s\", slowquery.SlowQueryTable))\n\t\t})\n\t}\n\twg.Wait()\n\ts.db.MustExec(\"SET tidb_slow_log_threshold = 300\")\n\n\tutil.LoadFixtures(s.T(), s.db, \"../../fixtures\")\n}\n\nfunc (s *testCompatibilitySuite) TearDownSuite() {\n\ts.db.MustClose()\n\t_ = s.sysSchema.Close()\n}\n\nfunc (s *testCompatibilitySuite) dbSession() *gorm.DB {\n\treturn s.db.Gorm().Debug().Table(slowquery.SlowQueryTable)\n}\n\nfunc (s *testCompatibilitySuite) mockDBSession() *gorm.DB {\n\treturn s.db.Gorm().Debug().Table(TestSlowQueryTableName)\n}\n\nfunc (s *testCompatibilitySuite) mustQuerySlowLogListWithMockDB(req *slowquery.GetListRequest) []slowquery.Model {\n\td, err := slowquery.QuerySlowLogList(req, s.sysSchema, s.mockDBSession())\n\ts.Require().NoError(err)\n\treturn d\n}\n\nfunc (s *testCompatibilitySuite) TestFieldsCompatibility() {\n\tif util.CheckTiDBVersion(s.Require(), \"< 5.0.0\") {\n\t\tds := s.mustQuerySlowLogListWithMockDB(&slowquery.GetListRequest{Digest: \"TEST_ALL_FIELDS\", Fields: \"*\"})\n\t\ts.Require().Len(ds, 1)\n\t\td := ds[0]\n\t\ts.Require().Empty(d.DiskMax)\n\t\ts.Require().Empty(d.ExecRetryTime)\n\t\ts.Require().Empty(d.OptimizeTime)\n\t\ts.Require().Empty(d.PreprocSubqueriesTime)\n\t\ts.Require().Empty(d.RewriteTime)\n\t\ts.Require().Empty(d.WaitTSTime)\n\t\ts.Require().Empty(d.WriteRespTime)\n\t\ts.Require().Empty(d.RocksdbBlockCacheHitCount)\n\t\ts.Require().Empty(d.RocksdbBlockReadByte)\n\t\ts.Require().Empty(d.RocksdbBlockReadCount)\n\t\ts.Require().Empty(d.RocksdbDeleteSkippedCount)\n\t\ts.Require().Empty(d.RocksdbKeySkippedCount)\n\t}\n\n\tif util.CheckTiDBVersion(s.Require(), \">= 5.0.0\") {\n\t\tds := s.mustQuerySlowLogListWithMockDB(&slowquery.GetListRequest{Digest: \"TEST_ALL_FIELDS\", Fields: \"*\"})\n\t\ts.Require().Len(ds, 1)\n\t\td := ds[0]\n\t\ts.Require().NotEmpty(d.DiskMax)\n\t\ts.Require().NotEmpty(d.ExecRetryTime)\n\t\ts.Require().NotEmpty(d.OptimizeTime)\n\t\ts.Require().NotEmpty(d.PreprocSubqueriesTime)\n\t\ts.Require().NotEmpty(d.RewriteTime)\n\t\ts.Require().NotEmpty(d.WaitTSTime)\n\t\ts.Require().NotEmpty(d.WriteRespTime)\n\t\ts.Require().NotEmpty(d.RocksdbBlockCacheHitCount)\n\t\ts.Require().NotEmpty(d.RocksdbBlockReadByte)\n\t\ts.Require().NotEmpty(d.RocksdbBlockReadCount)\n\t\ts.Require().NotEmpty(d.RocksdbDeleteSkippedCount)\n\t\ts.Require().NotEmpty(d.RocksdbKeySkippedCount)\n\t}\n}\n\nfunc (s *testCompatibilitySuite) TestQueryTableColumns() {\n\tif util.CheckTiDBVersion(s.Require(), \"< 5.0.0\") {\n\t\tcls, err := slowquery.GetAvailableFields(s.sysSchema, s.dbSession())\n\t\ts.Require().NoError(err)\n\t\ts.Require().NotContains(cls, \"rocksdb_delete_skipped_count\")\n\t\ts.Require().NotContains(cls, \"rocksdb_key_skipped_count\")\n\t\ts.Require().NotContains(cls, \"rocksdb_block_cache_hit_count\")\n\t\ts.Require().NotContains(cls, \"rocksdb_block_read_count\")\n\t\ts.Require().NotContains(cls, \"rocksdb_block_read_byte\")\n\t}\n\n\tif util.CheckTiDBVersion(s.Require(), \">= 5.0.0\") {\n\t\tcls, err := slowquery.GetAvailableFields(s.sysSchema, s.dbSession())\n\t\ts.Require().NoError(err)\n\t\ts.Require().Contains(cls, \"rocksdb_delete_skipped_count\")\n\t\ts.Require().Contains(cls, \"rocksdb_key_skipped_count\")\n\t\ts.Require().Contains(cls, \"rocksdb_block_cache_hit_count\")\n\t\ts.Require().Contains(cls, \"rocksdb_block_read_count\")\n\t\ts.Require().Contains(cls, \"rocksdb_block_read_byte\")\n\t}\n}\n\nfunc (s *testCompatibilitySuite) TestAllAvailableFields() {\n\t// TODO: use latest instead of specific versions\n\tif util.CheckTiDBVersion(s.Require(), \">= 5.0.0\") {\n\t\tcls, err := slowquery.GetAvailableFields(s.sysSchema, s.dbSession())\n\t\ts.Require().NoError(err)\n\t\ts.Require().Contains(cls, \"digest\")\n\t\ts.Require().Contains(cls, \"query\")\n\t\ts.Require().Contains(cls, \"instance\")\n\t\ts.Require().Contains(cls, \"db\")\n\t\ts.Require().Contains(cls, \"connection_id\")\n\t\ts.Require().Contains(cls, \"success\")\n\t\ts.Require().Contains(cls, \"timestamp\")\n\t\ts.Require().Contains(cls, \"query_time\")\n\t\ts.Require().Contains(cls, \"parse_time\")\n\t\ts.Require().Contains(cls, \"compile_time\")\n\t\ts.Require().Contains(cls, \"rewrite_time\")\n\t\ts.Require().Contains(cls, \"preproc_subqueries_time\")\n\t\ts.Require().Contains(cls, \"optimize_time\")\n\t\ts.Require().Contains(cls, \"wait_ts\")\n\t\ts.Require().Contains(cls, \"cop_time\")\n\t\ts.Require().Contains(cls, \"lock_keys_time\")\n\t\ts.Require().Contains(cls, \"write_sql_response_total\")\n\t\ts.Require().Contains(cls, \"exec_retry_time\")\n\t\ts.Require().Contains(cls, \"memory_max\")\n\t\ts.Require().Contains(cls, \"disk_max\")\n\t\ts.Require().Contains(cls, \"txn_start_ts\")\n\t\ts.Require().Contains(cls, \"prev_stmt\")\n\t\ts.Require().Contains(cls, \"plan\")\n\t\ts.Require().Contains(cls, \"is_internal\")\n\t\ts.Require().Contains(cls, \"index_names\")\n\t\ts.Require().Contains(cls, \"stats\")\n\t\ts.Require().Contains(cls, \"backoff_types\")\n\t\ts.Require().Contains(cls, \"user\")\n\t\ts.Require().Contains(cls, \"host\")\n\t\ts.Require().Contains(cls, \"process_time\")\n\t\ts.Require().Contains(cls, \"wait_time\")\n\t\ts.Require().Contains(cls, \"backoff_time\")\n\t\ts.Require().Contains(cls, \"get_commit_ts_time\")\n\t\ts.Require().Contains(cls, \"local_latch_wait_time\")\n\t\ts.Require().Contains(cls, \"resolve_lock_time\")\n\t\ts.Require().Contains(cls, \"prewrite_time\")\n\t\ts.Require().Contains(cls, \"wait_prewrite_binlog_time\")\n\t\ts.Require().Contains(cls, \"commit_time\")\n\t\ts.Require().Contains(cls, \"commit_backoff_time\")\n\t\ts.Require().Contains(cls, \"cop_proc_avg\")\n\t\ts.Require().Contains(cls, \"cop_proc_p90\")\n\t\ts.Require().Contains(cls, \"cop_proc_max\")\n\t\ts.Require().Contains(cls, \"cop_wait_avg\")\n\t\ts.Require().Contains(cls, \"cop_wait_p90\")\n\t\ts.Require().Contains(cls, \"cop_wait_max\")\n\t\ts.Require().Contains(cls, \"write_keys\")\n\t\ts.Require().Contains(cls, \"write_size\")\n\t\ts.Require().Contains(cls, \"prewrite_region\")\n\t\ts.Require().Contains(cls, \"txn_retry\")\n\t\ts.Require().Contains(cls, \"request_count\")\n\t\ts.Require().Contains(cls, \"process_keys\")\n\t\ts.Require().Contains(cls, \"total_keys\")\n\t\ts.Require().Contains(cls, \"cop_proc_addr\")\n\t\ts.Require().Contains(cls, \"cop_wait_addr\")\n\t\ts.Require().Contains(cls, \"rocksdb_delete_skipped_count\")\n\t\ts.Require().Contains(cls, \"rocksdb_key_skipped_count\")\n\t\ts.Require().Contains(cls, \"rocksdb_block_cache_hit_count\")\n\t\ts.Require().Contains(cls, \"rocksdb_block_read_count\")\n\t\ts.Require().Contains(cls, \"rocksdb_block_read_byte\")\n\t}\n}\n"
  },
  {
    "path": "tests/integration/slowquery/mock_db_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage slowquery\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/suite\"\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/slowquery\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/utils\"\n\t\"github.com/pingcap/tidb-dashboard/tests/util\"\n\t\"github.com/pingcap/tidb-dashboard/util/testutil\"\n)\n\nconst (\n\tTestSlowQueryTableName = \"test.CLUSTER_SLOW_QUERY\"\n)\n\ntype testMockDBSuite struct {\n\tsuite.Suite\n\tdb        *testutil.TestDB\n\tsysSchema *utils.SysSchema\n}\n\nfunc TestMockDBSuite(t *testing.T) {\n\tdb := testutil.OpenTestDB(t)\n\tsysSchema := utils.NewSysSchema()\n\n\tsuite.Run(t, &testMockDBSuite{\n\t\tdb:        db,\n\t\tsysSchema: sysSchema,\n\t})\n}\n\nfunc (s *testMockDBSuite) SetupSuite() {\n\tutil.LoadFixtures(s.T(), s.db, \"../../fixtures\")\n}\n\nfunc (s *testMockDBSuite) TearDownSuite() {\n\ts.db.MustExec(fmt.Sprintf(\"DROP TABLE IF EXISTS `%s`\", TestSlowQueryTableName))\n\ts.db.MustClose()\n\t_ = s.sysSchema.Close()\n}\n\nfunc (s *testMockDBSuite) mustQuerySlowLogList(req *slowquery.GetListRequest) []slowquery.Model {\n\td, err := slowquery.QuerySlowLogList(req, s.sysSchema, s.mockDBSession())\n\ts.Require().NoError(err)\n\treturn d\n}\n\nfunc (s *testMockDBSuite) mustQuerySlowLogDetail(req *slowquery.GetDetailRequest) (*slowquery.Model, error) {\n\td, err := slowquery.QuerySlowLogDetail(req, s.sysSchema, s.mockDBSession())\n\treturn d, err\n}\n\nfunc (s *testMockDBSuite) mockDBSession() *gorm.DB {\n\treturn s.db.Gorm().Debug().Table(TestSlowQueryTableName)\n}\n\nfunc (s *testMockDBSuite) TestGetListDefaultRequest() {\n\tds := s.mustQuerySlowLogList(&slowquery.GetListRequest{})\n\n\ts.Require().Len(ds, 9)\n\n\tfor i, d := range ds {\n\t\ts.Require().NotEmpty(d.Digest)\n\t\ts.Require().NotEmpty(d.ConnectionID)\n\t\ts.Require().NotEmpty(d.Timestamp)\n\n\t\t// order by timestamp\n\t\tif i == 0 {\n\t\t\tcontinue\n\t\t}\n\t\ts.Require().GreaterOrEqual(d.Timestamp, ds[i-1].Timestamp)\n\t}\n}\n\nfunc (s *testMockDBSuite) TestGetListSpecificFieldsRequest() {\n\tds := s.mustQuerySlowLogList(&slowquery.GetListRequest{Fields: \"digest,query\"})\n\n\tfor _, d := range ds {\n\t\ts.Require().NotEmpty(d.Digest)\n\t\ts.Require().NotEmpty(d.ConnectionID)\n\t\ts.Require().NotEmpty(d.Timestamp)\n\t\ts.Require().NotEmpty(d.Query)\n\t}\n}\n\n// Contains fields available for all tidb versions, other fields will be tested in compatibility_test.go.\nfunc (s *testMockDBSuite) TestGetListAllFieldsRequest() {\n\tds := s.mustQuerySlowLogList(&slowquery.GetListRequest{Digest: \"TEST_ALL_FIELDS\", Fields: \"*\"})\n\ts.Require().Len(ds, 1)\n\n\td := ds[0]\n\ts.Require().NotEmpty(d.BackoffTime)\n\ts.Require().NotEmpty(d.BackoffTypes)\n\ts.Require().NotEmpty(d.CommitBackoffTime)\n\ts.Require().NotEmpty(d.CommitTime)\n\ts.Require().NotEmpty(d.CompileTime)\n\ts.Require().NotEmpty(d.ConnectionID)\n\ts.Require().NotEmpty(d.CopProcAddr)\n\ts.Require().NotEmpty(d.CopProcAvg)\n\ts.Require().NotEmpty(d.CopProcMax)\n\ts.Require().NotEmpty(d.CopProcP90)\n\ts.Require().NotEmpty(d.CopTime)\n\ts.Require().NotEmpty(d.CopWaitAddr)\n\ts.Require().NotEmpty(d.CopWaitAvg)\n\ts.Require().NotEmpty(d.CopWaitMax)\n\ts.Require().NotEmpty(d.CopWaitP90)\n\ts.Require().NotEmpty(d.DB)\n\ts.Require().NotEmpty(d.Digest)\n\ts.Require().NotEmpty(d.GetCommitTSTime)\n\ts.Require().NotEmpty(d.Host)\n\ts.Require().NotEmpty(d.IndexNames)\n\ts.Require().NotEmpty(d.Instance)\n\ts.Require().NotEmpty(d.IsInternal)\n\ts.Require().NotEmpty(d.LocalLatchWaitTime)\n\ts.Require().NotEmpty(d.LockKeysTime)\n\ts.Require().NotEmpty(d.MemoryMax)\n\ts.Require().NotEmpty(d.ParseTime)\n\ts.Require().NotEmpty(d.Plan)\n\ts.Require().NotEmpty(d.PrevStmt)\n\ts.Require().NotEmpty(d.PrewriteRegion)\n\ts.Require().NotEmpty(d.PrewriteTime)\n\ts.Require().NotEmpty(d.ProcessKeys)\n\ts.Require().NotEmpty(d.ProcessTime)\n\ts.Require().NotEmpty(d.Query)\n\ts.Require().NotEmpty(d.QueryTime)\n\ts.Require().NotEmpty(d.RequestCount)\n\ts.Require().NotEmpty(d.Stats)\n\ts.Require().NotEmpty(d.Success)\n\ts.Require().NotEmpty(d.Timestamp)\n\ts.Require().NotEmpty(d.TotalKeys)\n\ts.Require().NotEmpty(d.TxnRetry)\n\ts.Require().NotEmpty(d.User)\n\ts.Require().NotEmpty(d.WaitPreWriteBinlogTime)\n\ts.Require().NotEmpty(d.WaitTime)\n\ts.Require().NotEmpty(d.WriteKeys)\n\ts.Require().NotEmpty(d.WriteSize)\n}\n\nfunc (s *testMockDBSuite) TestGetListTimeRangeRequest() {\n\t// 1639928730 - 2021-12-19T23:45:30+08:00\n\t// 1639928987 - 2021-12-19T23:49:48+08:00\n\tds := s.mustQuerySlowLogList(&slowquery.GetListRequest{BeginTime: 1639928730, EndTime: 1639928988})\n\n\ts.Require().Len(ds, 8)\n}\n\nfunc (s *testMockDBSuite) TestGetListLimitRequest() {\n\tds := s.mustQuerySlowLogList(&slowquery.GetListRequest{Limit: 5})\n\n\ts.Require().Len(ds, 5)\n}\n\nfunc (s *testMockDBSuite) TestGetListSearchRequest() {\n\tdigest := \"2375da6810d9c5a0d1c84875b1376bfd469ad952c1884f5dc1d6f36fc953b5df\"\n\tds := s.mustQuerySlowLogList(&slowquery.GetListRequest{Fields: \"digest\", Text: digest})\n\ts.Require().Len(ds, 4)\n\tfor _, d := range ds {\n\t\ts.Require().Contains(d.Digest, digest)\n\t}\n\n\ttxnStartTS := \"429897544566046725\"\n\tds2 := s.mustQuerySlowLogList(&slowquery.GetListRequest{Fields: \"txn_start_ts\", Text: txnStartTS})\n\ts.Require().Len(ds2, 1)\n\ts.Require().Contains(ds2[0].TxnStartTS, txnStartTS)\n\n\tquery := \"INFORMATION_SCHEMA.CLUSTER_SLOW_QUERY\"\n\tds3 := s.mustQuerySlowLogList(&slowquery.GetListRequest{Fields: \"query\", Text: query})\n\ts.Require().Len(ds3, 4)\n\tfor _, d := range ds3 {\n\t\ts.Require().Contains(d.Query, query)\n\t}\n\n\tprevStmt := \"test prev stmt\"\n\tds4 := s.mustQuerySlowLogList(&slowquery.GetListRequest{Fields: \"prev_stmt\", Text: prevStmt})\n\ts.Require().Len(ds4, 1)\n\ts.Require().Contains(ds4[0].PrevStmt, prevStmt)\n}\n\nfunc (s *testMockDBSuite) TestGetListMultiKeywordsSearchRequest() {\n\tdigest := \"2375da6810d9c5a0d1c84875b1376bfd469ad952c1884f5dc1d6f36fc953b5df\"\n\ttxnStartTS := \"429897544566046725\"\n\tds := s.mustQuerySlowLogList(&slowquery.GetListRequest{Fields: \"digest,txn_start_ts\", Text: fmt.Sprintf(\"%s %s\", digest, txnStartTS)})\n\n\ts.Require().Len(ds, 1)\n\ts.Require().Contains(ds[0].Digest, digest)\n\ts.Require().Contains(ds[0].TxnStartTS, txnStartTS)\n}\n\nfunc (s *testMockDBSuite) TestGetListUseDBRequest() {\n\tds := s.mustQuerySlowLogList(&slowquery.GetListRequest{DB: []string{\"test\"}})\n\ts.Require().NotEmpty(ds)\n\n\tds2 := s.mustQuerySlowLogList(&slowquery.GetListRequest{DB: []string{\"not_exist_db\"}})\n\ts.Require().Empty(ds2)\n}\n\nfunc (s *testMockDBSuite) TestGetListOrderRequest() {\n\tds := s.mustQuerySlowLogList(&slowquery.GetListRequest{OrderBy: \"txn_start_ts\"})\n\tfor i, d := range ds {\n\t\tif i == 0 {\n\t\t\tcontinue\n\t\t}\n\t\ts.Require().GreaterOrEqual(d.TxnStartTS, ds[i-1].TxnStartTS)\n\t}\n\n\tds2 := s.mustQuerySlowLogList(&slowquery.GetListRequest{IsDesc: true, OrderBy: \"txn_start_ts\"})\n\tfor i, d := range ds2 {\n\t\tif i == 0 {\n\t\t\tcontinue\n\t\t}\n\t\ts.Require().LessOrEqual(d.TxnStartTS, ds[i-1].TxnStartTS)\n\t}\n}\n\nfunc (s *testMockDBSuite) TestGetListPlansRequest() {\n\tds := s.mustQuerySlowLogList(&slowquery.GetListRequest{Plans: []string{\"a5e33155313418557311d13039dbf20aa54df3b825d062bdca92f1a271e5778a\"}})\n\ts.Require().NotEmpty(ds)\n\n\tds2 := s.mustQuerySlowLogList(&slowquery.GetListRequest{Plans: []string{\"not_exist_plan\"}})\n\ts.Require().Empty(ds2)\n}\n\nfunc (s *testMockDBSuite) TestGetListAllRequest() {\n\tdigest := \"2375da6810d9c5a0d1c84875b1376bfd469ad952c1884f5dc1d6f36fc953b5df\"\n\ttxnStartTS := \"429897544566046725\"\n\tds := s.mustQuerySlowLogList(&slowquery.GetListRequest{\n\t\tBeginTime: 1639928730,\n\t\tEndTime:   1639928988,\n\t\tLimit:     5,\n\t\tIsDesc:    true,\n\t\tOrderBy:   \"txn_start_ts\",\n\t\tDB:        []string{\"test\"},\n\t\tFields:    \"digest,txn_start_ts\",\n\t\tText:      fmt.Sprintf(\"%s %s\", digest, txnStartTS),\n\t})\n\n\ts.Require().NotEmpty(ds)\n\ts.Require().LessOrEqual(len(ds), 5)\n\ts.Require().Contains(ds[0].Digest, digest)\n\ts.Require().Contains(ds[0].TxnStartTS, txnStartTS)\n\n\tds2 := s.mustQuerySlowLogList(&slowquery.GetListRequest{\n\t\tBeginTime: 1639928730,\n\t\tEndTime:   1639928988,\n\t\tLimit:     5,\n\t\tIsDesc:    true,\n\t\tOrderBy:   \"timestamp\",\n\t\tDB:        []string{},\n\t\tFields:    \"query,timestamp,query_time,memory_max,digest\",\n\t\tDigest:    digest,\n\t\tPlans:     []string{\"a5e33155313418557311d13039dbf20aa54df3b825d062bdca92f1a271e5778a\"},\n\t})\n\n\ts.Require().NotEmpty(ds2)\n\ts.Require().LessOrEqual(len(ds2), 5)\n\ts.Require().Contains(ds2[0].Digest, digest)\n}\n\nfunc (s *testMockDBSuite) TestGetDetailRequest() {\n\tds, err := s.mustQuerySlowLogDetail(&slowquery.GetDetailRequest{\n\t\tDigest:    \"2375da6810d9c5a0d1c84875b1376bfd469ad952c1884f5dc1d6f36fc953b5df\",\n\t\tTimestamp: 1639928730,\n\t\tConnectID: \"0\",\n\t})\n\ts.Require().Error(err)\n\ts.Require().Nil(ds)\n\ts.Require().Contains(err.Error(), \"record not found\")\n\n\tds2, err := s.mustQuerySlowLogDetail(&slowquery.GetDetailRequest{\n\t\tDigest:    \"2375da6810d9c5a0d1c84875b1376bfd469ad952c1884f5dc1d6f36fc953b5df\",\n\t\tTimestamp: 1639928987.802016,\n\t\tConnectID: \"7\",\n\t})\n\ts.Require().Nil(err)\n\ts.Require().NotNil(ds2)\n\ts.Require().Equal(ds2.Timestamp, 1639928987.802016)\n\ts.Require().Equal(ds2.Digest, \"2375da6810d9c5a0d1c84875b1376bfd469ad952c1884f5dc1d6f36fc953b5df\")\n\ts.Require().Equal(ds2.ConnectionID, \"7\")\n}\n"
  },
  {
    "path": "tests/integration/user/user_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage user\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/stretchr/testify/suite\"\n\t\"go.uber.org/fx\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/info\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver/user\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/tidb\"\n\t\"github.com/pingcap/tidb-dashboard/tests/util\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n\t\"github.com/pingcap/tidb-dashboard/util/testutil\"\n)\n\ntype testUserSuite struct {\n\tsuite.Suite\n\tdb          *testutil.TestDB\n\tauthService *user.AuthService\n\tinfoService *info.Service\n}\n\nfunc TestUserSuite(t *testing.T) {\n\tdb := testutil.OpenTestDB(t)\n\ttidbVersion := util.GetTiDBVersion(t, db)\n\n\tauthService := &user.AuthService{}\n\tinfoService := &info.Service{}\n\n\tapp := util.NewMockApp(t,\n\t\ttidbVersion,\n\t\tconfig.Default(),\n\t\tfx.Populate(&authService),\n\t\tfx.Populate(&infoService),\n\t)\n\tapp.RequireStart()\n\n\tsuite.Run(t, &testUserSuite{\n\t\tdb:          db,\n\t\tauthService: authService,\n\t\tinfoService: infoService,\n\t})\n\n\tapp.RequireStop()\n}\n\nfunc (s *testUserSuite) supportNonRootLogin() bool {\n\treturn s.authService.FeatureFlagNonRootLogin.IsSupported()\n}\n\nfunc (s *testUserSuite) SetupSuite() {\n\t// drop user if exist\n\ts.db.MustExec(\"DROP USER IF EXISTS 'dashboardAdmin'@'%'\")\n\ts.db.MustExec(\"DROP USER IF EXISTS 'dashboardAdmin-2'@'%'\")\n\n\t// create user 1 with sufficient priviledges\n\ts.db.MustExec(\"CREATE USER 'dashboardAdmin'@'%' IDENTIFIED BY '12345678'\")\n\ts.db.MustExec(\"GRANT PROCESS, CONFIG ON *.* TO 'dashboardAdmin'@'%'\")\n\ts.db.MustExec(\"GRANT SHOW DATABASES ON *.* TO 'dashboardAdmin'@'%'\")\n\tif s.supportNonRootLogin() {\n\t\ts.db.MustExec(\"GRANT DASHBOARD_CLIENT ON *.* TO 'dashboardAdmin'@'%'\")\n\t}\n\n\t// create user 2 with insufficient priviledges\n\ts.db.MustExec(\"CREATE USER 'dashboardAdmin-2'@'%' IDENTIFIED BY '12345678'\")\n\ts.db.MustExec(\"GRANT PROCESS, CONFIG ON *.* TO 'dashboardAdmin-2'@'%'\")\n\ts.db.MustExec(\"GRANT SHOW DATABASES ON *.* TO 'dashboardAdmin-2'@'%'\")\n}\n\nfunc (s *testUserSuite) TearDownSuite() {\n\ts.db.MustExec(\"DROP USER IF EXISTS 'dashboardAdmin'@'%'\")\n\ts.db.MustExec(\"DROP USER IF EXISTS 'dashboardAdmin-2'@'%'\")\n}\n\nfunc (s *testUserSuite) TestLoginWithEmpty() {\n\treq, _ := http.NewRequest(http.MethodPost, \"/user/login\", nil)\n\tc, w := util.TestReqWithHandlers(req, s.authService.LoginHandler)\n\n\ts.Require().True(errorx.IsOfType(c.Errors.Last().Err, rest.ErrBadRequest))\n\ts.Require().Equal(401, c.Writer.Status())\n\ts.Require().Equal(401, w.Code)\n}\n\nfunc (s *testUserSuite) TestLoginWithNotExistUser() {\n\tparam := make(map[string]interface{})\n\tparam[\"type\"] = 0\n\tparam[\"username\"] = \"not_exist\"\n\tpwd, _ := user.Encrypt(\"aaa\", s.authService.RsaPublicKey)\n\tparam[\"password\"] = pwd\n\n\tjsonByte, _ := json.Marshal(param)\n\treq, _ := http.NewRequest(http.MethodPost, \"/user/login\", bytes.NewReader(jsonByte))\n\tc, w := util.TestReqWithHandlers(req, s.authService.LoginHandler)\n\n\ts.Require().Contains(c.Errors.Last().Err.Error(), \"authenticate failed\")\n\ts.Require().True(errorx.IsOfType(c.Errors.Last().Err, tidb.ErrTiDBAuthFailed))\n\ts.Require().Equal(401, c.Writer.Status())\n\ts.Require().Equal(401, w.Code)\n}\n\nfunc (s *testUserSuite) TestLoginWithWrongPassword() {\n\tparam := make(map[string]interface{})\n\tparam[\"type\"] = 0\n\tparam[\"username\"] = \"dashboardAdmin\"\n\tpwd, _ := user.Encrypt(\"123456789\", s.authService.RsaPublicKey)\n\tparam[\"password\"] = pwd\n\n\tjsonByte, _ := json.Marshal(param)\n\treq, _ := http.NewRequest(http.MethodPost, \"/user/login\", bytes.NewReader(jsonByte))\n\tc, w := util.TestReqWithHandlers(req, s.authService.LoginHandler)\n\n\ts.Require().Contains(c.Errors.Last().Err.Error(), \"authenticate failed\")\n\ts.Require().True(errorx.IsOfType(c.Errors.Last().Err, tidb.ErrTiDBAuthFailed))\n\ts.Require().Equal(401, c.Writer.Status())\n\ts.Require().Equal(401, w.Code)\n}\n\nfunc (s *testUserSuite) TestLoginWithInsufficientPrivs() {\n\tparam := make(map[string]interface{})\n\tparam[\"type\"] = 0\n\tparam[\"username\"] = \"dashboardAdmin-2\"\n\tpwd, _ := user.Encrypt(\"12345678\", s.authService.RsaPublicKey)\n\tparam[\"password\"] = pwd\n\n\tjsonByte, _ := json.Marshal(param)\n\treq, _ := http.NewRequest(http.MethodPost, \"/user/login\", bytes.NewReader(jsonByte))\n\tc, w := util.TestReqWithHandlers(req, s.authService.LoginHandler)\n\n\ts.Require().Contains(c.Errors.Last().Err.Error(), \"authenticate failed\")\n\ts.Require().True(errorx.IsOfType(c.Errors.Last().Err, user.ErrInsufficientPrivs))\n\ts.Require().Equal(401, c.Writer.Status())\n\ts.Require().Equal(401, w.Code)\n}\n\nfunc (s *testUserSuite) TestLoginWithSufficientPrivs() {\n\tif s.supportNonRootLogin() {\n\t\tparam := make(map[string]interface{})\n\t\tparam[\"type\"] = 0\n\t\tparam[\"username\"] = \"dashboardAdmin\"\n\t\tpwd, _ := user.Encrypt(\"12345678\", s.authService.RsaPublicKey)\n\t\tparam[\"password\"] = pwd\n\n\t\tjsonByte, _ := json.Marshal(param)\n\t\treq, _ := http.NewRequest(http.MethodPost, \"/user/login\", bytes.NewReader(jsonByte))\n\t\tc, w := util.TestReqWithHandlers(req, s.authService.LoginHandler)\n\n\t\ts.Require().Len(c.Errors, 0)\n\t\ts.Require().Equal(200, c.Writer.Status())\n\t\ts.Require().Equal(200, w.Code)\n\n\t\tres := struct {\n\t\t\tToken string\n\t\t}{}\n\t\terr := json.Unmarshal(w.Body.Bytes(), &res)\n\t\ts.Require().Nil(err)\n\n\t\t// request /info/whoami by the token\n\t\treq2, _ := http.NewRequest(http.MethodPost, \"/info/whoami\", nil)\n\t\treq2.Header.Add(\"Authorization\", \"Bearer \"+res.Token)\n\t\tc2, w2 := util.TestReqWithHandlers(req2, s.authService.MWAuthRequired(), s.infoService.WhoamiHandler)\n\n\t\ts.Require().Equal(200, c2.Writer.Status())\n\t\ts.Require().Equal(200, w2.Code)\n\n\t\tres2 := info.WhoAmIResponse{}\n\t\terr2 := json.Unmarshal(w2.Body.Bytes(), &res2)\n\t\ts.Require().Nil(err2)\n\t\ts.Require().Equal(res2.DisplayName, \"dashboardAdmin\")\n\t}\n}\n\nfunc (s *testUserSuite) TestLoginWithWrongPasswordForRoot() {\n\tparam := make(map[string]interface{})\n\tparam[\"type\"] = 0\n\tparam[\"username\"] = \"root\"\n\tpwd, _ := user.Encrypt(\"aaa\", s.authService.RsaPublicKey)\n\tparam[\"password\"] = pwd\n\n\tjsonByte, _ := json.Marshal(param)\n\treq, _ := http.NewRequest(http.MethodPost, \"/user/login\", bytes.NewReader(jsonByte))\n\tc, w := util.TestReqWithHandlers(req, s.authService.LoginHandler)\n\n\ts.Require().Contains(c.Errors.Last().Err.Error(), \"authenticate failed\")\n\ts.Require().True(errorx.IsOfType(c.Errors.Last().Err, tidb.ErrTiDBAuthFailed))\n\ts.Require().Equal(401, c.Writer.Status())\n\ts.Require().Equal(401, w.Code)\n}\n\nfunc (s *testUserSuite) TestLoginWithCorrectPasswordForRoot() {\n\tparam := make(map[string]interface{})\n\tparam[\"type\"] = 0\n\tparam[\"username\"] = \"root\"\n\tpwd, _ := user.Encrypt(\"\", s.authService.RsaPublicKey)\n\tparam[\"password\"] = pwd\n\n\tjsonByte, _ := json.Marshal(param)\n\treq, _ := http.NewRequest(http.MethodPost, \"/user/login\", bytes.NewReader(jsonByte))\n\tc, w := util.TestReqWithHandlers(req, s.authService.LoginHandler)\n\n\ts.Require().Len(c.Errors, 0)\n\ts.Require().Equal(200, c.Writer.Status())\n\ts.Require().Equal(200, w.Code)\n\n\tres := struct {\n\t\tToken string\n\t}{}\n\terr := json.Unmarshal(w.Body.Bytes(), &res)\n\ts.Require().Nil(err)\n}\n\n// TODO: uncomment it after thinking clearly\n// func (s *testUserSuite) TestLoginWithSamePayloadTwice() {\n// \tparam := make(map[string]interface{})\n// \tparam[\"type\"] = 0\n// \tparam[\"username\"] = \"root\"\n// \tpwd, _ := user.Encrypt(\"\", s.authService.RsaPublicKey)\n// \tparam[\"password\"] = pwd\n\n// \t// success at the first time\n// \tjsonByte, _ := json.Marshal(param)\n// \treq, _ := http.NewRequest(http.MethodPost, \"/user/login\", bytes.NewReader(jsonByte))\n// \tc, w := util.TestReqWithHandlers(req, s.authService.LoginHandler)\n\n// \ts.Require().Len(c.Errors, 0)\n// \ts.Require().Equal(200, c.Writer.Status())\n// \ts.Require().Equal(200, w.Code)\n\n// \t// fail at the second time\n// \treq, _ = http.NewRequest(http.MethodPost, \"/user/login\", bytes.NewReader(jsonByte))\n// \tc, w = util.TestReqWithHandlers(req, s.authService.LoginHandler)\n\n// \ts.Require().Contains(c.Errors.Last().Err.Error(), \"authenticate failed\")\n// \ts.Require().Contains(c.Errors.Last().Err.Error(), \"crypto/rsa: decryption error\")\n// \ts.Require().Equal(401, c.Writer.Status())\n// \ts.Require().Equal(401, w.Code)\n// }\n\nfunc (s *testUserSuite) TestLoginInfo() {\n\treq, _ := http.NewRequest(http.MethodGet, \"/user/login_info\", nil)\n\tc, w := util.TestReqWithHandlers(req, s.authService.GetLoginInfoHandler)\n\n\ts.Require().Len(c.Errors, 0)\n\ts.Require().Equal(200, c.Writer.Status())\n\ts.Require().Equal(200, w.Code)\n\n\tres := user.GetLoginInfoResponse{}\n\terr := json.Unmarshal(w.Body.Bytes(), &res)\n\ts.Require().Nil(err)\n\n\t// SSO is not enabled default, so only returns []int{0, 1}\n\ts.Require().Equal([]int{0, 1}, res.SupportedAuthTypes)\n}\n"
  },
  {
    "path": "tests/run.sh",
    "content": "#!/usr/bin/env bash\n\n# Available flags:\n# COVER_PKG\n#   Pass to coverpkg flag, apply coverage analysis in each test to packages matching the patterns.\n#   default: ./pkg/...\n# TIDB_VERSION\n#   Run tests with the specified tidb version\n#   default: latest\n\n# See code coverage html\n# go tool cover -html ./coverage/integration_$TIDB_VERSION.txt\n\nset -euo pipefail\n\nPROJECT_DIR=\"$(dirname \"$0\")/..\"\n\nsource scripts/_inc/download_tools.sh >/dev/null\nsource scripts/_inc/run_services.sh >/dev/null\n\ndownload_tools\n\ntrap stop_tidb EXIT\nstart_tidb ${TIDB_VERSION:=latest}\n\n$PROJECT_DIR/tests/create_table.sh\n\nPRECISE_TIDB_VERSION=$(mysql --host 127.0.0.1 --port 4000 -u root -se \"SELECT VERSION()\" | sed -r \"s/.*TiDB-(v[0-9]+\\.[0-9]+\\.[0-9]+).*/\\1/g\")\n\necho \"+ Run integration tests on tidb $PRECISE_TIDB_VERSION\"\nTIDB_VERSION=$PRECISE_TIDB_VERSION go test -race -v -cover \\\n-coverprofile=coverage/integration_${TIDB_VERSION}.txt \\\n-coverpkg=${COVER_PKG:-./pkg/...} \\\n./tests/integration/...\n\necho \"  - All tests passed!\"\n"
  },
  {
    "path": "tests/schema/test.CLUSTER_SLOW_QUERY-schema.sql",
    "content": "/*!40101 SET NAMES binary*/;\n/*T![placement] SET PLACEMENT_CHECKS = 0*/;\nCREATE TABLE `CLUSTER_SLOW_QUERY` (`INSTANCE` varchar(64) DEFAULT NULL, `Time` timestamp(6) NOT NULL, `Txn_start_ts` bigint(20) unsigned DEFAULT NULL, `User` varchar(64) DEFAULT NULL, `Host` varchar(64) DEFAULT NULL, `Conn_ID` bigint(20) unsigned DEFAULT NULL, `Session_alias` varchar(64) DEFAULT NULL, `Exec_retry_count` bigint(20) unsigned DEFAULT NULL, `Exec_retry_time` double DEFAULT NULL, `Query_time` double DEFAULT NULL, `Parse_time` double DEFAULT NULL, `Compile_time` double DEFAULT NULL, `Rewrite_time` double DEFAULT NULL, `Preproc_subqueries` bigint(20) unsigned DEFAULT NULL, `Preproc_subqueries_time` double DEFAULT NULL, `Optimize_time` double DEFAULT NULL, `Wait_TS` double DEFAULT NULL, `Prewrite_time` double DEFAULT NULL, `Wait_prewrite_binlog_time` double DEFAULT NULL, `Commit_time` double DEFAULT NULL, `Get_commit_ts_time` double DEFAULT NULL, `Commit_backoff_time` double DEFAULT NULL, `Backoff_types` varchar(64) DEFAULT NULL, `Resolve_lock_time` double DEFAULT NULL, `Local_latch_wait_time` double DEFAULT NULL, `Write_keys` bigint(22) DEFAULT NULL, `Write_size` bigint(22) DEFAULT NULL, `Prewrite_region` bigint(22) DEFAULT NULL, `Txn_retry` bigint(22) DEFAULT NULL, `Cop_time` double DEFAULT NULL, `Process_time` double DEFAULT NULL, `Wait_time` double DEFAULT NULL, `Backoff_time` double DEFAULT NULL, `LockKeys_time` double DEFAULT NULL, `Request_count` bigint(20) unsigned DEFAULT NULL, `Total_keys` bigint(20) unsigned DEFAULT NULL, `Process_keys` bigint(20) unsigned DEFAULT NULL, `Rocksdb_delete_skipped_count` bigint(20) unsigned DEFAULT NULL, `Rocksdb_key_skipped_count` bigint(20) unsigned DEFAULT NULL, `Rocksdb_block_cache_hit_count` bigint(20) unsigned DEFAULT NULL, `Rocksdb_block_read_count` bigint(20) unsigned DEFAULT NULL, `Rocksdb_block_read_byte` bigint(20) unsigned DEFAULT NULL, `DB` varchar(64) DEFAULT NULL, `Index_names` varchar(100) DEFAULT NULL, `Is_internal` tinyint(1) DEFAULT NULL, `Digest` varchar(64) DEFAULT NULL, `Stats` varchar(512) DEFAULT NULL, `Cop_proc_avg` double DEFAULT NULL, `Cop_proc_p90` double DEFAULT NULL, `Cop_proc_max` double DEFAULT NULL, `Cop_proc_addr` varchar(64) DEFAULT NULL, `Cop_wait_avg` double DEFAULT NULL, `Cop_wait_p90` double DEFAULT NULL, `Cop_wait_max` double DEFAULT NULL, `Cop_wait_addr` varchar(64) DEFAULT NULL, `Mem_max` bigint(20) DEFAULT NULL, `Mem_arbitration` double DEFAULT NULL, `Disk_max` bigint(20) DEFAULT NULL, `KV_total` double DEFAULT NULL, `PD_total` double DEFAULT NULL, `Backoff_total` double DEFAULT NULL, `Write_sql_response_total` double DEFAULT NULL, `Result_rows` bigint(22) DEFAULT NULL, `Warnings` longtext DEFAULT NULL, `Backoff_Detail` varchar(4096) DEFAULT NULL, `Prepared` tinyint(1) DEFAULT NULL, `Succ` tinyint(1) DEFAULT NULL, `IsExplicitTxn` tinyint(1) DEFAULT NULL, `IsWriteCacheTable` tinyint(1) DEFAULT NULL, `Plan_from_cache` tinyint(1) DEFAULT NULL, `Plan_from_binding` tinyint(1) DEFAULT NULL, `Has_more_results` tinyint(1) DEFAULT NULL, `Resource_group` varchar(64) DEFAULT NULL, `Request_unit_read` double DEFAULT NULL, `Request_unit_write` double DEFAULT NULL, `Time_queued_by_rc` double DEFAULT NULL, `Unpacked_bytes_sent_tikv_total` bigint DEFAULT NULL, `Unpacked_bytes_received_tikv_total` bigint DEFAULT NULL, `Unpacked_bytes_sent_tikv_cross_zone` bigint DEFAULT NULL, `Unpacked_bytes_received_tikv_cross_zone` bigint DEFAULT NULL, `Unpacked_bytes_sent_tiflash_total` bigint DEFAULT NULL, `Unpacked_bytes_received_tiflash_total` bigint DEFAULT NULL, `Unpacked_bytes_sent_tiflash_cross_zone` bigint DEFAULT NULL, `Unpacked_bytes_received_tiflash_cross_zone` bigint DEFAULT NULL, `Plan` longtext DEFAULT NULL, `Plan_digest` varchar(128) DEFAULT NULL, `Binary_plan` longtext DEFAULT NULL, `Prev_stmt` longtext DEFAULT NULL, `Query` longtext DEFAULT NULL, PRIMARY KEY (`Time`) /*T![clustered_index] CLUSTERED */ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;"
  },
  {
    "path": "tests/util/compatibility.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage util\n\nimport (\n\t\"os\"\n\n\t\"github.com/Masterminds/semver\"\n\t\"github.com/stretchr/testify/require\"\n)\n\n// CheckTiDBVersion tests if tidb version satisfies the constraints.\n// Constraint examples: \"~5.2.2\", \">= 5.3.0\", see github.com/Masterminds/semver to get more information.\nfunc CheckTiDBVersion(r *require.Assertions, constraint string) bool {\n\ttidbVersion := os.Getenv(\"TIDB_VERSION\")\n\tif tidbVersion == \"\" {\n\t\treturn false\n\t}\n\tc, err := semver.NewConstraint(constraint)\n\tr.NoError(err)\n\tv, err := semver.NewVersion(tidbVersion)\n\tr.NoError(err)\n\treturn c.Check(v)\n}\n"
  },
  {
    "path": "tests/util/dump/dump.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/shhdgit/testfixtures/v3\"\n\t\"gorm.io/driver/mysql\"\n\t\"gorm.io/gorm\"\n)\n\nfunc main() {\n\tif len(os.Args) < 1 {\n\t\tlog.Fatal(\"Require 1 arg db.table\")\n\t}\n\tdbTable := os.Args[1]\n\ts := strings.Split(dbTable, \".\")\n\tdbName := s[0]\n\ttableName := s[1]\n\n\tgormDB, err := gorm.Open(mysql.Open(fmt.Sprintf(\"root:@tcp(127.0.0.1:4000)/%s?charset=utf8&parseTime=True&loc=Local\", dbName)))\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdb, err := gormDB.DB()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tdumper, err := testfixtures.NewDumper(\n\t\ttestfixtures.DumpDatabase(db),\n\t\ttestfixtures.DumpDialect(\"tidb\"),\n\t\ttestfixtures.DumpDirectory(\"tests/fixtures\"),\n\t\ttestfixtures.DumpTables(\n\t\t\ttableName,\n\t\t),\n\t)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tif err := dumper.Dump(); err != nil {\n\t\tpanic(err)\n\t}\n}\n"
  },
  {
    "path": "tests/util/fixtures.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage util\n\nimport (\n\t\"testing\"\n\n\t\"github.com/shhdgit/testfixtures/v3\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil\"\n)\n\nfunc LoadFixtures(t *testing.T, testDB *testutil.TestDB, dir string) {\n\tr := require.New(t)\n\n\tdb, err := testDB.Gorm().DB()\n\tr.NoError(err)\n\n\tfixtures, err := testfixtures.New(\n\t\ttestfixtures.Database(db),\n\t\ttestfixtures.Dialect(\"tidb\"),\n\t\ttestfixtures.Directory(dir),\n\t)\n\tr.NoError(err)\n\n\terr = fixtures.Load()\n\tr.NoError(err)\n}\n"
  },
  {
    "path": "tests/util/gin_test_helper.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage util\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc TestReqWithHandlers(req *http.Request, handlers ...gin.HandlerFunc) (*gin.Context, *httptest.ResponseRecorder) {\n\tw := httptest.NewRecorder()\n\tc, e := gin.CreateTestContext(w)\n\tc.Request = req\n\te.Handle(req.Method, req.URL.Path, handlers...)\n\te.HandleContext(c)\n\treturn c, w\n}\n"
  },
  {
    "path": "tests/util/mock_app.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage util\n\nimport (\n\t\"context\"\n\t\"time\"\n\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/fx/fxtest\"\n\n\t\"github.com/pingcap/tidb-dashboard/pkg/apiserver\"\n\t\"github.com/pingcap/tidb-dashboard/pkg/config\"\n\t\"github.com/pingcap/tidb-dashboard/util/featureflag\"\n)\n\ntype App struct {\n\t*fxtest.App\n\n\ttb fxtest.TB\n}\n\nfunc NewMockApp(tb fxtest.TB, tidbVersion string, c *config.Config, opts ...fx.Option) *App {\n\tallOpts := make([]fx.Option, 0, len(opts)+1)\n\tallOpts = append(allOpts,\n\t\tapiserver.Modules,\n\t\tfx.Supply(featureflag.NewRegistry(tidbVersion)),\n\t\tfx.Supply(c),\n\t)\n\tallOpts = append(allOpts, opts...)\n\n\tapp := fxtest.New(tb, allOpts...)\n\n\treturn &App{\n\t\tApp: app,\n\t\ttb:  tb,\n\t}\n}\n\n// RequireStart calls Start, failing the test if an error is encountered.\n// It also sleep 5 seconds to wait for the server to start.\nfunc (app *App) RequireStart() *App {\n\tif err := app.Start(context.Background()); err != nil {\n\t\tapp.tb.Errorf(\"application didn't start cleanly: %v\", err)\n\t\tapp.tb.FailNow()\n\t}\n\ttime.Sleep(5 * time.Second)\n\treturn app\n}\n\n// RequireStop calls Stop, failing the test if an error is encountered.\nfunc (app *App) RequireStop() {\n\tif err := app.Stop(context.Background()); err != nil {\n\t\tapp.tb.Errorf(\"application didn't stop cleanly: %v\", err)\n\t\tapp.tb.FailNow()\n\t}\n}\n"
  },
  {
    "path": "tests/util/tidb_version.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage util\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil\"\n)\n\nfunc GetTiDBVersion(t *testing.T, testDB *testutil.TestDB) string {\n\tr := require.New(t)\n\n\t// get tidb version\n\ttype versionSchemas struct {\n\t\tVersion string `gorm:\"column:version\"`\n\t}\n\tvar result []versionSchemas\n\terr := testDB.Gorm().Raw(\"select version() as version\").Scan(&result).Error\n\t// output example:\n\t// +--------------------+\n\t// | version            |\n\t// +--------------------+\n\t// | 5.7.25-TiDB-v5.3.0 |\n\t// +--------------------+\n\tr.Nil(err)\n\tr.Len(result, 1)\n\tver := strings.Split(result[0].Version, \"-TiDB-\")\n\tr.Len(ver, 2)\n\treturn ver[1]\n}\n"
  },
  {
    "path": "ui/.editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": "ui/.eslintrc.js",
    "content": "module.exports = {\n  extends: ['react-app'],\n  rules: {\n    'react/react-in-jsx-scope': 'error',\n    'jsx-a11y/anchor-is-valid': 'off',\n    'jsx-a11y/alt-text': 'off',\n    'import/no-anonymous-default-export': 'off',\n    'react/jsx-no-target-blank': 'off'\n  }\n}\n"
  },
  {
    "path": "ui/.gitignore",
    "content": "node_modules\ndist\nbuild\n\n.eslintcache\n\n# testing\ncoverage\ncypress/downloads\ncypress/videos\ncypress/screeshots\ncypress/integration/1-getting-started\ncypress/integration/2-advanced-examples\n.nyc_output\n*.log\n\n.env.local\n"
  },
  {
    "path": "ui/.husky/pre-commit",
    "content": "#!/bin/sh\n\ncd ui\npnpm lint-staged\n"
  },
  {
    "path": "ui/.prettierignore",
    "content": "packages/clinic-client/src\npackages/clinic-client/swagger/spec.json\npackages/clinic-client/dist\n\npackages/tidb-dashboard-client/src\npackages/tidb-dashboard-client/swagger/spec.json\npackages/tidb-dashboard-client/dist\n\npackages/tidb-dashboard-lib/src/client/models.ts\npackages/tidb-dashboard-lib/dist\n\npackages/tidb-dashboard-for-op/src/utils/distro/strings_res.json\npackages/tidb-dashboard-for-op/public/speedscope\npackages/tidb-dashboard-for-op/dist\npackages/tidb-dashboard-for-op/.nyc_output\npackages/tidb-dashboard-for-op/coverage\n\npnpm-lock.yaml\n"
  },
  {
    "path": "ui/.prettierrc",
    "content": "{\n  \"semi\": false,\n  \"trailingComma\": \"none\",\n  \"singleQuote\": true\n}\n"
  },
  {
    "path": "ui/OWNERS",
    "content": "# See the OWNERS docs at https://go.k8s.io/owners\nlabels:\n  - area/frontend\n"
  },
  {
    "path": "ui/README.md",
    "content": "# TiDB Dashboard UI\n\n## Arch\n\n![ui arch](./ui_arch.png)\n\n## Requirements\n\n- Node >= 22.0.0\n- [use corepack](https://www.totaltypescript.com/how-to-use-corepack): `corepack enable && corepack enable npm`\n\n## Run\n\n### Dev\n\n1. `pnpm i`\n1. `pnpm dev`\n\n> Note:\n>\n> You can run `pnpm dev:op`, `pnpm dev:clinic-op`, `pnpm dev:clinic-cloud` only to start a specific dashboard variant, while `pnpm dev` starts all of them.\n>\n> Copy `.env.development` to `.env.local` in the variant folder to override the environment variables, and add `TARGET_VARIANT_DASHBOARD_PATH` to the `.env.local` file. e.g. `cp .env.development .env.local` in `packages/tidb-dashboard-for-clinic-cloud` to override the environment variables for clinic-cloud.\n>\n> Before starting `pnpm dev:clinic-op` and `pnpm dev:clinic-cloud`, you need to start clinic ui.\n\n### Build\n\n1. `pnpm i`\n1. `pnpm build`\n"
  },
  {
    "path": "ui/go.mod",
    "content": "module ignore_ui // a hack to ignore this directory in go commands\n\ngo 1.13\n"
  },
  {
    "path": "ui/less-vars.js",
    "content": "const lessModifyVars = {\n  '@primary-color': '#0ca6f2',\n  '@body-background': '#fff',\n  '@tooltip-bg': 'rgba(0, 0, 0, 0.9)',\n  '@tooltip-max-width': '500px'\n}\nconst lessGlobalVars = {\n  '@padding-page': '48px',\n  '@gray-1': '#fff',\n  '@gray-2': '#fafafa',\n  '@gray-3': '#f5f5f5',\n  '@gray-4': '#f0f0f0',\n  '@gray-5': '#d9d9d9',\n  '@gray-6': '#bfbfbf',\n  '@gray-7': '#8c8c8c',\n  '@gray-8': '#595959',\n  '@gray-9': '#262626',\n  '@gray-10': '#000'\n}\n\nmodule.exports = {\n  lessModifyVars,\n  lessGlobalVars\n}\n"
  },
  {
    "path": "ui/netlify.toml",
    "content": "[[headers]]\n  # Define which paths this specific [[headers]] block will cover.\n  for = \"/*\"\n    [headers.values]\n    Access-Control-Allow-Origin = \"*\"\n"
  },
  {
    "path": "ui/package.json",
    "content": "{\n  \"name\": \"tidb-dashboard-ui\",\n  \"private\": \"true\",\n  \"version\": \"1.0.0\",\n  \"license\": \"MIT\",\n  \"engines\": {\n    \"node\": \">=22.0.0\"\n  },\n  \"packageManager\": \"pnpm@8.6.12\",\n  \"scripts\": {\n    \"fmt-check\": \"prettier --check .\",\n    \"fmt-fix\": \"prettier --write .\",\n    \"prepare\": \"cd .. && husky install ui/.husky\",\n    \"build:lib\": \"pnpm -r --filter @pingcap/tidb-dashboard-lib build\",\n    \"dev\": \"pnpm build:lib && pnpm -r --parallel dev\",\n    \"dev:op\": \"pnpm build:lib && pnpm -r --parallel --filter @pingcap/tidb-dashboard-for-op... dev\",\n    \"dev:clinic-op\": \"pnpm build:lib && pnpm -r --parallel --filter @pingcap/tidb-dashboard-for-clinic-op... dev\",\n    \"dev:clinic-cloud\": \"pnpm build:lib && pnpm -r --parallel --filter @pingcap/tidb-dashboard-for-clinic-cloud... dev\",\n    \"dev:watch_api\": \"WATCH_API=1 pnpm dev\",\n    \"build\": \"pnpm -r build\"\n  },\n  \"devDependencies\": {\n    \"@typescript-eslint/eslint-plugin\": \"^4.31.0\",\n    \"@typescript-eslint/parser\": \"^4.31.0\",\n    \"eslint\": \"^8.9.0\",\n    \"eslint-config-react-app\": \"^7.0.0\",\n    \"husky\": \"^8.0.0\",\n    \"lint-staged\": \"^11.1.2\",\n    \"prettier\": \"^2.4.0\"\n  },\n  \"pnpm\": {\n    \"overrides\": {\n      \"@babel/helpers\": \"7.26.10\",\n      \"@babel/runtime\": \"7.26.10\",\n      \"@babel/traverse\": \"7.23.2\",\n      \"braces@3.0.2\": \"3.0.3\",\n      \"cross-spawn@6.0.5\": \"6.0.6\",\n      \"cross-spawn@7.0.3\": \"7.0.5\",\n      \"decode-uri-component@0.2.0\": \"0.2.1\",\n      \"luxon\": \"1.28.1\",\n      \"@nestjs/common>axios\": \"0.30.2\",\n      \"ua-parser-js\": \"0.7.33\",\n      \"ws@6.2.2\": \"7.5.10\",\n      \"ws@7.5.9\": \"7.5.10\"\n    }\n  },\n  \"lint-staged\": {\n    \"*.+(ts|tsx|js)\": [\n      \"eslint --fix\",\n      \"prettier --write\"\n    ],\n    \"*.+(json|css|md|html)\": \"prettier --write\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"lint-staged\"\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/clinic-client/gulpfile.js",
    "content": "const { task, series } = require('gulp')\nconst shell = require('gulp-shell')\n\n///////////////////////////\n\ntask('swagger:gen', shell.task('./swagger/gen_api.sh'))\n\n///////////////////////////\n\ntask('tsc:watch', shell.task('tsc -w'))\ntask('tsc:build', shell.task('tsc'))\n\n///////////////////////////\n\nif (process.env.SKIP_GEN_API === '1') {\n  task('dev', series('tsc:build'))\n} else {\n  task('dev', series('swagger:gen', 'tsc:build'))\n}\n\n// in netlify or vercel, we only build frontend, we don't need to generate api, else it will fail because we don't have go and java\nif (process.env.SKIP_GEN_API === '1') {\n  task('build', shell.task('echo \"skip gen api\" & tsc'))\n} else {\n  task('build', series('swagger:gen', 'tsc:build'))\n}\n"
  },
  {
    "path": "ui/packages/clinic-client/openapitools.json",
    "content": "{\n  \"$schema\": \"node_modules/@openapitools/openapi-generator-cli/config.schema.json\",\n  \"spaces\": 2,\n  \"generator-cli\": {\n    \"version\": \"5.4.0\"\n  }\n}\n"
  },
  {
    "path": "ui/packages/clinic-client/package.json",
    "content": "{\n  \"name\": \"@pingcap/clinic-client\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"private\": true,\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"scripts\": {\n    \"dev\": \"gulp dev\",\n    \"build\": \"gulp build\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"@openapitools/openapi-generator-cli\": \"^2.5.1\",\n    \"gulp\": \"^4.0.2\",\n    \"gulp-shell\": \"^0.8.0\",\n    \"typescript\": \"^4.7.3\"\n  },\n  \"dependencies\": {\n    \"axios\": \"^1.12.0\"\n  }\n}\n"
  },
  {
    "path": "ui/packages/clinic-client/src/client/api/api.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Clinic Example API\n * This is a Clinic server.\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { Configuration } from './configuration';\nimport globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';\n// Some imports not used depending on template conditions\n// @ts-ignore\nimport { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common';\n// @ts-ignore\nimport { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from './base';\n\n\n/**\n * DefaultApi - axios parameter creator\n * @export\n */\nexport const DefaultApiAxiosParamCreator = function (configuration?: Configuration) {\n    return {\n        /**\n         * get slow log list in cluster\n         * @summary get slow log list\n         * @param {string} xCsrfToken get value from login.ValidationResp response\n         * @param {string} oid organization id\n         * @param {string} itemID package id\n         * @param {string} cid cluster id\n         * @param {number} [beginTime] start time\n         * @param {number} [endTime] end time\n         * @param {Array<string>} [db] db list\n         * @param {number} [limit] limit\n         * @param {string} [text] text\n         * @param {string} [orderBy] orderBy\n         * @param {boolean} [desc] desc\n         * @param {Array<string>} [plans] plans\n         * @param {string} [digest] digest\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        orgsOidClustersCidSlowqueriesGet: async (xCsrfToken: string, oid: string, itemID: string, cid: string, beginTime?: number, endTime?: number, db?: Array<string>, limit?: number, text?: string, orderBy?: string, desc?: boolean, plans?: Array<string>, digest?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'xCsrfToken' is not null or undefined\n            assertParamExists('orgsOidClustersCidSlowqueriesGet', 'xCsrfToken', xCsrfToken)\n            // verify required parameter 'oid' is not null or undefined\n            assertParamExists('orgsOidClustersCidSlowqueriesGet', 'oid', oid)\n            // verify required parameter 'itemID' is not null or undefined\n            assertParamExists('orgsOidClustersCidSlowqueriesGet', 'itemID', itemID)\n            // verify required parameter 'cid' is not null or undefined\n            assertParamExists('orgsOidClustersCidSlowqueriesGet', 'cid', cid)\n            const localVarPath = `/orgs/{oid}/clusters/{cid}/slowqueries`\n                .replace(`{${\"oid\"}}`, encodeURIComponent(String(oid)))\n                .replace(`{${\"cid\"}}`, encodeURIComponent(String(cid)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            if (itemID !== undefined) {\n                localVarQueryParameter['itemID'] = itemID;\n            }\n\n            if (beginTime !== undefined) {\n                localVarQueryParameter['begin_time'] = beginTime;\n            }\n\n            if (endTime !== undefined) {\n                localVarQueryParameter['end_time'] = endTime;\n            }\n\n            if (db) {\n                localVarQueryParameter['db'] = db.join(COLLECTION_FORMATS.csv);\n            }\n\n            if (limit !== undefined) {\n                localVarQueryParameter['limit'] = limit;\n            }\n\n            if (text !== undefined) {\n                localVarQueryParameter['text'] = text;\n            }\n\n            if (orderBy !== undefined) {\n                localVarQueryParameter['orderBy'] = orderBy;\n            }\n\n            if (desc !== undefined) {\n                localVarQueryParameter['desc'] = desc;\n            }\n\n            if (plans) {\n                localVarQueryParameter['plans'] = plans.join(COLLECTION_FORMATS.csv);\n            }\n\n            if (digest !== undefined) {\n                localVarQueryParameter['digest'] = digest;\n            }\n\n            if (xCsrfToken !== undefined && xCsrfToken !== null) {\n                localVarHeaderParameter['x-csrf-token'] = String(xCsrfToken);\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * get slow log detail in cluster\n         * @summary get slow log detail\n         * @param {string} xCsrfToken get value from login.ValidationResp response\n         * @param {string} oid organization id\n         * @param {string} itemID package id\n         * @param {string} cid cluster id\n         * @param {string} queryid log id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        orgsOidClustersCidSlowqueriesQueryidGet: async (xCsrfToken: string, oid: string, itemID: string, cid: string, queryid: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'xCsrfToken' is not null or undefined\n            assertParamExists('orgsOidClustersCidSlowqueriesQueryidGet', 'xCsrfToken', xCsrfToken)\n            // verify required parameter 'oid' is not null or undefined\n            assertParamExists('orgsOidClustersCidSlowqueriesQueryidGet', 'oid', oid)\n            // verify required parameter 'itemID' is not null or undefined\n            assertParamExists('orgsOidClustersCidSlowqueriesQueryidGet', 'itemID', itemID)\n            // verify required parameter 'cid' is not null or undefined\n            assertParamExists('orgsOidClustersCidSlowqueriesQueryidGet', 'cid', cid)\n            // verify required parameter 'queryid' is not null or undefined\n            assertParamExists('orgsOidClustersCidSlowqueriesQueryidGet', 'queryid', queryid)\n            const localVarPath = `/orgs/{oid}/clusters/{cid}/slowqueries/{queryid}`\n                .replace(`{${\"oid\"}}`, encodeURIComponent(String(oid)))\n                .replace(`{${\"cid\"}}`, encodeURIComponent(String(cid)))\n                .replace(`{${\"queryid\"}}`, encodeURIComponent(String(queryid)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            if (itemID !== undefined) {\n                localVarQueryParameter['itemID'] = itemID;\n            }\n\n            if (xCsrfToken !== undefined && xCsrfToken !== null) {\n                localVarHeaderParameter['x-csrf-token'] = String(xCsrfToken);\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n    }\n};\n\n/**\n * DefaultApi - functional programming interface\n * @export\n */\nexport const DefaultApiFp = function(configuration?: Configuration) {\n    const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration)\n    return {\n        /**\n         * get slow log list in cluster\n         * @summary get slow log list\n         * @param {string} xCsrfToken get value from login.ValidationResp response\n         * @param {string} oid organization id\n         * @param {string} itemID package id\n         * @param {string} cid cluster id\n         * @param {number} [beginTime] start time\n         * @param {number} [endTime] end time\n         * @param {Array<string>} [db] db list\n         * @param {number} [limit] limit\n         * @param {string} [text] text\n         * @param {string} [orderBy] orderBy\n         * @param {boolean} [desc] desc\n         * @param {Array<string>} [plans] plans\n         * @param {string} [digest] digest\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async orgsOidClustersCidSlowqueriesGet(xCsrfToken: string, oid: string, itemID: string, cid: string, beginTime?: number, endTime?: number, db?: Array<string>, limit?: number, text?: string, orderBy?: string, desc?: boolean, plans?: Array<string>, digest?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<object>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.orgsOidClustersCidSlowqueriesGet(xCsrfToken, oid, itemID, cid, beginTime, endTime, db, limit, text, orderBy, desc, plans, digest, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * get slow log detail in cluster\n         * @summary get slow log detail\n         * @param {string} xCsrfToken get value from login.ValidationResp response\n         * @param {string} oid organization id\n         * @param {string} itemID package id\n         * @param {string} cid cluster id\n         * @param {string} queryid log id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async orgsOidClustersCidSlowqueriesQueryidGet(xCsrfToken: string, oid: string, itemID: string, cid: string, queryid: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.orgsOidClustersCidSlowqueriesQueryidGet(xCsrfToken, oid, itemID, cid, queryid, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n    }\n};\n\n/**\n * DefaultApi - factory interface\n * @export\n */\nexport const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {\n    const localVarFp = DefaultApiFp(configuration)\n    return {\n        /**\n         * get slow log list in cluster\n         * @summary get slow log list\n         * @param {string} xCsrfToken get value from login.ValidationResp response\n         * @param {string} oid organization id\n         * @param {string} itemID package id\n         * @param {string} cid cluster id\n         * @param {number} [beginTime] start time\n         * @param {number} [endTime] end time\n         * @param {Array<string>} [db] db list\n         * @param {number} [limit] limit\n         * @param {string} [text] text\n         * @param {string} [orderBy] orderBy\n         * @param {boolean} [desc] desc\n         * @param {Array<string>} [plans] plans\n         * @param {string} [digest] digest\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        orgsOidClustersCidSlowqueriesGet(xCsrfToken: string, oid: string, itemID: string, cid: string, beginTime?: number, endTime?: number, db?: Array<string>, limit?: number, text?: string, orderBy?: string, desc?: boolean, plans?: Array<string>, digest?: string, options?: any): AxiosPromise<Array<object>> {\n            return localVarFp.orgsOidClustersCidSlowqueriesGet(xCsrfToken, oid, itemID, cid, beginTime, endTime, db, limit, text, orderBy, desc, plans, digest, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * get slow log detail in cluster\n         * @summary get slow log detail\n         * @param {string} xCsrfToken get value from login.ValidationResp response\n         * @param {string} oid organization id\n         * @param {string} itemID package id\n         * @param {string} cid cluster id\n         * @param {string} queryid log id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        orgsOidClustersCidSlowqueriesQueryidGet(xCsrfToken: string, oid: string, itemID: string, cid: string, queryid: string, options?: any): AxiosPromise<object> {\n            return localVarFp.orgsOidClustersCidSlowqueriesQueryidGet(xCsrfToken, oid, itemID, cid, queryid, options).then((request) => request(axios, basePath));\n        },\n    };\n};\n\n/**\n * Request parameters for orgsOidClustersCidSlowqueriesGet operation in DefaultApi.\n * @export\n * @interface DefaultApiOrgsOidClustersCidSlowqueriesGetRequest\n */\nexport interface DefaultApiOrgsOidClustersCidSlowqueriesGetRequest {\n    /**\n     * get value from login.ValidationResp response\n     * @type {string}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesGet\n     */\n    readonly xCsrfToken: string\n\n    /**\n     * organization id\n     * @type {string}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesGet\n     */\n    readonly oid: string\n\n    /**\n     * package id\n     * @type {string}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesGet\n     */\n    readonly itemID: string\n\n    /**\n     * cluster id\n     * @type {string}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesGet\n     */\n    readonly cid: string\n\n    /**\n     * start time\n     * @type {number}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesGet\n     */\n    readonly beginTime?: number\n\n    /**\n     * end time\n     * @type {number}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesGet\n     */\n    readonly endTime?: number\n\n    /**\n     * db list\n     * @type {Array<string>}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesGet\n     */\n    readonly db?: Array<string>\n\n    /**\n     * limit\n     * @type {number}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesGet\n     */\n    readonly limit?: number\n\n    /**\n     * text\n     * @type {string}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesGet\n     */\n    readonly text?: string\n\n    /**\n     * orderBy\n     * @type {string}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesGet\n     */\n    readonly orderBy?: string\n\n    /**\n     * desc\n     * @type {boolean}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesGet\n     */\n    readonly desc?: boolean\n\n    /**\n     * plans\n     * @type {Array<string>}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesGet\n     */\n    readonly plans?: Array<string>\n\n    /**\n     * digest\n     * @type {string}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesGet\n     */\n    readonly digest?: string\n}\n\n/**\n * Request parameters for orgsOidClustersCidSlowqueriesQueryidGet operation in DefaultApi.\n * @export\n * @interface DefaultApiOrgsOidClustersCidSlowqueriesQueryidGetRequest\n */\nexport interface DefaultApiOrgsOidClustersCidSlowqueriesQueryidGetRequest {\n    /**\n     * get value from login.ValidationResp response\n     * @type {string}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesQueryidGet\n     */\n    readonly xCsrfToken: string\n\n    /**\n     * organization id\n     * @type {string}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesQueryidGet\n     */\n    readonly oid: string\n\n    /**\n     * package id\n     * @type {string}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesQueryidGet\n     */\n    readonly itemID: string\n\n    /**\n     * cluster id\n     * @type {string}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesQueryidGet\n     */\n    readonly cid: string\n\n    /**\n     * log id\n     * @type {string}\n     * @memberof DefaultApiOrgsOidClustersCidSlowqueriesQueryidGet\n     */\n    readonly queryid: string\n}\n\n/**\n * DefaultApi - object-oriented interface\n * @export\n * @class DefaultApi\n * @extends {BaseAPI}\n */\nexport class DefaultApi extends BaseAPI {\n    /**\n     * get slow log list in cluster\n     * @summary get slow log list\n     * @param {DefaultApiOrgsOidClustersCidSlowqueriesGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public orgsOidClustersCidSlowqueriesGet(requestParameters: DefaultApiOrgsOidClustersCidSlowqueriesGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).orgsOidClustersCidSlowqueriesGet(requestParameters.xCsrfToken, requestParameters.oid, requestParameters.itemID, requestParameters.cid, requestParameters.beginTime, requestParameters.endTime, requestParameters.db, requestParameters.limit, requestParameters.text, requestParameters.orderBy, requestParameters.desc, requestParameters.plans, requestParameters.digest, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * get slow log detail in cluster\n     * @summary get slow log detail\n     * @param {DefaultApiOrgsOidClustersCidSlowqueriesQueryidGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public orgsOidClustersCidSlowqueriesQueryidGet(requestParameters: DefaultApiOrgsOidClustersCidSlowqueriesQueryidGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).orgsOidClustersCidSlowqueriesQueryidGet(requestParameters.xCsrfToken, requestParameters.oid, requestParameters.itemID, requestParameters.cid, requestParameters.queryid, options).then((request) => request(this.axios, this.basePath));\n    }\n}\n\n\n"
  },
  {
    "path": "ui/packages/clinic-client/src/client/api/base.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Clinic Example API\n * This is a Clinic server.\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { Configuration } from \"./configuration\";\n// Some imports not used depending on template conditions\n// @ts-ignore\nimport globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';\n\nexport const BASE_PATH = \"/clinic/api/v1\".replace(/\\/+$/, \"\");\n\n/**\n *\n * @export\n */\nexport const COLLECTION_FORMATS = {\n    csv: \",\",\n    ssv: \" \",\n    tsv: \"\\t\",\n    pipes: \"|\",\n};\n\n/**\n *\n * @export\n * @interface RequestArgs\n */\nexport interface RequestArgs {\n    url: string;\n    options: AxiosRequestConfig;\n}\n\n/**\n *\n * @export\n * @class BaseAPI\n */\nexport class BaseAPI {\n    protected configuration: Configuration | undefined;\n\n    constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {\n        if (configuration) {\n            this.configuration = configuration;\n            this.basePath = configuration.basePath || this.basePath;\n        }\n    }\n};\n\n/**\n *\n * @export\n * @class RequiredError\n * @extends {Error}\n */\nexport class RequiredError extends Error {\n    name: \"RequiredError\" = \"RequiredError\";\n    constructor(public field: string, msg?: string) {\n        super(msg);\n    }\n}\n"
  },
  {
    "path": "ui/packages/clinic-client/src/client/api/common.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Clinic Example API\n * This is a Clinic server.\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { Configuration } from \"./configuration\";\nimport { RequiredError, RequestArgs } from \"./base\";\nimport { AxiosInstance, AxiosResponse } from 'axios';\n\n/**\n *\n * @export\n */\nexport const DUMMY_BASE_URL = 'https://example.com'\n\n/**\n *\n * @throws {RequiredError}\n * @export\n */\nexport const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) {\n    if (paramValue === null || paramValue === undefined) {\n        throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`);\n    }\n}\n\n/**\n *\n * @export\n */\nexport const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) {\n    if (configuration && configuration.apiKey) {\n        const localVarApiKeyValue = typeof configuration.apiKey === 'function'\n            ? await configuration.apiKey(keyParamName)\n            : await configuration.apiKey;\n        object[keyParamName] = localVarApiKeyValue;\n    }\n}\n\n/**\n *\n * @export\n */\nexport const setBasicAuthToObject = function (object: any, configuration?: Configuration) {\n    if (configuration && (configuration.username || configuration.password)) {\n        object[\"auth\"] = { username: configuration.username, password: configuration.password };\n    }\n}\n\n/**\n *\n * @export\n */\nexport const setBearerAuthToObject = async function (object: any, configuration?: Configuration) {\n    if (configuration && configuration.accessToken) {\n        const accessToken = typeof configuration.accessToken === 'function'\n            ? await configuration.accessToken()\n            : await configuration.accessToken;\n        object[\"Authorization\"] = \"Bearer \" + accessToken;\n    }\n}\n\n/**\n *\n * @export\n */\nexport const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) {\n    if (configuration && configuration.accessToken) {\n        const localVarAccessTokenValue = typeof configuration.accessToken === 'function'\n            ? await configuration.accessToken(name, scopes)\n            : await configuration.accessToken;\n        object[\"Authorization\"] = \"Bearer \" + localVarAccessTokenValue;\n    }\n}\n\n/**\n *\n * @export\n */\nexport const setSearchParams = function (url: URL, ...objects: any[]) {\n    const searchParams = new URLSearchParams(url.search);\n    for (const object of objects) {\n        for (const key in object) {\n            if (Array.isArray(object[key])) {\n                searchParams.delete(key);\n                for (const item of object[key]) {\n                    searchParams.append(key, item);\n                }\n            } else {\n                searchParams.set(key, object[key]);\n            }\n        }\n    }\n    url.search = searchParams.toString();\n}\n\n/**\n *\n * @export\n */\nexport const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) {\n    const nonString = typeof value !== 'string';\n    const needsSerialization = nonString && configuration && configuration.isJsonMime\n        ? configuration.isJsonMime(requestOptions.headers['Content-Type'])\n        : nonString;\n    return needsSerialization\n        ? JSON.stringify(value !== undefined ? value : {})\n        : (value || \"\");\n}\n\n/**\n *\n * @export\n */\nexport const toPathString = function (url: URL) {\n    return url.pathname + url.search + url.hash\n}\n\n/**\n *\n * @export\n */\nexport const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {\n    return <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {\n        const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url};\n        return axios.request<T, R>(axiosRequestArgs);\n    };\n}\n"
  },
  {
    "path": "ui/packages/clinic-client/src/client/api/configuration.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Clinic Example API\n * This is a Clinic server.\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nexport interface ConfigurationParameters {\n    apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);\n    username?: string;\n    password?: string;\n    accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);\n    basePath?: string;\n    baseOptions?: any;\n    formDataCtor?: new () => any;\n}\n\nexport class Configuration {\n    /**\n     * parameter for apiKey security\n     * @param name security name\n     * @memberof Configuration\n     */\n    apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);\n    /**\n     * parameter for basic security\n     *\n     * @type {string}\n     * @memberof Configuration\n     */\n    username?: string;\n    /**\n     * parameter for basic security\n     *\n     * @type {string}\n     * @memberof Configuration\n     */\n    password?: string;\n    /**\n     * parameter for oauth2 security\n     * @param name security name\n     * @param scopes oauth2 scope\n     * @memberof Configuration\n     */\n    accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);\n    /**\n     * override base path\n     *\n     * @type {string}\n     * @memberof Configuration\n     */\n    basePath?: string;\n    /**\n     * base options for axios calls\n     *\n     * @type {any}\n     * @memberof Configuration\n     */\n    baseOptions?: any;\n    /**\n     * The FormData constructor that will be used to create multipart form data\n     * requests. You can inject this here so that execution environments that\n     * do not support the FormData class can still run the generated client.\n     *\n     * @type {new () => FormData}\n     */\n    formDataCtor?: new () => any;\n\n    constructor(param: ConfigurationParameters = {}) {\n        this.apiKey = param.apiKey;\n        this.username = param.username;\n        this.password = param.password;\n        this.accessToken = param.accessToken;\n        this.basePath = param.basePath;\n        this.baseOptions = param.baseOptions;\n        this.formDataCtor = param.formDataCtor;\n    }\n\n    /**\n     * Check if the given MIME is a JSON MIME.\n     * JSON MIME examples:\n     *   application/json\n     *   application/json; charset=UTF8\n     *   APPLICATION/JSON\n     *   application/vnd.company+json\n     * @param mime - MIME (Multipurpose Internet Mail Extensions)\n     * @return True if the given MIME is JSON, false otherwise.\n     */\n    public isJsonMime(mime: string): boolean {\n        const jsonMime: RegExp = new RegExp('^(application\\/json|[^;/ \\t]+\\/[^;/ \\t]+[+]json)[ \\t]*(;.*)?$', 'i');\n        return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');\n    }\n}\n"
  },
  {
    "path": "ui/packages/clinic-client/src/client/api/index.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Clinic Example API\n * This is a Clinic server.\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nexport * from \"./api\";\nexport * from \"./configuration\";\n\n"
  },
  {
    "path": "ui/packages/clinic-client/swagger/.openapi_config.yaml",
    "content": "# https://openapi-generator.tech/docs/generators/typescript-axios#config-options\n\nenumPropertyNaming: original\nmodelPropertyNaming: original\nsupportsES6: true\n\n# Setting this property to true will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter.\nuseSingleRequestParameter: true\n# conflicts with `useSingleRequestParameter`\n# withInterfaces: true\n\n# Put the model and api in separate folders and in separate classes\n# https://github.com/OpenAPITools/openapi-generator/issues/5008#issuecomment-613791804\n# withSeparateModelsAndApi: true\n# apiPackage: api\n# modelPackage: models\n"
  },
  {
    "path": "ui/packages/clinic-client/swagger/gen_api.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\nPROJECT_DIR=\"$(dirname \"$DIR\")\"\n\nAPI_SPEC_DIR=$PROJECT_DIR/swagger/spec.json\nOPENAPI_CONFIG_DIR=$PROJECT_DIR/swagger/.openapi_config.yaml\nOUTPUT_DIR=$PROJECT_DIR/src/client/api\n\ncd $PROJECT_DIR/swagger\n\n# touch spec.json && rm spec.json\n\n# curl -o spec.json -fsSL http://clinic-staging-1072990385.us-west-2.elb.amazonaws.com:8085/swagger/doc.json\n\npnpm openapi-generator-cli generate -i $API_SPEC_DIR -g typescript-axios -c $OPENAPI_CONFIG_DIR -o $OUTPUT_DIR\n\nrm -rf $OUTPUT_DIR/.openapi-generator\nrm $OUTPUT_DIR/.gitignore\nrm $OUTPUT_DIR/.npmignore\nrm $OUTPUT_DIR/.openapi-generator-ignore\nrm $OUTPUT_DIR/git_push.sh\n"
  },
  {
    "path": "ui/packages/clinic-client/swagger/spec.json",
    "content": "{\n  \"schemes\": [],\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"description\": \"This is a Clinic server.\",\n    \"title\": \"Clinic Example API\",\n    \"contact\": {},\n    \"version\": \"1.0\"\n  },\n  \"host\": \"\",\n  \"basePath\": \"/clinic/api/v1\",\n  \"paths\": {\n    \"/orgs/{oid}/clusters/{cid}/slowqueries\": {\n      \"get\": {\n        \"description\": \"get slow log list in cluster\",\n        \"produces\": [\"application/json\"],\n        \"summary\": \"get slow log list\",\n        \"parameters\": [\n          {\n            \"type\": \"string\",\n            \"description\": \"get value from login.ValidationResp response\",\n            \"name\": \"x-csrf-token\",\n            \"in\": \"header\",\n            \"required\": true\n          },\n          {\n            \"type\": \"string\",\n            \"description\": \"organization id\",\n            \"name\": \"oid\",\n            \"in\": \"path\",\n            \"required\": true\n          },\n          {\n            \"type\": \"string\",\n            \"description\": \"package id\",\n            \"name\": \"itemID\",\n            \"in\": \"query\",\n            \"required\": true\n          },\n          {\n            \"type\": \"integer\",\n            \"description\": \"start time\",\n            \"name\": \"begin_time\",\n            \"in\": \"query\"\n          },\n          {\n            \"type\": \"integer\",\n            \"description\": \"end time\",\n            \"name\": \"end_time\",\n            \"in\": \"query\"\n          },\n          {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"description\": \"db list\",\n            \"name\": \"db\",\n            \"in\": \"query\"\n          },\n          {\n            \"type\": \"integer\",\n            \"description\": \"limit\",\n            \"name\": \"limit\",\n            \"in\": \"query\"\n          },\n          {\n            \"type\": \"string\",\n            \"description\": \"text\",\n            \"name\": \"text\",\n            \"in\": \"query\"\n          },\n          {\n            \"type\": \"string\",\n            \"description\": \"orderBy\",\n            \"name\": \"orderBy\",\n            \"in\": \"query\"\n          },\n          {\n            \"type\": \"boolean\",\n            \"description\": \"desc\",\n            \"name\": \"desc\",\n            \"in\": \"query\"\n          },\n          {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"description\": \"plans\",\n            \"name\": \"plans\",\n            \"in\": \"query\"\n          },\n          {\n            \"type\": \"string\",\n            \"description\": \"digest\",\n            \"name\": \"digest\",\n            \"in\": \"query\"\n          },\n          {\n            \"type\": \"string\",\n            \"description\": \"cluster id\",\n            \"name\": \"cid\",\n            \"in\": \"path\",\n            \"required\": true\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"type\": \"array\",\n              \"items\": {\n                \"$ref\": \"#/definitions/cluster.SlowQueryProfile\"\n              }\n            }\n          }\n        }\n      }\n    },\n    \"/orgs/{oid}/clusters/{cid}/slowqueries/{queryid}\": {\n      \"get\": {\n        \"description\": \"get slow log detail in cluster\",\n        \"produces\": [\"application/json\"],\n        \"summary\": \"get slow log detail\",\n        \"parameters\": [\n          {\n            \"type\": \"string\",\n            \"description\": \"get value from login.ValidationResp response\",\n            \"name\": \"x-csrf-token\",\n            \"in\": \"header\",\n            \"required\": true\n          },\n          {\n            \"type\": \"string\",\n            \"description\": \"organization id\",\n            \"name\": \"oid\",\n            \"in\": \"path\",\n            \"required\": true\n          },\n          {\n            \"type\": \"string\",\n            \"description\": \"package id\",\n            \"name\": \"itemID\",\n            \"in\": \"query\",\n            \"required\": true\n          },\n          {\n            \"type\": \"string\",\n            \"description\": \"cluster id\",\n            \"name\": \"cid\",\n            \"in\": \"path\",\n            \"required\": true\n          },\n          {\n            \"type\": \"string\",\n            \"description\": \"log id\",\n            \"name\": \"queryid\",\n            \"in\": \"path\",\n            \"required\": true\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"OK\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/cluster.SlowQueryProfile\"\n            }\n          }\n        }\n      }\n    }\n  },\n  \"definitions\": {\n    \"cluster.SlowQueryProfile\": {\n      \"type\": \"object\",\n      \"additionalProperties\": true\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/clinic-client/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"declaration\": true\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/gulpfile.js",
    "content": "const { task, watch, series, parallel } = require('gulp')\nconst shell = require('gulp-shell')\n\n///////////////////////////\n\ntask(\n  'swagger:gen_spec',\n  shell.task('../../../scripts/generate_swagger_spec.sh')\n)\ntask('swagger:gen_client', shell.task('./swagger/gen_api.sh'))\ntask('swagger:gen', series('swagger:gen_spec', 'swagger:gen_client'))\ntask('swagger:watch', () =>\n  watch(['../../../cmd/**/*.go', '../../../pkg/**/*.go'], series('swagger:gen'))\n)\n\n///////////////////////////\n\ntask('tsc:watch', shell.task('tsc -w'))\ntask('tsc:build', shell.task('tsc'))\n\n///////////////////////////\n\nif (process.env.WATCH_API === '1') {\n  task('dev', series('swagger:gen', parallel('swagger:watch', 'tsc:watch')))\n} else if (process.env.SKIP_GEN_API === '1') {\n  task('dev', series('tsc:build'))\n} else {\n  // WATCH_API = 0\n  task('dev', series('swagger:gen', 'tsc:build'))\n}\n\n// in netlify or vercel, we only build frontend, we don't need to generate api, else it will fail because we don't have go and java\nif (process.env.SKIP_GEN_API === '1') {\n  task('build', shell.task('echo \"skip gen api\" & tsc'))\n} else {\n  task('build', series('swagger:gen', 'tsc:build'))\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/openapitools.json",
    "content": "{\n  \"$schema\": \"node_modules/@openapitools/openapi-generator-cli/config.schema.json\",\n  \"spaces\": 2,\n  \"generator-cli\": {\n    \"version\": \"5.4.0\"\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/package.json",
    "content": "{\n  \"name\": \"@pingcap/tidb-dashboard-client\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"private\": true,\n  \"main\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"scripts\": {\n    \"dev\": \"gulp dev\",\n    \"build\": \"gulp build\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"@openapitools/openapi-generator-cli\": \"^2.5.1\",\n    \"gulp\": \"^4.0.2\",\n    \"gulp-shell\": \"^0.8.0\",\n    \"typescript\": \"^4.7.3\"\n  },\n  \"dependencies\": {\n    \"axios\": \"^1.12.0\"\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/api/default-api.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';\nimport { Configuration } from '../configuration';\n// Some imports not used depending on template conditions\n// @ts-ignore\nimport { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from '../common';\n// @ts-ignore\nimport { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from '../base';\n// @ts-ignore\nimport { ClusterinfoClusterStatistics } from '../models';\n// @ts-ignore\nimport { ClusterinfoGetHostsInfoResponse } from '../models';\n// @ts-ignore\nimport { ClusterinfoStoreTopologyResponse } from '../models';\n// @ts-ignore\nimport { CodeShareRequest } from '../models';\n// @ts-ignore\nimport { CodeShareResponse } from '../models';\n// @ts-ignore\nimport { ConfigKeyVisualConfig } from '../models';\n// @ts-ignore\nimport { ConfigProfilingConfig } from '../models';\n// @ts-ignore\nimport { ConfigSSOCoreConfig } from '../models';\n// @ts-ignore\nimport { ConfigurationAllConfigItems } from '../models';\n// @ts-ignore\nimport { ConfigurationEditRequest } from '../models';\n// @ts-ignore\nimport { ConfigurationEditResponse } from '../models';\n// @ts-ignore\nimport { ConprofComponent } from '../models';\n// @ts-ignore\nimport { ConprofEstimateSizeRes } from '../models';\n// @ts-ignore\nimport { ConprofGroupProfileDetail } from '../models';\n// @ts-ignore\nimport { ConprofGroupProfiles } from '../models';\n// @ts-ignore\nimport { ConprofNgMonitoringConfig } from '../models';\n// @ts-ignore\nimport { DeadlockModel } from '../models';\n// @ts-ignore\nimport { DiagnoseGenDiagnosisReportRequest } from '../models';\n// @ts-ignore\nimport { DiagnoseGenerateMetricsRelationRequest } from '../models';\n// @ts-ignore\nimport { DiagnoseGenerateReportRequest } from '../models';\n// @ts-ignore\nimport { DiagnoseReport } from '../models';\n// @ts-ignore\nimport { DiagnoseTableDef } from '../models';\n// @ts-ignore\nimport { EndpointAPIDefinition } from '../models';\n// @ts-ignore\nimport { EndpointRequestPayload } from '../models';\n// @ts-ignore\nimport { InfoInfoResponse } from '../models';\n// @ts-ignore\nimport { InfoTableSchema } from '../models';\n// @ts-ignore\nimport { InfoWhoAmIResponse } from '../models';\n// @ts-ignore\nimport { LogsearchCreateTaskGroupRequest } from '../models';\n// @ts-ignore\nimport { LogsearchPreviewModel } from '../models';\n// @ts-ignore\nimport { LogsearchTaskGroupModel } from '../models';\n// @ts-ignore\nimport { LogsearchTaskGroupResponse } from '../models';\n// @ts-ignore\nimport { MatrixMatrix } from '../models';\n// @ts-ignore\nimport { MetricsGetPromAddressConfigResponse } from '../models';\n// @ts-ignore\nimport { MetricsPutCustomPromAddressRequest } from '../models';\n// @ts-ignore\nimport { MetricsPutCustomPromAddressResponse } from '../models';\n// @ts-ignore\nimport { MetricsQueryResponse } from '../models';\n// @ts-ignore\nimport { ProfilingGroupDetailResponse } from '../models';\n// @ts-ignore\nimport { ProfilingStartRequest } from '../models';\n// @ts-ignore\nimport { ProfilingTaskGroupModel } from '../models';\n// @ts-ignore\nimport { QueryeditorRunRequest } from '../models';\n// @ts-ignore\nimport { QueryeditorRunResponse } from '../models';\n// @ts-ignore\nimport { ResourcemanagerCalibrateResponse } from '../models';\n// @ts-ignore\nimport { ResourcemanagerGetConfigResponse } from '../models';\n// @ts-ignore\nimport { ResourcemanagerResourceInfoRowDef } from '../models';\n// @ts-ignore\nimport { RestErrorResponse } from '../models';\n// @ts-ignore\nimport { SlowqueryGetListRequest } from '../models';\n// @ts-ignore\nimport { SlowqueryModel } from '../models';\n// @ts-ignore\nimport { SsoCreateImpersonationRequest } from '../models';\n// @ts-ignore\nimport { SsoSSOImpersonationModel } from '../models';\n// @ts-ignore\nimport { SsoSetConfigRequest } from '../models';\n// @ts-ignore\nimport { StatementBinding } from '../models';\n// @ts-ignore\nimport { StatementEditableConfig } from '../models';\n// @ts-ignore\nimport { StatementGetStatementsRequest } from '../models';\n// @ts-ignore\nimport { StatementModel } from '../models';\n// @ts-ignore\nimport { TopologyAlertManagerInfo } from '../models';\n// @ts-ignore\nimport { TopologyGrafanaInfo } from '../models';\n// @ts-ignore\nimport { TopologyPDInfo } from '../models';\n// @ts-ignore\nimport { TopologySchedulingInfo } from '../models';\n// @ts-ignore\nimport { TopologyStoreLocation } from '../models';\n// @ts-ignore\nimport { TopologyTSOInfo } from '../models';\n// @ts-ignore\nimport { TopologyTiCDCInfo } from '../models';\n// @ts-ignore\nimport { TopologyTiDBInfo } from '../models';\n// @ts-ignore\nimport { TopologyTiProxyInfo } from '../models';\n// @ts-ignore\nimport { TopsqlEditableConfig } from '../models';\n// @ts-ignore\nimport { TopsqlInstanceResponse } from '../models';\n// @ts-ignore\nimport { TopsqlSummaryResponse } from '../models';\n// @ts-ignore\nimport { TopsqlTikvNetworkIoCollectionConfig } from '../models';\n// @ts-ignore\nimport { TopsqlUpdateTikvNetworkIoCollectionResponse } from '../models';\n// @ts-ignore\nimport { UserAuthenticateForm } from '../models';\n// @ts-ignore\nimport { UserGetLoginInfoResponse } from '../models';\n// @ts-ignore\nimport { UserSignOutInfo } from '../models';\n// @ts-ignore\nimport { UserTokenResponse } from '../models';\n/**\n * DefaultApi - axios parameter creator\n * @export\n */\nexport const DefaultApiAxiosParamCreator = function (configuration?: Configuration) {\n    return {\n        /**\n         * Cancel all profling tasks with a given group ID\n         * @summary Cancel all tasks with a given group ID\n         * @param {string} groupId group ID\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        cancelProfilingGroup: async (groupId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'groupId' is not null or undefined\n            assertParamExists('cancelProfilingGroup', 'groupId', groupId)\n            const localVarPath = `/profiling/group/cancel/{groupId}`\n                .replace(`{${\"groupId\"}}`, encodeURIComponent(String(groupId)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get information of all hosts\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        clusterInfoGetHostsInfo: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/host/all`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get cluster statistics\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        clusterInfoGetStatistics: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/host/statistics`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Edit a configuration\n         * @param {ConfigurationEditRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        configurationEdit: async (request: ConfigurationEditRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('configurationEdit', 'request', request)\n            const localVarPath = `/configuration/edit`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get all configurations\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        configurationGetAll: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/configuration/all`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get action token for download or view profile\n         * @param {string} q target query string\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingActionTokenGet: async (q: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'q' is not null or undefined\n            assertParamExists('continuousProfilingActionTokenGet', 'q', q)\n            const localVarPath = `/continuous_profiling/action_token`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (q !== undefined) {\n                localVarQueryParameter['q'] = q;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get current scraping components\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingComponentsGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/continuous_profiling/components`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get Continuous Profiling Config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingConfigGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/continuous_profiling/config`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Update Continuous Profiling Config\n         * @param {ConprofNgMonitoringConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingConfigPost: async (request: ConprofNgMonitoringConfig, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('continuousProfilingConfigPost', 'request', request)\n            const localVarPath = `/continuous_profiling/config`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Download Group Profile files\n         * @param {number} ts timestamp\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingDownloadGet: async (ts: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'ts' is not null or undefined\n            assertParamExists('continuousProfilingDownloadGet', 'ts', ts)\n            const localVarPath = `/continuous_profiling/download`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (ts !== undefined) {\n                localVarQueryParameter['ts'] = ts;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get Estimate Size\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingEstimateSizeGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/continuous_profiling/estimate_size`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get Group Profile Detail\n         * @param {number} ts timestamp\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingGroupProfileDetailGet: async (ts: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'ts' is not null or undefined\n            assertParamExists('continuousProfilingGroupProfileDetailGet', 'ts', ts)\n            const localVarPath = `/continuous_profiling/group_profile/detail`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (ts !== undefined) {\n                localVarQueryParameter['ts'] = ts;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get Group Profiles\n         * @param {number} [beginTime] \n         * @param {number} [endTime] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingGroupProfilesGet: async (beginTime?: number, endTime?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/continuous_profiling/group_profiles`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (beginTime !== undefined) {\n                localVarQueryParameter['begin_time'] = beginTime;\n            }\n\n            if (endTime !== undefined) {\n                localVarQueryParameter['end_time'] = endTime;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary View Single Profile files\n         * @param {string} [address] \n         * @param {string} [component] \n         * @param {string} [profileType] \n         * @param {number} [ts] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingSingleProfileViewGet: async (address?: string, component?: string, profileType?: string, ts?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/continuous_profiling/single_profile/view`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (address !== undefined) {\n                localVarQueryParameter['address'] = address;\n            }\n\n            if (component !== undefined) {\n                localVarQueryParameter['component'] = component;\n            }\n\n            if (profileType !== undefined) {\n                localVarQueryParameter['profile_type'] = profileType;\n            }\n\n            if (ts !== undefined) {\n                localVarQueryParameter['ts'] = ts;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary List all deadlock records\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        deadlockListGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/deadlock/list`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get all endpoints\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        debugAPIGetEndpoints: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/debug_api/endpoints`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Send request remote endpoint and return a token for downloading results\n         * @param {EndpointRequestPayload} req request payload\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        debugAPIRequestEndpoint: async (req: EndpointRequestPayload, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'req' is not null or undefined\n            assertParamExists('debugAPIRequestEndpoint', 'req', req)\n            const localVarPath = `/debug_api/endpoint`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(req, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Download a finished request result\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        debugApiDownloadGet: async (token: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'token' is not null or undefined\n            assertParamExists('debugApiDownloadGet', 'token', token)\n            const localVarPath = `/debug_api/download`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            if (token !== undefined) {\n                localVarQueryParameter['token'] = token;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Delete all finished profiling tasks with a given group ID\n         * @summary Delete all tasks with a given group ID\n         * @param {string} groupId group ID\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        deleteProfilingGroup: async (groupId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'groupId' is not null or undefined\n            assertParamExists('deleteProfilingGroup', 'groupId', groupId)\n            const localVarPath = `/profiling/group/delete/{groupId}`\n                .replace(`{${\"groupId\"}}`, encodeURIComponent(String(groupId)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Generate sql diagnosis report\n         * @summary SQL diagnosis report\n         * @param {DiagnoseGenDiagnosisReportRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseDiagnosisPost: async (request: DiagnoseGenDiagnosisReportRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('diagnoseDiagnosisPost', 'request', request)\n            const localVarPath = `/diagnose/diagnosis`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Generate metrics relationship graph.\n         * @param {DiagnoseGenerateMetricsRelationRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseGenerateMetricsRelationship: async (request: DiagnoseGenerateMetricsRelationRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('diagnoseGenerateMetricsRelationship', 'request', request)\n            const localVarPath = `/diagnose/metrics_relation/generate`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary View metrics relationship graph.\n         * @param {string} token token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseMetricsRelationViewGet: async (token: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'token' is not null or undefined\n            assertParamExists('diagnoseMetricsRelationViewGet', 'token', token)\n            const localVarPath = `/diagnose/metrics_relation/view`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            if (token !== undefined) {\n                localVarQueryParameter['token'] = token;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Get sql diagnosis reports history\n         * @summary SQL diagnosis reports history\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseReportsGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/diagnose/reports`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Get sql diagnosis report data\n         * @summary SQL diagnosis report data\n         * @param {string} id report id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseReportsIdDataJsGet: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'id' is not null or undefined\n            assertParamExists('diagnoseReportsIdDataJsGet', 'id', id)\n            const localVarPath = `/diagnose/reports/{id}/data.js`\n                .replace(`{${\"id\"}}`, encodeURIComponent(String(id)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Get sql diagnosis report HTML\n         * @summary SQL diagnosis report\n         * @param {string} id report id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseReportsIdDetailGet: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'id' is not null or undefined\n            assertParamExists('diagnoseReportsIdDetailGet', 'id', id)\n            const localVarPath = `/diagnose/reports/{id}/detail`\n                .replace(`{${\"id\"}}`, encodeURIComponent(String(id)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Get diagnosis report status\n         * @summary Diagnosis report status\n         * @param {string} id report id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseReportsIdStatusGet: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'id' is not null or undefined\n            assertParamExists('diagnoseReportsIdStatusGet', 'id', id)\n            const localVarPath = `/diagnose/reports/{id}/status`\n                .replace(`{${\"id\"}}`, encodeURIComponent(String(id)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Generate sql diagnosis report\n         * @summary SQL diagnosis report\n         * @param {DiagnoseGenerateReportRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseReportsPost: async (request: DiagnoseGenerateReportRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('diagnoseReportsPost', 'request', request)\n            const localVarPath = `/diagnose/reports`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Download all finished profiling results of a task group\n         * @summary Download all results of a task group\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        downloadProfilingGroup: async (token: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'token' is not null or undefined\n            assertParamExists('downloadProfilingGroup', 'token', token)\n            const localVarPath = `/profiling/group/download`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (token !== undefined) {\n                localVarQueryParameter['token'] = token;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Download the finished profiling result of a task\n         * @summary Download the result of a task\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        downloadProfilingSingle: async (token: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'token' is not null or undefined\n            assertParamExists('downloadProfilingSingle', 'token', token)\n            const localVarPath = `/profiling/single/download`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (token !== undefined) {\n                localVarQueryParameter['token'] = token;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Get token with a given group ID or task ID and action type\n         * @summary Get action token for download or view\n         * @param {string} [id] group or task ID\n         * @param {string} [action] action\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getActionToken: async (id?: string, action?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/profiling/action_token`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (id !== undefined) {\n                localVarQueryParameter['id'] = id;\n            }\n\n            if (action !== undefined) {\n                localVarQueryParameter['action'] = action;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get current alert count from AlertManager\n         * @param {string} address ip:port\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getAlertManagerCounts: async (address: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'address' is not null or undefined\n            assertParamExists('getAlertManagerCounts', 'address', address)\n            const localVarPath = `/topology/alertmanager/{address}/count`\n                .replace(`{${\"address\"}}`, encodeURIComponent(String(address)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get AlertManager instance\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getAlertManagerTopology: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topology/alertmanager`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get Grafana instance\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getGrafanaTopology: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topology/grafana`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get all PD instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getPDTopology: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topology/pd`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * List all profiling tasks with a given group ID\n         * @summary List all tasks with a given group ID\n         * @param {string} groupId group ID\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getProfilingGroupDetail: async (groupId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'groupId' is not null or undefined\n            assertParamExists('getProfilingGroupDetail', 'groupId', groupId)\n            const localVarPath = `/profiling/group/detail/{groupId}`\n                .replace(`{${\"groupId\"}}`, encodeURIComponent(String(groupId)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * List all profiling groups\n         * @summary List all profiling groups\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getProfilingGroups: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/profiling/group/list`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get all Scheduling instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getSchedulingTopology: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topology/scheduling`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get location labels of all TiKV / TiFlash instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getStoreLocationTopology: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topology/store_location`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get all TiKV / TiFlash instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getStoreTopology: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topology/store`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get all TSO instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getTSOTopology: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topology/tso`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get all TiCDC instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getTiCDCTopology: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topology/ticdc`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get all TiDB instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getTiDBTopology: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topology/tidb`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get all TiProxy instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getTiProxyTopology: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topology/tiproxy`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get information about this TiDB Dashboard\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        infoGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/info/info`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary List all databases\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        infoListDatabases: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/info/databases`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary List tables by database name\n         * @param {string} [databaseName] Database name\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        infoListTables: async (databaseName?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/info/tables`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (databaseName !== undefined) {\n                localVarQueryParameter['database_name'] = databaseName;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get information about current session\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        infoWhoami: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/info/whoami`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get Key Visual Dynamic Config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        keyvisualConfigGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/keyvisual/config`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Set Key Visual Dynamic Config\n         * @param {ConfigKeyVisualConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        keyvisualConfigPut: async (request: ConfigKeyVisualConfig, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('keyvisualConfigPut', 'request', request)\n            const localVarPath = `/keyvisual/config`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Heatmaps in a given range to visualize TiKV usage\n         * @summary Key Visual Heatmaps\n         * @param {string} [startkey] The start of the key range\n         * @param {string} [endkey] The end of the key range\n         * @param {number} [starttime] The start of the time range (Unix)\n         * @param {number} [endtime] The end of the time range (Unix)\n         * @param {'written_bytes' | 'read_bytes' | 'written_keys' | 'read_keys' | 'integration'} [type] Main types of data\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        keyvisualHeatmapsGet: async (startkey?: string, endkey?: string, starttime?: number, endtime?: number, type?: 'written_bytes' | 'read_bytes' | 'written_keys' | 'read_keys' | 'integration', options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/keyvisual/heatmaps`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (startkey !== undefined) {\n                localVarQueryParameter['startkey'] = startkey;\n            }\n\n            if (endkey !== undefined) {\n                localVarQueryParameter['endkey'] = endkey;\n            }\n\n            if (starttime !== undefined) {\n                localVarQueryParameter['starttime'] = starttime;\n            }\n\n            if (endtime !== undefined) {\n                localVarQueryParameter['endtime'] = endtime;\n            }\n\n            if (type !== undefined) {\n                localVarQueryParameter['type'] = type;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Generate a download token for downloading logs\n         * @param {Array<string>} [id] task id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsDownloadAcquireTokenGet: async (id?: Array<string>, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/logs/download/acquire_token`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (id) {\n                localVarQueryParameter['id'] = id.join(COLLECTION_FORMATS.csv);\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Download logs\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsDownloadGet: async (token: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'token' is not null or undefined\n            assertParamExists('logsDownloadGet', 'token', token)\n            const localVarPath = `/logs/download`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            if (token !== undefined) {\n                localVarQueryParameter['token'] = token;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Create and run a new log search task group\n         * @param {LogsearchCreateTaskGroupRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupPut: async (request: LogsearchCreateTaskGroupRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('logsTaskgroupPut', 'request', request)\n            const localVarPath = `/logs/taskgroup`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary List all log search task groups\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupsGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/logs/taskgroups`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Cancel running tasks in a log search task group\n         * @param {string} id task group id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupsIdCancelPost: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'id' is not null or undefined\n            assertParamExists('logsTaskgroupsIdCancelPost', 'id', id)\n            const localVarPath = `/logs/taskgroups/{id}/cancel`\n                .replace(`{${\"id\"}}`, encodeURIComponent(String(id)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Delete a log search task group\n         * @param {string} id task group id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupsIdDelete: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'id' is not null or undefined\n            assertParamExists('logsTaskgroupsIdDelete', 'id', id)\n            const localVarPath = `/logs/taskgroups/{id}`\n                .replace(`{${\"id\"}}`, encodeURIComponent(String(id)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary List tasks in a log search task group\n         * @param {string} id Task Group ID\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupsIdGet: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'id' is not null or undefined\n            assertParamExists('logsTaskgroupsIdGet', 'id', id)\n            const localVarPath = `/logs/taskgroups/{id}`\n                .replace(`{${\"id\"}}`, encodeURIComponent(String(id)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Preview a log search task group\n         * @param {string} id task group id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupsIdPreviewGet: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'id' is not null or undefined\n            assertParamExists('logsTaskgroupsIdPreviewGet', 'id', id)\n            const localVarPath = `/logs/taskgroups/{id}/preview`\n                .replace(`{${\"id\"}}`, encodeURIComponent(String(id)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Retry failed tasks in a log search task group\n         * @param {string} id task group id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupsIdRetryPost: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'id' is not null or undefined\n            assertParamExists('logsTaskgroupsIdRetryPost', 'id', id)\n            const localVarPath = `/logs/taskgroups/{id}/retry`\n                .replace(`{${\"id\"}}`, encodeURIComponent(String(id)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get the Prometheus address cluster config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        metricsGetPromAddress: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/metrics/prom_address`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Query metrics in the given range\n         * @summary Query metrics\n         * @param {number} [endTimeSec] \n         * @param {string} [query] \n         * @param {number} [startTimeSec] \n         * @param {number} [stepSec] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        metricsQueryGet: async (endTimeSec?: number, query?: string, startTimeSec?: number, stepSec?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/metrics/query`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (endTimeSec !== undefined) {\n                localVarQueryParameter['end_time_sec'] = endTimeSec;\n            }\n\n            if (query !== undefined) {\n                localVarQueryParameter['query'] = query;\n            }\n\n            if (startTimeSec !== undefined) {\n                localVarQueryParameter['start_time_sec'] = startTimeSec;\n            }\n\n            if (stepSec !== undefined) {\n                localVarQueryParameter['step_sec'] = stepSec;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Set or clear the customized Prometheus address\n         * @param {MetricsPutCustomPromAddressRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        metricsSetCustomPromAddress: async (request: MetricsPutCustomPromAddressRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('metricsSetCustomPromAddress', 'request', request)\n            const localVarPath = `/metrics/prom_address`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get Profiling Dynamic Config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        profilingConfigGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/profiling/config`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Set Profiling Dynamic Config\n         * @param {ConfigProfilingConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        profilingConfigPut: async (request: ConfigProfilingConfig, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('profilingConfigPut', 'request', request)\n            const localVarPath = `/profiling/config`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Run statements\n         * @param {QueryeditorRunRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        queryEditorRun: async (request: QueryeditorRunRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('queryEditorRun', 'request', request)\n            const localVarPath = `/query_editor/run`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get calibrate of Resource Groups by actual workload\n         * @param {number} [endTime] \n         * @param {number} [startTime] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        resourceManagerCalibrateActualGet: async (endTime?: number, startTime?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/resource_manager/calibrate/actual`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (endTime !== undefined) {\n                localVarQueryParameter['end_time'] = endTime;\n            }\n\n            if (startTime !== undefined) {\n                localVarQueryParameter['start_time'] = startTime;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get calibrate of Resource Groups by hardware deployment\n         * @param {string} workload workload\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        resourceManagerCalibrateHardwareGet: async (workload: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'workload' is not null or undefined\n            assertParamExists('resourceManagerCalibrateHardwareGet', 'workload', workload)\n            const localVarPath = `/resource_manager/calibrate/hardware`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (workload !== undefined) {\n                localVarQueryParameter['workload'] = workload;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get Resource Control enable config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        resourceManagerConfigGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/resource_manager/config`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get Information of Resource Groups\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        resourceManagerInformationGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/resource_manager/information`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary List all resource groups\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        resourceManagerInformationGroupNamesGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/resource_manager/information/group_names`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Get available field names by slowquery table columns\n         * @summary Get available field names\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        slowQueryAvailableFieldsGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/slow_query/available_fields`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get details of a slow query\n         * @param {string} [connectId] TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\n         * @param {string} [digest] \n         * @param {number} [timestamp] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        slowQueryDetailGet: async (connectId?: string, digest?: string, timestamp?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/slow_query/detail`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (connectId !== undefined) {\n                localVarQueryParameter['connect_id'] = connectId;\n            }\n\n            if (digest !== undefined) {\n                localVarQueryParameter['digest'] = digest;\n            }\n\n            if (timestamp !== undefined) {\n                localVarQueryParameter['timestamp'] = timestamp;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Download slow query statements\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        slowQueryDownloadGet: async (token: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'token' is not null or undefined\n            assertParamExists('slowQueryDownloadGet', 'token', token)\n            const localVarPath = `/slow_query/download`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            if (token !== undefined) {\n                localVarQueryParameter['token'] = token;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Generate a download token for exported slow query statements\n         * @param {SlowqueryGetListRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        slowQueryDownloadTokenPost: async (request: SlowqueryGetListRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('slowQueryDownloadTokenPost', 'request', request)\n            const localVarPath = `/slow_query/download/token`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary List all slow queries\n         * @param {number} [beginTime] \n         * @param {Array<string>} [db] \n         * @param {boolean} [desc] \n         * @param {string} [digest] \n         * @param {number} [endTime] \n         * @param {string} [fields] example: \\&quot;Query,Digest\\&quot;\n         * @param {number} [limit] \n         * @param {string} [orderBy] \n         * @param {Array<string>} [plans] for showing slow queries in the statement detail page\n         * @param {Array<string>} [resourceGroup] \n         * @param {string} [text] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        slowQueryListGet: async (beginTime?: number, db?: Array<string>, desc?: boolean, digest?: string, endTime?: number, fields?: string, limit?: number, orderBy?: string, plans?: Array<string>, resourceGroup?: Array<string>, text?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/slow_query/list`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (beginTime !== undefined) {\n                localVarQueryParameter['begin_time'] = beginTime;\n            }\n\n            if (db) {\n                localVarQueryParameter['db'] = db;\n            }\n\n            if (desc !== undefined) {\n                localVarQueryParameter['desc'] = desc;\n            }\n\n            if (digest !== undefined) {\n                localVarQueryParameter['digest'] = digest;\n            }\n\n            if (endTime !== undefined) {\n                localVarQueryParameter['end_time'] = endTime;\n            }\n\n            if (fields !== undefined) {\n                localVarQueryParameter['fields'] = fields;\n            }\n\n            if (limit !== undefined) {\n                localVarQueryParameter['limit'] = limit;\n            }\n\n            if (orderBy !== undefined) {\n                localVarQueryParameter['orderBy'] = orderBy;\n            }\n\n            if (plans) {\n                localVarQueryParameter['plans'] = plans;\n            }\n\n            if (resourceGroup) {\n                localVarQueryParameter['resource_group'] = resourceGroup;\n            }\n\n            if (text !== undefined) {\n                localVarQueryParameter['text'] = text;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Start a profiling task group\n         * @summary Start profiling\n         * @param {ProfilingStartRequest} req profiling request\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        startProfiling: async (req: ProfilingStartRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'req' is not null or undefined\n            assertParamExists('startProfiling', 'req', req)\n            const localVarPath = `/profiling/group/start`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(req, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * Get available field names by statements table columns\n         * @summary Get available field names\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsAvailableFieldsGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/statements/available_fields`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get statement configurations\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsConfigGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/statements/config`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Update statement configurations\n         * @param {StatementEditableConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsConfigPost: async (request: StatementEditableConfig, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('statementsConfigPost', 'request', request)\n            const localVarPath = `/statements/config`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Download statements\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsDownloadGet: async (token: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'token' is not null or undefined\n            assertParamExists('statementsDownloadGet', 'token', token)\n            const localVarPath = `/statements/download`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            if (token !== undefined) {\n                localVarQueryParameter['token'] = token;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Generate a download token for exported statements\n         * @param {StatementGetStatementsRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsDownloadTokenPost: async (request: StatementGetStatementsRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('statementsDownloadTokenPost', 'request', request)\n            const localVarPath = `/statements/download/token`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get a list of statements\n         * @param {number} [beginTime] \n         * @param {number} [endTime] \n         * @param {string} [fields] \n         * @param {Array<string>} [resourceGroups] \n         * @param {Array<string>} [schemas] \n         * @param {Array<string>} [stmtTypes] \n         * @param {string} [text] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsListGet: async (beginTime?: number, endTime?: number, fields?: string, resourceGroups?: Array<string>, schemas?: Array<string>, stmtTypes?: Array<string>, text?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/statements/list`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (beginTime !== undefined) {\n                localVarQueryParameter['begin_time'] = beginTime;\n            }\n\n            if (endTime !== undefined) {\n                localVarQueryParameter['end_time'] = endTime;\n            }\n\n            if (fields !== undefined) {\n                localVarQueryParameter['fields'] = fields;\n            }\n\n            if (resourceGroups) {\n                localVarQueryParameter['resource_groups'] = resourceGroups;\n            }\n\n            if (schemas) {\n                localVarQueryParameter['schemas'] = schemas;\n            }\n\n            if (stmtTypes) {\n                localVarQueryParameter['stmt_types'] = stmtTypes;\n            }\n\n            if (text !== undefined) {\n                localVarQueryParameter['text'] = text;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Drop all manually created bindings for a statement\n         * @param {string} sqlDigest query template ID (a.k.a. sql digest)\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanBindingDelete: async (sqlDigest: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'sqlDigest' is not null or undefined\n            assertParamExists('statementsPlanBindingDelete', 'sqlDigest', sqlDigest)\n            const localVarPath = `/statements/plan/binding`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (sqlDigest !== undefined) {\n                localVarQueryParameter['sql_digest'] = sqlDigest;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get the bound plan digest (if exists) of a statement\n         * @param {string} sqlDigest query template id\n         * @param {number} beginTime begin time\n         * @param {number} endTime end time\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanBindingGet: async (sqlDigest: string, beginTime: number, endTime: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'sqlDigest' is not null or undefined\n            assertParamExists('statementsPlanBindingGet', 'sqlDigest', sqlDigest)\n            // verify required parameter 'beginTime' is not null or undefined\n            assertParamExists('statementsPlanBindingGet', 'beginTime', beginTime)\n            // verify required parameter 'endTime' is not null or undefined\n            assertParamExists('statementsPlanBindingGet', 'endTime', endTime)\n            const localVarPath = `/statements/plan/binding`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (sqlDigest !== undefined) {\n                localVarQueryParameter['sql_digest'] = sqlDigest;\n            }\n\n            if (beginTime !== undefined) {\n                localVarQueryParameter['begin_time'] = beginTime;\n            }\n\n            if (endTime !== undefined) {\n                localVarQueryParameter['end_time'] = endTime;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Create a binding for a statement and a plan\n         * @param {string} planDigest plan digest id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanBindingPost: async (planDigest: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'planDigest' is not null or undefined\n            assertParamExists('statementsPlanBindingPost', 'planDigest', planDigest)\n            const localVarPath = `/statements/plan/binding`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (planDigest !== undefined) {\n                localVarQueryParameter['plan_digest'] = planDigest;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get details of a statement in an execution plan\n         * @param {number} [beginTime] \n         * @param {string} [digest] \n         * @param {number} [endTime] \n         * @param {Array<string>} [plans] \n         * @param {string} [schemaName] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanDetailGet: async (beginTime?: number, digest?: string, endTime?: number, plans?: Array<string>, schemaName?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/statements/plan/detail`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (beginTime !== undefined) {\n                localVarQueryParameter['begin_time'] = beginTime;\n            }\n\n            if (digest !== undefined) {\n                localVarQueryParameter['digest'] = digest;\n            }\n\n            if (endTime !== undefined) {\n                localVarQueryParameter['end_time'] = endTime;\n            }\n\n            if (plans) {\n                localVarQueryParameter['plans'] = plans;\n            }\n\n            if (schemaName !== undefined) {\n                localVarQueryParameter['schema_name'] = schemaName;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get execution plans of a statement\n         * @param {number} [beginTime] \n         * @param {string} [digest] \n         * @param {number} [endTime] \n         * @param {string} [schemaName] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlansGet: async (beginTime?: number, digest?: string, endTime?: number, schemaName?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/statements/plans`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (beginTime !== undefined) {\n                localVarQueryParameter['begin_time'] = beginTime;\n            }\n\n            if (digest !== undefined) {\n                localVarQueryParameter['digest'] = digest;\n            }\n\n            if (endTime !== undefined) {\n                localVarQueryParameter['end_time'] = endTime;\n            }\n\n            if (schemaName !== undefined) {\n                localVarQueryParameter['schema_name'] = schemaName;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get all statement types\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsStmtTypesGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/statements/stmt_types`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Hide a TiDB instance\n         * @param {string} address ip:port\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topologyTidbAddressDelete: async (address: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'address' is not null or undefined\n            assertParamExists('topologyTidbAddressDelete', 'address', address)\n            const localVarPath = `/topology/tidb/{address}`\n                .replace(`{${\"address\"}}`, encodeURIComponent(String(address)));\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get Top SQL config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topsqlConfigGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topsql/config`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Update Top SQL config\n         * @param {TopsqlEditableConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topsqlConfigPost: async (request: TopsqlEditableConfig, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('topsqlConfigPost', 'request', request)\n            const localVarPath = `/topsql/config`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get TiKV network IO collection config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topsqlGetTiKVNetworkIOCollection: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topsql/tikv_network_io_collection`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get available instances\n         * @param {string} [dataSource] \n         * @param {string} [end] \n         * @param {string} [start] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topsqlInstancesGet: async (dataSource?: string, end?: string, start?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topsql/instances`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (dataSource !== undefined) {\n                localVarQueryParameter['data_source'] = dataSource;\n            }\n\n            if (end !== undefined) {\n                localVarQueryParameter['end'] = end;\n            }\n\n            if (start !== undefined) {\n                localVarQueryParameter['start'] = start;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get summaries\n         * @param {string} [dataSource] \n         * @param {string} [end] \n         * @param {string} [groupBy] \n         * @param {string} [instance] \n         * @param {string} [instanceType] \n         * @param {string} [orderBy] \n         * @param {string} [start] \n         * @param {string} [top] \n         * @param {string} [window] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topsqlSummaryGet: async (dataSource?: string, end?: string, groupBy?: string, instance?: string, instanceType?: string, orderBy?: string, start?: string, top?: string, window?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/topsql/summary`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (dataSource !== undefined) {\n                localVarQueryParameter['data_source'] = dataSource;\n            }\n\n            if (end !== undefined) {\n                localVarQueryParameter['end'] = end;\n            }\n\n            if (groupBy !== undefined) {\n                localVarQueryParameter['group_by'] = groupBy;\n            }\n\n            if (instance !== undefined) {\n                localVarQueryParameter['instance'] = instance;\n            }\n\n            if (instanceType !== undefined) {\n                localVarQueryParameter['instance_type'] = instanceType;\n            }\n\n            if (orderBy !== undefined) {\n                localVarQueryParameter['order_by'] = orderBy;\n            }\n\n            if (start !== undefined) {\n                localVarQueryParameter['start'] = start;\n            }\n\n            if (top !== undefined) {\n                localVarQueryParameter['top'] = top;\n            }\n\n            if (window !== undefined) {\n                localVarQueryParameter['window'] = window;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Update TiKV network IO collection config\n         * @param {TopsqlTikvNetworkIoCollectionConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topsqlUpdateTiKVNetworkIOCollection: async (request: TopsqlTikvNetworkIoCollectionConfig, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('topsqlUpdateTiKVNetworkIOCollection', 'request', request)\n            const localVarPath = `/topsql/tikv_network_io_collection`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get log in information, like supported authenticate types\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userGetLoginInfo: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/user/login_info`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get sign out info\n         * @param {string} [redirectUrl] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userGetSignOutInfo: async (redirectUrl?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/user/sign_out_info`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (redirectUrl !== undefined) {\n                localVarQueryParameter['redirect_url'] = redirectUrl;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Log in\n         * @param {UserAuthenticateForm} message Credentials\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userLogin: async (message: UserAuthenticateForm, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'message' is not null or undefined\n            assertParamExists('userLogin', 'message', message)\n            const localVarPath = `/user/login`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(message, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Reset encryption key to revoke all authorized codes\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userRevokeSession: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/user/share/revoke`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Create an impersonation\n         * @param {SsoCreateImpersonationRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userSSOCreateImpersonation: async (request: SsoCreateImpersonationRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('userSSOCreateImpersonation', 'request', request)\n            const localVarPath = `/user/sso/impersonation`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get SSO Auth URL\n         * @param {string} [codeVerifier] \n         * @param {string} [redirectUrl] \n         * @param {string} [state] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userSSOGetAuthURL: async (codeVerifier?: string, redirectUrl?: string, state?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/user/sso/auth_url`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            if (codeVerifier !== undefined) {\n                localVarQueryParameter['code_verifier'] = codeVerifier;\n            }\n\n            if (redirectUrl !== undefined) {\n                localVarQueryParameter['redirect_url'] = redirectUrl;\n            }\n\n            if (state !== undefined) {\n                localVarQueryParameter['state'] = state;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Get SSO config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userSSOGetConfig: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/user/sso/config`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary List all impersonations\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userSSOListImpersonations: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            const localVarPath = `/user/sso/impersonations/list`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Set SSO config\n         * @param {SsoSetConfigRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userSSOSetConfig: async (request: SsoSetConfigRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('userSSOSetConfig', 'request', request)\n            const localVarPath = `/user/sso/config`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * \n         * @summary Share current session and generate a sharing code\n         * @param {CodeShareRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userShareSession: async (request: CodeShareRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'request' is not null or undefined\n            assertParamExists('userShareSession', 'request', request)\n            const localVarPath = `/user/share/code`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n\n    \n            localVarHeaderParameter['Content-Type'] = 'application/json';\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n            localVarRequestOptions.data = serializeDataIfNeeded(request, localVarRequestOptions, configuration)\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         * View the finished profiling result of a task\n         * @summary View the result of a task\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        viewProfilingSingle: async (token: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'token' is not null or undefined\n            assertParamExists('viewProfilingSingle', 'token', token)\n            const localVarPath = `/profiling/single/view`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (token !== undefined) {\n                localVarQueryParameter['token'] = token;\n            }\n\n\n    \n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n    }\n};\n\n/**\n * DefaultApi - functional programming interface\n * @export\n */\nexport const DefaultApiFp = function(configuration?: Configuration) {\n    const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration)\n    return {\n        /**\n         * Cancel all profling tasks with a given group ID\n         * @summary Cancel all tasks with a given group ID\n         * @param {string} groupId group ID\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async cancelProfilingGroup(groupId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.cancelProfilingGroup(groupId, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get information of all hosts\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async clusterInfoGetHostsInfo(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ClusterinfoGetHostsInfoResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.clusterInfoGetHostsInfo(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get cluster statistics\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async clusterInfoGetStatistics(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ClusterinfoClusterStatistics>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.clusterInfoGetStatistics(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Edit a configuration\n         * @param {ConfigurationEditRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async configurationEdit(request: ConfigurationEditRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ConfigurationEditResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.configurationEdit(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get all configurations\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async configurationGetAll(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ConfigurationAllConfigItems>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.configurationGetAll(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get action token for download or view profile\n         * @param {string} q target query string\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async continuousProfilingActionTokenGet(q: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.continuousProfilingActionTokenGet(q, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get current scraping components\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async continuousProfilingComponentsGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ConprofComponent>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.continuousProfilingComponentsGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get Continuous Profiling Config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async continuousProfilingConfigGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ConprofNgMonitoringConfig>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.continuousProfilingConfigGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Update Continuous Profiling Config\n         * @param {ConprofNgMonitoringConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async continuousProfilingConfigPost(request: ConprofNgMonitoringConfig, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.continuousProfilingConfigPost(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Download Group Profile files\n         * @param {number} ts timestamp\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async continuousProfilingDownloadGet(ts: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.continuousProfilingDownloadGet(ts, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get Estimate Size\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async continuousProfilingEstimateSizeGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ConprofEstimateSizeRes>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.continuousProfilingEstimateSizeGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get Group Profile Detail\n         * @param {number} ts timestamp\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async continuousProfilingGroupProfileDetailGet(ts: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ConprofGroupProfileDetail>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.continuousProfilingGroupProfileDetailGet(ts, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get Group Profiles\n         * @param {number} [beginTime] \n         * @param {number} [endTime] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async continuousProfilingGroupProfilesGet(beginTime?: number, endTime?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ConprofGroupProfiles>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.continuousProfilingGroupProfilesGet(beginTime, endTime, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary View Single Profile files\n         * @param {string} [address] \n         * @param {string} [component] \n         * @param {string} [profileType] \n         * @param {number} [ts] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async continuousProfilingSingleProfileViewGet(address?: string, component?: string, profileType?: string, ts?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.continuousProfilingSingleProfileViewGet(address, component, profileType, ts, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary List all deadlock records\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async deadlockListGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<DeadlockModel>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.deadlockListGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get all endpoints\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async debugAPIGetEndpoints(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<EndpointAPIDefinition>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.debugAPIGetEndpoints(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Send request remote endpoint and return a token for downloading results\n         * @param {EndpointRequestPayload} req request payload\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async debugAPIRequestEndpoint(req: EndpointRequestPayload, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.debugAPIRequestEndpoint(req, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Download a finished request result\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async debugApiDownloadGet(token: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.debugApiDownloadGet(token, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Delete all finished profiling tasks with a given group ID\n         * @summary Delete all tasks with a given group ID\n         * @param {string} groupId group ID\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async deleteProfilingGroup(groupId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.deleteProfilingGroup(groupId, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Generate sql diagnosis report\n         * @summary SQL diagnosis report\n         * @param {DiagnoseGenDiagnosisReportRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async diagnoseDiagnosisPost(request: DiagnoseGenDiagnosisReportRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DiagnoseTableDef>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.diagnoseDiagnosisPost(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Generate metrics relationship graph.\n         * @param {DiagnoseGenerateMetricsRelationRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async diagnoseGenerateMetricsRelationship(request: DiagnoseGenerateMetricsRelationRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.diagnoseGenerateMetricsRelationship(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary View metrics relationship graph.\n         * @param {string} token token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async diagnoseMetricsRelationViewGet(token: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.diagnoseMetricsRelationViewGet(token, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Get sql diagnosis reports history\n         * @summary SQL diagnosis reports history\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async diagnoseReportsGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<DiagnoseReport>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.diagnoseReportsGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Get sql diagnosis report data\n         * @summary SQL diagnosis report data\n         * @param {string} id report id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async diagnoseReportsIdDataJsGet(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.diagnoseReportsIdDataJsGet(id, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Get sql diagnosis report HTML\n         * @summary SQL diagnosis report\n         * @param {string} id report id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async diagnoseReportsIdDetailGet(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.diagnoseReportsIdDetailGet(id, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Get diagnosis report status\n         * @summary Diagnosis report status\n         * @param {string} id report id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async diagnoseReportsIdStatusGet(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DiagnoseReport>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.diagnoseReportsIdStatusGet(id, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Generate sql diagnosis report\n         * @summary SQL diagnosis report\n         * @param {DiagnoseGenerateReportRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async diagnoseReportsPost(request: DiagnoseGenerateReportRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<number>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.diagnoseReportsPost(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Download all finished profiling results of a task group\n         * @summary Download all results of a task group\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async downloadProfilingGroup(token: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.downloadProfilingGroup(token, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Download the finished profiling result of a task\n         * @summary Download the result of a task\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async downloadProfilingSingle(token: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.downloadProfilingSingle(token, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Get token with a given group ID or task ID and action type\n         * @summary Get action token for download or view\n         * @param {string} [id] group or task ID\n         * @param {string} [action] action\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getActionToken(id?: string, action?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getActionToken(id, action, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get current alert count from AlertManager\n         * @param {string} address ip:port\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getAlertManagerCounts(address: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<number>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getAlertManagerCounts(address, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get AlertManager instance\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getAlertManagerTopology(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TopologyAlertManagerInfo>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getAlertManagerTopology(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get Grafana instance\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getGrafanaTopology(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TopologyGrafanaInfo>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getGrafanaTopology(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get all PD instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getPDTopology(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<TopologyPDInfo>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getPDTopology(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * List all profiling tasks with a given group ID\n         * @summary List all tasks with a given group ID\n         * @param {string} groupId group ID\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getProfilingGroupDetail(groupId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ProfilingGroupDetailResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getProfilingGroupDetail(groupId, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * List all profiling groups\n         * @summary List all profiling groups\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getProfilingGroups(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ProfilingTaskGroupModel>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getProfilingGroups(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get all Scheduling instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getSchedulingTopology(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<TopologySchedulingInfo>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getSchedulingTopology(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get location labels of all TiKV / TiFlash instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getStoreLocationTopology(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TopologyStoreLocation>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getStoreLocationTopology(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get all TiKV / TiFlash instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getStoreTopology(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ClusterinfoStoreTopologyResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getStoreTopology(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get all TSO instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getTSOTopology(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<TopologyTSOInfo>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getTSOTopology(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get all TiCDC instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getTiCDCTopology(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<TopologyTiCDCInfo>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getTiCDCTopology(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get all TiDB instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getTiDBTopology(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<TopologyTiDBInfo>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getTiDBTopology(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get all TiProxy instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async getTiProxyTopology(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<TopologyTiProxyInfo>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.getTiProxyTopology(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get information about this TiDB Dashboard\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async infoGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InfoInfoResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.infoGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary List all databases\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async infoListDatabases(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.infoListDatabases(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary List tables by database name\n         * @param {string} [databaseName] Database name\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async infoListTables(databaseName?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<InfoTableSchema>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.infoListTables(databaseName, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get information about current session\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async infoWhoami(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<InfoWhoAmIResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.infoWhoami(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get Key Visual Dynamic Config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async keyvisualConfigGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ConfigKeyVisualConfig>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.keyvisualConfigGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Set Key Visual Dynamic Config\n         * @param {ConfigKeyVisualConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async keyvisualConfigPut(request: ConfigKeyVisualConfig, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ConfigKeyVisualConfig>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.keyvisualConfigPut(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Heatmaps in a given range to visualize TiKV usage\n         * @summary Key Visual Heatmaps\n         * @param {string} [startkey] The start of the key range\n         * @param {string} [endkey] The end of the key range\n         * @param {number} [starttime] The start of the time range (Unix)\n         * @param {number} [endtime] The end of the time range (Unix)\n         * @param {'written_bytes' | 'read_bytes' | 'written_keys' | 'read_keys' | 'integration'} [type] Main types of data\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async keyvisualHeatmapsGet(startkey?: string, endkey?: string, starttime?: number, endtime?: number, type?: 'written_bytes' | 'read_bytes' | 'written_keys' | 'read_keys' | 'integration', options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<MatrixMatrix>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.keyvisualHeatmapsGet(startkey, endkey, starttime, endtime, type, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Generate a download token for downloading logs\n         * @param {Array<string>} [id] task id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async logsDownloadAcquireTokenGet(id?: Array<string>, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.logsDownloadAcquireTokenGet(id, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Download logs\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async logsDownloadGet(token: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.logsDownloadGet(token, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Create and run a new log search task group\n         * @param {LogsearchCreateTaskGroupRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async logsTaskgroupPut(request: LogsearchCreateTaskGroupRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<LogsearchTaskGroupResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.logsTaskgroupPut(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary List all log search task groups\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async logsTaskgroupsGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<LogsearchTaskGroupModel>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.logsTaskgroupsGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Cancel running tasks in a log search task group\n         * @param {string} id task group id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async logsTaskgroupsIdCancelPost(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.logsTaskgroupsIdCancelPost(id, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Delete a log search task group\n         * @param {string} id task group id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async logsTaskgroupsIdDelete(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.logsTaskgroupsIdDelete(id, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary List tasks in a log search task group\n         * @param {string} id Task Group ID\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async logsTaskgroupsIdGet(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<LogsearchTaskGroupResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.logsTaskgroupsIdGet(id, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Preview a log search task group\n         * @param {string} id task group id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async logsTaskgroupsIdPreviewGet(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<LogsearchPreviewModel>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.logsTaskgroupsIdPreviewGet(id, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Retry failed tasks in a log search task group\n         * @param {string} id task group id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async logsTaskgroupsIdRetryPost(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.logsTaskgroupsIdRetryPost(id, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get the Prometheus address cluster config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async metricsGetPromAddress(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<MetricsGetPromAddressConfigResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.metricsGetPromAddress(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Query metrics in the given range\n         * @summary Query metrics\n         * @param {number} [endTimeSec] \n         * @param {string} [query] \n         * @param {number} [startTimeSec] \n         * @param {number} [stepSec] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async metricsQueryGet(endTimeSec?: number, query?: string, startTimeSec?: number, stepSec?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<MetricsQueryResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.metricsQueryGet(endTimeSec, query, startTimeSec, stepSec, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Set or clear the customized Prometheus address\n         * @param {MetricsPutCustomPromAddressRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async metricsSetCustomPromAddress(request: MetricsPutCustomPromAddressRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<MetricsPutCustomPromAddressResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.metricsSetCustomPromAddress(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get Profiling Dynamic Config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async profilingConfigGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ConfigProfilingConfig>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.profilingConfigGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Set Profiling Dynamic Config\n         * @param {ConfigProfilingConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async profilingConfigPut(request: ConfigProfilingConfig, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ConfigProfilingConfig>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.profilingConfigPut(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Run statements\n         * @param {QueryeditorRunRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async queryEditorRun(request: QueryeditorRunRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<QueryeditorRunResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.queryEditorRun(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get calibrate of Resource Groups by actual workload\n         * @param {number} [endTime] \n         * @param {number} [startTime] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async resourceManagerCalibrateActualGet(endTime?: number, startTime?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ResourcemanagerCalibrateResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.resourceManagerCalibrateActualGet(endTime, startTime, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get calibrate of Resource Groups by hardware deployment\n         * @param {string} workload workload\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async resourceManagerCalibrateHardwareGet(workload: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ResourcemanagerCalibrateResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.resourceManagerCalibrateHardwareGet(workload, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get Resource Control enable config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async resourceManagerConfigGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ResourcemanagerGetConfigResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.resourceManagerConfigGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get Information of Resource Groups\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async resourceManagerInformationGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ResourcemanagerResourceInfoRowDef>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.resourceManagerInformationGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary List all resource groups\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async resourceManagerInformationGroupNamesGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.resourceManagerInformationGroupNamesGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Get available field names by slowquery table columns\n         * @summary Get available field names\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async slowQueryAvailableFieldsGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.slowQueryAvailableFieldsGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get details of a slow query\n         * @param {string} [connectId] TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\n         * @param {string} [digest] \n         * @param {number} [timestamp] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async slowQueryDetailGet(connectId?: string, digest?: string, timestamp?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SlowqueryModel>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.slowQueryDetailGet(connectId, digest, timestamp, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Download slow query statements\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async slowQueryDownloadGet(token: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.slowQueryDownloadGet(token, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Generate a download token for exported slow query statements\n         * @param {SlowqueryGetListRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async slowQueryDownloadTokenPost(request: SlowqueryGetListRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.slowQueryDownloadTokenPost(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary List all slow queries\n         * @param {number} [beginTime] \n         * @param {Array<string>} [db] \n         * @param {boolean} [desc] \n         * @param {string} [digest] \n         * @param {number} [endTime] \n         * @param {string} [fields] example: \\&quot;Query,Digest\\&quot;\n         * @param {number} [limit] \n         * @param {string} [orderBy] \n         * @param {Array<string>} [plans] for showing slow queries in the statement detail page\n         * @param {Array<string>} [resourceGroup] \n         * @param {string} [text] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async slowQueryListGet(beginTime?: number, db?: Array<string>, desc?: boolean, digest?: string, endTime?: number, fields?: string, limit?: number, orderBy?: string, plans?: Array<string>, resourceGroup?: Array<string>, text?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<SlowqueryModel>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.slowQueryListGet(beginTime, db, desc, digest, endTime, fields, limit, orderBy, plans, resourceGroup, text, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Start a profiling task group\n         * @summary Start profiling\n         * @param {ProfilingStartRequest} req profiling request\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async startProfiling(req: ProfilingStartRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ProfilingTaskGroupModel>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.startProfiling(req, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * Get available field names by statements table columns\n         * @summary Get available field names\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsAvailableFieldsGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsAvailableFieldsGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get statement configurations\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsConfigGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<StatementEditableConfig>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsConfigGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Update statement configurations\n         * @param {StatementEditableConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsConfigPost(request: StatementEditableConfig, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsConfigPost(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Download statements\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsDownloadGet(token: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsDownloadGet(token, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Generate a download token for exported statements\n         * @param {StatementGetStatementsRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsDownloadTokenPost(request: StatementGetStatementsRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsDownloadTokenPost(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get a list of statements\n         * @param {number} [beginTime] \n         * @param {number} [endTime] \n         * @param {string} [fields] \n         * @param {Array<string>} [resourceGroups] \n         * @param {Array<string>} [schemas] \n         * @param {Array<string>} [stmtTypes] \n         * @param {string} [text] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsListGet(beginTime?: number, endTime?: number, fields?: string, resourceGroups?: Array<string>, schemas?: Array<string>, stmtTypes?: Array<string>, text?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<StatementModel>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsListGet(beginTime, endTime, fields, resourceGroups, schemas, stmtTypes, text, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Drop all manually created bindings for a statement\n         * @param {string} sqlDigest query template ID (a.k.a. sql digest)\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsPlanBindingDelete(sqlDigest: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsPlanBindingDelete(sqlDigest, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get the bound plan digest (if exists) of a statement\n         * @param {string} sqlDigest query template id\n         * @param {number} beginTime begin time\n         * @param {number} endTime end time\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsPlanBindingGet(sqlDigest: string, beginTime: number, endTime: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<StatementBinding>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsPlanBindingGet(sqlDigest, beginTime, endTime, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Create a binding for a statement and a plan\n         * @param {string} planDigest plan digest id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsPlanBindingPost(planDigest: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsPlanBindingPost(planDigest, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get details of a statement in an execution plan\n         * @param {number} [beginTime] \n         * @param {string} [digest] \n         * @param {number} [endTime] \n         * @param {Array<string>} [plans] \n         * @param {string} [schemaName] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsPlanDetailGet(beginTime?: number, digest?: string, endTime?: number, plans?: Array<string>, schemaName?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<StatementModel>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsPlanDetailGet(beginTime, digest, endTime, plans, schemaName, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get execution plans of a statement\n         * @param {number} [beginTime] \n         * @param {string} [digest] \n         * @param {number} [endTime] \n         * @param {string} [schemaName] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsPlansGet(beginTime?: number, digest?: string, endTime?: number, schemaName?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<StatementModel>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsPlansGet(beginTime, digest, endTime, schemaName, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get all statement types\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsStmtTypesGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<string>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsStmtTypesGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Hide a TiDB instance\n         * @param {string} address ip:port\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async topologyTidbAddressDelete(address: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.topologyTidbAddressDelete(address, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get Top SQL config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async topsqlConfigGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TopsqlEditableConfig>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.topsqlConfigGet(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Update Top SQL config\n         * @param {TopsqlEditableConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async topsqlConfigPost(request: TopsqlEditableConfig, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.topsqlConfigPost(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get TiKV network IO collection config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async topsqlGetTiKVNetworkIOCollection(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TopsqlTikvNetworkIoCollectionConfig>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.topsqlGetTiKVNetworkIOCollection(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get available instances\n         * @param {string} [dataSource] \n         * @param {string} [end] \n         * @param {string} [start] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async topsqlInstancesGet(dataSource?: string, end?: string, start?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TopsqlInstanceResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.topsqlInstancesGet(dataSource, end, start, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get summaries\n         * @param {string} [dataSource] \n         * @param {string} [end] \n         * @param {string} [groupBy] \n         * @param {string} [instance] \n         * @param {string} [instanceType] \n         * @param {string} [orderBy] \n         * @param {string} [start] \n         * @param {string} [top] \n         * @param {string} [window] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async topsqlSummaryGet(dataSource?: string, end?: string, groupBy?: string, instance?: string, instanceType?: string, orderBy?: string, start?: string, top?: string, window?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TopsqlSummaryResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.topsqlSummaryGet(dataSource, end, groupBy, instance, instanceType, orderBy, start, top, window, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Update TiKV network IO collection config\n         * @param {TopsqlTikvNetworkIoCollectionConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async topsqlUpdateTiKVNetworkIOCollection(request: TopsqlTikvNetworkIoCollectionConfig, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<TopsqlUpdateTikvNetworkIoCollectionResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.topsqlUpdateTiKVNetworkIOCollection(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get log in information, like supported authenticate types\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async userGetLoginInfo(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserGetLoginInfoResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.userGetLoginInfo(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get sign out info\n         * @param {string} [redirectUrl] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async userGetSignOutInfo(redirectUrl?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserSignOutInfo>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.userGetSignOutInfo(redirectUrl, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Log in\n         * @param {UserAuthenticateForm} message Credentials\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async userLogin(message: UserAuthenticateForm, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<UserTokenResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.userLogin(message, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Reset encryption key to revoke all authorized codes\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async userRevokeSession(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.userRevokeSession(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Create an impersonation\n         * @param {SsoCreateImpersonationRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async userSSOCreateImpersonation(request: SsoCreateImpersonationRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SsoSSOImpersonationModel>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.userSSOCreateImpersonation(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get SSO Auth URL\n         * @param {string} [codeVerifier] \n         * @param {string} [redirectUrl] \n         * @param {string} [state] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async userSSOGetAuthURL(codeVerifier?: string, redirectUrl?: string, state?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.userSSOGetAuthURL(codeVerifier, redirectUrl, state, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Get SSO config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async userSSOGetConfig(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ConfigSSOCoreConfig>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.userSSOGetConfig(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary List all impersonations\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async userSSOListImpersonations(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<SsoSSOImpersonationModel>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.userSSOListImpersonations(options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Set SSO config\n         * @param {SsoSetConfigRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async userSSOSetConfig(request: SsoSetConfigRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ConfigSSOCoreConfig>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.userSSOSetConfig(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * \n         * @summary Share current session and generate a sharing code\n         * @param {CodeShareRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async userShareSession(request: CodeShareRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<CodeShareResponse>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.userShareSession(request, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         * View the finished profiling result of a task\n         * @summary View the result of a task\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async viewProfilingSingle(token: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.viewProfilingSingle(token, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n    }\n};\n\n/**\n * DefaultApi - factory interface\n * @export\n */\nexport const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {\n    const localVarFp = DefaultApiFp(configuration)\n    return {\n        /**\n         * Cancel all profling tasks with a given group ID\n         * @summary Cancel all tasks with a given group ID\n         * @param {string} groupId group ID\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        cancelProfilingGroup(groupId: string, options?: any): AxiosPromise<object> {\n            return localVarFp.cancelProfilingGroup(groupId, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get information of all hosts\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        clusterInfoGetHostsInfo(options?: any): AxiosPromise<ClusterinfoGetHostsInfoResponse> {\n            return localVarFp.clusterInfoGetHostsInfo(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get cluster statistics\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        clusterInfoGetStatistics(options?: any): AxiosPromise<ClusterinfoClusterStatistics> {\n            return localVarFp.clusterInfoGetStatistics(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Edit a configuration\n         * @param {ConfigurationEditRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        configurationEdit(request: ConfigurationEditRequest, options?: any): AxiosPromise<ConfigurationEditResponse> {\n            return localVarFp.configurationEdit(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get all configurations\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        configurationGetAll(options?: any): AxiosPromise<ConfigurationAllConfigItems> {\n            return localVarFp.configurationGetAll(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get action token for download or view profile\n         * @param {string} q target query string\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingActionTokenGet(q: string, options?: any): AxiosPromise<string> {\n            return localVarFp.continuousProfilingActionTokenGet(q, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get current scraping components\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingComponentsGet(options?: any): AxiosPromise<Array<ConprofComponent>> {\n            return localVarFp.continuousProfilingComponentsGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get Continuous Profiling Config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingConfigGet(options?: any): AxiosPromise<ConprofNgMonitoringConfig> {\n            return localVarFp.continuousProfilingConfigGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Update Continuous Profiling Config\n         * @param {ConprofNgMonitoringConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingConfigPost(request: ConprofNgMonitoringConfig, options?: any): AxiosPromise<string> {\n            return localVarFp.continuousProfilingConfigPost(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Download Group Profile files\n         * @param {number} ts timestamp\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingDownloadGet(ts: number, options?: any): AxiosPromise<void> {\n            return localVarFp.continuousProfilingDownloadGet(ts, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get Estimate Size\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingEstimateSizeGet(options?: any): AxiosPromise<ConprofEstimateSizeRes> {\n            return localVarFp.continuousProfilingEstimateSizeGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get Group Profile Detail\n         * @param {number} ts timestamp\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingGroupProfileDetailGet(ts: number, options?: any): AxiosPromise<ConprofGroupProfileDetail> {\n            return localVarFp.continuousProfilingGroupProfileDetailGet(ts, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get Group Profiles\n         * @param {number} [beginTime] \n         * @param {number} [endTime] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingGroupProfilesGet(beginTime?: number, endTime?: number, options?: any): AxiosPromise<Array<ConprofGroupProfiles>> {\n            return localVarFp.continuousProfilingGroupProfilesGet(beginTime, endTime, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary View Single Profile files\n         * @param {string} [address] \n         * @param {string} [component] \n         * @param {string} [profileType] \n         * @param {number} [ts] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        continuousProfilingSingleProfileViewGet(address?: string, component?: string, profileType?: string, ts?: number, options?: any): AxiosPromise<void> {\n            return localVarFp.continuousProfilingSingleProfileViewGet(address, component, profileType, ts, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary List all deadlock records\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        deadlockListGet(options?: any): AxiosPromise<Array<DeadlockModel>> {\n            return localVarFp.deadlockListGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get all endpoints\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        debugAPIGetEndpoints(options?: any): AxiosPromise<Array<EndpointAPIDefinition>> {\n            return localVarFp.debugAPIGetEndpoints(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Send request remote endpoint and return a token for downloading results\n         * @param {EndpointRequestPayload} req request payload\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        debugAPIRequestEndpoint(req: EndpointRequestPayload, options?: any): AxiosPromise<string> {\n            return localVarFp.debugAPIRequestEndpoint(req, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Download a finished request result\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        debugApiDownloadGet(token: string, options?: any): AxiosPromise<string> {\n            return localVarFp.debugApiDownloadGet(token, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Delete all finished profiling tasks with a given group ID\n         * @summary Delete all tasks with a given group ID\n         * @param {string} groupId group ID\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        deleteProfilingGroup(groupId: string, options?: any): AxiosPromise<object> {\n            return localVarFp.deleteProfilingGroup(groupId, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Generate sql diagnosis report\n         * @summary SQL diagnosis report\n         * @param {DiagnoseGenDiagnosisReportRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseDiagnosisPost(request: DiagnoseGenDiagnosisReportRequest, options?: any): AxiosPromise<DiagnoseTableDef> {\n            return localVarFp.diagnoseDiagnosisPost(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Generate metrics relationship graph.\n         * @param {DiagnoseGenerateMetricsRelationRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseGenerateMetricsRelationship(request: DiagnoseGenerateMetricsRelationRequest, options?: any): AxiosPromise<string> {\n            return localVarFp.diagnoseGenerateMetricsRelationship(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary View metrics relationship graph.\n         * @param {string} token token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseMetricsRelationViewGet(token: string, options?: any): AxiosPromise<void> {\n            return localVarFp.diagnoseMetricsRelationViewGet(token, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Get sql diagnosis reports history\n         * @summary SQL diagnosis reports history\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseReportsGet(options?: any): AxiosPromise<Array<DiagnoseReport>> {\n            return localVarFp.diagnoseReportsGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Get sql diagnosis report data\n         * @summary SQL diagnosis report data\n         * @param {string} id report id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseReportsIdDataJsGet(id: string, options?: any): AxiosPromise<string> {\n            return localVarFp.diagnoseReportsIdDataJsGet(id, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Get sql diagnosis report HTML\n         * @summary SQL diagnosis report\n         * @param {string} id report id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseReportsIdDetailGet(id: string, options?: any): AxiosPromise<string> {\n            return localVarFp.diagnoseReportsIdDetailGet(id, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Get diagnosis report status\n         * @summary Diagnosis report status\n         * @param {string} id report id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseReportsIdStatusGet(id: string, options?: any): AxiosPromise<DiagnoseReport> {\n            return localVarFp.diagnoseReportsIdStatusGet(id, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Generate sql diagnosis report\n         * @summary SQL diagnosis report\n         * @param {DiagnoseGenerateReportRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        diagnoseReportsPost(request: DiagnoseGenerateReportRequest, options?: any): AxiosPromise<number> {\n            return localVarFp.diagnoseReportsPost(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Download all finished profiling results of a task group\n         * @summary Download all results of a task group\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        downloadProfilingGroup(token: string, options?: any): AxiosPromise<void> {\n            return localVarFp.downloadProfilingGroup(token, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Download the finished profiling result of a task\n         * @summary Download the result of a task\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        downloadProfilingSingle(token: string, options?: any): AxiosPromise<void> {\n            return localVarFp.downloadProfilingSingle(token, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Get token with a given group ID or task ID and action type\n         * @summary Get action token for download or view\n         * @param {string} [id] group or task ID\n         * @param {string} [action] action\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getActionToken(id?: string, action?: string, options?: any): AxiosPromise<string> {\n            return localVarFp.getActionToken(id, action, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get current alert count from AlertManager\n         * @param {string} address ip:port\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getAlertManagerCounts(address: string, options?: any): AxiosPromise<number> {\n            return localVarFp.getAlertManagerCounts(address, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get AlertManager instance\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getAlertManagerTopology(options?: any): AxiosPromise<TopologyAlertManagerInfo> {\n            return localVarFp.getAlertManagerTopology(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get Grafana instance\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getGrafanaTopology(options?: any): AxiosPromise<TopologyGrafanaInfo> {\n            return localVarFp.getGrafanaTopology(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get all PD instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getPDTopology(options?: any): AxiosPromise<Array<TopologyPDInfo>> {\n            return localVarFp.getPDTopology(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * List all profiling tasks with a given group ID\n         * @summary List all tasks with a given group ID\n         * @param {string} groupId group ID\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getProfilingGroupDetail(groupId: string, options?: any): AxiosPromise<ProfilingGroupDetailResponse> {\n            return localVarFp.getProfilingGroupDetail(groupId, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * List all profiling groups\n         * @summary List all profiling groups\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getProfilingGroups(options?: any): AxiosPromise<Array<ProfilingTaskGroupModel>> {\n            return localVarFp.getProfilingGroups(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get all Scheduling instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getSchedulingTopology(options?: any): AxiosPromise<Array<TopologySchedulingInfo>> {\n            return localVarFp.getSchedulingTopology(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get location labels of all TiKV / TiFlash instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getStoreLocationTopology(options?: any): AxiosPromise<TopologyStoreLocation> {\n            return localVarFp.getStoreLocationTopology(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get all TiKV / TiFlash instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getStoreTopology(options?: any): AxiosPromise<ClusterinfoStoreTopologyResponse> {\n            return localVarFp.getStoreTopology(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get all TSO instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getTSOTopology(options?: any): AxiosPromise<Array<TopologyTSOInfo>> {\n            return localVarFp.getTSOTopology(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get all TiCDC instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getTiCDCTopology(options?: any): AxiosPromise<Array<TopologyTiCDCInfo>> {\n            return localVarFp.getTiCDCTopology(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get all TiDB instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getTiDBTopology(options?: any): AxiosPromise<Array<TopologyTiDBInfo>> {\n            return localVarFp.getTiDBTopology(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get all TiProxy instances\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        getTiProxyTopology(options?: any): AxiosPromise<Array<TopologyTiProxyInfo>> {\n            return localVarFp.getTiProxyTopology(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get information about this TiDB Dashboard\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        infoGet(options?: any): AxiosPromise<InfoInfoResponse> {\n            return localVarFp.infoGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary List all databases\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        infoListDatabases(options?: any): AxiosPromise<Array<string>> {\n            return localVarFp.infoListDatabases(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary List tables by database name\n         * @param {string} [databaseName] Database name\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        infoListTables(databaseName?: string, options?: any): AxiosPromise<Array<InfoTableSchema>> {\n            return localVarFp.infoListTables(databaseName, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get information about current session\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        infoWhoami(options?: any): AxiosPromise<InfoWhoAmIResponse> {\n            return localVarFp.infoWhoami(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get Key Visual Dynamic Config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        keyvisualConfigGet(options?: any): AxiosPromise<ConfigKeyVisualConfig> {\n            return localVarFp.keyvisualConfigGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Set Key Visual Dynamic Config\n         * @param {ConfigKeyVisualConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        keyvisualConfigPut(request: ConfigKeyVisualConfig, options?: any): AxiosPromise<ConfigKeyVisualConfig> {\n            return localVarFp.keyvisualConfigPut(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Heatmaps in a given range to visualize TiKV usage\n         * @summary Key Visual Heatmaps\n         * @param {string} [startkey] The start of the key range\n         * @param {string} [endkey] The end of the key range\n         * @param {number} [starttime] The start of the time range (Unix)\n         * @param {number} [endtime] The end of the time range (Unix)\n         * @param {'written_bytes' | 'read_bytes' | 'written_keys' | 'read_keys' | 'integration'} [type] Main types of data\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        keyvisualHeatmapsGet(startkey?: string, endkey?: string, starttime?: number, endtime?: number, type?: 'written_bytes' | 'read_bytes' | 'written_keys' | 'read_keys' | 'integration', options?: any): AxiosPromise<MatrixMatrix> {\n            return localVarFp.keyvisualHeatmapsGet(startkey, endkey, starttime, endtime, type, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Generate a download token for downloading logs\n         * @param {Array<string>} [id] task id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsDownloadAcquireTokenGet(id?: Array<string>, options?: any): AxiosPromise<string> {\n            return localVarFp.logsDownloadAcquireTokenGet(id, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Download logs\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsDownloadGet(token: string, options?: any): AxiosPromise<void> {\n            return localVarFp.logsDownloadGet(token, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Create and run a new log search task group\n         * @param {LogsearchCreateTaskGroupRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupPut(request: LogsearchCreateTaskGroupRequest, options?: any): AxiosPromise<LogsearchTaskGroupResponse> {\n            return localVarFp.logsTaskgroupPut(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary List all log search task groups\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupsGet(options?: any): AxiosPromise<Array<LogsearchTaskGroupModel>> {\n            return localVarFp.logsTaskgroupsGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Cancel running tasks in a log search task group\n         * @param {string} id task group id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupsIdCancelPost(id: string, options?: any): AxiosPromise<object> {\n            return localVarFp.logsTaskgroupsIdCancelPost(id, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Delete a log search task group\n         * @param {string} id task group id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupsIdDelete(id: string, options?: any): AxiosPromise<object> {\n            return localVarFp.logsTaskgroupsIdDelete(id, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary List tasks in a log search task group\n         * @param {string} id Task Group ID\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupsIdGet(id: string, options?: any): AxiosPromise<LogsearchTaskGroupResponse> {\n            return localVarFp.logsTaskgroupsIdGet(id, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Preview a log search task group\n         * @param {string} id task group id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupsIdPreviewGet(id: string, options?: any): AxiosPromise<Array<LogsearchPreviewModel>> {\n            return localVarFp.logsTaskgroupsIdPreviewGet(id, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Retry failed tasks in a log search task group\n         * @param {string} id task group id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        logsTaskgroupsIdRetryPost(id: string, options?: any): AxiosPromise<object> {\n            return localVarFp.logsTaskgroupsIdRetryPost(id, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get the Prometheus address cluster config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        metricsGetPromAddress(options?: any): AxiosPromise<MetricsGetPromAddressConfigResponse> {\n            return localVarFp.metricsGetPromAddress(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Query metrics in the given range\n         * @summary Query metrics\n         * @param {number} [endTimeSec] \n         * @param {string} [query] \n         * @param {number} [startTimeSec] \n         * @param {number} [stepSec] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        metricsQueryGet(endTimeSec?: number, query?: string, startTimeSec?: number, stepSec?: number, options?: any): AxiosPromise<MetricsQueryResponse> {\n            return localVarFp.metricsQueryGet(endTimeSec, query, startTimeSec, stepSec, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Set or clear the customized Prometheus address\n         * @param {MetricsPutCustomPromAddressRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        metricsSetCustomPromAddress(request: MetricsPutCustomPromAddressRequest, options?: any): AxiosPromise<MetricsPutCustomPromAddressResponse> {\n            return localVarFp.metricsSetCustomPromAddress(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get Profiling Dynamic Config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        profilingConfigGet(options?: any): AxiosPromise<ConfigProfilingConfig> {\n            return localVarFp.profilingConfigGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Set Profiling Dynamic Config\n         * @param {ConfigProfilingConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        profilingConfigPut(request: ConfigProfilingConfig, options?: any): AxiosPromise<ConfigProfilingConfig> {\n            return localVarFp.profilingConfigPut(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Run statements\n         * @param {QueryeditorRunRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        queryEditorRun(request: QueryeditorRunRequest, options?: any): AxiosPromise<QueryeditorRunResponse> {\n            return localVarFp.queryEditorRun(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get calibrate of Resource Groups by actual workload\n         * @param {number} [endTime] \n         * @param {number} [startTime] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        resourceManagerCalibrateActualGet(endTime?: number, startTime?: number, options?: any): AxiosPromise<ResourcemanagerCalibrateResponse> {\n            return localVarFp.resourceManagerCalibrateActualGet(endTime, startTime, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get calibrate of Resource Groups by hardware deployment\n         * @param {string} workload workload\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        resourceManagerCalibrateHardwareGet(workload: string, options?: any): AxiosPromise<ResourcemanagerCalibrateResponse> {\n            return localVarFp.resourceManagerCalibrateHardwareGet(workload, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get Resource Control enable config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        resourceManagerConfigGet(options?: any): AxiosPromise<ResourcemanagerGetConfigResponse> {\n            return localVarFp.resourceManagerConfigGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get Information of Resource Groups\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        resourceManagerInformationGet(options?: any): AxiosPromise<Array<ResourcemanagerResourceInfoRowDef>> {\n            return localVarFp.resourceManagerInformationGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary List all resource groups\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        resourceManagerInformationGroupNamesGet(options?: any): AxiosPromise<Array<string>> {\n            return localVarFp.resourceManagerInformationGroupNamesGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Get available field names by slowquery table columns\n         * @summary Get available field names\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        slowQueryAvailableFieldsGet(options?: any): AxiosPromise<Array<string>> {\n            return localVarFp.slowQueryAvailableFieldsGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get details of a slow query\n         * @param {string} [connectId] TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\n         * @param {string} [digest] \n         * @param {number} [timestamp] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        slowQueryDetailGet(connectId?: string, digest?: string, timestamp?: number, options?: any): AxiosPromise<SlowqueryModel> {\n            return localVarFp.slowQueryDetailGet(connectId, digest, timestamp, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Download slow query statements\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        slowQueryDownloadGet(token: string, options?: any): AxiosPromise<void> {\n            return localVarFp.slowQueryDownloadGet(token, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Generate a download token for exported slow query statements\n         * @param {SlowqueryGetListRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        slowQueryDownloadTokenPost(request: SlowqueryGetListRequest, options?: any): AxiosPromise<string> {\n            return localVarFp.slowQueryDownloadTokenPost(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary List all slow queries\n         * @param {number} [beginTime] \n         * @param {Array<string>} [db] \n         * @param {boolean} [desc] \n         * @param {string} [digest] \n         * @param {number} [endTime] \n         * @param {string} [fields] example: \\&quot;Query,Digest\\&quot;\n         * @param {number} [limit] \n         * @param {string} [orderBy] \n         * @param {Array<string>} [plans] for showing slow queries in the statement detail page\n         * @param {Array<string>} [resourceGroup] \n         * @param {string} [text] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        slowQueryListGet(beginTime?: number, db?: Array<string>, desc?: boolean, digest?: string, endTime?: number, fields?: string, limit?: number, orderBy?: string, plans?: Array<string>, resourceGroup?: Array<string>, text?: string, options?: any): AxiosPromise<Array<SlowqueryModel>> {\n            return localVarFp.slowQueryListGet(beginTime, db, desc, digest, endTime, fields, limit, orderBy, plans, resourceGroup, text, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Start a profiling task group\n         * @summary Start profiling\n         * @param {ProfilingStartRequest} req profiling request\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        startProfiling(req: ProfilingStartRequest, options?: any): AxiosPromise<ProfilingTaskGroupModel> {\n            return localVarFp.startProfiling(req, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * Get available field names by statements table columns\n         * @summary Get available field names\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsAvailableFieldsGet(options?: any): AxiosPromise<Array<string>> {\n            return localVarFp.statementsAvailableFieldsGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get statement configurations\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsConfigGet(options?: any): AxiosPromise<StatementEditableConfig> {\n            return localVarFp.statementsConfigGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Update statement configurations\n         * @param {StatementEditableConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsConfigPost(request: StatementEditableConfig, options?: any): AxiosPromise<string> {\n            return localVarFp.statementsConfigPost(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Download statements\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsDownloadGet(token: string, options?: any): AxiosPromise<void> {\n            return localVarFp.statementsDownloadGet(token, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Generate a download token for exported statements\n         * @param {StatementGetStatementsRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsDownloadTokenPost(request: StatementGetStatementsRequest, options?: any): AxiosPromise<string> {\n            return localVarFp.statementsDownloadTokenPost(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get a list of statements\n         * @param {number} [beginTime] \n         * @param {number} [endTime] \n         * @param {string} [fields] \n         * @param {Array<string>} [resourceGroups] \n         * @param {Array<string>} [schemas] \n         * @param {Array<string>} [stmtTypes] \n         * @param {string} [text] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsListGet(beginTime?: number, endTime?: number, fields?: string, resourceGroups?: Array<string>, schemas?: Array<string>, stmtTypes?: Array<string>, text?: string, options?: any): AxiosPromise<Array<StatementModel>> {\n            return localVarFp.statementsListGet(beginTime, endTime, fields, resourceGroups, schemas, stmtTypes, text, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Drop all manually created bindings for a statement\n         * @param {string} sqlDigest query template ID (a.k.a. sql digest)\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanBindingDelete(sqlDigest: string, options?: any): AxiosPromise<string> {\n            return localVarFp.statementsPlanBindingDelete(sqlDigest, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get the bound plan digest (if exists) of a statement\n         * @param {string} sqlDigest query template id\n         * @param {number} beginTime begin time\n         * @param {number} endTime end time\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanBindingGet(sqlDigest: string, beginTime: number, endTime: number, options?: any): AxiosPromise<StatementBinding> {\n            return localVarFp.statementsPlanBindingGet(sqlDigest, beginTime, endTime, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Create a binding for a statement and a plan\n         * @param {string} planDigest plan digest id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanBindingPost(planDigest: string, options?: any): AxiosPromise<string> {\n            return localVarFp.statementsPlanBindingPost(planDigest, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get details of a statement in an execution plan\n         * @param {number} [beginTime] \n         * @param {string} [digest] \n         * @param {number} [endTime] \n         * @param {Array<string>} [plans] \n         * @param {string} [schemaName] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanDetailGet(beginTime?: number, digest?: string, endTime?: number, plans?: Array<string>, schemaName?: string, options?: any): AxiosPromise<StatementModel> {\n            return localVarFp.statementsPlanDetailGet(beginTime, digest, endTime, plans, schemaName, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get execution plans of a statement\n         * @param {number} [beginTime] \n         * @param {string} [digest] \n         * @param {number} [endTime] \n         * @param {string} [schemaName] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlansGet(beginTime?: number, digest?: string, endTime?: number, schemaName?: string, options?: any): AxiosPromise<Array<StatementModel>> {\n            return localVarFp.statementsPlansGet(beginTime, digest, endTime, schemaName, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get all statement types\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsStmtTypesGet(options?: any): AxiosPromise<Array<string>> {\n            return localVarFp.statementsStmtTypesGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Hide a TiDB instance\n         * @param {string} address ip:port\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topologyTidbAddressDelete(address: string, options?: any): AxiosPromise<void> {\n            return localVarFp.topologyTidbAddressDelete(address, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get Top SQL config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topsqlConfigGet(options?: any): AxiosPromise<TopsqlEditableConfig> {\n            return localVarFp.topsqlConfigGet(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Update Top SQL config\n         * @param {TopsqlEditableConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topsqlConfigPost(request: TopsqlEditableConfig, options?: any): AxiosPromise<string> {\n            return localVarFp.topsqlConfigPost(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get TiKV network IO collection config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topsqlGetTiKVNetworkIOCollection(options?: any): AxiosPromise<TopsqlTikvNetworkIoCollectionConfig> {\n            return localVarFp.topsqlGetTiKVNetworkIOCollection(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get available instances\n         * @param {string} [dataSource] \n         * @param {string} [end] \n         * @param {string} [start] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topsqlInstancesGet(dataSource?: string, end?: string, start?: string, options?: any): AxiosPromise<TopsqlInstanceResponse> {\n            return localVarFp.topsqlInstancesGet(dataSource, end, start, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get summaries\n         * @param {string} [dataSource] \n         * @param {string} [end] \n         * @param {string} [groupBy] \n         * @param {string} [instance] \n         * @param {string} [instanceType] \n         * @param {string} [orderBy] \n         * @param {string} [start] \n         * @param {string} [top] \n         * @param {string} [window] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topsqlSummaryGet(dataSource?: string, end?: string, groupBy?: string, instance?: string, instanceType?: string, orderBy?: string, start?: string, top?: string, window?: string, options?: any): AxiosPromise<TopsqlSummaryResponse> {\n            return localVarFp.topsqlSummaryGet(dataSource, end, groupBy, instance, instanceType, orderBy, start, top, window, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Update TiKV network IO collection config\n         * @param {TopsqlTikvNetworkIoCollectionConfig} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        topsqlUpdateTiKVNetworkIOCollection(request: TopsqlTikvNetworkIoCollectionConfig, options?: any): AxiosPromise<TopsqlUpdateTikvNetworkIoCollectionResponse> {\n            return localVarFp.topsqlUpdateTiKVNetworkIOCollection(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get log in information, like supported authenticate types\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userGetLoginInfo(options?: any): AxiosPromise<UserGetLoginInfoResponse> {\n            return localVarFp.userGetLoginInfo(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get sign out info\n         * @param {string} [redirectUrl] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userGetSignOutInfo(redirectUrl?: string, options?: any): AxiosPromise<UserSignOutInfo> {\n            return localVarFp.userGetSignOutInfo(redirectUrl, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Log in\n         * @param {UserAuthenticateForm} message Credentials\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userLogin(message: UserAuthenticateForm, options?: any): AxiosPromise<UserTokenResponse> {\n            return localVarFp.userLogin(message, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Reset encryption key to revoke all authorized codes\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userRevokeSession(options?: any): AxiosPromise<void> {\n            return localVarFp.userRevokeSession(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Create an impersonation\n         * @param {SsoCreateImpersonationRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userSSOCreateImpersonation(request: SsoCreateImpersonationRequest, options?: any): AxiosPromise<SsoSSOImpersonationModel> {\n            return localVarFp.userSSOCreateImpersonation(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get SSO Auth URL\n         * @param {string} [codeVerifier] \n         * @param {string} [redirectUrl] \n         * @param {string} [state] \n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userSSOGetAuthURL(codeVerifier?: string, redirectUrl?: string, state?: string, options?: any): AxiosPromise<string> {\n            return localVarFp.userSSOGetAuthURL(codeVerifier, redirectUrl, state, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Get SSO config\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userSSOGetConfig(options?: any): AxiosPromise<ConfigSSOCoreConfig> {\n            return localVarFp.userSSOGetConfig(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary List all impersonations\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userSSOListImpersonations(options?: any): AxiosPromise<Array<SsoSSOImpersonationModel>> {\n            return localVarFp.userSSOListImpersonations(options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Set SSO config\n         * @param {SsoSetConfigRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userSSOSetConfig(request: SsoSetConfigRequest, options?: any): AxiosPromise<ConfigSSOCoreConfig> {\n            return localVarFp.userSSOSetConfig(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * \n         * @summary Share current session and generate a sharing code\n         * @param {CodeShareRequest} request Request body\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        userShareSession(request: CodeShareRequest, options?: any): AxiosPromise<CodeShareResponse> {\n            return localVarFp.userShareSession(request, options).then((request) => request(axios, basePath));\n        },\n        /**\n         * View the finished profiling result of a task\n         * @summary View the result of a task\n         * @param {string} token download token\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        viewProfilingSingle(token: string, options?: any): AxiosPromise<void> {\n            return localVarFp.viewProfilingSingle(token, options).then((request) => request(axios, basePath));\n        },\n    };\n};\n\n/**\n * Request parameters for cancelProfilingGroup operation in DefaultApi.\n * @export\n * @interface DefaultApiCancelProfilingGroupRequest\n */\nexport interface DefaultApiCancelProfilingGroupRequest {\n    /**\n     * group ID\n     * @type {string}\n     * @memberof DefaultApiCancelProfilingGroup\n     */\n    readonly groupId: string\n}\n\n/**\n * Request parameters for configurationEdit operation in DefaultApi.\n * @export\n * @interface DefaultApiConfigurationEditRequest\n */\nexport interface DefaultApiConfigurationEditRequest {\n    /**\n     * Request body\n     * @type {ConfigurationEditRequest}\n     * @memberof DefaultApiConfigurationEdit\n     */\n    readonly request: ConfigurationEditRequest\n}\n\n/**\n * Request parameters for continuousProfilingActionTokenGet operation in DefaultApi.\n * @export\n * @interface DefaultApiContinuousProfilingActionTokenGetRequest\n */\nexport interface DefaultApiContinuousProfilingActionTokenGetRequest {\n    /**\n     * target query string\n     * @type {string}\n     * @memberof DefaultApiContinuousProfilingActionTokenGet\n     */\n    readonly q: string\n}\n\n/**\n * Request parameters for continuousProfilingConfigPost operation in DefaultApi.\n * @export\n * @interface DefaultApiContinuousProfilingConfigPostRequest\n */\nexport interface DefaultApiContinuousProfilingConfigPostRequest {\n    /**\n     * Request body\n     * @type {ConprofNgMonitoringConfig}\n     * @memberof DefaultApiContinuousProfilingConfigPost\n     */\n    readonly request: ConprofNgMonitoringConfig\n}\n\n/**\n * Request parameters for continuousProfilingDownloadGet operation in DefaultApi.\n * @export\n * @interface DefaultApiContinuousProfilingDownloadGetRequest\n */\nexport interface DefaultApiContinuousProfilingDownloadGetRequest {\n    /**\n     * timestamp\n     * @type {number}\n     * @memberof DefaultApiContinuousProfilingDownloadGet\n     */\n    readonly ts: number\n}\n\n/**\n * Request parameters for continuousProfilingGroupProfileDetailGet operation in DefaultApi.\n * @export\n * @interface DefaultApiContinuousProfilingGroupProfileDetailGetRequest\n */\nexport interface DefaultApiContinuousProfilingGroupProfileDetailGetRequest {\n    /**\n     * timestamp\n     * @type {number}\n     * @memberof DefaultApiContinuousProfilingGroupProfileDetailGet\n     */\n    readonly ts: number\n}\n\n/**\n * Request parameters for continuousProfilingGroupProfilesGet operation in DefaultApi.\n * @export\n * @interface DefaultApiContinuousProfilingGroupProfilesGetRequest\n */\nexport interface DefaultApiContinuousProfilingGroupProfilesGetRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiContinuousProfilingGroupProfilesGet\n     */\n    readonly beginTime?: number\n\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiContinuousProfilingGroupProfilesGet\n     */\n    readonly endTime?: number\n}\n\n/**\n * Request parameters for continuousProfilingSingleProfileViewGet operation in DefaultApi.\n * @export\n * @interface DefaultApiContinuousProfilingSingleProfileViewGetRequest\n */\nexport interface DefaultApiContinuousProfilingSingleProfileViewGetRequest {\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiContinuousProfilingSingleProfileViewGet\n     */\n    readonly address?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiContinuousProfilingSingleProfileViewGet\n     */\n    readonly component?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiContinuousProfilingSingleProfileViewGet\n     */\n    readonly profileType?: string\n\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiContinuousProfilingSingleProfileViewGet\n     */\n    readonly ts?: number\n}\n\n/**\n * Request parameters for debugAPIRequestEndpoint operation in DefaultApi.\n * @export\n * @interface DefaultApiDebugAPIRequestEndpointRequest\n */\nexport interface DefaultApiDebugAPIRequestEndpointRequest {\n    /**\n     * request payload\n     * @type {EndpointRequestPayload}\n     * @memberof DefaultApiDebugAPIRequestEndpoint\n     */\n    readonly req: EndpointRequestPayload\n}\n\n/**\n * Request parameters for debugApiDownloadGet operation in DefaultApi.\n * @export\n * @interface DefaultApiDebugApiDownloadGetRequest\n */\nexport interface DefaultApiDebugApiDownloadGetRequest {\n    /**\n     * download token\n     * @type {string}\n     * @memberof DefaultApiDebugApiDownloadGet\n     */\n    readonly token: string\n}\n\n/**\n * Request parameters for deleteProfilingGroup operation in DefaultApi.\n * @export\n * @interface DefaultApiDeleteProfilingGroupRequest\n */\nexport interface DefaultApiDeleteProfilingGroupRequest {\n    /**\n     * group ID\n     * @type {string}\n     * @memberof DefaultApiDeleteProfilingGroup\n     */\n    readonly groupId: string\n}\n\n/**\n * Request parameters for diagnoseDiagnosisPost operation in DefaultApi.\n * @export\n * @interface DefaultApiDiagnoseDiagnosisPostRequest\n */\nexport interface DefaultApiDiagnoseDiagnosisPostRequest {\n    /**\n     * Request body\n     * @type {DiagnoseGenDiagnosisReportRequest}\n     * @memberof DefaultApiDiagnoseDiagnosisPost\n     */\n    readonly request: DiagnoseGenDiagnosisReportRequest\n}\n\n/**\n * Request parameters for diagnoseGenerateMetricsRelationship operation in DefaultApi.\n * @export\n * @interface DefaultApiDiagnoseGenerateMetricsRelationshipRequest\n */\nexport interface DefaultApiDiagnoseGenerateMetricsRelationshipRequest {\n    /**\n     * Request body\n     * @type {DiagnoseGenerateMetricsRelationRequest}\n     * @memberof DefaultApiDiagnoseGenerateMetricsRelationship\n     */\n    readonly request: DiagnoseGenerateMetricsRelationRequest\n}\n\n/**\n * Request parameters for diagnoseMetricsRelationViewGet operation in DefaultApi.\n * @export\n * @interface DefaultApiDiagnoseMetricsRelationViewGetRequest\n */\nexport interface DefaultApiDiagnoseMetricsRelationViewGetRequest {\n    /**\n     * token\n     * @type {string}\n     * @memberof DefaultApiDiagnoseMetricsRelationViewGet\n     */\n    readonly token: string\n}\n\n/**\n * Request parameters for diagnoseReportsIdDataJsGet operation in DefaultApi.\n * @export\n * @interface DefaultApiDiagnoseReportsIdDataJsGetRequest\n */\nexport interface DefaultApiDiagnoseReportsIdDataJsGetRequest {\n    /**\n     * report id\n     * @type {string}\n     * @memberof DefaultApiDiagnoseReportsIdDataJsGet\n     */\n    readonly id: string\n}\n\n/**\n * Request parameters for diagnoseReportsIdDetailGet operation in DefaultApi.\n * @export\n * @interface DefaultApiDiagnoseReportsIdDetailGetRequest\n */\nexport interface DefaultApiDiagnoseReportsIdDetailGetRequest {\n    /**\n     * report id\n     * @type {string}\n     * @memberof DefaultApiDiagnoseReportsIdDetailGet\n     */\n    readonly id: string\n}\n\n/**\n * Request parameters for diagnoseReportsIdStatusGet operation in DefaultApi.\n * @export\n * @interface DefaultApiDiagnoseReportsIdStatusGetRequest\n */\nexport interface DefaultApiDiagnoseReportsIdStatusGetRequest {\n    /**\n     * report id\n     * @type {string}\n     * @memberof DefaultApiDiagnoseReportsIdStatusGet\n     */\n    readonly id: string\n}\n\n/**\n * Request parameters for diagnoseReportsPost operation in DefaultApi.\n * @export\n * @interface DefaultApiDiagnoseReportsPostRequest\n */\nexport interface DefaultApiDiagnoseReportsPostRequest {\n    /**\n     * Request body\n     * @type {DiagnoseGenerateReportRequest}\n     * @memberof DefaultApiDiagnoseReportsPost\n     */\n    readonly request: DiagnoseGenerateReportRequest\n}\n\n/**\n * Request parameters for downloadProfilingGroup operation in DefaultApi.\n * @export\n * @interface DefaultApiDownloadProfilingGroupRequest\n */\nexport interface DefaultApiDownloadProfilingGroupRequest {\n    /**\n     * download token\n     * @type {string}\n     * @memberof DefaultApiDownloadProfilingGroup\n     */\n    readonly token: string\n}\n\n/**\n * Request parameters for downloadProfilingSingle operation in DefaultApi.\n * @export\n * @interface DefaultApiDownloadProfilingSingleRequest\n */\nexport interface DefaultApiDownloadProfilingSingleRequest {\n    /**\n     * download token\n     * @type {string}\n     * @memberof DefaultApiDownloadProfilingSingle\n     */\n    readonly token: string\n}\n\n/**\n * Request parameters for getActionToken operation in DefaultApi.\n * @export\n * @interface DefaultApiGetActionTokenRequest\n */\nexport interface DefaultApiGetActionTokenRequest {\n    /**\n     * group or task ID\n     * @type {string}\n     * @memberof DefaultApiGetActionToken\n     */\n    readonly id?: string\n\n    /**\n     * action\n     * @type {string}\n     * @memberof DefaultApiGetActionToken\n     */\n    readonly action?: string\n}\n\n/**\n * Request parameters for getAlertManagerCounts operation in DefaultApi.\n * @export\n * @interface DefaultApiGetAlertManagerCountsRequest\n */\nexport interface DefaultApiGetAlertManagerCountsRequest {\n    /**\n     * ip:port\n     * @type {string}\n     * @memberof DefaultApiGetAlertManagerCounts\n     */\n    readonly address: string\n}\n\n/**\n * Request parameters for getProfilingGroupDetail operation in DefaultApi.\n * @export\n * @interface DefaultApiGetProfilingGroupDetailRequest\n */\nexport interface DefaultApiGetProfilingGroupDetailRequest {\n    /**\n     * group ID\n     * @type {string}\n     * @memberof DefaultApiGetProfilingGroupDetail\n     */\n    readonly groupId: string\n}\n\n/**\n * Request parameters for infoListTables operation in DefaultApi.\n * @export\n * @interface DefaultApiInfoListTablesRequest\n */\nexport interface DefaultApiInfoListTablesRequest {\n    /**\n     * Database name\n     * @type {string}\n     * @memberof DefaultApiInfoListTables\n     */\n    readonly databaseName?: string\n}\n\n/**\n * Request parameters for keyvisualConfigPut operation in DefaultApi.\n * @export\n * @interface DefaultApiKeyvisualConfigPutRequest\n */\nexport interface DefaultApiKeyvisualConfigPutRequest {\n    /**\n     * Request body\n     * @type {ConfigKeyVisualConfig}\n     * @memberof DefaultApiKeyvisualConfigPut\n     */\n    readonly request: ConfigKeyVisualConfig\n}\n\n/**\n * Request parameters for keyvisualHeatmapsGet operation in DefaultApi.\n * @export\n * @interface DefaultApiKeyvisualHeatmapsGetRequest\n */\nexport interface DefaultApiKeyvisualHeatmapsGetRequest {\n    /**\n     * The start of the key range\n     * @type {string}\n     * @memberof DefaultApiKeyvisualHeatmapsGet\n     */\n    readonly startkey?: string\n\n    /**\n     * The end of the key range\n     * @type {string}\n     * @memberof DefaultApiKeyvisualHeatmapsGet\n     */\n    readonly endkey?: string\n\n    /**\n     * The start of the time range (Unix)\n     * @type {number}\n     * @memberof DefaultApiKeyvisualHeatmapsGet\n     */\n    readonly starttime?: number\n\n    /**\n     * The end of the time range (Unix)\n     * @type {number}\n     * @memberof DefaultApiKeyvisualHeatmapsGet\n     */\n    readonly endtime?: number\n\n    /**\n     * Main types of data\n     * @type {'written_bytes' | 'read_bytes' | 'written_keys' | 'read_keys' | 'integration'}\n     * @memberof DefaultApiKeyvisualHeatmapsGet\n     */\n    readonly type?: 'written_bytes' | 'read_bytes' | 'written_keys' | 'read_keys' | 'integration'\n}\n\n/**\n * Request parameters for logsDownloadAcquireTokenGet operation in DefaultApi.\n * @export\n * @interface DefaultApiLogsDownloadAcquireTokenGetRequest\n */\nexport interface DefaultApiLogsDownloadAcquireTokenGetRequest {\n    /**\n     * task id\n     * @type {Array<string>}\n     * @memberof DefaultApiLogsDownloadAcquireTokenGet\n     */\n    readonly id?: Array<string>\n}\n\n/**\n * Request parameters for logsDownloadGet operation in DefaultApi.\n * @export\n * @interface DefaultApiLogsDownloadGetRequest\n */\nexport interface DefaultApiLogsDownloadGetRequest {\n    /**\n     * download token\n     * @type {string}\n     * @memberof DefaultApiLogsDownloadGet\n     */\n    readonly token: string\n}\n\n/**\n * Request parameters for logsTaskgroupPut operation in DefaultApi.\n * @export\n * @interface DefaultApiLogsTaskgroupPutRequest\n */\nexport interface DefaultApiLogsTaskgroupPutRequest {\n    /**\n     * Request body\n     * @type {LogsearchCreateTaskGroupRequest}\n     * @memberof DefaultApiLogsTaskgroupPut\n     */\n    readonly request: LogsearchCreateTaskGroupRequest\n}\n\n/**\n * Request parameters for logsTaskgroupsIdCancelPost operation in DefaultApi.\n * @export\n * @interface DefaultApiLogsTaskgroupsIdCancelPostRequest\n */\nexport interface DefaultApiLogsTaskgroupsIdCancelPostRequest {\n    /**\n     * task group id\n     * @type {string}\n     * @memberof DefaultApiLogsTaskgroupsIdCancelPost\n     */\n    readonly id: string\n}\n\n/**\n * Request parameters for logsTaskgroupsIdDelete operation in DefaultApi.\n * @export\n * @interface DefaultApiLogsTaskgroupsIdDeleteRequest\n */\nexport interface DefaultApiLogsTaskgroupsIdDeleteRequest {\n    /**\n     * task group id\n     * @type {string}\n     * @memberof DefaultApiLogsTaskgroupsIdDelete\n     */\n    readonly id: string\n}\n\n/**\n * Request parameters for logsTaskgroupsIdGet operation in DefaultApi.\n * @export\n * @interface DefaultApiLogsTaskgroupsIdGetRequest\n */\nexport interface DefaultApiLogsTaskgroupsIdGetRequest {\n    /**\n     * Task Group ID\n     * @type {string}\n     * @memberof DefaultApiLogsTaskgroupsIdGet\n     */\n    readonly id: string\n}\n\n/**\n * Request parameters for logsTaskgroupsIdPreviewGet operation in DefaultApi.\n * @export\n * @interface DefaultApiLogsTaskgroupsIdPreviewGetRequest\n */\nexport interface DefaultApiLogsTaskgroupsIdPreviewGetRequest {\n    /**\n     * task group id\n     * @type {string}\n     * @memberof DefaultApiLogsTaskgroupsIdPreviewGet\n     */\n    readonly id: string\n}\n\n/**\n * Request parameters for logsTaskgroupsIdRetryPost operation in DefaultApi.\n * @export\n * @interface DefaultApiLogsTaskgroupsIdRetryPostRequest\n */\nexport interface DefaultApiLogsTaskgroupsIdRetryPostRequest {\n    /**\n     * task group id\n     * @type {string}\n     * @memberof DefaultApiLogsTaskgroupsIdRetryPost\n     */\n    readonly id: string\n}\n\n/**\n * Request parameters for metricsQueryGet operation in DefaultApi.\n * @export\n * @interface DefaultApiMetricsQueryGetRequest\n */\nexport interface DefaultApiMetricsQueryGetRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiMetricsQueryGet\n     */\n    readonly endTimeSec?: number\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiMetricsQueryGet\n     */\n    readonly query?: string\n\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiMetricsQueryGet\n     */\n    readonly startTimeSec?: number\n\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiMetricsQueryGet\n     */\n    readonly stepSec?: number\n}\n\n/**\n * Request parameters for metricsSetCustomPromAddress operation in DefaultApi.\n * @export\n * @interface DefaultApiMetricsSetCustomPromAddressRequest\n */\nexport interface DefaultApiMetricsSetCustomPromAddressRequest {\n    /**\n     * Request body\n     * @type {MetricsPutCustomPromAddressRequest}\n     * @memberof DefaultApiMetricsSetCustomPromAddress\n     */\n    readonly request: MetricsPutCustomPromAddressRequest\n}\n\n/**\n * Request parameters for profilingConfigPut operation in DefaultApi.\n * @export\n * @interface DefaultApiProfilingConfigPutRequest\n */\nexport interface DefaultApiProfilingConfigPutRequest {\n    /**\n     * Request body\n     * @type {ConfigProfilingConfig}\n     * @memberof DefaultApiProfilingConfigPut\n     */\n    readonly request: ConfigProfilingConfig\n}\n\n/**\n * Request parameters for queryEditorRun operation in DefaultApi.\n * @export\n * @interface DefaultApiQueryEditorRunRequest\n */\nexport interface DefaultApiQueryEditorRunRequest {\n    /**\n     * Request body\n     * @type {QueryeditorRunRequest}\n     * @memberof DefaultApiQueryEditorRun\n     */\n    readonly request: QueryeditorRunRequest\n}\n\n/**\n * Request parameters for resourceManagerCalibrateActualGet operation in DefaultApi.\n * @export\n * @interface DefaultApiResourceManagerCalibrateActualGetRequest\n */\nexport interface DefaultApiResourceManagerCalibrateActualGetRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiResourceManagerCalibrateActualGet\n     */\n    readonly endTime?: number\n\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiResourceManagerCalibrateActualGet\n     */\n    readonly startTime?: number\n}\n\n/**\n * Request parameters for resourceManagerCalibrateHardwareGet operation in DefaultApi.\n * @export\n * @interface DefaultApiResourceManagerCalibrateHardwareGetRequest\n */\nexport interface DefaultApiResourceManagerCalibrateHardwareGetRequest {\n    /**\n     * workload\n     * @type {string}\n     * @memberof DefaultApiResourceManagerCalibrateHardwareGet\n     */\n    readonly workload: string\n}\n\n/**\n * Request parameters for slowQueryDetailGet operation in DefaultApi.\n * @export\n * @interface DefaultApiSlowQueryDetailGetRequest\n */\nexport interface DefaultApiSlowQueryDetailGetRequest {\n    /**\n     * TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\n     * @type {string}\n     * @memberof DefaultApiSlowQueryDetailGet\n     */\n    readonly connectId?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiSlowQueryDetailGet\n     */\n    readonly digest?: string\n\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiSlowQueryDetailGet\n     */\n    readonly timestamp?: number\n}\n\n/**\n * Request parameters for slowQueryDownloadGet operation in DefaultApi.\n * @export\n * @interface DefaultApiSlowQueryDownloadGetRequest\n */\nexport interface DefaultApiSlowQueryDownloadGetRequest {\n    /**\n     * download token\n     * @type {string}\n     * @memberof DefaultApiSlowQueryDownloadGet\n     */\n    readonly token: string\n}\n\n/**\n * Request parameters for slowQueryDownloadTokenPost operation in DefaultApi.\n * @export\n * @interface DefaultApiSlowQueryDownloadTokenPostRequest\n */\nexport interface DefaultApiSlowQueryDownloadTokenPostRequest {\n    /**\n     * Request body\n     * @type {SlowqueryGetListRequest}\n     * @memberof DefaultApiSlowQueryDownloadTokenPost\n     */\n    readonly request: SlowqueryGetListRequest\n}\n\n/**\n * Request parameters for slowQueryListGet operation in DefaultApi.\n * @export\n * @interface DefaultApiSlowQueryListGetRequest\n */\nexport interface DefaultApiSlowQueryListGetRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiSlowQueryListGet\n     */\n    readonly beginTime?: number\n\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof DefaultApiSlowQueryListGet\n     */\n    readonly db?: Array<string>\n\n    /**\n     * \n     * @type {boolean}\n     * @memberof DefaultApiSlowQueryListGet\n     */\n    readonly desc?: boolean\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiSlowQueryListGet\n     */\n    readonly digest?: string\n\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiSlowQueryListGet\n     */\n    readonly endTime?: number\n\n    /**\n     * example: \\&quot;Query,Digest\\&quot;\n     * @type {string}\n     * @memberof DefaultApiSlowQueryListGet\n     */\n    readonly fields?: string\n\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiSlowQueryListGet\n     */\n    readonly limit?: number\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiSlowQueryListGet\n     */\n    readonly orderBy?: string\n\n    /**\n     * for showing slow queries in the statement detail page\n     * @type {Array<string>}\n     * @memberof DefaultApiSlowQueryListGet\n     */\n    readonly plans?: Array<string>\n\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof DefaultApiSlowQueryListGet\n     */\n    readonly resourceGroup?: Array<string>\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiSlowQueryListGet\n     */\n    readonly text?: string\n}\n\n/**\n * Request parameters for startProfiling operation in DefaultApi.\n * @export\n * @interface DefaultApiStartProfilingRequest\n */\nexport interface DefaultApiStartProfilingRequest {\n    /**\n     * profiling request\n     * @type {ProfilingStartRequest}\n     * @memberof DefaultApiStartProfiling\n     */\n    readonly req: ProfilingStartRequest\n}\n\n/**\n * Request parameters for statementsConfigPost operation in DefaultApi.\n * @export\n * @interface DefaultApiStatementsConfigPostRequest\n */\nexport interface DefaultApiStatementsConfigPostRequest {\n    /**\n     * Request body\n     * @type {StatementEditableConfig}\n     * @memberof DefaultApiStatementsConfigPost\n     */\n    readonly request: StatementEditableConfig\n}\n\n/**\n * Request parameters for statementsDownloadGet operation in DefaultApi.\n * @export\n * @interface DefaultApiStatementsDownloadGetRequest\n */\nexport interface DefaultApiStatementsDownloadGetRequest {\n    /**\n     * download token\n     * @type {string}\n     * @memberof DefaultApiStatementsDownloadGet\n     */\n    readonly token: string\n}\n\n/**\n * Request parameters for statementsDownloadTokenPost operation in DefaultApi.\n * @export\n * @interface DefaultApiStatementsDownloadTokenPostRequest\n */\nexport interface DefaultApiStatementsDownloadTokenPostRequest {\n    /**\n     * Request body\n     * @type {StatementGetStatementsRequest}\n     * @memberof DefaultApiStatementsDownloadTokenPost\n     */\n    readonly request: StatementGetStatementsRequest\n}\n\n/**\n * Request parameters for statementsListGet operation in DefaultApi.\n * @export\n * @interface DefaultApiStatementsListGetRequest\n */\nexport interface DefaultApiStatementsListGetRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiStatementsListGet\n     */\n    readonly beginTime?: number\n\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiStatementsListGet\n     */\n    readonly endTime?: number\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiStatementsListGet\n     */\n    readonly fields?: string\n\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof DefaultApiStatementsListGet\n     */\n    readonly resourceGroups?: Array<string>\n\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof DefaultApiStatementsListGet\n     */\n    readonly schemas?: Array<string>\n\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof DefaultApiStatementsListGet\n     */\n    readonly stmtTypes?: Array<string>\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiStatementsListGet\n     */\n    readonly text?: string\n}\n\n/**\n * Request parameters for statementsPlanBindingDelete operation in DefaultApi.\n * @export\n * @interface DefaultApiStatementsPlanBindingDeleteRequest\n */\nexport interface DefaultApiStatementsPlanBindingDeleteRequest {\n    /**\n     * query template ID (a.k.a. sql digest)\n     * @type {string}\n     * @memberof DefaultApiStatementsPlanBindingDelete\n     */\n    readonly sqlDigest: string\n}\n\n/**\n * Request parameters for statementsPlanBindingGet operation in DefaultApi.\n * @export\n * @interface DefaultApiStatementsPlanBindingGetRequest\n */\nexport interface DefaultApiStatementsPlanBindingGetRequest {\n    /**\n     * query template id\n     * @type {string}\n     * @memberof DefaultApiStatementsPlanBindingGet\n     */\n    readonly sqlDigest: string\n\n    /**\n     * begin time\n     * @type {number}\n     * @memberof DefaultApiStatementsPlanBindingGet\n     */\n    readonly beginTime: number\n\n    /**\n     * end time\n     * @type {number}\n     * @memberof DefaultApiStatementsPlanBindingGet\n     */\n    readonly endTime: number\n}\n\n/**\n * Request parameters for statementsPlanBindingPost operation in DefaultApi.\n * @export\n * @interface DefaultApiStatementsPlanBindingPostRequest\n */\nexport interface DefaultApiStatementsPlanBindingPostRequest {\n    /**\n     * plan digest id\n     * @type {string}\n     * @memberof DefaultApiStatementsPlanBindingPost\n     */\n    readonly planDigest: string\n}\n\n/**\n * Request parameters for statementsPlanDetailGet operation in DefaultApi.\n * @export\n * @interface DefaultApiStatementsPlanDetailGetRequest\n */\nexport interface DefaultApiStatementsPlanDetailGetRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiStatementsPlanDetailGet\n     */\n    readonly beginTime?: number\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiStatementsPlanDetailGet\n     */\n    readonly digest?: string\n\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiStatementsPlanDetailGet\n     */\n    readonly endTime?: number\n\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof DefaultApiStatementsPlanDetailGet\n     */\n    readonly plans?: Array<string>\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiStatementsPlanDetailGet\n     */\n    readonly schemaName?: string\n}\n\n/**\n * Request parameters for statementsPlansGet operation in DefaultApi.\n * @export\n * @interface DefaultApiStatementsPlansGetRequest\n */\nexport interface DefaultApiStatementsPlansGetRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiStatementsPlansGet\n     */\n    readonly beginTime?: number\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiStatementsPlansGet\n     */\n    readonly digest?: string\n\n    /**\n     * \n     * @type {number}\n     * @memberof DefaultApiStatementsPlansGet\n     */\n    readonly endTime?: number\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiStatementsPlansGet\n     */\n    readonly schemaName?: string\n}\n\n/**\n * Request parameters for topologyTidbAddressDelete operation in DefaultApi.\n * @export\n * @interface DefaultApiTopologyTidbAddressDeleteRequest\n */\nexport interface DefaultApiTopologyTidbAddressDeleteRequest {\n    /**\n     * ip:port\n     * @type {string}\n     * @memberof DefaultApiTopologyTidbAddressDelete\n     */\n    readonly address: string\n}\n\n/**\n * Request parameters for topsqlConfigPost operation in DefaultApi.\n * @export\n * @interface DefaultApiTopsqlConfigPostRequest\n */\nexport interface DefaultApiTopsqlConfigPostRequest {\n    /**\n     * Request body\n     * @type {TopsqlEditableConfig}\n     * @memberof DefaultApiTopsqlConfigPost\n     */\n    readonly request: TopsqlEditableConfig\n}\n\n/**\n * Request parameters for topsqlInstancesGet operation in DefaultApi.\n * @export\n * @interface DefaultApiTopsqlInstancesGetRequest\n */\nexport interface DefaultApiTopsqlInstancesGetRequest {\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiTopsqlInstancesGet\n     */\n    readonly dataSource?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiTopsqlInstancesGet\n     */\n    readonly end?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiTopsqlInstancesGet\n     */\n    readonly start?: string\n}\n\n/**\n * Request parameters for topsqlSummaryGet operation in DefaultApi.\n * @export\n * @interface DefaultApiTopsqlSummaryGetRequest\n */\nexport interface DefaultApiTopsqlSummaryGetRequest {\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiTopsqlSummaryGet\n     */\n    readonly dataSource?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiTopsqlSummaryGet\n     */\n    readonly end?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiTopsqlSummaryGet\n     */\n    readonly groupBy?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiTopsqlSummaryGet\n     */\n    readonly instance?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiTopsqlSummaryGet\n     */\n    readonly instanceType?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiTopsqlSummaryGet\n     */\n    readonly orderBy?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiTopsqlSummaryGet\n     */\n    readonly start?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiTopsqlSummaryGet\n     */\n    readonly top?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiTopsqlSummaryGet\n     */\n    readonly window?: string\n}\n\n/**\n * Request parameters for topsqlUpdateTiKVNetworkIOCollection operation in DefaultApi.\n * @export\n * @interface DefaultApiTopsqlUpdateTiKVNetworkIOCollectionRequest\n */\nexport interface DefaultApiTopsqlUpdateTiKVNetworkIOCollectionRequest {\n    /**\n     * Request body\n     * @type {TopsqlTikvNetworkIoCollectionConfig}\n     * @memberof DefaultApiTopsqlUpdateTiKVNetworkIOCollection\n     */\n    readonly request: TopsqlTikvNetworkIoCollectionConfig\n}\n\n/**\n * Request parameters for userGetSignOutInfo operation in DefaultApi.\n * @export\n * @interface DefaultApiUserGetSignOutInfoRequest\n */\nexport interface DefaultApiUserGetSignOutInfoRequest {\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiUserGetSignOutInfo\n     */\n    readonly redirectUrl?: string\n}\n\n/**\n * Request parameters for userLogin operation in DefaultApi.\n * @export\n * @interface DefaultApiUserLoginRequest\n */\nexport interface DefaultApiUserLoginRequest {\n    /**\n     * Credentials\n     * @type {UserAuthenticateForm}\n     * @memberof DefaultApiUserLogin\n     */\n    readonly message: UserAuthenticateForm\n}\n\n/**\n * Request parameters for userSSOCreateImpersonation operation in DefaultApi.\n * @export\n * @interface DefaultApiUserSSOCreateImpersonationRequest\n */\nexport interface DefaultApiUserSSOCreateImpersonationRequest {\n    /**\n     * Request body\n     * @type {SsoCreateImpersonationRequest}\n     * @memberof DefaultApiUserSSOCreateImpersonation\n     */\n    readonly request: SsoCreateImpersonationRequest\n}\n\n/**\n * Request parameters for userSSOGetAuthURL operation in DefaultApi.\n * @export\n * @interface DefaultApiUserSSOGetAuthURLRequest\n */\nexport interface DefaultApiUserSSOGetAuthURLRequest {\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiUserSSOGetAuthURL\n     */\n    readonly codeVerifier?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiUserSSOGetAuthURL\n     */\n    readonly redirectUrl?: string\n\n    /**\n     * \n     * @type {string}\n     * @memberof DefaultApiUserSSOGetAuthURL\n     */\n    readonly state?: string\n}\n\n/**\n * Request parameters for userSSOSetConfig operation in DefaultApi.\n * @export\n * @interface DefaultApiUserSSOSetConfigRequest\n */\nexport interface DefaultApiUserSSOSetConfigRequest {\n    /**\n     * Request body\n     * @type {SsoSetConfigRequest}\n     * @memberof DefaultApiUserSSOSetConfig\n     */\n    readonly request: SsoSetConfigRequest\n}\n\n/**\n * Request parameters for userShareSession operation in DefaultApi.\n * @export\n * @interface DefaultApiUserShareSessionRequest\n */\nexport interface DefaultApiUserShareSessionRequest {\n    /**\n     * Request body\n     * @type {CodeShareRequest}\n     * @memberof DefaultApiUserShareSession\n     */\n    readonly request: CodeShareRequest\n}\n\n/**\n * Request parameters for viewProfilingSingle operation in DefaultApi.\n * @export\n * @interface DefaultApiViewProfilingSingleRequest\n */\nexport interface DefaultApiViewProfilingSingleRequest {\n    /**\n     * download token\n     * @type {string}\n     * @memberof DefaultApiViewProfilingSingle\n     */\n    readonly token: string\n}\n\n/**\n * DefaultApi - object-oriented interface\n * @export\n * @class DefaultApi\n * @extends {BaseAPI}\n */\nexport class DefaultApi extends BaseAPI {\n    /**\n     * Cancel all profling tasks with a given group ID\n     * @summary Cancel all tasks with a given group ID\n     * @param {DefaultApiCancelProfilingGroupRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public cancelProfilingGroup(requestParameters: DefaultApiCancelProfilingGroupRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).cancelProfilingGroup(requestParameters.groupId, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get information of all hosts\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public clusterInfoGetHostsInfo(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).clusterInfoGetHostsInfo(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get cluster statistics\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public clusterInfoGetStatistics(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).clusterInfoGetStatistics(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Edit a configuration\n     * @param {DefaultApiConfigurationEditRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public configurationEdit(requestParameters: DefaultApiConfigurationEditRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).configurationEdit(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get all configurations\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public configurationGetAll(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).configurationGetAll(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get action token for download or view profile\n     * @param {DefaultApiContinuousProfilingActionTokenGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public continuousProfilingActionTokenGet(requestParameters: DefaultApiContinuousProfilingActionTokenGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).continuousProfilingActionTokenGet(requestParameters.q, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get current scraping components\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public continuousProfilingComponentsGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).continuousProfilingComponentsGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get Continuous Profiling Config\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public continuousProfilingConfigGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).continuousProfilingConfigGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Update Continuous Profiling Config\n     * @param {DefaultApiContinuousProfilingConfigPostRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public continuousProfilingConfigPost(requestParameters: DefaultApiContinuousProfilingConfigPostRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).continuousProfilingConfigPost(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Download Group Profile files\n     * @param {DefaultApiContinuousProfilingDownloadGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public continuousProfilingDownloadGet(requestParameters: DefaultApiContinuousProfilingDownloadGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).continuousProfilingDownloadGet(requestParameters.ts, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get Estimate Size\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public continuousProfilingEstimateSizeGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).continuousProfilingEstimateSizeGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get Group Profile Detail\n     * @param {DefaultApiContinuousProfilingGroupProfileDetailGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public continuousProfilingGroupProfileDetailGet(requestParameters: DefaultApiContinuousProfilingGroupProfileDetailGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).continuousProfilingGroupProfileDetailGet(requestParameters.ts, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get Group Profiles\n     * @param {DefaultApiContinuousProfilingGroupProfilesGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public continuousProfilingGroupProfilesGet(requestParameters: DefaultApiContinuousProfilingGroupProfilesGetRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).continuousProfilingGroupProfilesGet(requestParameters.beginTime, requestParameters.endTime, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary View Single Profile files\n     * @param {DefaultApiContinuousProfilingSingleProfileViewGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public continuousProfilingSingleProfileViewGet(requestParameters: DefaultApiContinuousProfilingSingleProfileViewGetRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).continuousProfilingSingleProfileViewGet(requestParameters.address, requestParameters.component, requestParameters.profileType, requestParameters.ts, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary List all deadlock records\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public deadlockListGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).deadlockListGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get all endpoints\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public debugAPIGetEndpoints(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).debugAPIGetEndpoints(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Send request remote endpoint and return a token for downloading results\n     * @param {DefaultApiDebugAPIRequestEndpointRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public debugAPIRequestEndpoint(requestParameters: DefaultApiDebugAPIRequestEndpointRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).debugAPIRequestEndpoint(requestParameters.req, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Download a finished request result\n     * @param {DefaultApiDebugApiDownloadGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public debugApiDownloadGet(requestParameters: DefaultApiDebugApiDownloadGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).debugApiDownloadGet(requestParameters.token, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Delete all finished profiling tasks with a given group ID\n     * @summary Delete all tasks with a given group ID\n     * @param {DefaultApiDeleteProfilingGroupRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public deleteProfilingGroup(requestParameters: DefaultApiDeleteProfilingGroupRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).deleteProfilingGroup(requestParameters.groupId, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Generate sql diagnosis report\n     * @summary SQL diagnosis report\n     * @param {DefaultApiDiagnoseDiagnosisPostRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public diagnoseDiagnosisPost(requestParameters: DefaultApiDiagnoseDiagnosisPostRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).diagnoseDiagnosisPost(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Generate metrics relationship graph.\n     * @param {DefaultApiDiagnoseGenerateMetricsRelationshipRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public diagnoseGenerateMetricsRelationship(requestParameters: DefaultApiDiagnoseGenerateMetricsRelationshipRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).diagnoseGenerateMetricsRelationship(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary View metrics relationship graph.\n     * @param {DefaultApiDiagnoseMetricsRelationViewGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public diagnoseMetricsRelationViewGet(requestParameters: DefaultApiDiagnoseMetricsRelationViewGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).diagnoseMetricsRelationViewGet(requestParameters.token, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Get sql diagnosis reports history\n     * @summary SQL diagnosis reports history\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public diagnoseReportsGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).diagnoseReportsGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Get sql diagnosis report data\n     * @summary SQL diagnosis report data\n     * @param {DefaultApiDiagnoseReportsIdDataJsGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public diagnoseReportsIdDataJsGet(requestParameters: DefaultApiDiagnoseReportsIdDataJsGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).diagnoseReportsIdDataJsGet(requestParameters.id, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Get sql diagnosis report HTML\n     * @summary SQL diagnosis report\n     * @param {DefaultApiDiagnoseReportsIdDetailGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public diagnoseReportsIdDetailGet(requestParameters: DefaultApiDiagnoseReportsIdDetailGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).diagnoseReportsIdDetailGet(requestParameters.id, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Get diagnosis report status\n     * @summary Diagnosis report status\n     * @param {DefaultApiDiagnoseReportsIdStatusGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public diagnoseReportsIdStatusGet(requestParameters: DefaultApiDiagnoseReportsIdStatusGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).diagnoseReportsIdStatusGet(requestParameters.id, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Generate sql diagnosis report\n     * @summary SQL diagnosis report\n     * @param {DefaultApiDiagnoseReportsPostRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public diagnoseReportsPost(requestParameters: DefaultApiDiagnoseReportsPostRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).diagnoseReportsPost(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Download all finished profiling results of a task group\n     * @summary Download all results of a task group\n     * @param {DefaultApiDownloadProfilingGroupRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public downloadProfilingGroup(requestParameters: DefaultApiDownloadProfilingGroupRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).downloadProfilingGroup(requestParameters.token, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Download the finished profiling result of a task\n     * @summary Download the result of a task\n     * @param {DefaultApiDownloadProfilingSingleRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public downloadProfilingSingle(requestParameters: DefaultApiDownloadProfilingSingleRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).downloadProfilingSingle(requestParameters.token, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Get token with a given group ID or task ID and action type\n     * @summary Get action token for download or view\n     * @param {DefaultApiGetActionTokenRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getActionToken(requestParameters: DefaultApiGetActionTokenRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getActionToken(requestParameters.id, requestParameters.action, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get current alert count from AlertManager\n     * @param {DefaultApiGetAlertManagerCountsRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getAlertManagerCounts(requestParameters: DefaultApiGetAlertManagerCountsRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getAlertManagerCounts(requestParameters.address, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get AlertManager instance\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getAlertManagerTopology(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getAlertManagerTopology(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get Grafana instance\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getGrafanaTopology(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getGrafanaTopology(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get all PD instances\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getPDTopology(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getPDTopology(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * List all profiling tasks with a given group ID\n     * @summary List all tasks with a given group ID\n     * @param {DefaultApiGetProfilingGroupDetailRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getProfilingGroupDetail(requestParameters: DefaultApiGetProfilingGroupDetailRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getProfilingGroupDetail(requestParameters.groupId, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * List all profiling groups\n     * @summary List all profiling groups\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getProfilingGroups(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getProfilingGroups(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get all Scheduling instances\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getSchedulingTopology(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getSchedulingTopology(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get location labels of all TiKV / TiFlash instances\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getStoreLocationTopology(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getStoreLocationTopology(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get all TiKV / TiFlash instances\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getStoreTopology(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getStoreTopology(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get all TSO instances\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getTSOTopology(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getTSOTopology(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get all TiCDC instances\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getTiCDCTopology(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getTiCDCTopology(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get all TiDB instances\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getTiDBTopology(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getTiDBTopology(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get all TiProxy instances\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public getTiProxyTopology(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).getTiProxyTopology(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get information about this TiDB Dashboard\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public infoGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).infoGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary List all databases\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public infoListDatabases(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).infoListDatabases(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary List tables by database name\n     * @param {DefaultApiInfoListTablesRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public infoListTables(requestParameters: DefaultApiInfoListTablesRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).infoListTables(requestParameters.databaseName, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get information about current session\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public infoWhoami(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).infoWhoami(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get Key Visual Dynamic Config\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public keyvisualConfigGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).keyvisualConfigGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Set Key Visual Dynamic Config\n     * @param {DefaultApiKeyvisualConfigPutRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public keyvisualConfigPut(requestParameters: DefaultApiKeyvisualConfigPutRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).keyvisualConfigPut(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Heatmaps in a given range to visualize TiKV usage\n     * @summary Key Visual Heatmaps\n     * @param {DefaultApiKeyvisualHeatmapsGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public keyvisualHeatmapsGet(requestParameters: DefaultApiKeyvisualHeatmapsGetRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).keyvisualHeatmapsGet(requestParameters.startkey, requestParameters.endkey, requestParameters.starttime, requestParameters.endtime, requestParameters.type, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Generate a download token for downloading logs\n     * @param {DefaultApiLogsDownloadAcquireTokenGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public logsDownloadAcquireTokenGet(requestParameters: DefaultApiLogsDownloadAcquireTokenGetRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).logsDownloadAcquireTokenGet(requestParameters.id, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Download logs\n     * @param {DefaultApiLogsDownloadGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public logsDownloadGet(requestParameters: DefaultApiLogsDownloadGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).logsDownloadGet(requestParameters.token, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Create and run a new log search task group\n     * @param {DefaultApiLogsTaskgroupPutRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public logsTaskgroupPut(requestParameters: DefaultApiLogsTaskgroupPutRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).logsTaskgroupPut(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary List all log search task groups\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public logsTaskgroupsGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).logsTaskgroupsGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Cancel running tasks in a log search task group\n     * @param {DefaultApiLogsTaskgroupsIdCancelPostRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public logsTaskgroupsIdCancelPost(requestParameters: DefaultApiLogsTaskgroupsIdCancelPostRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).logsTaskgroupsIdCancelPost(requestParameters.id, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Delete a log search task group\n     * @param {DefaultApiLogsTaskgroupsIdDeleteRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public logsTaskgroupsIdDelete(requestParameters: DefaultApiLogsTaskgroupsIdDeleteRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).logsTaskgroupsIdDelete(requestParameters.id, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary List tasks in a log search task group\n     * @param {DefaultApiLogsTaskgroupsIdGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public logsTaskgroupsIdGet(requestParameters: DefaultApiLogsTaskgroupsIdGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).logsTaskgroupsIdGet(requestParameters.id, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Preview a log search task group\n     * @param {DefaultApiLogsTaskgroupsIdPreviewGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public logsTaskgroupsIdPreviewGet(requestParameters: DefaultApiLogsTaskgroupsIdPreviewGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).logsTaskgroupsIdPreviewGet(requestParameters.id, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Retry failed tasks in a log search task group\n     * @param {DefaultApiLogsTaskgroupsIdRetryPostRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public logsTaskgroupsIdRetryPost(requestParameters: DefaultApiLogsTaskgroupsIdRetryPostRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).logsTaskgroupsIdRetryPost(requestParameters.id, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get the Prometheus address cluster config\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public metricsGetPromAddress(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).metricsGetPromAddress(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Query metrics in the given range\n     * @summary Query metrics\n     * @param {DefaultApiMetricsQueryGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public metricsQueryGet(requestParameters: DefaultApiMetricsQueryGetRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).metricsQueryGet(requestParameters.endTimeSec, requestParameters.query, requestParameters.startTimeSec, requestParameters.stepSec, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Set or clear the customized Prometheus address\n     * @param {DefaultApiMetricsSetCustomPromAddressRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public metricsSetCustomPromAddress(requestParameters: DefaultApiMetricsSetCustomPromAddressRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).metricsSetCustomPromAddress(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get Profiling Dynamic Config\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public profilingConfigGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).profilingConfigGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Set Profiling Dynamic Config\n     * @param {DefaultApiProfilingConfigPutRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public profilingConfigPut(requestParameters: DefaultApiProfilingConfigPutRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).profilingConfigPut(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Run statements\n     * @param {DefaultApiQueryEditorRunRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public queryEditorRun(requestParameters: DefaultApiQueryEditorRunRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).queryEditorRun(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get calibrate of Resource Groups by actual workload\n     * @param {DefaultApiResourceManagerCalibrateActualGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public resourceManagerCalibrateActualGet(requestParameters: DefaultApiResourceManagerCalibrateActualGetRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).resourceManagerCalibrateActualGet(requestParameters.endTime, requestParameters.startTime, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get calibrate of Resource Groups by hardware deployment\n     * @param {DefaultApiResourceManagerCalibrateHardwareGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public resourceManagerCalibrateHardwareGet(requestParameters: DefaultApiResourceManagerCalibrateHardwareGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).resourceManagerCalibrateHardwareGet(requestParameters.workload, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get Resource Control enable config\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public resourceManagerConfigGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).resourceManagerConfigGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get Information of Resource Groups\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public resourceManagerInformationGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).resourceManagerInformationGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary List all resource groups\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public resourceManagerInformationGroupNamesGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).resourceManagerInformationGroupNamesGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Get available field names by slowquery table columns\n     * @summary Get available field names\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public slowQueryAvailableFieldsGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).slowQueryAvailableFieldsGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get details of a slow query\n     * @param {DefaultApiSlowQueryDetailGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public slowQueryDetailGet(requestParameters: DefaultApiSlowQueryDetailGetRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).slowQueryDetailGet(requestParameters.connectId, requestParameters.digest, requestParameters.timestamp, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Download slow query statements\n     * @param {DefaultApiSlowQueryDownloadGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public slowQueryDownloadGet(requestParameters: DefaultApiSlowQueryDownloadGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).slowQueryDownloadGet(requestParameters.token, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Generate a download token for exported slow query statements\n     * @param {DefaultApiSlowQueryDownloadTokenPostRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public slowQueryDownloadTokenPost(requestParameters: DefaultApiSlowQueryDownloadTokenPostRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).slowQueryDownloadTokenPost(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary List all slow queries\n     * @param {DefaultApiSlowQueryListGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public slowQueryListGet(requestParameters: DefaultApiSlowQueryListGetRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).slowQueryListGet(requestParameters.beginTime, requestParameters.db, requestParameters.desc, requestParameters.digest, requestParameters.endTime, requestParameters.fields, requestParameters.limit, requestParameters.orderBy, requestParameters.plans, requestParameters.resourceGroup, requestParameters.text, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Start a profiling task group\n     * @summary Start profiling\n     * @param {DefaultApiStartProfilingRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public startProfiling(requestParameters: DefaultApiStartProfilingRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).startProfiling(requestParameters.req, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * Get available field names by statements table columns\n     * @summary Get available field names\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public statementsAvailableFieldsGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).statementsAvailableFieldsGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get statement configurations\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public statementsConfigGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).statementsConfigGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Update statement configurations\n     * @param {DefaultApiStatementsConfigPostRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public statementsConfigPost(requestParameters: DefaultApiStatementsConfigPostRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).statementsConfigPost(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Download statements\n     * @param {DefaultApiStatementsDownloadGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public statementsDownloadGet(requestParameters: DefaultApiStatementsDownloadGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).statementsDownloadGet(requestParameters.token, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Generate a download token for exported statements\n     * @param {DefaultApiStatementsDownloadTokenPostRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public statementsDownloadTokenPost(requestParameters: DefaultApiStatementsDownloadTokenPostRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).statementsDownloadTokenPost(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get a list of statements\n     * @param {DefaultApiStatementsListGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public statementsListGet(requestParameters: DefaultApiStatementsListGetRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).statementsListGet(requestParameters.beginTime, requestParameters.endTime, requestParameters.fields, requestParameters.resourceGroups, requestParameters.schemas, requestParameters.stmtTypes, requestParameters.text, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Drop all manually created bindings for a statement\n     * @param {DefaultApiStatementsPlanBindingDeleteRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public statementsPlanBindingDelete(requestParameters: DefaultApiStatementsPlanBindingDeleteRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).statementsPlanBindingDelete(requestParameters.sqlDigest, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get the bound plan digest (if exists) of a statement\n     * @param {DefaultApiStatementsPlanBindingGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public statementsPlanBindingGet(requestParameters: DefaultApiStatementsPlanBindingGetRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).statementsPlanBindingGet(requestParameters.sqlDigest, requestParameters.beginTime, requestParameters.endTime, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Create a binding for a statement and a plan\n     * @param {DefaultApiStatementsPlanBindingPostRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public statementsPlanBindingPost(requestParameters: DefaultApiStatementsPlanBindingPostRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).statementsPlanBindingPost(requestParameters.planDigest, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get details of a statement in an execution plan\n     * @param {DefaultApiStatementsPlanDetailGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public statementsPlanDetailGet(requestParameters: DefaultApiStatementsPlanDetailGetRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).statementsPlanDetailGet(requestParameters.beginTime, requestParameters.digest, requestParameters.endTime, requestParameters.plans, requestParameters.schemaName, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get execution plans of a statement\n     * @param {DefaultApiStatementsPlansGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public statementsPlansGet(requestParameters: DefaultApiStatementsPlansGetRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).statementsPlansGet(requestParameters.beginTime, requestParameters.digest, requestParameters.endTime, requestParameters.schemaName, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get all statement types\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public statementsStmtTypesGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).statementsStmtTypesGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Hide a TiDB instance\n     * @param {DefaultApiTopologyTidbAddressDeleteRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public topologyTidbAddressDelete(requestParameters: DefaultApiTopologyTidbAddressDeleteRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).topologyTidbAddressDelete(requestParameters.address, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get Top SQL config\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public topsqlConfigGet(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).topsqlConfigGet(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Update Top SQL config\n     * @param {DefaultApiTopsqlConfigPostRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public topsqlConfigPost(requestParameters: DefaultApiTopsqlConfigPostRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).topsqlConfigPost(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get TiKV network IO collection config\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public topsqlGetTiKVNetworkIOCollection(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).topsqlGetTiKVNetworkIOCollection(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get available instances\n     * @param {DefaultApiTopsqlInstancesGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public topsqlInstancesGet(requestParameters: DefaultApiTopsqlInstancesGetRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).topsqlInstancesGet(requestParameters.dataSource, requestParameters.end, requestParameters.start, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get summaries\n     * @param {DefaultApiTopsqlSummaryGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public topsqlSummaryGet(requestParameters: DefaultApiTopsqlSummaryGetRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).topsqlSummaryGet(requestParameters.dataSource, requestParameters.end, requestParameters.groupBy, requestParameters.instance, requestParameters.instanceType, requestParameters.orderBy, requestParameters.start, requestParameters.top, requestParameters.window, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Update TiKV network IO collection config\n     * @param {DefaultApiTopsqlUpdateTiKVNetworkIOCollectionRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public topsqlUpdateTiKVNetworkIOCollection(requestParameters: DefaultApiTopsqlUpdateTiKVNetworkIOCollectionRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).topsqlUpdateTiKVNetworkIOCollection(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get log in information, like supported authenticate types\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public userGetLoginInfo(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).userGetLoginInfo(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get sign out info\n     * @param {DefaultApiUserGetSignOutInfoRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public userGetSignOutInfo(requestParameters: DefaultApiUserGetSignOutInfoRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).userGetSignOutInfo(requestParameters.redirectUrl, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Log in\n     * @param {DefaultApiUserLoginRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public userLogin(requestParameters: DefaultApiUserLoginRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).userLogin(requestParameters.message, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Reset encryption key to revoke all authorized codes\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public userRevokeSession(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).userRevokeSession(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Create an impersonation\n     * @param {DefaultApiUserSSOCreateImpersonationRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public userSSOCreateImpersonation(requestParameters: DefaultApiUserSSOCreateImpersonationRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).userSSOCreateImpersonation(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get SSO Auth URL\n     * @param {DefaultApiUserSSOGetAuthURLRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public userSSOGetAuthURL(requestParameters: DefaultApiUserSSOGetAuthURLRequest = {}, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).userSSOGetAuthURL(requestParameters.codeVerifier, requestParameters.redirectUrl, requestParameters.state, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get SSO config\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public userSSOGetConfig(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).userSSOGetConfig(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary List all impersonations\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public userSSOListImpersonations(options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).userSSOListImpersonations(options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Set SSO config\n     * @param {DefaultApiUserSSOSetConfigRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public userSSOSetConfig(requestParameters: DefaultApiUserSSOSetConfigRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).userSSOSetConfig(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Share current session and generate a sharing code\n     * @param {DefaultApiUserShareSessionRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public userShareSession(requestParameters: DefaultApiUserShareSessionRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).userShareSession(requestParameters.request, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * View the finished profiling result of a task\n     * @summary View the result of a task\n     * @param {DefaultApiViewProfilingSingleRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof DefaultApi\n     */\n    public viewProfilingSingle(requestParameters: DefaultApiViewProfilingSingleRequest, options?: AxiosRequestConfig) {\n        return DefaultApiFp(this.configuration).viewProfilingSingle(requestParameters.token, options).then((request) => request(this.axios, this.basePath));\n    }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/api/statement-api.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';\nimport { Configuration } from '../configuration';\n// Some imports not used depending on template conditions\n// @ts-ignore\nimport { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from '../common';\n// @ts-ignore\nimport { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from '../base';\n// @ts-ignore\nimport { RestErrorResponse } from '../models';\n// @ts-ignore\nimport { StatementBinding } from '../models';\n/**\n * StatementApi - axios parameter creator\n * @export\n */\nexport const StatementApiAxiosParamCreator = function (configuration?: Configuration) {\n    return {\n        /**\n         *\n         * @summary Drop all manually created bindings for a statement\n         * @param {string} sqlDigest query template ID (a.k.a. sql digest)\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanBindingDelete: async (sqlDigest: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'sqlDigest' is not null or undefined\n            assertParamExists('statementsPlanBindingDelete', 'sqlDigest', sqlDigest)\n            const localVarPath = `/statements/plan/binding`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (sqlDigest !== undefined) {\n                localVarQueryParameter['sql_digest'] = sqlDigest;\n            }\n\n\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         *\n         * @summary Get the bound plan digest (if exists) of a statement\n         * @param {string} sqlDigest query template id\n         * @param {number} beginTime begin time\n         * @param {number} endTime end time\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanBindingGet: async (sqlDigest: string, beginTime: number, endTime: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'sqlDigest' is not null or undefined\n            assertParamExists('statementsPlanBindingGet', 'sqlDigest', sqlDigest)\n            // verify required parameter 'beginTime' is not null or undefined\n            assertParamExists('statementsPlanBindingGet', 'beginTime', beginTime)\n            // verify required parameter 'endTime' is not null or undefined\n            assertParamExists('statementsPlanBindingGet', 'endTime', endTime)\n            const localVarPath = `/statements/plan/binding`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (sqlDigest !== undefined) {\n                localVarQueryParameter['sql_digest'] = sqlDigest;\n            }\n\n            if (beginTime !== undefined) {\n                localVarQueryParameter['begin_time'] = beginTime;\n            }\n\n            if (endTime !== undefined) {\n                localVarQueryParameter['end_time'] = endTime;\n            }\n\n\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n        /**\n         *\n         * @summary Create a binding for a statement and a plan\n         * @param {string} planDigest plan digest id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanBindingPost: async (planDigest: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {\n            // verify required parameter 'planDigest' is not null or undefined\n            assertParamExists('statementsPlanBindingPost', 'planDigest', planDigest)\n            const localVarPath = `/statements/plan/binding`;\n            // use dummy base URL string because the URL constructor only accepts absolute URLs.\n            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);\n            let baseOptions;\n            if (configuration) {\n                baseOptions = configuration.baseOptions;\n            }\n\n            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};\n            const localVarHeaderParameter = {} as any;\n            const localVarQueryParameter = {} as any;\n\n            // authentication JwtAuth required\n            await setApiKeyToObject(localVarHeaderParameter, \"Authorization\", configuration)\n\n            if (planDigest !== undefined) {\n                localVarQueryParameter['plan_digest'] = planDigest;\n            }\n\n\n\n            setSearchParams(localVarUrlObj, localVarQueryParameter);\n            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};\n            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};\n\n            return {\n                url: toPathString(localVarUrlObj),\n                options: localVarRequestOptions,\n            };\n        },\n    }\n};\n\n/**\n * StatementApi - functional programming interface\n * @export\n */\nexport const StatementApiFp = function(configuration?: Configuration) {\n    const localVarAxiosParamCreator = StatementApiAxiosParamCreator(configuration)\n    return {\n        /**\n         *\n         * @summary Drop all manually created bindings for a statement\n         * @param {string} sqlDigest query template ID (a.k.a. sql digest)\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsPlanBindingDelete(sqlDigest: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsPlanBindingDelete(sqlDigest, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         *\n         * @summary Get the bound plan digest (if exists) of a statement\n         * @param {string} sqlDigest query template id\n         * @param {number} beginTime begin time\n         * @param {number} endTime end time\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsPlanBindingGet(sqlDigest: string, beginTime: number, endTime: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<StatementBinding>>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsPlanBindingGet(sqlDigest, beginTime, endTime, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n        /**\n         *\n         * @summary Create a binding for a statement and a plan\n         * @param {string} planDigest plan digest id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        async statementsPlanBindingPost(planDigest: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {\n            const localVarAxiosArgs = await localVarAxiosParamCreator.statementsPlanBindingPost(planDigest, options);\n            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);\n        },\n    }\n};\n\n/**\n * StatementApi - factory interface\n * @export\n */\nexport const StatementApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {\n    const localVarFp = StatementApiFp(configuration)\n    return {\n        /**\n         *\n         * @summary Drop all manually created bindings for a statement\n         * @param {string} sqlDigest query template ID (a.k.a. sql digest)\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanBindingDelete(sqlDigest: string, options?: any): AxiosPromise<string> {\n            return localVarFp.statementsPlanBindingDelete(sqlDigest, options).then((request) => request(axios, basePath));\n        },\n        /**\n         *\n         * @summary Get the bound plan digest (if exists) of a statement\n         * @param {string} sqlDigest query template id\n         * @param {number} beginTime begin time\n         * @param {number} endTime end time\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanBindingGet(sqlDigest: string, beginTime: number, endTime: number, options?: any): AxiosPromise<Array<StatementBinding>> {\n            return localVarFp.statementsPlanBindingGet(sqlDigest, beginTime, endTime, options).then((request) => request(axios, basePath));\n        },\n        /**\n         *\n         * @summary Create a binding for a statement and a plan\n         * @param {string} planDigest plan digest id\n         * @param {*} [options] Override http request option.\n         * @throws {RequiredError}\n         */\n        statementsPlanBindingPost(planDigest: string, options?: any): AxiosPromise<string> {\n            return localVarFp.statementsPlanBindingPost(planDigest, options).then((request) => request(axios, basePath));\n        },\n    };\n};\n\n/**\n * Request parameters for statementsPlanBindingDelete operation in StatementApi.\n * @export\n * @interface StatementApiStatementsPlanBindingDeleteRequest\n */\nexport interface StatementApiStatementsPlanBindingDeleteRequest {\n    /**\n     * query template ID (a.k.a. sql digest)\n     * @type {string}\n     * @memberof StatementApiStatementsPlanBindingDelete\n     */\n    readonly sqlDigest: string\n}\n\n/**\n * Request parameters for statementsPlanBindingGet operation in StatementApi.\n * @export\n * @interface StatementApiStatementsPlanBindingGetRequest\n */\nexport interface StatementApiStatementsPlanBindingGetRequest {\n    /**\n     * query template id\n     * @type {string}\n     * @memberof StatementApiStatementsPlanBindingGet\n     */\n    readonly sqlDigest: string\n\n    /**\n     * begin time\n     * @type {number}\n     * @memberof StatementApiStatementsPlanBindingGet\n     */\n    readonly beginTime: number\n\n    /**\n     * end time\n     * @type {number}\n     * @memberof StatementApiStatementsPlanBindingGet\n     */\n    readonly endTime: number\n}\n\n/**\n * Request parameters for statementsPlanBindingPost operation in StatementApi.\n * @export\n * @interface StatementApiStatementsPlanBindingPostRequest\n */\nexport interface StatementApiStatementsPlanBindingPostRequest {\n    /**\n     * plan digest id\n     * @type {string}\n     * @memberof StatementApiStatementsPlanBindingPost\n     */\n    readonly planDigest: string\n}\n\n/**\n * StatementApi - object-oriented interface\n * @export\n * @class StatementApi\n * @extends {BaseAPI}\n */\nexport class StatementApi extends BaseAPI {\n    /**\n     * \n     * @summary Drop all manually created bindings for a statement\n     * @param {StatementApiStatementsPlanBindingDeleteRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof StatementApi\n     */\n    public statementsPlanBindingDelete(requestParameters: StatementApiStatementsPlanBindingDeleteRequest, options?: AxiosRequestConfig) {\n        return StatementApiFp(this.configuration).statementsPlanBindingDelete(requestParameters.sqlDigest, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Get the bound plan digest (if exists) of a statement\n     * @param {StatementApiStatementsPlanBindingGetRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof StatementApi\n     */\n    public statementsPlanBindingGet(requestParameters: StatementApiStatementsPlanBindingGetRequest, options?: AxiosRequestConfig) {\n        return StatementApiFp(this.configuration).statementsPlanBindingGet(requestParameters.sqlDigest, requestParameters.beginTime, requestParameters.endTime, options).then((request) => request(this.axios, this.basePath));\n    }\n\n    /**\n     * \n     * @summary Create a binding for a statement and a plan\n     * @param {StatementApiStatementsPlanBindingPostRequest} requestParameters Request parameters.\n     * @param {*} [options] Override http request option.\n     * @throws {RequiredError}\n     * @memberof StatementApi\n     */\n    public statementsPlanBindingPost(requestParameters: StatementApiStatementsPlanBindingPostRequest, options?: AxiosRequestConfig) {\n        return StatementApiFp(this.configuration).statementsPlanBindingPost(requestParameters.planDigest, options).then((request) => request(this.axios, this.basePath));\n    }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/api.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\nexport * from './api/default-api';\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/base.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { Configuration } from \"./configuration\";\n// Some imports not used depending on template conditions\n// @ts-ignore\nimport globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';\n\nexport const BASE_PATH = \"/dashboard/api\".replace(/\\/+$/, \"\");\n\n/**\n *\n * @export\n */\nexport const COLLECTION_FORMATS = {\n    csv: \",\",\n    ssv: \" \",\n    tsv: \"\\t\",\n    pipes: \"|\",\n};\n\n/**\n *\n * @export\n * @interface RequestArgs\n */\nexport interface RequestArgs {\n    url: string;\n    options: AxiosRequestConfig;\n}\n\n/**\n *\n * @export\n * @class BaseAPI\n */\nexport class BaseAPI {\n    protected configuration: Configuration | undefined;\n\n    constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {\n        if (configuration) {\n            this.configuration = configuration;\n            this.basePath = configuration.basePath || this.basePath;\n        }\n    }\n};\n\n/**\n *\n * @export\n * @class RequiredError\n * @extends {Error}\n */\nexport class RequiredError extends Error {\n    name: \"RequiredError\" = \"RequiredError\";\n    constructor(public field: string, msg?: string) {\n        super(msg);\n    }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/common.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { Configuration } from \"./configuration\";\nimport { RequiredError, RequestArgs } from \"./base\";\nimport { AxiosInstance, AxiosResponse } from 'axios';\n\n/**\n *\n * @export\n */\nexport const DUMMY_BASE_URL = 'https://example.com'\n\n/**\n *\n * @throws {RequiredError}\n * @export\n */\nexport const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) {\n    if (paramValue === null || paramValue === undefined) {\n        throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`);\n    }\n}\n\n/**\n *\n * @export\n */\nexport const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) {\n    if (configuration && configuration.apiKey) {\n        const localVarApiKeyValue = typeof configuration.apiKey === 'function'\n            ? await configuration.apiKey(keyParamName)\n            : await configuration.apiKey;\n        object[keyParamName] = localVarApiKeyValue;\n    }\n}\n\n/**\n *\n * @export\n */\nexport const setBasicAuthToObject = function (object: any, configuration?: Configuration) {\n    if (configuration && (configuration.username || configuration.password)) {\n        object[\"auth\"] = { username: configuration.username, password: configuration.password };\n    }\n}\n\n/**\n *\n * @export\n */\nexport const setBearerAuthToObject = async function (object: any, configuration?: Configuration) {\n    if (configuration && configuration.accessToken) {\n        const accessToken = typeof configuration.accessToken === 'function'\n            ? await configuration.accessToken()\n            : await configuration.accessToken;\n        object[\"Authorization\"] = \"Bearer \" + accessToken;\n    }\n}\n\n/**\n *\n * @export\n */\nexport const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) {\n    if (configuration && configuration.accessToken) {\n        const localVarAccessTokenValue = typeof configuration.accessToken === 'function'\n            ? await configuration.accessToken(name, scopes)\n            : await configuration.accessToken;\n        object[\"Authorization\"] = \"Bearer \" + localVarAccessTokenValue;\n    }\n}\n\n/**\n *\n * @export\n */\nexport const setSearchParams = function (url: URL, ...objects: any[]) {\n    const searchParams = new URLSearchParams(url.search);\n    for (const object of objects) {\n        for (const key in object) {\n            if (Array.isArray(object[key])) {\n                searchParams.delete(key);\n                for (const item of object[key]) {\n                    searchParams.append(key, item);\n                }\n            } else {\n                searchParams.set(key, object[key]);\n            }\n        }\n    }\n    url.search = searchParams.toString();\n}\n\n/**\n *\n * @export\n */\nexport const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) {\n    const nonString = typeof value !== 'string';\n    const needsSerialization = nonString && configuration && configuration.isJsonMime\n        ? configuration.isJsonMime(requestOptions.headers['Content-Type'])\n        : nonString;\n    return needsSerialization\n        ? JSON.stringify(value !== undefined ? value : {})\n        : (value || \"\");\n}\n\n/**\n *\n * @export\n */\nexport const toPathString = function (url: URL) {\n    return url.pathname + url.search + url.hash\n}\n\n/**\n *\n * @export\n */\nexport const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {\n    return <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {\n        const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url};\n        return axios.request<T, R>(axiosRequestArgs);\n    };\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/configuration.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nexport interface ConfigurationParameters {\n    apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);\n    username?: string;\n    password?: string;\n    accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);\n    basePath?: string;\n    baseOptions?: any;\n    formDataCtor?: new () => any;\n}\n\nexport class Configuration {\n    /**\n     * parameter for apiKey security\n     * @param name security name\n     * @memberof Configuration\n     */\n    apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);\n    /**\n     * parameter for basic security\n     *\n     * @type {string}\n     * @memberof Configuration\n     */\n    username?: string;\n    /**\n     * parameter for basic security\n     *\n     * @type {string}\n     * @memberof Configuration\n     */\n    password?: string;\n    /**\n     * parameter for oauth2 security\n     * @param name security name\n     * @param scopes oauth2 scope\n     * @memberof Configuration\n     */\n    accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);\n    /**\n     * override base path\n     *\n     * @type {string}\n     * @memberof Configuration\n     */\n    basePath?: string;\n    /**\n     * base options for axios calls\n     *\n     * @type {any}\n     * @memberof Configuration\n     */\n    baseOptions?: any;\n    /**\n     * The FormData constructor that will be used to create multipart form data\n     * requests. You can inject this here so that execution environments that\n     * do not support the FormData class can still run the generated client.\n     *\n     * @type {new () => FormData}\n     */\n    formDataCtor?: new () => any;\n\n    constructor(param: ConfigurationParameters = {}) {\n        this.apiKey = param.apiKey;\n        this.username = param.username;\n        this.password = param.password;\n        this.accessToken = param.accessToken;\n        this.basePath = param.basePath;\n        this.baseOptions = param.baseOptions;\n        this.formDataCtor = param.formDataCtor;\n    }\n\n    /**\n     * Check if the given MIME is a JSON MIME.\n     * JSON MIME examples:\n     *   application/json\n     *   application/json; charset=UTF8\n     *   APPLICATION/JSON\n     *   application/vnd.company+json\n     * @param mime - MIME (Multipurpose Internet Mail Extensions)\n     * @return True if the given MIME is JSON, false otherwise.\n     */\n    public isJsonMime(mime: string): boolean {\n        const jsonMime: RegExp = new RegExp('^(application\\/json|[^;/ \\t]+\\/[^;/ \\t]+[+]json)[ \\t]*(;.*)?$', 'i');\n        return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');\n    }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/index.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nexport * from \"./api\";\nexport * from \"./configuration\";\nexport * from \"./models\";\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/clusterinfo-cluster-statistics-partial.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ClusterinfoClusterStatisticsPartial\n */\nexport interface ClusterinfoClusterStatisticsPartial {\n    /**\n     * \n     * @type {number}\n     * @memberof ClusterinfoClusterStatisticsPartial\n     */\n    'number_of_hosts'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ClusterinfoClusterStatisticsPartial\n     */\n    'number_of_instances'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ClusterinfoClusterStatisticsPartial\n     */\n    'total_logical_cores'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ClusterinfoClusterStatisticsPartial\n     */\n    'total_memory_capacity_bytes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ClusterinfoClusterStatisticsPartial\n     */\n    'total_physical_cores'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/clusterinfo-cluster-statistics.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { ClusterinfoClusterStatisticsPartial } from './clusterinfo-cluster-statistics-partial';\n\n/**\n * \n * @export\n * @interface ClusterinfoClusterStatistics\n */\nexport interface ClusterinfoClusterStatistics {\n    /**\n     * \n     * @type {number}\n     * @memberof ClusterinfoClusterStatistics\n     */\n    'probe_failure_hosts'?: number;\n    /**\n     * \n     * @type {{ [key: string]: ClusterinfoClusterStatisticsPartial; }}\n     * @memberof ClusterinfoClusterStatistics\n     */\n    'stats_by_instance_kind'?: { [key: string]: ClusterinfoClusterStatisticsPartial; };\n    /**\n     * \n     * @type {ClusterinfoClusterStatisticsPartial}\n     * @memberof ClusterinfoClusterStatistics\n     */\n    'total_stats'?: ClusterinfoClusterStatisticsPartial;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof ClusterinfoClusterStatistics\n     */\n    'versions'?: Array<string>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/clusterinfo-get-hosts-info-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { HostinfoInfo } from './hostinfo-info';\nimport { RestErrorResponse } from './rest-error-response';\n\n/**\n * \n * @export\n * @interface ClusterinfoGetHostsInfoResponse\n */\nexport interface ClusterinfoGetHostsInfoResponse {\n    /**\n     * \n     * @type {Array<HostinfoInfo>}\n     * @memberof ClusterinfoGetHostsInfoResponse\n     */\n    'hosts'?: Array<HostinfoInfo>;\n    /**\n     * \n     * @type {RestErrorResponse}\n     * @memberof ClusterinfoGetHostsInfoResponse\n     */\n    'warning'?: RestErrorResponse;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/clusterinfo-store-topology-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { TopologyStoreInfo } from './topology-store-info';\n\n/**\n * \n * @export\n * @interface ClusterinfoStoreTopologyResponse\n */\nexport interface ClusterinfoStoreTopologyResponse {\n    /**\n     * \n     * @type {Array<TopologyStoreInfo>}\n     * @memberof ClusterinfoStoreTopologyResponse\n     */\n    'tiflash'?: Array<TopologyStoreInfo>;\n    /**\n     * \n     * @type {Array<TopologyStoreInfo>}\n     * @memberof ClusterinfoStoreTopologyResponse\n     */\n    'tikv'?: Array<TopologyStoreInfo>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/code-share-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface CodeShareRequest\n */\nexport interface CodeShareRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof CodeShareRequest\n     */\n    'expire_in_sec'?: number;\n    /**\n     * \n     * @type {boolean}\n     * @memberof CodeShareRequest\n     */\n    'revoke_write_priv'?: boolean;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/code-share-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface CodeShareResponse\n */\nexport interface CodeShareResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof CodeShareResponse\n     */\n    'code'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/config-key-visual-config.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ConfigKeyVisualConfig\n */\nexport interface ConfigKeyVisualConfig {\n    /**\n     * \n     * @type {boolean}\n     * @memberof ConfigKeyVisualConfig\n     */\n    'auto_collection_disabled'?: boolean;\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigKeyVisualConfig\n     */\n    'policy'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigKeyVisualConfig\n     */\n    'policy_kv_separator'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/config-profiling-config.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { ModelRequestTargetNode } from './model-request-target-node';\n\n/**\n * \n * @export\n * @interface ConfigProfilingConfig\n */\nexport interface ConfigProfilingConfig {\n    /**\n     * \n     * @type {number}\n     * @memberof ConfigProfilingConfig\n     */\n    'auto_collection_duration_secs'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConfigProfilingConfig\n     */\n    'auto_collection_interval_secs'?: number;\n    /**\n     * \n     * @type {Array<ModelRequestTargetNode>}\n     * @memberof ConfigProfilingConfig\n     */\n    'auto_collection_targets'?: Array<ModelRequestTargetNode>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/config-ssocore-config.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ConfigSSOCoreConfig\n */\nexport interface ConfigSSOCoreConfig {\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigSSOCoreConfig\n     */\n    'client_id'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigSSOCoreConfig\n     */\n    'client_secret'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigSSOCoreConfig\n     */\n    'discovery_url'?: string;\n    /**\n     * \n     * @type {boolean}\n     * @memberof ConfigSSOCoreConfig\n     */\n    'enabled'?: boolean;\n    /**\n     * \n     * @type {boolean}\n     * @memberof ConfigSSOCoreConfig\n     */\n    'is_read_only'?: boolean;\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigSSOCoreConfig\n     */\n    'scopes'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/configuration-all-config-items.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { ConfigurationItem } from './configuration-item';\nimport { RestErrorResponse } from './rest-error-response';\n\n/**\n * \n * @export\n * @interface ConfigurationAllConfigItems\n */\nexport interface ConfigurationAllConfigItems {\n    /**\n     * \n     * @type {Array<RestErrorResponse>}\n     * @memberof ConfigurationAllConfigItems\n     */\n    'errors'?: Array<RestErrorResponse>;\n    /**\n     * \n     * @type {{ [key: string]: Array<ConfigurationItem>; }}\n     * @memberof ConfigurationAllConfigItems\n     */\n    'items'?: { [key: string]: Array<ConfigurationItem>; };\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/configuration-edit-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ConfigurationEditRequest\n */\nexport interface ConfigurationEditRequest {\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigurationEditRequest\n     */\n    'id'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigurationEditRequest\n     */\n    'kind'?: string;\n    /**\n     * \n     * @type {object}\n     * @memberof ConfigurationEditRequest\n     */\n    'new_value'?: object;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/configuration-edit-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { RestErrorResponse } from './rest-error-response';\n\n/**\n * \n * @export\n * @interface ConfigurationEditResponse\n */\nexport interface ConfigurationEditResponse {\n    /**\n     * \n     * @type {Array<RestErrorResponse>}\n     * @memberof ConfigurationEditResponse\n     */\n    'warnings'?: Array<RestErrorResponse>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/configuration-item.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ConfigurationItem\n */\nexport interface ConfigurationItem {\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigurationItem\n     */\n    'id'?: string;\n    /**\n     * \n     * @type {boolean}\n     * @memberof ConfigurationItem\n     */\n    'is_editable'?: boolean;\n    /**\n     * TODO: Support per-instance config\n     * @type {boolean}\n     * @memberof ConfigurationItem\n     */\n    'is_multi_value'?: boolean;\n    /**\n     * When multi value present, this contains one of the value\n     * @type {object}\n     * @memberof ConfigurationItem\n     */\n    'value'?: object;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/conprof-component-num.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ConprofComponentNum\n */\nexport interface ConprofComponentNum {\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponentNum\n     */\n    'pd'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponentNum\n     */\n    'ticdc'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponentNum\n     */\n    'tidb'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponentNum\n     */\n    'tiflash'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponentNum\n     */\n    'tikv'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/conprof-component.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ConprofComponent\n */\nexport interface ConprofComponent {\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofComponent\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofComponent\n     */\n    'name'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponent\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponent\n     */\n    'status_port'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/conprof-continuous-profiling-config.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ConprofContinuousProfilingConfig\n */\nexport interface ConprofContinuousProfilingConfig {\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofContinuousProfilingConfig\n     */\n    'data_retention_seconds'?: number;\n    /**\n     * \n     * @type {boolean}\n     * @memberof ConprofContinuousProfilingConfig\n     */\n    'enable'?: boolean;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofContinuousProfilingConfig\n     */\n    'interval_seconds'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofContinuousProfilingConfig\n     */\n    'profile_seconds'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofContinuousProfilingConfig\n     */\n    'timeout_seconds'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/conprof-estimate-size-res.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ConprofEstimateSizeRes\n */\nexport interface ConprofEstimateSizeRes {\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofEstimateSizeRes\n     */\n    'instance_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofEstimateSizeRes\n     */\n    'profile_size'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/conprof-group-profile-detail.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { ConprofProfileDetail } from './conprof-profile-detail';\n\n/**\n * \n * @export\n * @interface ConprofGroupProfileDetail\n */\nexport interface ConprofGroupProfileDetail {\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofGroupProfileDetail\n     */\n    'profile_duration_secs'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofGroupProfileDetail\n     */\n    'state'?: string;\n    /**\n     * \n     * @type {Array<ConprofProfileDetail>}\n     * @memberof ConprofGroupProfileDetail\n     */\n    'target_profiles'?: Array<ConprofProfileDetail>;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofGroupProfileDetail\n     */\n    'ts'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/conprof-group-profiles.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { ConprofComponentNum } from './conprof-component-num';\n\n/**\n * \n * @export\n * @interface ConprofGroupProfiles\n */\nexport interface ConprofGroupProfiles {\n    /**\n     * \n     * @type {ConprofComponentNum}\n     * @memberof ConprofGroupProfiles\n     */\n    'component_num'?: ConprofComponentNum;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofGroupProfiles\n     */\n    'profile_duration_secs'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofGroupProfiles\n     */\n    'state'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofGroupProfiles\n     */\n    'ts'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/conprof-ng-monitoring-config.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { ConprofContinuousProfilingConfig } from './conprof-continuous-profiling-config';\n\n/**\n * \n * @export\n * @interface ConprofNgMonitoringConfig\n */\nexport interface ConprofNgMonitoringConfig {\n    /**\n     * \n     * @type {ConprofContinuousProfilingConfig}\n     * @memberof ConprofNgMonitoringConfig\n     */\n    'continuous_profiling'?: ConprofContinuousProfilingConfig;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/conprof-profile-detail.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { ConprofTarget } from './conprof-target';\n\n/**\n * \n * @export\n * @interface ConprofProfileDetail\n */\nexport interface ConprofProfileDetail {\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofProfileDetail\n     */\n    'error'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofProfileDetail\n     */\n    'profile_type'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofProfileDetail\n     */\n    'state'?: string;\n    /**\n     * \n     * @type {ConprofTarget}\n     * @memberof ConprofProfileDetail\n     */\n    'target'?: ConprofTarget;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/conprof-target.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ConprofTarget\n */\nexport interface ConprofTarget {\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofTarget\n     */\n    'address'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofTarget\n     */\n    'component'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/deadlock-model.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface DeadlockModel\n */\nexport interface DeadlockModel {\n    /**\n     * \n     * @type {string}\n     * @memberof DeadlockModel\n     */\n    'current_sql'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof DeadlockModel\n     */\n    'id'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof DeadlockModel\n     */\n    'instance'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DeadlockModel\n     */\n    'key'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DeadlockModel\n     */\n    'key_info'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DeadlockModel\n     */\n    'occur_time'?: string;\n    /**\n     * \n     * @type {boolean}\n     * @memberof DeadlockModel\n     */\n    'retryable'?: boolean;\n    /**\n     * \n     * @type {number}\n     * @memberof DeadlockModel\n     */\n    'trx_holding_lock'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof DeadlockModel\n     */\n    'try_lock_trx_id'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/decorator-label-key.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface DecoratorLabelKey\n */\nexport interface DecoratorLabelKey {\n    /**\n     * \n     * @type {string}\n     * @memberof DecoratorLabelKey\n     */\n    'key': string;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof DecoratorLabelKey\n     */\n    'labels': Array<string>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/diagnose-gen-diagnosis-report-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface DiagnoseGenDiagnosisReportRequest\n */\nexport interface DiagnoseGenDiagnosisReportRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenDiagnosisReportRequest\n     */\n    'end_time'?: number;\n    /**\n     * values: config, error, performance\n     * @type {string}\n     * @memberof DiagnoseGenDiagnosisReportRequest\n     */\n    'kind'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenDiagnosisReportRequest\n     */\n    'start_time'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/diagnose-generate-metrics-relation-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface DiagnoseGenerateMetricsRelationRequest\n */\nexport interface DiagnoseGenerateMetricsRelationRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenerateMetricsRelationRequest\n     */\n    'end_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenerateMetricsRelationRequest\n     */\n    'start_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseGenerateMetricsRelationRequest\n     */\n    'type'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/diagnose-generate-report-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface DiagnoseGenerateReportRequest\n */\nexport interface DiagnoseGenerateReportRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenerateReportRequest\n     */\n    'compare_end_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenerateReportRequest\n     */\n    'compare_start_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenerateReportRequest\n     */\n    'end_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenerateReportRequest\n     */\n    'start_time'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/diagnose-report.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface DiagnoseReport\n */\nexport interface DiagnoseReport {\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'compare_end_time'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'compare_start_time'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'content'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'created_at'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'end_time'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'id'?: string;\n    /**\n     * 0~100\n     * @type {number}\n     * @memberof DiagnoseReport\n     */\n    'progress'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'start_time'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/diagnose-table-def.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { DiagnoseTableRowDef } from './diagnose-table-row-def';\n\n/**\n * \n * @export\n * @interface DiagnoseTableDef\n */\nexport interface DiagnoseTableDef {\n    /**\n     * The category of the table, such as [TiDB]\n     * @type {Array<string>}\n     * @memberof DiagnoseTableDef\n     */\n    'category'?: Array<string>;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof DiagnoseTableDef\n     */\n    'column'?: Array<string>;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseTableDef\n     */\n    'comment'?: string;\n    /**\n     * \n     * @type {Array<DiagnoseTableRowDef>}\n     * @memberof DiagnoseTableDef\n     */\n    'rows'?: Array<DiagnoseTableRowDef>;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseTableDef\n     */\n    'title'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/diagnose-table-row-def.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface DiagnoseTableRowDef\n */\nexport interface DiagnoseTableRowDef {\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseTableRowDef\n     */\n    'comment'?: string;\n    /**\n     * SubValues need fold default.\n     * @type {Array<Array<string>>}\n     * @memberof DiagnoseTableRowDef\n     */\n    'sub_values'?: Array<Array<string>>;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof DiagnoseTableRowDef\n     */\n    'values'?: Array<string>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/endpoint-apidefinition.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { EndpointAPIParamDefinition } from './endpoint-apiparam-definition';\n\n/**\n * \n * @export\n * @interface EndpointAPIDefinition\n */\nexport interface EndpointAPIDefinition {\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointAPIDefinition\n     */\n    'component'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointAPIDefinition\n     */\n    'id'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointAPIDefinition\n     */\n    'method'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointAPIDefinition\n     */\n    'path'?: string;\n    /**\n     * e.g. /stats/dump/{db}/{table} -> db, table\n     * @type {Array<EndpointAPIParamDefinition>}\n     * @memberof EndpointAPIDefinition\n     */\n    'path_params'?: Array<EndpointAPIParamDefinition>;\n    /**\n     * e.g. /debug/pprof?seconds=1 -> seconds\n     * @type {Array<EndpointAPIParamDefinition>}\n     * @memberof EndpointAPIDefinition\n     */\n    'query_params'?: Array<EndpointAPIParamDefinition>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/endpoint-apiparam-definition.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface EndpointAPIParamDefinition\n */\nexport interface EndpointAPIParamDefinition {\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointAPIParamDefinition\n     */\n    'name'?: string;\n    /**\n     * \n     * @type {boolean}\n     * @memberof EndpointAPIParamDefinition\n     */\n    'required'?: boolean;\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointAPIParamDefinition\n     */\n    'ui_kind'?: string;\n    /**\n     * varies by different ui kinds\n     * @type {object}\n     * @memberof EndpointAPIParamDefinition\n     */\n    'ui_props'?: object;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/endpoint-request-payload.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface EndpointRequestPayload\n */\nexport interface EndpointRequestPayload {\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointRequestPayload\n     */\n    'api_id'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointRequestPayload\n     */\n    'host'?: string;\n    /**\n     * \n     * @type {{ [key: string]: string; }}\n     * @memberof EndpointRequestPayload\n     */\n    'param_values'?: { [key: string]: string; };\n    /**\n     * \n     * @type {number}\n     * @memberof EndpointRequestPayload\n     */\n    'port'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/hostinfo-cpuinfo.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface HostinfoCPUInfo\n */\nexport interface HostinfoCPUInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof HostinfoCPUInfo\n     */\n    'arch'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoCPUInfo\n     */\n    'logical_cores'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoCPUInfo\n     */\n    'physical_cores'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/hostinfo-cpuusage-info.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface HostinfoCPUUsageInfo\n */\nexport interface HostinfoCPUUsageInfo {\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoCPUUsageInfo\n     */\n    'idle'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoCPUUsageInfo\n     */\n    'system'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/hostinfo-info.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { HostinfoCPUInfo } from './hostinfo-cpuinfo';\nimport { HostinfoCPUUsageInfo } from './hostinfo-cpuusage-info';\nimport { HostinfoInstanceInfo } from './hostinfo-instance-info';\nimport { HostinfoMemoryUsageInfo } from './hostinfo-memory-usage-info';\nimport { HostinfoPartitionInfo } from './hostinfo-partition-info';\n\n/**\n * \n * @export\n * @interface HostinfoInfo\n */\nexport interface HostinfoInfo {\n    /**\n     * \n     * @type {HostinfoCPUInfo}\n     * @memberof HostinfoInfo\n     */\n    'cpu_info'?: HostinfoCPUInfo;\n    /**\n     * \n     * @type {HostinfoCPUUsageInfo}\n     * @memberof HostinfoInfo\n     */\n    'cpu_usage'?: HostinfoCPUUsageInfo;\n    /**\n     * \n     * @type {string}\n     * @memberof HostinfoInfo\n     */\n    'host'?: string;\n    /**\n     * Instances in the current host. The key is instance address\n     * @type {{ [key: string]: HostinfoInstanceInfo; }}\n     * @memberof HostinfoInfo\n     */\n    'instances'?: { [key: string]: HostinfoInstanceInfo; };\n    /**\n     * \n     * @type {HostinfoMemoryUsageInfo}\n     * @memberof HostinfoInfo\n     */\n    'memory_usage'?: HostinfoMemoryUsageInfo;\n    /**\n     * Containing unused partitions. The key is path in lower case. Note: deviceName is not used as the key, since TiDB and TiKV may return different deviceName for the same device.\n     * @type {{ [key: string]: HostinfoPartitionInfo; }}\n     * @memberof HostinfoInfo\n     */\n    'partitions'?: { [key: string]: HostinfoPartitionInfo; };\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/hostinfo-instance-info.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface HostinfoInstanceInfo\n */\nexport interface HostinfoInstanceInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof HostinfoInstanceInfo\n     */\n    'partition_path_lower'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof HostinfoInstanceInfo\n     */\n    'type'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/hostinfo-memory-usage-info.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface HostinfoMemoryUsageInfo\n */\nexport interface HostinfoMemoryUsageInfo {\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoMemoryUsageInfo\n     */\n    'total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoMemoryUsageInfo\n     */\n    'used'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/hostinfo-partition-info.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface HostinfoPartitionInfo\n */\nexport interface HostinfoPartitionInfo {\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoPartitionInfo\n     */\n    'free'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof HostinfoPartitionInfo\n     */\n    'fstype'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof HostinfoPartitionInfo\n     */\n    'path'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoPartitionInfo\n     */\n    'total'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/index.ts",
    "content": "export * from './clusterinfo-cluster-statistics';\nexport * from './clusterinfo-cluster-statistics-partial';\nexport * from './clusterinfo-get-hosts-info-response';\nexport * from './clusterinfo-store-topology-response';\nexport * from './code-share-request';\nexport * from './code-share-response';\nexport * from './config-key-visual-config';\nexport * from './config-profiling-config';\nexport * from './config-ssocore-config';\nexport * from './configuration-all-config-items';\nexport * from './configuration-edit-request';\nexport * from './configuration-edit-response';\nexport * from './configuration-item';\nexport * from './conprof-component';\nexport * from './conprof-component-num';\nexport * from './conprof-continuous-profiling-config';\nexport * from './conprof-estimate-size-res';\nexport * from './conprof-group-profile-detail';\nexport * from './conprof-group-profiles';\nexport * from './conprof-ng-monitoring-config';\nexport * from './conprof-profile-detail';\nexport * from './conprof-target';\nexport * from './deadlock-model';\nexport * from './decorator-label-key';\nexport * from './diagnose-gen-diagnosis-report-request';\nexport * from './diagnose-generate-metrics-relation-request';\nexport * from './diagnose-generate-report-request';\nexport * from './diagnose-report';\nexport * from './diagnose-table-def';\nexport * from './diagnose-table-row-def';\nexport * from './endpoint-apidefinition';\nexport * from './endpoint-apiparam-definition';\nexport * from './endpoint-request-payload';\nexport * from './hostinfo-cpuinfo';\nexport * from './hostinfo-cpuusage-info';\nexport * from './hostinfo-info';\nexport * from './hostinfo-instance-info';\nexport * from './hostinfo-memory-usage-info';\nexport * from './hostinfo-partition-info';\nexport * from './info-info-response';\nexport * from './info-table-schema';\nexport * from './info-who-am-iresponse';\nexport * from './logsearch-create-task-group-request';\nexport * from './logsearch-preview-model';\nexport * from './logsearch-search-log-request';\nexport * from './logsearch-task-group-model';\nexport * from './logsearch-task-group-response';\nexport * from './logsearch-task-model';\nexport * from './matrix-matrix';\nexport * from './metrics-get-prom-address-config-response';\nexport * from './metrics-put-custom-prom-address-request';\nexport * from './metrics-put-custom-prom-address-response';\nexport * from './metrics-query-response';\nexport * from './model-request-target-node';\nexport * from './model-request-target-statistics';\nexport * from './profiling-group-detail-response';\nexport * from './profiling-start-request';\nexport * from './profiling-task-group-model';\nexport * from './profiling-task-model';\nexport * from './queryeditor-run-request';\nexport * from './queryeditor-run-response';\nexport * from './resourcemanager-calibrate-response';\nexport * from './resourcemanager-get-config-response';\nexport * from './resourcemanager-resource-info-row-def';\nexport * from './rest-error-response';\nexport * from './slowquery-get-list-request';\nexport * from './slowquery-model';\nexport * from './sso-create-impersonation-request';\nexport * from './sso-ssoimpersonation-model';\nexport * from './sso-set-config-request';\nexport * from './statement-binding';\nexport * from './statement-editable-config';\nexport * from './statement-get-statements-request';\nexport * from './statement-model';\nexport * from './topology-alert-manager-info';\nexport * from './topology-grafana-info';\nexport * from './topology-pdinfo';\nexport * from './topology-scheduling-info';\nexport * from './topology-store-info';\nexport * from './topology-store-labels';\nexport * from './topology-store-location';\nexport * from './topology-tsoinfo';\nexport * from './topology-ti-cdcinfo';\nexport * from './topology-ti-dbinfo';\nexport * from './topology-ti-proxy-info';\nexport * from './topsql-editable-config';\nexport * from './topsql-instance-item';\nexport * from './topsql-instance-response';\nexport * from './topsql-summary-by-item';\nexport * from './topsql-summary-item';\nexport * from './topsql-summary-plan-item';\nexport * from './topsql-summary-response';\nexport * from './topsql-tikv-network-io-collection-config';\nexport * from './topsql-update-tikv-network-io-collection-response';\nexport * from './user-authenticate-form';\nexport * from './user-get-login-info-response';\nexport * from './user-sign-out-info';\nexport * from './user-token-response';\nexport * from './version-info';\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/info-info-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { VersionInfo } from './version-info';\n\n/**\n * \n * @export\n * @interface InfoInfoResponse\n */\nexport interface InfoInfoResponse {\n    /**\n     * \n     * @type {boolean}\n     * @memberof InfoInfoResponse\n     */\n    'enable_experimental'?: boolean;\n    /**\n     * \n     * @type {boolean}\n     * @memberof InfoInfoResponse\n     */\n    'enable_telemetry'?: boolean;\n    /**\n     * \n     * @type {string}\n     * @memberof InfoInfoResponse\n     */\n    'ngm_state'?: string;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof InfoInfoResponse\n     */\n    'supported_features'?: Array<string>;\n    /**\n     * \n     * @type {VersionInfo}\n     * @memberof InfoInfoResponse\n     */\n    'version'?: VersionInfo;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/info-table-schema.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface InfoTableSchema\n */\nexport interface InfoTableSchema {\n    /**\n     * \n     * @type {string}\n     * @memberof InfoTableSchema\n     */\n    'table_id'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof InfoTableSchema\n     */\n    'table_name'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/info-who-am-iresponse.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface InfoWhoAmIResponse\n */\nexport interface InfoWhoAmIResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof InfoWhoAmIResponse\n     */\n    'display_name'?: string;\n    /**\n     * \n     * @type {boolean}\n     * @memberof InfoWhoAmIResponse\n     */\n    'is_shareable'?: boolean;\n    /**\n     * \n     * @type {boolean}\n     * @memberof InfoWhoAmIResponse\n     */\n    'is_writeable'?: boolean;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/logsearch-create-task-group-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { LogsearchSearchLogRequest } from './logsearch-search-log-request';\nimport { ModelRequestTargetNode } from './model-request-target-node';\n\n/**\n * \n * @export\n * @interface LogsearchCreateTaskGroupRequest\n */\nexport interface LogsearchCreateTaskGroupRequest {\n    /**\n     * \n     * @type {LogsearchSearchLogRequest}\n     * @memberof LogsearchCreateTaskGroupRequest\n     */\n    'request': LogsearchSearchLogRequest;\n    /**\n     * \n     * @type {Array<ModelRequestTargetNode>}\n     * @memberof LogsearchCreateTaskGroupRequest\n     */\n    'targets': Array<ModelRequestTargetNode>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/logsearch-preview-model.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface LogsearchPreviewModel\n */\nexport interface LogsearchPreviewModel {\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchPreviewModel\n     */\n    'id'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchPreviewModel\n     */\n    'level'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof LogsearchPreviewModel\n     */\n    'message'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchPreviewModel\n     */\n    'task_group_id'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchPreviewModel\n     */\n    'task_id'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchPreviewModel\n     */\n    'time'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/logsearch-search-log-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface LogsearchSearchLogRequest\n */\nexport interface LogsearchSearchLogRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchSearchLogRequest\n     */\n    'end_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchSearchLogRequest\n     */\n    'min_level'?: number;\n    /**\n     * We use a string array to represent multiple CNF pattern sceniaor like: SELECT * FROM t WHERE c LIKE \\'%s%\\' and c REGEXP \\'.*a.*\\' because Golang and Rust don\\'t support perl-like (?=re1)(?=re2)\n     * @type {Array<string>}\n     * @memberof LogsearchSearchLogRequest\n     */\n    'patterns'?: Array<string>;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchSearchLogRequest\n     */\n    'start_time'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/logsearch-task-group-model.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { LogsearchSearchLogRequest } from './logsearch-search-log-request';\nimport { ModelRequestTargetStatistics } from './model-request-target-statistics';\n\n/**\n * \n * @export\n * @interface LogsearchTaskGroupModel\n */\nexport interface LogsearchTaskGroupModel {\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchTaskGroupModel\n     */\n    'id'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof LogsearchTaskGroupModel\n     */\n    'log_store_dir'?: string;\n    /**\n     * \n     * @type {LogsearchSearchLogRequest}\n     * @memberof LogsearchTaskGroupModel\n     */\n    'search_request'?: LogsearchSearchLogRequest;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchTaskGroupModel\n     */\n    'state'?: number;\n    /**\n     * \n     * @type {ModelRequestTargetStatistics}\n     * @memberof LogsearchTaskGroupModel\n     */\n    'target_stats'?: ModelRequestTargetStatistics;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/logsearch-task-group-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { LogsearchTaskGroupModel } from './logsearch-task-group-model';\nimport { LogsearchTaskModel } from './logsearch-task-model';\n\n/**\n * \n * @export\n * @interface LogsearchTaskGroupResponse\n */\nexport interface LogsearchTaskGroupResponse {\n    /**\n     * \n     * @type {LogsearchTaskGroupModel}\n     * @memberof LogsearchTaskGroupResponse\n     */\n    'task_group'?: LogsearchTaskGroupModel;\n    /**\n     * \n     * @type {Array<LogsearchTaskModel>}\n     * @memberof LogsearchTaskGroupResponse\n     */\n    'tasks'?: Array<LogsearchTaskModel>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/logsearch-task-model.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { ModelRequestTargetNode } from './model-request-target-node';\n\n/**\n * \n * @export\n * @interface LogsearchTaskModel\n */\nexport interface LogsearchTaskModel {\n    /**\n     * \n     * @type {string}\n     * @memberof LogsearchTaskModel\n     */\n    'error'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchTaskModel\n     */\n    'id'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof LogsearchTaskModel\n     */\n    'log_store_path'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchTaskModel\n     */\n    'size'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof LogsearchTaskModel\n     */\n    'slow_log_store_path'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchTaskModel\n     */\n    'state'?: number;\n    /**\n     * \n     * @type {ModelRequestTargetNode}\n     * @memberof LogsearchTaskModel\n     */\n    'target'?: ModelRequestTargetNode;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchTaskModel\n     */\n    'task_group_id'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/matrix-matrix.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { DecoratorLabelKey } from './decorator-label-key';\n\n/**\n * \n * @export\n * @interface MatrixMatrix\n */\nexport interface MatrixMatrix {\n    /**\n     * \n     * @type {{ [key: string]: Array<Array<number>>; }}\n     * @memberof MatrixMatrix\n     */\n    'data': { [key: string]: Array<Array<number>>; };\n    /**\n     * \n     * @type {Array<DecoratorLabelKey>}\n     * @memberof MatrixMatrix\n     */\n    'keyAxis': Array<DecoratorLabelKey>;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof MatrixMatrix\n     */\n    'timeAxis': Array<number>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/metrics-get-prom-address-config-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface MetricsGetPromAddressConfigResponse\n */\nexport interface MetricsGetPromAddressConfigResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof MetricsGetPromAddressConfigResponse\n     */\n    'customized_addr'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof MetricsGetPromAddressConfigResponse\n     */\n    'deployed_addr'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/metrics-put-custom-prom-address-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface MetricsPutCustomPromAddressRequest\n */\nexport interface MetricsPutCustomPromAddressRequest {\n    /**\n     * \n     * @type {string}\n     * @memberof MetricsPutCustomPromAddressRequest\n     */\n    'address'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/metrics-put-custom-prom-address-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface MetricsPutCustomPromAddressResponse\n */\nexport interface MetricsPutCustomPromAddressResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof MetricsPutCustomPromAddressResponse\n     */\n    'normalized_address'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/metrics-query-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface MetricsQueryResponse\n */\nexport interface MetricsQueryResponse {\n    /**\n     * \n     * @type {object}\n     * @memberof MetricsQueryResponse\n     */\n    'data'?: object;\n    /**\n     * \n     * @type {string}\n     * @memberof MetricsQueryResponse\n     */\n    'status'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/model-request-target-node.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ModelRequestTargetNode\n */\nexport interface ModelRequestTargetNode {\n    /**\n     * \n     * @type {string}\n     * @memberof ModelRequestTargetNode\n     */\n    'display_name'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ModelRequestTargetNode\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ModelRequestTargetNode\n     */\n    'kind'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetNode\n     */\n    'port'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/model-request-target-statistics.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ModelRequestTargetStatistics\n */\nexport interface ModelRequestTargetStatistics {\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_pd_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_scheduling_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_ticdc_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_tidb_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_tiflash_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_tikv_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_tiproxy_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_tso_nodes'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/profiling-group-detail-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { ProfilingTaskGroupModel } from './profiling-task-group-model';\nimport { ProfilingTaskModel } from './profiling-task-model';\n\n/**\n * \n * @export\n * @interface ProfilingGroupDetailResponse\n */\nexport interface ProfilingGroupDetailResponse {\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingGroupDetailResponse\n     */\n    'server_time'?: number;\n    /**\n     * \n     * @type {ProfilingTaskGroupModel}\n     * @memberof ProfilingGroupDetailResponse\n     */\n    'task_group_status'?: ProfilingTaskGroupModel;\n    /**\n     * \n     * @type {Array<ProfilingTaskModel>}\n     * @memberof ProfilingGroupDetailResponse\n     */\n    'tasks_status'?: Array<ProfilingTaskModel>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/profiling-start-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { ModelRequestTargetNode } from './model-request-target-node';\n\n/**\n * \n * @export\n * @interface ProfilingStartRequest\n */\nexport interface ProfilingStartRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingStartRequest\n     */\n    'duration_secs'?: number;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof ProfilingStartRequest\n     */\n    'requsted_profiling_types'?: Array<string>;\n    /**\n     * \n     * @type {Array<ModelRequestTargetNode>}\n     * @memberof ProfilingStartRequest\n     */\n    'targets'?: Array<ModelRequestTargetNode>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/profiling-task-group-model.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { ModelRequestTargetStatistics } from './model-request-target-statistics';\n\n/**\n * \n * @export\n * @interface ProfilingTaskGroupModel\n */\nexport interface ProfilingTaskGroupModel {\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskGroupModel\n     */\n    'id'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskGroupModel\n     */\n    'profile_duration_secs'?: number;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof ProfilingTaskGroupModel\n     */\n    'requsted_profiling_types'?: Array<string>;\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskGroupModel\n     */\n    'started_at'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskGroupModel\n     */\n    'state'?: number;\n    /**\n     * \n     * @type {ModelRequestTargetStatistics}\n     * @memberof ProfilingTaskGroupModel\n     */\n    'target_stats'?: ModelRequestTargetStatistics;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/profiling-task-model.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { ModelRequestTargetNode } from './model-request-target-node';\n\n/**\n * \n * @export\n * @interface ProfilingTaskModel\n */\nexport interface ProfilingTaskModel {\n    /**\n     * \n     * @type {string}\n     * @memberof ProfilingTaskModel\n     */\n    'error'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskModel\n     */\n    'id'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof ProfilingTaskModel\n     */\n    'profiling_type'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ProfilingTaskModel\n     */\n    'raw_data_type'?: string;\n    /**\n     * The start running time, reset when retry. Used to estimate approximate profiling progress.\n     * @type {number}\n     * @memberof ProfilingTaskModel\n     */\n    'started_at'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskModel\n     */\n    'state'?: number;\n    /**\n     * \n     * @type {ModelRequestTargetNode}\n     * @memberof ProfilingTaskModel\n     */\n    'target'?: ModelRequestTargetNode;\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskModel\n     */\n    'task_group_id'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/queryeditor-run-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface QueryeditorRunRequest\n */\nexport interface QueryeditorRunRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof QueryeditorRunRequest\n     */\n    'max_rows'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof QueryeditorRunRequest\n     */\n    'statements'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/queryeditor-run-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface QueryeditorRunResponse\n */\nexport interface QueryeditorRunResponse {\n    /**\n     * \n     * @type {number}\n     * @memberof QueryeditorRunResponse\n     */\n    'actual_rows'?: number;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof QueryeditorRunResponse\n     */\n    'column_names'?: Array<string>;\n    /**\n     * \n     * @type {string}\n     * @memberof QueryeditorRunResponse\n     */\n    'error_msg'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof QueryeditorRunResponse\n     */\n    'execution_ms'?: number;\n    /**\n     * \n     * @type {Array<Array<object>>}\n     * @memberof QueryeditorRunResponse\n     */\n    'rows'?: Array<Array<object>>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/resourcemanager-calibrate-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ResourcemanagerCalibrateResponse\n */\nexport interface ResourcemanagerCalibrateResponse {\n    /**\n     * \n     * @type {number}\n     * @memberof ResourcemanagerCalibrateResponse\n     */\n    'estimated_capacity'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/resourcemanager-get-config-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ResourcemanagerGetConfigResponse\n */\nexport interface ResourcemanagerGetConfigResponse {\n    /**\n     * \n     * @type {boolean}\n     * @memberof ResourcemanagerGetConfigResponse\n     */\n    'enable'?: boolean;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/resourcemanager-resource-info-row-def.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface ResourcemanagerResourceInfoRowDef\n */\nexport interface ResourcemanagerResourceInfoRowDef {\n    /**\n     * \n     * @type {string}\n     * @memberof ResourcemanagerResourceInfoRowDef\n     */\n    'burstable'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ResourcemanagerResourceInfoRowDef\n     */\n    'name'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ResourcemanagerResourceInfoRowDef\n     */\n    'priority'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ResourcemanagerResourceInfoRowDef\n     */\n    'ru_per_sec'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/rest-error-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface RestErrorResponse\n */\nexport interface RestErrorResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof RestErrorResponse\n     */\n    'code'?: string;\n    /**\n     * \n     * @type {boolean}\n     * @memberof RestErrorResponse\n     */\n    'error'?: boolean;\n    /**\n     * \n     * @type {string}\n     * @memberof RestErrorResponse\n     */\n    'full_text'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof RestErrorResponse\n     */\n    'message'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/slowquery-get-list-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface SlowqueryGetListRequest\n */\nexport interface SlowqueryGetListRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryGetListRequest\n     */\n    'begin_time'?: number;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof SlowqueryGetListRequest\n     */\n    'db'?: Array<string>;\n    /**\n     * \n     * @type {boolean}\n     * @memberof SlowqueryGetListRequest\n     */\n    'desc'?: boolean;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryGetListRequest\n     */\n    'digest'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryGetListRequest\n     */\n    'end_time'?: number;\n    /**\n     * example: \\\"Query,Digest\\\"\n     * @type {string}\n     * @memberof SlowqueryGetListRequest\n     */\n    'fields'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryGetListRequest\n     */\n    'limit'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryGetListRequest\n     */\n    'orderBy'?: string;\n    /**\n     * for showing slow queries in the statement detail page\n     * @type {Array<string>}\n     * @memberof SlowqueryGetListRequest\n     */\n    'plans'?: Array<string>;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof SlowqueryGetListRequest\n     */\n    'resource_group'?: Array<string>;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryGetListRequest\n     */\n    'text'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/slowquery-model.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface SlowqueryModel\n */\nexport interface SlowqueryModel {\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'backoff_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'backoff_types'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'binary_plan'?: string;\n    /**\n     * Computed fields\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'binary_plan_json'?: string;\n    /**\n     * binary plan plain text\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'binary_plan_text'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'commit_backoff_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'commit_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'compile_time'?: number;\n    /**\n     * TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'connection_id'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'cop_proc_addr'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_proc_avg'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_proc_max'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_proc_p90'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'cop_wait_addr'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_wait_avg'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_wait_max'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_wait_p90'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'db'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'digest'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'disk_max'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'exec_retry_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'get_commit_ts_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'host'?: string;\n    /**\n     * IA remote read\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'ia_remote_read_segment_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'ia_remote_read_segment_wait_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'index_names'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'instance'?: string;\n    /**\n     * Basic\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'is_internal'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'local_latch_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'lock_keys_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'mem_arbitration'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'memory_max'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'optimize_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'parse_time'?: number;\n    /**\n     * deprecated, replaced by BinaryPlanText\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'plan'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'plan_from_binding'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'plan_from_cache'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'prepared'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'preproc_subqueries_time'?: number;\n    /**\n     * Detail\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'prev_stmt'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'prewrite_region'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'prewrite_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'process_keys'?: number;\n    /**\n     * Time\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'process_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'query'?: string;\n    /**\n     * latency\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'query_time'?: number;\n    /**\n     * Coprocessor\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'request_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'resolve_lock_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'resource_group'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'rewrite_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'rocksdb_block_cache_hit_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'rocksdb_block_read_byte'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'rocksdb_block_read_count'?: number;\n    /**\n     * RocksDB\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'rocksdb_delete_skipped_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'rocksdb_key_skipped_count'?: number;\n    /**\n     * Resource Control\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'ru'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'stats'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'success'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'time_queued_by_rc'?: number;\n    /**\n     * finish time\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'total_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'txn_retry'?: number;\n    /**\n     * TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'txn_start_ts'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_received_tiflash_cross_zone'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_received_tiflash_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_received_tikv_cross_zone'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_received_tikv_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_sent_tiflash_cross_zone'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_sent_tiflash_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_sent_tikv_cross_zone'?: number;\n    /**\n     * Network fields\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_sent_tikv_total'?: number;\n    /**\n     * Connection\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'user'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'wait_prewrite_binlog_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'wait_ts'?: number;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof SlowqueryModel\n     */\n    'warnings'?: Array<number>;\n    /**\n     * Transaction\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'write_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'write_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'write_sql_response_total'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/sso-create-impersonation-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface SsoCreateImpersonationRequest\n */\nexport interface SsoCreateImpersonationRequest {\n    /**\n     * \n     * @type {string}\n     * @memberof SsoCreateImpersonationRequest\n     */\n    'password'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof SsoCreateImpersonationRequest\n     */\n    'sql_user'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/sso-set-config-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { ConfigSSOCoreConfig } from './config-ssocore-config';\n\n/**\n * \n * @export\n * @interface SsoSetConfigRequest\n */\nexport interface SsoSetConfigRequest {\n    /**\n     * \n     * @type {ConfigSSOCoreConfig}\n     * @memberof SsoSetConfigRequest\n     */\n    'config'?: ConfigSSOCoreConfig;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/sso-ssoimpersonation-model.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface SsoSSOImpersonationModel\n */\nexport interface SsoSSOImpersonationModel {\n    /**\n     * \n     * @type {string}\n     * @memberof SsoSSOImpersonationModel\n     */\n    'last_impersonate_status'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof SsoSSOImpersonationModel\n     */\n    'sql_user'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/statement-binding.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface StatementBinding\n */\nexport interface StatementBinding {\n    /**\n     * \n     * @type {string}\n     * @memberof StatementBinding\n     */\n    'plan_digest'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementBinding\n     */\n    'source'?: StatementBindingSourceEnum;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementBinding\n     */\n    'status'?: StatementBindingStatusEnum;\n}\n\nexport const StatementBindingSourceEnum = {\n    manual: 'manual',\n    history: 'history',\n    capture: 'capture',\n    evolve: 'evolve'\n} as const;\n\nexport type StatementBindingSourceEnum = typeof StatementBindingSourceEnum[keyof typeof StatementBindingSourceEnum];\nexport const StatementBindingStatusEnum = {\n    enabled: 'enabled',\n    using: 'using',\n    disabled: 'disabled',\n    deleted: 'deleted',\n    invalid: 'invalid',\n    rejected: 'rejected',\n    pending_verify: 'pending verify'\n} as const;\n\nexport type StatementBindingStatusEnum = typeof StatementBindingStatusEnum[keyof typeof StatementBindingStatusEnum];\n\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/statement-editable-config.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface StatementEditableConfig\n */\nexport interface StatementEditableConfig {\n    /**\n     * \n     * @type {boolean}\n     * @memberof StatementEditableConfig\n     */\n    'enable'?: boolean;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementEditableConfig\n     */\n    'history_size'?: number;\n    /**\n     * \n     * @type {boolean}\n     * @memberof StatementEditableConfig\n     */\n    'internal_query'?: boolean;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementEditableConfig\n     */\n    'max_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementEditableConfig\n     */\n    'refresh_interval'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/statement-get-statements-request.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface StatementGetStatementsRequest\n */\nexport interface StatementGetStatementsRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof StatementGetStatementsRequest\n     */\n    'begin_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementGetStatementsRequest\n     */\n    'end_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementGetStatementsRequest\n     */\n    'fields'?: string;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof StatementGetStatementsRequest\n     */\n    'resource_groups'?: Array<string>;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof StatementGetStatementsRequest\n     */\n    'schemas'?: Array<string>;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof StatementGetStatementsRequest\n     */\n    'stmt_types'?: Array<string>;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementGetStatementsRequest\n     */\n    'text'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/statement-model.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface StatementModel\n */\nexport interface StatementModel {\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_affected_rows'?: number;\n    /**\n     * avg total back off time per sql\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_backoff_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_commit_backoff_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_commit_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_compile_latency'?: number;\n    /**\n     * avg process time per copr task\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_cop_process_time'?: number;\n    /**\n     * avg wait time per copr task\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_cop_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_disk'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_get_commit_ts_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_ia_read_segment_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_ia_remote_read_segment_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_ia_remote_read_segment_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_latency'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_local_latch_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_mem'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_mem_arbitration'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_parse_latency'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_prewrite_regions'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_prewrite_time'?: number;\n    /**\n     * avg total process time per sql\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_process_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_processed_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_resolve_lock_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_rocksdb_block_cache_hit_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_rocksdb_block_read_byte'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_rocksdb_block_read_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_rocksdb_delete_skipped_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_rocksdb_key_skipped_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_ru'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_time_queued_by_rc'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_total_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_txn_retry'?: number;\n    /**\n     * avg total wait time per sql\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_write_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_write_size'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'binary_plan'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'binary_plan_json'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'binary_plan_text'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'digest'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'digest_text'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'exec_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'first_seen'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'index_names'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'last_seen'?: number;\n    /**\n     * max back off time per sql\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_backoff_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_commit_backoff_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_commit_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_compile_latency'?: number;\n    /**\n     * max process time per copr task\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_cop_process_time'?: number;\n    /**\n     * max wait time per copr task\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_cop_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_disk'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_get_commit_ts_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_ia_read_segment_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_ia_remote_read_segment_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_ia_remote_read_segment_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_latency'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_local_latch_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_mem'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_mem_arbitration'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_parse_latency'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_prewrite_regions'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_prewrite_time'?: number;\n    /**\n     * max process time per sql\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_process_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_processed_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_resolve_lock_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_rocksdb_block_cache_hit_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_rocksdb_block_read_byte'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_rocksdb_block_read_count'?: number;\n    /**\n     * RocksDB\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_rocksdb_delete_skipped_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_rocksdb_key_skipped_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_ru'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_time_queued_by_rc'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_total_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_txn_retry'?: number;\n    /**\n     * max wait time per sql\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_write_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_write_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'min_latency'?: number;\n    /**\n     * deprecated, replaced by BinaryPlanText\n     * @type {string}\n     * @memberof StatementModel\n     */\n    'plan'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'plan_cache_hits'?: number;\n    /**\n     * \n     * @type {boolean}\n     * @memberof StatementModel\n     */\n    'plan_can_be_bound'?: boolean;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'plan_count'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'plan_digest'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'plan_hint'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'prev_sample_text'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'query_sample_text'?: string;\n    /**\n     * Computed fields\n     * @type {string}\n     * @memberof StatementModel\n     */\n    'related_schemas'?: string;\n    /**\n     * Resource Control\n     * @type {string}\n     * @memberof StatementModel\n     */\n    'resource_group'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'sample_user'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'schema_name'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'stmt_type'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_backoff_times'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_cop_task_num'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_errors'?: number;\n    /**\n     * IA remote read segment metrics\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_ia_read_segment_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_ia_remote_read_segment_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_ia_remote_read_segment_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_latency'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_ru'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_received_tiflash_cross_zone'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_received_tiflash_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_received_tikv_cross_zone'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_received_tikv_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_sent_tiflash_cross_zone'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_sent_tiflash_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_sent_tikv_cross_zone'?: number;\n    /**\n     * Network Fields\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_sent_tikv_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_warnings'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'summary_begin_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'summary_end_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'table_names'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/statement-time-range.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n *\n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n/**\n *\n * @export\n * @interface StatementTimeRange\n */\nexport interface StatementTimeRange {\n  /**\n   *\n   * @type {number}\n   * @memberof StatementTimeRange\n   */\n  begin_time?: number\n  /**\n   *\n   * @type {number}\n   * @memberof StatementTimeRange\n   */\n  end_time?: number\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topology-alert-manager-info.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopologyAlertManagerInfo\n */\nexport interface TopologyAlertManagerInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyAlertManagerInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyAlertManagerInfo\n     */\n    'port'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topology-grafana-info.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopologyGrafanaInfo\n */\nexport interface TopologyGrafanaInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyGrafanaInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyGrafanaInfo\n     */\n    'port'?: number;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topology-pdinfo.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopologyPDInfo\n */\nexport interface TopologyPDInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyPDInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyPDInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyPDInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyPDInfo\n     */\n    'port'?: number;\n    /**\n     * Ts = 0 means unknown\n     * @type {number}\n     * @memberof TopologyPDInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyPDInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyPDInfo\n     */\n    'version'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topology-scheduling-info.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopologySchedulingInfo\n */\nexport interface TopologySchedulingInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologySchedulingInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologySchedulingInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologySchedulingInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologySchedulingInfo\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologySchedulingInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologySchedulingInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologySchedulingInfo\n     */\n    'version'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topology-store-info.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopologyStoreInfo\n */\nexport interface TopologyStoreInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyStoreInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyStoreInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyStoreInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {{ [key: string]: string; }}\n     * @memberof TopologyStoreInfo\n     */\n    'labels'?: { [key: string]: string; };\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyStoreInfo\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyStoreInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyStoreInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyStoreInfo\n     */\n    'status_port'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyStoreInfo\n     */\n    'version'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topology-store-labels.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopologyStoreLabels\n */\nexport interface TopologyStoreLabels {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyStoreLabels\n     */\n    'address'?: string;\n    /**\n     * \n     * @type {{ [key: string]: string; }}\n     * @memberof TopologyStoreLabels\n     */\n    'labels'?: { [key: string]: string; };\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topology-store-location.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { TopologyStoreLabels } from './topology-store-labels';\n\n/**\n * \n * @export\n * @interface TopologyStoreLocation\n */\nexport interface TopologyStoreLocation {\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof TopologyStoreLocation\n     */\n    'location_labels'?: Array<string>;\n    /**\n     * \n     * @type {Array<TopologyStoreLabels>}\n     * @memberof TopologyStoreLocation\n     */\n    'stores'?: Array<TopologyStoreLabels>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topology-ti-cdcinfo.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopologyTiCDCInfo\n */\nexport interface TopologyTiCDCInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiCDCInfo\n     */\n    'cluster_name'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiCDCInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiCDCInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiCDCInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiCDCInfo\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiCDCInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiCDCInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiCDCInfo\n     */\n    'status_port'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiCDCInfo\n     */\n    'version'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topology-ti-dbinfo.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopologyTiDBInfo\n */\nexport interface TopologyTiDBInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiDBInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiDBInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiDBInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiDBInfo\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiDBInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiDBInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiDBInfo\n     */\n    'status_port'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiDBInfo\n     */\n    'version'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topology-ti-proxy-info.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopologyTiProxyInfo\n */\nexport interface TopologyTiProxyInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiProxyInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiProxyInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiProxyInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiProxyInfo\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiProxyInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiProxyInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiProxyInfo\n     */\n    'status_port'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiProxyInfo\n     */\n    'version'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topology-tsoinfo.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopologyTSOInfo\n */\nexport interface TopologyTSOInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTSOInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTSOInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTSOInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTSOInfo\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTSOInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTSOInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTSOInfo\n     */\n    'version'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topsql-editable-config.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopsqlEditableConfig\n */\nexport interface TopsqlEditableConfig {\n    /**\n     * \n     * @type {boolean}\n     * @memberof TopsqlEditableConfig\n     */\n    'enable'?: boolean;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topsql-instance-item.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopsqlInstanceItem\n */\nexport interface TopsqlInstanceItem {\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlInstanceItem\n     */\n    'instance'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlInstanceItem\n     */\n    'instance_type'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topsql-instance-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { TopsqlInstanceItem } from './topsql-instance-item';\n\n/**\n * \n * @export\n * @interface TopsqlInstanceResponse\n */\nexport interface TopsqlInstanceResponse {\n    /**\n     * \n     * @type {Array<TopsqlInstanceItem>}\n     * @memberof TopsqlInstanceResponse\n     */\n    'data'?: Array<TopsqlInstanceItem>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topsql-summary-by-item.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopsqlSummaryByItem\n */\nexport interface TopsqlSummaryByItem {\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryByItem\n     */\n    'cpu_time_ms'?: Array<number>;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryByItem\n     */\n    'cpu_time_ms_sum'?: number;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryByItem\n     */\n    'logical_io_bytes'?: Array<number>;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryByItem\n     */\n    'logical_io_bytes_sum'?: number;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryByItem\n     */\n    'network_bytes'?: Array<number>;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryByItem\n     */\n    'network_bytes_sum'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlSummaryByItem\n     */\n    'text'?: string;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryByItem\n     */\n    'timestamp_sec'?: Array<number>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topsql-summary-item.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { TopsqlSummaryPlanItem } from './topsql-summary-plan-item';\n\n/**\n * \n * @export\n * @interface TopsqlSummaryItem\n */\nexport interface TopsqlSummaryItem {\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'cpu_time_ms'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'duration_per_exec_ms'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'exec_count_per_sec'?: number;\n    /**\n     * \n     * @type {boolean}\n     * @memberof TopsqlSummaryItem\n     */\n    'is_other'?: boolean;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'logical_io_bytes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'network_bytes'?: number;\n    /**\n     * \n     * @type {Array<TopsqlSummaryPlanItem>}\n     * @memberof TopsqlSummaryItem\n     */\n    'plans'?: Array<TopsqlSummaryPlanItem>;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'scan_indexes_per_sec'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'scan_records_per_sec'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlSummaryItem\n     */\n    'sql_digest'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlSummaryItem\n     */\n    'sql_text'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topsql-summary-plan-item.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopsqlSummaryPlanItem\n */\nexport interface TopsqlSummaryPlanItem {\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'cpu_time_ms'?: Array<number>;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'duration_per_exec_ms'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'exec_count_per_sec'?: number;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'logical_io_bytes'?: Array<number>;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'network_bytes'?: Array<number>;\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'plan_digest'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'plan_text'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'scan_indexes_per_sec'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'scan_records_per_sec'?: number;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'timestamp_sec'?: Array<number>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topsql-summary-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { TopsqlSummaryByItem } from './topsql-summary-by-item';\nimport { TopsqlSummaryItem } from './topsql-summary-item';\n\n/**\n * \n * @export\n * @interface TopsqlSummaryResponse\n */\nexport interface TopsqlSummaryResponse {\n    /**\n     * \n     * @type {Array<TopsqlSummaryItem>}\n     * @memberof TopsqlSummaryResponse\n     */\n    'data'?: Array<TopsqlSummaryItem>;\n    /**\n     * \n     * @type {Array<TopsqlSummaryByItem>}\n     * @memberof TopsqlSummaryResponse\n     */\n    'data_by'?: Array<TopsqlSummaryByItem>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topsql-tikv-network-io-collection-config.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface TopsqlTikvNetworkIoCollectionConfig\n */\nexport interface TopsqlTikvNetworkIoCollectionConfig {\n    /**\n     * \n     * @type {boolean}\n     * @memberof TopsqlTikvNetworkIoCollectionConfig\n     */\n    'enable'?: boolean;\n    /**\n     * \n     * @type {boolean}\n     * @memberof TopsqlTikvNetworkIoCollectionConfig\n     */\n    'is_multi_value'?: boolean;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/topsql-update-tikv-network-io-collection-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\nimport { RestErrorResponse } from './rest-error-response';\n\n/**\n * \n * @export\n * @interface TopsqlUpdateTikvNetworkIoCollectionResponse\n */\nexport interface TopsqlUpdateTikvNetworkIoCollectionResponse {\n    /**\n     * \n     * @type {Array<RestErrorResponse>}\n     * @memberof TopsqlUpdateTikvNetworkIoCollectionResponse\n     */\n    'warnings'?: Array<RestErrorResponse>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/user-authenticate-form.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface UserAuthenticateForm\n */\nexport interface UserAuthenticateForm {\n    /**\n     * FIXME: Use strong type\n     * @type {string}\n     * @memberof UserAuthenticateForm\n     */\n    'extra'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof UserAuthenticateForm\n     */\n    'password'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof UserAuthenticateForm\n     */\n    'type'?: number;\n    /**\n     * Does not present for AuthTypeSharingCode\n     * @type {string}\n     * @memberof UserAuthenticateForm\n     */\n    'username'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/user-get-login-info-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface UserGetLoginInfoResponse\n */\nexport interface UserGetLoginInfoResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof UserGetLoginInfoResponse\n     */\n    'sql_auth_public_key'?: string;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof UserGetLoginInfoResponse\n     */\n    'supported_auth_types'?: Array<number>;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/user-sign-out-info.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface UserSignOutInfo\n */\nexport interface UserSignOutInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof UserSignOutInfo\n     */\n    'end_session_url'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/user-token-response.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface UserTokenResponse\n */\nexport interface UserTokenResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof UserTokenResponse\n     */\n    'expire'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof UserTokenResponse\n     */\n    'token'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/src/client/api/models/version-info.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/**\n * Dashboard API\n * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)\n *\n * The version of the OpenAPI document: 1.0\n * \n *\n * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).\n * https://openapi-generator.tech\n * Do not edit the class manually.\n */\n\n\n\n/**\n * \n * @export\n * @interface VersionInfo\n */\nexport interface VersionInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof VersionInfo\n     */\n    'build_git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof VersionInfo\n     */\n    'build_time'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof VersionInfo\n     */\n    'internal_version'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof VersionInfo\n     */\n    'pd_version'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof VersionInfo\n     */\n    'standalone'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/swagger/.openapi_config.yaml",
    "content": "# https://openapi-generator.tech/docs/generators/typescript-axios#config-options\n\nenumPropertyNaming: original\nmodelPropertyNaming: original\nsupportsES6: true\n\n# Setting this property to true will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter.\nuseSingleRequestParameter: true\n\n# conflicts with `useSingleRequestParameter`\n# withInterfaces: true\n\n# Put the model and api in separate folders and in separate classes\n# https://github.com/OpenAPITools/openapi-generator/issues/5008#issuecomment-613791804\nwithSeparateModelsAndApi: true\napiPackage: api\nmodelPackage: models\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/swagger/gen_api.sh",
    "content": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )\" >/dev/null 2>&1 && pwd )\"\nPROJECT_DIR=\"$(dirname \"$DIR\")\"\n\nSPEC_SOURCE_PATH=$PROJECT_DIR/../../../swaggerspec/swagger.json\nSPEC_TARGET_PATH=$PROJECT_DIR/swagger/spec.json\nOPENAPI_CONFIG_DIR=$PROJECT_DIR/swagger/.openapi_config.yaml\n\nOUTPUT_DIR=$PROJECT_DIR/src/client/api\nOUTPUT_MODELS_DIR=$PROJECT_DIR/src/client/api/models\nLIB_MODELS_DIR=$PROJECT_DIR/../tidb-dashboard-lib/src/client/\n\ncd $PROJECT_DIR/swagger\n\n# copy spec if spec source exists\nif [ -f \"$SPEC_SOURCE_PATH\" ]; then\n  cp $SPEC_SOURCE_PATH $SPEC_TARGET_PATH\nfi\n\n# gen api\npnpm openapi-generator-cli generate -i $SPEC_TARGET_PATH -g typescript-axios -c $OPENAPI_CONFIG_DIR -o $OUTPUT_DIR\n\n# copy models to tidb-dashboard-lib\n# merge all models into a large file, to reduce the rebuild times for tidb-dashboard-lib\ncd $OUTPUT_MODELS_DIR\nMODEL_FILES=\"$(ls | grep -v index.ts)\"\necho \"/* tslint:disable */\" > ../_models.ts\necho \"/* eslint-disable */\" >> ../_models.ts\necho \"/* This file is auto generated by tidb-dashboard-client/swagger/gen_api.sh */\" >> ../_models.ts\nfor f in $MODEL_FILES; do\n  tail -n +14 $f >> ../_models.ts\ndone\nsed '/^import/d' ../_models.ts > ../__models.ts\nrm ../_models.ts\nmv ../__models.ts $LIB_MODELS_DIR/models.ts\n\n# clean\nrm -rf $OUTPUT_DIR/.openapi-generator\nrm $OUTPUT_DIR/.gitignore\nrm $OUTPUT_DIR/.npmignore\nrm $OUTPUT_DIR/.openapi-generator-ignore\nrm $OUTPUT_DIR/git_push.sh\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/swagger/spec.json",
    "content": "{\n    \"swagger\": \"2.0\",\n    \"info\": {\n        \"title\": \"Dashboard API\",\n        \"contact\": {},\n        \"license\": {\n            \"name\": \"Apache 2.0\",\n            \"url\": \"http://www.apache.org/licenses/LICENSE-2.0.html\"\n        },\n        \"version\": \"1.0\"\n    },\n    \"basePath\": \"/dashboard/api\",\n    \"paths\": {\n        \"/configuration/all\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get all configurations\",\n                \"operationId\": \"configurationGetAll\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/configuration.AllConfigItems\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"403\": {\n                        \"description\": \"Forbidden\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/configuration/edit\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Edit a configuration\",\n                \"operationId\": \"configurationEdit\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/configuration.EditRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/configuration.EditResponse\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"403\": {\n                        \"description\": \"Forbidden\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/continuous_profiling/action_token\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get action token for download or view profile\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"target query string\",\n                        \"name\": \"q\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/continuous_profiling/components\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get current scraping components\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/conprof.Component\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/continuous_profiling/config\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get Continuous Profiling Config\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/conprof.NgMonitoringConfig\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            },\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Update Continuous Profiling Config\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/conprof.NgMonitoringConfig\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"ok\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/continuous_profiling/download\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"application/x-gzip\"\n                ],\n                \"summary\": \"Download Group Profile files\",\n                \"parameters\": [\n                    {\n                        \"type\": \"number\",\n                        \"description\": \"timestamp\",\n                        \"name\": \"ts\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/continuous_profiling/estimate_size\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get Estimate Size\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/conprof.EstimateSizeRes\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/continuous_profiling/group_profile/detail\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get Group Profile Detail\",\n                \"parameters\": [\n                    {\n                        \"type\": \"number\",\n                        \"description\": \"timestamp\",\n                        \"name\": \"ts\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/conprof.GroupProfileDetail\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/continuous_profiling/group_profiles\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get Group Profiles\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"begin_time\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"end_time\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/conprof.GroupProfiles\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/continuous_profiling/single_profile/view\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"text/html\"\n                ],\n                \"summary\": \"View Single Profile files\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"address\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"component\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"profile_type\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"ts\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/deadlock/list\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"List all deadlock records\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/deadlock.Model\"\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/debug_api/download\": {\n            \"get\": {\n                \"summary\": \"Download a finished request result\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"download token\",\n                        \"name\": \"token\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/debug_api/endpoint\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Send request remote endpoint and return a token for downloading results\",\n                \"operationId\": \"debugAPIRequestEndpoint\",\n                \"parameters\": [\n                    {\n                        \"description\": \"request payload\",\n                        \"name\": \"req\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/endpoint.RequestPayload\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/debug_api/endpoints\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get all endpoints\",\n                \"operationId\": \"debugAPIGetEndpoints\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/endpoint.APIDefinition\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/diagnose/diagnosis\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Generate sql diagnosis report\",\n                \"produces\": [\n                    \"application/json\"\n                ],\n                \"summary\": \"SQL diagnosis report\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/diagnose.GenDiagnosisReportRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/diagnose.TableDef\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/diagnose/metrics_relation/generate\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Generate metrics relationship graph.\",\n                \"operationId\": \"diagnoseGenerateMetricsRelationship\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/diagnose.GenerateMetricsRelationRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/diagnose/metrics_relation/view\": {\n            \"get\": {\n                \"produces\": [\n                    \"image/svg\"\n                ],\n                \"summary\": \"View metrics relationship graph.\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"token\",\n                        \"name\": \"token\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/diagnose/reports\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Get sql diagnosis reports history\",\n                \"summary\": \"SQL diagnosis reports history\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/diagnose.Report\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            },\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Generate sql diagnosis report\",\n                \"summary\": \"SQL diagnosis report\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/diagnose.GenerateReportRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"integer\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/diagnose/reports/{id}/data.js\": {\n            \"get\": {\n                \"description\": \"Get sql diagnosis report data\",\n                \"produces\": [\n                    \"text/javascript\"\n                ],\n                \"summary\": \"SQL diagnosis report data\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"report id\",\n                        \"name\": \"id\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/diagnose/reports/{id}/detail\": {\n            \"get\": {\n                \"description\": \"Get sql diagnosis report HTML\",\n                \"produces\": [\n                    \"text/html\"\n                ],\n                \"summary\": \"SQL diagnosis report\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"report id\",\n                        \"name\": \"id\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/diagnose/reports/{id}/status\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Get diagnosis report status\",\n                \"summary\": \"Diagnosis report status\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"report id\",\n                        \"name\": \"id\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/diagnose.Report\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/host/all\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get information of all hosts\",\n                \"operationId\": \"clusterInfoGetHostsInfo\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/clusterinfo.GetHostsInfoResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/host/statistics\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get cluster statistics\",\n                \"operationId\": \"clusterInfoGetStatistics\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/clusterinfo.ClusterStatistics\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/databases\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"List all databases\",\n                \"operationId\": \"infoListDatabases\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"type\": \"string\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/info\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get information about this TiDB Dashboard\",\n                \"operationId\": \"infoGet\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/info.InfoResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/tables\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"List tables by database name\",\n                \"operationId\": \"infoListTables\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"Database name\",\n                        \"name\": \"database_name\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/info.tableSchema\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/info/whoami\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get information about current session\",\n                \"operationId\": \"infoWhoami\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/info.WhoAmIResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/keyvisual/config\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get Key Visual Dynamic Config\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/config.KeyVisualConfig\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            },\n            \"put\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Set Key Visual Dynamic Config\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/config.KeyVisualConfig\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/config.KeyVisualConfig\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/keyvisual/heatmaps\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Heatmaps in a given range to visualize TiKV usage\",\n                \"summary\": \"Key Visual Heatmaps\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"The start of the key range\",\n                        \"name\": \"startkey\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"The end of the key range\",\n                        \"name\": \"endkey\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"The start of the time range (Unix)\",\n                        \"name\": \"starttime\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"The end of the time range (Unix)\",\n                        \"name\": \"endtime\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"enum\": [\n                            \"written_bytes\",\n                            \"read_bytes\",\n                            \"written_keys\",\n                            \"read_keys\",\n                            \"integration\"\n                        ],\n                        \"type\": \"string\",\n                        \"description\": \"Main types of data\",\n                        \"name\": \"type\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/matrix.Matrix\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/logs/download\": {\n            \"get\": {\n                \"produces\": [\n                    \"application/x-tar\",\n                    \"application/zip\"\n                ],\n                \"summary\": \"Download logs\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"download token\",\n                        \"name\": \"token\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/logs/download/acquire_token\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"text/plain\"\n                ],\n                \"summary\": \"Generate a download token for downloading logs\",\n                \"parameters\": [\n                    {\n                        \"type\": \"array\",\n                        \"items\": {\n                            \"type\": \"string\"\n                        },\n                        \"collectionFormat\": \"csv\",\n                        \"description\": \"task id\",\n                        \"name\": \"id\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"xxx\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/logs/taskgroup\": {\n            \"put\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Create and run a new log search task group\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/logsearch.CreateTaskGroupRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/logsearch.TaskGroupResponse\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/logs/taskgroups\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"List all log search task groups\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/logsearch.TaskGroupModel\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/logs/taskgroups/{id}\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"List tasks in a log search task group\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"Task Group ID\",\n                        \"name\": \"id\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/logsearch.TaskGroupResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            },\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Delete a log search task group\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"task group id\",\n                        \"name\": \"id\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.EmptyResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/logs/taskgroups/{id}/cancel\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Cancel running tasks in a log search task group\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"task group id\",\n                        \"name\": \"id\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.EmptyResponse\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/logs/taskgroups/{id}/preview\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Preview a log search task group\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"task group id\",\n                        \"name\": \"id\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/logsearch.PreviewModel\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/logs/taskgroups/{id}/retry\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Retry failed tasks in a log search task group\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"task group id\",\n                        \"name\": \"id\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.EmptyResponse\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/metrics/prom_address\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get the Prometheus address cluster config\",\n                \"operationId\": \"metricsGetPromAddress\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/metrics.GetPromAddressConfigResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            },\n            \"put\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Set or clear the customized Prometheus address\",\n                \"operationId\": \"metricsSetCustomPromAddress\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/metrics.PutCustomPromAddressRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/metrics.PutCustomPromAddressResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/metrics/query\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Query metrics in the given range\",\n                \"summary\": \"Query metrics\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"end_time_sec\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"query\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"start_time_sec\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"step_sec\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/metrics.QueryResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/profiling/action_token\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Get token with a given group ID or task ID and action type\",\n                \"produces\": [\n                    \"text/plain\"\n                ],\n                \"summary\": \"Get action token for download or view\",\n                \"operationId\": \"getActionToken\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"group or task ID\",\n                        \"name\": \"id\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"action\",\n                        \"name\": \"action\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/profiling/config\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get Profiling Dynamic Config\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/config.ProfilingConfig\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            },\n            \"put\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Set Profiling Dynamic Config\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/config.ProfilingConfig\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/config.ProfilingConfig\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/profiling/group/cancel/{groupId}\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Cancel all profling tasks with a given group ID\",\n                \"summary\": \"Cancel all tasks with a given group ID\",\n                \"operationId\": \"cancelProfilingGroup\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"group ID\",\n                        \"name\": \"groupId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.EmptyResponse\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/profiling/group/delete/{groupId}\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Delete all finished profiling tasks with a given group ID\",\n                \"summary\": \"Delete all tasks with a given group ID\",\n                \"operationId\": \"deleteProfilingGroup\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"group ID\",\n                        \"name\": \"groupId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.EmptyResponse\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/profiling/group/detail/{groupId}\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"List all profiling tasks with a given group ID\",\n                \"summary\": \"List all tasks with a given group ID\",\n                \"operationId\": \"getProfilingGroupDetail\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"group ID\",\n                        \"name\": \"groupId\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/profiling.GroupDetailResponse\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/profiling/group/download\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Download all finished profiling results of a task group\",\n                \"produces\": [\n                    \"application/x-gzip\"\n                ],\n                \"summary\": \"Download all results of a task group\",\n                \"operationId\": \"downloadProfilingGroup\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"download token\",\n                        \"name\": \"token\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/profiling/group/list\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"List all profiling groups\",\n                \"summary\": \"List all profiling groups\",\n                \"operationId\": \"getProfilingGroups\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/profiling.TaskGroupModel\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/profiling/group/start\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Start a profiling task group\",\n                \"summary\": \"Start profiling\",\n                \"operationId\": \"startProfiling\",\n                \"parameters\": [\n                    {\n                        \"description\": \"profiling request\",\n                        \"name\": \"req\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/profiling.StartRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"task group\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/profiling.TaskGroupModel\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/profiling/single/download\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Download the finished profiling result of a task\",\n                \"produces\": [\n                    \"application/x-gzip\"\n                ],\n                \"summary\": \"Download the result of a task\",\n                \"operationId\": \"downloadProfilingSingle\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"download token\",\n                        \"name\": \"token\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/profiling/single/view\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"View the finished profiling result of a task\",\n                \"produces\": [\n                    \"text/html\"\n                ],\n                \"summary\": \"View the result of a task\",\n                \"operationId\": \"viewProfilingSingle\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"download token\",\n                        \"name\": \"token\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/query_editor/run\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Run statements\",\n                \"operationId\": \"queryEditorRun\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/queryeditor.RunRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/queryeditor.RunResponse\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"403\": {\n                        \"description\": \"Forbidden\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/resource_manager/calibrate/actual\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get calibrate of Resource Groups by actual workload\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"end_time\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"start_time\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resourcemanager.CalibrateResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/resource_manager/calibrate/hardware\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get calibrate of Resource Groups by hardware deployment\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"default\": \"\\\"tpcc\\\"\",\n                        \"description\": \"workload\",\n                        \"name\": \"workload\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resourcemanager.CalibrateResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/resource_manager/config\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get Resource Control enable config\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/resourcemanager.GetConfigResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/resource_manager/information\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get Information of Resource Groups\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/resourcemanager.ResourceInfoRowDef\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/resource_manager/information/group_names\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"List all resource groups\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"type\": \"string\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/slow_query/available_fields\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Get available field names by slowquery table columns\",\n                \"summary\": \"Get available field names\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"type\": \"string\"\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/slow_query/detail\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get details of a slow query\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\",\n                        \"name\": \"connect_id\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"digest\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"number\",\n                        \"name\": \"timestamp\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/slowquery.Model\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/slow_query/download\": {\n            \"get\": {\n                \"produces\": [\n                    \"text/csv\"\n                ],\n                \"summary\": \"Download slow query statements\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"download token\",\n                        \"name\": \"token\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/slow_query/download/token\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"text/plain\"\n                ],\n                \"summary\": \"Generate a download token for exported slow query statements\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/slowquery.GetListRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"xxx\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/slow_query/list\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"List all slow queries\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"begin_time\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"array\",\n                        \"items\": {\n                            \"type\": \"string\"\n                        },\n                        \"collectionFormat\": \"multi\",\n                        \"name\": \"db\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"boolean\",\n                        \"name\": \"desc\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"digest\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"end_time\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"example: \\\"Query,Digest\\\"\",\n                        \"name\": \"fields\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"limit\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"orderBy\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"array\",\n                        \"items\": {\n                            \"type\": \"string\"\n                        },\n                        \"collectionFormat\": \"multi\",\n                        \"description\": \"for showing slow queries in the statement detail page\",\n                        \"name\": \"plans\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"array\",\n                        \"items\": {\n                            \"type\": \"string\"\n                        },\n                        \"collectionFormat\": \"multi\",\n                        \"name\": \"resource_group\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"text\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/slowquery.Model\"\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/statements/available_fields\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"description\": \"Get available field names by statements table columns\",\n                \"summary\": \"Get available field names\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"type\": \"string\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/statements/config\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get statement configurations\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/statement.EditableConfig\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            },\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Update statement configurations\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/statement.EditableConfig\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"204\": {\n                        \"description\": \"No Content\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/statements/download\": {\n            \"get\": {\n                \"produces\": [\n                    \"text/csv\"\n                ],\n                \"summary\": \"Download statements\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"download token\",\n                        \"name\": \"token\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/statements/download/token\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"produces\": [\n                    \"text/plain\"\n                ],\n                \"summary\": \"Generate a download token for exported statements\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/statement.GetStatementsRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"xxx\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/statements/list\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get a list of statements\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"begin_time\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"end_time\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"fields\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"array\",\n                        \"items\": {\n                            \"type\": \"string\"\n                        },\n                        \"collectionFormat\": \"multi\",\n                        \"name\": \"resource_groups\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"array\",\n                        \"items\": {\n                            \"type\": \"string\"\n                        },\n                        \"collectionFormat\": \"multi\",\n                        \"name\": \"schemas\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"array\",\n                        \"items\": {\n                            \"type\": \"string\"\n                        },\n                        \"collectionFormat\": \"multi\",\n                        \"name\": \"stmt_types\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"text\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/statement.Model\"\n                            }\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/statements/plan/binding\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get the bound plan digest (if exists) of a statement\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"query template id\",\n                        \"name\": \"sql_digest\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"begin time\",\n                        \"name\": \"begin_time\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"description\": \"end time\",\n                        \"name\": \"end_time\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/statement.Binding\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            },\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Create a binding for a statement and a plan\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"plan digest id\",\n                        \"name\": \"plan_digest\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"success\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            },\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Drop all manually created bindings for a statement\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"query template ID (a.k.a. sql digest)\",\n                        \"name\": \"sql_digest\",\n                        \"in\": \"query\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"success\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/statements/plan/detail\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get details of a statement in an execution plan\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"begin_time\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"digest\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"end_time\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"array\",\n                        \"items\": {\n                            \"type\": \"string\"\n                        },\n                        \"collectionFormat\": \"multi\",\n                        \"name\": \"plans\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"schema_name\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/statement.Model\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/statements/plans\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get execution plans of a statement\",\n                \"parameters\": [\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"begin_time\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"digest\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"integer\",\n                        \"name\": \"end_time\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"schema_name\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/statement.Model\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/statements/stmt_types\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get all statement types\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"type\": \"string\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topology/alertmanager\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get AlertManager instance\",\n                \"operationId\": \"getAlertManagerTopology\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/topology.AlertManagerInfo\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topology/alertmanager/{address}/count\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get current alert count from AlertManager\",\n                \"operationId\": \"getAlertManagerCounts\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"ip:port\",\n                        \"name\": \"address\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"integer\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topology/grafana\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get Grafana instance\",\n                \"operationId\": \"getGrafanaTopology\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/topology.GrafanaInfo\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topology/pd\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get all PD instances\",\n                \"operationId\": \"getPDTopology\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/topology.PDInfo\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topology/scheduling\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get all Scheduling instances\",\n                \"operationId\": \"getSchedulingTopology\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/topology.SchedulingInfo\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topology/store\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get all TiKV / TiFlash instances\",\n                \"operationId\": \"getStoreTopology\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/clusterinfo.StoreTopologyResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topology/store_location\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get location labels of all TiKV / TiFlash instances\",\n                \"operationId\": \"getStoreLocationTopology\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/topology.StoreLocation\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topology/ticdc\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get all TiCDC instances\",\n                \"operationId\": \"getTiCDCTopology\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/topology.TiCDCInfo\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topology/tidb\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get all TiDB instances\",\n                \"operationId\": \"getTiDBTopology\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/topology.TiDBInfo\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topology/tidb/{address}\": {\n            \"delete\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Hide a TiDB instance\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"description\": \"ip:port\",\n                        \"name\": \"address\",\n                        \"in\": \"path\",\n                        \"required\": true\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"delete ok\"\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topology/tiproxy\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get all TiProxy instances\",\n                \"operationId\": \"getTiProxyTopology\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/topology.TiProxyInfo\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topology/tso\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get all TSO instances\",\n                \"operationId\": \"getTSOTopology\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/topology.TSOInfo\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topsql/config\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get Top SQL config\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"ok\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/topsql.EditableConfig\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            },\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Update Top SQL config\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/topsql.EditableConfig\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"204\": {\n                        \"description\": \"No Content\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topsql/instances\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get available instances\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"data_source\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"end\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"start\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"ok\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/topsql.InstanceResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topsql/summary\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get summaries\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"data_source\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"end\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"group_by\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"instance\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"instance_type\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"order_by\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"start\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"top\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"window\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"ok\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/topsql.SummaryResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/topsql/tikv_network_io_collection\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get TiKV network IO collection config\",\n                \"operationId\": \"topsqlGetTiKVNetworkIOCollection\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"ok\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/topsql.TikvNetworkIoCollectionConfig\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            },\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Update TiKV network IO collection config\",\n                \"operationId\": \"topsqlUpdateTiKVNetworkIOCollection\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/topsql.TikvNetworkIoCollectionConfig\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"ok\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/topsql.UpdateTikvNetworkIoCollectionResponse\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/login\": {\n            \"post\": {\n                \"summary\": \"Log in\",\n                \"operationId\": \"userLogin\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Credentials\",\n                        \"name\": \"message\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/user.AuthenticateForm\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/user.TokenResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/login_info\": {\n            \"get\": {\n                \"summary\": \"Get log in information, like supported authenticate types\",\n                \"operationId\": \"userGetLoginInfo\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/user.GetLoginInfoResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/share/code\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Share current session and generate a sharing code\",\n                \"operationId\": \"userShareSession\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/code.ShareRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/code.ShareResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/share/revoke\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Reset encryption key to revoke all authorized codes\",\n                \"operationId\": \"userRevokeSession\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"\"\n                    }\n                }\n            }\n        },\n        \"/user/sign_out_info\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get sign out info\",\n                \"operationId\": \"userGetSignOutInfo\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"redirect_url\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/user.SignOutInfo\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/sso/auth_url\": {\n            \"get\": {\n                \"summary\": \"Get SSO Auth URL\",\n                \"operationId\": \"userSSOGetAuthURL\",\n                \"parameters\": [\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"code_verifier\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"redirect_url\",\n                        \"in\": \"query\"\n                    },\n                    {\n                        \"type\": \"string\",\n                        \"name\": \"state\",\n                        \"in\": \"query\"\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/sso/config\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Get SSO config\",\n                \"operationId\": \"userSSOGetConfig\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/config.SSOCoreConfig\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            },\n            \"put\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Set SSO config\",\n                \"operationId\": \"userSSOSetConfig\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/sso.SetConfigRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/config.SSOCoreConfig\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/sso/impersonation\": {\n            \"post\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"Create an impersonation\",\n                \"operationId\": \"userSSOCreateImpersonation\",\n                \"parameters\": [\n                    {\n                        \"description\": \"Request body\",\n                        \"name\": \"request\",\n                        \"in\": \"body\",\n                        \"required\": true,\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/sso.CreateImpersonationRequest\"\n                        }\n                    }\n                ],\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/sso.SSOImpersonationModel\"\n                        }\n                    },\n                    \"400\": {\n                        \"description\": \"Bad Request\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    },\n                    \"500\": {\n                        \"description\": \"Internal Server Error\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        },\n        \"/user/sso/impersonations/list\": {\n            \"get\": {\n                \"security\": [\n                    {\n                        \"JwtAuth\": []\n                    }\n                ],\n                \"summary\": \"List all impersonations\",\n                \"operationId\": \"userSSOListImpersonations\",\n                \"responses\": {\n                    \"200\": {\n                        \"description\": \"OK\",\n                        \"schema\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"$ref\": \"#/definitions/sso.SSOImpersonationModel\"\n                            }\n                        }\n                    },\n                    \"401\": {\n                        \"description\": \"Unauthorized\",\n                        \"schema\": {\n                            \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                        }\n                    }\n                }\n            }\n        }\n    },\n    \"definitions\": {\n        \"clusterinfo.ClusterStatistics\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"probe_failure_hosts\": {\n                    \"type\": \"integer\"\n                },\n                \"stats_by_instance_kind\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": {\n                        \"$ref\": \"#/definitions/clusterinfo.ClusterStatisticsPartial\"\n                    }\n                },\n                \"total_stats\": {\n                    \"$ref\": \"#/definitions/clusterinfo.ClusterStatisticsPartial\"\n                },\n                \"versions\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                }\n            }\n        },\n        \"clusterinfo.ClusterStatisticsPartial\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"number_of_hosts\": {\n                    \"type\": \"integer\"\n                },\n                \"number_of_instances\": {\n                    \"type\": \"integer\"\n                },\n                \"total_logical_cores\": {\n                    \"type\": \"integer\"\n                },\n                \"total_memory_capacity_bytes\": {\n                    \"type\": \"integer\"\n                },\n                \"total_physical_cores\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"clusterinfo.GetHostsInfoResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"hosts\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/hostinfo.Info\"\n                    }\n                },\n                \"warning\": {\n                    \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                }\n            }\n        },\n        \"clusterinfo.StoreTopologyResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"tiflash\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/topology.StoreInfo\"\n                    }\n                },\n                \"tikv\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/topology.StoreInfo\"\n                    }\n                }\n            }\n        },\n        \"code.ShareRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"expire_in_sec\": {\n                    \"type\": \"integer\"\n                },\n                \"revoke_write_priv\": {\n                    \"type\": \"boolean\"\n                }\n            }\n        },\n        \"code.ShareResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"code\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.KeyVisualConfig\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"auto_collection_disabled\": {\n                    \"type\": \"boolean\"\n                },\n                \"policy\": {\n                    \"type\": \"string\"\n                },\n                \"policy_kv_separator\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"config.ProfilingConfig\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"auto_collection_duration_secs\": {\n                    \"type\": \"integer\"\n                },\n                \"auto_collection_interval_secs\": {\n                    \"type\": \"integer\"\n                },\n                \"auto_collection_targets\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/model.RequestTargetNode\"\n                    }\n                }\n            }\n        },\n        \"config.SSOCoreConfig\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"client_id\": {\n                    \"type\": \"string\"\n                },\n                \"client_secret\": {\n                    \"type\": \"string\"\n                },\n                \"discovery_url\": {\n                    \"type\": \"string\"\n                },\n                \"enabled\": {\n                    \"type\": \"boolean\"\n                },\n                \"is_read_only\": {\n                    \"type\": \"boolean\"\n                },\n                \"scopes\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"configuration.AllConfigItems\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"errors\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                    }\n                },\n                \"items\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": {\n                        \"type\": \"array\",\n                        \"items\": {\n                            \"$ref\": \"#/definitions/configuration.Item\"\n                        }\n                    }\n                }\n            }\n        },\n        \"configuration.EditRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"id\": {\n                    \"type\": \"string\"\n                },\n                \"kind\": {\n                    \"type\": \"string\"\n                },\n                \"new_value\": {}\n            }\n        },\n        \"configuration.EditResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"warnings\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                    }\n                }\n            }\n        },\n        \"configuration.Item\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"id\": {\n                    \"type\": \"string\"\n                },\n                \"is_editable\": {\n                    \"type\": \"boolean\"\n                },\n                \"is_multi_value\": {\n                    \"description\": \"TODO: Support per-instance config\",\n                    \"type\": \"boolean\"\n                },\n                \"value\": {\n                    \"description\": \"When multi value present, this contains one of the value\"\n                }\n            }\n        },\n        \"conprof.Component\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ip\": {\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"type\": \"integer\"\n                },\n                \"status_port\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"conprof.ComponentNum\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"pd\": {\n                    \"type\": \"integer\"\n                },\n                \"ticdc\": {\n                    \"type\": \"integer\"\n                },\n                \"tidb\": {\n                    \"type\": \"integer\"\n                },\n                \"tiflash\": {\n                    \"type\": \"integer\"\n                },\n                \"tikv\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"conprof.ContinuousProfilingConfig\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"data_retention_seconds\": {\n                    \"type\": \"integer\"\n                },\n                \"enable\": {\n                    \"type\": \"boolean\"\n                },\n                \"interval_seconds\": {\n                    \"type\": \"integer\"\n                },\n                \"profile_seconds\": {\n                    \"type\": \"integer\"\n                },\n                \"timeout_seconds\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"conprof.EstimateSizeRes\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"instance_count\": {\n                    \"type\": \"integer\"\n                },\n                \"profile_size\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"conprof.GroupProfileDetail\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"profile_duration_secs\": {\n                    \"type\": \"integer\"\n                },\n                \"state\": {\n                    \"type\": \"string\"\n                },\n                \"target_profiles\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/conprof.ProfileDetail\"\n                    }\n                },\n                \"ts\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"conprof.GroupProfiles\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"component_num\": {\n                    \"$ref\": \"#/definitions/conprof.ComponentNum\"\n                },\n                \"profile_duration_secs\": {\n                    \"type\": \"integer\"\n                },\n                \"state\": {\n                    \"type\": \"string\"\n                },\n                \"ts\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"conprof.NgMonitoringConfig\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"continuous_profiling\": {\n                    \"$ref\": \"#/definitions/conprof.ContinuousProfilingConfig\"\n                }\n            }\n        },\n        \"conprof.ProfileDetail\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"error\": {\n                    \"type\": \"string\"\n                },\n                \"profile_type\": {\n                    \"type\": \"string\"\n                },\n                \"state\": {\n                    \"type\": \"string\"\n                },\n                \"target\": {\n                    \"$ref\": \"#/definitions/conprof.Target\"\n                }\n            }\n        },\n        \"conprof.Target\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"address\": {\n                    \"type\": \"string\"\n                },\n                \"component\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"deadlock.Model\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"current_sql\": {\n                    \"type\": \"string\"\n                },\n                \"id\": {\n                    \"type\": \"integer\"\n                },\n                \"instance\": {\n                    \"type\": \"string\"\n                },\n                \"key\": {\n                    \"type\": \"string\"\n                },\n                \"key_info\": {\n                    \"type\": \"string\"\n                },\n                \"occur_time\": {\n                    \"type\": \"string\"\n                },\n                \"retryable\": {\n                    \"type\": \"boolean\"\n                },\n                \"trx_holding_lock\": {\n                    \"type\": \"integer\"\n                },\n                \"try_lock_trx_id\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"decorator.LabelKey\": {\n            \"type\": \"object\",\n            \"required\": [\n                \"key\",\n                \"labels\"\n            ],\n            \"properties\": {\n                \"key\": {\n                    \"type\": \"string\"\n                },\n                \"labels\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                }\n            }\n        },\n        \"diagnose.GenDiagnosisReportRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"end_time\": {\n                    \"type\": \"integer\"\n                },\n                \"kind\": {\n                    \"description\": \"values: config, error, performance\",\n                    \"type\": \"string\"\n                },\n                \"start_time\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"diagnose.GenerateMetricsRelationRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"end_time\": {\n                    \"type\": \"integer\"\n                },\n                \"start_time\": {\n                    \"type\": \"integer\"\n                },\n                \"type\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"diagnose.GenerateReportRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"compare_end_time\": {\n                    \"type\": \"integer\"\n                },\n                \"compare_start_time\": {\n                    \"type\": \"integer\"\n                },\n                \"end_time\": {\n                    \"type\": \"integer\"\n                },\n                \"start_time\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"diagnose.Report\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"compare_end_time\": {\n                    \"type\": \"string\"\n                },\n                \"compare_start_time\": {\n                    \"type\": \"string\"\n                },\n                \"content\": {\n                    \"type\": \"string\"\n                },\n                \"created_at\": {\n                    \"type\": \"string\"\n                },\n                \"end_time\": {\n                    \"type\": \"string\"\n                },\n                \"id\": {\n                    \"type\": \"string\"\n                },\n                \"progress\": {\n                    \"description\": \"0~100\",\n                    \"type\": \"integer\"\n                },\n                \"start_time\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"diagnose.TableDef\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"category\": {\n                    \"description\": \"The category of the table, such as [TiDB]\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"column\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"comment\": {\n                    \"type\": \"string\"\n                },\n                \"rows\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/diagnose.TableRowDef\"\n                    }\n                },\n                \"title\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"diagnose.TableRowDef\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"comment\": {\n                    \"type\": \"string\"\n                },\n                \"sub_values\": {\n                    \"description\": \"SubValues need fold default.\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"array\",\n                        \"items\": {\n                            \"type\": \"string\"\n                        }\n                    }\n                },\n                \"values\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                }\n            }\n        },\n        \"endpoint.APIDefinition\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"component\": {\n                    \"type\": \"string\"\n                },\n                \"id\": {\n                    \"type\": \"string\"\n                },\n                \"method\": {\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"type\": \"string\"\n                },\n                \"path_params\": {\n                    \"description\": \"e.g. /stats/dump/{db}/{table} -\\u003e db, table\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/endpoint.APIParamDefinition\"\n                    }\n                },\n                \"query_params\": {\n                    \"description\": \"e.g. /debug/pprof?seconds=1 -\\u003e seconds\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/endpoint.APIParamDefinition\"\n                    }\n                }\n            }\n        },\n        \"endpoint.APIParamDefinition\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"name\": {\n                    \"type\": \"string\"\n                },\n                \"required\": {\n                    \"type\": \"boolean\"\n                },\n                \"ui_kind\": {\n                    \"type\": \"string\"\n                },\n                \"ui_props\": {\n                    \"description\": \"varies by different ui kinds\"\n                }\n            }\n        },\n        \"endpoint.RequestPayload\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"api_id\": {\n                    \"type\": \"string\"\n                },\n                \"host\": {\n                    \"type\": \"string\"\n                },\n                \"param_values\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"port\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"hostinfo.CPUInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"arch\": {\n                    \"type\": \"string\"\n                },\n                \"logical_cores\": {\n                    \"type\": \"integer\"\n                },\n                \"physical_cores\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"hostinfo.CPUUsageInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"idle\": {\n                    \"type\": \"number\"\n                },\n                \"system\": {\n                    \"type\": \"number\"\n                }\n            }\n        },\n        \"hostinfo.Info\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"cpu_info\": {\n                    \"$ref\": \"#/definitions/hostinfo.CPUInfo\"\n                },\n                \"cpu_usage\": {\n                    \"$ref\": \"#/definitions/hostinfo.CPUUsageInfo\"\n                },\n                \"host\": {\n                    \"type\": \"string\"\n                },\n                \"instances\": {\n                    \"description\": \"Instances in the current host. The key is instance address\",\n                    \"type\": \"object\",\n                    \"additionalProperties\": {\n                        \"$ref\": \"#/definitions/hostinfo.InstanceInfo\"\n                    }\n                },\n                \"memory_usage\": {\n                    \"$ref\": \"#/definitions/hostinfo.MemoryUsageInfo\"\n                },\n                \"partitions\": {\n                    \"description\": \"Containing unused partitions. The key is path in lower case.\\nNote: deviceName is not used as the key, since TiDB and TiKV may return different deviceName for the same device.\",\n                    \"type\": \"object\",\n                    \"additionalProperties\": {\n                        \"$ref\": \"#/definitions/hostinfo.PartitionInfo\"\n                    }\n                }\n            }\n        },\n        \"hostinfo.InstanceInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"partition_path_lower\": {\n                    \"type\": \"string\"\n                },\n                \"type\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"hostinfo.MemoryUsageInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"total\": {\n                    \"type\": \"integer\"\n                },\n                \"used\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"hostinfo.PartitionInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"free\": {\n                    \"type\": \"integer\"\n                },\n                \"fstype\": {\n                    \"type\": \"string\"\n                },\n                \"path\": {\n                    \"type\": \"string\"\n                },\n                \"total\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"info.InfoResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"enable_experimental\": {\n                    \"type\": \"boolean\"\n                },\n                \"enable_telemetry\": {\n                    \"type\": \"boolean\"\n                },\n                \"ngm_state\": {\n                    \"type\": \"string\"\n                },\n                \"supported_features\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"version\": {\n                    \"$ref\": \"#/definitions/version.Info\"\n                }\n            }\n        },\n        \"info.WhoAmIResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"display_name\": {\n                    \"type\": \"string\"\n                },\n                \"is_shareable\": {\n                    \"type\": \"boolean\"\n                },\n                \"is_writeable\": {\n                    \"type\": \"boolean\"\n                }\n            }\n        },\n        \"info.tableSchema\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"table_id\": {\n                    \"type\": \"string\"\n                },\n                \"table_name\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"logsearch.CreateTaskGroupRequest\": {\n            \"type\": \"object\",\n            \"required\": [\n                \"request\",\n                \"targets\"\n            ],\n            \"properties\": {\n                \"request\": {\n                    \"$ref\": \"#/definitions/logsearch.SearchLogRequest\"\n                },\n                \"targets\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/model.RequestTargetNode\"\n                    }\n                }\n            }\n        },\n        \"logsearch.PreviewModel\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"id\": {\n                    \"type\": \"integer\"\n                },\n                \"level\": {\n                    \"type\": \"integer\"\n                },\n                \"message\": {\n                    \"type\": \"string\"\n                },\n                \"task_group_id\": {\n                    \"type\": \"integer\"\n                },\n                \"task_id\": {\n                    \"type\": \"integer\"\n                },\n                \"time\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"logsearch.SearchLogRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"end_time\": {\n                    \"type\": \"integer\"\n                },\n                \"min_level\": {\n                    \"type\": \"integer\"\n                },\n                \"patterns\": {\n                    \"description\": \"We use a string array to represent multiple CNF pattern sceniaor like:\\nSELECT * FROM t WHERE c LIKE '%s%' and c REGEXP '.*a.*' because\\nGolang and Rust don't support perl-like (?=re1)(?=re2)\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"start_time\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"logsearch.TaskGroupModel\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"id\": {\n                    \"type\": \"integer\"\n                },\n                \"log_store_dir\": {\n                    \"type\": \"string\"\n                },\n                \"search_request\": {\n                    \"$ref\": \"#/definitions/logsearch.SearchLogRequest\"\n                },\n                \"state\": {\n                    \"type\": \"integer\"\n                },\n                \"target_stats\": {\n                    \"$ref\": \"#/definitions/model.RequestTargetStatistics\"\n                }\n            }\n        },\n        \"logsearch.TaskGroupResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"task_group\": {\n                    \"$ref\": \"#/definitions/logsearch.TaskGroupModel\"\n                },\n                \"tasks\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/logsearch.TaskModel\"\n                    }\n                }\n            }\n        },\n        \"logsearch.TaskModel\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"error\": {\n                    \"type\": \"string\"\n                },\n                \"id\": {\n                    \"type\": \"integer\"\n                },\n                \"log_store_path\": {\n                    \"type\": \"string\"\n                },\n                \"size\": {\n                    \"type\": \"integer\"\n                },\n                \"slow_log_store_path\": {\n                    \"type\": \"string\"\n                },\n                \"state\": {\n                    \"type\": \"integer\"\n                },\n                \"target\": {\n                    \"$ref\": \"#/definitions/model.RequestTargetNode\"\n                },\n                \"task_group_id\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"matrix.Matrix\": {\n            \"type\": \"object\",\n            \"required\": [\n                \"data\",\n                \"keyAxis\",\n                \"timeAxis\"\n            ],\n            \"properties\": {\n                \"data\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": {\n                        \"type\": \"array\",\n                        \"items\": {\n                            \"type\": \"array\",\n                            \"items\": {\n                                \"type\": \"integer\"\n                            }\n                        }\n                    }\n                },\n                \"keyAxis\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/decorator.LabelKey\"\n                    }\n                },\n                \"timeAxis\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                }\n            }\n        },\n        \"metrics.GetPromAddressConfigResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"customized_addr\": {\n                    \"type\": \"string\"\n                },\n                \"deployed_addr\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"metrics.PutCustomPromAddressRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"address\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"metrics.PutCustomPromAddressResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"normalized_address\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"metrics.QueryResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"data\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": true\n                },\n                \"status\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"model.RequestTargetNode\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"display_name\": {\n                    \"type\": \"string\",\n                    \"example\": \"127.0.0.1:4000\"\n                },\n                \"ip\": {\n                    \"type\": \"string\",\n                    \"example\": \"127.0.0.1\"\n                },\n                \"kind\": {\n                    \"type\": \"string\",\n                    \"example\": \"tidb\"\n                },\n                \"port\": {\n                    \"type\": \"integer\",\n                    \"example\": 4000\n                }\n            }\n        },\n        \"model.RequestTargetStatistics\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"num_pd_nodes\": {\n                    \"type\": \"integer\"\n                },\n                \"num_scheduling_nodes\": {\n                    \"type\": \"integer\"\n                },\n                \"num_ticdc_nodes\": {\n                    \"type\": \"integer\"\n                },\n                \"num_tidb_nodes\": {\n                    \"type\": \"integer\"\n                },\n                \"num_tiflash_nodes\": {\n                    \"type\": \"integer\"\n                },\n                \"num_tikv_nodes\": {\n                    \"type\": \"integer\"\n                },\n                \"num_tiproxy_nodes\": {\n                    \"type\": \"integer\"\n                },\n                \"num_tso_nodes\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"profiling.GroupDetailResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"server_time\": {\n                    \"type\": \"integer\"\n                },\n                \"task_group_status\": {\n                    \"$ref\": \"#/definitions/profiling.TaskGroupModel\"\n                },\n                \"tasks_status\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/profiling.TaskModel\"\n                    }\n                }\n            }\n        },\n        \"profiling.StartRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"duration_secs\": {\n                    \"type\": \"integer\"\n                },\n                \"requsted_profiling_types\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"targets\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/model.RequestTargetNode\"\n                    }\n                }\n            }\n        },\n        \"profiling.TaskGroupModel\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"id\": {\n                    \"type\": \"integer\"\n                },\n                \"profile_duration_secs\": {\n                    \"type\": \"integer\"\n                },\n                \"requsted_profiling_types\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"started_at\": {\n                    \"type\": \"integer\"\n                },\n                \"state\": {\n                    \"type\": \"integer\"\n                },\n                \"target_stats\": {\n                    \"$ref\": \"#/definitions/model.RequestTargetStatistics\"\n                }\n            }\n        },\n        \"profiling.TaskModel\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"error\": {\n                    \"type\": \"string\"\n                },\n                \"id\": {\n                    \"type\": \"integer\"\n                },\n                \"profiling_type\": {\n                    \"type\": \"string\"\n                },\n                \"raw_data_type\": {\n                    \"type\": \"string\"\n                },\n                \"started_at\": {\n                    \"description\": \"The start running time, reset when retry. Used to estimate approximate profiling progress.\",\n                    \"type\": \"integer\"\n                },\n                \"state\": {\n                    \"type\": \"integer\"\n                },\n                \"target\": {\n                    \"$ref\": \"#/definitions/model.RequestTargetNode\"\n                },\n                \"task_group_id\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"queryeditor.RunRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"max_rows\": {\n                    \"type\": \"integer\",\n                    \"example\": 1000\n                },\n                \"statements\": {\n                    \"type\": \"string\",\n                    \"example\": \"show databases;\"\n                }\n            }\n        },\n        \"queryeditor.RunResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"actual_rows\": {\n                    \"type\": \"integer\"\n                },\n                \"column_names\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"error_msg\": {\n                    \"type\": \"string\"\n                },\n                \"execution_ms\": {\n                    \"type\": \"integer\"\n                },\n                \"rows\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"array\",\n                        \"items\": {}\n                    }\n                }\n            }\n        },\n        \"resourcemanager.CalibrateResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"estimated_capacity\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"resourcemanager.GetConfigResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"enable\": {\n                    \"type\": \"boolean\"\n                }\n            }\n        },\n        \"resourcemanager.ResourceInfoRowDef\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"burstable\": {\n                    \"type\": \"string\"\n                },\n                \"name\": {\n                    \"type\": \"string\"\n                },\n                \"priority\": {\n                    \"type\": \"string\"\n                },\n                \"ru_per_sec\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"rest.EmptyResponse\": {\n            \"type\": \"object\"\n        },\n        \"rest.ErrorResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"code\": {\n                    \"type\": \"string\"\n                },\n                \"error\": {\n                    \"type\": \"boolean\"\n                },\n                \"full_text\": {\n                    \"type\": \"string\"\n                },\n                \"message\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"slowquery.GetListRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"begin_time\": {\n                    \"type\": \"integer\"\n                },\n                \"db\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"desc\": {\n                    \"type\": \"boolean\"\n                },\n                \"digest\": {\n                    \"type\": \"string\"\n                },\n                \"end_time\": {\n                    \"type\": \"integer\"\n                },\n                \"fields\": {\n                    \"description\": \"example: \\\"Query,Digest\\\"\",\n                    \"type\": \"string\"\n                },\n                \"limit\": {\n                    \"type\": \"integer\"\n                },\n                \"orderBy\": {\n                    \"type\": \"string\"\n                },\n                \"plans\": {\n                    \"description\": \"for showing slow queries in the statement detail page\",\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"resource_group\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"text\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"slowquery.Model\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"backoff_time\": {\n                    \"type\": \"number\"\n                },\n                \"backoff_types\": {\n                    \"type\": \"string\"\n                },\n                \"binary_plan\": {\n                    \"type\": \"string\"\n                },\n                \"binary_plan_json\": {\n                    \"description\": \"Computed fields\",\n                    \"type\": \"string\"\n                },\n                \"binary_plan_text\": {\n                    \"description\": \"binary plan plain text\",\n                    \"type\": \"string\"\n                },\n                \"commit_backoff_time\": {\n                    \"type\": \"number\"\n                },\n                \"commit_time\": {\n                    \"type\": \"number\"\n                },\n                \"compile_time\": {\n                    \"type\": \"number\"\n                },\n                \"connection_id\": {\n                    \"description\": \"TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\",\n                    \"type\": \"string\"\n                },\n                \"cop_proc_addr\": {\n                    \"type\": \"string\"\n                },\n                \"cop_proc_avg\": {\n                    \"type\": \"number\"\n                },\n                \"cop_proc_max\": {\n                    \"type\": \"number\"\n                },\n                \"cop_proc_p90\": {\n                    \"type\": \"number\"\n                },\n                \"cop_time\": {\n                    \"type\": \"number\"\n                },\n                \"cop_wait_addr\": {\n                    \"type\": \"string\"\n                },\n                \"cop_wait_avg\": {\n                    \"type\": \"number\"\n                },\n                \"cop_wait_max\": {\n                    \"type\": \"number\"\n                },\n                \"cop_wait_p90\": {\n                    \"type\": \"number\"\n                },\n                \"db\": {\n                    \"type\": \"string\"\n                },\n                \"digest\": {\n                    \"type\": \"string\"\n                },\n                \"disk_max\": {\n                    \"type\": \"integer\"\n                },\n                \"exec_retry_time\": {\n                    \"type\": \"number\"\n                },\n                \"get_commit_ts_time\": {\n                    \"type\": \"number\"\n                },\n                \"host\": {\n                    \"type\": \"string\"\n                },\n                \"ia_remote_read_segment_size\": {\n                    \"description\": \"IA remote read\",\n                    \"type\": \"integer\"\n                },\n                \"ia_remote_read_segment_wait_time\": {\n                    \"type\": \"number\"\n                },\n                \"index_names\": {\n                    \"type\": \"string\"\n                },\n                \"instance\": {\n                    \"type\": \"string\"\n                },\n                \"is_internal\": {\n                    \"description\": \"Basic\",\n                    \"type\": \"integer\"\n                },\n                \"local_latch_wait_time\": {\n                    \"type\": \"number\"\n                },\n                \"lock_keys_time\": {\n                    \"type\": \"number\"\n                },\n                \"mem_arbitration\": {\n                    \"type\": \"number\"\n                },\n                \"memory_max\": {\n                    \"type\": \"integer\"\n                },\n                \"optimize_time\": {\n                    \"type\": \"number\"\n                },\n                \"parse_time\": {\n                    \"type\": \"number\"\n                },\n                \"plan\": {\n                    \"description\": \"deprecated, replaced by BinaryPlanText\",\n                    \"type\": \"string\"\n                },\n                \"plan_from_binding\": {\n                    \"type\": \"integer\"\n                },\n                \"plan_from_cache\": {\n                    \"type\": \"integer\"\n                },\n                \"prepared\": {\n                    \"type\": \"integer\"\n                },\n                \"preproc_subqueries_time\": {\n                    \"type\": \"number\"\n                },\n                \"prev_stmt\": {\n                    \"description\": \"Detail\",\n                    \"type\": \"string\"\n                },\n                \"prewrite_region\": {\n                    \"type\": \"integer\"\n                },\n                \"prewrite_time\": {\n                    \"type\": \"number\"\n                },\n                \"process_keys\": {\n                    \"type\": \"integer\"\n                },\n                \"process_time\": {\n                    \"description\": \"Time\",\n                    \"type\": \"number\"\n                },\n                \"query\": {\n                    \"type\": \"string\"\n                },\n                \"query_time\": {\n                    \"description\": \"latency\",\n                    \"type\": \"number\"\n                },\n                \"request_count\": {\n                    \"description\": \"Coprocessor\",\n                    \"type\": \"integer\"\n                },\n                \"resolve_lock_time\": {\n                    \"type\": \"number\"\n                },\n                \"resource_group\": {\n                    \"type\": \"string\"\n                },\n                \"rewrite_time\": {\n                    \"type\": \"number\"\n                },\n                \"rocksdb_block_cache_hit_count\": {\n                    \"type\": \"integer\"\n                },\n                \"rocksdb_block_read_byte\": {\n                    \"type\": \"integer\"\n                },\n                \"rocksdb_block_read_count\": {\n                    \"type\": \"integer\"\n                },\n                \"rocksdb_delete_skipped_count\": {\n                    \"description\": \"RocksDB\",\n                    \"type\": \"integer\"\n                },\n                \"rocksdb_key_skipped_count\": {\n                    \"type\": \"integer\"\n                },\n                \"ru\": {\n                    \"description\": \"Resource Control\",\n                    \"type\": \"number\"\n                },\n                \"stats\": {\n                    \"type\": \"string\"\n                },\n                \"success\": {\n                    \"type\": \"integer\"\n                },\n                \"time_queued_by_rc\": {\n                    \"type\": \"number\"\n                },\n                \"timestamp\": {\n                    \"description\": \"finish time\",\n                    \"type\": \"number\"\n                },\n                \"total_keys\": {\n                    \"type\": \"integer\"\n                },\n                \"txn_retry\": {\n                    \"type\": \"integer\"\n                },\n                \"txn_start_ts\": {\n                    \"description\": \"TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\",\n                    \"type\": \"string\"\n                },\n                \"unpacked_bytes_received_tiflash_cross_zone\": {\n                    \"type\": \"integer\"\n                },\n                \"unpacked_bytes_received_tiflash_total\": {\n                    \"type\": \"integer\"\n                },\n                \"unpacked_bytes_received_tikv_cross_zone\": {\n                    \"type\": \"integer\"\n                },\n                \"unpacked_bytes_received_tikv_total\": {\n                    \"type\": \"integer\"\n                },\n                \"unpacked_bytes_sent_tiflash_cross_zone\": {\n                    \"type\": \"integer\"\n                },\n                \"unpacked_bytes_sent_tiflash_total\": {\n                    \"type\": \"integer\"\n                },\n                \"unpacked_bytes_sent_tikv_cross_zone\": {\n                    \"type\": \"integer\"\n                },\n                \"unpacked_bytes_sent_tikv_total\": {\n                    \"description\": \"Network fields\",\n                    \"type\": \"integer\"\n                },\n                \"user\": {\n                    \"description\": \"Connection\",\n                    \"type\": \"string\"\n                },\n                \"wait_prewrite_binlog_time\": {\n                    \"type\": \"number\"\n                },\n                \"wait_time\": {\n                    \"type\": \"number\"\n                },\n                \"wait_ts\": {\n                    \"type\": \"number\"\n                },\n                \"warnings\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                },\n                \"write_keys\": {\n                    \"description\": \"Transaction\",\n                    \"type\": \"integer\"\n                },\n                \"write_size\": {\n                    \"type\": \"integer\"\n                },\n                \"write_sql_response_total\": {\n                    \"type\": \"number\"\n                }\n            }\n        },\n        \"sso.CreateImpersonationRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"password\": {\n                    \"type\": \"string\"\n                },\n                \"sql_user\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"sso.SSOImpersonationModel\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"last_impersonate_status\": {\n                    \"type\": \"string\"\n                },\n                \"sql_user\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"sso.SetConfigRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"config\": {\n                    \"$ref\": \"#/definitions/config.SSOCoreConfig\"\n                }\n            }\n        },\n        \"statement.Binding\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"plan_digest\": {\n                    \"type\": \"string\"\n                },\n                \"source\": {\n                    \"type\": \"string\",\n                    \"enum\": [\n                        \"manual\",\n                        \"history\",\n                        \"capture\",\n                        \"evolve\"\n                    ],\n                    \"example\": \"manual\"\n                },\n                \"status\": {\n                    \"type\": \"string\",\n                    \"enum\": [\n                        \"enabled\",\n                        \"using\",\n                        \"disabled\",\n                        \"deleted\",\n                        \"invalid\",\n                        \"rejected\",\n                        \"pending verify\"\n                    ],\n                    \"example\": \"enabled\"\n                }\n            }\n        },\n        \"statement.EditableConfig\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"enable\": {\n                    \"type\": \"boolean\"\n                },\n                \"history_size\": {\n                    \"type\": \"integer\"\n                },\n                \"internal_query\": {\n                    \"type\": \"boolean\"\n                },\n                \"max_size\": {\n                    \"type\": \"integer\"\n                },\n                \"refresh_interval\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"statement.GetStatementsRequest\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"begin_time\": {\n                    \"type\": \"integer\"\n                },\n                \"end_time\": {\n                    \"type\": \"integer\"\n                },\n                \"fields\": {\n                    \"type\": \"string\"\n                },\n                \"resource_groups\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"schemas\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"stmt_types\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"text\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"statement.Model\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"avg_affected_rows\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_backoff_time\": {\n                    \"description\": \"avg total back off time per sql\",\n                    \"type\": \"integer\"\n                },\n                \"avg_commit_backoff_time\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_commit_time\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_compile_latency\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_cop_process_time\": {\n                    \"description\": \"avg process time per copr task\",\n                    \"type\": \"integer\"\n                },\n                \"avg_cop_wait_time\": {\n                    \"description\": \"avg wait time per copr task\",\n                    \"type\": \"integer\"\n                },\n                \"avg_disk\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_get_commit_ts_time\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_ia_read_segment_count\": {\n                    \"type\": \"number\"\n                },\n                \"avg_ia_remote_read_segment_size\": {\n                    \"type\": \"number\"\n                },\n                \"avg_ia_remote_read_segment_wait_time\": {\n                    \"type\": \"number\"\n                },\n                \"avg_latency\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_local_latch_wait_time\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_mem\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_mem_arbitration\": {\n                    \"type\": \"number\"\n                },\n                \"avg_parse_latency\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_prewrite_regions\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_prewrite_time\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_process_time\": {\n                    \"description\": \"avg total process time per sql\",\n                    \"type\": \"integer\"\n                },\n                \"avg_processed_keys\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_resolve_lock_time\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_rocksdb_block_cache_hit_count\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_rocksdb_block_read_byte\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_rocksdb_block_read_count\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_rocksdb_delete_skipped_count\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_rocksdb_key_skipped_count\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_ru\": {\n                    \"type\": \"number\"\n                },\n                \"avg_time_queued_by_rc\": {\n                    \"type\": \"number\"\n                },\n                \"avg_total_keys\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_txn_retry\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_wait_time\": {\n                    \"description\": \"avg total wait time per sql\",\n                    \"type\": \"integer\"\n                },\n                \"avg_write_keys\": {\n                    \"type\": \"integer\"\n                },\n                \"avg_write_size\": {\n                    \"type\": \"integer\"\n                },\n                \"binary_plan\": {\n                    \"type\": \"string\"\n                },\n                \"binary_plan_json\": {\n                    \"type\": \"string\"\n                },\n                \"binary_plan_text\": {\n                    \"type\": \"string\"\n                },\n                \"digest\": {\n                    \"type\": \"string\"\n                },\n                \"digest_text\": {\n                    \"type\": \"string\"\n                },\n                \"exec_count\": {\n                    \"type\": \"integer\"\n                },\n                \"first_seen\": {\n                    \"type\": \"integer\"\n                },\n                \"index_names\": {\n                    \"type\": \"string\"\n                },\n                \"last_seen\": {\n                    \"type\": \"integer\"\n                },\n                \"max_backoff_time\": {\n                    \"description\": \"max back off time per sql\",\n                    \"type\": \"integer\"\n                },\n                \"max_commit_backoff_time\": {\n                    \"type\": \"integer\"\n                },\n                \"max_commit_time\": {\n                    \"type\": \"integer\"\n                },\n                \"max_compile_latency\": {\n                    \"type\": \"integer\"\n                },\n                \"max_cop_process_time\": {\n                    \"description\": \"max process time per copr task\",\n                    \"type\": \"integer\"\n                },\n                \"max_cop_wait_time\": {\n                    \"description\": \"max wait time per copr task\",\n                    \"type\": \"integer\"\n                },\n                \"max_disk\": {\n                    \"type\": \"integer\"\n                },\n                \"max_get_commit_ts_time\": {\n                    \"type\": \"integer\"\n                },\n                \"max_ia_read_segment_count\": {\n                    \"type\": \"integer\"\n                },\n                \"max_ia_remote_read_segment_size\": {\n                    \"type\": \"integer\"\n                },\n                \"max_ia_remote_read_segment_wait_time\": {\n                    \"type\": \"number\"\n                },\n                \"max_latency\": {\n                    \"type\": \"integer\"\n                },\n                \"max_local_latch_wait_time\": {\n                    \"type\": \"integer\"\n                },\n                \"max_mem\": {\n                    \"type\": \"integer\"\n                },\n                \"max_mem_arbitration\": {\n                    \"type\": \"number\"\n                },\n                \"max_parse_latency\": {\n                    \"type\": \"integer\"\n                },\n                \"max_prewrite_regions\": {\n                    \"type\": \"integer\"\n                },\n                \"max_prewrite_time\": {\n                    \"type\": \"integer\"\n                },\n                \"max_process_time\": {\n                    \"description\": \"max process time per sql\",\n                    \"type\": \"integer\"\n                },\n                \"max_processed_keys\": {\n                    \"type\": \"integer\"\n                },\n                \"max_resolve_lock_time\": {\n                    \"type\": \"integer\"\n                },\n                \"max_rocksdb_block_cache_hit_count\": {\n                    \"type\": \"integer\"\n                },\n                \"max_rocksdb_block_read_byte\": {\n                    \"type\": \"integer\"\n                },\n                \"max_rocksdb_block_read_count\": {\n                    \"type\": \"integer\"\n                },\n                \"max_rocksdb_delete_skipped_count\": {\n                    \"description\": \"RocksDB\",\n                    \"type\": \"integer\"\n                },\n                \"max_rocksdb_key_skipped_count\": {\n                    \"type\": \"integer\"\n                },\n                \"max_ru\": {\n                    \"type\": \"number\"\n                },\n                \"max_time_queued_by_rc\": {\n                    \"type\": \"number\"\n                },\n                \"max_total_keys\": {\n                    \"type\": \"integer\"\n                },\n                \"max_txn_retry\": {\n                    \"type\": \"integer\"\n                },\n                \"max_wait_time\": {\n                    \"description\": \"max wait time per sql\",\n                    \"type\": \"integer\"\n                },\n                \"max_write_keys\": {\n                    \"type\": \"integer\"\n                },\n                \"max_write_size\": {\n                    \"type\": \"integer\"\n                },\n                \"min_latency\": {\n                    \"type\": \"integer\"\n                },\n                \"plan\": {\n                    \"description\": \"deprecated, replaced by BinaryPlanText\",\n                    \"type\": \"string\"\n                },\n                \"plan_cache_hits\": {\n                    \"type\": \"integer\"\n                },\n                \"plan_can_be_bound\": {\n                    \"type\": \"boolean\"\n                },\n                \"plan_count\": {\n                    \"type\": \"integer\"\n                },\n                \"plan_digest\": {\n                    \"type\": \"string\"\n                },\n                \"plan_hint\": {\n                    \"type\": \"string\"\n                },\n                \"prev_sample_text\": {\n                    \"type\": \"string\"\n                },\n                \"query_sample_text\": {\n                    \"type\": \"string\"\n                },\n                \"related_schemas\": {\n                    \"description\": \"Computed fields\",\n                    \"type\": \"string\"\n                },\n                \"resource_group\": {\n                    \"description\": \"Resource Control\",\n                    \"type\": \"string\"\n                },\n                \"sample_user\": {\n                    \"type\": \"string\"\n                },\n                \"schema_name\": {\n                    \"type\": \"string\"\n                },\n                \"stmt_type\": {\n                    \"type\": \"string\"\n                },\n                \"sum_backoff_times\": {\n                    \"type\": \"integer\"\n                },\n                \"sum_cop_task_num\": {\n                    \"type\": \"integer\"\n                },\n                \"sum_errors\": {\n                    \"type\": \"integer\"\n                },\n                \"sum_ia_read_segment_count\": {\n                    \"description\": \"IA remote read segment metrics\",\n                    \"type\": \"integer\"\n                },\n                \"sum_ia_remote_read_segment_size\": {\n                    \"type\": \"integer\"\n                },\n                \"sum_ia_remote_read_segment_wait_time\": {\n                    \"type\": \"number\"\n                },\n                \"sum_latency\": {\n                    \"type\": \"integer\"\n                },\n                \"sum_ru\": {\n                    \"type\": \"number\"\n                },\n                \"sum_unpacked_bytes_received_tiflash_cross_zone\": {\n                    \"type\": \"integer\"\n                },\n                \"sum_unpacked_bytes_received_tiflash_total\": {\n                    \"type\": \"integer\"\n                },\n                \"sum_unpacked_bytes_received_tikv_cross_zone\": {\n                    \"type\": \"integer\"\n                },\n                \"sum_unpacked_bytes_received_tikv_total\": {\n                    \"type\": \"integer\"\n                },\n                \"sum_unpacked_bytes_sent_tiflash_cross_zone\": {\n                    \"type\": \"integer\"\n                },\n                \"sum_unpacked_bytes_sent_tiflash_total\": {\n                    \"type\": \"integer\"\n                },\n                \"sum_unpacked_bytes_sent_tikv_cross_zone\": {\n                    \"type\": \"integer\"\n                },\n                \"sum_unpacked_bytes_sent_tikv_total\": {\n                    \"description\": \"Network Fields\",\n                    \"type\": \"integer\"\n                },\n                \"sum_warnings\": {\n                    \"type\": \"integer\"\n                },\n                \"summary_begin_time\": {\n                    \"type\": \"integer\"\n                },\n                \"summary_end_time\": {\n                    \"type\": \"integer\"\n                },\n                \"table_names\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"topology.AlertManagerInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ip\": {\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"topology.GrafanaInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"ip\": {\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"type\": \"integer\"\n                }\n            }\n        },\n        \"topology.PDInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"deploy_path\": {\n                    \"type\": \"string\"\n                },\n                \"git_hash\": {\n                    \"type\": \"string\"\n                },\n                \"ip\": {\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"type\": \"integer\"\n                },\n                \"start_timestamp\": {\n                    \"description\": \"Ts = 0 means unknown\",\n                    \"type\": \"integer\"\n                },\n                \"status\": {\n                    \"type\": \"integer\"\n                },\n                \"version\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"topology.SchedulingInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"deploy_path\": {\n                    \"type\": \"string\"\n                },\n                \"git_hash\": {\n                    \"type\": \"string\"\n                },\n                \"ip\": {\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"type\": \"integer\"\n                },\n                \"start_timestamp\": {\n                    \"type\": \"integer\"\n                },\n                \"status\": {\n                    \"type\": \"integer\"\n                },\n                \"version\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"topology.StoreInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"deploy_path\": {\n                    \"type\": \"string\"\n                },\n                \"git_hash\": {\n                    \"type\": \"string\"\n                },\n                \"ip\": {\n                    \"type\": \"string\"\n                },\n                \"labels\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"port\": {\n                    \"type\": \"integer\"\n                },\n                \"start_timestamp\": {\n                    \"type\": \"integer\"\n                },\n                \"status\": {\n                    \"type\": \"integer\"\n                },\n                \"status_port\": {\n                    \"type\": \"integer\"\n                },\n                \"version\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"topology.StoreLabels\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"address\": {\n                    \"type\": \"string\"\n                },\n                \"labels\": {\n                    \"type\": \"object\",\n                    \"additionalProperties\": {\n                        \"type\": \"string\"\n                    }\n                }\n            }\n        },\n        \"topology.StoreLocation\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"location_labels\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"string\"\n                    }\n                },\n                \"stores\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/topology.StoreLabels\"\n                    }\n                }\n            }\n        },\n        \"topology.TSOInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"deploy_path\": {\n                    \"type\": \"string\"\n                },\n                \"git_hash\": {\n                    \"type\": \"string\"\n                },\n                \"ip\": {\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"type\": \"integer\"\n                },\n                \"start_timestamp\": {\n                    \"type\": \"integer\"\n                },\n                \"status\": {\n                    \"type\": \"integer\"\n                },\n                \"version\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"topology.TiCDCInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"cluster_name\": {\n                    \"type\": \"string\"\n                },\n                \"deploy_path\": {\n                    \"type\": \"string\"\n                },\n                \"git_hash\": {\n                    \"type\": \"string\"\n                },\n                \"ip\": {\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"type\": \"integer\"\n                },\n                \"start_timestamp\": {\n                    \"type\": \"integer\"\n                },\n                \"status\": {\n                    \"type\": \"integer\"\n                },\n                \"status_port\": {\n                    \"type\": \"integer\"\n                },\n                \"version\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"topology.TiDBInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"deploy_path\": {\n                    \"type\": \"string\"\n                },\n                \"git_hash\": {\n                    \"type\": \"string\"\n                },\n                \"ip\": {\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"type\": \"integer\"\n                },\n                \"start_timestamp\": {\n                    \"type\": \"integer\"\n                },\n                \"status\": {\n                    \"type\": \"integer\"\n                },\n                \"status_port\": {\n                    \"type\": \"integer\"\n                },\n                \"version\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"topology.TiProxyInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"deploy_path\": {\n                    \"type\": \"string\"\n                },\n                \"git_hash\": {\n                    \"type\": \"string\"\n                },\n                \"ip\": {\n                    \"type\": \"string\"\n                },\n                \"port\": {\n                    \"type\": \"integer\"\n                },\n                \"start_timestamp\": {\n                    \"type\": \"integer\"\n                },\n                \"status\": {\n                    \"type\": \"integer\"\n                },\n                \"status_port\": {\n                    \"type\": \"integer\"\n                },\n                \"version\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"topsql.EditableConfig\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"enable\": {\n                    \"type\": \"boolean\"\n                }\n            }\n        },\n        \"topsql.InstanceItem\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"instance\": {\n                    \"type\": \"string\"\n                },\n                \"instance_type\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"topsql.InstanceResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"data\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/topsql.InstanceItem\"\n                    }\n                }\n            }\n        },\n        \"topsql.SummaryByItem\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"cpu_time_ms\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                },\n                \"cpu_time_ms_sum\": {\n                    \"type\": \"integer\"\n                },\n                \"logical_io_bytes\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                },\n                \"logical_io_bytes_sum\": {\n                    \"type\": \"integer\"\n                },\n                \"network_bytes\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                },\n                \"network_bytes_sum\": {\n                    \"type\": \"integer\"\n                },\n                \"text\": {\n                    \"type\": \"string\"\n                },\n                \"timestamp_sec\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                }\n            }\n        },\n        \"topsql.SummaryItem\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"cpu_time_ms\": {\n                    \"type\": \"integer\"\n                },\n                \"duration_per_exec_ms\": {\n                    \"type\": \"number\"\n                },\n                \"exec_count_per_sec\": {\n                    \"type\": \"number\"\n                },\n                \"is_other\": {\n                    \"type\": \"boolean\"\n                },\n                \"logical_io_bytes\": {\n                    \"type\": \"integer\"\n                },\n                \"network_bytes\": {\n                    \"type\": \"integer\"\n                },\n                \"plans\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/topsql.SummaryPlanItem\"\n                    }\n                },\n                \"scan_indexes_per_sec\": {\n                    \"type\": \"number\"\n                },\n                \"scan_records_per_sec\": {\n                    \"type\": \"number\"\n                },\n                \"sql_digest\": {\n                    \"type\": \"string\"\n                },\n                \"sql_text\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"topsql.SummaryPlanItem\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"cpu_time_ms\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                },\n                \"duration_per_exec_ms\": {\n                    \"type\": \"number\"\n                },\n                \"exec_count_per_sec\": {\n                    \"type\": \"number\"\n                },\n                \"logical_io_bytes\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                },\n                \"network_bytes\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                },\n                \"plan_digest\": {\n                    \"type\": \"string\"\n                },\n                \"plan_text\": {\n                    \"type\": \"string\"\n                },\n                \"scan_indexes_per_sec\": {\n                    \"type\": \"number\"\n                },\n                \"scan_records_per_sec\": {\n                    \"type\": \"number\"\n                },\n                \"timestamp_sec\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                }\n            }\n        },\n        \"topsql.SummaryResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"data\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/topsql.SummaryItem\"\n                    }\n                },\n                \"data_by\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/topsql.SummaryByItem\"\n                    }\n                }\n            }\n        },\n        \"topsql.TikvNetworkIoCollectionConfig\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"enable\": {\n                    \"type\": \"boolean\"\n                },\n                \"is_multi_value\": {\n                    \"type\": \"boolean\"\n                }\n            }\n        },\n        \"topsql.UpdateTikvNetworkIoCollectionResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"warnings\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"$ref\": \"#/definitions/rest.ErrorResponse\"\n                    }\n                }\n            }\n        },\n        \"user.AuthenticateForm\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"extra\": {\n                    \"description\": \"FIXME: Use strong type\",\n                    \"type\": \"string\"\n                },\n                \"password\": {\n                    \"type\": \"string\"\n                },\n                \"type\": {\n                    \"type\": \"integer\",\n                    \"example\": 0\n                },\n                \"username\": {\n                    \"description\": \"Does not present for AuthTypeSharingCode\",\n                    \"type\": \"string\",\n                    \"example\": \"root\"\n                }\n            }\n        },\n        \"user.GetLoginInfoResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"sql_auth_public_key\": {\n                    \"type\": \"string\"\n                },\n                \"supported_auth_types\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"integer\"\n                    }\n                }\n            }\n        },\n        \"user.SignOutInfo\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"end_session_url\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"user.TokenResponse\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"expire\": {\n                    \"type\": \"string\"\n                },\n                \"token\": {\n                    \"type\": \"string\"\n                }\n            }\n        },\n        \"version.Info\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"build_git_hash\": {\n                    \"type\": \"string\"\n                },\n                \"build_time\": {\n                    \"type\": \"string\"\n                },\n                \"internal_version\": {\n                    \"type\": \"string\"\n                },\n                \"pd_version\": {\n                    \"type\": \"string\"\n                },\n                \"standalone\": {\n                    \"type\": \"string\"\n                }\n            }\n        }\n    },\n    \"securityDefinitions\": {\n        \"JwtAuth\": {\n            \"type\": \"apiKey\",\n            \"name\": \"Authorization\",\n            \"in\": \"header\"\n        }\n    }\n}"
  },
  {
    "path": "ui/packages/tidb-dashboard-client/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"declaration\": true\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/.gitignore",
    "content": "/public/speedscope\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/builder.js",
    "content": "const path = require('path')\nconst fs = require('fs-extra')\nconst chalk = require('chalk')\nconst { watch } = require('chokidar')\n\nconst { build } = require('esbuild')\nconst postCssPlugin = require('@baurine/esbuild-plugin-postcss3')\nconst autoprefixer = require('autoprefixer')\nconst { yamlPlugin } = require('esbuild-plugin-yaml')\n\nconst { lessModifyVars, lessGlobalVars } = require('../../less-vars')\n\nconst isDev = process.env.NODE_ENV !== 'production'\n\n// load env\nconst dotenv = require('dotenv')\nconst envFile = isDev ? './.env.development' : './.env.production'\ndotenv.config({ path: path.resolve(process.cwd(), envFile) })\nif (isDev && fs.pathExistsSync(path.resolve(process.cwd(), '.env.local'))) {\n  dotenv.config({\n    path: '.env.local',\n    override: true\n  })\n}\n\nconst outDir = 'dist'\nconst targetVariantDashboardPath = process.env.TARGET_VARIANT_DASHBOARD_PATH\n\nfunction genDefine() {\n  const define = {}\n  for (const k in process.env) {\n    if (k.startsWith('REACT_APP_')) {\n      let envVal = process.env[k]\n      // Example: REACT_APP_VERSION=$npm_package_version\n      // Expect output: REACT_APP_VERSION=0.1.0\n      if (envVal.startsWith('$')) {\n        envVal = process.env[envVal.substring(1)]\n      }\n      define[`process.env.${k}`] = JSON.stringify(envVal)\n    }\n  }\n  return define\n}\n\n// customized plugin: log time\nconst logTime = (_options = {}) => ({\n  name: 'logTime',\n  setup(build) {\n    let time\n\n    build.onStart(() => {\n      time = new Date()\n      console.log(`Build started`)\n    })\n\n    build.onEnd(() => {\n      console.log(`Build ended: ${chalk.yellow(`${new Date() - time}ms`)}`)\n    })\n  }\n})\n\nconst esbuildParams = {\n  color: true,\n  entryPoints: {\n    dashboardApp: 'src/dashboardApp/index.ts',\n    diagnoseReport: 'src/diagnoseReportApp/index.tsx'\n  },\n  outdir: outDir,\n  minify: !isDev,\n  format: 'esm',\n  bundle: true,\n  sourcemap: true,\n  logLevel: 'error',\n  incremental: true,\n  // splitting: true,\n  platform: 'browser',\n  plugins: [\n    postCssPlugin.default({\n      lessOptions: {\n        modifyVars: lessModifyVars,\n        globalVars: lessGlobalVars,\n        javascriptEnabled: true\n      },\n      enableCache: true,\n      plugins: [autoprefixer]\n    }),\n    yamlPlugin(),\n    logTime()\n  ],\n  define: genDefine(),\n  inject: ['./process-shim.js'] // fix runtime crash\n}\n\nfunction buildHtml(inputFilename, outputFilename) {\n  let result = fs.readFileSync(inputFilename).toString()\n\n  // replace TIME_PLACE_HOLDER\n  const nowTime = new Date().valueOf()\n  result = result.replace(new RegExp(`%TIME_PLACE_HOLDER%`, 'g'), nowTime)\n\n  fs.writeFileSync(outputFilename, result)\n}\n\nfunction handleAssets() {\n  fs.copySync('./public', `./${outDir}`)\n  buildHtml('./public/index.html', `./${outDir}/index.html`)\n}\n\nfunction copyAssets() {\n  if (!fs.existsSync(targetVariantDashboardPath)) {\n    console.log(\n      `target variant dashboard path ${targetVariantDashboardPath} doesn't exist, ignore`\n    )\n    return\n  }\n  fs.removeSync(targetVariantDashboardPath)\n  fs.copySync(`./${outDir}`, targetVariantDashboardPath)\n  console.log('copy dashboard to target variant')\n}\n\nasync function main() {\n  fs.removeSync(`./${outDir}`)\n\n  const builder = await build(esbuildParams)\n  handleAssets()\n\n  function rebuild() {\n    builder\n      .rebuild()\n      .then(() => {\n        copyAssets()\n      })\n      .catch((err) => console.log(err))\n  }\n\n  if (isDev) {\n    copyAssets()\n\n    watch(`src/**/*`, { ignoreInitial: true }).on('all', () => {\n      rebuild()\n    })\n    watch('public/**/*', { ignoreInitial: true }).on('all', () => {\n      handleAssets()\n      copyAssets()\n    })\n    // watch \"node_modules/@pingcap/tidb-dashboard-lib/dist/**/*\" triggers too many rebuild\n    // so we just watch index.js to refine the experience\n    watch('node_modules/@pingcap/tidb-dashboard-lib/dist/index.js', {\n      ignoreInitial: true\n    }).on('all', () => {\n      rebuild()\n    })\n  } else {\n    process.exit(0)\n  }\n}\n\nmain()\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/gulpfile.js",
    "content": "const { task, series, parallel } = require('gulp')\nconst shell = require('gulp-shell')\n\ntask(\n  'speedscope:copy',\n  shell.task(\n    'mkdir -p public/speedscope && cp node_modules/@duorou_xu/speedscope/dist/release/* public/speedscope/'\n  )\n)\n\ntask('tsc:watch', shell.task('tsc --watch'))\ntask('tsc:check', shell.task('tsc'))\n\n// https://www.npmjs.com/package/eslint-watch\ntask('lint:watch', shell.task('esw --watch --cache --ext .tsx,.ts .'))\ntask('lint:check', shell.task('esw --cache --ext tsx,ts .'))\n\ntask('esbuild:dev', shell.task('NODE_ENV=development node builder.js'))\ntask('esbuild:build', shell.task('NODE_ENV=production node builder.js'))\n\ntask(\n  'dev',\n  series('speedscope:copy', parallel('tsc:watch', 'lint:watch', 'esbuild:dev'))\n)\n\ntask(\n  'build',\n  series(\n    'speedscope:copy',\n    parallel('tsc:check', 'lint:check', 'esbuild:build')\n  )\n)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/package.json",
    "content": "{\n  \"name\": \"@pingcap/tidb-dashboard-for-clinic-cloud\",\n  \"version\": \"0.0.56\",\n  \"main\": \"dist/dashboardApp.js\",\n  \"module\": \"dist/dashboardApp.js\",\n  \"files\": [\n    \"dist/*.js\",\n    \"dist/*.css\",\n    \"dist/*.map\",\n    \"dist/speedscope/*.js\",\n    \"dist/speedscope/*.css\",\n    \"dist/speedscope/*.map\",\n    \"dist/speedscope/*.png\",\n    \"dist/speedscope/*.txt\",\n    \"package.json\",\n    \"README.md\"\n  ],\n  \"scripts\": {\n    \"dev\": \"gulp dev\",\n    \"build\": \"gulp build\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"@ant-design/icons\": \"^4.7.0\",\n    \"@duorou_xu/speedscope\": \"1.14.1\",\n    \"@fortawesome/fontawesome-free\": \"^6.1.1\",\n    \"@g07cha/flexbox-react\": \"^5.0.0\",\n    \"@pingcap/tidb-dashboard-client\": \"workspace:^1.0.0\",\n    \"@pingcap/tidb-dashboard-lib\": \"workspace:^1.0.0\",\n    \"ahooks\": \"^3.1.9\",\n    \"antd\": \"^4.18.7\",\n    \"axios\": \"^1.12.0\",\n    \"bulma\": \"^0.9.4\",\n    \"classnames\": \"^2.3.1\",\n    \"compare-versions\": \"^5.0.1\",\n    \"eventemitter2\": \"^6.4.5\",\n    \"i18next\": \"^23.7.11\",\n    \"nprogress\": \"^0.2.0\",\n    \"rc-animate\": \"^3.1.0\",\n    \"react\": \"^17.0.2\",\n    \"react-dom\": \"^17.0.2\",\n    \"react-i18next\": \"^11.15.4\",\n    \"react-markdown\": \"^8.0.3\",\n    \"react-router-dom\": \"6\",\n    \"react-spring\": \"^8.0.27\",\n    \"react-use\": \"^15.3.3\",\n    \"single-spa\": \"^5.9.4\",\n    \"single-spa-react\": \"^4.6.1\"\n  },\n  \"devDependencies\": {\n    \"@baurine/esbuild-plugin-babel\": \"^0.3.0\",\n    \"@baurine/esbuild-plugin-postcss3\": \"^0.4.3\",\n    \"@cypress/code-coverage\": \"^3.9.12\",\n    \"@cypress/skip-test\": \"^2.6.1\",\n    \"@types/node\": \"^16.9.1\",\n    \"@types/react\": \"^17.0.20\",\n    \"@types/react-dom\": \"^17.0.9\",\n    \"autoprefixer\": \"^10.4.2\",\n    \"babel-plugin-istanbul\": \"^6.1.1\",\n    \"chalk\": \"4.1.2\",\n    \"chokidar\": \"^3.5.2\",\n    \"clipboardy\": \"2.3.0\",\n    \"cypress\": \"8.5.0\",\n    \"cypress-image-snapshot\": \"^4.0.1\",\n    \"cypress-real-events\": \"^1.7.0\",\n    \"dayjs\": \"^1.10.8\",\n    \"dotenv\": \"^16.0.1\",\n    \"esbuild\": \"^0.14.23\",\n    \"esbuild-plugin-svgr\": \"^1.0.0\",\n    \"esbuild-plugin-yaml\": \"^0.0.1\",\n    \"eslint-plugin-cypress\": \"^2.12.1\",\n    \"eslint-watch\": \"^8.0.0\",\n    \"fs-extra\": \"^10.0.0\",\n    \"gulp\": \"^4.0.2\",\n    \"gulp-shell\": \"^0.8.0\",\n    \"http-proxy-middleware\": \"^2.0.6\",\n    \"live-server\": \"^1.2.1\",\n    \"neat-csv\": \"5.1.0\",\n    \"typescript\": \"^4.7.3\"\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/process-shim.js",
    "content": "export let process = {\n  // cwd: () => '',\n  env: {} // to avoid `process.env` undefined in runtime\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/public/diagnose-report/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"../distro-res/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <link rel=\"stylesheet\" href=\"../diagnoseReport.css?t=%TIME_PLACE_HOLDER%\" />\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <script\n      type=\"module\"\n      src=\"../diagnoseReport.js?t=%TIME_PLACE_HOLDER%\"\n    ></script>\n\n    <script>\n      window.onload = function () {\n        const searchParams = new URLSearchParams(window.location.search)\n        const orgId = searchParams.get('orgId') || ''\n        const clusterId = searchParams.get('clusterId') || ''\n        const reportId = searchParams.get('reportId') || ''\n        if (orgId === '' || clusterId === '' || reportId === '') {\n          window.alert('Invalid orgId, clusterId or reportId parameter!')\n          return\n        }\n\n        const token = localStorage.getItem('clinic.auth.csrf_token')\n        const apiBasePath = `/clinic/api/v1/dashboard/proxy/cluster/${clusterId}/pd/dashboard/api`\n        const reportDataApiPath = `${apiBasePath}/diagnose/reports/${reportId}/data.js`\n        // https://stackoverflow.com/a/67999347/2998877\n        fetch(reportDataApiPath, {\n          headers: {\n            'x-org-id': orgId,\n            'x-cluster-id': clusterId,\n            'x-csrf-token': token\n          }\n        })\n          .then((res) => res.blob())\n          .then((blob) => {\n            var objectURL = URL.createObjectURL(blob)\n            var sc = document.createElement('script')\n            sc.setAttribute('src', objectURL)\n            sc.setAttribute('type', 'text/javascript')\n            document.head.appendChild(sc)\n            sc.onload = function () {\n              window.dispatchEvent(\n                new CustomEvent('dashboard:diagnose_report_event')\n              )\n            }\n          })\n          .catch((err) => {\n            alert(err)\n          })\n      }\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/public/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"./distro-res/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta\n      http-equiv=\"Cache-control\"\n      content=\"no-cache, no-store, must-revalidate\"\n    />\n    <style>\n      body {\n        margin: 0;\n        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,\n          Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n          sans-serif;\n        -webkit-font-smoothing: antialiased;\n        -moz-osx-font-smoothing: grayscale;\n        background: #fff;\n      }\n\n      #dashboard_page_spinner {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n      }\n\n      #dashboard_page_spinner p {\n        margin-left: -50%;\n        margin-top: 8px;\n        color: #888;\n      }\n\n      .dot-flashing {\n        position: relative;\n        width: 10px;\n        height: 10px;\n        border-radius: 5px;\n        background-color: #aaa;\n        -webkit-animation: dot-flashing 1s infinite linear alternate;\n        animation: dot-flashing 1s infinite linear alternate;\n        -webkit-animation-delay: 0.5s;\n        animation-delay: 0.5s;\n      }\n\n      .dot-flashing::before,\n      .dot-flashing::after {\n        content: '';\n        display: inline-block;\n        position: absolute;\n        top: 0;\n      }\n\n      .dot-flashing::before {\n        left: -15px;\n        width: 10px;\n        height: 10px;\n        border-radius: 5px;\n        background-color: #aaa;\n        -webkit-animation: dot-flashing 1s infinite alternate;\n        animation: dot-flashing 1s infinite alternate;\n        -webkit-animation-delay: 0s;\n        animation-delay: 0s;\n      }\n\n      .dot-flashing::after {\n        left: 15px;\n        width: 10px;\n        height: 10px;\n        border-radius: 5px;\n        background-color: #aaa;\n        -webkit-animation: dot-flashing 1s infinite alternate;\n        animation: dot-flashing 1s infinite alternate;\n        -webkit-animation-delay: 1s;\n        animation-delay: 1s;\n      }\n\n      @-webkit-keyframes dot-flashing {\n        0% {\n          background-color: #aaa;\n        }\n        50%,\n        100% {\n          background-color: #ddd;\n        }\n      }\n\n      @keyframes dot-flashing {\n        0% {\n          background-color: #aaa;\n        }\n        50%,\n        100% {\n          background-color: #ddd;\n        }\n      }\n    </style>\n    <link rel=\"stylesheet\" href=\"./dashboardApp.css?t=%TIME_PLACE_HOLDER%\" />\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n\n    <div id=\"dashboard_page_spinner\">\n      <div class=\"dot-flashing\"></div>\n      <p>\n        It may take a bit long time to load for the first time, due to download\n        some js files.\n      </p>\n    </div>\n    <div id=\"root\"></div>\n\n    <script type=\"module\">\n      import startDashboard from './dashboardApp.js?t=%TIME_PLACE_HOLDER%'\n\n      const apiToken = localStorage.getItem('clinic.auth.csrf_token')\n\n      // example entry link:\n      // http://localhost:8181/clinic/dashboard/cloud/?orgId=1&clusterId=63405#/overview\n      const searchParams = new URLSearchParams(window.location.search)\n      const orgId = searchParams.get('orgId') || ''\n      const clusterId = searchParams.get('clusterId') || ''\n      if (apiToken === '' || orgId === '' || clusterId === '') {\n        window.alert('Invalid token, orgId or clusterId!')\n        window.location.assign('/')\n      }\n\n      const apiPathBase = `/clinic/api/v1/dashboard/proxy/orgs/${orgId}/clusters/${clusterId}/pd/dashboard/api`\n      startDashboard({\n        clientOptions: {\n          apiPathBase,\n          apiToken\n        },\n        clusterInfo: {\n          orgId,\n          clusterId\n        },\n\n        appsConfig: {\n          overview: {\n            showMetrics: false\n          }\n        },\n        appsDisabled: [\n          'topsql',\n          'slow_query',\n          'conprof',\n          'query_editor',\n          'diagnose',\n          'monitoring',\n          'search_logs',\n          'system_report',\n          'resource_manager'\n        ]\n      })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/public/ngm.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"./distro-res/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta\n      http-equiv=\"Cache-control\"\n      content=\"no-cache, no-store, must-revalidate\"\n    />\n    <style>\n      body {\n        margin: 0;\n        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,\n          Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n          sans-serif;\n        -webkit-font-smoothing: antialiased;\n        -moz-osx-font-smoothing: grayscale;\n        background: #fff;\n      }\n\n      #dashboard_page_spinner {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n      }\n\n      #dashboard_page_spinner p {\n        margin-left: -50%;\n        margin-top: 8px;\n        color: #888;\n      }\n\n      .dot-flashing {\n        position: relative;\n        width: 10px;\n        height: 10px;\n        border-radius: 5px;\n        background-color: #aaa;\n        -webkit-animation: dot-flashing 1s infinite linear alternate;\n        animation: dot-flashing 1s infinite linear alternate;\n        -webkit-animation-delay: 0.5s;\n        animation-delay: 0.5s;\n      }\n\n      .dot-flashing::before,\n      .dot-flashing::after {\n        content: '';\n        display: inline-block;\n        position: absolute;\n        top: 0;\n      }\n\n      .dot-flashing::before {\n        left: -15px;\n        width: 10px;\n        height: 10px;\n        border-radius: 5px;\n        background-color: #aaa;\n        -webkit-animation: dot-flashing 1s infinite alternate;\n        animation: dot-flashing 1s infinite alternate;\n        -webkit-animation-delay: 0s;\n        animation-delay: 0s;\n      }\n\n      .dot-flashing::after {\n        left: 15px;\n        width: 10px;\n        height: 10px;\n        border-radius: 5px;\n        background-color: #aaa;\n        -webkit-animation: dot-flashing 1s infinite alternate;\n        animation: dot-flashing 1s infinite alternate;\n        -webkit-animation-delay: 1s;\n        animation-delay: 1s;\n      }\n\n      @-webkit-keyframes dot-flashing {\n        0% {\n          background-color: #aaa;\n        }\n        50%,\n        100% {\n          background-color: #ddd;\n        }\n      }\n\n      @keyframes dot-flashing {\n        0% {\n          background-color: #aaa;\n        }\n        50%,\n        100% {\n          background-color: #ddd;\n        }\n      }\n    </style>\n    <link rel=\"stylesheet\" href=\"./dashboardApp.css?t=%TIME_PLACE_HOLDER%\" />\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n\n    <div id=\"dashboard_page_spinner\">\n      <div class=\"dot-flashing\"></div>\n      <p>\n        It may take a bit long time to load for the first time, due to download\n        some js files.\n      </p>\n    </div>\n    <div id=\"root\"></div>\n\n    <script type=\"module\">\n      import startDashboard from './dashboardApp.js?t=%TIME_PLACE_HOLDER%'\n\n      const apiToken = localStorage.getItem('clinic.auth.csrf_token')\n\n      // example entry link:\n      // http://localhost:8181/clinic/dashboard/cloud/ngm.html?provider=aws&region=us-west-2&orgName=xxx&orgId=30052&projectId=43584&clusterId=61992&deployType=shared&userName=xxx#/slow_query\n      const searchParams = new URLSearchParams(window.location.search)\n      const provider = searchParams.get('provider') || ''\n      const region = searchParams.get('region') || ''\n      const orgId = searchParams.get('orgId') || ''\n      const projectId = searchParams.get('projectId') || ''\n      const clusterId = searchParams.get('clusterId') || ''\n      const deployType = searchParams.get('deployType') || ''\n\n      const orgName = searchParams.get('orgName') || ''\n      const clusterName = searchParams.get('clusterName') || ''\n      const userName = searchParams.get('userName') || ''\n\n      if (\n        apiToken === '' ||\n        provider === '' ||\n        region === '' ||\n        orgId === '' ||\n        projectId === '' ||\n        clusterId === '' ||\n        deployType === ''\n      ) {\n        window.alert(\n          'Invalid token, provider, region, orgId, projectId, clusterId or deployType!'\n        )\n        window.location.assign('/')\n      }\n\n      let clusterInfo = {\n        provider,\n        region,\n        orgId,\n        projectId,\n        clusterId,\n        deployType\n      }\n      // test\n      // clusterInfo = {\n      //   provider: 'aws',\n      //   region: 'us-east-1',\n      //   orgId: '1372813089209061633',\n      //   projectId: '1372813089454525346',\n      //   clusterId: '1379661944646413143',\n      //   deployType\n      // }\n\n      let topSQLTimeRange = [\n        5 * 60,\n        15 * 60,\n        30 * 60,\n        60 * 60,\n        6 * 60 * 60,\n        12 * 60 * 60,\n        24 * 60 * 60\n      ]\n      const topSQLTimeRangeLimit = 15 * 24 * 60 * 60\n      const longerTimeRangeOrgs = [\n        '1372813089209214865',\n        '1372813089209213994',\n        '1372813089209223465'\n      ]\n      if (longerTimeRangeOrgs.indexOf(orgId) >= 0) {\n        topSQLTimeRange = topSQLTimeRange.concat([\n          2 * 24 * 60 * 60,\n          3 * 24 * 60 * 60,\n          7 * 24 * 60 * 60\n        ])\n      }\n\n      const apiPathBase = `/ngm/api/v1`\n      startDashboard({\n        appOptions: {\n          hideNav: true,\n          skipLoadAppInfo: true,\n          skipReloadWhoAmI: true\n        },\n        clientOptions: {\n          apiPathBase,\n          apiToken\n        },\n        clusterInfo,\n        appsConfig: {\n          statement: {\n            enableExport: false,\n            showDBFilter: false,\n            showConfig: false,\n            showResourceGroupFilter: false,\n            showHelp: false,\n            enablePlanBinding: false\n          },\n          slowQuery: {\n            enableExport: false,\n            showDBFilter: true,\n            showDigestFilter: true,\n            listApiReturnDetail: false, // so we can use the result returned by list api in the detail page, avoid request detail api\n\n            orgName,\n            clusterName,\n            showTopSlowQueryLink: true\n            // timeRangeSelector: {\n            //   recentSeconds: [\n            //     5 * 60,\n            //     15 * 60,\n            //     30 * 60,\n            //     60 * 60,\n            //     2 * 60 * 60,\n            //     3 * 60 * 60\n            //   ],\n            //   customAbsoluteRangePicker: false\n            // }\n          },\n          topSlowQuery: {\n            orgName,\n            clusterName\n          },\n          topSQL: {\n            checkNgm: false,\n            showSetting: false,\n            orgName,\n            clusterName,\n\n            timeRangeSelector: {\n              recentSeconds: topSQLTimeRange,\n              customAbsoluteRangePicker: true,\n              timeRangeLimit: topSQLTimeRangeLimit\n            },\n            autoRefresh: false,\n            showSearchInStatements: false\n          },\n          conProf: {\n            checkNgm: false,\n            showSetting: false,\n            enableDownloadGroup: false,\n            enableDotGraph: false,\n            enablePreviewGoroutine: false,\n            listDuration: 1,\n            maxDays: 30\n          },\n          keyViz: {\n            showHelp: false,\n            showSetting: false\n          }\n        },\n        appsEnabled: [\n          'topsql',\n          'statement',\n          'slow_query',\n          'conprof',\n          'top_slowquery',\n          'keyviz'\n        ]\n      })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/ClusterInfo/context.ts",
    "content": "import {\n  IClusterInfoDataSource,\n  IClusterInfoContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client from '~/client'\n\nclass DataSource implements IClusterInfoDataSource {\n  clusterInfoGetHostsInfo(options?: ReqConfig) {\n    return client.getInstance().clusterInfoGetHostsInfo(options)\n  }\n\n  getStoreLocationTopology(options?: ReqConfig) {\n    return client.getInstance().getStoreLocationTopology(options)\n  }\n\n  getTiDBTopology(options?: ReqConfig) {\n    return client.getInstance().getTiDBTopology(options)\n  }\n\n  getStoreTopology(options?: ReqConfig) {\n    return client.getInstance().getStoreTopology(options)\n  }\n\n  getPDTopology(options?: ReqConfig) {\n    return client.getInstance().getPDTopology(options)\n  }\n\n  getTiCDCTopology(options?: ReqConfig) {\n    return client.getInstance().getTiCDCTopology(options)\n  }\n\n  getTiProxyTopology(options?: ReqConfig) {\n    return client.getInstance().getTiProxyTopology(options)\n  }\n\n  getTSOTopology(options?: ReqConfig) {\n    return client.getInstance().getTSOTopology(options)\n  }\n\n  getSchedulingTopology(options?: ReqConfig) {\n    return client.getInstance().getSchedulingTopology(options)\n  }\n\n  topologyTidbAddressDelete(address: string, options?: ReqConfig) {\n    return client.getInstance().topologyTidbAddressDelete({ address }, options)\n  }\n\n  clusterInfoGetStatistics(options?: ReqConfig) {\n    return client.getInstance().clusterInfoGetStatistics(options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IClusterInfoContext = {\n  ds,\n  cfg: { apiPathBase: client.getBasePath() }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/ClusterInfo/index.tsx",
    "content": "import React from 'react'\nimport {\n  ClusterInfoApp,\n  ClusterInfoProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <ClusterInfoProvider value={ctx}>\n      <ClusterInfoApp />\n    </ClusterInfoProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/ClusterInfo/meta.ts",
    "content": "import { ClusterOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'cluster_info',\n  routerPrefix: '/cluster_info',\n  icon: ClusterOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Configuration/context.ts",
    "content": "import {\n  IConfigurationDataSource,\n  IConfigurationContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { ConfigurationEditRequest } from '~/client'\n\nclass DataSource implements IConfigurationDataSource {\n  configurationEdit(request: ConfigurationEditRequest, options?: ReqConfig) {\n    return client.getInstance().configurationEdit({ request }, options)\n  }\n\n  configurationGetAll(options?: ReqConfig) {\n    return client.getInstance().configurationGetAll(options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IConfigurationContext = {\n  ds\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Configuration/index.tsx",
    "content": "import React from 'react'\nimport {\n  ConfigurationApp,\n  ConfigurationProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <ConfigurationProvider value={ctx}>\n      <ConfigurationApp />\n    </ConfigurationProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Configuration/meta.ts",
    "content": "import { ToolOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'configuration',\n  routerPrefix: '/configuration',\n  icon: ToolOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/ContinuousProfiling/context.ts",
    "content": "import {\n  IConProfilingDataSource,\n  IConProfilingContext,\n  ReqConfig,\n  IConProfilingConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { ConprofNgMonitoringConfig } from '~/client'\n\nimport publicPathBase from '~/utils/publicPathPrefix'\n\nclass DataSource implements IConProfilingDataSource {\n  private headers: {} = {}\n\n  constructor(cfg: Partial<IConProfilingConfig>) {\n    this.headers =\n      cfg.deployType === 'nextgen-host'\n        ? {\n            'x-cluster-id': cfg.clusterId,\n            'x-deploy-type': 'premium'\n          }\n        : {}\n  }\n\n  continuousProfilingActionTokenGet(q: string, options?: ReqConfig) {\n    return client\n      .getInstance()\n      .continuousProfilingActionTokenGet(\n        { q },\n        { headers: this.headers, ...options }\n      )\n  }\n\n  continuousProfilingComponentsGet(options?: ReqConfig) {\n    return client\n      .getInstance()\n      .continuousProfilingComponentsGet({ headers: this.headers, ...options })\n  }\n\n  continuousProfilingConfigGet(options?: ReqConfig) {\n    return client\n      .getInstance()\n      .continuousProfilingConfigGet({ headers: this.headers, ...options })\n  }\n\n  continuousProfilingConfigPost(\n    request: ConprofNgMonitoringConfig,\n    options?: ReqConfig\n  ) {\n    return client\n      .getInstance()\n      .continuousProfilingConfigPost(\n        { request },\n        { headers: this.headers, ...options }\n      )\n  }\n\n  continuousProfilingDownloadGet(ts: number, options?: ReqConfig) {\n    return client\n      .getInstance()\n      .continuousProfilingDownloadGet(\n        { ts },\n        { headers: this.headers, ...options }\n      )\n  }\n\n  continuousProfilingEstimateSizeGet(options?: ReqConfig) {\n    return client\n      .getInstance()\n      .continuousProfilingEstimateSizeGet({ headers: this.headers, ...options })\n  }\n\n  continuousProfilingGroupProfileDetailGet(ts: number, options?: ReqConfig) {\n    return client\n      .getInstance()\n      .continuousProfilingGroupProfileDetailGet(\n        { ts },\n        { headers: this.headers, ...options }\n      )\n  }\n\n  continuousProfilingGroupProfilesGet(\n    beginTime?: number,\n    endTime?: number,\n    options?: ReqConfig\n  ) {\n    return client\n      .getInstance()\n      .continuousProfilingGroupProfilesGet(\n        { beginTime, endTime },\n        { headers: this.headers, ...options }\n      )\n  }\n\n  continuousProfilingSingleProfileViewGet(\n    address?: string,\n    component?: string,\n    profileType?: string,\n    ts?: number,\n    options?: ReqConfig\n  ) {\n    return client\n      .getInstance()\n      .continuousProfilingSingleProfileViewGet(\n        { address, component, profileType, ts },\n        { headers: this.headers, ...options }\n      )\n  }\n\n  getTiDBTopology(options?: ReqConfig) {\n    return client\n      .getInstance()\n      .getTiDBTopology({ headers: this.headers, ...options })\n  }\n  getStoreTopology(options?: ReqConfig) {\n    return client\n      .getInstance()\n      .getStoreTopology({ headers: this.headers, ...options })\n  }\n  getPDTopology(options?: ReqConfig) {\n    return client\n      .getInstance()\n      .getPDTopology({ headers: this.headers, ...options })\n  }\n}\n\nexport const ctx: (\n  cfg: Partial<IConProfilingConfig>\n) => IConProfilingContext = (cfg) => ({\n  ds: new DataSource(cfg),\n  cfg: {\n    apiPathBase: client.getBasePath(),\n    publicPathBase,\n    ...cfg\n  }\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/ContinuousProfiling/index.tsx",
    "content": "import React from 'react'\nimport {\n  ConProfilingApp,\n  ConProfilingProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\nimport { getGlobalConfig } from '~/utils/globalConfig'\n\nexport default function () {\n  return (\n    <ConProfilingProvider\n      value={ctx(getGlobalConfig().appsConfig?.conProf || {})}\n    >\n      <ConProfilingApp />\n    </ConProfilingProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/ContinuousProfiling/meta.ts",
    "content": "import { AimOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'conprof',\n  routerPrefix: '/continuous_profiling',\n  icon: AimOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Deadlock/context.ts",
    "content": "import {\n  IDeadlockDataSource,\n  IDeadlockContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client from '~/client'\n\nclass DataSource implements IDeadlockDataSource {\n  deadlockListGet(options?: ReqConfig) {\n    return client.getInstance().deadlockListGet(options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IDeadlockContext = {\n  ds\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Deadlock/index.tsx",
    "content": "import React from 'react'\nimport { DeadlockApp, DeadlockProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <DeadlockProvider value={ctx}>\n      <DeadlockApp />\n    </DeadlockProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Deadlock/meta.ts",
    "content": "import { SyncOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'deadlock',\n  routerPrefix: '/deadlock',\n  icon: SyncOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/DebugAPI/context.ts",
    "content": "import {\n  IDebugAPIDataSource,\n  IDebugAPIContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { EndpointRequestPayload } from '~/client'\n\nclass DataSource implements IDebugAPIDataSource {\n  debugAPIGetEndpoints(options?: ReqConfig) {\n    return client.getInstance().debugAPIGetEndpoints(options)\n  }\n\n  debugAPIRequestEndpoint(req: EndpointRequestPayload, options?: ReqConfig) {\n    return client.getInstance().debugAPIRequestEndpoint(\n      {\n        req: {\n          ...req,\n          // To compatible with the old tidb-dashboard backend api before 5.4.0\n          // By PR https://github.com/pingcap/tidb-dashboard/pull/1103 (release to v2021.12.30.1 and PD 5.4.0)\n          // It changes `id` to `api_id`, `params` to `param_values`\n          id: req.api_id,\n          params: req.param_values\n        } as any\n      },\n      options\n    )\n  }\n\n  infoListDatabases(options?: ReqConfig) {\n    return client.getInstance().infoListDatabases(options)\n  }\n\n  infoListTables(databaseName?: string, options?: ReqConfig) {\n    return client.getInstance().infoListTables({ databaseName }, options)\n  }\n\n  getTiDBTopology(options?: ReqConfig) {\n    return client.getInstance().getTiDBTopology(options)\n  }\n\n  getStoreTopology(options?: ReqConfig) {\n    return client.getInstance().getStoreTopology(options)\n  }\n\n  getPDTopology(options?: ReqConfig) {\n    return client.getInstance().getPDTopology(options)\n  }\n\n  getTiProxyTopology(options?: ReqConfig) {\n    return client.getInstance().getTiProxyTopology(options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IDebugAPIContext = {\n  ds,\n  cfg: { apiPathBase: client.getBasePath() }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/DebugAPI/index.tsx",
    "content": "import React from 'react'\nimport { DebugAPIApp, DebugAPIProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <DebugAPIProvider value={ctx}>\n      <DebugAPIApp />\n    </DebugAPIProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/DebugAPI/meta.ts",
    "content": "import { ApiOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'debug_api',\n  routerPrefix: '/debug_api',\n  icon: ApiOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Diagnose/context.ts",
    "content": "import {\n  IDiagnoseDataSource,\n  IDiagnoseContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { DiagnoseGenDiagnosisReportRequest } from '~/client'\n\nclass DataSource implements IDiagnoseDataSource {\n  diagnoseDiagnosisPost(\n    request: DiagnoseGenDiagnosisReportRequest,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().diagnoseDiagnosisPost({ request }, options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IDiagnoseContext = {\n  ds\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Diagnose/index.tsx",
    "content": "import React from 'react'\nimport { DiagnoseApp, DiagnoseProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <DiagnoseProvider value={ctx}>\n      <DiagnoseApp />\n    </DiagnoseProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Diagnose/meta.ts",
    "content": "import { SafetyCertificateOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'diagnose',\n  routerPrefix: '/diagnose',\n  icon: SafetyCertificateOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/InstanceProfiling/context.ts",
    "content": "import {\n  IInstanceProfilingDataSource,\n  IInstanceProfilingContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { ProfilingStartRequest } from '~/client'\n\nimport publicPathBase from '~/utils/publicPathPrefix'\n\nclass DataSource implements IInstanceProfilingDataSource {\n  getActionToken(id?: string, action?: string, options?: ReqConfig) {\n    return client.getInstance().getActionToken({ id, action }, options)\n  }\n  getProfilingGroupDetail(groupId: string, options?: ReqConfig) {\n    return client.getInstance().getProfilingGroupDetail({ groupId }, options)\n  }\n  getProfilingGroups(options?: ReqConfig) {\n    return client.getInstance().getProfilingGroups(options)\n  }\n  startProfiling(req: ProfilingStartRequest, options?: ReqConfig) {\n    return client.getInstance().startProfiling({ req }, options)\n  }\n  continuousProfilingConfigGet(options?: ReqConfig) {\n    return client.getInstance().continuousProfilingConfigGet(options)\n  }\n\n  getTiDBTopology(options?: ReqConfig) {\n    return client.getInstance().getTiDBTopology(options)\n  }\n  getStoreTopology(options?: ReqConfig) {\n    return client.getInstance().getStoreTopology(options)\n  }\n  getPDTopology(options?: ReqConfig) {\n    return client.getInstance().getPDTopology(options)\n  }\n  getTiCDCTopology(options?: ReqConfig) {\n    return client.getInstance().getTiCDCTopology(options)\n  }\n  getTiProxyTopology(options?: ReqConfig) {\n    return client.getInstance().getTiProxyTopology(options)\n  }\n  getTSOTopology(options?: ReqConfig) {\n    return client.getInstance().getTSOTopology(options)\n  }\n  getSchedulingTopology(options?: ReqConfig) {\n    return client.getInstance().getSchedulingTopology(options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IInstanceProfilingContext = {\n  ds,\n  cfg: { apiPathBase: client.getBasePath(), publicPathBase }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/InstanceProfiling/index.tsx",
    "content": "import React from 'react'\nimport {\n  InstanceProfilingApp,\n  InstanceProfilingProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <InstanceProfilingProvider value={ctx}>\n      <InstanceProfilingApp />\n    </InstanceProfilingProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/InstanceProfiling/meta.ts",
    "content": "import { AimOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'instance_profiling',\n  routerPrefix: '/instance_profiling',\n  icon: AimOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/KeyViz/context.ts",
    "content": "import {\n  IKeyVizDataSource,\n  IKeyVizContext,\n  ReqConfig,\n  IKeyVizConfig\n} from '@pingcap/tidb-dashboard-lib'\nimport client, { ConfigKeyVisualConfig } from '~/client'\n\nclass DataSource implements IKeyVizDataSource {\n  keyvisualConfigGet(options?: ReqConfig) {\n    return client.getInstance().keyvisualConfigGet(options)\n  }\n\n  keyvisualConfigPut(request: ConfigKeyVisualConfig, options?: ReqConfig) {\n    return client.getInstance().keyvisualConfigPut({ request }, options)\n  }\n  keyvisualHeatmapsGet(\n    startkey?: string,\n    endkey?: string,\n    starttime?: number,\n    endtime?: number,\n    type?:\n      | 'written_bytes'\n      | 'read_bytes'\n      | 'written_keys'\n      | 'read_keys'\n      | 'integration',\n    options?: ReqConfig\n  ) {\n    return client.getInstance().keyvisualHeatmapsGet(\n      {\n        startkey,\n        endkey,\n        starttime,\n        type\n      },\n      options\n    )\n  }\n}\n\nconst ds = new DataSource()\n\nexport function ctx(cfg: Partial<IKeyVizConfig>): IKeyVizContext {\n  return {\n    ds,\n    cfg: {\n      showHelp: true,\n      showSetting: true,\n      ...cfg\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/KeyViz/index.tsx",
    "content": "import React from 'react'\nimport { KeyVizApp, KeyVizProvider } from '@pingcap/tidb-dashboard-lib'\nimport { getGlobalConfig } from '~/utils/globalConfig'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <KeyVizProvider value={ctx(getGlobalConfig().appsConfig?.keyViz || {})}>\n      <KeyVizApp />\n    </KeyVizProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/KeyViz/meta.ts",
    "content": "import { EyeOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'keyviz',\n  routerPrefix: '/keyviz',\n  icon: EyeOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Monitoring/context.ts",
    "content": "import {\n  IMonitoringDataSource,\n  IMonitoringContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client from '~/client'\n\nimport { getMonitoringItems } from './metricsQueries'\n\nclass DataSource implements IMonitoringDataSource {\n  metricsQueryGet(params: {\n    endTimeSec?: number\n    query?: string\n    startTimeSec?: number\n    stepSec?: number\n  }) {\n    return client\n      .getInstance()\n      .metricsQueryGet(params, {\n        handleError: 'custom'\n      } as ReqConfig)\n      .then((res) => res.data)\n  }\n}\n\nconst RECENT_SECONDS = [\n  5 * 60,\n  15 * 60,\n  30 * 60,\n  60 * 60,\n  3 * 60 * 60,\n  6 * 60 * 60,\n  12 * 60 * 60,\n  24 * 60 * 60,\n  2 * 24 * 60 * 60\n]\n\nconst ds = new DataSource()\n\nexport const ctx: IMonitoringContext = {\n  ds,\n  cfg: {\n    getMetricsQueries: (pdVersion: string | undefined) =>\n      getMonitoringItems(pdVersion),\n    timeRangeSelector: {\n      recent_seconds: RECENT_SECONDS,\n      customAbsoluteRangePicker: true\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Monitoring/index.tsx",
    "content": "import React from 'react'\nimport { MonitoringApp, MonitoringProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <MonitoringProvider value={ctx}>\n      <MonitoringApp />\n    </MonitoringProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Monitoring/meta.ts",
    "content": "import { LineChartOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'monitoring',\n  routerPrefix: '/monitoring',\n  icon: LineChartOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Monitoring/metricsQueries.ts",
    "content": "import {\n  ColorType,\n  TransformNullValue,\n  MetricsQueryType\n} from '@pingcap/tidb-dashboard-lib'\n\nimport { compare } from 'compare-versions'\n\nfunction transformColorBySQLType(legendLabel: string) {\n  switch (legendLabel) {\n    case 'Select':\n      return ColorType.BLUE_3\n    case 'Commit':\n      return ColorType.GREEN_2\n    case 'Insert':\n      return ColorType.GREEN_3\n    case 'Update':\n      return ColorType.GREEN_4\n    case 'general':\n      return ColorType.PINK\n    default:\n      return undefined\n  }\n}\n\nfunction transformColorByExecTimeOverview(legendLabel: string) {\n  switch (legendLabel) {\n    case 'tso_wait':\n      return ColorType.RED_5\n    case 'Commit':\n      return ColorType.GREEN_4\n    case 'Prewrite':\n      return ColorType.GREEN_3\n    case 'PessimisticLock':\n      return ColorType.RED_4\n    case 'Get':\n      return ColorType.BLUE_3\n    case 'BatchGet':\n      return ColorType.BLUE_4\n    case 'Cop':\n      return ColorType.BLUE_1\n    case 'ScanLock':\n    case 'Scan':\n      return ColorType.PURPLE\n    case 'execute time':\n      return ColorType.YELLOW\n    default:\n      return undefined\n  }\n}\n\nconst getMonitoringItems = (\n  pdVersion: string | undefined\n): MetricsQueryType[] => {\n  function loadTiKVStoragePromql() {\n    const PDVersion = pdVersion?.replace('v', '')\n\n    if (PDVersion && PDVersion !== 'N/A' && compare(PDVersion, '5.4.1', '<')) {\n      return 'sum(tikv_engine_size_bytes) by (instance)'\n    }\n    return 'sum(tikv_store_size_bytes{type=\"used\"}) by (instance)'\n  }\n\n  const monitoringItems: MetricsQueryType[] = [\n    {\n      category: 'database_time',\n      metrics: [\n        {\n          title: 'Database Time by SQL Types',\n          queries: [\n            {\n              promql: `sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval]))`,\n              name: 'database time',\n              color: ColorType.YELLOW,\n              type: 'line'\n            },\n            {\n              promql: `sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval])) by (sql_type)`,\n              name: '{sql_type}',\n              color: (seriesName: string) =>\n                transformColorBySQLType(seriesName),\n              type: 'bar_stacked'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Database Time by SQL Phase',\n          queries: [\n            {\n              promql: `sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval]))`,\n              name: 'database time',\n              color: ColorType.YELLOW,\n              type: 'line'\n            },\n            {\n              promql: `sum(rate(tidb_session_parse_duration_seconds_sum{sql_type=\"general\"}[$__rate_interval]))`,\n              name: 'parse',\n              color: ColorType.RED_2,\n              type: 'bar_stacked'\n            },\n            {\n              promql: `sum(rate(tidb_session_compile_duration_seconds_sum{sql_type=\"general\"}[$__rate_interval]))`,\n              name: 'compile',\n              color: ColorType.ORANGE,\n              type: 'bar_stacked'\n            },\n            {\n              promql: `sum(rate(tidb_session_execute_duration_seconds_sum{sql_type=\"general\"}[$__rate_interval]))`,\n              name: 'execute',\n              color: ColorType.GREEN_3,\n              type: 'bar_stacked'\n            },\n            {\n              promql: `sum(rate(tidb_server_get_token_duration_seconds_sum[$__rate_interval]))/1000000`,\n              name: 'get token',\n              color: ColorType.RED_3,\n              type: 'bar_stacked'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'SQL Execute Time Overview',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_tikvclient_request_seconds_sum{store!=\"0\"}[$__rate_interval])) by (type)',\n              name: '{type}',\n              color: (seriesName: string) =>\n                transformColorByExecTimeOverview(seriesName),\n              type: 'bar_stacked'\n            },\n            {\n              promql:\n                'sum(rate(pd_client_cmd_handle_cmds_duration_seconds_sum{type=\"wait\"}[$__rate_interval]))',\n              name: 'tso_wait',\n              color: ColorType.RED_5,\n              type: 'bar_stacked'\n            }\n          ],\n          unit: 's'\n        }\n      ]\n    },\n    {\n      category: 'application_connection',\n      metrics: [\n        {\n          title: 'Connection Count',\n          queries: [\n            {\n              promql: 'sum(tidb_server_connections)',\n              name: 'Total',\n              type: 'line'\n            },\n            {\n              promql: 'sum(tidb_server_tokens)',\n              name: 'active connections',\n              type: 'line'\n            }\n          ],\n          unit: 'short',\n          nullValue: TransformNullValue.AS_ZERO\n        },\n        {\n          title: 'Disconnection',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_server_disconnection_total[$__rate_interval])) by (instance, result)',\n              name: '{instance}-{result}',\n              type: 'area_stack'\n            }\n          ],\n          unit: 'short',\n          nullValue: TransformNullValue.AS_ZERO\n        }\n      ]\n    },\n    {\n      category: 'sql_count',\n      metrics: [\n        {\n          title: 'Query Per Second',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_executor_statement_total[$__rate_interval]))',\n              name: 'Total',\n              type: 'line'\n            },\n            {\n              promql:\n                'sum(rate(tidb_executor_statement_total[$__rate_interval])) by (type)',\n              name: '{type}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'short'\n        },\n        {\n          title: 'Failed Queries',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_server_execute_error_total[$__rate_interval]))',\n              name: '{type} @ {instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'short'\n        },\n        {\n          title: 'Command Per Second',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_server_query_total[$__rate_interval])) by (type)',\n              name: '{type}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'short'\n        },\n        {\n          title: 'Queries Using Plan Cache OPS',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_server_plan_cache_total[$__rate_interval]))',\n              name: 'avg - hit',\n              type: 'line'\n            },\n            {\n              promql:\n                'sum(rate(tidb_server_plan_cache_miss_total[$__rate_interval]))',\n              name: 'avg - miss',\n              type: 'line'\n            }\n          ],\n          unit: 'short',\n          nullValue: TransformNullValue.AS_ZERO\n        }\n      ]\n    },\n    {\n      category: 'latency_break_down',\n      metrics: [\n        {\n          title: 'Query Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval])) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\"internal\"}[$__rate_interval]))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket{sql_type!=\"internal\"}[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            },\n            {\n              promql:\n                'sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval])) by (sql_type) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\"internal\"}[$__rate_interval])) by (sql_type)',\n              name: 'avg-{sql_type}',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket[$__rate_interval])) by (le,sql_type))',\n              name: '99-{sql_type}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average Idle Connection Duration',\n          queries: [\n            {\n              promql: `(sum(rate(tidb_server_conn_idle_duration_seconds_sum{in_txn='1'}[$__rate_interval])) / sum(rate(tidb_server_conn_idle_duration_seconds_count{in_txn='1'}[$__rate_interval])))`,\n              name: 'avg-in-txn',\n              type: 'line'\n            },\n            {\n              promql: `(sum(rate(tidb_server_conn_idle_duration_seconds_sum{in_txn='0'}[$__rate_interval])) / sum(rate(tidb_server_conn_idle_duration_seconds_count{in_txn='0'}[$__rate_interval])))`,\n              name: 'avg-not-in-txn',\n              type: 'line'\n            }\n          ],\n          unit: 's',\n          nullValue: TransformNullValue.AS_ZERO\n        },\n        {\n          title: 'Get Token Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_server_get_token_duration_seconds_sum[$__rate_interval])) / sum(rate(tidb_server_get_token_duration_seconds_count[$__rate_interval]))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_server_get_token_duration_seconds_bucket[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'µs'\n        },\n        {\n          title: 'Parse Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tidb_session_parse_duration_seconds_sum{sql_type=\"general\"}[$__rate_interval])) / sum(rate(tidb_session_parse_duration_seconds_count{sql_type=\"general\"}[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_session_parse_duration_seconds_bucket{sql_type=\"general\"}[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Compile Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tidb_session_compile_duration_seconds_sum{sql_type=\"general\"}[$__rate_interval])) / sum(rate(tidb_session_compile_duration_seconds_count{sql_type=\"general\"}[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_session_compile_duration_seconds_bucket{sql_type=\"general\"}[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Execute Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tidb_session_execute_duration_seconds_sum{sql_type=\"general\"}[$__rate_interval])) / sum(rate(tidb_session_execute_duration_seconds_count{sql_type=\"general\"}[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_session_execute_duration_seconds_bucket{sql_type=\"general\"}[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        }\n      ]\n    },\n    {\n      category: 'transaction',\n      metrics: [\n        {\n          title: 'Transaction Per Second',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_session_transaction_duration_seconds_count{scope=~\"general\"}[$__rate_interval])) by (type, txn_mode)',\n              name: '{type}-{txn_mode}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'short'\n        },\n        {\n          title: 'Transaction Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_session_transaction_duration_seconds_sum{scope=~\"general\"}[$__rate_interval])) by (txn_mode)/ sum(rate(tidb_session_transaction_duration_seconds_count{scope=~\"general\"}[$__rate_interval])) by (txn_mode)',\n              name: 'avg-{txn_mode}',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_session_transaction_duration_seconds_bucket[$__rate_interval])) by (le, txn_mode))',\n              name: '99-{txn_mode}',\n              type: 'line'\n            }\n          ],\n          unit: 's'\n        }\n      ]\n    },\n    {\n      category: 'core_path_duration',\n      metrics: [\n        {\n          title: 'Avg TiDB KV Request Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_tikvclient_request_seconds_sum{store!=\"0\"}[$__rate_interval])) by (type)/ sum(rate(tidb_tikvclient_request_seconds_count{store!=\"0\"}[$__rate_interval])) by (type)',\n              name: '{type}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Avg TiKV GRPC Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tikv_grpc_msg_duration_seconds_sum{store!=\"0\"}[$__rate_interval])) by (type)/ sum(rate(tikv_grpc_msg_duration_seconds_count{store!=\"0\"}[$__rate_interval])) by (type)',\n              name: '{type}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 PD TSO Wait/RPC Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(pd_client_cmd_handle_cmds_duration_seconds_sum{type=\"wait\"}[$__rate_interval])) / sum(rate(pd_client_cmd_handle_cmds_duration_seconds_count{type=\"wait\"}[$__rate_interval])))',\n              name: 'wait - avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(pd_client_cmd_handle_cmds_duration_seconds_bucket{type=\"wait\"}[$__rate_interval])) by (le))',\n              name: 'wait - 99',\n              type: 'line'\n            },\n            {\n              promql:\n                '(sum(rate(pd_client_request_handle_requests_duration_seconds_sum{type=\"tso\"}[$__rate_interval])) / sum(rate(pd_client_request_handle_requests_duration_seconds_count{type=\"tso\"}[$__rate_interval])))',\n              name: 'rpc - avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(pd_client_request_handle_requests_duration_seconds_bucket{type=\"tso\"}[$__rate_interval])) by (le))',\n              name: 'rpc - 99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 Storage Async Write Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tikv_storage_engine_async_request_duration_seconds_sum{type=\"write\"}[$__rate_interval])) / sum(rate(tikv_storage_engine_async_request_duration_seconds_count{type=\"write\"}[$__rate_interval]))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tikv_storage_engine_async_request_duration_seconds_bucket{type=\"write\"}[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 Store Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tikv_raftstore_store_duration_secs_sum[$__rate_interval])) / sum(rate(tikv_raftstore_store_duration_secs_count[$__rate_interval]))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tikv_raftstore_store_duration_secs_bucket[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 Apply Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tikv_raftstore_apply_duration_secs_sum[$__rate_interval])) / sum(rate(tikv_raftstore_apply_duration_secs_count[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tikv_raftstore_apply_duration_secs_bucket[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 Append Log Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tikv_raftstore_append_log_duration_seconds_sum[$__rate_interval])) / sum(rate(tikv_raftstore_append_log_duration_seconds_count[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tikv_raftstore_append_log_duration_seconds_bucket[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 Commit Log Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tikv_raftstore_commit_log_duration_seconds_sum[$__rate_interval])) / sum(rate(tikv_raftstore_commit_log_duration_seconds_count[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tikv_raftstore_commit_log_duration_seconds_bucket[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 Apply Log Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tikv_raftstore_apply_log_duration_seconds_sum[$__rate_interval])) / sum(rate(tikv_raftstore_apply_log_duration_seconds_count[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tikv_raftstore_apply_log_duration_seconds_bucket[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        }\n      ]\n    },\n    {\n      category: 'server',\n      metrics: [\n        {\n          title: 'TiDB Uptime',\n          queries: [\n            {\n              promql: '(time() - process_start_time_seconds{component=\"tidb\"})',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'TiDB CPU Usage',\n          queries: [\n            {\n              promql:\n                'irate(process_cpu_seconds_total{component=\"tidb\"}[$__rate_interval])',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'percentunit'\n        },\n        {\n          title: 'TiDB Memory Usage',\n          queries: [\n            {\n              promql: 'process_resident_memory_bytes{component=\"tidb\"}',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'bytes'\n        },\n        {\n          title: 'TiKV Uptime',\n          queries: [\n            {\n              promql: '(time() - process_start_time_seconds{component=\"tikv\"})',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          unit: 's'\n        },\n        {\n          title: 'TiKV CPU Usage',\n          queries: [\n            {\n              promql:\n                'sum(rate(tikv_thread_cpu_seconds_total[$__rate_interval])) by (instance)',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          unit: 'percentunit'\n        },\n        {\n          title: 'TiKV Memory Usage',\n          queries: [\n            {\n              promql:\n                'avg(process_resident_memory_bytes{component=\"tikv\"}) by (instance)',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          unit: 'bytes'\n        },\n        {\n          title: 'TiKV IO MBps',\n          queries: [\n            {\n              promql:\n                'sum(rate(tikv_engine_flow_bytes{db=\"kv\", type=\"wal_file_bytes\"}[$__rate_interval])) by (instance) + (sum(rate(tikv_engine_flow_bytes{db=\"raft\", type=\"wal_file_bytes\"}[$__rate_interval])) by (instance) or (0 * sum(rate(raft_engine_write_size_sum[$__rate_interval])) by (instance))) + (sum(rate(raft_engine_write_size_sum[$__rate_interval])) by (instance) or (0 * sum(rate(tikv_engine_flow_bytes{db=\"raft\", type=\"wal_file_bytes\"}[$__rate_interval])) by (instance)))',\n              name: '{instance}-write',\n              type: 'line'\n            },\n            {\n              promql:\n                'sum(rate(tikv_engine_flow_bytes{db=\"kv\", type=~\"bytes_read|iter_bytes_read\"}[$__rate_interval])) by (instance)',\n              name: '{instance}-read',\n              type: 'line'\n            }\n          ],\n          unit: 'Bps'\n        },\n        {\n          title: 'TiKV Storage Usage',\n          queries: [\n            {\n              promql: loadTiKVStoragePromql(),\n              name: '{instance}',\n              type: 'area_stack'\n            }\n          ],\n          unit: 'bytes'\n        },\n        {\n          title: 'TiFlash Uptime',\n          queries: [\n            {\n              promql: 'tiflash_system_asynchronous_metric_Uptime',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'TiFlash CPU Usage',\n          queries: [\n            {\n              promql:\n                'rate(tiflash_proxy_process_cpu_seconds_total{component=\"tiflash\"}[$__rate_interval])',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'percentunit'\n        },\n        {\n          title: 'TiFlash Memory',\n          queries: [\n            {\n              promql:\n                'tiflash_proxy_process_resident_memory_bytes{component=\"tiflash\"}',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'bytes'\n        },\n        {\n          title: 'TiFlash IO MBps',\n          queries: [\n            {\n              promql:\n                'sum(rate(tiflash_system_profile_event_WriteBufferFromFileDescriptorWriteBytes[$__rate_interval])) by (instance) + sum(rate(tiflash_system_profile_event_PSMWriteBytes[$__rate_interval])) by (instance) + sum(rate(tiflash_system_profile_event_WriteBufferAIOWriteBytes[$__rate_interval])) by (instance)',\n              name: '{instance}-write',\n              type: 'line'\n            },\n            {\n              promql:\n                'sum(rate(tiflash_system_profile_event_ReadBufferFromFileDescriptorReadBytes[$__rate_interval])) by (instance) + sum(rate(tiflash_system_profile_event_PSMReadBytes[$__rate_interval])) by (instance) + sum(rate(tiflash_system_profile_event_ReadBufferAIOReadBytes[$__rate_interval])) by (instance)',\n              name: '{instance}-read',\n              type: 'line'\n            }\n          ],\n          unit: 'Bps'\n        },\n        {\n          title: 'TiFlash Storage Usage',\n          queries: [\n            {\n              promql:\n                'sum(tiflash_system_current_metric_StoreSizeUsed) by (instance)',\n              name: '{instance}',\n              type: 'area_stack'\n            }\n          ],\n          unit: 'bytes'\n        }\n      ]\n    }\n  ]\n\n  return monitoringItems\n}\n\nexport { getMonitoringItems }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/OptimizerTrace/context.ts",
    "content": "import {\n  IOptimizerTraceDataSource,\n  IOptimizerTraceContext\n  // ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\n// import client from '~/client'\n\nclass DataSource implements IOptimizerTraceDataSource {}\n\nconst ds = new DataSource()\n\nexport const ctx: IOptimizerTraceContext = {\n  ds\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/OptimizerTrace/index.tsx",
    "content": "import React from 'react'\nimport {\n  OptimizerTraceApp,\n  OptimizerTraceProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <OptimizerTraceProvider value={ctx}>\n      <OptimizerTraceApp />\n    </OptimizerTraceProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/OptimizerTrace/meta.ts",
    "content": "export default {\n  id: 'optimizer_trace',\n  routerPrefix: '/optimizer_trace',\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Overview/context.ts",
    "content": "import {\n  IOverviewDataSource,\n  IOverviewContext,\n  IOverviewConfig,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client from '~/client'\nimport { overviewMetrics } from './metricsQueries'\n\nclass DataSource implements IOverviewDataSource {\n  getTiDBTopology(options?: ReqConfig) {\n    return client.getInstance().getTiDBTopology(options)\n  }\n\n  getStoreTopology(options?: ReqConfig) {\n    return client.getInstance().getStoreTopology(options)\n  }\n\n  getPDTopology(options?: ReqConfig) {\n    return client.getInstance().getPDTopology(options)\n  }\n\n  getTiCDCTopology(options?: ReqConfig) {\n    return client.getInstance().getTiCDCTopology(options)\n  }\n\n  getTiProxyTopology(options?: ReqConfig) {\n    return client.getInstance().getTiProxyTopology(options)\n  }\n\n  getTSOTopology(options?: ReqConfig) {\n    return client.getInstance().getTSOTopology(options)\n  }\n\n  getSchedulingTopology(options?: ReqConfig) {\n    return client.getInstance().getSchedulingTopology(options)\n  }\n\n  metricsQueryGet(params: {\n    endTimeSec?: number\n    query?: string\n    startTimeSec?: number\n    stepSec?: number\n  }) {\n    return client\n      .getInstance()\n      .metricsQueryGet(params, {\n        handleError: 'custom'\n      } as ReqConfig)\n      .then((res) => res.data)\n  }\n\n  getGrafanaTopology(options?: ReqConfig) {\n    return client.getInstance().getGrafanaTopology(options)\n  }\n\n  getAlertManagerTopology(options?: ReqConfig) {\n    return client.getInstance().getAlertManagerTopology(options)\n  }\n\n  getAlertManagerCounts(address: string, options?: ReqConfig) {\n    return client.getInstance().getAlertManagerCounts({ address }, options)\n  }\n}\n\nconst ds = new DataSource()\n\nconst RECENT_SECONDS = [\n  5 * 60,\n  15 * 60,\n  30 * 60,\n  60 * 60,\n  3 * 60 * 60,\n  6 * 60 * 60,\n  12 * 60 * 60,\n  24 * 60 * 60,\n  2 * 24 * 60 * 60\n]\n\nexport const ctx: (cfg: Partial<IOverviewConfig>) => IOverviewContext = (\n  cfg\n) => ({\n  ds,\n  cfg: {\n    apiPathBase: client.getBasePath(),\n    metricsQueries: overviewMetrics,\n    timeRangeSelector: {\n      recent_seconds: RECENT_SECONDS,\n      customAbsoluteRangePicker: true\n    },\n    showMetrics: false,\n    ...cfg\n  }\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Overview/index.tsx",
    "content": "import React from 'react'\nimport { OverviewApp, OverviewProvider } from '@pingcap/tidb-dashboard-lib'\nimport { getGlobalConfig } from '~/utils/globalConfig'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <OverviewProvider value={ctx(getGlobalConfig().appsConfig?.overview || {})}>\n      <OverviewApp />\n    </OverviewProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Overview/meta.ts",
    "content": "import { AppstoreOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'overview',\n  routerPrefix: '/overview',\n  icon: AppstoreOutlined,\n  isDefaultRouter: true,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Overview/metricsQueries.ts",
    "content": "import {\n  TransformNullValue,\n  OverviewMetricsQueryType\n} from '@pingcap/tidb-dashboard-lib'\n\nconst overviewMetrics: OverviewMetricsQueryType[] = [\n  {\n    title: 'total_requests',\n    queries: [\n      {\n        promql: 'sum(rate(tidb_executor_statement_total[$__rate_interval]))',\n        name: 'Total',\n        type: 'line'\n      },\n      {\n        promql:\n          'sum(rate(tidb_executor_statement_total[$__rate_interval])) by (type)',\n        name: '{type}',\n        type: 'line'\n      }\n    ],\n    nullValue: TransformNullValue.AS_ZERO,\n    unit: 'short'\n  },\n  {\n    title: 'latency',\n    queries: [\n      {\n        promql:\n          'sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval])) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\"internal\"}[$__rate_interval]))',\n        name: 'avg',\n        type: 'line'\n      },\n      {\n        promql:\n          'histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket{sql_type!=\"internal\"}[$__rate_interval])) by (le))',\n        name: '99',\n        type: 'line'\n      },\n      {\n        promql:\n          'sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval])) by (sql_type) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\"internal\"}[$__rate_interval])) by (sql_type)',\n        name: 'avg-{sql_type}',\n        type: 'line'\n      },\n      {\n        promql:\n          'histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket{sql_type!=\"internal\"}[$__rate_interval])) by (le,sql_type))',\n        name: '99-{sql_type}',\n        type: 'line'\n      }\n    ],\n    nullValue: TransformNullValue.AS_ZERO,\n    unit: 's'\n  },\n  {\n    title: 'cpu',\n    queries: [\n      {\n        promql:\n          'irate(process_cpu_seconds_total{component=\"tidb\"}[$__rate_interval])',\n        name: '{instance}',\n        type: 'line'\n      }\n    ],\n    nullValue: TransformNullValue.AS_ZERO,\n    unit: 'percentunit'\n  },\n  {\n    title: 'memory',\n    queries: [\n      {\n        promql: 'process_resident_memory_bytes{component=\"tidb\"}',\n        name: '{instance}',\n        type: 'line'\n      }\n    ],\n    nullValue: TransformNullValue.AS_ZERO,\n    unit: 'bytes'\n  },\n  {\n    title: 'io',\n    queries: [\n      {\n        promql:\n          'sum(rate(tikv_engine_flow_bytes{db=\"kv\", type=\"wal_file_bytes\"}[$__rate_interval])) by (instance) + (sum(rate(tikv_engine_flow_bytes{db=\"raft\", type=\"wal_file_bytes\"}[$__rate_interval])) by (instance) or (0 * sum(rate(raft_engine_write_size_sum[$__rate_interval])) by (instance))) + (sum(rate(raft_engine_write_size_sum[$__rate_interval])) by (instance) or (0 * sum(rate(tikv_engine_flow_bytes{db=\"raft\", type=\"wal_file_bytes\"}[$__rate_interval])) by (instance)))',\n        name: '{instance}-write',\n        type: 'line'\n      },\n      {\n        promql:\n          'sum(rate(tikv_engine_flow_bytes{db=\"kv\", type=~\"bytes_read|iter_bytes_read\"}[$__rate_interval])) by (instance)',\n        name: '{instance}-read',\n        type: 'line'\n      }\n    ],\n    unit: 'Bps'\n  }\n]\n\nexport { overviewMetrics }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/QueryEditor/context.ts",
    "content": "import {\n  IQueryEditorDataSource,\n  IQueryEditorContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { QueryeditorRunRequest } from '~/client'\n\nclass DataSource implements IQueryEditorDataSource {\n  queryEditorRun(request: QueryeditorRunRequest, options?: ReqConfig) {\n    return client.getInstance().queryEditorRun({ request }, options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IQueryEditorContext = {\n  ds\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/QueryEditor/index.tsx",
    "content": "import React from 'react'\nimport {\n  QueryEditorApp,\n  QueryEditorProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <QueryEditorProvider value={ctx}>\n      <QueryEditorApp />\n    </QueryEditorProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/QueryEditor/meta.ts",
    "content": "import { ConsoleSqlOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'query_editor',\n  routerPrefix: '/query_editor',\n  icon: ConsoleSqlOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/ResourceManager/context-impl.ts",
    "content": "import {\n  IResourceManagerDataSource,\n  IResourceManagerContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\nimport { AxiosPromise } from 'axios'\n\nimport client, {\n  ResourcemanagerCalibrateResponse,\n  ResourcemanagerGetConfigResponse,\n  ResourcemanagerResourceInfoRowDef\n} from '~/client'\n\nclass DataSource implements IResourceManagerDataSource {\n  getConfig(\n    options?: ReqConfig\n  ): AxiosPromise<ResourcemanagerGetConfigResponse> {\n    return client.getInstance().resourceManagerConfigGet(options)\n  }\n  getInformation(\n    options?: ReqConfig\n  ): AxiosPromise<ResourcemanagerResourceInfoRowDef[]> {\n    return client.getInstance().resourceManagerInformationGet(options)\n  }\n\n  getCalibrateByHardware(\n    params: { workload: string },\n    options?: ReqConfig | undefined\n  ): AxiosPromise<ResourcemanagerCalibrateResponse> {\n    return client\n      .getInstance()\n      .resourceManagerCalibrateHardwareGet(params, options)\n  }\n  getCalibrateByActual(\n    params: { startTime: number; endTime: number },\n    options?: ReqConfig | undefined\n  ): AxiosPromise<ResourcemanagerCalibrateResponse> {\n    return client\n      .getInstance()\n      .resourceManagerCalibrateActualGet(params, options)\n  }\n\n  metricsQueryGet(params: {\n    endTimeSec?: number\n    query?: string\n    startTimeSec?: number\n    stepSec?: number\n  }) {\n    return client\n      .getInstance()\n      .metricsQueryGet(params, {\n        handleError: 'custom'\n      } as ReqConfig)\n      .then((res) => res.data)\n  }\n}\n\nexport const getResourceManagerContext: () => IResourceManagerContext = () => {\n  return {\n    ds: new DataSource(),\n    cfg: {}\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/ResourceManager/index.tsx",
    "content": "import React from 'react'\nimport {\n  ResourceManagerApp,\n  ResourceManagerProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { getResourceManagerContext } from './context-impl'\n\nexport default function () {\n  return (\n    <ResourceManagerProvider value={getResourceManagerContext()}>\n      <ResourceManagerApp />\n    </ResourceManagerProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/ResourceManager/meta.ts",
    "content": "import { HddOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'resource_manager',\n  routerPrefix: '/resource_manager',\n  icon: HddOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/SearchLogs/context.ts",
    "content": "import {\n  ISearchLogsDataSource,\n  ISearchLogsContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { LogsearchCreateTaskGroupRequest } from '~/client'\n\nclass DataSource implements ISearchLogsDataSource {\n  logsDownloadAcquireTokenGet(id?: Array<string>, options?: ReqConfig) {\n    return client.getInstance().logsDownloadAcquireTokenGet({ id }, options)\n  }\n\n  // logsDownloadGet(token: string, options?: ReqConfig) {\n  //   return client.getInstance().logsDownloadGet({ token }, options)\n  // }\n\n  logsTaskgroupPut(\n    request: LogsearchCreateTaskGroupRequest,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().logsTaskgroupPut({ request }, options)\n  }\n\n  logsTaskgroupsGet(options?: ReqConfig) {\n    return client.getInstance().logsTaskgroupsGet(options)\n  }\n\n  logsTaskgroupsIdCancelPost(id: string, options?: ReqConfig) {\n    return client.getInstance().logsTaskgroupsIdCancelPost({ id }, options)\n  }\n\n  logsTaskgroupsIdDelete(id: string, options?: ReqConfig) {\n    return client.getInstance().logsTaskgroupsIdDelete({ id }, options)\n  }\n\n  logsTaskgroupsIdGet(id: string, options?: ReqConfig) {\n    return client.getInstance().logsTaskgroupsIdGet({ id }, options)\n  }\n\n  logsTaskgroupsIdPreviewGet(id: string, options?: ReqConfig) {\n    return client.getInstance().logsTaskgroupsIdPreviewGet({ id }, options)\n  }\n\n  logsTaskgroupsIdRetryPost(id: string, options?: ReqConfig) {\n    return client.getInstance().logsTaskgroupsIdRetryPost({ id }, options)\n  }\n\n  getTiDBTopology(options?: ReqConfig) {\n    return client.getInstance().getTiDBTopology(options)\n  }\n  getStoreTopology(options?: ReqConfig) {\n    return client.getInstance().getStoreTopology(options)\n  }\n  getPDTopology(options?: ReqConfig) {\n    return client.getInstance().getPDTopology(options)\n  }\n  getTiCDCTopology(options?: ReqConfig) {\n    return client.getInstance().getTiCDCTopology(options)\n  }\n  getTiProxyTopology(options?: ReqConfig) {\n    return client.getInstance().getTiProxyTopology(options)\n  }\n  getTSOTopology(options?: ReqConfig) {\n    return client.getInstance().getTSOTopology(options)\n  }\n  getSchedulingTopology(options?: ReqConfig) {\n    return client.getInstance().getSchedulingTopology(options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: ISearchLogsContext = {\n  ds,\n  cfg: { apiPathBase: client.getBasePath() }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/SearchLogs/index.tsx",
    "content": "import React from 'react'\nimport { SearchLogsApp, SearchLogsProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <SearchLogsProvider value={ctx}>\n      <SearchLogsApp />\n    </SearchLogsProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/SearchLogs/meta.ts",
    "content": "import { FileSearchOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'search_logs',\n  routerPrefix: '/search_logs',\n  icon: FileSearchOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/SlowQuery/context.ts",
    "content": "import {\n  ISlowQueryDataSource,\n  ISlowQueryContext,\n  ISlowQueryConfig,\n  ISlowQueryEvent,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { SlowqueryModel } from '~/client'\n\nconst debugHeaders = {\n  // 'x-cluster-id': '1379661944646413143',\n  // 'x-org-id': '1372813089209061633',\n  // 'x-project-id': '1372813089454525346',\n  // 'x-provider': 'aws',\n  // 'x-region': 'us-east-1',\n  // 'x-env': 'prod'\n}\n\nclass DataSource implements ISlowQueryDataSource {\n  constructor(public cache: SlowqueryModel[]) {}\n\n  getDatabaseList(beginTime: number, endTime: number, options?: ReqConfig) {\n    // get database list from PD\n    if (beginTime === 0) {\n      return client.getInstance().infoListDatabases(options)\n    }\n\n    // get database list from s3\n    return client\n      .getAxiosInstance()\n      .get(\n        `/slow_query/databases?begin_time=${beginTime}&end_time=${endTime}`,\n        { headers: debugHeaders }\n      )\n  }\n\n  infoListResourceGroupNames(options?: ReqConfig) {\n    return client.getInstance().resourceManagerInformationGroupNamesGet(options)\n  }\n\n  slowQueryAvailableFieldsGet(options?: ReqConfig) {\n    return client.getInstance().slowQueryAvailableFieldsGet(options)\n  }\n\n  slowQueryListGet(\n    beginTime?: number,\n    db?: Array<string>,\n    desc?: boolean,\n    digest?: string,\n    endTime?: number,\n    fields?: string,\n    limit?: number,\n    orderBy?: string,\n    plans?: Array<string>,\n    resourceGroup?: Array<string>,\n    text?: string,\n    showInternal?: boolean,\n    options?: ReqConfig\n  ) {\n    const localVarQueryParameter = {} as any\n    if (beginTime !== undefined) {\n      localVarQueryParameter['begin_time'] = beginTime\n    }\n    if (db) {\n      localVarQueryParameter['db'] = db\n    }\n    if (desc !== undefined) {\n      localVarQueryParameter['desc'] = desc\n    }\n    if (digest !== undefined) {\n      localVarQueryParameter['digest'] = digest\n    }\n    if (endTime !== undefined) {\n      localVarQueryParameter['end_time'] = endTime\n    }\n    if (fields !== undefined) {\n      localVarQueryParameter['fields'] = fields\n    }\n    if (limit !== undefined) {\n      localVarQueryParameter['limit'] = limit\n    }\n    if (orderBy !== undefined) {\n      localVarQueryParameter['orderBy'] = orderBy\n    }\n    if (plans) {\n      localVarQueryParameter['plans'] = plans\n    }\n    if (resourceGroup) {\n      localVarQueryParameter['resource_group'] = resourceGroup\n    }\n    if (text !== undefined) {\n      localVarQueryParameter['text'] = text\n    }\n    if (showInternal !== undefined) {\n      localVarQueryParameter['show_internal'] = showInternal\n    }\n    const searchParams = new URLSearchParams()\n    for (const field in localVarQueryParameter) {\n      const value = localVarQueryParameter[field]\n      if (Array.isArray(value)) {\n        searchParams.delete(field)\n        for (const item of value) {\n          searchParams.append(field, item)\n        }\n      } else {\n        searchParams.set(field, value)\n      }\n    }\n    const searchString = searchParams.toString()\n\n    return client.getAxiosInstance().get(`/slow_query/list?${searchString}`)\n  }\n\n  slowQueryDetailGet(\n    connectId?: string,\n    digest?: string,\n    timestamp?: number,\n    options?: ReqConfig\n  ) {\n    // to make this.cache as small as possible\n    const cachedItem = this.cache.pop()\n    if (cachedItem) {\n      return Promise.resolve({\n        data: cachedItem,\n        status: 200,\n        statusText: 'ok',\n        headers: {},\n        config: {}\n      } as any)\n    } else {\n      return client.getInstance().slowQueryDetailGet(\n        {\n          connectId,\n          digest,\n          timestamp\n        },\n        options\n      )\n    }\n  }\n\n  slowQueryDownloadTokenPost(request: any, options?: ReqConfig) {\n    return client.getInstance().slowQueryDownloadTokenPost({ request }, options)\n  }\n\n  slowQueryAnalyze(start: number, end: number) {\n    return client\n      .getAxiosInstance()\n      .get(`/slow_query/analyze?begin_time=${start}&end_time=${end}`)\n  }\n\n  slowQueryDownloadDBFile(begin_time: number, end_time: number) {\n    return client\n      .getAxiosInstance()\n      .get(`/slow_query/files?begin_time=${begin_time}&end_time=${end_time}`, {\n        responseType: 'blob',\n        headers: {\n          Accept: 'application/octet-stream'\n        }\n      })\n  }\n\n  promqlQuery(query: string, time: number, timeout: string) {\n    return client\n      .getAxiosInstance()\n      .get(\n        `/slow_query/vm_query?query=${query}&time=${time}&timeout=${timeout}`\n      )\n      .then((res) => res.data)\n  }\n\n  promqlQueryRange(query: string, start: number, end: number, step: string) {\n    return client\n      .getAxiosInstance()\n      .get(\n        `/slow_query/vm_query_range?query=${query}&start=${start}&end=${end}&step=${step}`\n      )\n      .then((res) => res.data)\n  }\n}\n\nclass EventHandler implements ISlowQueryEvent {\n  constructor(\n    public listApiReturnDetail: boolean,\n    public cache: SlowqueryModel[]\n  ) {}\n\n  selectSlowQueryItem(item: any) {\n    if (this.listApiReturnDetail === true) {\n      this.cache.push(item)\n    }\n  }\n}\n\nexport const ctx: (cfg: Partial<ISlowQueryConfig>) => ISlowQueryContext = (\n  cfg\n) => {\n  const slowQueryCache: SlowqueryModel[] = []\n\n  return {\n    ds: new DataSource(slowQueryCache),\n    event: new EventHandler(cfg.listApiReturnDetail ?? false, slowQueryCache),\n    cfg: {\n      apiPathBase: client.getBasePath(),\n      enableExport: true,\n      showDBFilter: true,\n      showDigestFilter: false,\n      showResourceGroupFilter: true,\n      showDownloadSlowQueryDBFile: true,\n      showInternalFilter: true,\n      showRuV2: true,\n      ...cfg\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/SlowQuery/index.tsx",
    "content": "import React from 'react'\nimport { SlowQueryApp, SlowQueryProvider } from '@pingcap/tidb-dashboard-lib'\nimport { getGlobalConfig } from '~/utils/globalConfig'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <SlowQueryProvider\n      value={ctx(getGlobalConfig().appsConfig?.slowQuery || {})}\n    >\n      <SlowQueryApp />\n    </SlowQueryProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/SlowQuery/meta.ts",
    "content": "import { RocketOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'slow_query',\n  routerPrefix: '/slow_query',\n  icon: RocketOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Statement/context.ts",
    "content": "import {\n  IStatementDataSource,\n  IStatementContext,\n  ReqConfig,\n  IStatementConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, {\n  StatementEditableConfig,\n  StatementGetStatementsRequest\n} from '~/client'\n\nclass DataSource implements IStatementDataSource {\n  getDatabaseList(\n    beginTime: number,\n    endTime: number,\n    options?: ReqConfig | undefined\n  ) {\n    return client.getInstance().infoListDatabases(options)\n  }\n\n  infoListResourceGroupNames(options?: ReqConfig) {\n    return client.getInstance().resourceManagerInformationGroupNamesGet(options)\n  }\n\n  statementsAvailableFieldsGet(options?: ReqConfig) {\n    return client.getInstance().statementsAvailableFieldsGet(options)\n  }\n\n  statementsConfigGet(options?: ReqConfig) {\n    return client.getInstance().statementsConfigGet(options)\n  }\n\n  statementsConfigPost(request: StatementEditableConfig, options?: ReqConfig) {\n    return client.getInstance().statementsConfigPost({ request }, options)\n  }\n\n  statementsDownloadGet(token: string, options?: ReqConfig) {\n    return client.getInstance().statementsDownloadGet({ token }, options)\n  }\n\n  statementsDownloadTokenPost(\n    request: StatementGetStatementsRequest,\n    options?: ReqConfig\n  ) {\n    return client\n      .getInstance()\n      .statementsDownloadTokenPost({ request }, options)\n  }\n\n  statementsListGet(\n    beginTime?: number,\n    endTime?: number,\n    fields?: string,\n    schemas?: Array<string>,\n    resourceGroups?: Array<string>,\n    stmtTypes?: Array<string>,\n    text?: string,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().statementsListGet(\n      {\n        beginTime,\n        endTime,\n        fields,\n        schemas,\n        resourceGroups,\n        stmtTypes,\n        text\n      },\n      options\n    )\n  }\n\n  statementsPlanDetailGet(\n    beginTime?: number,\n    digest?: string,\n    endTime?: number,\n    plans?: Array<string>,\n    schemaName?: string,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().statementsPlanDetailGet(\n      {\n        beginTime,\n        digest,\n        endTime,\n        plans,\n        schemaName\n      },\n      options\n    )\n  }\n\n  statementsPlansGet(\n    beginTime?: number,\n    digest?: string,\n    endTime?: number,\n    schemaName?: string,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().statementsPlansGet(\n      {\n        beginTime,\n        digest,\n        endTime,\n        schemaName\n      },\n      options\n    )\n  }\n\n  statementsStmtTypesGet(options?: ReqConfig) {\n    return client.getInstance().statementsStmtTypesGet(options)\n  }\n\n  statementsTimeRangesGet(options?: ReqConfig) {\n    return client.getAxiosInstance().get('/statements/time_ranges', options)\n  }\n\n  // slow query\n  slowQueryAvailableFieldsGet(options?: ReqConfig) {\n    return client.getInstance().slowQueryAvailableFieldsGet(options)\n  }\n\n  slowQueryListGet(\n    beginTime?: number,\n    db?: Array<string>,\n    desc?: boolean,\n    digest?: string,\n    endTime?: number,\n    fields?: string,\n    limit?: number,\n    orderBy?: string,\n    plans?: Array<string>,\n    resourceGroup?: Array<string>,\n    text?: string,\n    showInternal?: boolean,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().slowQueryListGet(\n      {\n        beginTime,\n        db,\n        desc,\n        digest,\n        endTime,\n        fields,\n        limit,\n        orderBy,\n        plans,\n        resourceGroup,\n        text\n      },\n      options\n    )\n  }\n\n  slowQueryDetailGet(\n    connectId?: string,\n    digest?: string,\n    timestamp?: number,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().slowQueryDetailGet(\n      {\n        connectId,\n        digest,\n        timestamp\n      },\n      options\n    )\n  }\n\n  slowQueryDownloadTokenPost(request: any, options?: ReqConfig) {\n    return client.getInstance().slowQueryDownloadTokenPost({ request }, options)\n  }\n}\n\nexport const ctx: (cfg: Partial<IStatementConfig>) => IStatementContext = (\n  cfg\n) => {\n  return {\n    ds: new DataSource(),\n    cfg: {\n      apiPathBase: client.getBasePath(),\n      showRuV2: true,\n      ...cfg\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Statement/index.tsx",
    "content": "import React from 'react'\nimport { StatementApp, StatementProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\nimport { getGlobalConfig } from '~/utils/globalConfig'\n\nexport default function () {\n  return (\n    <StatementProvider\n      value={ctx(getGlobalConfig().appsConfig?.statement || {})}\n    >\n      <StatementApp />\n    </StatementProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/Statement/meta.ts",
    "content": "import { ThunderboltOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'statement',\n  routerPrefix: '/statement',\n  icon: ThunderboltOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/SystemReport/context.ts",
    "content": "import {\n  ISystemReportDataSource,\n  ISystemReportContext,\n  ReqConfig,\n  ISystemReportConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, {\n  DiagnoseGenerateReportRequest,\n  DiagnoseGenerateMetricsRelationRequest\n} from '~/client'\n\nimport publicPathBase from '~/utils/publicPathPrefix'\n\nexport type DsExtra = {\n  orgId: string\n  clusterId: string\n}\n\nclass DataSource implements ISystemReportDataSource {\n  diagnoseReportsGet(options?: ReqConfig) {\n    return client.getInstance().diagnoseReportsGet(options)\n  }\n\n  diagnoseReportsPost(\n    request: DiagnoseGenerateReportRequest,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().diagnoseReportsPost({ request }, options)\n  }\n\n  diagnoseGenerateMetricsRelationship(\n    request: DiagnoseGenerateMetricsRelationRequest,\n    options?: ReqConfig\n  ) {\n    return client\n      .getInstance()\n      .diagnoseGenerateMetricsRelationship({ request }, options)\n  }\n  diagnoseReportsIdStatusGet(id: string, options?: ReqConfig) {\n    return client.getInstance().diagnoseReportsIdStatusGet({ id }, options)\n  }\n}\n\nclass SystemReportConfig implements ISystemReportConfig {\n  constructor(public extra: DsExtra) {}\n\n  public apiPathBase = client.getBasePath()\n\n  public publicPathBase = publicPathBase\n\n  public fullReportLink(reportId: string): string {\n    const { orgId, clusterId } = this.extra\n    return `${publicPathBase}/diagnose-report/?orgId=${orgId}&clusterId=${clusterId}&reportId=${reportId}`\n  }\n}\n\nexport const ctx: (extra: DsExtra) => ISystemReportContext = (extra) => ({\n  ds: new DataSource(),\n  cfg: new SystemReportConfig(extra)\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/SystemReport/index.tsx",
    "content": "import React, { useMemo } from 'react'\nimport {\n  SystemReportApp,\n  SystemReportProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx, DsExtra } from './context'\n\nfunction getDsExtra(): DsExtra {\n  const searchParams = new URLSearchParams(window.location.search)\n  return {\n    orgId: searchParams.get('orgId')!,\n    clusterId: searchParams.get('clusterId')!\n  }\n}\n\nexport default function () {\n  const dsExtra = useMemo(() => getDsExtra(), [])\n\n  return (\n    <SystemReportProvider value={ctx(dsExtra)}>\n      <SystemReportApp />\n    </SystemReportProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/SystemReport/meta.ts",
    "content": "import { SnippetsOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'system_report',\n  routerPrefix: '/system_report',\n  icon: SnippetsOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/TopSQL/context.ts",
    "content": "import {\n  ITopSQLDataSource,\n  ITopSQLContext,\n  ITopSQLConfig,\n  TopsqlTikvNetworkIoCollectionConfig,\n  TopsqlTikvNetworkIoCollectionUpdateResponse,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { TopsqlEditableConfig } from '~/client'\n\nclass DataSource implements ITopSQLDataSource {\n  topsqlConfigGet(options?: ReqConfig) {\n    return client.getInstance().topsqlConfigGet(options)\n  }\n\n  topsqlConfigPost(request: TopsqlEditableConfig, options?: ReqConfig) {\n    return client.getInstance().topsqlConfigPost({ request }, options)\n  }\n\n  topsqlTikvNetworkIoCollectionGet(options?: ReqConfig) {\n    // Cloud TopSQL does not expose TiKV multi-dimensional collection settings.\n    // Return a fixed disabled state to keep interface compatibility.\n    return Promise.resolve({\n      data: {\n        enable: false,\n        is_multi_value: false\n      } as TopsqlTikvNetworkIoCollectionConfig\n    } as any)\n  }\n\n  topsqlTikvNetworkIoCollectionPost(\n    request: TopsqlTikvNetworkIoCollectionConfig,\n    options?: ReqConfig\n  ) {\n    // Cloud TopSQL does not expose TiKV multi-dimensional collection settings.\n    // Keep no-op behavior for compatibility if called unexpectedly.\n    return Promise.resolve({\n      data: {\n        warnings: []\n      } as TopsqlTikvNetworkIoCollectionUpdateResponse\n    } as any)\n  }\n\n  topsqlInstancesGet(\n    end?: string,\n    start?: string,\n    dataSource?: string,\n    options?: ReqConfig\n  ) {\n    const requestParameters: any = { start, end }\n    if (dataSource !== undefined) {\n      requestParameters.dataSource = dataSource\n    }\n    return client.getInstance().topsqlInstancesGet(requestParameters, options)\n  }\n\n  topsqlSummaryGet(\n    end?: string,\n    groupBy?: string,\n    instance?: string,\n    instanceType?: string,\n    orderBy?: string,\n    start?: string,\n    top?: string,\n    window?: string,\n    dataSource?: string,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().topsqlSummaryGet(\n      {\n        end,\n        groupBy,\n        instance,\n        instanceType,\n        orderBy,\n        start,\n        top,\n        window,\n        dataSource\n      },\n      options\n    )\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: (cfg: Partial<ITopSQLConfig>) => ITopSQLContext = (cfg) => ({\n  ds,\n  cfg: {\n    checkNgm: true,\n    showSetting: true,\n    ...cfg\n  }\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/TopSQL/index.tsx",
    "content": "import React from 'react'\nimport { TopSQLApp, TopSQLProvider } from '@pingcap/tidb-dashboard-lib'\nimport { getGlobalConfig } from '~/utils/globalConfig'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <TopSQLProvider value={ctx(getGlobalConfig().appsConfig?.topSQL || {})}>\n      <TopSQLApp />\n    </TopSQLProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/TopSQL/meta.ts",
    "content": "import { BarChartOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'topsql',\n  routerPrefix: '/topsql',\n  icon: BarChartOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/TopSlowQuery/context-provider.tsx",
    "content": "import React, { useMemo } from 'react'\nimport {\n  TopSlowQueryContext,\n  TopSlowQueryCtxValue\n} from '@pingcap/tidb-dashboard-lib'\nimport { getGlobalConfig } from '~/utils/globalConfig'\n\nimport client from '~/client'\n\nconst debugHeaders = {\n  // 'x-cluster-id': '1379661944646413143',\n  // 'x-org-id': '1372813089209061633',\n  // 'x-project-id': '1372813089454525346',\n  // 'x-provider': 'aws',\n  // 'x-region': 'us-east-1',\n  // 'x-env': 'prod'\n}\n\nexport function TopSlowQueryProvider(props: { children: React.ReactNode }) {\n  const ctxValue = useMemo<TopSlowQueryCtxValue>(() => {\n    return {\n      api: {\n        getAvailableTimeWindows: async ({\n          from,\n          to,\n          duration\n        }: {\n          from: number\n          to: number\n          duration: number\n        }) => {\n          const hours = duration / 3600\n          return client\n            .getAxiosInstance()\n            .get(\n              `/slow_query/stats/time_windows?begin_time=${from}&end_time=${to}&hours=${hours}`,\n              {\n                headers: debugHeaders\n              }\n            )\n            .then((res) => res.data)\n        },\n\n        getMetrics: async (params: { start: number; end: number }) => {\n          const hours = (params.end - params.start) / 3600\n          return client\n            .getAxiosInstance()\n            .get(\n              `/slow_query/stats/metric?begin_time=${params.start}&hours=${hours}&metric_name=count_per_minute`,\n              {\n                headers: debugHeaders\n              }\n            )\n            .then((res) => res.data)\n        },\n\n        getDatabaseList: async (params: { start: number; end: number }) => {\n          const hours = (params.end - params.start) / 3600\n          return client\n            .getAxiosInstance()\n            .get(\n              `/slow_query/stats/databases?begin_time=${params.start}&hours=${hours}`,\n              {\n                headers: debugHeaders\n              }\n            )\n            .then((res) => res.data)\n        },\n\n        getTopSlowQueries: async (params: {\n          start: number\n          end: number\n          order: string\n          dbs: string[]\n          internal: string\n          stmtKinds: string[]\n        }) => {\n          const hours = (params.end - params.start) / 3600\n          const p = new URLSearchParams()\n          p.append('begin_time', params.start + '')\n          p.append('hours', hours + '')\n          p.append('order_by', params.order)\n          p.append('limit', '10')\n          params.dbs.forEach((d) => p.append('databases', d))\n          params.stmtKinds.forEach((d) => p.append('statement_types', d))\n\n          return client\n            .getAxiosInstance()\n            .get(`/slow_query/stats?${p.toString()}`, {\n              headers: debugHeaders\n            })\n            .then((res) => res.data)\n        }\n      },\n      cfg: getGlobalConfig().appsConfig?.topSlowQuery || {}\n    }\n  }, [])\n\n  return (\n    <TopSlowQueryContext.Provider value={ctxValue}>\n      {props.children}\n    </TopSlowQueryContext.Provider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/TopSlowQuery/index.tsx",
    "content": "import React from 'react'\nimport { TopSlowQueryApp } from '@pingcap/tidb-dashboard-lib'\nimport { TopSlowQueryProvider } from './context-provider'\n\nexport default function () {\n  return (\n    <TopSlowQueryProvider>\n      <TopSlowQueryApp />\n    </TopSlowQueryProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/TopSlowQuery/meta.ts",
    "content": "import { BarChartOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'top_slowquery',\n  routerPrefix: '/top_slowquery',\n  icon: BarChartOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/TopSlowQuery/sample-data/slowqueries.json",
    "content": "[\n  {\n    \"sql_digest\": \"9a4170a059f3113e196bdac9c156d6cecf3ccbef8eb238b878d04e61aa7d9741\",\n    \"sql_text\": \"start transaction;\",\n    \"count\": 8559,\n    \"sum_latency\": 21234.817677733,\n    \"max_latency\": 4.577684212,\n    \"avg_latency\": 2.4809928353467696,\n    \"sum_memory\": 22654976,\n    \"max_memory\": 520192,\n    \"avg_memory\": 2646.91856525295,\n    \"sum_disk\": 0,\n    \"max_disk\": 0,\n    \"avg_disk\": 0\n  },\n  {\n    \"sql_digest\": \"9505cacb7c710ed17125fcc6cb3669e8ddca6c8cd8af6a31f6b3cd64604c3098\",\n    \"sql_text\": \"commit;\",\n    \"count\": 6973,\n    \"sum_latency\": 19676.377774166976,\n    \"max_latency\": 9.5105206,\n    \"avg_latency\": 2.821795177709304,\n    \"sum_memory\": 83042304,\n    \"max_memory\": 2088960,\n    \"avg_memory\": 11909.12146852144,\n    \"sum_disk\": 0,\n    \"max_disk\": 0,\n    \"avg_disk\": 0\n  },\n  {\n    \"sql_digest\": \"6d07d8784dc1f26e88f9498494ea658077d8f24354ea9840386ffa5b05bb96e3\",\n    \"sql_text\": \"select * from `dh_audit_record` where `user_id` = ? and status in ( ... ) and `charge_time` \\u003c ? order by `charge_time` asc , `id` asc;\",\n    \"count\": 2358,\n    \"sum_latency\": 5021.569481481002,\n    \"max_latency\": 3.45770166,\n    \"avg_latency\": 2.1295884145381687,\n    \"sum_memory\": 73377498,\n    \"max_memory\": 63388,\n    \"avg_memory\": 31118.531806615778,\n    \"sum_disk\": 0,\n    \"max_disk\": 0,\n    \"avg_disk\": 0\n  },\n  {\n    \"sql_digest\": \"3067b3ecccb86eceaeb26d43420b0414ad2378bfe9d90cc01da6ba1b0619609e\",\n    \"sql_text\": \"select `dh_active_category_new` . `id` , `dh_active_category_new` . `category_name` , `dh_active_category_new` . `category_type` , `dh_active_category_new` . `sort_number` , `dh_active_category_new` . `category_status` , `dh_active_category_new` . `active_no` , `dh_active_category_new` . `translate_names` , `dh_active_category_new` . `content` , `dh_active_category_new` . `update_time` , `dh_active_category_new` . `op_user` from `dh_active_category_new` order by `sort_number` asc;\",\n    \"count\": 1779,\n    \"sum_latency\": 4512.599653008,\n    \"max_latency\": 4.171903397,\n    \"avg_latency\": 2.536593396856661,\n    \"sum_memory\": 9178920,\n    \"max_memory\": 12288,\n    \"avg_memory\": 5159.595278246205,\n    \"sum_disk\": 0,\n    \"max_disk\": 0,\n    \"avg_disk\": 0\n  },\n  {\n    \"sql_digest\": \"c2d9a6d6ea4c41fcf811146999a6113be6d2db02cf9e2ba63c5d54f5d7d89c90\",\n    \"sql_text\": \"select `id` from `dh_game_record` where `game_record_id` = ? limit ?;\",\n    \"count\": 1074,\n    \"sum_latency\": 2279.700366508,\n    \"max_latency\": 3.455839219,\n    \"avg_latency\": 2.1226260395791434,\n    \"sum_memory\": 0,\n    \"max_memory\": 0,\n    \"avg_memory\": 0,\n    \"sum_disk\": 0,\n    \"max_disk\": 0,\n    \"avg_disk\": 0\n  },\n  {\n    \"sql_digest\": \"d58c4a60c4657c30d588c86a7d85a4894e9cffdd12076a61d1b3b2b318904c3a\",\n    \"sql_text\": \"select * from `dh_finance_details` where `message_status` = ? and `create_time` \\u003c ? order by `create_time` desc limit ?;\",\n    \"count\": 830,\n    \"sum_latency\": 3391.784238015004,\n    \"max_latency\": 12.926423231,\n    \"avg_latency\": 4.086487033753017,\n    \"sum_memory\": 21707732,\n    \"max_memory\": 61440,\n    \"avg_memory\": 26153.893975903615,\n    \"sum_disk\": 0,\n    \"max_disk\": 0,\n    \"avg_disk\": 0\n  },\n  {\n    \"sql_digest\": \"77f10c86e21cb52c123ba0c7b3f0642136051351f4bc59a637290ffdc20b3678\",\n    \"sql_text\": \"select `useridx` , sum ( if ( `direct` = ? and `member_is_agent` = ... ) ) as `direct_agents` , sum ( if ( `direct` = ? and `member_is_agent` = ... ) ) as `direct_accounts` , sum ( if ( `direct` = ? and `member_is_agent` = ... ) ) as `other_agents` , sum ( if ( `direct` = ? and `member_is_agent` = ... ) ) as `other_accounts` from `dh_promote_myteam_detail` where `useridx` = ?;\",\n    \"count\": 751,\n    \"sum_latency\": 926.2322493650005,\n    \"max_latency\": 3.791190985,\n    \"avg_latency\": 1.2333318899667116,\n    \"sum_memory\": 85776781,\n    \"max_memory\": 433917,\n    \"avg_memory\": 114216.75233022636,\n    \"sum_disk\": 0,\n    \"max_disk\": 0,\n    \"avg_disk\": 0\n  },\n  {\n    \"sql_digest\": \"8b04b2a8bb0777143b5de1111ffdb142180bec5c58941568b2a2450aead8956d\",\n    \"sql_text\": \"select count ( ? ) as `days` from `dh_user_day_report` where `user_idx` = ? and `valid_bet` \\u003e ?;\",\n    \"count\": 746,\n    \"sum_latency\": 1915.1985941270002,\n    \"max_latency\": 4.038490419,\n    \"avg_latency\": 2.567290340652815,\n    \"sum_memory\": 17516890,\n    \"max_memory\": 67432,\n    \"avg_memory\": 23481.085790884717,\n    \"sum_disk\": 0,\n    \"max_disk\": 0,\n    \"avg_disk\": 0\n  },\n  {\n    \"sql_digest\": \"3adcc90f3cc8cdfba25f8b1fdad067d2d6e7c4b3079836004aa6c2d330d13683\",\n    \"sql_text\": \"select `ifnull` ( sum ( `deposit` ) , ? ) as `total_deposit` from `dh_user_day_report` where `user_idx` = ?;\",\n    \"count\": 582,\n    \"sum_latency\": 1478.318133973,\n    \"max_latency\": 3.483108902,\n    \"avg_latency\": 2.540065522290378,\n    \"sum_memory\": 16735193,\n    \"max_memory\": 60872,\n    \"avg_memory\": 28754.62714776632,\n    \"sum_disk\": 0,\n    \"max_disk\": 0,\n    \"avg_disk\": 0\n  },\n  {\n    \"sql_digest\": \"daa1f705cac4db15a1a54fece6a5148fd1180d3ac422d8e5e1c1db7c3f9ae6f3\",\n    \"sql_text\": \"select `agent_id` , `agent_mode` , `settle_type` , `profit_agent_mode` , `commission_type` , `commission_start_time` , `commission_end_time` from `dh_agentmode` limit ?;\",\n    \"count\": 386,\n    \"sum_latency\": 971.2267713840008,\n    \"max_latency\": 3.185514788,\n    \"avg_latency\": 2.5161315320829036,\n    \"sum_memory\": 13527988,\n    \"max_memory\": 74808,\n    \"avg_memory\": 35046.60103626943,\n    \"sum_disk\": 0,\n    \"max_disk\": 0,\n    \"avg_disk\": 0\n  }\n]\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/UserProfile/context.ts",
    "content": "import {\n  IUserProfileDataSource,\n  IUserProfileContext,\n  ReqConfig,\n  IUserProfileEvent\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, {\n  SsoCreateImpersonationRequest,\n  SsoSetConfigRequest,\n  CodeShareRequest,\n  MetricsPutCustomPromAddressRequest\n} from '~/client'\n\nclass DataSource implements IUserProfileDataSource {\n  userGetSignOutInfo(redirectUrl?: string, options?: ReqConfig) {\n    return client.getInstance().userGetSignOutInfo({ redirectUrl }, options)\n  }\n\n  userSSOCreateImpersonation(\n    request: SsoCreateImpersonationRequest,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().userSSOCreateImpersonation({ request }, options)\n  }\n\n  userSSOGetConfig(options?: ReqConfig) {\n    return client.getInstance().userSSOGetConfig(options)\n  }\n\n  userSSOListImpersonations(options?: ReqConfig) {\n    return client.getInstance().userSSOListImpersonations(options)\n  }\n\n  userSSOSetConfig(request: SsoSetConfigRequest, options?: ReqConfig) {\n    return client.getInstance().userSSOSetConfig({ request }, options)\n  }\n\n  userShareSession(request: CodeShareRequest, options?: ReqConfig) {\n    return client.getInstance().userShareSession({ request }, options)\n  }\n\n  userRevokeSession(options?: ReqConfig) {\n    return client.getInstance().userRevokeSession(options)\n  }\n\n  metricsGetPromAddress(options?: ReqConfig) {\n    return client.getInstance().metricsGetPromAddress(options)\n  }\n\n  metricsSetCustomPromAddress(\n    request: MetricsPutCustomPromAddressRequest,\n    options?: ReqConfig\n  ) {\n    return client\n      .getInstance()\n      .metricsSetCustomPromAddress({ request }, options)\n  }\n}\n\nclass EventHandler implements IUserProfileEvent {\n  logOut(): void {}\n}\n\nexport const ctx: IUserProfileContext = {\n  ds: new DataSource(),\n  event: new EventHandler()\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/UserProfile/index.tsx",
    "content": "import React from 'react'\nimport {\n  UserProfileApp,\n  UserProfileProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <UserProfileProvider value={ctx}>\n      <UserProfileApp />\n    </UserProfileProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/apps/UserProfile/meta.ts",
    "content": "import { UserOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'user_profile',\n  routerPrefix: '/user_profile',\n  icon: UserOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/client/index.tsx",
    "content": "import React from 'react'\nimport i18next from 'i18next'\nimport axios, { AxiosInstance } from 'axios'\nimport { message, Modal, notification } from 'antd'\n\nimport { routing, i18n } from '@pingcap/tidb-dashboard-lib'\nimport {\n  Configuration,\n  DefaultApi as DashboardApi\n} from '@pingcap/tidb-dashboard-client'\n\nimport { ClientOptions, ClusterInfo } from '~/utils/globalConfig'\n\nimport translations from './translations'\n\nexport * from '@pingcap/tidb-dashboard-client'\n\n//////////////////////////////\n\nconst client = {\n  init(\n    apiBasePath: string,\n    apiInstance: DashboardApi,\n    axiosInstance: AxiosInstance\n  ) {\n    this.apiBasePath = apiBasePath\n    this.apiInstance = apiInstance\n    this.axiosInstance = axiosInstance\n  },\n\n  getInstance(): DashboardApi {\n    return this.apiInstance\n  },\n\n  getBasePath(): string {\n    return this.apiBasePath\n  },\n\n  getAxiosInstance(): AxiosInstance {\n    return this.axiosInstance\n  }\n}\n\nexport default client\n\n//////////////////////////////\n\ntype HandleError = 'default' | 'custom'\n\nfunction applyErrorHandlerInterceptor(instance: AxiosInstance) {\n  instance.interceptors.response.use(undefined, async function (err) {\n    const { response, config } = err\n    const handleError = config.handleError as HandleError\n    const method = (config.method as string).toLowerCase()\n\n    let errCode: string\n    let content: string\n    if (err.message === 'Network Error') {\n      errCode = 'common.network'\n    } else {\n      errCode = response?.data?.code\n    }\n    if (i18next.exists(`error.${errCode ?? ''}`)) {\n      // If there is a translation for the code, use the translation.\n      // TODO: Better to display error details somewhere.\n      content = i18next.t(`error.${errCode}`)\n    } else {\n      content = String(\n        response?.data?.message || err.message || 'Internal error'\n      )\n    }\n    err.message = content\n    err.errCode = errCode\n\n    if (errCode === 'common.unauthenticated' || response?.status === 401) {\n      // Handle unauthorized error in a unified way\n      if (!routing.isLocationMatch('/') && !routing.isSignInPage()) {\n        message.error({ content, key: errCode ?? '401' })\n      }\n      // Remember the current url before redirecting to login page,\n      // to support redirect back after login.\n      localStorage.setItem('clinic.login.from', window.location.href)\n      setTimeout(() => {\n        window.location.href = window.location.origin\n      }, 2000)\n      err.handled = true\n    } else if (handleError === 'default') {\n      if (method === 'get') {\n        const fullUrl = config.url as string\n        const API = fullUrl.replace(client.getBasePath(), '').split('?')[0]\n        notification.error({\n          key: API,\n          message: i18next.t('error.title'),\n          description: (\n            <span>\n              API: {API}\n              <br />\n              {content}\n            </span>\n          )\n        })\n      } else if (['post', 'put', 'delete', 'patch'].includes(method)) {\n        Modal.error({\n          title: i18next.t('error.title'),\n          content: content,\n          zIndex: 2000 // higher than popover\n        })\n      }\n      err.handled = true\n    }\n\n    return Promise.reject(err)\n  })\n}\n\nfunction initAxios(clientOptions: ClientOptions, clusterInfo: ClusterInfo) {\n  const { apiToken } = clientOptions\n  const { provider, region, orgId, projectId, clusterId, deployType, env } =\n    clusterInfo\n\n  let headers = {}\n  // for clinic\n  headers['x-csrf-token'] = apiToken\n\n  // for tidb cloud\n  // headers['authorization'] = `Bearer ${apiToken}`\n\n  if (provider) {\n    headers['x-provider'] = provider\n  }\n  if (region) {\n    headers['x-region'] = region\n  }\n  if (orgId) {\n    headers['x-org-id'] = orgId\n  }\n  if (projectId) {\n    headers['x-project-id'] = projectId\n  }\n  if (clusterId) {\n    headers['x-cluster-id'] = clusterId\n  }\n  if (deployType) {\n    headers['x-deploy-type'] = deployType\n  }\n  if (env) {\n    headers['x-env'] = env\n  }\n  const instance = axios.create({\n    baseURL: clientOptions.apiPathBase,\n    headers\n  })\n  applyErrorHandlerInterceptor(instance)\n\n  return instance\n}\n\nexport function setupClient(\n  clientOptions: ClientOptions,\n  clusterInfo: ClusterInfo\n) {\n  i18n.addTranslations(translations)\n\n  const axiosInstance = initAxios(clientOptions, clusterInfo)\n  const dashboardApi = new DashboardApi(\n    new Configuration({\n      baseOptions: {\n        handleError: 'default'\n      }\n    }),\n    // basePath, it's set in the axiosInstance, so we pass empty string to dashboard Api\n    // if basePath and baseURL are both relative path\n    // the final api path will be the value that combined by dashboardApi basePath and axiosInstance baseURL\n    // if we use undefined for this param, dashboardApi basePath will be the default value `/dashboard/api`\n    '',\n    axiosInstance\n  )\n\n  client.init(clientOptions.apiPathBase, dashboardApi, axiosInstance)\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/client/translations/en.yaml",
    "content": "error:\n  title: Error\n  common:\n    network: Network connection error\n    unauthenticated: Please sign in again (session is expired)\n    forbidden: The current user is not authorized to perform this action\n  api:\n    user:\n      signin:\n        invalid_code: Authorization Code is invalid or expired\n        insufficient_priv: The user does not have sufficient privileges to access {{distro.tidb}} Dashboard.\n    slow_query:\n      export_no_data: No slow queires can be exported\n    statement:\n      export_no_data: No statements can be exported\n    continuous_profiling:\n      ng_monitoring_not_ready: |\n        To use or learn more about \"Continuous Profiling\" feature, please search for \"Continuous Profiling\" in the {{distro.tidb}} official docs for more information.\n        If it doesn't resove the issue, please contact the product's technical support.\n    feature_not_supported: The cluster of this version doesn't support or can't use this feature, please contact with technical support to get more information.\n  tidb:\n    no_alive_tidb: No alive {{distro.tidb}} instance\n    pd_access_failed: Failed to access {{distro.pd}} node\n    tidb_conn_failed: Failed to connect to {{distro.tidb}}\n    tidb_auth_failed: '{{distro.tidb}} authentication failed'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/client/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/client/translations/zh.yaml",
    "content": "error:\n  title: 错误\n  common:\n    network: 网络连接失败\n    unauthenticated: 会话已过期，请重新登录\n    forbidden: 当前用户没有权限进行该操作\n  api:\n    user:\n      signin:\n        invalid_code: 授权码无效或已过期\n        insufficient_priv: 该用户缺少足够的权限访问 {{distro.tidb}} Dashboard。\n    slow_query:\n      export_no_data: 没有可导出的慢查询日志\n    statement:\n      export_no_data: 没有可导出的语句\n    continuous_profiling:\n      ng_monitoring_not_ready: |\n        想使用或深入了解“持续性能分析”功能，请在 {{distro.tidb}} 官方文档搜索“持续性能分析”查看更多内容。\n        若未能解决问题，请联系本产品技术支持。\n    feature_not_supported: 当前版本的集群不支持或无法使用该功能，请联系技术支持了解详细情况。\n  tidb:\n    no_alive_tidb: 没有正在运行的 {{distro.tidb}} 实例\n    pd_access_failed: 无法访问 {{distro.pd}} 节点\n    tidb_conn_failed: 无法连接到 {{distro.tidb}}\n    tidb_auth_failed: '{{distro.tidb}} 登录验证失败'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/dashboardApp/index.ts",
    "content": "import '../styles/style.less'\nimport '@pingcap/tidb-dashboard-lib/dist/index.css'\n\nimport { start } from './main'\n\nimport '../styles/override.less'\n\nexport default start\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/dashboardApp/layout/main/Sider/Banner.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.banner {\n  background: @primary-color;\n  color: #fff;\n  cursor: pointer;\n  overflow: hidden;\n  user-select: none;\n  position: relative;\n  margin-bottom: 20px;\n  flex-shrink: 0;\n}\n\n.bannerLeft {\n  padding: 20px 16px 20px 24px;\n}\n\n.bannerRight {\n  position: absolute;\n  top: 0;\n  height: 100%;\n  transition: background-color 0.2s @ease-out;\n  display: flex;\n}\n\n.banner:hover .bannerRight {\n  background: lighten(@primary-color, 5%);\n}\n\n.bannerLogo {\n  margin: 5px 0;\n}\n\n.bannerContent {\n  margin-left: 15px;\n}\n\n.bannerTitle {\n  font-size: 1rem;\n}\n\n.bannerVersion {\n  font-size: 0.9rem;\n  opacity: 0.7;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/dashboardApp/layout/main/Sider/Banner.tsx",
    "content": "import React, { useMemo, useRef } from 'react'\nimport { CaretRightOutlined, CaretLeftOutlined } from '@ant-design/icons'\nimport { useSize } from 'ahooks'\nimport Flexbox from '@g07cha/flexbox-react'\nimport { useSpring, animated } from 'react-spring'\nimport { useTranslation, TFunction } from 'react-i18next'\n\nimport { InfoInfoResponse } from '~/client'\n\nimport {\n  // store\n  store\n} from '@pingcap/tidb-dashboard-lib'\n\nimport { lightLogoSvg } from '~/utils/distro/assetsRes'\n\nimport styles from './Banner.module.less'\n\nconst toggleWidth = 40\nconst toggleHeight = 50\n\nfunction parseVersion(i: InfoInfoResponse, t: TFunction) {\n  if (!i.version) {\n    return null\n  }\n  if (i.version.standalone !== 'No') {\n    // For Standalone == Yes / Unknown, display internal version\n    if (i.version.internal_version === 'nightly') {\n      let vPrefix = i.version.internal_version\n      if (i.version.build_git_hash) {\n        vPrefix += `-${i.version.build_git_hash.substr(0, 8)}`\n      }\n      // e.g. nightly-xxxxxxxx\n      return vPrefix\n    }\n    if (i.version.internal_version) {\n      // e.g. v2020.07.01.1\n      if (i.version.internal_version.startsWith('v')) {\n        return i.version.internal_version\n      } else {\n        return `v${i.version.internal_version}`\n      }\n    }\n    return null\n  }\n\n  if (i.version.pd_version) {\n    // e.g. PD v4.0.1\n    return `${t('distro.pd')} ${i.version.pd_version}`\n  }\n}\n\nexport default function ToggleBanner({\n  fullWidth,\n  collapsedWidth,\n  collapsed,\n  onToggle\n}) {\n  const { t } = useTranslation()\n  const bannerRef = useRef(null)\n  const bannerSize = useSize(bannerRef)\n  const transBanner = useSpring({\n    opacity: collapsed ? 0 : 1,\n    height: collapsed ? toggleHeight : bannerSize?.height ?? 0\n  })\n  const transButton = useSpring({\n    left: collapsed ? 0 : fullWidth - toggleWidth,\n    width: collapsed ? collapsedWidth : toggleWidth\n  })\n\n  const appInfo = store.useState((s) => s.appInfo)\n\n  const version = useMemo(() => {\n    if (appInfo) {\n      return parseVersion(appInfo, t)\n    }\n    return null\n  }, [appInfo, t])\n\n  return (\n    <div className={styles.banner} onClick={onToggle}>\n      <animated.div\n        style={transBanner}\n        className={styles.bannerLeftAnimationWrapper}\n      >\n        <div\n          ref={bannerRef}\n          className={styles.bannerLeft}\n          style={{ width: fullWidth - toggleWidth }}\n        >\n          <Flexbox flexDirection=\"row\">\n            <div className={styles.bannerLogo}>\n              <img src={lightLogoSvg} style={{ height: 30 }} />\n            </div>\n            <div className={styles.bannerContent}>\n              <div className={styles.bannerTitle}>\n                {t('distro.tidb')} Dashboard\n              </div>\n              <div className={styles.bannerVersion}>\n                {version || 'Version unknown'}\n              </div>\n            </div>\n          </Flexbox>\n        </div>\n      </animated.div>\n      <animated.div style={transButton} className={styles.bannerRight}>\n        {collapsed ? (\n          <CaretRightOutlined style={{ margin: 'auto' }} />\n        ) : (\n          <CaretLeftOutlined style={{ margin: 'auto' }} />\n        )}\n      </animated.div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/dashboardApp/layout/main/Sider/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n@sider-background: rgb(246, 246, 246);\n@sider-highlight-height: 36px;\n@sider-ribbon-height: 30px;\n\n.sider {\n  position: fixed;\n  left: 0;\n  top: 0;\n  height: 100%;\n  z-index: 1;\n  background: @sider-background;\n  overflow-x: hidden;\n  overflow-y: auto;\n  transition: none;\n  user-select: none;\n\n  :global {\n    /* cancel text animations */\n    .ant-menu-item .ant-menu-item-icon,\n    .ant-menu-submenu-title .ant-menu-item-icon,\n    .ant-menu-item .anticon,\n    .ant-menu-submenu-title .anticon {\n      transition-duration: 0.1s;\n    }\n\n    .ant-menu-item .ant-menu-item-icon + span,\n    .ant-menu-submenu-title .ant-menu-item-icon + span,\n    .ant-menu-item .anticon + span,\n    .ant-menu-submenu-title .anticon + span {\n      transition-duration: 0.1s;\n    }\n\n    .ant-menu-item,\n    .ant-menu-submenu-title {\n      transition-duration: 0.1s;\n    }\n\n    .ant-menu-title-content {\n      transition-duration: 0.1s;\n    }\n\n    .ant-layout-sider-children {\n      display: flex;\n      flex-direction: column;\n      padding-bottom: 20px;\n\n      .ant-menu.ant-menu-inline-collapsed {\n        width: @menu-collapsed-width; // 80px\n      }\n    }\n\n    .ant-menu {\n      border-right: 0;\n      background: none;\n    }\n\n    .ant-menu-submenu-selected {\n      color: #000;\n    }\n\n    .ant-menu-item {\n      background: none !important;\n\n      &::after {\n        left: 0;\n        top: 0;\n        height: @sider-ribbon-height;\n        margin-top: -(@sider-ribbon-height / 2);\n        top: 50%;\n        border: 0px;\n        width: 5px;\n        border-radius: 0 5px 5px 0;\n        background: @primary-color;\n      }\n\n      &::before {\n        content: '';\n        position: absolute;\n        left: 0;\n        right: 20px;\n        height: @sider-highlight-height;\n        top: 50%;\n        margin-top: -(@sider-highlight-height / 2);\n        border-radius: 0 (@sider-highlight-height / 2)\n          (@sider-highlight-height / 2) 0;\n        z-index: -1;\n      }\n\n      &:hover::before {\n        background: rgba(lighten(@primary-color, 20%), 0.2);\n      }\n\n      a {\n        color: #666;\n        transition-duration: 0s;\n\n        &:hover {\n          color: #000;\n        }\n      }\n\n      &.ant-menu-item-selected {\n        &::before {\n          background: rgba(darken(@sider-background, 30%), 0.15);\n        }\n\n        a {\n          color: #000;\n        }\n      }\n    }\n\n    .ant-menu-submenu-title {\n      background: none !important;\n    }\n\n    .ant-menu-inline-collapsed .ant-menu-item {\n      &::before {\n        right: 10px;\n        left: 10px;\n        border-radius: (@sider-highlight-height / 2);\n      }\n    }\n  }\n}\n\n:global {\n  .ant-menu-inline-collapsed-tooltip {\n    a {\n      color: @text-color-dark; // hsla(0, 0%, 100%, 0.85)\n    }\n    .anticon {\n      display: none;\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/dashboardApp/layout/main/Sider/index.tsx",
    "content": "import React, { useState, useMemo } from 'react'\nimport {\n  ExperimentOutlined,\n  BugOutlined,\n  AimOutlined\n  // PullRequestOutlined\n} from '@ant-design/icons'\nimport { Layout, Menu } from 'antd'\nimport { Link } from 'react-router-dom'\nimport { useEventListener } from 'ahooks'\nimport { useTranslation } from 'react-i18next'\nimport { useSpring, animated } from 'react-spring'\nimport Banner from './Banner'\nimport styles from './index.module.less'\n\nimport { store, useIsFeatureSupport } from '@pingcap/tidb-dashboard-lib'\n\nfunction useAppMenuItem(\n  registry,\n  appId,\n  enable: boolean = true,\n  title?: string,\n  hideIcon?: boolean\n) {\n  const { t } = useTranslation()\n  const app = registry.apps[appId]\n  if (!enable || !app) {\n    return null\n  }\n  return (\n    <Menu.Item key={appId} data-e2e={`menu_item_${appId}`}>\n      <Link to={app.indexRoute} id={appId}>\n        {!hideIcon && app.icon ? <app.icon /> : null}\n        <span>{title ? title : t(`${appId}.nav_title`, appId)}</span>\n      </Link>\n    </Menu.Item>\n  )\n}\n\nfunction useActiveAppId(registry) {\n  const [appId, set] = useState('')\n  useEventListener(\n    'single-spa:routing-event',\n    () => {\n      const activeApp = registry.getActiveApp()\n      if (activeApp) {\n        set(activeApp.id)\n      }\n    },\n    {\n      target: window\n    }\n  )\n  return appId\n}\n\nfunction Sider({\n  registry,\n  fullWidth,\n  defaultCollapsed,\n  collapsed,\n  collapsedWidth,\n  onToggle,\n  animationDelay\n}) {\n  const { t } = useTranslation()\n  const activeAppId = useActiveAppId(registry)\n\n  const whoAmI = store.useState((s) => s.whoAmI)\n  const appInfo = store.useState((s) => s.appInfo)\n\n  const supportConProf = useIsFeatureSupport('conprof')\n  const profilingSubMenuItems = [\n    useAppMenuItem(registry, 'instance_profiling', true, '', true),\n    useAppMenuItem(registry, 'conprof', supportConProf, '', true)\n  ]\n\n  const profilingSubMenu = (\n    <Menu.SubMenu\n      key=\"profiling\"\n      title={\n        <span>\n          <AimOutlined />\n          <span>{t('profiling.nav_title')}</span>\n        </span>\n      }\n    >\n      {profilingSubMenuItems}\n    </Menu.SubMenu>\n  )\n\n  const debugSubMenuItems = [\n    profilingSubMenu,\n    useAppMenuItem(registry, 'debug_api')\n  ]\n\n  const debugSubMenu = (\n    <Menu.SubMenu\n      key=\"debug\"\n      title={\n        <span>\n          <BugOutlined />\n          <span>{t('nav.sider.debug')}</span>\n        </span>\n      }\n    >\n      {debugSubMenuItems}\n    </Menu.SubMenu>\n  )\n\n  // const conflictSubMenuItems = [useAppMenuItem(registry, 'deadlock')]\n\n  // const conflictSubMenu = (\n  //   <Menu.SubMenu\n  //     key=\"conflict\"\n  //     title={\n  //       <span>\n  //         <PullRequestOutlined />\n  //         <span>{t('nav.sider.conflict')}</span>\n  //       </span>\n  //     }\n  //   >\n  //     {conflictSubMenuItems}\n  //   </Menu.SubMenu>\n  // )\n\n  const experimentalSubMenuItems = [\n    useAppMenuItem(registry, 'query_editor'),\n    useAppMenuItem(registry, 'configuration')\n  ]\n  const experimentalSubMenu = (\n    <Menu.SubMenu\n      key=\"experimental\"\n      title={\n        <span>\n          <ExperimentOutlined />\n          <span>{t('nav.sider.experimental')}</span>\n        </span>\n      }\n    >\n      {experimentalSubMenuItems}\n    </Menu.SubMenu>\n  )\n\n  const supportTopSQL = useIsFeatureSupport('topsql')\n  const supportResourceManager = useIsFeatureSupport('resource_manager')\n  const menuItems = [\n    useAppMenuItem(registry, 'overview'),\n    useAppMenuItem(registry, 'cluster_info'),\n    // topSQL\n    useAppMenuItem(registry, 'topsql', supportTopSQL),\n    useAppMenuItem(registry, 'statement'),\n    useAppMenuItem(registry, 'slow_query'),\n    useAppMenuItem(registry, 'keyviz'),\n    useAppMenuItem(registry, 'system_report'),\n    // warning: \"diagnose\" app doesn't release yet\n    // useAppMenuItem(registry, 'diagnose'),\n    useAppMenuItem(registry, 'monitoring'),\n    useAppMenuItem(registry, 'search_logs'),\n    useAppMenuItem(registry, 'resource_manager', supportResourceManager),\n    // useAppMenuItem(registry, '__APP_NAME__'),\n    // NOTE: Don't remove above comment line, it is a placeholder for code generator\n    debugSubMenu\n    // conflictSubMenu\n  ]\n\n  if (appInfo?.enable_experimental) {\n    menuItems.push(experimentalSubMenu)\n  }\n\n  let displayName = whoAmI?.display_name || '...'\n\n  const extraMenuItems = [\n    useAppMenuItem(registry, 'dashboard_settings'),\n    useAppMenuItem(registry, 'user_profile', true, displayName)\n  ]\n\n  const transSider = useSpring({\n    width: collapsed ? collapsedWidth : fullWidth\n  })\n\n  const defaultOpenKeys = useMemo(() => {\n    if (defaultCollapsed) {\n      return []\n    } else {\n      return ['debug', 'experimental', 'profiling']\n    }\n  }, [defaultCollapsed])\n\n  return (\n    <animated.div style={transSider}>\n      <Layout.Sider\n        className={styles.sider}\n        width={fullWidth}\n        trigger={null}\n        collapsible\n        collapsed={collapsed}\n        collapsedWidth={fullWidth}\n        defaultCollapsed={defaultCollapsed}\n        theme=\"light\"\n      >\n        <Banner\n          collapsed={collapsed}\n          onToggle={onToggle}\n          fullWidth={fullWidth}\n          collapsedWidth={collapsedWidth}\n        />\n        <Menu\n          subMenuOpenDelay={animationDelay}\n          subMenuCloseDelay={animationDelay + 0.1}\n          mode=\"inline\"\n          selectedKeys={[activeAppId]}\n          style={{ flexGrow: 1 }}\n          defaultOpenKeys={defaultOpenKeys}\n        >\n          {menuItems}\n        </Menu>\n        <Menu\n          subMenuOpenDelay={animationDelay}\n          subMenuCloseDelay={animationDelay}\n          mode=\"inline\"\n          selectedKeys={[activeAppId]}\n        >\n          {extraMenuItems}\n        </Menu>\n      </Layout.Sider>\n    </animated.div>\n  )\n}\n\nexport default Sider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/dashboardApp/layout/main/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.container {\n  height: 100vh;\n}\n\n.content {\n  position: relative;\n  z-index: 3;\n  background: #fff;\n  min-height: 100vh;\n\n  &:before,\n  &:after {\n    // Handle margin collapse\n    content: ' ';\n    display: table;\n  }\n}\n\n.contentBack {\n  position: fixed;\n  z-index: 2;\n  background: #fff;\n  top: 0;\n  height: 100%;\n  right: 0;\n  box-shadow: 0 0 20px rgba(#000, 0.1);\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/dashboardApp/layout/main/index.tsx",
    "content": "import React, { useState, useCallback, useEffect } from 'react'\n// import { Root } from '@lib/components'\nimport { HashRouter as Router } from 'react-router-dom'\nimport { useSpring, animated } from 'react-spring'\n// import { useVersionedLocalStorageState } from '@lib/utils/useVersionedLocalStorageState'\n\nimport {\n  Root,\n  useVersionedLocalStorageState\n} from '@pingcap/tidb-dashboard-lib'\n\nimport Sider from './Sider'\nimport styles from './index.module.less'\n\nconst siderWidth = 260\nconst siderCollapsedWidth = 80\nconst collapsedContentOffset = siderCollapsedWidth - siderWidth\nconst contentOffsetTrigger = collapsedContentOffset * 0.99\n\nfunction triggerResizeEvent() {\n  const event = document.createEvent('HTMLEvents')\n  event.initEvent('resize', true, false)\n  window.dispatchEvent(event)\n}\n\nconst useContentLeftOffset = (collapsed) => {\n  const [offset, setOffset] = useState(siderWidth)\n  const onAnimationStart = useCallback(() => {\n    if (!collapsed) {\n      setOffset(siderWidth)\n    }\n  }, [collapsed])\n  const onAnimationFrame = useCallback(\n    ({ x }) => {\n      if (collapsed && x < contentOffsetTrigger) {\n        setOffset(siderCollapsedWidth)\n      }\n    },\n    [collapsed]\n  )\n  useEffect(triggerResizeEvent, [offset])\n  return {\n    contentLeftOffset: offset,\n    onAnimationStart,\n    onAnimationFrame\n  }\n}\n\nexport default function App({ registry }) {\n  const [collapsed, setCollapsed] = useVersionedLocalStorageState(\n    'layout.sider.collapsed',\n    { defaultValue: false }\n  )\n  const [defaultCollapsed] = useState(collapsed)\n  const { contentLeftOffset, onAnimationStart, onAnimationFrame } =\n    useContentLeftOffset(collapsed)\n  const transContentBack = useSpring({\n    x: collapsed ? collapsedContentOffset : 0,\n    onStart: onAnimationStart,\n    onFrame: onAnimationFrame\n  })\n  const transContainer = useSpring({\n    opacity: 1,\n    from: { opacity: 0 },\n    delay: 100\n  })\n\n  const handleToggle = useCallback(() => {\n    setCollapsed((c) => !c)\n  }, [setCollapsed])\n\n  const { appOptions } = registry\n\n  return (\n    <Root>\n      <Router>\n        <animated.div className={styles.container} style={transContainer}>\n          {!appOptions.hideNav && (\n            <>\n              <Sider\n                registry={registry}\n                fullWidth={siderWidth}\n                onToggle={handleToggle}\n                defaultCollapsed={defaultCollapsed}\n                collapsed={collapsed}\n                collapsedWidth={siderCollapsedWidth}\n                animationDelay={0}\n              />\n              <animated.div\n                className={styles.contentBack}\n                style={{\n                  left: `${siderWidth}px`,\n                  transform: transContentBack.x.interpolate(\n                    (x) => `translate3d(${x}px, 0, 0)`\n                  )\n                }}\n              ></animated.div>\n            </>\n          )}\n          <div\n            className={styles.content}\n            style={\n              appOptions.hideNav\n                ? {}\n                : {\n                    marginLeft: contentLeftOffset\n                  }\n            }\n          >\n            <div id=\"__spa_content__\"></div>\n          </div>\n        </animated.div>\n      </Router>\n    </Root>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/dashboardApp/layout/translations/en.yaml",
    "content": "signin:\n  message:\n    error: 'Sign in failed: {{ msg }}'\n    success: Sign in successfully\n    access_doc: Help\n    access_doc_link: https://docs.pingcap.com/tidb/stable/dashboard-user\n  form:\n    username: Username\n    username_tooltip: Sign in user can be customized in TiDB 5.3 or later versions\n    password: Password\n    button: Sign In\n    tidb_auth:\n      title: SQL User Sign In\n      switch:\n        title: SQL User\n        description: I know the username and password to connect to the database\n    code_auth:\n      title: Authorization Code Sign In\n      switch:\n        title: Authorization Code\n        description: I was invited by others with an authorization code\n      code: Code\n    sso:\n      button: Sign In via Company Account (SSO)\n      switch:\n        title: SSO\n        description: I want to sign in use my company account\n    use_alternative: Use Alternative Authentication\n    alternative:\n      title: Select Authentication\nnav:\n  user:\n    signout: Sign Out\n  sider:\n    debug: Advanced Debugging\n    conflict: Conflict Diagnosing\n    experimental: Experimental Features\nhealth_check:\n  failed_notification_title: System Health Check Failed\n  ngm_not_started: A required component `NgMonitoring` is not started in this cluster. Some features may not work.\n  help_text: Help\n  help_url: https://docs.pingcap.com/tidb/dev/dashboard-faq#a-required-component-ngmonitoring-is-not-started-error-is-shown\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/dashboardApp/layout/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/dashboardApp/layout/translations/zh.yaml",
    "content": "signin:\n  message:\n    error: '登录失败: {{ msg }}'\n    success: 登录成功\n    access_doc: 帮助\n    access_doc_link: https://docs.pingcap.com/zh/tidb/stable/dashboard-user\n  form:\n    username: 用户名\n    username_tooltip: 升级到 TiDB 5.3 及更高版本后可自定义登录用户\n    password: 密码\n    button: 登录\n    tidb_auth:\n      title: SQL 用户登录\n      switch:\n        title: SQL 用户\n        description: 我知道数据库的登录用户名和密码\n    code_auth:\n      title: 授权码登录\n      switch:\n        title: 授权码\n        description: 其他人通过授权码邀请我使用\n      code: 授权码\n    sso:\n      button: 使用公司账号 SSO 登录\n      switch:\n        title: SSO\n        description: 使用公司账号登录\n    use_alternative: 使用其他登录方式\n    alternative:\n      title: 选择登录方式\n\nnav:\n  user:\n    signout: 登出\n  sider:\n    debug: 高级调试\n    conflict: 冲突诊断\n    experimental: 实验性功能\nhealth_check:\n  failed_notification_title: 系统健康检查失败\n  ngm_not_started: 集群中未启动必要组件 `NgMonitoring`，部分功能将不可用。\n  help_text: 帮助\n  help_url: https://docs.pingcap.com/zh/tidb/dev/dashboard-faq#界面提示-集群中未启动必要组件-ngmonitoring\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/dashboardApp/main.tsx",
    "content": "import React from 'react'\n\nimport * as singleSpa from 'single-spa'\nimport i18next from 'i18next'\nimport { Modal, notification } from 'antd'\nimport NProgress from 'nprogress'\nimport './nprogress.less'\n\nimport {\n  routing,\n  i18n,\n  // telemetry\n  telemetry,\n  // store\n  NgmState,\n  // distro\n  distro,\n  isDistro\n} from '@pingcap/tidb-dashboard-lib'\n\nimport { InfoInfoResponse, setupClient } from '~/client'\nimport { mustLoadAppInfo, reloadWhoAmI } from '~/utils/store'\nimport {\n  AppOptions,\n  defAppOptions,\n  GlobalConfig,\n  setGlobalConfig\n} from '~/utils/globalConfig'\nimport AppRegistry from '~/utils/registry'\n\nimport AppOverview from '~/apps/Overview/meta'\nimport AppMonitoring from '~/apps/Monitoring/meta'\nimport AppClusterInfo from '~/apps/ClusterInfo/meta'\nimport AppTopSQL from '~/apps/TopSQL/meta'\nimport AppSlowQuery from '~/apps/SlowQuery/meta'\nimport AppStatement from '~/apps/Statement/meta'\nimport AppKeyViz from '~/apps/KeyViz/meta'\nimport AppSystemReport from '~/apps/SystemReport/meta'\nimport AppSearchLogs from '~/apps/SearchLogs/meta'\nimport AppInstanceProfiling from '~/apps/InstanceProfiling/meta'\nimport AppConProfiling from '~/apps/ContinuousProfiling/meta'\nimport AppDebugAPI from '~/apps/DebugAPI/meta'\nimport AppQueryEditor from '~/apps/QueryEditor/meta'\nimport AppConfiguration from '~/apps/Configuration/meta'\nimport AppUserProfile from '~/apps/UserProfile/meta'\nimport AppDiagnose from '~/apps/Diagnose/meta'\nimport AppOptimizerTrace from '~/apps/OptimizerTrace/meta'\nimport AppDeadlock from '~/apps/Deadlock/meta'\nimport AppResourceManager from '~/apps/ResourceManager/meta'\nimport AppTopSlowQuery from '~/apps/TopSlowQuery/meta'\n\nimport LayoutMain from './layout/main'\n\nimport translations from './layout/translations'\n\n// for update distro strings resource\nimport '~/utils/distro/stringsRes'\n\nfunction removeSpinner() {\n  const spinner = document.getElementById('dashboard_page_spinner')\n  if (spinner) {\n    spinner.remove()\n  }\n}\n\nasync function webPageStart(appOptions: AppOptions) {\n  i18n.addTranslations(translations)\n  i18next.changeLanguage(appOptions.lang)\n\n  let info: InfoInfoResponse\n\n  if (!appOptions.skipLoadAppInfo) {\n    try {\n      info = await mustLoadAppInfo()\n\n      if (!appOptions.skipNgmCheck && info?.ngm_state === NgmState.NotStarted) {\n        notification.error({\n          key: 'ngm_not_started',\n          message: i18next.t('health_check.failed_notification_title'),\n          description: (\n            <span>\n              {i18next.t('health_check.ngm_not_started')}\n              {!isDistro() && (\n                <>\n                  {' '}\n                  <a\n                    href={i18next.t('health_check.help_url')}\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                  >\n                    {i18next.t('health_check.help_text')}\n                  </a>\n                </>\n              )}\n            </span>\n          ),\n          duration: null\n        })\n      }\n    } catch (e) {\n      Modal.error({\n        title: `Failed to connect to server`,\n        content: '' + e,\n        okText: 'Reload',\n        onOk: () => window.location.reload()\n      })\n      removeSpinner()\n      return\n    }\n  }\n\n  telemetry.init(\n    process.env.REACT_APP_MIXPANEL_HOST,\n    process.env.REACT_APP_MIXPANEL_TOKEN\n  )\n  // if (info?.enable_telemetry) {\n  // }\n  // mixpanel\n  telemetry.enable(\n    `tidb-dashboard-for-clinic-cloud-${process.env.REACT_APP_VERSION}`\n  )\n\n  let preRoute = ''\n  window.addEventListener('single-spa:routing-event', () => {\n    const curRoute = routing.getPathInLocationHash()\n    if (curRoute !== preRoute) {\n      telemetry.trackRouteChange(curRoute)\n      preRoute = curRoute\n    }\n  })\n\n  const registry = new AppRegistry(appOptions)\n\n  if (!appOptions.hidePageLoadProgress) {\n    NProgress.configure({\n      showSpinner: false\n    })\n    window.addEventListener('single-spa:before-routing-event', () => {\n      NProgress.set(0.2)\n    })\n    window.addEventListener('single-spa:routing-event', () => {\n      NProgress.done(true)\n    })\n  }\n\n  singleSpa.registerApplication(\n    'layout',\n    AppRegistry.newReactSpaApp(() => LayoutMain, 'root'),\n    () => {\n      return !routing.isSignInPage()\n    },\n    { registry }\n  )\n\n  registry\n    .register(AppUserProfile)\n    .register(AppOverview)\n    .register(AppClusterInfo)\n    .register(AppKeyViz)\n    .register(AppTopSQL)\n    .register(AppStatement)\n    .register(AppSystemReport)\n    .register(AppSlowQuery)\n    .register(AppDiagnose)\n    .register(AppMonitoring)\n    .register(AppSearchLogs)\n    .register(AppInstanceProfiling)\n    .register(AppConProfiling)\n    .register(AppQueryEditor)\n    .register(AppConfiguration)\n    .register(AppDebugAPI)\n    .register(AppOptimizerTrace)\n    .register(AppDeadlock)\n    .register(AppResourceManager)\n    .register(AppTopSlowQuery)\n\n  if (!appOptions.skipReloadWhoAmI) {\n    try {\n      const ok = await reloadWhoAmI()\n\n      if (routing.isLocationMatch('/') && ok) {\n        singleSpa.navigateToUrl('#' + registry.getDefaultRouter())\n      }\n    } catch (e) {\n      // If there are auth errors, redirection will happen any way. So we continue.\n    }\n  }\n\n  window.addEventListener('single-spa:first-mount', () => {\n    removeSpinner()\n  })\n\n  singleSpa.start()\n}\n\nexport function start(globalConfig: GlobalConfig) {\n  document.title = `${distro().tidb} Dashboard`\n\n  setGlobalConfig(globalConfig)\n\n  setupClient(globalConfig.clientOptions, globalConfig.clusterInfo)\n  webPageStart({ ...defAppOptions, ...globalConfig.appOptions })\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/dashboardApp/nprogress.less",
    "content": "@progress-color: #ffc53d;\n\n#nprogress {\n  pointer-events: none;\n}\n\n#nprogress .bar {\n  background: @progress-color;\n\n  position: fixed;\n  z-index: 1031;\n  top: 0;\n  left: 0;\n\n  width: 100%;\n  height: 2px;\n}\n\n/* Fancy blur effect */\n#nprogress .peg {\n  display: block;\n  position: absolute;\n  right: 0px;\n  width: 100px;\n  height: 100%;\n  box-shadow: 0 0 10px @progress-color, 0 0 5px @progress-color;\n  opacity: 1;\n  transform: rotate(3deg) translate(0px, -4px);\n}\n\n/* Remove these to get rid of the spinner */\n#nprogress .spinner {\n  display: block;\n  position: fixed;\n  z-index: 1031;\n  top: 15px;\n  right: 15px;\n}\n\n#nprogress .spinner-icon {\n  width: 18px;\n  height: 18px;\n  box-sizing: border-box;\n\n  border: solid 2px transparent;\n  border-top-color: @progress-color;\n  border-left-color: @progress-color;\n  border-radius: 50%;\n  animation: nprogress-spinner 400ms linear infinite;\n}\n\n.nprogress-custom-parent {\n  overflow: hidden;\n  position: relative;\n}\n\n.nprogress-custom-parent #nprogress .spinner,\n.nprogress-custom-parent #nprogress .bar {\n  position: absolute;\n}\n\n@keyframes nprogress-spinner {\n  0% {\n    transform: rotate(0deg);\n  }\n  100% {\n    transform: rotate(360deg);\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/diagnoseReportApp/components/DiagnosisReport.tsx",
    "content": "import React, { useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { i18n } from '@pingcap/tidb-dashboard-lib'\n\nimport DiagnosisTable from './DiagnosisTable'\nimport { ExpandContext, TableDef } from '../types'\n\nfunction LangDropdown() {\n  const { i18n: i18next } = useTranslation()\n  return (\n    <div className=\"select\">\n      <select\n        onChange={(e) => i18next.changeLanguage(e.target.value)}\n        defaultValue={i18next.language}\n      >\n        {Object.keys(i18n.ALL_LANGUAGES).map((langKey) => (\n          <option value={langKey} key={langKey}>\n            {i18n.ALL_LANGUAGES[langKey]}\n          </option>\n        ))}\n      </select>\n    </div>\n  )\n}\n\ntype Props = {\n  diagnosisTables: TableDef[]\n}\n\nfunction TablesNavMenu({ diagnosisTables }: Props) {\n  const { t } = useTranslation()\n  return (\n    <div className=\"dropdown is-hoverable\">\n      <div className=\"dropdown-trigger\">\n        <a className=\"navbar-link\">{t('diagnosis.all_tables')}</a>\n      </div>\n      <div className=\"dropdown-menu\">\n        <div\n          className=\"dropdown-content\"\n          style={{\n            maxHeight: 500,\n            overflowY: 'scroll'\n          }}\n        >\n          {diagnosisTables.map((item) => (\n            <React.Fragment key={item.title}>\n              <h2 style={{ paddingLeft: 16 }}>\n                {item.category[0] &&\n                  t(`diagnosis.tables.category.${item.category[0]}`)}\n              </h2>\n              <a\n                style={{ paddingLeft: 32 }}\n                className=\"dropdown-item\"\n                href={`#${item.title}`}\n              >\n                {t(`diagnosis.tables.title.${item.title}`)}\n              </a>\n            </React.Fragment>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default function DiagnosisReport({ diagnosisTables }: Props) {\n  const [expandAll, setExpandAll] = useState(false)\n  const { t } = useTranslation()\n\n  return (\n    <section className=\"section\">\n      <div className=\"container\">\n        <h1 className=\"title is-size-1\">{t('diagnosis.title')}</h1>\n        <div className=\"actions\">\n          <LangDropdown />\n          <button\n            className=\"button is-link is-light\"\n            style={{ marginRight: 12, marginLeft: 12 }}\n            onClick={() => setExpandAll(true)}\n          >\n            {t('diagnosis.expand_all')}\n          </button>\n          <button\n            className=\"button is-link is-light\"\n            onClick={() => setExpandAll(false)}\n          >\n            {t('diagnosis.fold_all')}\n          </button>\n          <TablesNavMenu diagnosisTables={diagnosisTables} />\n        </div>\n\n        <ExpandContext.Provider value={expandAll}>\n          {diagnosisTables.map((item, idx) => (\n            <DiagnosisTable diagnosis={item} key={idx} />\n          ))}\n        </ExpandContext.Provider>\n      </div>\n    </section>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/diagnoseReportApp/components/DiagnosisTable.tsx",
    "content": "import React, { useContext, useState, useEffect } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport ReactMarkdown from 'react-markdown'\n\nimport { distro } from '@pingcap/tidb-dashboard-lib'\n\nimport { TableDef, ExpandContext, TableRowDef } from '../types'\n\nconst lowerDistro = Object.keys(distro).reduce((accu, cur) => {\n  if (typeof distro[cur] === 'string') {\n    accu[cur] = distro[cur].toLowerCase()\n  }\n  return accu\n}, {})\n\nconst distroRegs = Object.keys(lowerDistro).reduce((accu, cur) => {\n  accu[cur] = new RegExp(cur, 'ig')\n  return accu\n}, {})\n\nfunction replaceDistro(oriStr: string): string {\n  let retStr = oriStr\n  Object.keys(lowerDistro).forEach((key) => {\n    retStr = retStr.replace(distroRegs[key], lowerDistro[key])\n  })\n  return retStr\n}\n\nfunction DiagnosisRow({ row }: { row: TableRowDef }) {\n  const outsideExpand = useContext(ExpandContext)\n  const [internalExpand, setInternalExpand] = useState(false)\n  const { t, i18n } = useTranslation()\n\n  // when outsideExpand changes, reset the internalExpand to the same as outsideExpand\n  useEffect(() => {\n    setInternalExpand(outsideExpand)\n  }, [outsideExpand])\n\n  function showRowName(rowName: string) {\n    const i18nKey = `diagnosis.tables.table.name.${rowName}`\n    if (i18n.exists(i18nKey)) {\n      return t(i18nKey)\n    }\n    return replaceDistro(rowName)\n  }\n\n  function showOthers(val: string | number) {\n    if (typeof val === 'string') {\n      return replaceDistro(val)\n    }\n    return val\n  }\n\n  return (\n    <>\n      <tr>\n        {(row.values || []).map((val, valIdx) => (\n          <td key={valIdx}>\n            {valIdx === 0 ? showRowName(val) : showOthers(val)}\n            {valIdx === 0 &&\n              t(`diagnosis.tables.table.comment.${val}`, '') !== '' && (\n                <div className=\"dropdown is-hoverable is-up\">\n                  <div className=\"dropdown-trigger\">\n                    <span className=\"icon has-text-info\">\n                      <i className=\"fas fa-info-circle\"></i>\n                    </span>\n                  </div>\n                  <div className=\"dropdown-menu\">\n                    <div className=\"dropdown-content\">\n                      <div className=\"dropdown-item\">\n                        <p>{t(`diagnosis.tables.table.comment.${val}`)}</p>\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              )}\n            {valIdx === 0 && (row.sub_values || []).length > 0 && (\n              <>\n                &nbsp;&nbsp;&nbsp;\n                <span\n                  className=\"subvalues-toggle\"\n                  onClick={() => setInternalExpand(!internalExpand)}\n                >\n                  {internalExpand ? t('diagnosis.fold') : t('diagnosis.expand')}\n                </span>\n              </>\n            )}\n          </td>\n        ))}\n      </tr>\n      {(row.sub_values || []).map((subVals, subValsIdx) => (\n        <tr\n          key={subValsIdx}\n          className={`subvalues ${!internalExpand && 'fold'}`}\n        >\n          {subVals.map((subVal, subValIdx) => (\n            <td key={subValIdx}>\n              {subValIdx === 0 && '|-- '}\n              {showOthers(subVal)}\n            </td>\n          ))}\n        </tr>\n      ))}\n    </>\n  )\n}\n\ntype Props = {\n  diagnosis: TableDef\n}\n\nexport default function DiagnosisTable({ diagnosis }: Props) {\n  const { category, title, column, rows } = diagnosis\n  const { t } = useTranslation()\n\n  return (\n    <div className=\"report-container\" id={title}>\n      {(category || []).map((c, idx) => (\n        <h1 className={`title is-size-${idx + 2}`} key={idx}>\n          {c && t(`diagnosis.tables.category.${c}`)}\n        </h1>\n      ))}\n      <h3 className=\"is-size-4\">{t(`diagnosis.tables.title.${title}`)}</h3>\n      <ReactMarkdown>\n        {t(`diagnosis.tables.comment.${title}`, '')}\n      </ReactMarkdown>\n      <table\n        className=\"table is-bordered is-hoverable is-narrow is-fullwidth\"\n        style={{ position: 'relative' }}\n      >\n        <thead>\n          <tr>\n            {column.map((col, colIdx) => (\n              <th className=\"table-header-row\" key={colIdx}>\n                {col}\n              </th>\n            ))}\n          </tr>\n        </thead>\n        <tbody>\n          {(rows || []).map((row, rowIdx) => (\n            <DiagnosisRow key={rowIdx} row={row} />\n          ))}\n        </tbody>\n      </table>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/diagnoseReportApp/index.css",
    "content": ".report-container {\n  margin-bottom: 16px;\n}\n\ntr.subvalues {\n  background-color: lightcyan;\n}\n\ntr.subvalues.fold {\n  display: none;\n}\n\n.subvalues-toggle {\n  display: inline-block;\n  width: 60px;\n  cursor: pointer;\n  color: #2160c4;\n}\n\n.actions {\n  padding: 8px 0;\n  background-color: white;\n\n  position: sticky;\n  top: 0;\n  z-index: 2;\n}\n\n.table-header-row {\n  position: sticky;\n  top: 55px;\n  z-index: 1;\n\n  background-color: #888;\n  color: white !important;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/diagnoseReportApp/index.tsx",
    "content": "import React from 'react'\nimport ReactDOM from 'react-dom'\n\nimport 'bulma/css/bulma.css'\nimport '@fortawesome/fontawesome-free/js/all.js'\n\nimport { i18n, distro } from '@pingcap/tidb-dashboard-lib'\n\nimport DiagnosisReport from './components/DiagnosisReport'\nimport translations from './translations'\n\n// for update distro strings resource\nimport '~/utils/distro/stringsRes'\n\nimport './index.css'\n\nfunction refineDiagnosisData() {\n  const diagnosisData = window.__diagnosis_data__ || []\n\n  let preCategory = ''\n  diagnosisData.forEach((d) => {\n    if (d.category.join('') === preCategory) {\n      d.category = []\n    } else {\n      preCategory = d.category.join('')\n    }\n  })\n  return diagnosisData\n}\n\ni18n.addTranslations(translations)\ndocument.title = `${distro().tidb} Dashboard Diagnosis Report`\n\nfunction main() {\n  ReactDOM.render(\n    <DiagnosisReport diagnosisTables={refineDiagnosisData()} />,\n    document.getElementById('root')\n  )\n}\n\nmain()\n\nwindow.addEventListener('dashboard:diagnose_report_event', function (event) {\n  main()\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/diagnoseReportApp/react-app-env.d.ts",
    "content": "/// <reference types=\"react-scripts\" />\n\n// https://stackoverflow.com/questions/12709074/how-do-you-explicitly-set-a-new-property-on-window-in-typescript\ninterface Window {\n  __diagnosis_data__: any\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/diagnoseReportApp/translations/en.yaml",
    "content": "diagnosis:\n  title: '{{distro.tidb}} Cluster System Report'\n  expand_all: Expand All\n  fold_all: Collapse All\n  expand: Expand\n  fold: Collapse\n  all_tables: Report Overview\n  tables:\n    category:\n      header: Basic Info\n      diagnose: Diagnose\n      load: Load Info\n      overview: Component Info\n      TiDB: '{{distro.tidb}} Component'\n      PD: '{{distro.pd}} Component'\n      TiKV: '{{distro.tikv}} Component'\n      config: Configuration Info\n      error: Error Info\n    title:\n      compare_diagnose: Diagnostics Comparison\n      compare_report_time_range: Comparison Time Range\n      top_10_slow_query_in_time_range_t1: Top 10 Slow Queries in Time Range t1\n      top_10_slow_query_in_time_range_t2: top 10 Slow Queries in Time Range t2\n      top_10_slow_query_group_by_digest_in_time_range_t1: Top 10 Slow Queries Group by Digest in Time Range t1\n      top_10_slow_query_group_by_digest_in_time_range_t2: Top 10 slow queries group by digest in Time Range t2\n      slow_query_with_diff_plan in_in_time_range_t1: Slow Queries with Different Plan in Time Range t1\n      slow_query_with_diff_plan in_in_time_range_t2: Slow queries with Different Plan in Time Range t2\n      diagnose_in_time_range_t1: Diagnostics in Time Range t1\n      diagnose_in_time_range_t2: Diagnostics in Time Range t2\n      max_diff_item: Maximum Different Item\n      slow_query_t2: Slow Queries In Time Range t2\n      generate_report_error: Report Generation Error\n      report_time_range: Report Time Range\n      diagnose: Diagnosis Result\n      total_time_consume: Time Consumed by Each Component\n      total_error: Errors Occurred in Each Component\n      time_consume: Time Consumed\n      tidb_time_consume: Time Consumed by {{distro.tidb}} Component\n      transaction: '{{distro.tidb}} Transaction'\n      tidb_connection_count: '{{distro.tidb}} Server Connections'\n      statistics_info: Statistics Info\n      ddl_owner: DDL Owner\n      scheduler_initial_config: Scheduler Initial Config\n      scheduler_change_config: Scheduler Config Change History\n      tidb_gc_initial_config: '{{distro.tidb}} GC Initial Config'\n      tidb_gc_change_config: '{{distro.tidb}} GC Config Change History'\n      tikv_rocksdb_initial_config: '{{distro.tikv}} RocksDB Initial Config'\n      tikv_rocksdb_change_config: '{{distro.tikv}} RocksDB Config Change History'\n      tikv_raftstore_initial_config: '{{distro.tikv}} RaftStore Initial Config'\n      tikv_raftstore_change_config: '{{distro.tikv}} RaftStore Config Change History'\n      pd_time_consume: Time Consumed by {{distro.pd}} Component\n      balance_leader_region: Scheduled Leader/Region Count\n      approximate_region_size: Approximate Region Size\n      tikv_engine_size: '{{distro.tikv}} Engine Size'\n      tikv_time_consume: Time Consumed by {{distro.tikv}} Component\n      scheduler_info: Scheduler Info\n      gc_info: GC Info\n      task_info: Task Info\n      snapshot_info: Snapshot Info\n      coprocessor_info: Coprocessor Info\n      raft_info: Raft Info\n      tikv_error: '{{distro.tikv}} Error'\n      tidb_current_config: \"{{distro.tidb}}'s Current Config\"\n      pd_current_config: \"{{distro.pd}}'s Current Config\"\n      tikv_current_config: \"{{distro.tikv}}'s Current Config\"\n      node_load_info: Server Load Info\n      process_cpu_usage: Instance CPU Usage\n      process_memory_usage: Instance Memory Usage\n      tidb/pd_goroutines_count: '{{distro.tidb}}/{{distro.pd}} Goroutines Count'\n      tikv_thread_cpu_usage: '{{distro.tikv}} Thread CPU Usage'\n      store_status: Store Status\n      cluster_status: Cluster Status\n      etcd_status: etcd Status\n      cluster_info: Cluster Topology Info\n      cache_hit: Cache Hit\n      cluster_hardware: Cluster Hardware Info\n      rocksdb_time_consume: Time Consumed by RocksDB\n      top_10_slow_query: Top 10 Slow Queries\n      top_10_slow_query_group_by_digest: Top 10 Slow Queries Group By Digest\n      slow_query_with_diff_plan: Slow Queries with Different Plan\n    comment:\n      compare_diagnose: Automatically diagnose the cluster problem by comparing with the reference time.\n      max_diff_item: The maximum different metrics between two time ranges.\n      diagnose: Automatically diagnose the cluster problem and record the problem in the table below.\n      total_time_consume: This table contains the event time consumed in {{distro.tidb}}/{{distro.tikv}}/{{distro.pd}}. METRIC_NAME is the event name; LABEL is the event label, such as instance, event type, etc; TIME_RATIO is the TOTAL_TIME of this event divided by the TOTAL_TIME of the events whose TIME_RATIO is 1; TOTAL_TIME is the total time cost of this event; TOTAL_COUNT is the total count of this event; P999 is the max time of 0.999 quantile; P99 is the max time of 0.99 quantile; P90 is the max time of 0.90 quantile; P80 is the max time of 0.80 quantile.\n      total_error: This table contains the total count of each error event. METRIC_NAME is the error event name; LABEL is the event label, such as instance, event type, etc; TOTAL_COUNT is the total count of this event.\n      tidb_time_consume: This table contains the event time consumed in {{distro.tidb}}. METRIC_NAME is the event name; LABEL is the event label, such as instance, event type, etc; TIME_RATIO is the TOTAL_TIME of this event divided by the TOTAL_TIME of the events whose TIME_RATIO is 1; TOTAL_TIME is the total time cost of this event; TOTAL_COUNT is the total count of this event; P999 is the max time of 0.999 quantile; P99 is the max time of 0.99 quantile; P90 is the max time of 0.90 quantile; P80 is the max time of 0.80 quantile.\n      transaction: This table contains the {{distro.tidb}} transaction statistics information. METRIC_NAME is the object name; LABEL is the object label, such as instance, event type, etc; TOTAL_VALUE is the total size/value of this object; TOTAL_COUNT is the total count of this object; P999 is the max size/value of 0.999 quantile; P99 is the max size/value of 0.99 quantile; P90 is the max size/value of 0.90 quantile; P80 is the max size/value of 0.80 quantile.\n      tidb_connection_count: The number of connections of each {{distro.tidb}} server.\n      ddl_owner: This table contains the DDL Owner info. Note that if no DDL request has been executed, the Owner info maybe null below, but it doesn't indicate that no DDL Owner exists.\n      scheduler_initial_config: The initial config value of {{distro.pd}} scheduler. The initial time is the start time of this report.\n      scheduler_change_config: The config change history of {{distro.pd}} scheduler. APPROXIMATE_CHANGE_TIME is the minimum start effective time.\n      tidb_gc_initial_config: The initial config value of {{distro.tidb}} GC. The initial time is the start time of this report.\n      tidb_gc_change_config: The config change history of {{distro.tidb}} GC. APPROXIMATE_CHANGE_TIME is the minimum start effective time.\n      tikv_rocksdb_initial_config: The initial config value of {{distro.tikv}} RocksDB. The initial time is the start time of this report.\n      tikv_rocksdb_change_config: The config change history of {{distro.tikv}} RocksDB. APPROXIMATE_CHANGE_TIME is the minimum start effective time.\n      tikv_raftstore_initial_config: The initial config value of {{distro.tikv}} RaftStore. The initial time is the start time of this report.\n      tikv_raftstore_change_config: The config change history of {{distro.tikv}} RaftStore. APPROXIMATE_CHANGE_TIME is the minimum start effective time.\n      pd_time_consume: This table contains the event time consumed in {{distro.pd}}. METRIC_NAME is the event name; LABEL is the event label, such as instance, event type, etc; TIME_RATIO is the TOTAL_TIME of this event divided by the TOTAL_TIME of the events whose TIME_RATIO is 1; TOTAL_TIME is the total time cost of this event; TOTAL_COUNT is the total count of this event; P999 is the max time of 0.999 quantile; P99 is the max time of 0.99 quantile; P90 is the max time of 0.90 quantile; P80 is the max time of 0.80 quantile.\n      tikv_time_consume: This table contains the event time consumed in {{distro.tikv}}. METRIC_NAME is the event name; LABEL is the event label, such as instance, event type, etc; TIME_RATIO is the TOTAL_TIME of this event divided by the TOTAL_TIME of the events whose TIME_RATIO is 1; TOTAL_TIME is the total time cost of this event; TOTAL_COUNT is the total count of this event; P999 is the max time of 0.999 quantile; P99 is the max time of 0.99 quantile; P90 is the max time of 0.90 quantile; P80 is the max time of 0.80 quantile.\n    table:\n      name:\n        tidb_transaction: Transaction\n        tidb_kv_request: KV request\n        tidb_slow_query: Slow query\n        tidb_ddl_handle_job: DDL job\n        tidb_ddl_batch_add_index: Batch add index\n        tidb_load_schema: Schema load\n        tidb_meta_operation: '{{distro.tidb}} meta operation'\n        tidb_auto_id_request: '{{distro.tidb}} auto ID request'\n        tidb_statistics_auto_analyze: '{{distro.tidb}} auto analyze'\n        tidb_gc: '{{distro.tidb}} GC'\n        pd_client_cmd: '{{distro.pd}} client cmd'\n        pd_handle_request: '{{distro.pd}} request'\n        pd_handle_transactions: etcd transactions\n        tikv_cop_request: Coprocessor request\n        tikv_cop_handle: Coprocessor handling request\n        tikv_handle_snapshot: Snapshot handling\n        tikv_send_snapshot: Snapshot sending\n        tikv_commit_log: Raft commit log\n        tidb_transaction_retry_num: '{{distro.tidb}} transaction retry'\n        tidb_txn_region_num: Transaction Region count\n        tidb_txn_kv_write_num: Transaction KV write count\n        tidb_txn_kv_write_size: Transaction KV write size\n        tidb_load_safepoint_total_num: Safepoint load\n        tikv_scheduler_stage_total_num: Scheduler stage\n        tikv_worker_handled_tasks_total_num: '{{distro.tikv}} worker handled tasks'\n        tikv_worker_pending_tasks_total_num: '{{distro.tikv}} worker pending tasks'\n        tikv_futurepool_handled_tasks_total_num: future_pool handled tasks\n        tikv_futurepool_pending_tasks_total_num: future_pool pending tasks\n        tikv_snapshot_kv_count: Snapshot KV\n        tikv_snapshot_size: Snapshot size\n        tikv_cop_scan_keys_num: '{{distro.tikv}} Coprocessor scan keys'\n        tikv_cop_total_response_total_size: '{{distro.tikv}} Coprocessor response'\n        tikv_cop_scan_num: '{{distro.tikv}} Coprocessor scan'\n        tikv_raft_sent_messages_total_num: Raft sent messages\n        tikv_flush_messages_total_num: Raft flush messages\n        tikv_receive_messages_total_num: Raft receive messages\n        tikv_raft_dropped_messages_total: Raft dropped messages\n        tikv_raft_proposals_total_num: Raft proposals\n        tikv_grpc_error_total_count: gRPC errors\n        tikv_critical_error_total_count: '{{distro.tikv}} critical errors'\n        tikv_coprocessor_request_error_total_count: Coprocessor request errors\n        node_disk_write_latency: Disk write latency\n        node_disk_read_latency: Disk read latency\n        sched_worker: Scheduler worker\n        tikv_memtable_hit: memtable hit\n        tikv_block_all_cache_hit: All block cache hit\n        tikv_block_index_cache_hit: Index block cache hit\n        tikv_block_filter_cache_hit: Filter block cache hit\n        tikv_block_data_cache_hit: Data block cache hit\n        tikv_block_bloom_prefix_cache_hit: Bloom prefix block cache hit\n      comment:\n        tidb_query: The time cost of SQL queries. The label is [sql_type].\n        tidb_get_token(us): The time cost of a session getting token to execute the SQL query. The label is [instance].\n        tidb_parse: The time cost of parsing SQL queries. The label is [sql_type].\n        tidb_compile: The time cost of building the query plan. The label is [sql_type].\n        tidb_execute: The time cost of executing the SQL query, which does not include the time to get the results of the query. The label is [sql_type].\n        tidb_distsql_execution: The time cost of distsql execution. The label is [type].\n        tidb_cop: The processing time of KV storage Coprocessor. The label is [instance].\n        tidb_transaction: The time cost of a transaction executing durations, including retry. The label is [sql_type].\n        tidb_transaction_local_latch_wait: The time cost of waiting for local latch. The label is [instance].\n        tidb_kv_backoff: The time cost of {{distro.tidb}} transaction latch waiting for key value storage. The label is [type].\n        tidb_kv_request: The time cost of KV requests durations. The label is [type].\n        tidb_slow_query: The time cost of {{distro.tidb}} slow queries. The label is [instance].\n        tidb_slow_query_cop_process: The total Coprocessor processing time of {{distro.tidb}} slow queries. The label is [instance].\n        tidb_slow_query_cop_wait: The total Coprocessor waiting time of {{distro.tidb}} slow queries. The label is [instance].\n        tidb_ddl_handle_job: The time cost of processing {{distro.tidb}} DDL jobs. The label is [type].\n        tidb_ddl_worker: The time cost of DDL worker handling jobs. The label is [action].\n        tidb_ddl_update_self_version: The time cost of updating {{distro.tidb}} schema syncer version. The label is [result].\n        tidb_owner_handle_syncer: The time cost of {{distro.tidb}} DDL owner operations on etcd. The label is [type].\n        tidb_ddl_batch_add_index: The time cost of {{distro.tidb}} batch adding index. The label is [type].\n        tidb_ddl_deploy_syncer: The time cost of {{distro.tidb}} DDL schema syncer statistics, including init, start, watch, and clear. The label is [type].\n        tidb_load_schema: The time cost of {{distro.tidb}} loading schema. The label is [type].\n        tidb_meta_operation: The time cost of {{distro.tidb}} meta operations, including get/set schema and DDL jobs. The label is [instance].\n        tidb_auto_id_request: The time cost of handling requests for {{distro.tidb}} auto ID. The label is [type].\n        tidb_statistics_auto_analyze: The time cost of {{distro.tidb}} auto analyze. The label is [type].\n        tidb_gc: The time cost of KV storage garbage collection. The label is [instance].\n        tidb_gc_push_task: The time cost of KV storage range worker processing one task. The label is [instance].\n        tidb_batch_client_unavailable: The time cost of KV storage batch processing unavailable. The label is [type].\n        tidb_batch_client_wait: The time cost of {{distro.tidb}} KV storage batch processing client requests that are waiting. The label is [instance].\n        pd_start_tso_wait: The time cost of waiting for the start timestamp oracle. The label is [instance].\n        pd_tso_rpc: The time cost from sending TSO request to receiving the response. The label is [instance].\n        pd_tso_wait: The time cost from the client starting to wait for the timestamp to receiving the timestamp. The label is [instance].\n        pd_client_cmd: The time cost of {{distro.pd}} client command. The label is [type].\n        pd_handle_request: The time cost of {{distro.pd}} handling request. The label is [type].\n        pd_grpc_completed_commands: The time cost of {{distro.pd}} completing each kind of gRPC commands. The label is [grpc_method].\n        pd_operator_finish: The time cost of {{distro.pd}} completing each kind of scheduling commands. The label is [type].\n        pd_operator_step_finish: The time cost of {{distro.pd}} completing operating steps. The label is [type].\n        pd_handle_transactions: The time cost of {{distro.pd}} handling etcd transactions. The label is [result].\n        pd_region_heartbeat: The time cost of heartbeats in each {{distro.tikv}} instance. The label is [address].\n        etcd_wal_fsync: The time cost of etcd writing WAL into the persistent storage. The label is [instance].\n        pd_peer_round_trip: The latency of the network. The label is [To].\n        tikv_grpc_message: The time cost of handling {{distro.tikv}} gRPC messages. The label is [type].\n        tikv_cop_request: The time cost of Coprocessor handling read requests. The label is [req].\n        tikv_cop_handle: The time cost of handling Coprocessor requests. The label is [req].\n        tikv_cop_wait: The time cost of Coprocessor requests that wait for being handled. The label is [req].\n        tikv_scheduler_command: The time cost of executing commit command. The label is [type].\n        tikv_scheduler_latch_wait: The waiting time of {{distro.tikv}} latch in commit command. The label is [type].\n        tikv_handle_snapshot: The time cost of handling snapshots. The label is [type].\n        tikv_send_snapshot: The time cost of sending snapshots. The label is [instance].\n        tikv_storage_async_request: The time cost of processing asynchronous snapshot requests. The label is [type].\n        tikv_raft_append_log: The time cost of Raft appends log. The label is [instance].\n        tikv_raft_apply_log: The time cost of Raft apply log. The label is [instance].\n        tikv_raft_apply_wait: The time cost of Raft apply wait. The label is [instance].\n        tikv_raft_process: The time cost of peer processes in Raft. The label is [instance].\n        tikv_raft_propose_wait: The waiting time of each proposal. The label is [type].\n        tikv_raft_store_events: The time cost of raftstore events. The label is [type].\n        tikv_commit_log: The time cost of Raft commits log. The label is [instance].\n        tikv_check_split: The time cost of running split check. The label is [instance].\n        tikv_ingest_sst: The time cost of ingesting SST files. The label is [instance].\n        tikv_gc_tasks: The time cost of executing GC tasks. The label is [task].\n        tikv_pd_request: The time cost of {{distro.tikv}} sending requests to {{distro.pd}}. The label is [type].\n        tikv_lock_manager_deadlock_detect:\n        tikv_lock_manager_waiter_lifetime:\n        tikv_backup_range:\n        tikv_backup:\n        tidb_transaction_retry_num: '{{distro.tidb}} transaction retry count. The label is [instance].'\n        tidb_transaction_statement_num: The total number of {{distro.tidb}} statements within a transaction. Internal means the internal transaction of {{distro.tidb}}. The label is [sql_type].\n        tidb_txn_region_num: The number of Regions that each transaction operates. The label is [instance].\n        tidb_txn_kv_write_num: The number of KV writes per transaction execution. The label is [instance].\n        tidb_txn_kv_write_size: The KV write size per transaction execution. The label is [instance].\n        tidb_load_safepoint_total_num: The total count of safe point loading. The label is [instance].\n        tidb_lock_resolver_total_num: The total count of lock resolve. The label is [instance].\n        pseudo_estimation_total_count: The total count of {{distro.tidb}} Optimizer using pseudo estimation. The label is [instance, type].\n        dump_feedback_total_count: The total count of operations that {{distro.tidb}} dumping statistics back to KV storage. The label is [instance, type].\n        store_query_feedback_total_count: The total count of {{distro.tidb}} store querying feedback. The label is [instance, type].\n        update_stats_total_count: The total count of {{distro.tidb}} updating statistics using feed back. The label is [instance].\n        balance-leader-in: balance-leader-in is the total count of Leader moving into the {{distro.tikv}} store. The label is [address].\n        balance-leader-out: balance-leader-out is the total count of Leader moving out of the {{distro.tikv}} store. The label is [address].\n        balance-region-in: balance-region-in is the total count of Regions moving into the {{distro.tikv}} store. The label is [address].\n        balance-region-out: balance-region-in is the total count of Regions moving into the {{distro.tikv}} store. The label is [address].\n        Approximate Region size: The approximate Region size. The label is [instance].\n        store size: The storage size. The label is [instance, type].\n        tikv_scheduler_keys_read: The number of keys read by a command. The label is [instance, type].\n        tikv_scheduler_keys_written: The number of keys written by a command. The label is [instance, type].\n        tikv_scheduler_scan_details_total_num: The keys scan details of each CF when executing a command. The label is [instance,req,tag].\n        tikv_scheduler_stage_total_num: The total number of scheduler states. The label is [instance,type,stage].\n        tikv_gc_keys_total_num: The total number of keys in CF affected during GC. The label is [instance,cf,tag].\n        tidb_gc_worker_action_total_num: The total count of KV storage garbage collection. The label is [instance,type].\n        tikv_worker_handled_tasks_total_num: The total number of tasks handled by worker. The label is [instance,name].\n        tikv_worker_pending_tasks_total_num: The total number of pending and running tasks of worker. The label is [instance,name].\n        tikv_futurepool_handled_tasks_total_num: The total number of tasks handled by future_pool. The label is [instance,name].\n        tikv_futurepool_pending_tasks_total_num: The total number of pending and running tasks of future_pool. The label is [instance,name].\n        tikv_snapshot_kv_count: tikv_snapshot_kv_count. The label is [instance].\n        tikv_snapshot_size: The number of KV pairs within a snapshot. The label is [instance].\n        tikv_snapshot_state_total_count: tikv_snapshot_size. The label is [instance,type].\n        tikv_cop_scan_keys_num: The total number of {{distro.tikv}} Coprocessor scan keys. The label is [instance,req].\n        tikv_cop_total_response_total_size: '{{distro.tikv}} coprocessor response total size. The label is [instance].'\n        tikv_cop_scan_num: The total number of {{distro.tikv}} coprocessor scan operations. The label is [instance,req,tag,cf].\n        tikv_raft_sent_messages_total_num: The total number of sent Raft messages. The label is [instance,type].\n        tikv_flush_messages_total_num: The total number of flushed Raft messages. The label is [instance].\n        tikv_receive_messages_total_num: The total number of received Raft messages. The label is [instance].\n        tikv_raft_dropped_messages_total: The total number of dropped Raft messages. The label is [instance,type].\n        tikv_raft_proposals_total_num: The total number of raft proposals. The label is [instance,type].\n        tikv_grpc_error_total_count: The total number of the gRPC message failures. The label is [instance,type].\n        tikv_critical_error_total_count: The total number of the {{distro.tikv}} critical errors. The label is [instance,type].\n        tikv_scheduler_is_busy_total_count: The total number of Scheduler Busy events that make the {{distro.tikv}} instance temporarily unavailable. The label is [instance,db,type,stage].\n        tikv_channel_full_total_count: The total number of channel full errors, which will make the {{distro.tikv}} instance temporarily unavailable. The label is [instance,db,type].\n        tikv_coprocessor_request_error_total_count: The total number of Coprocessor errors. The label is [instance,reason].\n        tikv_engine_write_stall: Indicates occurrences of Write Stall events that make the {{distro.tikv}} instance temporarily unavailable. The label is [instance,db].\n        tikv_server_report_failures_total_count: The total number of reported failure messages. The label is [instance].\n        tikv_storage_async_request_error: The total number of storage request errors. The label is [instance,status,type].\n        tikv_lock_manager_detect_error_total_count: The total number of {{distro.tikv}} lock manager detect error. The label is [instance,type].\n        tikv_backup_errors_total_count: The total number of {{distro.tikv}} lock manager detected errors. The label is [instance,error].\n        node_disk_write_latency: The disk write latency in each node. The label is [instance,device].\n        node_disk_read_latency: The disk read latency in each node. The label is [instance,device].\n        grpc: The CPU utilization of each {{distro.tikv}} gRPC. The label is [instance].\n        raftstore: The CPU utilization of {{distro.tikv}} raftstore thread. The label is [instance].\n        Async apply: The CPU utilization of {{distro.tikv}} async apply thread. The label is [instance].\n        sched_worker: The CPU utilization of {{distro.tikv}} scheduler worker thread. The label is [instance].\n        snapshot: The CPU utilization of {{distro.tikv}} snapshot. The label is [instance].\n        unified read pool: The CPU utilization of {{distro.tikv}} unified read pool thread. The label is [instance].\n        storage read pool: The CPU utilization of {{distro.tikv}} storage read pool thread. The label is [instance].\n        storage read pool normal: The CPU utilization of {{distro.tikv}} storage read pool normal thread. The label is [instance].\n        storage read pool high: The CPU utilization of {{distro.tikv}} storage read pool high thread. The label is [instance].\n        storage read pool low: The CPU utilization of {{distro.tikv}} storage read pool low thread. The label is [instance].\n        cop: The CPU utilization of {{distro.tikv}} Coprocessor. The label is [instance].\n        cop normal: The CPU utilization of {{distro.tikv}} Coprocessor normal thread. The label is [instance].\n        cop high: The CPU utilization of {{distro.tikv}} Coprocessor high thread. The label is [instance].\n        cop low: The CPU utilization of {{distro.tikv}} Coprocessor low thread. The label is [instance].\n        rocksdb: The CPU utilization {{distro.tikv}} RocksDB. The label is [instance].\n        gc: The CPU utilization of {{distro.tikv}} GC. The label is [instance].\n        split_check: The CPU utilization of {{distro.tikv}} split_check. The label is [instance].\n        region_score: The Region score of store. The label is [address].\n        leader_score: The Leader score of store. The label is [address].\n        region_count: The Region count of store. The label is [address].\n        leader_count: The Leader score of store. The label is [address].\n        region_size: The Region size of store. The label is [address].\n        leader_size: The Leader size of store. The label is [address].\n        tikv_memtable_hit: The hit rate of memtable. The label is [instance].\n        tikv_block_all_cache_hit: The hit rate of all block cache. The label is [instance].\n        tikv_block_index_cache_hit: The hit rate of index block cache. The label is [instance].\n        tikv_block_filter_cache_hit: The hit rate of filter block cache. The label is [instance].\n        tikv_block_data_cache_hit: The hit rate of data block cache. The label is [instance].\n        tikv_block_bloom_prefix_cache_hit: The hit rate of bloom_prefix block cache. The label is [instance].\n        get duration: The time consumed when RocksDB executing get operations. The label is [instance].\n        seek duration: The time consumed when RocksDB executing seek operations. The label is [instance].\n        write duration: The time consumed when RocksDB executing write operations. The label is [instance].\n        WAL sync duration: The time consumed when RocksDB executing WAL sync operations. The label is [instance].\n        compaction duration: The time consumed when RocksDB executing compaction operations. The label is [instance].\n        SST read duration: The time consumed when RocksDB reading SST files. The label is [instance].\n        write stall duration: The time cost of write stall. The label is [instance].\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/diagnoseReportApp/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/diagnoseReportApp/translations/zh.yaml",
    "content": "diagnosis:\n  title: '{{distro.tidb}} 集群系统报告'\n  expand_all: 展开所有\n  fold_all: 收起所有\n  expand: 展开\n  fold: 收起\n  all_tables: 报告信息总览\n  tables:\n    category:\n      header: 基本信息\n      diagnose: 诊断\n      load: 负载\n      overview: 各组件信息总览\n      TiDB: '{{distro.tidb}} 组件'\n      PD: '{{distro.pd}} 组件'\n      TiKV: '{{distro.tikv}} 组件'\n      config: 配置\n      error: 错误\n    title:\n      compare_diagnose: 诊断对比\n      compare_report_time_range: 对比报告区间\n      top_10_slow_query_in_time_range_t1: t1 中的 Top 10 慢查询\n      top_10_slow_query_in_time_range_t2: t2 中的 Top 10 慢查询\n      top_10_slow_query_group_by_digest_in_time_range_t1: 按 SQL 指纹聚合的 t1 Top 10 慢查询\n      top_10_slow_query_group_by_digest_in_time_range_t2: 按 SQL 指纹聚合的 t2 Top 10 慢查询\n      slow_query_with_diff_plan_in_time_range_t1: t1 中的不同执行计划的慢查询\n      slow_query_with_diff_plan_in_time_range_t2: t2 中的不同执行计划的慢查询\n      diagnose_in_time_range_t1: t1 中的诊断信息\n      diagnose_in_time_range_t2: t2 中的诊断信息\n      max_diff_item: 最大不同项\n      slow_query_t2: t2 中的慢查询\n      generate_report_error: 生成报告的报错\n      report_time_range: 报告区间\n      diagnose: 诊断结果\n      total_time_consume: 各组件总耗时\n      total_error: 各组件总报错数\n      time_consume: 耗时\n      tidb_time_consume: '{{distro.tidb}} 中事件耗时'\n      transaction: '{{distro.tidb}} 事务'\n      tidb_connection_count: '{{distro.tidb}} 连接数'\n      statistics_info: 统计信息\n      ddl_owner: DDL Owner\n      scheduler_initial_config: 调度器初始配置\n      scheduler_change_config: 调度器配置修改历史\n      tidb_gc_initial_config: '{{distro.tidb}} GC 初始配置'\n      tidb_gc_change_config: '{{distro.tidb}} GC 配置修改历史'\n      tikv_rocksdb_initial_config: '{{distro.tikv}} RocksDB 初始配置'\n      tikv_rocksdb_change_config: '{{distro.tikv}} RocksDB 配置修改历史'\n      tikv_raftstore_initial_config: '{{distro.tikv}} RaftStore 初始配置'\n      tikv_raftstore_change_config: '{{distro.tikv}} RaftStore 配置修改历史'\n      pd_time_consume: '{{distro.pd}} 中事件耗时'\n      balance_leader_region: Leader/Region 调度数\n      approximate_region_size: Approximate Region 大小\n      tikv_engine_size: '{{distro.tikv}} 实例存储大小'\n      tikv_time_consume: '{{distro.tikv}} 中事件耗时'\n      scheduler_info: '{{distro.tikv}} 调度器信息'\n      gc_info: GC 信息\n      task_info: '{{distro.tikv}} 任务信息'\n      snapshot_info: '{{distro.tikv}} 快照信息'\n      coprocessor_info: Coprocessor 信息\n      raft_info: Raft 信息\n      tikv_error: '{{distro.tikv}} 错误'\n      tidb_current_config: '{{distro.tidb}} 当前配置'\n      pd_current_config: '{{distro.pd}} 当前配置'\n      tikv_current_config: '{{distro.tikv}} 当前配置'\n      node_load_info: 服务器负载信息\n      process_cpu_usage: 各实例 CPU 使用率\n      process_memory_usage: 各实例内存消耗\n      tidb/pd_goroutines_count: '{{distro.tidb}}/{{distro.pd}} 的 Goroutines 数量'\n      tikv_thread_cpu_usage: '{{distro.tikv}} 的 CPU 使用情况'\n      store_status: '{{distro.tikv}} 节点的存储状态'\n      cluster_status: 集群状态\n      etcd_status: etcd 状态\n      cluster_info: 集群拓扑信息\n      cache_hit: 缓存命中率\n      cluster_hardware: 集群硬件信息\n      rocksdb_time_consume: RocksDB 事件耗时\n      top_10_slow_query: Top 10 慢查询\n      top_10_slow_query_group_by_digest: 按 SQL 指纹聚合的 Top 10 慢查询\n      slow_query_with_diff_plan: 不同执行计划的慢查询\n    comment:\n      compare_diagnose: 通过与参考时间的比较，自动诊断集群问题。\n      max_diff_item: 两段时间中的最大不同项。\n      diagnose: 该表显示的是自动诊断的结果，即集群中出现的问题。\n      total_time_consume: 该表显示的是 {{distro.tidb}}/{{distro.tikv}}/{{distro.pd}} 组件中各事件的耗时。METRIC_NAME 是事件名称；LABEL 是事件标签，如实例、事件类型等；TIME_RATIO 是该事件的总时间除以 TIME_RATIO 为 1 的事件的总时间；TOTAL_TIME 是该事件的总耗时；TOTAL_COUNT 是该事件的总计数；P999 是 0.999 分位数的最大时间；P99 是 0.99 分位数的最大时间；P90 是 0.90 分位数的最大时间；P80 是 0.80 分位数的最大时间。\n      total_error: 该表显示的是各错误事件的数量。METRIC_NAME 是错误事件名称；LABEL 是事件标签，如实例、事件类型；TOTAL_COUNT 是该错误事件的总数。\n      tidb_time_consume: 该表显示的是 {{distro.tidb}} 组件中各事件的耗时。METRIC_NAME 是事件名称；LABEL 是事件标签，如实例、事件类型等；TIME_RATIO 是该事件的总时间除以 TIME_RATIO 为 1 的事件的总时间；TOTAL_TIME 是该事件的总耗时；TOTAL_COUNT 是该事件的总计数；P999 是 0.999 分位数的最大时间；P99 是 0.99 分位数的最大时间；P90 是 0.90 分位数的最大时间；P80 是 0.80 分位数的最大时间。\n      transaction: 该表显示了 {{distro.tidb}} 事务的统计信息。METRIC_NAME 是对象名；LABEL 是对象标签，如实例、事件类型等；TOTAL_VALUE 是该对象的总大小；TOTAL_COUNT 是该对象的总计数；P999 为 0.999 分位数的最大值；P99 是 0.99 分位数的最大值；P90 是 0.90 分位数的最大值；P80 是 0.80 分位数的最大值。\n      tidb_connection_count: '{{distro.tidb}} 服务器的连接数。'\n      ddl_owner: DDL Owner 的信息。注意：如果没有 DDL 请求被执行，下面的 Owner 信息可能为空，这并不表示 DDL Owner 不存在。\n      scheduler_initial_config: '{{distro.pd}} 调度器的初始配置值。初始时间是报表的开始时间。'\n      scheduler_change_config: '{{distro.pd}} 调度器的配置更改历史。APPROXIMATE_CHANGE_TIME 为最近的有效更改时间。'\n      tidb_gc_initial_config: '{{distro.tidb}} GC 的初始配置值。初始时间是报表的开始时间。'\n      tidb_gc_change_config: '{{distro.tidb}} GC 的配置更改历史。APPROXIMATE_CHANGE_TIME 为最近的有效更改时间。'\n      tikv_rocksdb_initial_config: '{{distro.tikv}} RocksDB 的初始配置值。初始时间是报表的开始时间。'\n      tikv_rocksdb_change_config: '{{distro.tikv}} RocksDB 的配置更改历史。APPROXIMATE_CHANGE_TIME 为最近的有效更改时间。'\n      tikv_raftstore_initial_config: '{{distro.tikv}} RaftStore 的初始配置值。初始时间是报表的开始时间。'\n      tikv_raftstore_change_config: '{{distro.tikv}} RaftStore 的配置更改历史。APPROXIMATE_CHANGE_TIME 为最近的有效更改时间。'\n      pd_time_consume: 该表显示的是 {{distro.pd}} 组件中各事件的耗时。METRIC_NAME 是事件名称；LABEL 是事件标签，如实例、事件类型等；TIME_RATIO 是该事件的总时间除以 TIME_RATIO 为 1 的事件的总时间；TOTAL_TIME 是该事件的总耗时；TOTAL_COUNT 是该事件的总计数；P999 是 0.999 分位数的最大时间；P99 是 0.99 分位数的最大时间；P90 是 0.90 分位数的最大时间；P80 是 0.80 分位数的最大时间。\n      tikv_time_consume: 该表显示的是 {{distro.tikv}} 组件中各事件的耗时。METRIC_NAME 是事件名称；LABEL 是事件标签，如实例、事件类型等；TIME_RATIO 是该事件的总时间除以 TIME_RATIO 为 1 的事件的总时间；TOTAL_TIME 是该事件的总耗时；TOTAL_COUNT 是该事件的总计数；P999 是 0.999 分位数的最大时间；P99 是 0.99 分位数的最大时间；P90 是 0.90 分位数的最大时间；P80 是 0.80 分位数的最大时间。\n    table:\n      name:\n        tidb_transaction: '{{distro.tidb}} 事务'\n        tidb_kv_request: '{{distro.tidb}} KV 请求'\n        tidb_slow_query: 慢查询\n        tidb_ddl_handle_job: DDL 任务\n        tidb_ddl_batch_add_index: 批量索引添加\n        tidb_load_schema: Schema 加载\n        tidb_meta_operation: '{{distro.tidb}} 元操作'\n        tidb_auto_id_request: '{{distro.tidb}} 自增 ID 请求'\n        tidb_statistics_auto_analyze: '{{distro.tidb}} 自动分析'\n        tidb_gc: 垃圾回收\n        pd_client_cmd: '{{distro.pd}} 客户端命令'\n        pd_handle_request: '{{distro.pd}} 请求'\n        pd_handle_transactions: etcd 事务\n        pd_peer_round_trip: 网络延迟\n        tikv_cop_request: Coprocessor 读请求\n        tikv_cop_handle: Coprocessor 请求\n        tikv_handle_snapshot: 快照处理\n        tikv_send_snapshot: 快照发送\n        tikv_commit_log: Raft 提交日志\n        tidb_transaction_retry_num: '{{distro.tidb}} 事务重试数'\n        tidb_txn_region_num: 事务操作的 Region 数量\n        tidb_txn_kv_write_num: 事务执行的 KV 写入数量\n        tidb_txn_kv_write_size: 事务执行的 KV 写入大小\n        tidb_load_safepoint_total_num: 安全点装载总数量\n        tikv_scheduler_stage_total_num: 调度程序状态的总数量\n        tikv_worker_handled_tasks_total_num: worker 处理的任务总数量\n        tikv_worker_pending_tasks_total_num: 工作进程的挂起和运行任务的总数量\n        tikv_futurepool_handled_tasks_total_num: future_pool 处理的任务总数量\n        tikv_futurepool_pending_tasks_total_num: future_pool 总挂起和运行任务数量\n        tikv_snapshot_kv_count: 快照的 KV 数量\n        tikv_snapshot_size: 快照大小\n        tikv_cop_scan_keys_num: '{{distro.tikv}} Coprocessor 扫描键总数量'\n        tikv_cop_total_response_total_size: '{{distro.tikv}} Coprocessor 响应总大小'\n        tikv_cop_scan_num: '{{distro.tikv}} Coprocessor 扫描操作总数量'\n        tikv_raft_sent_messages_total_num: 发送的 Raft 消息的总数量\n        tikv_flush_messages_total_num: 持久化 Raft 消息的总数量\n        tikv_receive_messages_total_num: 接受 Raft 消息的总数量\n        tikv_raft_dropped_messages_total: 丢弃 Raft 消息的总数量\n        tikv_raft_proposals_total_num: Raft proposal 的总数量\n        tikv_grpc_error_total_count: gRPC 消息失败的总数量\n        tikv_critical_error_total_count: '{{distro.tikv}} 临界误差的总数量'\n        tikv_coprocessor_request_error_total_count: Coprocessor 错误总数量\n        node_disk_write_latency: 磁盘写延迟\n        node_disk_read_latency: 磁盘读取延迟\n        sched_worker: 调度器工作线程\n        tikv_memtable_hit: memtable 命中率\n        tikv_block_all_cache_hit: 所有块缓存命中率\n        tikv_block_index_cache_hit: 索引块缓存命中率\n        tikv_block_filter_cache_hit: 过滤块缓存命中率\n        tikv_block_data_cache_hit: 数据块缓存命中率\n        tikv_block_bloom_prefix_cache_hit: bloom_prefix 块缓存命中率\n      comment:\n        tidb_query: SQL 查询耗时，标签是\"SQL 类型\"。\n        tidb_get_token(us): 会话获取令牌以执行 SQL 查询的耗时，标签是\"实例\"。\n        tidb_parse: 解析 SQL 的耗时，标签是\"SQL 类型\"。\n        tidb_compile: 构建查询计划的时间，标签是\"SQL 类型\"。\n        tidb_execute: 执行 SQL 的时间，不包括获得查询结果的时间，标签是\"SQL 类型\"。\n        tidb_distsql_execution: 执行 distsql 的耗时，标签是\"类型\"。\n        tidb_cop: KV storage Coprocessor 处理的耗时，标签是\"实例\"。\n        tidb_transaction: 事务执行 durations 的时间成本，包括重试，标签是\"SQL 类型\"。\n        tidb_transaction_local_latch_wait: 事务执行时本地锁占用的时间，标签是\"实例\"。\n        tidb_kv_backoff: '{{distro.tidb}} 事务锁等待键值存储的时间，标签是\"类型\"。'\n        tidb_kv_request: KV 请求 durations 的耗时，标签是\"类型\"。\n        tidb_slow_query: '{{distro.tidb}} 慢查询的时间开销，标签是\"实例\"。'\n        tidb_slow_query_cop_process: '{{distro.tidb}} 的慢查询总 cop 处理的耗时，标签是\"实例\"。'\n        tidb_slow_query_cop_wait: '{{distro.tidb}} 的慢查询总 cop 的等待时间，标签是\"实例\"。'\n        tidb_ddl_handle_job: 处理 {{distro.tidb}} DDL 任务的耗时，标签是\"类型\"。\n        tidb_ddl_worker: DDL worker 处理任务的耗时，标签是\"实例\"。\n        tidb_ddl_update_self_version: '{{distro.tidb}} schema 同步器版本更新的耗时，标签是\"结果\"。'\n        tidb_owner_handle_syncer: 在 etcd 上执行 {{distro.tidb}} DDL 所有者操作的耗时，标签是\"类型\"。\n        tidb_ddl_batch_add_index: '{{distro.tidb}} 批量添加索引的耗时，标签是\"类型\"。'\n        tidb_ddl_deploy_syncer: '{{distro.tidb}} DDL schema 同步器统计的时间成本，包括 init、start、watch、clear，标签是\"类型\"。'\n        tidb_load_schema: 加载 {{distro.tidb}} schema 的时间成本，标签是\"类型\"。\n        tidb_meta_operation: '{{distro.tidb}} 元操作的时间成本，包括 get/set 模式和 DDL 作业，标签是\"实例\"。'\n        tidb_auto_id_request: '{{distro.tidb}} 自增 ID 处理 ID 请求的耗时，标签是\"类型\"。'\n        tidb_statistics_auto_analyze: 自动分析 {{distro.tidb}} 的耗时，标签是\"类型\"。\n        tidb_gc: KV 存储垃圾回收的时间，标签是\"实例\"。\n        tidb_gc_push_task: KV 存储范围内 worker 处理一项任务的耗时，标签是\"实例\"。\n        tidb_batch_client_unavailable: KV 存储批量处理不可用的耗时，标签是\"类型\"。\n        tidb_batch_client_wait: KV 存储批量处理客户端等待请求的耗时，标签是\"实例\"。\n        pd_start_tso_wait: 等待获取开始时间戳 timestamp 的耗时，标签是\"实例\"。\n        pd_tso_rpc: 发送 TSO 请求直到收到响应的时间，标签是\"实例\"。\n        pd_tso_wait: 客户端开始等待 timestamp 直到收到 timestamp 结果的耗时，标签是\"实例\"。\n        pd_client_cmd: '{{distro.pd}} 客户端命令的耗时，标签是\"类型\"。'\n        pd_handle_request: '{{distro.pd}} 处理请求的耗时，标签是\"类型\"。'\n        pd_grpc_completed_commands: '{{distro.pd}} 完成各种 gRPC 命令的耗时，标签是\"gRPC 方法\"。'\n        pd_operator_finish: '{{distro.pd}} 完成各种调度命令的时间，标签是\"类型\"。'\n        pd_operator_step_finish: '{{distro.pd}} 完成操作步骤的耗时，标签是\"类型\"。'\n        pd_handle_transactions: '{{distro.pd}} 处理 etcd 事务的耗时，标签是\"结果\"。'\n        pd_region_heartbeat: 每个 {{distro.tikv}} 实例中心跳的耗时，标签是\"服务地址\"。\n        etcd_wal_fsync: etcd 将 WAL 写入持久存储器的耗时，标签是\"实例\"。\n        pd_peer_round_trip: 网络的延迟，标签是\"实例\"。\n        tikv_grpc_message: gRPC 报文的 {{distro.tikv}} 处理耗时，标签是\"类型\"。\n        tikv_cop_request: Coprocessor 处理读请求的时间开销，标签是\"请求\"。\n        tikv_cop_handle: 处理 Coprocessor 请求的时间开销，标签是\"请求\"。\n        tikv_cop_wait: Coprocessor 请求等待处理的耗时，标签是\"请求\"。\n        tikv_scheduler_command: 执行 commit 命令的耗时，标签是\"类型\"。\n        tikv_scheduler_latch_wait: 提交命令中 {{distro.tikv}} 锁存器等待的时间开销，标签是\"类型\"。\n        tikv_handle_snapshot: 处理快照的时间开销，标签是\"类型\"。\n        tikv_send_snapshot: 发送快照的时间开销，标签是\"实例\"。\n        tikv_storage_async_request: 处理异步快照请求的时间开销，标签是\"类型\"。\n        tikv_raft_append_log: Raft appends log 的时间开销，标签是\"实例\"。\n        tikv_raft_apply_log: Raft apply log 的时间开销，标签是\"实例\"。\n        tikv_raft_apply_wait: Raft apply wait 的时间开销，标签是\"实例\"。\n        tikv_raft_process: Peer processes in Raft 的时间开销，标签是\"实例\"。\n        tikv_raft_propose_wait: 每一个 Raft 提议的等待时间，标签是\"类型\"。\n        tikv_raft_store_events: RaftStore events 的时间开销，标签是\"类型\"。\n        tikv_commit_log: Raft 提交日志的时间开销，标签是\"实例\"。\n        tikv_check_split: 运行分割检查的耗时，标签是\"实例\"。\n        tikv_ingest_sst: Ingest SST 文件的耗时，标签是\"实例\"。\n        tikv_gc_tasks: 执行 GC 任务的耗时，标签是\"任务\"。\n        tikv_pd_request: '{{distro.tikv}} 向 {{distro.pd}} 发送请求的耗时，标签是\"类型\"。'\n        tikv_lock_manager_deadlock_detect:\n        tikv_lock_manager_waiter_lifetime:\n        tikv_backup_range:\n        tikv_backup:\n        tidb_transaction_retry_num: '{{distro.tidb}} 事务重试次数，标签是\"实例\"。'\n        tidb_transaction_statement_num: 一个事务中 {{distro.tidb}} 语句数的总数量。Internal 是指 {{distro.tidb}} 内部事务，标签是\"实例\"。'\n        tidb_txn_region_num: 每个事务进行操作的区域数量，标签是\"实例\"。\n        tidb_txn_kv_write_num: 每个事务执行的 KV 写入数量，标签是\"实例\"。\n        tidb_txn_kv_write_size: 每个事务执行的 KV 写入大小，标签是\"实例\"。\n        tidb_load_safepoint_total_num: 安全点装载总数量，标签是\"实例\"。\n        tidb_lock_resolver_total_num: lock resolve 的总数量，标签是\"实例\"。\n        pseudo_estimation_total_count: 使用伪估计的 {{distro.tidb}} 优化器的总数量，标签是\"实例\"，\"类型\"。\n        dump_feedback_total_count: '{{distro.tidb}} 转储统计数据回 KV 存储的操作总数量，标签是\"实例\"。'\n        store_query_feedback_total_count: '{{distro.tidb}} 存储查询反馈的总数量，标签是\"实例\"。'\n        update_stats_total_count: 使用反馈更新统计数据的 {{distro.tidb}} 总数量，标签是\"实例\"。\n        blance-leader-in: Leader 移动到 {{distro.tikv}} 存储的总数量，标签是\"实例\"。\n        blance-leader-out: Leader 移出 {{distro.tikv}} 存储的总数量，标签是\"实例\"。\n        blance-region-in: 移动到 {{distro.tikv}} 存储的 Region 总数量，标签是\"实例\"。\n        blance-region-out: 移出 {{distro.tikv}} 存储的的 Region 总数量，标签是\"实例\"。\n        Approximate Region size: 近似 Region 大小，标签是\"实例\"。\n        store size: 存储大小，标签是\"实例\"。\n        tikv_scheduler_keys_read: 由一条命令读取的键数量，标签是\"实例\"，\"类型\"。\n        tikv_scheduler_keys_written: 由一条命令写入的键数量，标签是\"实例\"，\"类型\"。\n        tikv_scheduler_scan_details_total_num: 在执行一条命令时，扫描每个 CF 的详细信息的总数量，标签是\"实例\"。\n        tikv_scheduler_stage_total_num: 调度程序状态的总数量，标签是\"实例\"，\"阶段\"，\"类型\"。\n        tikv_gc_keys_total_num: GC 期间 CF 中受影响的键的总数量，标签是\"实例\"。\n        tidb_gc_worker_action_total_num: KV 存储垃圾回收总量，标签是\"实例\"，\"类型\"。\n        tikv_worker_handled_tasks_total_num: worker 处理的任务总数量，标签是\"实例\"。\n        tikv_worker_pending_tasks_total_num: 工作进程的挂起和运行任务的总数量，标签是\"实例\"。\n        tikv_futurepool_handled_tasks_total_num: future_pool 处理的任务总数量，标签是\"实例\"。\n        tikv_futurepool_pending_tasks_total_num: future_pool 的总挂起和运行任务，标签是\"实例\"。\n        tikv_snapshot_kv_count: tikv_snapshot_kv_count，标签是\"实例\"。\n        tikv_snapshot_size: 快照内 KV 的数量，标签是\"实例\"。\n        tikv_snapshot_state_total_count: '{{distro.tikv}} 的快照大小，标签是\"实例\"，\"类型\"。'\n        tikv_cop_scan_keys_num: '{{distro.tikv}} Coprocessor 扫描键总数量，标签是\"实例\"。'\n        tikv_cop_total_response_total_size: '{{distro.tikv}} Coprocessor 响应总大小，标签是\"实例\"。'\n        tikv_cop_scan_num: '{{distro.tikv}} Coprocessor 扫描操作总数量，标签是\"实例\"。'\n        tikv_raft_sent_messages_total_num: 发送的 Raft 消息的总数量，标签是\"实例\"，\"类型\"。\n        tikv_flush_messages_total_num: Raft 上刷新了的信息总数量，标签是\"实例\"。\n        tikv_receive_messages_total_num: Raft 收到的的信息总数量，标签是\"实例\"。\n        tikv_raft_dropped_messages_total: Raft 丢掉的的信息总数量，标签是\"实例\"，\"类型\"。\n        tikv_raft_proposals_total_num: Raft 提议的的总数量，标签是\"实例\"，\"类型\"。\n        tikv_grpc_error_total_count: gRPC 消息失败的总数量，标签是\"实例\"，\"类型\"。\n        tikv_critical_error_total_count: '{{distro.tikv}} 临界误差的总数量，标签是\"实例\"，\"类型\"。'\n        tikv_scheduler_is_busy_total_count: 使 {{distro.tikv}} 实例暂时不可用的调度器繁忙事件的总数量，标签是\"实例\"。\n        tikv_channel_full_total_count: 通道完全错误的总数量，它将使 {{distro.tikv}} 实例暂时不可用，标签是\"实例\"。\n        tikv_coprocessor_request_error_total_count: Coprocessor 错误的总数量，标签是\"实例\"，\"原因\"。\n        tikv_engine_write_stall: 指示使 {{distro.tikv}} 实例暂时不可用的写失速事件，标签是\"实例\"。\n        tikv_server_report_failures_total_count: 报告失败消息的总数量，标签是\"实例\"。\n        tikv_storage_async_request_error: 存储请求错误的总数量，标签是\"实例\"，\"状态\"，\"类型\"。\n        tikv_lock_manager_detect_error_total_count: '{{distro.tikv}} 锁管理器检测错误的总数量，标签是\"实例\"，\"类型\"。'\n        tikv_backup_errors_total_count: '{{distro.tikv}} 锁管理的总错误，标签是\"实例\"，\"错误\"。'\n        node_disk_write_latency: 每个节点的磁盘写延迟，标签是\"实例\"，\"设备\"。\n        node_disk_read_latency: 每个节点的磁盘读取延迟，标签是\"实例\"，\"设备\"。\n        grpc: 每个 {{distro.tikv}} gRPC 的 CPU 利用率，标签是\"实例\"。'\n        raftstore: '{{distro.tikv}} RaftStore 线程的 CPU 利用率，标签是\"实例\"。'\n        Async apply: '{{distro.tikv}} 异步应用线程的 CPU 利用率，标签是\"实例\"。'\n        sched_worker: '{{distro.tikv}} 调度器工作线程的 CPU 利用率，标签是\"实例\"。'\n        snapshot: '{{distro.tikv}} 快照的 CPU 利用率，标签是\"实例\"。'\n        unified read pool: '{{distro.tikv}} 统一读池线程的 CPU 利用率，标签是\"实例\"。'\n        storage read pool: '{{distro.tikv}} 存储读池线程的 CPU 利用率，标签是\"实例\"。'\n        storage read pool normal: '{{distro.tikv}} 存储读池普通线程的 CPU 利用率，标签是\"实例\"。'\n        storage read pool high: '{{distro.tikv}} 存储较高读线程的 CPU 利用率，标签是\"实例\"。'\n        storage read pool low: '{{distro.tikv}} 存储较低读线程的 CPU 利用率，标签是\"实例\"。'\n        cop: '{{distro.tikv}} Coprocessor 的 CPU 利用率，标签是\"实例\"。'\n        cop normal: '{{distro.tikv}} Coprocessor 普通线程的 CPU 利用率，标签是\"实例\"。'\n        cop high: '{{distro.tikv}} Coprocessor 高线程的 CPU 利用率，标签是\"实例\"。'\n        cop low: '{{distro.tikv}} Coprocessor 低线程的 CPU 利用率，标签是\"实例\"。'\n        rocksdb: '{{distro.tikv}} RocksDB 的 CPU 利用率，标签是\"实例\"。'\n        gc: '{{distro.tikv}} GC 的 CPU 利用率，标签是\"实例\"。'\n        split_check: '{{distro.tikv}} split_chec 的 CPU 利用率，标签是\"实例\"。'\n        region_score: store 的 Region 得分，标签是\"服务地址\"。\n        leader_score: store 的 Leader 得分，标签是\"服务地址\"。\n        region_count: store 的 Region 数量，标签是\"服务地址\"。\n        leader_count: store 的 Leader 数量，标签是\"服务地址\"。\n        region_size: store 的 Region 大小，标签是\"服务地址\"。\n        leader_size: store 的 Leader 大小，标签是\"服务地址\"。\n        tikv_memtable_hit: memtable 的命中率，标签是\"实例\"。\n        tikv_block_all_cache_hit: 所有块缓存的命中率，标签是\"实例\"。\n        tikv_block_index_cache_hit: 索引块缓存的命中率，标签是\"实例\"。\n        tikv_block_filter_cache_hit: 过滤块缓存的命中率，标签是\"实例\"。\n        tikv_block_data_cache_hit: 数据块缓存的命中率，标签是\"实例\"。\n        tikv_block_bloom_prefix_cache_hit: bloom_prefix 块缓存的命中率，标签是\"实例\"。\n        get duration: RocksDB 执行 get 操作的耗时，标签是\"实例\"。\n        seek duration: RocksDB 执行 seek 操作的耗时，标签是\"实例\"。\n        write duration: RocksDB 执行写操作的耗时，标签是\"实例\"。\n        WAL sync duration: RocksDB 执行 WAL 同步操作的耗时，标签是\"实例\"。\n        compaction duration: RocksDB 执行压缩操作的耗时，标签是\"实例\"。\n        SST read duration: RocksDB 读取 SST 文件的耗时，标签是\"实例\"。\n        write stall duration: 由写停顿引起的时间，标签是\"实例\"。\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/diagnoseReportApp/types.ts",
    "content": "import { createContext } from 'react'\n\nexport interface TableRowDef {\n  values: string[]\n  sub_values: string[][]\n  comment: string\n}\n\nexport interface TableDef {\n  category: string[]\n  title: string\n  comment: string\n  column: string[]\n  rows: TableRowDef[]\n}\n\nexport const ExpandContext = createContext(false)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/react-app-env.d.ts",
    "content": "declare module '*.module.css' {\n  const classes: { readonly [key: string]: string }\n  export default classes\n}\n\ndeclare module '*.module.less' {\n  const classes: { readonly [key: string]: string }\n  export default classes\n}\n\ndeclare module '*.yaml' {\n  const classes: { readonly [key: string]: string }\n  export default classes\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/styles/override.less",
    "content": "// reset ::selection pseudo element values\n::selection {\n  color: currentColor;\n  background-color: #b3d6ff;\n}\n\nhtml {\n  font-size: 14px;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/styles/style.less",
    "content": "@root-entry-name: default;\n\n@import 'antd/lib/style/components.less';\n// it is expected to import 'antd/es/style/components.less' but it doesn't exist this file\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/utils/distro/assetsRes.ts",
    "content": "import publicPathPrefix from '../publicPathPrefix'\n\nconst lightLogoSvg = `${publicPathPrefix}/distro-res/logo-icon-light.svg`\n\nexport { lightLogoSvg }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/utils/distro/stringsRes.ts",
    "content": "import { updateDistro } from '@pingcap/tidb-dashboard-lib'\n\nimport defDistroStringsRes from './strings_res.json'\n\nlet distro = defDistroStringsRes\n\n// it is a base64 encoded string\nlet distroStringsRes = document\n  .querySelector('meta[name=\"x-distro-strings-res\"]')\n  ?.getAttribute('content')\n\nif (distroStringsRes && distroStringsRes !== '__DISTRO_STRINGS_RES__') {\n  try {\n    const distroObj = JSON.parse(atob(distroStringsRes))\n    distro = {\n      ...defDistroStringsRes,\n      ...distroObj\n    }\n  } catch (error) {\n    console.log(error)\n  }\n}\n\nupdateDistro(distro)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/utils/distro/strings_res.json",
    "content": "{\n  \"tidb\": \"TiDB\",\n  \"tikv\": \"TiKV\",\n  \"pd\": \"PD\",\n  \"tiflash\": \"TiFlash\",\n  \"ticdc\": \"TiCDC\",\n  \"tiproxy\": \"TiProxy\",\n  \"tso\": \"TSO\",\n  \"scheduling\": \"Scheduling\"\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/utils/globalConfig.ts",
    "content": "import {\n  IConProfilingConfig,\n  IKeyVizConfig,\n  IOverviewConfig,\n  ISlowQueryConfig,\n  IStatementConfig,\n  ITopSQLConfig,\n  ITopSlowQueryConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nexport type AppOptions = {\n  lang: string\n  hideNav: boolean\n  // hidePageLoadProgress controls whether show the thin progress bar in the top of the page when switching pages\n  hidePageLoadProgress: boolean\n\n  skipNgmCheck: boolean\n  skipLoadAppInfo: boolean\n  skipReloadWhoAmI: boolean\n}\n\nexport const defAppOptions: AppOptions = {\n  lang: 'en',\n  hideNav: false,\n  hidePageLoadProgress: false,\n\n  skipNgmCheck: false,\n  skipLoadAppInfo: false,\n  skipReloadWhoAmI: false\n}\n\nexport type ClientOptions = {\n  apiPathBase: string\n  apiToken: string\n}\n\nexport type ClusterInfo = {\n  provider?: string\n  region?: string\n  orgId?: string\n  projectId?: string\n  clusterId?: string\n  deployType?: string // dedicated / shared\n  env?: string\n}\n\nexport type AppsConfig = {\n  overview?: Partial<IOverviewConfig>\n  slowQuery?: Partial<ISlowQueryConfig>\n  topSlowQuery?: Partial<ITopSlowQueryConfig>\n  statement?: Partial<IStatementConfig>\n  topSQL?: Partial<ITopSQLConfig>\n  conProf?: Partial<IConProfilingConfig>\n  keyViz?: Partial<IKeyVizConfig>\n}\n\nexport type GlobalConfig = {\n  appOptions?: AppOptions\n  clientOptions: ClientOptions\n  clusterInfo: ClusterInfo\n\n  appsConfig?: AppsConfig\n\n  // internal api for performance insight\n  performanceInsightBaseUrl: string\n\n  // appsDisabled has a higher priority than appsEnabled\n  appsDisabled?: string[]\n  appsEnabled?: string[]\n}\n\n// export const GlobalConfigContext = createContext<IGlobalConfig | null>(null)\n// export const GlobalConfigProvider = GlobalConfigContext.Provider\n\n/////////////////////////////////////\n\nlet _globalConfig: GlobalConfig\n\nexport function setGlobalConfig(c: GlobalConfig) {\n  _globalConfig = c\n}\nexport function getGlobalConfig(): GlobalConfig {\n  return _globalConfig\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/utils/publicPathPrefix.ts",
    "content": "let prefix = '.'\n\nexport default prefix\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/utils/registry.ts",
    "content": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport singleSpaReact from 'single-spa-react'\nimport * as singleSpa from 'single-spa'\n\nimport { i18n, routing } from '@pingcap/tidb-dashboard-lib'\n\nimport { AppOptions, getGlobalConfig } from './globalConfig'\n\nexport default class AppRegistry {\n  public defaultRouter = ''\n  public apps = {}\n  public constructor(public appOptions: AppOptions) {}\n\n  static newReactSpaApp = function (rootComponentAsyncLoader, targetDomId) {\n    const reactLifecycles = singleSpaReact({\n      React,\n      ReactDOM,\n      loadRootComponent: async () => {\n        const component = await rootComponentAsyncLoader()\n        if (component.default) {\n          return component.default\n        }\n        return component\n      },\n      domElementGetter: () => document.getElementById(targetDomId)!\n    })\n    return {\n      bootstrap: [reactLifecycles.bootstrap],\n      mount: [reactLifecycles.mount],\n      unmount: [reactLifecycles.unmount]\n    }\n  }\n\n  /**\n   * Register a TiDB Dashboard application.\n   *\n   * This function is a light encapsulation over single-spa's registerApplication\n   * which provides some extra registry capabilities.\n   *\n   * @param {{\n   *  id: string,\n   *  reactRoot: Function,\n   *  routerPrefix: string,\n   *  indexRoute: string,\n   *  isDefaultRouter: boolean,\n   *  icon: string,\n   * }} app\n   */\n  register(app) {\n    // return if this app is disabled\n    const disabledApps = getGlobalConfig().appsDisabled\n    if (disabledApps && disabledApps.includes(app.id)) {\n      return this\n    }\n    const enabledApps = getGlobalConfig().appsEnabled\n    if (enabledApps && !enabledApps.includes(app.id)) {\n      return this\n    }\n\n    if (app.translations) {\n      i18n.addTranslations(app.translations)\n    }\n\n    singleSpa.registerApplication(\n      app.id,\n      AppRegistry.newReactSpaApp(app.reactRoot, '__spa_content__'),\n      () => {\n        return routing.isLocationMatchPrefix(app.routerPrefix)\n      },\n      {\n        registry: this,\n        app\n      }\n    )\n    if (!app.indexRoute) {\n      app.indexRoute = app.routerPrefix\n    }\n    if (!this.defaultRouter || app.isDefaultRouter) {\n      this.defaultRouter = app.indexRoute\n    }\n    this.apps[app.id] = app\n    return this\n  }\n\n  /**\n   * Get the default router for initial routing.\n   */\n  getDefaultRouter() {\n    return this.defaultRouter || '/'\n  }\n\n  /**\n   * Get the registry of the current active app.\n   */\n  getActiveApp() {\n    const mountedApps = singleSpa.getMountedApps()\n    for (let i = 0; i < mountedApps.length; i++) {\n      const app = mountedApps[i]\n      if (this.apps[app] !== undefined) {\n        return this.apps[app]\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/src/utils/store.ts",
    "content": "import { store, ReqConfig } from '@pingcap/tidb-dashboard-lib'\nimport client, { InfoInfoResponse } from '~/client'\n\nexport async function reloadWhoAmI(): Promise<boolean> {\n  try {\n    const resp = await client.getInstance().infoWhoami({\n      handleError: 'custom'\n    } as ReqConfig)\n    store.update((s) => {\n      s.whoAmI = resp.data\n    })\n    return true\n  } catch (ex) {\n    store.update((s) => {\n      s.whoAmI = undefined\n    })\n    return false\n  }\n}\n\nexport async function mustLoadAppInfo(): Promise<InfoInfoResponse> {\n  const resp = await client.getInstance().infoGet({\n    handleError: 'custom'\n  } as ReqConfig)\n  store.update((s) => {\n    s.appInfo = resp.data\n  })\n  return resp.data\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-cloud/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": false,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"src/*\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/README.md",
    "content": "# TiDB Dashboard for Clinic\n\nNPM: [@pingcap/tidb-dashboard-for-clinic-op](https://www.npmjs.com/package/@pingcap/tidb-dashboard-for-clinic-op)\n\n## How to Use\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <!-- ... -->\n    <link\n      rel=\"stylesheet\"\n      href=\"https://cdn.jsdelivr.net/npm/@pingcap/tidb-dashboard-for-clinic-op@<version>/dist/main.css\"\n    />\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\">\n      import startDashboard from 'https://cdn.jsdelivr.net/npm/@pingcap/tidb-dashboard-for-clinic-op@<version>/dist/main.js'\n\n      // get tidb dashboard api path base and api token\n      // ...\n\n      // startDashboard by apiPathBase and apiToken\n      startDashboard({ apiPathBase, apiToken })\n    </script>\n  </body>\n</html>\n```\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/builder.js",
    "content": "const path = require('path')\nconst fs = require('fs-extra')\nconst glob = require('glob')\nconst md5File = require('md5-file')\nconst chalk = require('chalk')\nconst { watch } = require('chokidar')\n\nconst { build } = require('esbuild')\nconst postCssPlugin = require('@baurine/esbuild-plugin-postcss3')\nconst autoprefixer = require('autoprefixer')\nconst { yamlPlugin } = require('esbuild-plugin-yaml')\n\nconst { lessModifyVars, lessGlobalVars } = require('../../less-vars')\n\nconst isDev = process.env.NODE_ENV !== 'production'\n\n// load env\nconst dotenv = require('dotenv')\nconst envFile = isDev ? './.env.development' : './.env.production'\ndotenv.config({ path: path.resolve(process.cwd(), envFile) })\nif (isDev && fs.pathExistsSync(path.resolve(process.cwd(), '.env.local'))) {\n  dotenv.config({\n    path: '.env.local',\n    override: true\n  })\n}\n\nconst outDir = 'dist'\nconst clinicUIDashboardPath = process.env.CLINIC_UI_DASHBOARD_PATH\n\nfunction genDefine() {\n  const define = {}\n  for (const k in process.env) {\n    if (k.startsWith('REACT_APP_')) {\n      let envVal = process.env[k]\n      // Example: REACT_APP_VERSION=$npm_package_version\n      // Expect output: REACT_APP_VERSION=0.1.0\n      if (envVal.startsWith('$')) {\n        envVal = process.env[envVal.substring(1)]\n      }\n      define[`process.env.${k}`] = JSON.stringify(envVal)\n    }\n  }\n  return define\n}\n\n// customized plugin: log time\nconst logTime = (_options = {}) => ({\n  name: 'logTime',\n  setup(build) {\n    let time\n\n    build.onStart(() => {\n      time = new Date()\n      console.log(`Build started`)\n    })\n\n    build.onEnd(() => {\n      console.log(`Build ended: ${chalk.yellow(`${new Date() - time}ms`)}`)\n    })\n  }\n})\n\nconst esbuildParams = {\n  color: true,\n  entryPoints: {\n    main: 'src/index.tsx'\n  },\n  outdir: outDir,\n  minify: !isDev,\n  format: 'esm',\n  bundle: true,\n  sourcemap: true,\n  logLevel: 'error',\n  incremental: true,\n  platform: 'browser',\n  plugins: [\n    postCssPlugin.default({\n      lessOptions: {\n        modifyVars: lessModifyVars,\n        globalVars: lessGlobalVars,\n        javascriptEnabled: true\n      },\n      enableCache: true,\n      plugins: [autoprefixer]\n    }),\n    yamlPlugin(),\n    logTime()\n  ],\n  define: genDefine(),\n  inject: ['./process-shim.js'] // fix runtime crash\n}\n\nfunction updateHtmlFiles(htmlFiles) {\n  const jsContentHash = md5File.sync(`./${outDir}/main.js`)\n  const cssContentHash = md5File.sync(`./${outDir}/main.css`)\n  const packageVersion = process.env.npm_package_version\n\n  htmlFiles.forEach(function (htmlFile) {\n    let result = fs.readFileSync(htmlFile).toString()\n    result = result.replaceAll('%JS_CONTENT_HASH%', jsContentHash.slice(0, 7))\n    result = result.replaceAll('%CSS_CONTENT_HASH%', cssContentHash.slice(0, 7))\n    result = result.replaceAll('%PACKAGE_VERSION%', packageVersion)\n    fs.writeFileSync(htmlFile, result)\n  })\n}\n\nfunction handleAssets() {\n  fs.copySync('./public', `./${outDir}`)\n\n  const htmlFiles = glob.sync(`./${outDir}/**/*.html`)\n  updateHtmlFiles(htmlFiles)\n}\n\nfunction copyAssets() {\n  // copy out dir to clinic ui repo\n  if (!fs.existsSync(clinicUIDashboardPath)) {\n    throw new Error(\n      `clini ui dashboard path ${clinicUIDashboardPath} doesn't exist, please change it by your local path`\n    )\n  }\n  fs.removeSync(clinicUIDashboardPath)\n  fs.copySync(`./${outDir}`, clinicUIDashboardPath)\n  console.log('copy dashboard to clinic ui')\n}\n\nasync function main() {\n  fs.removeSync(`./${outDir}`)\n\n  const builder = await build(esbuildParams)\n  handleAssets()\n\n  function rebuild() {\n    builder.rebuild().catch((err) => console.log(err))\n  }\n\n  if (isDev) {\n    watch(`src/**/*`, { ignoreInitial: true }).on('all', () => {\n      rebuild()\n    })\n    watch('public/**/*', { ignoreInitial: true }).on('all', () => {\n      handleAssets()\n    })\n\n    // watch \"node_modules/@pingcap/tidb-dashboard-lib/dist/**/*\" triggers too many rebuild\n    // so we just watch index.js to refine the experience\n    watch('node_modules/@pingcap/tidb-dashboard-lib/dist/index.js', {\n      ignoreInitial: true\n    }).on('all', () => {\n      rebuild()\n    })\n\n    watch(`dist/**/*`).on('all', () => {\n      copyAssets()\n    })\n  } else {\n    process.exit(0)\n  }\n}\n\nmain()\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/gulpfile.js",
    "content": "const { task, series, parallel } = require('gulp')\nconst shell = require('gulp-shell')\n\ntask('tsc:watch', shell.task('tsc --watch'))\ntask('tsc:check', shell.task('tsc'))\n\n// https://www.npmjs.com/package/eslint-watch\ntask('lint:watch', shell.task('esw --watch --cache --ext .tsx,.ts .'))\ntask('lint:check', shell.task('esw --cache --ext tsx,ts .'))\n\ntask('esbuild:dev', shell.task('NODE_ENV=development node builder.js'))\ntask('esbuild:build', shell.task('NODE_ENV=production node builder.js'))\n\ntask('dev', parallel('tsc:watch', 'lint:watch', 'esbuild:dev'))\ntask('build', parallel('tsc:check', 'lint:check', 'esbuild:build'))\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/package.json",
    "content": "{\n  \"name\": \"@pingcap/tidb-dashboard-for-clinic-op\",\n  \"version\": \"0.0.5\",\n  \"main\": \"dist/main.js\",\n  \"module\": \"dist/main.js\",\n  \"files\": [\n    \"dist/*.js\",\n    \"dist/*.css\",\n    \"dist/*.map\",\n    \"package.json\",\n    \"README.md\"\n  ],\n  \"scripts\": {\n    \"prepublish\": \"pnpm build\",\n    \"dev\": \"gulp dev\",\n    \"build\": \"gulp build\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/pingcap/tidb-dashboard.git\"\n  },\n  \"dependencies\": {\n    \"@pingcap/clinic-client\": \"workspace:^1.0.0\",\n    \"@pingcap/tidb-dashboard-lib\": \"workspace:^1.0.0\",\n    \"antd\": \"^4.18.7\",\n    \"axios\": \"^1.12.0\",\n    \"i18next\": \"^23.7.11\",\n    \"react\": \"^17.0.2\",\n    \"react-dom\": \"^17.0.2\",\n    \"react-router-dom\": \"6\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^16.9.1\",\n    \"autoprefixer\": \"^10.4.2\",\n    \"chalk\": \"4.1.2\",\n    \"chokidar\": \"^3.5.2\",\n    \"dotenv\": \"^16.0.1\",\n    \"esbuild\": \"^0.14.23\",\n    \"esbuild-plugin-svgr\": \"^1.0.0\",\n    \"esbuild-plugin-yaml\": \"^0.0.1\",\n    \"eslint-watch\": \"^8.0.0\",\n    \"fs-extra\": \"^10.0.0\",\n    \"glob\": \"^8.0.3\",\n    \"gulp\": \"^4.0.2\",\n    \"gulp-shell\": \"^0.8.0\",\n    \"live-server\": \"^1.2.1\",\n    \"md5-file\": \"^5.0.0\",\n    \"rimraf\": \"^3.0.2\",\n    \"typescript\": \"^4.7.3\"\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/process-shim.js",
    "content": "export let process = {\n  // cwd: () => '',\n  env: {} // to avoid `process.env` undefined in runtime\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/public/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin: 0;\n        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,\n          Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n          sans-serif;\n        -webkit-font-smoothing: antialiased;\n        -moz-osx-font-smoothing: grayscale;\n        background: #fff;\n      }\n\n      #dashboard_page_spinner {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n      }\n\n      .dot-flashing {\n        position: relative;\n        width: 10px;\n        height: 10px;\n        border-radius: 5px;\n        background-color: #aaa;\n        -webkit-animation: dot-flashing 1s infinite linear alternate;\n        animation: dot-flashing 1s infinite linear alternate;\n        -webkit-animation-delay: 0.5s;\n        animation-delay: 0.5s;\n      }\n\n      .dot-flashing::before,\n      .dot-flashing::after {\n        content: '';\n        display: inline-block;\n        position: absolute;\n        top: 0;\n      }\n\n      .dot-flashing::before {\n        left: -15px;\n        width: 10px;\n        height: 10px;\n        border-radius: 5px;\n        background-color: #aaa;\n        -webkit-animation: dot-flashing 1s infinite alternate;\n        animation: dot-flashing 1s infinite alternate;\n        -webkit-animation-delay: 0s;\n        animation-delay: 0s;\n      }\n\n      .dot-flashing::after {\n        left: 15px;\n        width: 10px;\n        height: 10px;\n        border-radius: 5px;\n        background-color: #aaa;\n        -webkit-animation: dot-flashing 1s infinite alternate;\n        animation: dot-flashing 1s infinite alternate;\n        -webkit-animation-delay: 1s;\n        animation-delay: 1s;\n      }\n\n      @-webkit-keyframes dot-flashing {\n        0% {\n          background-color: #aaa;\n        }\n        50%,\n        100% {\n          background-color: #ddd;\n        }\n      }\n\n      @keyframes dot-flashing {\n        0% {\n          background-color: #aaa;\n        }\n        50%,\n        100% {\n          background-color: #ddd;\n        }\n      }\n    </style>\n    <link rel=\"stylesheet\" href=\"./main.css?h=%CSS_CONTENT_HASH%\" />\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"dashboard_page_spinner\"><div class=\"dot-flashing\"></div></div>\n    <div id=\"root\"></div>\n    <script type=\"module\">\n      import startDashboard from './main.js?h=%JS_CONTENT_HASH%'\n\n      document.getElementById('dashboard_page_spinner').remove()\n\n      const csrfToken = localStorage.getItem('clinic.auth.csrf_token')\n      const apiPathBase = '/clinic/api/v1'\n      startDashboard({ apiPathBase, apiToken: csrfToken })\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/src/App.tsx",
    "content": "import React, { useEffect, useState } from 'react'\nimport SlowQuery from './apps/SlowQuery'\n\nfunction getLocHashPrefix() {\n  let urlHashPath = window.location.hash\n  const questionMarkPos = urlHashPath.indexOf('?')\n  if (questionMarkPos > 0) {\n    urlHashPath = urlHashPath.slice(0, questionMarkPos)\n  }\n  return urlHashPath.split('/')[1]\n}\n\nexport default function () {\n  const [locHashPrefix, setLocHashPrefix] = useState(() => getLocHashPrefix())\n\n  useEffect(() => {\n    function handleRouteChange() {\n      const curLocHashPrefix = getLocHashPrefix()\n      if (curLocHashPrefix !== locHashPrefix) {\n        setLocHashPrefix(curLocHashPrefix)\n      }\n    }\n    window.addEventListener('dashboard:route-change', handleRouteChange)\n    return () => {\n      window.removeEventListener('dashboard:route-change', handleRouteChange)\n    }\n  }, [locHashPrefix])\n\n  if (locHashPrefix === 'slow_query') {\n    return <SlowQuery />\n  }\n\n  return <p>No Matched Route!</p>\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/src/apps/SlowQuery/context.ts",
    "content": "import {\n  ISlowQueryDataSource,\n  ISlowQueryEvent,\n  ISlowQueryContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client from '~/client'\n\nexport type DsExtra = {\n  oid: string\n  cid: string\n  itemID: string\n  beginTime: number\n  endTime: number\n  curQueryID: string\n}\n\nclass DataSource implements ISlowQueryDataSource {\n  constructor(public extra: DsExtra) {}\n\n  getDatabaseList(beginTime: number, endTime: number, options?: ReqConfig) {\n    // return Promise.reject(new Error('no need to implemented'))\n    return Promise.resolve({\n      data: [],\n      status: 200,\n      statusText: 'ok',\n      headers: {},\n      config: {}\n    } as any)\n  }\n\n  infoListResourceGroupNames(options?: ReqConfig) {\n    // return Promise.reject(new Error('no need to implemented'))\n    return Promise.resolve({\n      data: [],\n      status: 200,\n      statusText: 'ok',\n      headers: {},\n      config: {}\n    } as any)\n  }\n\n  slowQueryAvailableFieldsGet(options?: ReqConfig) {\n    // return Promise.reject(new Error('no need to implemented'))\n    return Promise.resolve({\n      data: [],\n      status: 200,\n      statusText: 'ok',\n      headers: {},\n      config: {}\n    } as any)\n  }\n\n  slowQueryListGet(\n    beginTime?: number,\n    db?: Array<string>,\n    desc?: boolean,\n    digest?: string,\n    endTime?: number,\n    fields?: string,\n    limit?: number,\n    orderBy?: string,\n    plans?: Array<string>,\n    resourceGroup?: Array<string>,\n    text?: string,\n    showInternal?: boolean,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().orgsOidClustersCidSlowqueriesGet(\n      {\n        xCsrfToken: client.getToken(),\n        oid: this.extra.oid,\n        itemID: this.extra.itemID,\n        cid: this.extra.cid,\n        beginTime,\n        endTime,\n        db,\n        limit,\n        text,\n        orderBy,\n        desc,\n        plans,\n        digest\n      },\n      options\n    )\n  }\n\n  slowQueryDetailGet(\n    connectId?: string,\n    digest?: string,\n    timestamp?: number,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().orgsOidClustersCidSlowqueriesQueryidGet(\n      {\n        xCsrfToken: client.getToken(),\n        oid: this.extra.oid,\n        itemID: this.extra.itemID,\n        cid: this.extra.cid,\n        queryid: this.extra.curQueryID\n      },\n      options\n    )\n  }\n\n  slowQueryDownloadTokenPost(request: any, options?: ReqConfig) {\n    return Promise.reject(new Error('no need to implemented'))\n    // return Promise.resolve({\n    //   data: '',\n    //   status: 200,\n    //   statusText: 'ok',\n    //   headers: {},\n    //   config: {}\n    // })\n  }\n}\n\nclass EventHandler implements ISlowQueryEvent {\n  constructor(public extra: DsExtra) {}\n\n  selectSlowQueryItem(item: any) {\n    this.extra.curQueryID = item.id\n  }\n}\n\nexport const ctx: (extra: DsExtra) => ISlowQueryContext = (extra) => ({\n  ds: new DataSource(extra),\n  event: new EventHandler(extra),\n  cfg: {\n    apiPathBase: client.getBasePath(),\n    enableExport: false,\n    showDBFilter: false,\n    showDigestFilter: false,\n    showResourceGroupFilter: true\n  }\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/src/apps/SlowQuery/index.tsx",
    "content": "import React from 'react'\nimport { SlowQueryApp, SlowQueryProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx, DsExtra } from './context'\n\nfunction getDsExtra(): DsExtra {\n  const searchParams = new URLSearchParams(window.location.search)\n  const oid = searchParams.get('oid') || ''\n  const cid = searchParams.get('cid') || ''\n  const itemID = searchParams.get('item_id') || ''\n\n  const urlHashParmasStr = window.location.hash.slice(\n    window.location.hash.indexOf('?')\n  )\n  const params = new URLSearchParams(urlHashParmasStr)\n  const beginTime = parseInt(params.get('from') || '0')\n  const endTime = parseInt(params.get('to') || '0')\n\n  return {\n    oid,\n    cid,\n    itemID,\n    beginTime,\n    endTime,\n    curQueryID: ''\n  }\n}\n\nexport default function () {\n  return (\n    <SlowQueryProvider value={ctx(getDsExtra())}>\n      <SlowQueryApp />\n    </SlowQueryProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/src/client/index.tsx",
    "content": "import React from 'react'\nimport i18next from 'i18next'\nimport axios, { AxiosInstance } from 'axios'\nimport { message, Modal, notification } from 'antd'\n\nimport { routing, i18n } from '@pingcap/tidb-dashboard-lib'\nimport { Configuration, DefaultApi as ClinicApi } from '@pingcap/clinic-client'\n\nimport translations from './translations'\n\nexport * from '@pingcap/clinic-client'\n\n//////////////////////////////\n\nconst client = {\n  _init(apiBasePath: string, apiToken: string, apiInstance: ClinicApi) {\n    this.apiBasePath = apiBasePath\n    this.apiToken = apiToken\n    this.apiInstance = apiInstance\n  },\n\n  getInstance(): ClinicApi {\n    return this.apiInstance\n  },\n\n  getBasePath(): string {\n    return this.apiBasePath\n  },\n\n  getToken(): string {\n    return this.apiToken\n  }\n}\n\nexport default client\n\n//////////////////////////////\n\ntype HandleError = 'default' | 'custom'\n\nfunction applyErrorHandlerInterceptor(instance: AxiosInstance) {\n  instance.interceptors.response.use(undefined, async function (err) {\n    const { response, config } = err\n    const handleError = config.handleError as HandleError\n    const method = (config.method as string).toLowerCase()\n\n    let errCode: string\n    let content: string\n    if (err.message === 'Network Error') {\n      errCode = 'common.network'\n    } else {\n      errCode = response?.data?.code\n    }\n    if (i18next.exists(`error.${errCode ?? ''}`)) {\n      // If there is a translation for the code, use the translation.\n      // TODO: Better to display error details somewhere.\n      content = i18next.t(`error.${errCode}`)\n    } else {\n      content = String(\n        response?.data?.message || err.message || 'Internal error'\n      )\n    }\n    err.message = content\n    err.errCode = errCode\n\n    if (errCode === 'common.unauthenticated') {\n      // Handle unauthorized error in a unified way\n      if (!routing.isLocationMatch('/') && !routing.isSignInPage()) {\n        message.error({ content, key: errCode })\n      }\n      err.handled = true\n    } else if (handleError === 'default') {\n      if (method === 'get') {\n        const fullUrl = config.url as string\n        const API = fullUrl.replace(client.getBasePath(), '').split('?')[0]\n        notification.error({\n          key: API,\n          message: i18next.t('error.title'),\n          description: (\n            <span>\n              API: {API}\n              <br />\n              {content}\n            </span>\n          )\n        })\n      } else if (['post', 'put', 'delete', 'patch'].includes(method)) {\n        Modal.error({\n          title: i18next.t('error.title'),\n          content: content,\n          zIndex: 2000 // higher than popover\n        })\n      }\n      err.handled = true\n    }\n\n    return Promise.reject(err)\n  })\n}\n\nfunction initAxios() {\n  const instance = axios.create()\n  applyErrorHandlerInterceptor(instance)\n\n  return instance\n}\n\nexport function setupClient(apiBasePath: string, apiToken: string) {\n  i18n.addTranslations(translations)\n\n  const axiosInstance = initAxios()\n  const clinicApi = new ClinicApi(\n    new Configuration({\n      basePath: apiBasePath,\n      baseOptions: {\n        handleError: 'default'\n      }\n    }),\n    undefined,\n    axiosInstance\n  )\n\n  client._init(apiBasePath, apiToken, clinicApi)\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/src/client/translations/en.yaml",
    "content": "error:\n  title: Error\n  common:\n    network: Network connection error\n    unauthenticated: Please sign in again (session is expired)\n    forbidden: The current user is not authorized to perform this action\n  api:\n    user:\n      signin:\n        invalid_code: Authorization Code is invalid or expired\n        insufficient_priv: The user does not have sufficient privileges to access {{distro.tidb}} Dashboard.\n    slow_query:\n      export_no_data: No slow queires can be exported\n    statement:\n      export_no_data: No statements can be exported\n    continuous_profiling:\n      ng_monitoring_not_ready: |\n        To use or learn more about \"Continuous Profiling\" feature, please search for \"Continuous Profiling\" in the {{distro.tidb}} official docs for more information.\n        If it doesn't resove the issue, please contact the product's technical support.\n    feature_not_supported: The cluster of this version doesn't support or can't use this feature, please contact with technical support to get more information.\n  tidb:\n    no_alive_tidb: No alive {{distro.tidb}} instance\n    pd_access_failed: Failed to access {{distro.pd}} node\n    tidb_conn_failed: Failed to connect to {{distro.tidb}}\n    tidb_auth_failed: '{{distro.tidb}} authentication failed'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/src/client/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/src/client/translations/zh.yaml",
    "content": "error:\n  title: 错误\n  common:\n    network: 网络连接失败\n    unauthenticated: 会话已过期，请重新登录\n    forbidden: 当前用户没有权限进行该操作\n  api:\n    user:\n      signin:\n        invalid_code: 授权码无效或已过期\n        insufficient_priv: 该用户缺少足够的权限访问 {{distro.tidb}} Dashboard。\n    slow_query:\n      export_no_data: 没有可导出的慢查询日志\n    statement:\n      export_no_data: 没有可导出的语句\n    continuous_profiling:\n      ng_monitoring_not_ready: |\n        想使用或深入了解“持续性能分析”功能，请在 {{distro.tidb}} 官方文档搜索“持续性能分析”查看更多内容。\n        若未能解决问题，请联系本产品技术支持。\n    feature_not_supported: 当前版本的集群不支持或无法使用该功能，请联系技术支持了解详细情况。\n  tidb:\n    no_alive_tidb: 没有正在运行的 {{distro.tidb}} 实例\n    pd_access_failed: 无法访问 {{distro.pd}} 节点\n    tidb_conn_failed: 无法连接到 {{distro.tidb}}\n    tidb_auth_failed: '{{distro.tidb}} 登录验证失败'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/src/index.tsx",
    "content": "import i18next from 'i18next'\nimport React from 'react'\nimport ReactDOM from 'react-dom'\n\nimport { telemetry } from '@pingcap/tidb-dashboard-lib'\nimport { setupClient } from '~/client'\n\nimport App from './App'\n\nimport './styles/style.less'\nimport '@pingcap/tidb-dashboard-lib/dist/index.css'\nimport './styles/override.less'\n\nfunction renderApp() {\n  ReactDOM.render(\n    <React.StrictMode>\n      <App />\n    </React.StrictMode>,\n    document.getElementById('root')\n  )\n}\n\nfunction trackRouteChange() {\n  let preRoute = ''\n  function handler(ev) {\n    const loc = ev.detail.location\n    if (loc.pathname !== preRoute) {\n      telemetry.trackRouteChange('#' + loc.pathname)\n      preRoute = loc.pathname\n    }\n  }\n  window.addEventListener('dashboard:route-change', handler)\n}\n\ntype StartOptions = {\n  apiPathBase: string\n  apiToken: string\n}\n\nfunction start({ apiPathBase, apiToken }: StartOptions) {\n  // i18n\n  i18next.changeLanguage('en')\n\n  // api client\n  setupClient(apiPathBase, apiToken)\n\n  // telemetry\n  telemetry.init(\n    process.env.REACT_APP_MIXPANEL_HOST,\n    process.env.REACT_APP_MIXPANEL_TOKEN\n  )\n  telemetry.enable(\n    `tidb-dashboard-for-clinic-op-${process.env.REACT_APP_VERSION}`\n  )\n  trackRouteChange()\n\n  renderApp()\n}\n\nexport default start\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/src/react-app-env.d.ts",
    "content": "declare module '*.yaml' {\n  const classes: { readonly [key: string]: string }\n  export default classes\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/src/styles/override.less",
    "content": "// reset ::selection pseudo element values\n::selection {\n  color: currentColor;\n  background-color: #b3d6ff;\n}\n\nhtml {\n  font-size: 14px;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/src/styles/style.less",
    "content": "@root-entry-name: default;\n\n@import 'antd/lib/style/components.less';\n// it is expected to import 'antd/es/style/components.less' but it doesn't exist this file\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-clinic-op/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": false,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"src/*\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/builder.js",
    "content": "const os = require('os')\nconst path = require('path')\nconst fs = require('fs-extra')\nconst chalk = require('chalk')\nconst { watch } = require('chokidar')\n\nconst { start } = require('live-server')\nconst { createProxyMiddleware } = require('http-proxy-middleware')\n\nconst { build } = require('esbuild')\nconst postCssPlugin = require('@baurine/esbuild-plugin-postcss3')\nconst autoprefixer = require('autoprefixer')\nconst { yamlPlugin } = require('esbuild-plugin-yaml')\nconst babelPlugin = require('@baurine/esbuild-plugin-babel')\n\nconst { lessModifyVars, lessGlobalVars } = require('../../less-vars')\n\nconst isDev = process.env.NODE_ENV !== 'production'\nconst isE2E = process.env.E2E_TEST === 'true'\n\n// load env\nconst envFile = isDev ? './.env.development' : './.env.production'\nrequire('dotenv').config({ path: path.resolve(process.cwd(), envFile) })\n\nconst outDir = 'dist'\n\nconst dashboardApiPrefix =\n  process.env.REACT_APP_DASHBOARD_API_URL || 'http://127.0.0.1:12333'\nconst devServerPort = process.env.PORT\nconst devServerParams = {\n  port: devServerPort,\n  root: outDir,\n  open: '/dashboard',\n  // Set base URL\n  // https://github.com/tapio/live-server/issues/287 - How can I serve from a base URL?\n  proxy: [['/dashboard', `http://127.0.0.1:${devServerPort}`]],\n  middleware: isDev && [\n    function (req, _res, next) {\n      if (/\\/dashboard\\/api\\/diagnose\\/reports\\/\\S+\\/detail/.test(req.url)) {\n        req.url = '/diagnoseReport.html'\n      }\n      next()\n    },\n    createProxyMiddleware('/dashboard/api/diagnose/reports/*/data.js', {\n      target: dashboardApiPrefix,\n      changeOrigin: true\n    })\n  ]\n}\n\nfunction genDefine() {\n  const define = {}\n  for (const k in process.env) {\n    if (k.startsWith('REACT_APP_')) {\n      let envVal = process.env[k]\n      // Example: REACT_APP_VERSION=$npm_package_version\n      // Expect output: REACT_APP_VERSION=0.1.0\n      if (envVal.startsWith('$')) {\n        envVal = process.env[envVal.substring(1)]\n      }\n      define[`process.env.${k}`] = JSON.stringify(envVal)\n    }\n  }\n  define['process.env.E2E_TEST'] = JSON.stringify(process.env.E2E_TEST)\n  return define\n}\n\n// customized plugin: log time\nconst logTime = (_options = {}) => ({\n  name: 'logTime',\n  setup(build) {\n    let time\n\n    build.onStart(() => {\n      time = new Date()\n      console.log(`Build started`)\n    })\n\n    build.onEnd(() => {\n      console.log(`Build ended: ${chalk.yellow(`${new Date() - time}ms`)}`)\n    })\n  }\n})\n\nconst esbuildParams = {\n  color: true,\n  entryPoints: {\n    dashboardApp: 'src/dashboardApp/index.ts',\n    diagnoseReport: 'src/diagnoseReportApp/index.tsx'\n  },\n  outdir: outDir,\n  minify: !isDev,\n  format: 'esm',\n  bundle: true,\n  sourcemap: true,\n  logLevel: 'error',\n  incremental: true,\n  splitting: true,\n  platform: 'browser',\n  plugins: [\n    postCssPlugin.default({\n      lessOptions: {\n        modifyVars: lessModifyVars,\n        globalVars: lessGlobalVars,\n        javascriptEnabled: true\n      },\n      enableCache: true,\n      plugins: [autoprefixer]\n    }),\n    yamlPlugin(),\n    logTime()\n  ],\n  define: genDefine(),\n  inject: ['./process-shim.js'] // fix runtime crash\n}\nif (isE2E) {\n  // use babel and istanbul to report test coverage for e2e test\n  esbuildParams.plugins.push(\n    babelPlugin({\n      filter: /\\.tsx?/,\n      config: {\n        presets: ['@babel/preset-react', '@babel/preset-typescript'],\n        plugins: ['istanbul']\n      }\n    })\n  )\n}\n\nfunction buildHtml(inputFilename, outputFilename) {\n  let result = fs.readFileSync(inputFilename).toString()\n\n  const placeholders = ['PUBLIC_URL']\n  placeholders.forEach((key) => {\n    result = result.replace(new RegExp(`%${key}%`, 'g'), process.env[key])\n  })\n  // replace TIME_PLACE_HOLDER\n  const nowTime = new Date().valueOf()\n  result = result.replace(new RegExp(`%TIME_PLACE_HOLDER%`, 'g'), nowTime)\n  if (isDev) {\n    result = result.replace(\n      new RegExp('__DISTRO_ASSETS_RES_TIMESTAMP__', 'g'),\n      nowTime\n    )\n  }\n\n  // handle distro strings res, only for dev mode\n  const distroStringsResFilePath = `./${outDir}/distro-res/strings.json`\n  if (isDev && fs.existsSync(distroStringsResFilePath)) {\n    const distroStringsRes = require(distroStringsResFilePath)\n    result = result.replace(\n      '__DISTRO_STRINGS_RES__',\n      btoa(JSON.stringify(distroStringsRes))\n    )\n  }\n\n  fs.writeFileSync(outputFilename, result)\n}\n\nfunction handleAssets() {\n  fs.copySync('./public', `./${outDir}`)\n  if (isDev) {\n    copyDistroRes()\n  }\n\n  buildHtml('./public/index.html', `./${outDir}/index.html`)\n  buildHtml('./public/diagnoseReport.html', `./${outDir}/diagnoseReport.html`)\n}\n\nfunction copyDistroRes() {\n  const distroResPath = '../../../bin/distro-res'\n  if (fs.existsSync(distroResPath)) {\n    fs.copySync(distroResPath, `./${outDir}/distro-res`)\n  }\n}\n\nasync function main() {\n  fs.removeSync(`./${outDir}`)\n\n  const builder = await build(esbuildParams)\n  handleAssets()\n\n  function rebuild() {\n    builder.rebuild().catch((err) => console.log(err))\n  }\n\n  if (isDev) {\n    start(devServerParams)\n\n    watch(`src/**/*`, { ignoreInitial: true }).on('all', () => {\n      rebuild()\n    })\n    watch('public/**/*', { ignoreInitial: true }).on('all', () => {\n      handleAssets()\n    })\n    // watch \"node_modules/@pingcap/tidb-dashboard-lib/dist/**/*\" triggers too many rebuild\n    // so we just watch index.js to refine the experience\n    watch('node_modules/@pingcap/tidb-dashboard-lib/dist/index.js', {\n      ignoreInitial: true\n    }).on('all', () => {\n      rebuild()\n    })\n  } else {\n    process.exit(0)\n  }\n}\n\nmain()\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/.eslintrc.json",
    "content": "{\n  \"extends\": [\"plugin:cypress/recommended\"]\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/README.md",
    "content": "# E2E Test\n\nSince there are some features is different from version to version, we have `make e2e_compat_features_test` and `make e2e_common_features_test` to test features compatibility in different versions and common features in all versions, respectively.\n\n## Install Cypress\n\nThe Cypress has been added to package.json, so just run `pnpm i` to install it. We use Cypress@8.5.0 here since the version>8.5.0 has an unstable server connection error, the related issue can be referred [here](https://github.com/cypress-io/cypress/issues/18464).\n\n## Run Test\n\n**Prerequisite**: TiDB Dashboard server has to be started before run cypress test.\n\n### Open Test Runner to Run Test Locally\n\n#### Test E2E with FEATURE_VERSION >= 5.3.0\n\n```shell\n# start frontend server\ncd ui && pnpm dev\n# start backend server\nmake dev && make run\n# open cypress test runner\ncd ui/pacakges/tidb-dashboard-for-op && pnpm open:cypress\n```\n\n#### Test E2E with FEATURE_VERSION < 5.3.0\n\n```shell\n# start frontend server\ncd ui && pnpm dev\n# start backend server\nmake dev && make run FEATURE_VERSION=5.0.0\n# open cypress test runner\ncd ui/pacakges/tidb-dashboard-for-op && pnpm open:cypress --env FEATURE_VERSION=5.0.0\n```\n\nRun test by choosing test file under `/integration` on cypress test runner, cypress will open a broswer to run e2e test.\n\n### Run on CI\n\n```shell\n# start frontend server\nmake ui\n# start backend server\nUI=1 make && make run FEATURE_VERSION=${FEATURE_VERSION}\n# run e2e_compat_features and e2e_common_features tests\nmake e2e_test FEATURE_VERSION=${FEATURE_VERSION}\n```\n\n### Upload Visual Test Snapshots\n\n> TODO: Use the official cypress docker image to make sure visual test stable between operating systems.\n\nSince there was no cypress image of m1 before. So we use github actions to generate the snapshots that we need for visual tests.\n\n#### How to generate snapshots in GitHub Actions\n\n1. Go to [tidb-dashboard Actions - Upload E2E Snapshots](https://github.com/pingcap/tidb-dashboard/actions/workflows/upload-e2e-snapshots.yaml)\n\n2. Click \"Run workflow\"\n\n3. Enter which git SHA you want the test to run on\n\n4. Specify the test specs to generate the snapshots, base path is `${PROJECT_DIR}/ui/packages/tidb-dashboard-for-op/cypress/integration`\n\n5. Enter the action after all jobs finished, download the e2e-snapshots artifact below.\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/fixtures/topsql_instance:end=1641934800&start=1641916800.json",
    "content": "{\n  \"data\": [\n    { \"instance\": \"127.0.0.1:10080\", \"instance_type\": \"tidb\" },\n    { \"instance\": \"127.0.0.1:20160\", \"instance_type\": \"tikv\" }\n  ],\n  \"status\": \"ok\"\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/fixtures/topsql_summary:end=1641934800&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641916800&top=5&window=123s.json",
    "content": "{\n  \"data\": [\n    {\n      \"sql_digest\": \"b95a604794f9eff17a1a6a37d754324be11ede348a0d1e53da2bc3c32d6a4142\",\n      \"sql_text\": \"select `variable_name` , `variable_value` from `mysql` . `global_variables` where `variable_name` in ( ... )\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 390,\n      \"exec_count_per_sec\": 0.333259263374257,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"9449388a4efbc35c8eca1639aec164392df687869239f9ad16ea37887d98c42a\",\n          \"plan_text\": \"\\tBatch_Point_Get\\troot\\ttable:GLOBAL_VARIABLES, index:PRIMARY(VARIABLE_NAME), keep order:false, desc:false\",\n          \"timestamp_sec\": [\n            1641916791, 1641916914, 1641917037, 1641917160, 1641917283,\n            1641917406, 1641917529, 1641917652, 1641917775, 1641917898,\n            1641918021, 1641918144, 1641918267, 1641918390, 1641918513,\n            1641918636, 1641918759, 1641918882, 1641919005, 1641919128,\n            1641919251, 1641919374, 1641919497, 1641919620, 1641919743,\n            1641919866, 1641919989, 1641920112, 1641920235, 1641920358,\n            1641920481, 1641920604, 1641920727, 1641920850, 1641920973,\n            1641921096, 1641921219, 1641921342, 1641921465, 1641921588,\n            1641921711, 1641921834, 1641921957, 1641922080, 1641922203,\n            1641922326, 1641922449, 1641922572, 1641922695, 1641922818,\n            1641922941, 1641923064, 1641923187, 1641923310, 1641923433,\n            1641923556, 1641923679, 1641923802, 1641923925, 1641924048,\n            1641924171, 1641924294, 1641924417, 1641924540, 1641924663,\n            1641924786, 1641924909, 1641925032, 1641925155, 1641925278,\n            1641925401, 1641925524, 1641925647, 1641925770, 1641925893,\n            1641926016, 1641926139, 1641926262, 1641926385, 1641926508,\n            1641926631, 1641926754, 1641926877, 1641927000, 1641927123,\n            1641927246, 1641927369, 1641927492, 1641927615, 1641927738,\n            1641927861, 1641927984, 1641928107, 1641928230, 1641928353,\n            1641928476, 1641928599, 1641928722, 1641928845, 1641928968,\n            1641929091, 1641929214, 1641929337, 1641929460, 1641929583,\n            1641929706, 1641929829, 1641929952, 1641930075, 1641930198,\n            1641930321, 1641930444, 1641930567, 1641930690, 1641930813,\n            1641930936, 1641931059, 1641931182, 1641931305, 1641931428,\n            1641931551, 1641931674, 1641931797, 1641931920, 1641932043,\n            1641932166, 1641932289, 1641932412, 1641932535, 1641932658,\n            1641932781, 1641932904, 1641933027, 1641933150, 1641933273,\n            1641933396, 1641933519, 1641933642, 1641933765, 1641933888,\n            1641934011, 1641934134, 1641934257, 1641934380, 1641934503,\n            1641934626, 1641934749\n          ],\n          \"cpu_time_ms\": [\n            0, 0, 0, 0, 0, 10, 0, 0, 10, 0, 10, 0, 10, 10, 0, 0, 10, 0, 0, 0, 0,\n            0, 0, 10, 0, 0, 0, 0, 0, 10, 10, 0, 0, 0, 10, 0, 10, 10, 0, 10, 0,\n            0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10,\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 10, 10, 10,\n            0, 0, 0, 0, 0, 20, 10, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0,\n            0, 10, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0,\n            0, 0, 0, 10, 0, 10, 10, 0, 0, 10, 0, 0, 0, 0, 20, 0, 0, 0, 20, 0, 0,\n            0\n          ],\n          \"exec_count_per_sec\": 0.333259263374257,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"e2769f5619db3dc4916298c4c6b3bc2c36c6d64a84e2a5549284521482564ce0\",\n      \"sql_text\": \"select high_priority ( `variable_value` ) from `mysql` . `tidb` where `variable_name` = ? for update\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 390,\n      \"exec_count_per_sec\": 0.06994055885784123,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [\n            1641916791, 1641917406, 1641917775, 1641918144, 1641918513,\n            1641918759, 1641919005, 1641919866, 1641920112, 1641920604,\n            1641920850, 1641921342, 1641921465, 1641922695, 1641923187,\n            1641923433, 1641923925, 1641925524, 1641925647, 1641926508,\n            1641926877, 1641927861, 1641928845, 1641929706, 1641931059,\n            1641932289, 1641932658, 1641932904, 1641933150, 1641933396\n          ],\n          \"cpu_time_ms\": [\n            10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 20, 10, 10, 10, 10,\n            10, 20, 10, 20, 10, 10, 10, 10, 10, 10, 10, 10, 10\n          ],\n          \"exec_count_per_sec\": 0,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        },\n        {\n          \"plan_digest\": \"892b61d58192589f43d81f3ee50710d71e6a8c286a59877069eaa00cd3bb8f5c\",\n          \"plan_text\": \"\\tProjection          \\troot\\tmysql.tidb.variable_value\\n\\t└─SelectLock        \\troot\\t\\n\\t  └─IndexLookUp     \\troot\\t\\n\\t    ├─IndexRangeScan\\tcop \\ttable:tidb, index:PRIMARY(VARIABLE_NAME), range:[?,?], keep order:false\\n\\t    └─TableRowIDScan\\tcop \\ttable:tidb, keep order:false\",\n          \"timestamp_sec\": [\n            1641916791, 1641916914, 1641917037, 1641917160, 1641917283,\n            1641917406, 1641917529, 1641917652, 1641917775, 1641917898,\n            1641918021, 1641918144, 1641918267, 1641918390, 1641918513,\n            1641918636, 1641918759, 1641918882, 1641919005, 1641919128,\n            1641919251, 1641919374, 1641919497, 1641919620, 1641919743,\n            1641919866, 1641919989, 1641920112, 1641920235, 1641920358,\n            1641920481, 1641920604, 1641920727, 1641920850, 1641920973,\n            1641921096, 1641921219, 1641921342, 1641921465, 1641921588,\n            1641921711, 1641921834, 1641921957, 1641922080, 1641922203,\n            1641922326, 1641922449, 1641922572, 1641922695, 1641922818,\n            1641922941, 1641923064, 1641923187, 1641923310, 1641923433,\n            1641923556, 1641923679, 1641923802, 1641923925, 1641924048,\n            1641924171, 1641924294, 1641924417, 1641924540, 1641924663,\n            1641924786, 1641924909, 1641925032, 1641925155, 1641925278,\n            1641925401, 1641925524, 1641925647, 1641925770, 1641925893,\n            1641926016, 1641926139, 1641926262, 1641926385, 1641926508,\n            1641926631, 1641926754, 1641926877, 1641927000, 1641927123,\n            1641927246, 1641927369, 1641927492, 1641927615, 1641927738,\n            1641927861, 1641927984, 1641928107, 1641928230, 1641928353,\n            1641928476, 1641928599, 1641928722, 1641928845, 1641928968,\n            1641929091, 1641929214, 1641929337, 1641929460, 1641929583,\n            1641929706, 1641929829, 1641929952, 1641930075, 1641930198,\n            1641930321, 1641930444, 1641930567, 1641930690, 1641930813,\n            1641930936, 1641931059, 1641931182, 1641931305, 1641931428,\n            1641931551, 1641931674, 1641931797, 1641931920, 1641932043,\n            1641932166, 1641932289, 1641932412, 1641932535, 1641932658,\n            1641932781, 1641932904, 1641933027, 1641933150, 1641933273,\n            1641933396, 1641933519, 1641933642, 1641933765, 1641933888,\n            1641934011, 1641934134, 1641934257, 1641934380, 1641934503,\n            1641934626, 1641934749\n          ],\n          \"cpu_time_ms\": [\n            0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n            10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0,\n            10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n          ],\n          \"exec_count_per_sec\": 0.06994055885784123,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"da5f21b51be132c69a10de76148c95ab1ad8e6fb6c0fdacb3dcedfe4b6d3c41f\",\n      \"sql_text\": \"select distinct `table_id` from `mysql` . `stats_feedback`\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 320,\n      \"exec_count_per_sec\": 0.06666296316871285,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [\n            1641924294, 1641928476, 1641928845, 1641929460, 1641930075,\n            1641931551, 1641931797, 1641932535, 1641933396, 1641934011,\n            1641934749\n          ],\n          \"cpu_time_ms\": [10, 10, 10, 10, 10, 10, 10, 10, 10, 20, 10],\n          \"exec_count_per_sec\": 0,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        },\n        {\n          \"plan_digest\": \"8d5611b5d6f8ade5b8d2f721db81164a09cf915d836dfcfc3fd00237750350ca\",\n          \"plan_text\": \"\\tHashAgg            \\troot\\tgroup by:mysql.stats_feedback.table_id, funcs:firstrow(mysql.stats_feedback.table_id)-\\u003emysql.stats_feedback.table_id\\n\\t└─IndexReader      \\troot\\tindex:HashAgg_4\\n\\t  └─HashAgg        \\tcop \\tgroup by:mysql.stats_feedback.table_id, \\n\\t    └─IndexFullScan\\tcop \\ttable:stats_feedback, index:hist(table_id, is_index, hist_id), range:[?,?], keep order:false\",\n          \"timestamp_sec\": [\n            1641916791, 1641916914, 1641917037, 1641917160, 1641917283,\n            1641917406, 1641917529, 1641917652, 1641917775, 1641917898,\n            1641918021, 1641918144, 1641918267, 1641918390, 1641918513,\n            1641918636, 1641918759, 1641918882, 1641919005, 1641919128,\n            1641919251, 1641919374, 1641919497, 1641919620, 1641919743,\n            1641919866, 1641919989, 1641920112, 1641920235, 1641920358,\n            1641920481, 1641920604, 1641920727, 1641920850, 1641920973,\n            1641921096, 1641921219, 1641921342, 1641921465, 1641921588,\n            1641921711, 1641921834, 1641921957, 1641922080, 1641922203,\n            1641922326, 1641922449, 1641922572, 1641922695, 1641922818,\n            1641922941, 1641923064, 1641923187, 1641923310, 1641923433,\n            1641923556, 1641923679, 1641923802, 1641923925, 1641924048,\n            1641924171, 1641924294, 1641924417, 1641924540, 1641924663,\n            1641924786, 1641924909, 1641925032, 1641925155, 1641925278,\n            1641925401, 1641925524, 1641925647, 1641925770, 1641925893,\n            1641926016, 1641926139, 1641926262, 1641926385, 1641926508,\n            1641926631, 1641926754, 1641926877, 1641927000, 1641927123,\n            1641927246, 1641927369, 1641927492, 1641927615, 1641927738,\n            1641927861, 1641927984, 1641928107, 1641928230, 1641928353,\n            1641928476, 1641928599, 1641928722, 1641928845, 1641928968,\n            1641929091, 1641929214, 1641929337, 1641929460, 1641929583,\n            1641929706, 1641929829, 1641929952, 1641930075, 1641930198,\n            1641930321, 1641930444, 1641930567, 1641930690, 1641930813,\n            1641930936, 1641931059, 1641931182, 1641931305, 1641931428,\n            1641931551, 1641931674, 1641931797, 1641931920, 1641932043,\n            1641932166, 1641932289, 1641932412, 1641932535, 1641932658,\n            1641932781, 1641932904, 1641933027, 1641933150, 1641933273,\n            1641933396, 1641933519, 1641933642, 1641933765, 1641933888,\n            1641934011, 1641934134, 1641934257, 1641934380, 1641934503,\n            1641934626, 1641934749\n          ],\n          \"cpu_time_ms\": [\n            0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 10, 0, 20, 0, 0, 0, 0, 0, 20, 0,\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10,\n            0, 0, 10, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0,\n            0, 0, 0, 0, 0, 0, 0, 10, 0, 10, 0, 10, 0, 0, 0, 0, 0, 0, 10, 0, 0,\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0,\n            0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 10, 0, 0,\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0\n          ],\n          \"exec_count_per_sec\": 0.06666296316871285,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"ea4709893ffb8edc8d58191ccbd93c4c4fdfc1d20ebbcc7f48707df328d6dbb2\",\n      \"sql_text\": \"select `version` , `table_id` , `modify_count` , `count` from `mysql` . `stats_meta` where `version` \\u003e ? order by `version`\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 3590,\n      \"exec_count_per_sec\": 0.333259263374257,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [\n            1641916791, 1641916914, 1641917037, 1641917160, 1641917283,\n            1641917406, 1641917529, 1641917652, 1641917775, 1641917898,\n            1641918144, 1641918390, 1641918513, 1641918636, 1641918759,\n            1641919005, 1641919251, 1641919620, 1641919743, 1641919866,\n            1641919989, 1641920112, 1641920235, 1641920358, 1641920481,\n            1641920604, 1641920850, 1641920973, 1641921342, 1641921465,\n            1641921588, 1641921711, 1641921834, 1641921957, 1641922080,\n            1641922203, 1641922326, 1641922449, 1641922572, 1641922695,\n            1641922818, 1641922941, 1641923064, 1641923187, 1641923310,\n            1641923433, 1641923556, 1641923679, 1641923802, 1641923925,\n            1641924048, 1641924171, 1641924294, 1641924417, 1641924663,\n            1641924786, 1641924909, 1641925032, 1641925155, 1641925278,\n            1641925401, 1641925524, 1641925770, 1641925893, 1641926016,\n            1641926139, 1641926262, 1641926385, 1641926631, 1641926754,\n            1641926877, 1641927000, 1641927246, 1641927369, 1641927492,\n            1641927738, 1641927861, 1641927984, 1641928230, 1641928476,\n            1641928599, 1641928722, 1641928845, 1641929091, 1641929214,\n            1641929337, 1641929460, 1641929583, 1641929706, 1641929829,\n            1641929952, 1641930075, 1641930321, 1641930444, 1641930567,\n            1641930813, 1641930936, 1641931059, 1641931182, 1641931305,\n            1641931428, 1641931551, 1641931674, 1641931797, 1641931920,\n            1641932043, 1641932166, 1641932289, 1641932412, 1641932535,\n            1641932658, 1641932781, 1641932904, 1641933027, 1641933150,\n            1641933273, 1641933396, 1641933519, 1641933642, 1641933765,\n            1641933888, 1641934011, 1641934134, 1641934257, 1641934380,\n            1641934503, 1641934626, 1641934749\n          ],\n          \"cpu_time_ms\": [\n            50, 10, 10, 10, 20, 30, 30, 20, 30, 40, 10, 40, 20, 30, 40, 50, 20,\n            20, 10, 30, 50, 40, 40, 10, 20, 30, 10, 10, 10, 10, 50, 10, 10, 20,\n            20, 30, 10, 20, 20, 20, 10, 20, 40, 70, 40, 20, 20, 20, 10, 10, 20,\n            10, 10, 30, 10, 50, 20, 40, 30, 30, 10, 30, 30, 20, 30, 30, 30, 50,\n            10, 30, 20, 30, 10, 10, 20, 30, 10, 40, 20, 10, 30, 20, 10, 30, 20,\n            20, 10, 20, 20, 30, 30, 40, 40, 10, 20, 20, 10, 30, 20, 30, 40, 30,\n            20, 10, 30, 30, 10, 10, 20, 40, 10, 30, 20, 20, 30, 10, 20, 40, 20,\n            10, 10, 20, 20, 10, 30, 20, 10, 20\n          ],\n          \"exec_count_per_sec\": 0,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        },\n        {\n          \"plan_digest\": \"42d48b331dfe53300ddea68d4217dc467244bd898f80f10a913f2104d26d4989\",\n          \"plan_text\": \"\\tProjection        \\troot\\tmysql.stats_meta.count, mysql.stats_meta.modify_count, mysql.stats_meta.table_id, mysql.stats_meta.version\\n\\t└─IndexLookUp     \\troot\\t\\n\\t  ├─IndexRangeScan\\tcop \\ttable:stats_meta, index:idx_ver(version), range:[?,?], keep order:true\\n\\t  └─TableRowIDScan\\tcop \\ttable:stats_meta, keep order:false\",\n          \"timestamp_sec\": [\n            1641916791, 1641916914, 1641917037, 1641917160, 1641917283,\n            1641917406, 1641917529, 1641917652, 1641917775, 1641917898,\n            1641918021, 1641918144, 1641918267, 1641918390, 1641918513,\n            1641918636, 1641918759, 1641918882, 1641919005, 1641919128,\n            1641919251, 1641919374, 1641919497, 1641919620, 1641919743,\n            1641919866, 1641919989, 1641920112, 1641920235, 1641920358,\n            1641920481, 1641920604, 1641920727, 1641920850, 1641920973,\n            1641921096, 1641921219, 1641921342, 1641921465, 1641921588,\n            1641921711, 1641921834, 1641921957, 1641922080, 1641922203,\n            1641922326, 1641922449, 1641922572, 1641922695, 1641922818,\n            1641922941, 1641923064, 1641923187, 1641923310, 1641923433,\n            1641923556, 1641923679, 1641923802, 1641923925, 1641924048,\n            1641924171, 1641924294, 1641924417, 1641924540, 1641924663,\n            1641924786, 1641924909, 1641925032, 1641925155, 1641925278,\n            1641925401, 1641925524, 1641925647, 1641925770, 1641925893,\n            1641926016, 1641926139, 1641926262, 1641926385, 1641926508,\n            1641926631, 1641926754, 1641926877, 1641927000, 1641927123,\n            1641927246, 1641927369, 1641927492, 1641927615, 1641927738,\n            1641927861, 1641927984, 1641928107, 1641928230, 1641928353,\n            1641928476, 1641928599, 1641928722, 1641928845, 1641928968,\n            1641929091, 1641929214, 1641929337, 1641929460, 1641929583,\n            1641929706, 1641929829, 1641929952, 1641930075, 1641930198,\n            1641930321, 1641930444, 1641930567, 1641930690, 1641930813,\n            1641930936, 1641931059, 1641931182, 1641931305, 1641931428,\n            1641931551, 1641931674, 1641931797, 1641931920, 1641932043,\n            1641932166, 1641932289, 1641932412, 1641932535, 1641932658,\n            1641932781, 1641932904, 1641933027, 1641933150, 1641933273,\n            1641933396, 1641933519, 1641933642, 1641933765, 1641933888,\n            1641934011, 1641934134, 1641934257, 1641934380, 1641934503,\n            1641934626, 1641934749\n          ],\n          \"cpu_time_ms\": [\n            0, 0, 0, 0, 0, 10, 10, 10, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 10,\n            0, 0, 0, 10, 0, 0, 0, 0, 0, 10, 0, 10, 0, 0, 0, 0, 0, 10, 40, 0, 0,\n            10, 10, 20, 10, 0, 10, 10, 10, 10, 20, 0, 10, 0, 0, 10, 10, 0, 20,\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 10, 0, 10, 0,\n            0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 10, 10, 0, 0, 0, 0,\n            0, 10, 0, 0, 0, 0, 10, 10, 0, 0, 0, 10, 0, 10, 10, 0, 0, 20, 10, 0,\n            0, 0, 0, 20, 10, 40, 0, 0, 0, 0, 0, 10, 0, 20, 0, 0, 10, 0, 0, 0, 0,\n            10, 10, 20\n          ],\n          \"exec_count_per_sec\": 0.333259263374257,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"61f4cce222f1a26d6b0650865051ebed3d077412ae053636e2f5acf9dc426a42\",\n      \"sql_text\": \"insert high_priority into `mysql` . `tidb` values ( ... ) on duplicate key update `variable_value` = ? , comment = ?\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 260,\n      \"exec_count_per_sec\": 0.019998888950613854,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [\n            1641916791, 1641916914, 1641917037, 1641917160, 1641917283,\n            1641917406, 1641917529, 1641917652, 1641917775, 1641917898,\n            1641918021, 1641918144, 1641918267, 1641918390, 1641918513,\n            1641918636, 1641918759, 1641918882, 1641919005, 1641919128,\n            1641919251, 1641919374, 1641919497, 1641919620, 1641919743,\n            1641919866, 1641919989, 1641920112, 1641920235, 1641920358,\n            1641920481, 1641920604, 1641920727, 1641920850, 1641920973,\n            1641921096, 1641921219, 1641921342, 1641921465, 1641921588,\n            1641921711, 1641921834, 1641921957, 1641922080, 1641922203,\n            1641922326, 1641922449, 1641922572, 1641922695, 1641922818,\n            1641922941, 1641923064, 1641923187, 1641923310, 1641923433,\n            1641923556, 1641923679, 1641923802, 1641923925, 1641924048,\n            1641924171, 1641924294, 1641924417, 1641924540, 1641924663,\n            1641924786, 1641924909, 1641925032, 1641925155, 1641925278,\n            1641925401, 1641925524, 1641925647, 1641925770, 1641925893,\n            1641926016, 1641926139, 1641926262, 1641926385, 1641926508,\n            1641926631, 1641926754, 1641926877, 1641927000, 1641927123,\n            1641927246, 1641927369, 1641927492, 1641927615, 1641927738,\n            1641927861, 1641927984, 1641928107, 1641928230, 1641928353,\n            1641928476, 1641928599, 1641928722, 1641928845, 1641928968,\n            1641929091, 1641929214, 1641929337, 1641929460, 1641929583,\n            1641929706, 1641929829, 1641929952, 1641930075, 1641930198,\n            1641930321, 1641930444, 1641930567, 1641930690, 1641930813,\n            1641930936, 1641931059, 1641931182, 1641931305, 1641931428,\n            1641931551, 1641931674, 1641931797, 1641931920, 1641932043,\n            1641932166, 1641932289, 1641932412, 1641932535, 1641932658,\n            1641932781, 1641932904, 1641933027, 1641933150, 1641933273,\n            1641933396, 1641933519, 1641933642, 1641933765, 1641933888,\n            1641934011, 1641934134, 1641934257, 1641934380, 1641934503,\n            1641934626, 1641934749\n          ],\n          \"cpu_time_ms\": [\n            0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 10, 0,\n            0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 10, 10, 10, 0, 0, 0, 0, 0, 0,\n            0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 10, 0,\n            0, 0, 0, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0,\n            0, 0, 10, 0, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 0, 10, 0, 0, 0, 0,\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 10,\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 20\n          ],\n          \"exec_count_per_sec\": 0.019998888950613854,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"\",\n      \"sql_text\": \"\",\n      \"is_other\": true,\n      \"cpu_time_ms\": 660,\n      \"exec_count_per_sec\": 1.161546580745514,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [\n            1641916791, 1641916914, 1641917037, 1641917160, 1641917283,\n            1641917406, 1641917529, 1641917652, 1641917775, 1641917898,\n            1641918021, 1641918144, 1641918267, 1641918390, 1641918513,\n            1641918636, 1641918759, 1641918882, 1641919005, 1641919128,\n            1641919251, 1641919374, 1641919497, 1641919620, 1641919743,\n            1641919866, 1641919989, 1641920112, 1641920235, 1641920358,\n            1641920481, 1641920604, 1641920727, 1641920850, 1641920973,\n            1641921096, 1641921219, 1641921342, 1641921465, 1641921588,\n            1641921711, 1641921834, 1641921957, 1641922080, 1641922203,\n            1641922326, 1641922449, 1641922572, 1641922695, 1641922818,\n            1641922941, 1641923064, 1641923187, 1641923310, 1641923433,\n            1641923556, 1641923679, 1641923802, 1641923925, 1641924048,\n            1641924171, 1641924294, 1641924417, 1641924540, 1641924663,\n            1641924786, 1641924909, 1641925032, 1641925155, 1641925278,\n            1641925401, 1641925524, 1641925647, 1641925770, 1641925893,\n            1641926016, 1641926139, 1641926262, 1641926385, 1641926508,\n            1641926631, 1641926754, 1641926877, 1641927000, 1641927123,\n            1641927246, 1641927369, 1641927492, 1641927615, 1641927738,\n            1641927861, 1641927984, 1641928107, 1641928230, 1641928353,\n            1641928476, 1641928599, 1641928722, 1641928845, 1641928968,\n            1641929091, 1641929214, 1641929337, 1641929460, 1641929583,\n            1641929706, 1641929829, 1641929952, 1641930075, 1641930198,\n            1641930321, 1641930444, 1641930567, 1641930690, 1641930813,\n            1641930936, 1641931059, 1641931182, 1641931305, 1641931428,\n            1641931551, 1641931674, 1641931797, 1641931920, 1641932043,\n            1641932166, 1641932289, 1641932412, 1641932535, 1641932658,\n            1641932781, 1641932904, 1641933027, 1641933150, 1641933273,\n            1641933396, 1641933519, 1641933642, 1641933765, 1641933888,\n            1641934011, 1641934134, 1641934257, 1641934380, 1641934503,\n            1641934626, 1641934749\n          ],\n          \"cpu_time_ms\": [\n            0, 0, 10, 0, 0, 0, 0, 0, 20, 30, 10, 20, 0, 0, 10, 0, 10, 0, 10, 0,\n            0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 10, 20, 0, 10, 0, 0, 0, 0, 0, 10, 0,\n            0, 0, 0, 0, 10, 0, 20, 10, 0, 0, 0, 0, 10, 0, 10, 30, 0, 0, 0, 0, 0,\n            0, 0, 10, 10, 0, 10, 0, 10, 0, 0, 0, 0, 10, 0, 0, 0, 10, 20, 0, 0,\n            0, 0, 10, 0, 0, 0, 30, 10, 10, 0, 20, 0, 20, 0, 0, 0, 20, 0, 20, 0,\n            0, 10, 10, 0, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 10, 0,\n            0, 0, 10, 0, 0, 0, 10, 0, 0, 0, 20, 10, 10, 0, 0, 0, 0, 0, 0, 0, 10,\n            0, 10, 0\n          ],\n          \"exec_count_per_sec\": 1.161546580745514,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    }\n  ],\n  \"status\": \"ok\"\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/fixtures/topsql_summary_large_timerange:end=1641916800&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641484800&top=5&window=2929s.json",
    "content": "{\n  \"data\": [\n    {\n      \"sql_digest\": \"e6f07d43b5c21db0fbb9a31feac2dc599787763393dd5acbfad80e247eb02ad5\",\n      \"sql_text\": \"begin\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 1570,\n      \"exec_count_per_sec\": 0.0991201409255997,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [\n            1641804086, 1641807015, 1641809944, 1641812873, 1641815802,\n            1641818731, 1641821660, 1641824589, 1641827518, 1641830447,\n            1641833376, 1641836305, 1641839234, 1641842163, 1641845092,\n            1641848021, 1641850950, 1641853879, 1641856808, 1641859737,\n            1641862666, 1641865595, 1641868524, 1641871453, 1641874382,\n            1641877311, 1641880240, 1641883169, 1641886098, 1641889027,\n            1641891956, 1641894885, 1641897814, 1641900743, 1641903672,\n            1641906601, 1641909530, 1641912459, 1641915388\n          ],\n          \"cpu_time_ms\": [\n            20, 30, 30, 30, 30, 20, 10, 0, 60, 40, 20, 20, 40, 30, 10, 100, 30,\n            50, 30, 90, 30, 40, 80, 40, 40, 30, 50, 60, 40, 40, 50, 60, 40, 40,\n            70, 70, 20, 50, 30\n          ],\n          \"exec_count_per_sec\": 0.0991201409255997,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"ea4709893ffb8edc8d58191ccbd93c4c4fdfc1d20ebbcc7f48707df328d6dbb2\",\n      \"sql_text\": \"select `version` , `table_id` , `modify_count` , `count` from `mysql` . `stats_meta` where `version` \\u003e ? order by `version`\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 20790,\n      \"exec_count_per_sec\": 0.08658544771887101,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [\n            1641804086, 1641807015, 1641809944, 1641812873, 1641815802,\n            1641818731, 1641821660, 1641824589, 1641827518, 1641830447,\n            1641833376, 1641836305, 1641839234, 1641842163, 1641845092,\n            1641848021, 1641850950, 1641853879, 1641856808, 1641859737,\n            1641862666, 1641865595, 1641868524, 1641871453, 1641874382,\n            1641877311, 1641880240, 1641883169, 1641886098, 1641889027,\n            1641891956, 1641894885, 1641897814, 1641900743, 1641903672,\n            1641906601, 1641909530, 1641912459, 1641915388\n          ],\n          \"cpu_time_ms\": [\n            180, 340, 440, 490, 410, 410, 400, 630, 480, 450, 390, 570, 500,\n            490, 440, 500, 530, 530, 580, 390, 420, 440, 480, 490, 540, 550,\n            520, 470, 480, 370, 430, 430, 430, 410, 380, 590, 360, 300, 370\n          ],\n          \"exec_count_per_sec\": 0,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        },\n        {\n          \"plan_digest\": \"42d48b331dfe53300ddea68d4217dc467244bd898f80f10a913f2104d26d4989\",\n          \"plan_text\": \"\\tProjection        \\troot\\tmysql.stats_meta.count, mysql.stats_meta.modify_count, mysql.stats_meta.table_id, mysql.stats_meta.version\\n\\t└─IndexLookUp     \\troot\\t\\n\\t  ├─IndexRangeScan\\tcop \\ttable:stats_meta, index:idx_ver(version), range:[?,?], keep order:true\\n\\t  └─TableRowIDScan\\tcop \\ttable:stats_meta, keep order:false\",\n          \"timestamp_sec\": [\n            1641804086, 1641807015, 1641809944, 1641812873, 1641815802,\n            1641818731, 1641821660, 1641824589, 1641827518, 1641830447,\n            1641833376, 1641836305, 1641839234, 1641842163, 1641845092,\n            1641848021, 1641850950, 1641853879, 1641856808, 1641859737,\n            1641862666, 1641865595, 1641868524, 1641871453, 1641874382,\n            1641877311, 1641880240, 1641883169, 1641886098, 1641889027,\n            1641891956, 1641894885, 1641897814, 1641900743, 1641903672,\n            1641906601, 1641909530, 1641912459, 1641915388\n          ],\n          \"cpu_time_ms\": [\n            40, 170, 80, 70, 80, 40, 110, 100, 70, 90, 50, 60, 50, 120, 50, 80,\n            60, 80, 90, 110, 50, 50, 50, 60, 50, 90, 60, 100, 120, 100, 60, 160,\n            120, 100, 70, 90, 30, 110, 110\n          ],\n          \"exec_count_per_sec\": 0.08658544771887101,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"e2769f5619db3dc4916298c4c6b3bc2c36c6d64a84e2a5549284521482564ce0\",\n      \"sql_text\": \"select high_priority ( `variable_value` ) from `mysql` . `tidb` where `variable_name` = ? for update\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 2030,\n      \"exec_count_per_sec\": 0.01817588385212071,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [\n            1641804086, 1641807015, 1641809944, 1641812873, 1641815802,\n            1641818731, 1641821660, 1641824589, 1641827518, 1641830447,\n            1641833376, 1641836305, 1641839234, 1641842163, 1641845092,\n            1641848021, 1641850950, 1641853879, 1641856808, 1641859737,\n            1641862666, 1641865595, 1641868524, 1641871453, 1641874382,\n            1641877311, 1641880240, 1641883169, 1641886098, 1641889027,\n            1641891956, 1641894885, 1641897814, 1641900743, 1641903672,\n            1641906601, 1641909530, 1641912459, 1641915388\n          ],\n          \"cpu_time_ms\": [\n            30, 60, 50, 40, 60, 50, 10, 20, 110, 50, 60, 50, 40, 90, 20, 30, 60,\n            20, 50, 30, 50, 30, 100, 10, 60, 20, 10, 70, 10, 90, 30, 60, 50, 20,\n            30, 80, 60, 40, 30\n          ],\n          \"exec_count_per_sec\": 0,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        },\n        {\n          \"plan_digest\": \"892b61d58192589f43d81f3ee50710d71e6a8c286a59877069eaa00cd3bb8f5c\",\n          \"plan_text\": \"\\tProjection          \\troot\\tmysql.tidb.variable_value\\n\\t└─SelectLock        \\troot\\t\\n\\t  └─IndexLookUp     \\troot\\t\\n\\t    ├─IndexRangeScan\\tcop \\ttable:tidb, index:PRIMARY(VARIABLE_NAME), range:[?,?], keep order:false\\n\\t    └─TableRowIDScan\\tcop \\ttable:tidb, keep order:false\",\n          \"timestamp_sec\": [\n            1641804086, 1641807015, 1641809944, 1641812873, 1641815802,\n            1641818731, 1641821660, 1641824589, 1641827518, 1641830447,\n            1641833376, 1641836305, 1641839234, 1641842163, 1641845092,\n            1641848021, 1641850950, 1641853879, 1641856808, 1641859737,\n            1641862666, 1641865595, 1641868524, 1641871453, 1641874382,\n            1641877311, 1641880240, 1641883169, 1641886098, 1641889027,\n            1641891956, 1641894885, 1641897814, 1641900743, 1641903672,\n            1641906601, 1641909530, 1641912459, 1641915388\n          ],\n          \"cpu_time_ms\": [\n            0, 0, 0, 0, 0, 10, 10, 0, 10, 20, 30, 0, 10, 10, 0, 0, 20, 10, 0, 0,\n            10, 0, 10, 10, 0, 0, 20, 10, 0, 0, 10, 10, 0, 20, 0, 0, 0, 10, 10\n          ],\n          \"exec_count_per_sec\": 0.01817588385212071,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"b95a604794f9eff17a1a6a37d754324be11ede348a0d1e53da2bc3c32d6a4142\",\n      \"sql_text\": \"select `variable_name` , `variable_value` from `mysql` . `global_variables` where `variable_name` in ( ... )\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 2380,\n      \"exec_count_per_sec\": 0.08658544771887101,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [1641845092, 1641850950, 1641874382],\n          \"cpu_time_ms\": [10, 10, 10],\n          \"exec_count_per_sec\": 0,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        },\n        {\n          \"plan_digest\": \"9449388a4efbc35c8eca1639aec164392df687869239f9ad16ea37887d98c42a\",\n          \"plan_text\": \"\\tBatch_Point_Get\\troot\\ttable:GLOBAL_VARIABLES, index:PRIMARY(VARIABLE_NAME), keep order:false, desc:false\",\n          \"timestamp_sec\": [\n            1641804086, 1641807015, 1641809944, 1641812873, 1641815802,\n            1641818731, 1641821660, 1641824589, 1641827518, 1641830447,\n            1641833376, 1641836305, 1641839234, 1641842163, 1641845092,\n            1641848021, 1641850950, 1641853879, 1641856808, 1641859737,\n            1641862666, 1641865595, 1641868524, 1641871453, 1641874382,\n            1641877311, 1641880240, 1641883169, 1641886098, 1641889027,\n            1641891956, 1641894885, 1641897814, 1641900743, 1641903672,\n            1641906601, 1641909530, 1641912459, 1641915388\n          ],\n          \"cpu_time_ms\": [\n            10, 40, 50, 40, 40, 40, 50, 80, 40, 90, 100, 70, 90, 60, 50, 60,\n            110, 100, 40, 30, 20, 60, 100, 80, 80, 20, 90, 50, 50, 100, 60, 40,\n            50, 70, 80, 40, 40, 80, 50\n          ],\n          \"exec_count_per_sec\": 0.08658544771887101,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"da5f21b51be132c69a10de76148c95ab1ad8e6fb6c0fdacb3dcedfe4b6d3c41f\",\n      \"sql_text\": \"select distinct `table_id` from `mysql` . `stats_feedback`\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 1810,\n      \"exec_count_per_sec\": 0.017321719162687123,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [\n            1641807015, 1641809944, 1641812873, 1641815802, 1641818731,\n            1641821660, 1641833376, 1641836305, 1641842163, 1641848021,\n            1641853879, 1641859737, 1641862666, 1641865595, 1641871453,\n            1641874382, 1641877311, 1641880240, 1641883169, 1641886098,\n            1641889027, 1641891956, 1641894885, 1641900743, 1641903672,\n            1641906601, 1641909530, 1641912459, 1641915388\n          ],\n          \"cpu_time_ms\": [\n            10, 20, 10, 10, 10, 20, 20, 10, 10, 20, 20, 20, 20, 10, 20, 20, 30,\n            10, 10, 10, 20, 30, 30, 20, 10, 10, 40, 10, 30\n          ],\n          \"exec_count_per_sec\": 0,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        },\n        {\n          \"plan_digest\": \"8d5611b5d6f8ade5b8d2f721db81164a09cf915d836dfcfc3fd00237750350ca\",\n          \"plan_text\": \"\\tHashAgg            \\troot\\tgroup by:mysql.stats_feedback.table_id, funcs:firstrow(mysql.stats_feedback.table_id)-\\u003emysql.stats_feedback.table_id\\n\\t└─IndexReader      \\troot\\tindex:HashAgg_4\\n\\t  └─HashAgg        \\tcop \\tgroup by:mysql.stats_feedback.table_id, \\n\\t    └─IndexFullScan\\tcop \\ttable:stats_feedback, index:hist(table_id, is_index, hist_id), range:[?,?], keep order:false\",\n          \"timestamp_sec\": [\n            1641804086, 1641807015, 1641809944, 1641812873, 1641815802,\n            1641818731, 1641821660, 1641824589, 1641827518, 1641830447,\n            1641833376, 1641836305, 1641839234, 1641842163, 1641845092,\n            1641848021, 1641850950, 1641853879, 1641856808, 1641859737,\n            1641862666, 1641865595, 1641868524, 1641871453, 1641874382,\n            1641877311, 1641880240, 1641883169, 1641886098, 1641889027,\n            1641891956, 1641894885, 1641897814, 1641900743, 1641903672,\n            1641906601, 1641909530, 1641912459, 1641915388\n          ],\n          \"cpu_time_ms\": [\n            10, 40, 50, 30, 10, 40, 70, 50, 30, 60, 20, 10, 50, 50, 20, 40, 40,\n            80, 30, 20, 70, 60, 10, 50, 40, 0, 30, 20, 10, 20, 20, 30, 10, 30,\n            40, 40, 30, 20, 20\n          ],\n          \"exec_count_per_sec\": 0.017321719162687123,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"\",\n      \"sql_text\": \"\",\n      \"is_other\": true,\n      \"cpu_time_ms\": 5390,\n      \"exec_count_per_sec\": 0.21266848919331205,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [\n            1641804086, 1641807015, 1641809944, 1641812873, 1641815802,\n            1641818731, 1641821660, 1641824589, 1641827518, 1641830447,\n            1641833376, 1641836305, 1641839234, 1641842163, 1641845092,\n            1641848021, 1641850950, 1641853879, 1641856808, 1641859737,\n            1641862666, 1641865595, 1641868524, 1641871453, 1641874382,\n            1641877311, 1641880240, 1641883169, 1641886098, 1641889027,\n            1641891956, 1641894885, 1641897814, 1641900743, 1641903672,\n            1641906601, 1641909530, 1641912459, 1641915388\n          ],\n          \"cpu_time_ms\": [\n            90, 120, 290, 180, 160, 120, 100, 50, 150, 180, 140, 120, 120, 90,\n            120, 110, 190, 190, 120, 140, 120, 160, 150, 190, 240, 120, 130, 40,\n            140, 180, 110, 70, 180, 120, 160, 80, 130, 180, 110\n          ],\n          \"exec_count_per_sec\": 0.21266848919331205,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    }\n  ],\n  \"status\": \"ok\"\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/fixtures/topsql_summary_small_timerange:end=1641920460&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641920400&top=5&window=1s.json",
    "content": "{\n  \"data\": [\n    {\n      \"sql_digest\": \"61f4cce222f1a26d6b0650865051ebed3d077412ae053636e2f5acf9dc426a42\",\n      \"sql_text\": \"insert high_priority into `mysql` . `tidb` values ( ... ) on duplicate key update `variable_value` = ? , comment = ?\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 10,\n      \"exec_count_per_sec\": 0.01639344262295082,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [1641920435, 1641920436],\n          \"cpu_time_ms\": [10, 0],\n          \"exec_count_per_sec\": 0.01639344262295082,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"e6f07d43b5c21db0fbb9a31feac2dc599787763393dd5acbfad80e247eb02ad5\",\n      \"sql_text\": \"begin\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 10,\n      \"exec_count_per_sec\": 0.3770491803278688,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [\n            1641920402, 1641920405, 1641920408, 1641920411, 1641920414,\n            1641920417, 1641920420, 1641920423, 1641920426, 1641920429,\n            1641920432, 1641920435, 1641920436, 1641920438, 1641920441,\n            1641920444, 1641920447, 1641920450, 1641920453, 1641920456,\n            1641920459\n          ],\n          \"cpu_time_ms\": [\n            0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n          ],\n          \"exec_count_per_sec\": 0.3770491803278688,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"ea4709893ffb8edc8d58191ccbd93c4c4fdfc1d20ebbcc7f48707df328d6dbb2\",\n      \"sql_text\": \"select `version` , `table_id` , `modify_count` , `count` from `mysql` . `stats_meta` where `version` \\u003e ? order by `version`\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 10,\n      \"exec_count_per_sec\": 0.32786885245901637,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [1641920426],\n          \"cpu_time_ms\": [10],\n          \"exec_count_per_sec\": 0,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        },\n        {\n          \"plan_digest\": \"42d48b331dfe53300ddea68d4217dc467244bd898f80f10a913f2104d26d4989\",\n          \"plan_text\": \"\\tProjection        \\troot\\tmysql.stats_meta.count, mysql.stats_meta.modify_count, mysql.stats_meta.table_id, mysql.stats_meta.version\\n\\t└─IndexLookUp     \\troot\\t\\n\\t  ├─IndexRangeScan\\tcop \\ttable:stats_meta, index:idx_ver(version), range:[?,?], keep order:true\\n\\t  └─TableRowIDScan\\tcop \\ttable:stats_meta, keep order:false\",\n          \"timestamp_sec\": [\n            1641920402, 1641920405, 1641920408, 1641920411, 1641920414,\n            1641920417, 1641920420, 1641920423, 1641920426, 1641920429,\n            1641920432, 1641920435, 1641920438, 1641920441, 1641920444,\n            1641920447, 1641920450, 1641920453, 1641920456, 1641920459\n          ],\n          \"cpu_time_ms\": [\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n          ],\n          \"exec_count_per_sec\": 0.32786885245901637,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"b95a604794f9eff17a1a6a37d754324be11ede348a0d1e53da2bc3c32d6a4142\",\n      \"sql_text\": \"select `variable_name` , `variable_value` from `mysql` . `global_variables` where `variable_name` in ( ... )\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 10,\n      \"exec_count_per_sec\": 0.32786885245901637,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"9449388a4efbc35c8eca1639aec164392df687869239f9ad16ea37887d98c42a\",\n          \"plan_text\": \"\\tBatch_Point_Get\\troot\\ttable:GLOBAL_VARIABLES, index:PRIMARY(VARIABLE_NAME), keep order:false, desc:false\",\n          \"timestamp_sec\": [\n            1641920402, 1641920405, 1641920408, 1641920411, 1641920414,\n            1641920417, 1641920420, 1641920423, 1641920426, 1641920429,\n            1641920432, 1641920435, 1641920438, 1641920441, 1641920444,\n            1641920447, 1641920450, 1641920453, 1641920456, 1641920459\n          ],\n          \"cpu_time_ms\": [\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0\n          ],\n          \"exec_count_per_sec\": 0.32786885245901637,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"e2769f5619db3dc4916298c4c6b3bc2c36c6d64a84e2a5549284521482564ce0\",\n      \"sql_text\": \"select high_priority ( `variable_value` ) from `mysql` . `tidb` where `variable_name` = ? for update\",\n      \"is_other\": false,\n      \"cpu_time_ms\": 0,\n      \"exec_count_per_sec\": 0.06557377049180328,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"892b61d58192589f43d81f3ee50710d71e6a8c286a59877069eaa00cd3bb8f5c\",\n          \"plan_text\": \"\\tProjection          \\troot\\tmysql.tidb.variable_value\\n\\t└─SelectLock        \\troot\\t\\n\\t  └─IndexLookUp     \\troot\\t\\n\\t    ├─IndexRangeScan\\tcop \\ttable:tidb, index:PRIMARY(VARIABLE_NAME), range:[?,?], keep order:false\\n\\t    └─TableRowIDScan\\tcop \\ttable:tidb, keep order:false\",\n          \"timestamp_sec\": [1641920435, 1641920436],\n          \"cpu_time_ms\": [0, 0],\n          \"exec_count_per_sec\": 0.06557377049180328,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    },\n    {\n      \"sql_digest\": \"\",\n      \"sql_text\": \"\",\n      \"is_other\": true,\n      \"cpu_time_ms\": 0,\n      \"exec_count_per_sec\": 0.7868852459016393,\n      \"duration_per_exec_ms\": 0,\n      \"scan_records_per_sec\": 0,\n      \"scan_indexes_per_sec\": 0,\n      \"plans\": [\n        {\n          \"plan_digest\": \"\",\n          \"plan_text\": \"\",\n          \"timestamp_sec\": [\n            1641920402, 1641920405, 1641920408, 1641920411, 1641920414,\n            1641920417, 1641920419, 1641920420, 1641920423, 1641920426,\n            1641920429, 1641920432, 1641920435, 1641920436, 1641920438,\n            1641920441, 1641920444, 1641920447, 1641920449, 1641920450,\n            1641920453, 1641920456, 1641920459\n          ],\n          \"cpu_time_ms\": [\n            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0\n          ],\n          \"exec_count_per_sec\": 0.7868852459016393,\n          \"duration_per_exec_ms\": 0,\n          \"scan_records_per_sec\": 0,\n          \"scan_indexes_per_sec\": 0\n        }\n      ]\n    }\n  ],\n  \"status\": \"ok\"\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/fixtures/uri.json",
    "content": "{\n  \"root\": \"/\",\n  \"login\": \"/signin\",\n  \"overview\": \"/overview\",\n  \"slow_query\": \"/slow_query\",\n  \"statement\": \"/statement\",\n  \"configuration\": \"/configuration\",\n  \"topsql\": \"/topsql\"\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/components.js",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\nexport const testBaseSelectorOptions = (optionsList, dataE2EValue) => {\n  cy.get(`[data-e2e=${dataE2EValue}]`)\n    .click()\n    .then(() => {\n      cy.get('[data-e2e=multi_select_options]')\n        .should('have.length', optionsList.length)\n        .each(($option, $idx) => {\n          cy.wrap($option).should('have.text', optionsList[$idx])\n        })\n    })\n}\n\nexport const checkAllOptionsInBaseSelector = (dataE2EValue) => {\n  cy.get(`[data-e2e=${dataE2EValue}]`)\n    .click()\n    .then(() => {\n      if (cy.get('.ant-dropdown').should('exist')) {\n        cy.get('.ant-dropdown').within(() => {\n          cy.get('[role=columnheader]')\n            .eq(0)\n            .within(() => {\n              cy.get('.ant-checkbox').click()\n            })\n        })\n      }\n    })\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/login/login_session.spec.js",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\ndescribe('Login session', () => {\n  beforeEach(() => {\n    cy.fixture('uri.json').then(function (uri) {\n      this.uri = uri\n    })\n  })\n\n  it('Redirect to sigin page when user not login', function () {\n    cy.visit(this.uri.overview)\n    /* eslint-disable no-unused-expressions */\n    expect(localStorage.getItem('dashboard_auth_token')).to.be.null\n    cy.url().should('include', this.uri.login)\n  })\n\n  // Use fake token to indicate session expired.\n  it('Redirect user to sigin page when session token expired', function () {\n    // Set `dashboard_auth_token` with an invalid token\n    localStorage.setItem('dashboard_auth_token', 'invalid_auth_token')\n    cy.visit(this.uri.overview)\n\n    cy.url().should('include', this.uri.login)\n    cy.get('.ant-message').should('be.visible')\n    cy.get('.ant-message-error > span:last-child').should(\n      'has.text',\n      'Please sign in again (session is expired)'\n    )\n  })\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/login/user_login.compat_spec.js",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Test User Login Compatibility\n// FEATURE_VERSION < 5.3.0 of TiDB Dashboard does not support nonRootLogin\n// FEATURE_VERSION >= 5.3.0 of TiDB Dashboard supports nonRootLogin\ndescribe('User Login', () => {\n  if (Cypress.env('FEATURE_VERSION') === '6.0.0') {\n    // Create user test\n    before(() => {\n      let queryData = {\n        query: 'DROP USER IF EXISTS \"test\"@\"%\"'\n      }\n      cy.task('queryDB', { ...queryData })\n\n      queryData = {\n        query: \"CREATE USER 'test'@'%' IDENTIFIED BY 'test_pwd'\"\n      }\n\n      cy.task('queryDB', { ...queryData })\n\n      queryData = {\n        query: \"GRANT ALL PRIVILEGES ON *.* TO 'test'@'%' WITH GRANT OPTION\"\n      }\n      cy.task('queryDB', { ...queryData })\n    })\n\n    // Run before each test\n    beforeEach(() => {\n      // Load a fixed set of data located in cypress/fixtures.\n      // Direct to login page\n      cy.fixture('uri.json').then(function (uri) {\n        this.uri = uri\n        cy.visit(this.uri.overview)\n      })\n    })\n\n    it('noRootLogin is supported', () => {\n      cy.log('FEATURE_VERSION is: ', Cypress.env('FEATURE_VERSION'))\n\n      // Check username input is not disabled\n      cy.get('[data-e2e=signin_username_input]').should('not.be.disabled')\n    })\n\n    it('nonRoot user with correct password', function () {\n      cy.get('[data-e2e=signin_username_input]').clear().type('test')\n      cy.get('[data-e2e=\"signin_password_input\"]').type('test_pwd{enter}')\n      cy.url().should('include', this.uri.overview)\n    })\n\n    it('nonRoot user with incorrect password', () => {\n      cy.intercept('POST', '/dashboard/api/user/login').as('login')\n\n      cy.get('[data-e2e=signin_username_input]').clear().type('test')\n      cy.get('[data-e2e=\"signin_password_input\"]').type('incorrect_pwd{enter}')\n\n      cy.wait('@login').should(({ response }) => {\n        expect(response.body).to.have.property('code', 'tidb.tidb_auth_failed')\n      })\n    })\n  } else if (Cypress.env('FEATURE_VERSION') === '5.0.0') {\n    beforeEach(() => {\n      cy.fixture('uri.json').then(function (uri) {\n        this.uri = uri\n        cy.visit(this.uri.overview)\n      })\n    })\n\n    it('noRootLogin is unsupported', () => {\n      cy.log('FEATURE_VERSION is: ', Cypress.env('FEATURE_VERSION'))\n\n      // Check username input is disabled\n      cy.get('[data-e2e=signin_username_input]').should('be.disabled')\n    })\n  }\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/login/user_login.spec.js",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Test User login\ndescribe('Root User Login', () => {\n  beforeEach(function () {\n    cy.fixture('uri.json').then(function (uri) {\n      this.uri = uri\n      cy.visit(this.uri.root)\n    })\n  })\n\n  it('authenticated redirect', function () {\n    // Redirect to login\n    cy.visit(this.uri.root)\n    cy.url().should('eq', `${Cypress.config().baseUrl}${this.uri.root}`)\n  })\n\n  it('root login with no pwd', function () {\n    cy.get('[data-e2e=signin_username_input]').should('have.value', 'root')\n    cy.get('[data-e2e=signin_submit]').click()\n    cy.url().should('include', this.uri.overview)\n  })\n\n  it('remember last succeeded login username', () => {\n    cy.get('[data-e2e=signin_username_input]').should('have.value', 'root')\n  })\n\n  it('root login with incorrect pwd', () => {\n    cy.intercept('POST', `${Cypress.env('apiBasePath')}user/login`).as('login')\n\n    // {enter} causes the form to submit\n    cy.get('[data-e2e=\"signin_password_input\"]').type(\n      'incorrect_password{enter}'\n    )\n\n    cy.wait('@login').should(({ response }) => {\n      expect(response.body).to.have.property('code', 'tidb.tidb_auth_failed')\n    })\n  })\n\n  it('root login with correct pwd', function () {\n    // set password for root\n    let queryData = {\n      query: 'SET PASSWORD FOR \"root\"@\"%\" = \"root_pwd\"'\n    }\n    cy.task('queryDB', { ...queryData })\n\n    cy.get('[data-e2e=\"signin_password_input\"]').type('root_pwd{enter}')\n    cy.url().should('include', this.uri.overview)\n\n    // set empty password for root\n    queryData = {\n      query: 'SET PASSWORD FOR \"root\"@\"%\" = \"\"',\n      password: 'root_pwd'\n    }\n    cy.task('queryDB', { ...queryData })\n  })\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/slow_query/01-list.spec.js",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\nimport dayjs from 'dayjs'\nimport {\n  restartTiUP,\n  validateSlowQueryCSVList,\n  deleteDownloadsFolder\n} from '../utils'\nimport { testBaseSelectorOptions } from '../components'\n\nconst neatCSV = require('neat-csv')\nconst path = require('path')\n\ndescribe('SlowQuery list page', () => {\n  before(() => {\n    cy.fixture('uri.json').then(function (uri) {\n      this.uri = uri\n    })\n\n    // Restart tiup\n    restartTiUP()\n\n    deleteDownloadsFolder()\n  })\n\n  beforeEach(function () {\n    cy.login('root')\n    cy.visit(this.uri.slow_query)\n    cy.url().should('include', this.uri.slow_query)\n  })\n\n  describe('Initialize slow query page', () => {\n    it('Slow query side bar highlighted', () => {\n      cy.get('[data-e2e=menu_item_slow_query]')\n        .should('be.visible')\n        .and('has.class', 'ant-menu-item-selected')\n    })\n\n    it('Has Toolbar', function () {\n      cy.get('[data-e2e=slow_query_toolbar]').should('be.visible')\n    })\n\n    it('Get slow query bad request', () => {\n      const staticResponse = {\n        statusCode: 400,\n        body: {\n          code: 'common.bad_request',\n          error: true,\n          message: 'common.bad_request'\n        }\n      }\n\n      // stub out a response body\n      cy.intercept(\n        `${Cypress.env('apiBasePath')}slow_query/list*`,\n        staticResponse\n      ).as('slow_query_list')\n      cy.wait('@slow_query_list').then(() => {\n        cy.get('[data-e2e=alert_error_bar]').should(\n          'has.text',\n          staticResponse.body.message\n        )\n      })\n    })\n  })\n\n  describe('Filter slow query list', () => {\n    it('Run workload', () => {\n      const workloads = [\n        'SELECT sleep(1);',\n        'SELECT sleep(0.4);',\n        'SELECT sleep(2);'\n      ]\n\n      const waitTwoSecond = (query, idx) =>\n        new Promise((resolve) => {\n          // run workload every 5 seconds\n          setTimeout(() => {\n            resolve(query)\n          }, 5000 * idx)\n        })\n\n      workloads.forEach((query, idx) => {\n        cy.wrap(waitTwoSecond(query, idx)).then((query) => {\n          // return a promise to cy.then() that\n          // is awaited until it resolves\n          cy.task('queryDB', { query })\n        })\n      })\n    })\n\n    describe('Filter slow query by changing time range', () => {\n      let defaultSlowQueryList\n      let lastSlowQueryTimeStamp\n      let firstQueryTimeRangeStart,\n        secondQueryTimeRangeStart,\n        thirdQueryTimeRangeStart,\n        thirdQueryTimeRangeEnd\n\n      it('Default time range is 30 mins', () => {\n        cy.get('[data-e2e=selected_timerange]').should(\n          'has.text',\n          'Recent 30 min'\n        )\n      })\n\n      it('Show all slow_query', () => {\n        cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as(\n          'slow_query'\n        )\n\n        cy.wait('@slow_query').then((res) => {\n          defaultSlowQueryList = res.response.body\n\n          if (defaultSlowQueryList.length > 0) {\n            lastSlowQueryTimeStamp = defaultSlowQueryList[0].timestamp\n\n            const calTimestamp = (timestampDiff) => {\n              return dayjs\n                .unix(lastSlowQueryTimeStamp - timestampDiff)\n                .format('YYYY-MM-DD HH:mm:ss')\n            }\n            firstQueryTimeRangeStart = calTimestamp(12)\n            secondQueryTimeRangeStart = calTimestamp(7)\n            thirdQueryTimeRangeStart = calTimestamp(2)\n            thirdQueryTimeRangeEnd = calTimestamp(-3)\n          }\n        })\n      })\n\n      describe('Check slow query', () => {\n        it('Check slow query in the 1st 5 seconds time range', () => {\n          cy.get('[data-e2e=timerange-selector]')\n            .click()\n            .then(() => {\n              cy.get('.ant-picker-range').click()\n              cy.get('.ant-picker-input-active').type(\n                `${firstQueryTimeRangeStart}{leftarrow}{leftarrow}{backspace}{enter}`\n              )\n              cy.get('.ant-picker-input-active').type(\n                `${secondQueryTimeRangeStart}{leftarrow}{leftarrow}{backspace}{enter}`\n              )\n            })\n            .then(() => {\n              cy.get('[data-automation-key=query]')\n                .should('has.length', 1)\n                .and('has.text', 'SELECT sleep(1);')\n            })\n        })\n\n        it('Check slow query in the 2nd 5 seconds time range', () => {\n          cy.get('[data-e2e=timerange-selector]')\n            .click()\n            .then(() => {\n              cy.get('.ant-picker-range').click()\n              cy.get('.ant-picker-input-active').type(\n                `${secondQueryTimeRangeStart}{leftarrow}{leftarrow}{backspace}{enter}`\n              )\n              cy.get('.ant-picker-input-active').type(\n                `${thirdQueryTimeRangeStart}{leftarrow}{leftarrow}{backspace}{enter}`\n              )\n            })\n            .then(() => {\n              cy.get('[data-automation-key=query]').should('has.length', 0)\n            })\n        })\n\n        it('Check slow query in the 3rd 5 seconds time range', () => {\n          cy.get('[data-e2e=timerange-selector]')\n            .click()\n            .then(() => {\n              cy.get('.ant-picker-range').click()\n              cy.get('.ant-picker-input-active').type(\n                `${thirdQueryTimeRangeStart}{leftarrow}{leftarrow}{backspace}{enter}`\n              )\n              cy.get('.ant-picker-input-active').type(\n                `${thirdQueryTimeRangeEnd}{leftarrow}{leftarrow}{backspace}{enter}`\n              )\n            })\n            .then(() => {\n              cy.get('[data-automation-key=query]')\n                .should('has.length', 1)\n                .and('has.text', 'SELECT sleep(2);')\n            })\n        })\n\n        it('Check slow query in the latest 15 seconds time range', () => {\n          cy.get('[data-e2e=timerange-selector]')\n            .click()\n            .then(() => {\n              cy.get('.ant-picker-range').click()\n              cy.get('.ant-picker-input-active').type(\n                `${firstQueryTimeRangeStart}{leftarrow}{leftarrow}{backspace}{enter}`\n              )\n              cy.get('.ant-picker-input-active').type(\n                `${thirdQueryTimeRangeEnd}{leftarrow}{leftarrow}{backspace}{enter}`\n              )\n            })\n            .then(() => {\n              cy.get('[data-automation-key=query]').should('has.length', 2)\n            })\n        })\n      })\n    })\n\n    describe('Filter slow query by changing database', () => {\n      it('No database selected by default', () => {\n        cy.get('[data-e2e=base_select_input_text]').should(\n          'has.text',\n          'All Databases'\n        )\n      })\n\n      it('Show all databases', () => {\n        cy.intercept(`${Cypress.env('apiBasePath')}info/databases`).as(\n          'databases'\n        )\n\n        cy.wait('@databases').then((res) => {\n          const databaseList = res.response.body\n          testBaseSelectorOptions(databaseList, 'execution_database_name')\n        })\n      })\n\n      it('Run workload without use database', () => {\n        let queryData = {\n          query: 'SELECT sleep(1.5);',\n          database: ''\n        }\n        cy.task('queryDB', { ...queryData })\n\n        // eslint-disable-next-line cypress/no-unnecessary-waiting\n        cy.wait(2000)\n        cy.reload()\n        // global and use database queries will be listed\n        cy.get('[data-automation-key=query]').should('has.length', 3)\n\n        cy.get('[data-e2e=base_select_input_text]')\n          .click({ force: true })\n          .then(() => {\n            cy.get('.ms-DetailsHeader-checkTooltip')\n              .click({ force: true })\n              .then(() => {\n                // global query will not be listed\n                cy.get('[data-automation-key=query]').should('has.length', 2)\n              })\n          })\n      })\n    })\n\n    describe('Search function', () => {\n      it('Default search text', () => {\n        cy.get('[data-e2e=slow_query_search]').should('be.empty')\n      })\n\n      it('Search item with space', () => {\n        cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as(\n          'slow_query_list'\n        )\n        cy.wait('@slow_query_list')\n\n        cy.get('[data-e2e=slow_query_search]').type(\n          ' SELECT sleep\\\\(1\\\\) {enter}'\n        )\n\n        cy.wait('@slow_query_list')\n        cy.get('[data-automation-key=query]').should('has.length', 1)\n\n        // clear search text\n        cy.get('[data-e2e=slow_query_search]').clear().type('{enter}')\n\n        cy.wait('@slow_query_list')\n        cy.get('[data-automation-key=query]').should('has.length', 3)\n      })\n\n      it('Type search without pressing enter then reload', () => {\n        cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as(\n          'slow_query_list'\n        )\n        cy.wait('@slow_query_list')\n\n        cy.get('[data-e2e=slow_query_search]').type(' SELECT sleep\\\\(1\\\\)')\n        cy.wait('@slow_query_list')\n        cy.get('[data-automation-key=query]').should('has.length', 1)\n\n        cy.reload()\n        cy.get('[data-automation-key=query]').should('has.length', 1)\n      })\n    })\n\n    describe('Slow query list limitation', () => {\n      it('Default limit', () => {\n        cy.get('[data-e2e=slow_query_limit_select]').contains('100')\n      })\n\n      const limitOptions = ['100', '200', '500', '1000']\n\n      it('Check limit options', () => {\n        cy.get('[data-e2e=slow_query_limit_select]')\n          .click()\n          .then(() => {\n            cy.get('[data-e2e=slow_query_limit_option]')\n              .should('have.length', 4)\n              .each(($option, $idx) => {\n                cy.wrap($option).contains(limitOptions[$idx])\n              })\n          })\n      })\n\n      it('Check config remembered', () => {\n        cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as(\n          'slow_query_list'\n        )\n        cy.wait('@slow_query_list')\n\n        cy.get('[data-e2e=slow_query_limit_select]').click()\n        cy.get('[data-e2e=slow_query_limit_option]').eq(1).click()\n\n        cy.wait('@slow_query_list')\n        cy.reload()\n        cy.get('[data-e2e=slow_query_limit_select]').contains('200')\n      })\n    })\n\n    describe('Selected Columns', () => {\n      const defaultColumns = ['Query', 'Finish Time', 'Latency', 'Max Memory']\n      const defaultColumnsKeys = [\n        'query',\n        'timestamp',\n        'query_time',\n        'memory_max'\n      ]\n      it('Default selected columns', () => {\n        cy.get('[role=columnheader]')\n          .not('.is-empty')\n          .should('have.length', 4)\n          .each(($column, $idx) => {\n            cy.wrap($column).contains(defaultColumns[$idx])\n          })\n      })\n\n      it('Hover on columns selector and check selected fields ', () => {\n        cy.get('[data-e2e=columns_selector_popover]')\n          .trigger('mouseover')\n          .then(() => {\n            cy.get('[data-e2e=columns_selector_popover_content]')\n              .should('be.visible')\n              .within(() => {\n                // check default selectedColumns checked\n                defaultColumns.forEach((c, idx) => {\n                  cy.contains(c)\n                    .parent()\n                    .within(() => {\n                      cy.get(\n                        `[data-e2e=columns_selector_field_${defaultColumnsKeys[idx]}]`\n                      ).should('be.checked')\n                    })\n                })\n              })\n          })\n      })\n\n      it('Select all column fields and then reset', () => {\n        cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as(\n          'slow_query_list'\n        )\n        cy.wait('@slow_query_list')\n\n        cy.get('[data-e2e=columns_selector_popover]').trigger('mouseover')\n        cy.get('[data-e2e=column_selector_title]').check()\n\n        cy.wait('@slow_query_list')\n        cy.get('[role=columnheader]').not('.is-empty').should('have.length', 44)\n\n        // Columns should be remembered\n        cy.reload()\n        cy.wait('@slow_query_list')\n        cy.get('[role=columnheader]').not('.is-empty').should('have.length', 44)\n\n        // Click reset\n        cy.get('[data-e2e=columns_selector_popover]').trigger('mouseover')\n        cy.get('[data-e2e=column_selector_reset]').click()\n\n        cy.wait('@slow_query_list')\n        cy.get('[role=columnheader]').not('.is-empty').should('have.length', 4)\n      })\n\n      it('Select an arbitary column field', () => {\n        cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as(\n          'slow_query_list'\n        )\n        cy.wait('@slow_query_list')\n\n        cy.get('[data-e2e=columns_selector_popover]').trigger('mouseover')\n\n        cy.contains('Max Disk').within(() => {\n          cy.get('[data-e2e=columns_selector_field_disk_max]').check()\n        })\n\n        cy.wait('@slow_query_list')\n        cy.get('[role=columnheader]')\n          .not('.is-empty')\n          .last()\n          .should('have.text', 'Max Disk ')\n\n        // FIXME: the next contains should be performed over the popup only\n        // cy.contains('Max Disk').within(() => {\n        //   cy.get('[data-e2e=columns_selector_field_disk_max]').uncheck()\n        // })\n\n        // cy.wait('@slow_query_list')\n        // cy.get('[role=columnheader]').eq(1).should('have.text', 'Finish Time ')\n      })\n\n      it('Check SLOW_QUERY_SHOW_FULL_SQL', () => {\n        cy.get('[data-e2e=columns_selector_popover]')\n          .trigger('mouseover')\n          .then(() => {\n            cy.get('[data-e2e=slow_query_show_full_sql]')\n              .check()\n              .then(() => {\n                cy.get('[data-automation-key=query]')\n                  .eq(0)\n                  .find('[data-e2e=syntax_highlighter_original]')\n              })\n\n            cy.get('[data-e2e=slow_query_show_full_sql]')\n              .uncheck()\n              .then(() => {\n                cy.get('[data-automation-key=query]')\n                  .eq(0)\n                  .trigger('mouseover')\n                  .find('[data-e2e=syntax_highlighter_compact]')\n              })\n          })\n      })\n    })\n  })\n\n  describe('Refresh table list', () => {\n    it('Click refresh will update table list', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as(\n        'slow_query_list'\n      )\n      cy.wait('@slow_query_list')\n      cy.contains('SELECT sleep(1.2)').should('not.exist')\n\n      const queryData = {\n        query: 'SELECT sleep(1.2)'\n      }\n      cy.task('queryDB', { ...queryData })\n      cy.get('[data-e2e=slow_query_search]').type('{enter}')\n      cy.wait('@slow_query_list')\n      cy.contains('SELECT sleep(1.2)')\n    })\n  })\n\n  describe('Table list order', () => {\n    it('Default order(desc) by Timestamp', () => {\n      const defaultOrderByTimestamp = [\n        'SELECT sleep(1.2);',\n        'SELECT sleep(1.5);',\n        'SELECT sleep(2);',\n        'SELECT sleep(1);'\n      ]\n      cy.get('[data-automation-key=query]').each(($query, $idx) => {\n        cy.wrap($query).should('have.text', defaultOrderByTimestamp[$idx])\n      })\n    })\n\n    it('Asc order by Timestamp', () => {\n      const AscOrderByTimestamp = [\n        'SELECT sleep(1);',\n        'SELECT sleep(2);',\n        'SELECT sleep(1.5);',\n        'SELECT sleep(1.2);'\n      ]\n\n      cy.get('[data-item-key=timestamp]')\n        .should('be.visible')\n        .click()\n        .then(() => {\n          cy.get('[data-automation-key=query]').each(($query, $idx) => {\n            cy.wrap($query).should('have.text', AscOrderByTimestamp[$idx])\n          })\n        })\n    })\n\n    it('Desc/Asc order by Latency', () => {\n      const DescOrderByLatency = [\n        'SELECT sleep(2);',\n        'SELECT sleep(1.5);',\n        'SELECT sleep(1.2);',\n        'SELECT sleep(1);'\n      ]\n\n      cy.get('[data-item-key=query_time]')\n        .should('be.visible')\n        .click()\n        .then(() => {\n          // Desc order by Latency\n          cy.get('[data-automation-key=query]').each(($query, $idx) => {\n            cy.wrap($query).should('have.text', DescOrderByLatency[$idx])\n          })\n        })\n        .then(() => {\n          const AscOrderByLatency = [\n            'SELECT sleep(1);',\n            'SELECT sleep(1.2);',\n            'SELECT sleep(1.5);',\n            'SELECT sleep(2);'\n          ]\n\n          // Asc order by Latency\n          cy.get('[data-item-key=query_time]')\n            .should('be.visible')\n            .click()\n            .then(() => {\n              cy.get('[data-automation-key=query]').each(($query, $idx) => {\n                cy.wrap($query).should('have.text', AscOrderByLatency[$idx])\n              })\n            })\n        })\n    })\n  })\n\n  describe('Go to slow query detail page', () => {\n    it('Click first slow query and go to detail page', function () {\n      cy.get('[data-automationid=ListCell]')\n        .eq(0)\n        .click()\n        .then(() => {\n          cy.url().should('include', `${this.uri.slow_query}/detail`)\n          cy.get('[data-e2e=syntax_highlighter_compact]').should(\n            'have.text',\n            'SELECT sleep(1.2);'\n          )\n        })\n    })\n  })\n\n  // FIXME: The following tests will break slow-query details E2E since it executes a SQL.\n  // Fix the slow-query details E2E first.\n\n  // describe('Slow network condition', () => {\n  //   const slowNetworkText = 'On-the-fly update is disabled'\n\n  //   it('Does not show slow information when network is fast', () => {\n  //     cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`).as(\n  //       'slow_query_list'\n  //     )\n\n  //     cy.wait('@slow_query_list')\n\n  //     cy.wait(500)\n  //     cy.contains(slowNetworkText).should('not.exist')\n  //   })\n\n  //   it('Show slow information', () => {\n  //     cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`, (req) => {\n  //       req.on('response', (res) => {\n  //         res.setDelay(3000)\n  //       })\n  //     }).as('slow_query_list')\n\n  //     cy.wait('@slow_query_list')\n  //     cy.contains(slowNetworkText)\n  //   })\n\n  //   it('Does not send request automatically when network is slow', () => {\n  //     cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`, (req) => {\n  //       req.on('response', (res) => {\n  //         res.setDelay(3000)\n  //       })\n  //     }).as('slow_query_list')\n\n  //     cy.wait('@slow_query_list')\n  //     cy.contains(slowNetworkText)\n\n  //     const queryData = {\n  //       query: 'SELECT 41212, sleep(1)',\n  //     }\n  //     cy.task('queryDB', { ...queryData })\n  //     cy.reload()\n  //     cy.wait('@slow_query_list')\n  //     cy.contains(slowNetworkText)\n\n  //     cy.get('[data-e2e=slow_query_search]').type('SELECT 41212')\n\n  //     cy.wait(1000)\n  //     cy.get('[data-e2e=syntax_highlighter_compact]').contains(\n  //       'SELECT sleep(1.2)'\n  //     ) // TODO: this depends on a previous test to finish..\n\n  //     // request is sent only after a manual refresh\n  //     cy.get('[data-e2e=slow_query_search]').type('{enter}')\n  //     cy.wait('@slow_query_list')\n  //     cy.get('[data-e2e=syntax_highlighter_compact]').contains('SELECT 41212')\n  //     cy.get('[data-e2e=syntax_highlighter_compact]')\n  //       .contains('SELECT sleep(1.2)')\n  //       .should('not.exist')\n  //   })\n\n  //   it('Updates the info when network is no longer slow', () => {\n  //     let shouldDelay = true\n  //     cy.intercept(`${Cypress.env('apiBasePath')}slow_query/list*`, (req) => {\n  //       req.on('response', (res) => {\n  //         if (shouldDelay) {\n  //           res.setDelay(3000)\n  //         }\n  //       })\n  //     }).as('slow_query_list')\n\n  //     cy.wait('@slow_query_list')\n  //     cy.contains(slowNetworkText)\n  //     cy.get('[data-e2e=syntax_highlighter_compact]')\n  //       .contains('SELECT sleep(1.2)')\n  //       .then(() => {\n  //         shouldDelay = false\n  //       })\n\n  //     cy.get('[data-e2e=slow_query_search]').type('{enter}')\n  //     cy.wait('@slow_query_list')\n\n  //     cy.wait(500)\n  //     cy.contains(slowNetworkText).should('not.exist')\n\n  //     // On-the-fly request should be recovered\n  //     cy.get('[data-e2e=slow_query_search]').type('SELECT 41212')\n  //     cy.wait('@slow_query_list')\n  //     cy.get('[data-e2e=syntax_highlighter_compact]').contains('SELECT 41212')\n  //     cy.get('[data-e2e=syntax_highlighter_compact]')\n  //       .contains('SELECT sleep(1.2)')\n  //       .should('not.exist')\n  //   })\n  // })\n\n  describe('Export slow query CSV ', () => {\n    it('validate CSV File', () => {\n      const downloadsFolder = Cypress.config('downloadsFolder')\n      let downloadedFilename\n\n      cy.get('[data-e2e=slow_query_export_menu]')\n        .trigger('mouseover')\n        .then(() => {\n          cy.window()\n            .document()\n            .then(function (doc) {\n              // Clicking link to download file causes page load timeout\n              // it's a workround that fires a new page load event to skip this issue\n              // Related issue: https://github.com/cypress-io/cypress/issues/14857\n              doc.addEventListener('click', () => {\n                setTimeout(function () {\n                  doc.location?.reload()\n                }, 5000)\n              })\n\n              // Make sure the file exists\n              cy.intercept(\n                `${Cypress.env('apiBasePath')}slow_query/download?token=*`\n              ).as('download_slow_query')\n\n              cy.get('[data-e2e=slow_query_export_btn]').click()\n            })\n        })\n        .then(() => {\n          cy.wait('@download_slow_query').then((res) => {\n            // join downloadFolder with CSV filename\n            const filenameRegx = /\"(.*)\"/\n            downloadedFilename = path.join(\n              downloadsFolder,\n              res.response.headers['content-disposition'].match(filenameRegx)[1]\n            )\n\n            cy.readFile(downloadedFilename, { timeout: 15000 })\n              // parse CSV text into objects\n              .then(neatCSV)\n              .then(validateSlowQueryCSVList)\n          })\n        })\n    })\n  })\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/slow_query/02-detail.spec.js",
    "content": "describe('Slow query detail page E2E test', () => {\n  before(() => {\n    cy.fixture('uri.json').then(function (uri) {\n      this.uri = uri\n    })\n  })\n\n  beforeEach(function () {\n    cy.login('root')\n    cy.visit(this.uri.slow_query)\n    cy.url().should('include', this.uri.slow_query)\n    cy.intercept(`${Cypress.env('apiBasePath')}slow_query/detail?*`).as(\n      'slow_query_detail'\n    )\n\n    cy.get('[data-automation-key=query]').eq(0).click()\n  })\n\n  describe('Query descriptions', () => {\n    it('Check sql and default format', () => {\n      // sql is collapsed by default\n      cy.get('[data-e2e=expandText]').eq(0).should('have.text', 'Expand')\n      cy.get('[data-e2e=statement_query_detail_page_query]')\n        .eq(0)\n        .find('[data-e2e=syntax_highlighter_compact]')\n        .and('have.text', 'SELECT sleep(1.2);')\n    })\n\n    it('Expand sql', () => {\n      // expand sql\n      cy.get('[data-e2e=expandText]').eq(0).click()\n\n      // sql is collapsed by default\n      cy.get('[data-e2e=collapseText]').eq(0).should('have.text', 'Collapse')\n      cy.get('[data-e2e=statement_query_detail_page_query]')\n        .eq(0)\n        .find('[data-e2e=syntax_highlighter_original]')\n        .and('have.text', 'SELECT\\n  sleep(1.2);')\n    })\n\n    it('Copy formatted sql to clipboard', () => {\n      // Prompt alert when testing copy to clipboard on chromium-based browser\n      // https://github.com/cypress-io/cypress/issues/2739\n      cy.window().then((win) => {\n        cy.stub(win, 'prompt').returns(win.prompt).as('copyToClipboardPrompt')\n      })\n\n      // cypress cannot simulate  copy to the clipboard event,\n      // we need to use realClick to fire copy event.\n      // Related desc: https://github.com/dmtrKovalenko/cypress-real-events#why\n      cy.get('[data-e2e=copy_formatted_sql_to_clipboard]')\n        .realClick()\n        .then(() => {\n          cy.task('getClipboard').should('eq', 'SELECT\\n  sleep(1.2);')\n        })\n\n      cy.get('[data-e2e=copied_success]').should('exist')\n    })\n\n    it('Copy original sql to clipboard', () => {\n      cy.window().then((win) => {\n        cy.stub(win, 'prompt').returns(win.prompt).as('copyToClipboardPrompt')\n      })\n\n      cy.get('[data-e2e=copy_original_sql_to_clipboard]')\n        .realClick()\n        .then(() => {\n          cy.task('getClipboard').should('eq', 'SELECT sleep(1.2);')\n        })\n\n      cy.get('[data-e2e=copied_success]').should('exist')\n    })\n  })\n\n  describe('Plan descriptions', () => {\n    it('Check sql and default format', () => {\n      // sql is collapsed by default\n      cy.get('[data-e2e=expandText]').eq(1).should('have.text', 'Expand')\n\n      cy.wait('@slow_query_detail').then((res) => {\n        const responseBody = res.response.body\n        cy.get('[data-e2e=statement_query_detail_page_query]')\n          .eq(1)\n          .and('have.text', responseBody.plan)\n      })\n    })\n  })\n\n  describe('Detail tabs', () => {\n    it('Check tabs list', () => {\n      const tabList = ['Basic', 'Time', 'Coprocessor', 'Transaction']\n      cy.get('[data-e2e=tabs]')\n        .find('.ant-tabs-tab')\n        .should('have.length', 4)\n        .each(($tab, index) => {\n          cy.wrap($tab).should('have.text', tabList[index])\n        })\n    })\n  })\n\n  describe('Detail table tabs', () => {\n    it('Basic table rows count', () => {\n      cy.get('.ms-List-cell').should('have.length', 14)\n    })\n\n    it('Time table rows count', () => {\n      cy.get('.ant-tabs-tab').eq(1).click()\n      cy.get('.ms-List-cell').should('have.length', 21)\n    })\n\n    it('Coprocessor table rows count', () => {\n      cy.get('.ant-tabs-tab').eq(2).click()\n      cy.get('.ms-List-cell').should('have.length', 10)\n    })\n\n    it('Transaction table rows count', () => {\n      cy.get('.ant-tabs-tab').eq(3).click()\n      cy.get('.ms-List-cell').should('have.length', 5)\n    })\n  })\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/slow_query/list.compat_spec.js",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\nimport { skipOn } from '@cypress/skip-test'\n\ndescribe('SlowQuery list compatibility test', () => {\n  before(() => {\n    cy.fixture('uri.json').then(function (uri) {\n      this.uri = uri\n    })\n  })\n\n  beforeEach(function () {\n    cy.login('root')\n    cy.visit(this.uri.slow_query)\n    cy.url().should('include', this.uri.slow_query)\n  })\n\n  describe('Available fields', () => {\n    skipOn(Cypress.env('FEATURE_VERSION') !== '6.0.0', () => {\n      it('Show all available fields', () => {\n        cy.intercept(\n          `${Cypress.env('apiBasePath')}slow_query/available_fields`\n        ).as('getAvailableFields')\n        cy.wait('@getAvailableFields')\n\n        const availableFields = [\n          'query',\n          'digest',\n          'instance',\n          'db',\n          'connection_id',\n          'timestamp',\n\n          'query_time',\n          'parse_time',\n          'compile_time',\n          'process_time',\n          'memory_max',\n          'disk_max',\n\n          'txn_start_ts',\n          'success',\n          'is_internal',\n          'index_names',\n          'stats',\n          'backoff_types',\n\n          'user',\n          'host',\n\n          'wait_time',\n          'backoff_time',\n          'get_commit_ts_time',\n          'local_latch_wait_time',\n          'prewrite_time',\n          'commit_time',\n          'commit_backoff_time',\n          'resolve_lock_time',\n\n          'cop_proc_avg',\n          'cop_wait_avg',\n          'write_keys',\n          'write_size',\n          'prewrite_region',\n          'txn_retry',\n          'request_count',\n          'process_keys',\n          'total_keys',\n          'cop_proc_addr',\n          'cop_wait_addr',\n          'rocksdb_delete_skipped_count',\n          'rocksdb_key_skipped_count',\n          'rocksdb_block_cache_hit_count',\n          'rocksdb_block_read_count',\n          'rocksdb_block_read_byte'\n        ]\n\n        cy.get('[data-e2e=\"columns_selector_popover\"]').trigger('mouseover')\n        availableFields.forEach((f) => {\n          cy.get(`[data-e2e=\"columns_selector_field_${f}\"]`).should('exist')\n        })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/statement/01-list.spec.js",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\nimport dayjs from 'dayjs'\n\nimport {\n  restartTiUP,\n  validateStatementCSVList,\n  deleteDownloadsFolder\n} from '../utils'\nimport {\n  testBaseSelectorOptions,\n  checkAllOptionsInBaseSelector\n} from '../components'\n\nconst neatCSV = require('neat-csv')\nconst path = require('path')\n\ndescribe('SQL statements list page', () => {\n  before(() => {\n    cy.fixture('uri.json').then(function (uri) {\n      this.uri = uri\n    })\n\n    restartTiUP()\n\n    deleteDownloadsFolder()\n  })\n\n  beforeEach(function () {\n    cy.login('root')\n    cy.visit(this.uri.statement)\n    cy.url().should('include', this.uri.statement)\n  })\n\n  const defaultExecStmtList = [\n    'SHOW DATABASES',\n    'SELECT DISTINCT `stmt_type` FROM `information_schema`.`cluster_statements_summary_history` ORDER BY `stmt_type` ASC',\n    'SELECT `version` ()'\n  ]\n\n  describe('Initialize statement list page', () => {\n    it('Statement side bar highlighted', () => {\n      cy.get('[data-e2e=menu_item_statement]')\n        .should('be.visible')\n        .and('has.class', 'ant-menu-item-selected')\n    })\n\n    it('Has Toolbar', function () {\n      cy.get('[data-e2e=statement_toolbar]').should('be.visible')\n    })\n\n    it('Statements is enabled by default', () => {\n      cy.get('[data-e2e=statements_table]').should('be.visible')\n    })\n\n    it('Get statement list bad request', () => {\n      const staticResponse = {\n        statusCode: 400,\n        body: {\n          code: 'common.bad_request',\n          error: true,\n          message: 'common.bad_request'\n        }\n      }\n\n      // stub out a response body\n      cy.intercept(\n        `${Cypress.env('apiBasePath')}statements/list*`,\n        staticResponse\n      ).as('statements_list')\n      cy.wait('@statements_list').then(() => {\n        cy.get('[data-e2e=alert_error_bar]').should(\n          'has.text',\n          staticResponse.body.message\n        )\n      })\n    })\n\n    it('Statements which executed by default when starting TiDB', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n        'statements_list'\n      )\n\n      cy.wait('@statements_list').then((res) => {\n        const response = res.response.body\n\n        cy.get('[data-e2e=syntax_highlighter_compact]')\n          .should('have.length', response.length)\n          .then(($stmts) => {\n            // we get a list of jQuery elements\n            // let's convert the jQuery object into a plain array\n            return (\n              Cypress.$.makeArray($stmts)\n                // and extract inner text from each\n                .map((stmt) => stmt.innerText)\n            )\n          })\n          // make sure there exists the default executed statements\n          .should('to.include.members', defaultExecStmtList)\n      })\n    })\n  })\n\n  describe('Time range selector', () => {\n    beforeEach(() => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n        'statements_list'\n      )\n\n      cy.wait('@statements_list')\n\n      // select last_seen column field\n      cy.get('[data-e2e=columns_selector_popover]').trigger('mouseover')\n\n      cy.contains('Last Seen').within(() => {\n        cy.get('[data-e2e=columns_selector_field_last_seen]').check({\n          force: true\n        })\n      })\n    })\n\n    const checkStmtListWithTimeRange = (stmtList, timeDiff) => {\n      const now = dayjs().unix()\n\n      stmtList.forEach((stmt) => {\n        cy.wrap(stmt.last_seen)\n          .should('be.lte', now)\n          .and('be.gt', now - timeDiff)\n      })\n    }\n\n    describe('Common time range selector', () => {\n      it('Default time range', () => {\n        cy.get('[data-e2e=timerange-selector]').should(\n          'have.text',\n          'Recent 30 min'\n        )\n      })\n\n      it('Init statement list', () => {\n        cy.wait('@statements_list').then((res) => {\n          const response = res.response.body\n\n          cy.get('[data-automation-key=digest_text]').should(\n            'have.length',\n            response.length\n          )\n\n          checkStmtListWithTimeRange(response, 1800)\n        })\n      })\n\n      it('Select time range as recent 15 mins', () => {\n        cy.wait('@statements_list')\n\n        // select recent 15 mins\n        cy.get('[data-e2e=selected_timerange]')\n          .click()\n          .then(() => {\n            cy.get('[data-e2e=timerange-900]').click()\n          })\n\n        cy.wait('@statements_list').then((res) => {\n          const response = res.response.body\n          checkStmtListWithTimeRange(response, 900)\n        })\n\n        // time rage will be remebered after reload page\n        cy.reload()\n        cy.get('[data-e2e=selected_timerange]').should(\n          'have.text',\n          'Recent 15 min'\n        )\n      })\n    })\n  })\n\n  describe('Filter statements by changing database', () => {\n    it('No database selected by default', () => {\n      cy.get('[data-e2e=base_select_input_text]')\n        .eq(0)\n        .should('has.text', 'All Databases')\n    })\n\n    it('Show all databases', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}info/databases`).as(\n        'databases'\n      )\n\n      cy.wait('@databases').then((res) => {\n        const databases = res.response.body\n        testBaseSelectorOptions(databases, 'execution_database_name')\n      })\n    })\n\n    it('Filter statements without use database', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}info/databases`).as(\n        'databases'\n      )\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n        'statements_list'\n      )\n\n      cy.wait('@databases').wait('@statements_list')\n\n      // check all options in databases selector\n      checkAllOptionsInBaseSelector('execution_database_name')\n\n      // check the existence of statements without use database\n      cy.wait('@statements_list')\n      cy.contains(defaultExecStmtList[0]).should('not.exist')\n      cy.contains(defaultExecStmtList[2]).should('not.exist')\n    })\n\n    it('Filter statements with use database (mysql)', () => {\n      let queryData = {\n        query: 'SELECT count(*) from user;',\n        database: 'mysql'\n      }\n      cy.task('queryDB', { ...queryData })\n      cy.reload()\n\n      cy.intercept(`${Cypress.env('apiBasePath')}info/databases`).as(\n        'databases'\n      )\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n        'statements_list'\n      )\n\n      cy.wait('@databases').wait('@statements_list')\n\n      cy.get('[data-e2e=execution_database_name]')\n        .eq(0)\n        .click()\n        .get('.ant-dropdown')\n        .within(() => {\n          cy.get('.ant-checkbox-input').eq(3).click()\n        })\n\n      cy.wait('@statements_list')\n      cy.contains('SELECT count (?) FROM user;').should('exist')\n\n      // Use databases config remembered\n      cy.reload()\n      cy.get('[data-e2e=base_select_input_text]')\n        .eq(0)\n        .should('has.text', '1 Databases')\n    })\n  })\n\n  describe('Filter statements by changing kind', () => {\n    it('No kind selected by default', () => {\n      cy.get('[data-e2e=base_select_input_text]')\n        .eq(1)\n        .should('has.text', 'All Kinds')\n    })\n\n    it('Show all kind of statements', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/stmt_types`).as(\n        'stmt_types'\n      )\n\n      cy.wait('@stmt_types').then((res) => {\n        const stmtTypesList = res.response.body\n        testBaseSelectorOptions(stmtTypesList, 'statement_types')\n      })\n    })\n\n    it('Filter statements with all kind checked', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/stmt_types`).as(\n        'stmt_types'\n      )\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n        'statements_list'\n      )\n\n      cy.wait(['@stmt_types', '@statements_list']).then((interceptions) => {\n        // check all options in kind selector\n        checkAllOptionsInBaseSelector('statement_types')\n        const statementsList = interceptions[1].response.body\n        cy.get('[data-e2e=syntax_highlighter_compact]').should(\n          'have.length',\n          statementsList.length\n        )\n      })\n    })\n\n    it('Filter statements with one kind checked (select)', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/stmt_types`).as(\n        'stmt_types'\n      )\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n        'statements_list'\n      )\n\n      cy.wait('@stmt_types').wait('@statements_list')\n\n      cy.get('[data-e2e=statement_types]')\n        .click()\n        .get('.ant-dropdown')\n        .within(() => {\n          cy.get('[data-e2e=multi_select_options]')\n            .contains('Select')\n            .click({ force: true })\n        })\n\n      cy.wait('@statements_list')\n        .get('[data-e2e=syntax_highlighter_compact]')\n        .each(($sql) => {\n          cy.wrap($sql).contains('SELECT')\n        })\n    })\n  })\n\n  describe('Search function', () => {\n    it('Default search text', () => {\n      cy.get('[data-e2e=sql_statements_search]').should('be.empty')\n    })\n\n    it('Search item with space', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n        'statements_list'\n      )\n\n      cy.wait('@statements_list')\n      cy.get('[data-e2e=syntax_highlighter_compact]').contains('SHOW DATABASES')\n\n      cy.get('[data-e2e=sql_statements_search]').type('SELECT version')\n\n      cy.wait('@statements_list')\n        .get('[data-e2e=syntax_highlighter_compact]')\n        .contains('SELECT `version` ()')\n\n      cy.get('[data-e2e=syntax_highlighter_compact]')\n        .contains('SHOW DATABASES')\n        .should('not.exist')\n\n      // check search text remembered after reload page\n      cy.reload()\n\n      cy.wait('@statements_list')\n      cy.get('[data-e2e=syntax_highlighter_compact]').contains(\n        'SELECT `version` ()'\n      )\n      // this should be filtered away\n      cy.get('[data-e2e=syntax_highlighter_compact]')\n        .contains('SHOW DATABASES')\n        .should('not.exist')\n    })\n\n    it('Type search then reload', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n        'statements_list'\n      )\n\n      cy.wait('@statements_list')\n\n      cy.get('[data-e2e=sql_statements_search]')\n        .type('SELECT `version` ()')\n        .wait('@statements_list')\n\n      cy.reload()\n\n      cy.wait('@statements_list').then((res) => {\n        const statementsList = res.response.body\n        cy.get('[data-e2e=syntax_highlighter_compact]').should(\n          'has.length',\n          statementsList.length\n        )\n      })\n    })\n  })\n\n  describe('Selected Columns', () => {\n    const defaultColumns = {\n      digest_text: 'Statement Template ',\n      sum_latency: 'Total Latency ',\n      avg_latency: 'Mean Latency ',\n      exec_count: '# Exec ',\n      plan_count: '# Plans '\n    }\n\n    it('Default selected columns', () => {\n      cy.get('[role=columnheader]')\n        .not('.is-empty')\n        .should('have.length', 5)\n        .each(($column, idx) => {\n          cy.wrap($column).contains(\n            defaultColumns[Object.keys(defaultColumns)[idx]]\n          )\n        })\n    })\n\n    it('Hover on columns selector and check selected fields', () => {\n      cy.get('[data-e2e=columns_selector_popover]')\n        .trigger('mouseover')\n        .then(() => {\n          cy.get('[data-e2e=columns_selector_popover_content]')\n            .should('be.visible')\n            .within(() => {\n              cy.get('.ant-checkbox-wrapper-checked')\n                // .should('have.length', 5)\n                .then(($options) => {\n                  return Cypress.$.makeArray($options).map(\n                    (option) => option.innerText\n                  )\n                })\n                // make sure there exists the default executed statements\n                .should('to.deep.eq', Object.values(defaultColumns))\n            })\n        })\n    })\n\n    it('Select all column fields', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n        'statements_list'\n      )\n\n      cy.wait('@statements_list')\n\n      cy.get('[data-e2e=columns_selector_popover]')\n        .trigger('mouseover')\n        .get('[data-e2e=column_selector_title]')\n        .check()\n\n      cy.wait('@statements_list')\n        .get('[role=columnheader]')\n        .not('.is-empty')\n        .should('have.length', 43)\n\n      // Columns should be remembered\n      cy.reload()\n\n      cy.wait('@statements_list')\n        .get('[role=columnheader]')\n        .not('.is-empty')\n        .should('have.length', 43)\n    })\n\n    it('Reset selected column fields', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n        'statements_list'\n      )\n      cy.wait('@statements_list')\n\n      cy.get('[data-e2e=columns_selector_popover]')\n        .trigger('mouseover')\n        .get('[data-e2e=column_selector_title]')\n        .check()\n\n      cy.wait('@statements_list')\n        .get('[role=columnheader]')\n        .not('.is-empty')\n        .should('have.length', 43)\n\n      cy.get('[data-e2e=columns_selector_popover]')\n        .trigger('mouseover')\n        .get('[data-e2e=column_selector_reset]')\n        .click()\n\n      cy.wait('@statements_list')\n        .get('[role=columnheader]')\n        .not('.is-empty')\n        .should('have.length', 5)\n    })\n\n    it('Select an arbitary column field', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n        'statements_list'\n      )\n      cy.wait('@statements_list')\n\n      cy.get('[data-e2e=columns_selector_popover]').trigger('mouseover')\n\n      cy.contains('Total Coprocessor Tasks')\n        .within(() => {\n          cy.get('[data-e2e=columns_selector_field_sum_cop_task_num]').check()\n        })\n        .then(() => {\n          cy.wait('@statements_list')\n          cy.get('[data-item-key=sum_cop_task_num]').should(\n            'have.text',\n            'Total Coprocessor Tasks'\n          )\n        })\n\n      // FIXME: the next contains should be performed over the popup only\n      // cy.contains('Total Coprocessor Tasks')\n      //   .within(() => {\n      //     cy.get('[data-e2e=columns_selector_field_sum_cop_task_num]').uncheck()\n      //   })\n      //   .then(() => {\n      //     cy.wait('@statements_list')\n      //     cy.get('[data-item-key=sum_cop_task_num]').should('not.exist')\n      //   })\n    })\n\n    it('Check SHOW_FULL_QUERY_TEXT', () => {\n      cy.get('[data-e2e=columns_selector_popover]')\n        .trigger('mouseover', { force: true })\n        .then(() => {\n          cy.get('[data-e2e=statement_show_full_sql]')\n            .check()\n            .then(() => {\n              cy.get('[data-automation-key=digest_text]')\n                .eq(0)\n                .find('[data-e2e=syntax_highlighter_original]')\n            })\n\n          cy.get('[data-e2e=statement_show_full_sql]')\n            .uncheck()\n            .then(() => {\n              cy.get('[data-automation-key=digest_text]')\n                .eq(0)\n                .trigger('mouseover', { force: true })\n                .find('[data-e2e=syntax_highlighter_compact]')\n            })\n        })\n    })\n  })\n\n  describe('Reload statement', () => {\n    it('Reload statement table shows new query', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n        'statements_list'\n      )\n      cy.wait('@statements_list')\n      cy.get('[data-e2e=syntax_highlighter_compact]')\n        .contains('SELECT count (?) FROM tidb')\n        .should('not.exist')\n\n      // send a query now\n      let queryData = {\n        query: 'select count(*) from tidb;',\n        database: 'mysql'\n      }\n      cy.task('queryDB', { ...queryData })\n\n      // refresh!\n      cy.get('[data-e2e=sql_statements_search]').type('{enter}')\n      cy.wait('@statements_list')\n      cy.get('[data-e2e=syntax_highlighter_compact]').contains(\n        'SELECT count (?) FROM tidb'\n      )\n    })\n  })\n\n  const calcStmtHistorySize = (refreshInterval, historySize) => {\n    const totalMins = refreshInterval * historySize\n    const day = Math.floor(totalMins / (24 * 60))\n    const hour = Math.floor((totalMins - day * 24 * 60) / 60)\n    const min = totalMins - day * 24 * 60 - hour * 60\n    return `${day} day ${hour} hour ${min} min`\n  }\n\n  describe('Statement Setting', function () {\n    it('Close setting panel', () => {\n      // close panel by clicking mask\n      cy.get('[data-e2e=statement_setting]')\n        .click()\n        .then(() => {\n          cy.get('.ant-drawer-mask')\n            .click()\n            .then(() => {\n              cy.get('.ant-drawer-content').should('not.be.visible')\n            })\n        })\n\n      // close panel by clicking close icon\n      cy.get('[data-e2e=statement_setting]')\n        .click()\n        .then(() => {\n          cy.get('.ant-drawer-close')\n            .click()\n            .then(() => {\n              cy.get('.ant-drawer-content').should('not.be.visible')\n            })\n        })\n    })\n\n    const switchStatement = (isCurrentlyEnabled) => {\n      cy.get('[data-e2e=statement_setting]')\n        .click()\n        .then(() => {\n          cy.get('.ant-drawer-content').should('exist')\n          cy.get('[data-e2e=statemen_enbale_switcher]')\n            // the current of switcher is isEnabled\n            .should('have.attr', 'aria-checked', isCurrentlyEnabled)\n            .click()\n          cy.get('[data-e2e=submit_btn]').click()\n        })\n    }\n\n    it('Disable statement feature', () => {\n      switchStatement('true')\n      cy.contains('Current statement history will be cleared.')\n      cy.get('.ant-modal-confirm-btns').find('.ant-btn-dangerous').click()\n      cy.get('[data-e2e=statements_table]').should('not.exist')\n    })\n\n    it('Save again when statement feature is disabled', () => {\n      cy.get('[data-e2e=statement_setting]')\n        .click()\n        .then(() => {\n          cy.get('.ant-drawer-content').should('exist')\n          cy.get('[data-e2e=submit_btn]').click()\n        })\n\n      // eslint-disable-next-line cypress/no-unnecessary-waiting\n      cy.wait(500)\n      cy.contains('Current statement history will be cleared.').should(\n        'not.exist'\n      )\n    })\n\n    it('Enable statement feature', () => {\n      switchStatement('false')\n      cy.get('[data-e2e=statements_table]').should('exist')\n    })\n\n    describe('Default statement setting', () => {\n      beforeEach(() => {\n        cy.intercept(`${Cypress.env('apiBasePath')}statements/config`).as(\n          'statements_config'\n        )\n\n        cy.get('[data-e2e=statement_setting]').click()\n\n        // get refresh_interval value\n        cy.get(`[data-e2e=statement_setting_refresh_interval]`).within(() => {\n          cy.get('.ant-slider-handle')\n            .invoke('attr', 'aria-valuenow')\n            .as('refreshIntervalVal')\n        })\n\n        // get history_size value\n        cy.get(`[data-e2e=statement_setting_history_size]`).within(() => {\n          cy.get('.ant-slider-handle')\n            .invoke('attr', 'aria-valuenow')\n            .as('historySizeVal')\n        })\n      })\n\n      const checkSilder = (sizeList, defaultValueNow, dataE2EValue) => {\n        cy.wait('@statements_config').then(() => {\n          cy.get(`[data-e2e=${dataE2EValue}]`).within(() => {\n            cy.get('.ant-slider-handle').should(\n              'have.attr',\n              'aria-valuenow',\n              defaultValueNow\n            )\n\n            cy.get('.ant-slider-mark-text')\n              .then(($marks) => {\n                return Cypress.$.makeArray($marks).map((mark) => mark.innerText)\n              })\n              // make sure there exists the default executed statements\n              .should('to.deep.eq', sizeList)\n          })\n        })\n      }\n\n      it('Default statement setting max size', () => {\n        const sizeList = ['200', '1000', '2000', '5000']\n        const defaultMaxSizeValue =\n          Cypress.env('TIDB_VERSION') === 'v5.0.0' ? '200' : '3000'\n        checkSilder(sizeList, defaultMaxSizeValue, 'statement_setting_max_size')\n      })\n\n      it('Default statement setting window size', () => {\n        const sizeList = ['1', '5', '15', '30', '60']\n        checkSilder(sizeList, '30', 'statement_setting_refresh_interval')\n      })\n\n      it('Default Statement setting number of windows', () => {\n        const sizeList = ['1', '255']\n        checkSilder(sizeList, '24', 'statement_setting_history_size')\n      })\n\n      it('Default Check History Size', function () {\n        const stmtHistorySize = calcStmtHistorySize(\n          this.refreshIntervalVal,\n          this.historySizeVal\n        )\n        cy.get('[data-e2e=statement_setting_keep_duration]').within(() => {\n          cy.get('.ant-form-item-control-input-content').should(\n            'have.text',\n            stmtHistorySize\n          )\n        })\n      })\n    })\n\n    describe('Update statement setting', function () {\n      beforeEach(function () {\n        cy.get('[data-e2e=statement_setting]').click()\n      })\n\n      it('Update window size and number of windows', function () {\n        // change window size\n        cy.get('[data-e2e=statement_setting_refresh_interval]').within(() => {\n          cy.get('.ant-slider-step')\n            .find('.ant-slider-dot')\n            .eq(2)\n            .click()\n            .then(() => {\n              cy.get('.ant-slider-handle')\n                .invoke('attr', 'aria-valuenow')\n                .as('refreshIntervalVal')\n            })\n        })\n\n        // change number of windows\n        cy.get('[data-e2e=statement_setting_history_size]').within(() => {\n          cy.get('.ant-slider-step')\n            .find('.ant-slider-dot')\n            .eq(1)\n            .click()\n            .then(() => {\n              cy.get('.ant-slider-handle')\n                .invoke('attr', 'aria-valuenow')\n                .as('historySizeVal')\n            })\n        })\n\n        cy.get('@refreshIntervalVal').then((refreshIntervalVal) => {\n          cy.get('@historySizeVal').then((historySizeVal) => {\n            cy.get('[data-e2e=statement_setting_keep_duration]').within(() => {\n              // check statement history size by calculating window size and # windows\n              const stmtHistorySize = calcStmtHistorySize(\n                refreshIntervalVal,\n                historySizeVal\n              )\n              cy.get('.ant-form-item-control-input-content').should(\n                'have.text',\n                stmtHistorySize\n              )\n            })\n\n            cy.intercept(\n              'POST',\n              `${Cypress.env('apiBasePath')}statements/config`\n            ).as('update_config')\n            cy.get('[data-e2e=submit_btn]').click()\n\n            cy.wait('@update_config').then(() => {\n              // check configuration whether come to effect or not\n              cy.visit(this.uri.configuration)\n              cy.url().should('include', this.uri.configuration)\n\n              cy.get('[data-e2e=search_config]').type(\n                'tidb_stmt_summary_refresh_interval'\n              )\n              // eslint-disable-next-line cypress/no-unnecessary-waiting\n              cy.wait(1000)\n              cy.get('[data-automation-key=key]').contains(\n                'tidb_stmt_summary_refresh_interval'\n              )\n              cy.get('[data-automation-key=value]').contains(\n                refreshIntervalVal * 60\n              )\n\n              cy.get('[data-e2e=search_config]')\n                .clear()\n                .type('tidb_stmt_summary_history_size')\n              // eslint-disable-next-line cypress/no-unnecessary-waiting\n              cy.wait(1000)\n              cy.get('[data-automation-key=key]').contains(\n                'tidb_stmt_summary_history_size'\n              )\n              cy.get('[data-automation-key=value]').contains(historySizeVal)\n            })\n          })\n        })\n      })\n\n      it('Failed to save config list', () => {\n        cy.on('uncaught:exception', function () {\n          return false\n        })\n\n        const staticResponse = {\n          statusCode: 400,\n          body: {\n            code: 'common.bad_request',\n            error: true,\n            message: 'common.bad_request'\n          }\n        }\n\n        // stub out a response body\n        cy.intercept(\n          'POST',\n          `${Cypress.env('apiBasePath')}statements/config`,\n          staticResponse\n        ).as('statements_config')\n        cy.get('[data-e2e=submit_btn]').click()\n        cy.wait('@statements_config').then(() => {\n          // get error notifitcation on modal\n          cy.get('.ant-modal-confirm-content').should(\n            'has.text',\n            staticResponse.body.message\n          )\n        })\n      })\n    })\n  })\n\n  describe('Simulate bad request', () => {\n    beforeEach(() => {\n      const staticResponse = {\n        statusCode: 400,\n        body: {\n          code: 'common.bad_request',\n          error: true,\n          message: 'common.bad_request'\n        }\n      }\n\n      // stub out a response body\n      cy.intercept(\n        `${Cypress.env('apiBasePath')}statements/config`,\n        staticResponse\n      ).as('failed_to_get_statements_config')\n\n      cy.get('[data-e2e=statement_setting]').click()\n    })\n\n    it('Get config list bad request', () => {\n      cy.wait('@failed_to_get_statements_config').then(() => {\n        // get error alert on panel\n        cy.get('.ant-drawer-body').within(() => {\n          cy.get('[data-e2e=alert_error_bar]').should(\n            'has.text',\n            'common.bad_request'\n          )\n        })\n      })\n    })\n  })\n\n  describe('Slow network condition', () => {\n    const slowNetworkText = 'On-the-fly update is disabled'\n\n    it('Does not show slow information when network is fast', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n        'statements_list'\n      )\n\n      cy.wait('@statements_list')\n\n      // eslint-disable-next-line cypress/no-unnecessary-waiting\n      cy.wait(500)\n      cy.contains(slowNetworkText).should('not.exist')\n    })\n\n    it('Show slow information', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`, (req) => {\n        req.on('response', (res) => {\n          res.setDelay(3000)\n        })\n      }).as('statements_list')\n\n      cy.wait('@statements_list')\n      cy.contains(slowNetworkText)\n    })\n\n    it('Does not send request automatically when network is slow', () => {\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`, (req) => {\n        req.on('response', (res) => {\n          res.setDelay(3000)\n        })\n      }).as('statements_list')\n\n      cy.wait('@statements_list')\n      cy.contains(slowNetworkText)\n\n      cy.get('[data-e2e=sql_statements_search]').type('SELECT version')\n\n      // eslint-disable-next-line cypress/no-unnecessary-waiting\n      cy.wait(1000)\n      cy.get('[data-e2e=syntax_highlighter_compact]').contains('SHOW DATABASES')\n\n      // request is sent only after a manual refresh\n      cy.get('[data-e2e=sql_statements_search]').type('{enter}')\n      cy.wait('@statements_list')\n      cy.get('[data-e2e=syntax_highlighter_compact]').contains(\n        'SELECT `version` ()'\n      )\n      cy.get('[data-e2e=syntax_highlighter_compact]')\n        .contains('SHOW DATABASES')\n        .should('not.exist')\n    })\n\n    it('Updates the info when network is no longer slow', () => {\n      let shouldDelay = true\n      cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`, (req) => {\n        req.on('response', (res) => {\n          if (shouldDelay) {\n            res.setDelay(3000)\n          }\n        })\n      }).as('statements_list')\n\n      cy.wait('@statements_list')\n      cy.contains(slowNetworkText)\n      cy.get('[data-e2e=syntax_highlighter_compact]')\n        .contains('SHOW DATABASES')\n        .then(() => {\n          shouldDelay = false\n        })\n\n      cy.get('[data-e2e=sql_statements_search]').type('{enter}')\n      cy.wait('@statements_list')\n\n      // eslint-disable-next-line cypress/no-unnecessary-waiting\n      cy.wait(500)\n      cy.contains(slowNetworkText).should('not.exist')\n\n      // On-the-fly request should be recovered\n      cy.get('[data-e2e=sql_statements_search]').type('SELECT version')\n      cy.wait('@statements_list')\n      cy.get('[data-e2e=syntax_highlighter_compact]').contains(\n        'SELECT `version` ()'\n      )\n      cy.get('[data-e2e=syntax_highlighter_compact]')\n        .contains('SHOW DATABASES')\n        .should('not.exist')\n    })\n  })\n\n  describe('Export statement CSV ', () => {\n    it('validate CSV File', () => {\n      const downloadsFolder = Cypress.config('downloadsFolder')\n      let downloadedFilename\n\n      cy.get('[data-e2e=statement_export_menu]')\n        .trigger('mouseover')\n        .then(() => {\n          cy.window()\n            .document()\n            .then(function (doc) {\n              doc.addEventListener('click', () => {\n                setTimeout(function () {\n                  doc.location?.reload()\n                }, 5000)\n              })\n\n              // Make sure the file exists\n              cy.intercept(\n                `${Cypress.env('apiBasePath')}statements/download?token=*`\n              ).as('download_statement')\n\n              cy.get('[data-e2e=statement_export_btn]').click()\n            })\n        })\n        .then(() => {\n          cy.wait('@download_statement').then((res) => {\n            // join downloadFolder with CSV filename\n            const filenameRegx = /\"(.*)\"/\n            downloadedFilename = path.join(\n              downloadsFolder,\n              res.response.headers['content-disposition'].match(filenameRegx)[1]\n            )\n\n            cy.readFile(downloadedFilename, { timeout: 15000 })\n              // parse CSV text into objects\n              .then(neatCSV)\n              .then(validateStatementCSVList)\n          })\n        })\n    })\n  })\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/statement/02-detail.spec.js",
    "content": "describe('Statement detail page E2E test', () => {\n  before(() => {\n    const workloads = [\n      'DROP TABLE IF EXISTS mysql.t;',\n      'CREATE TABLE `t` (`a` bigint(20) DEFAULT NULL, `b` bigint(20) DEFAULT NULL, `c` timestamp(6) DEFAULT CURRENT_TIMESTAMP(6), `d` varchar(50) DEFAULT NULL, UNIQUE KEY `idx0` (`a`), KEY `idx1` (`b`), KEY `idx2` (`b`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;',\n      'select /*+ USE_INDEX(t, idx1) */ count(*)  from t where b < 100;',\n      'select /*+ USE_INDEX(t, idx2) */ count(*)  from t where b < 100;'\n    ]\n\n    workloads.forEach((query) => {\n      cy.task('queryDB', { query })\n    })\n\n    cy.fixture('uri.json').then(function (uri) {\n      this.uri = uri\n    })\n  })\n\n  beforeEach(function () {\n    cy.login('root')\n\n    cy.intercept(\n      `${Cypress.env('apiBasePath')}statements/plans?begin_time=*`\n    ).as('statements_plans')\n    cy.intercept(`${Cypress.env('apiBasePath')}statements/plan/detail?*`).as(\n      'statements_plan_detail'\n    )\n    cy.intercept(`${Cypress.env('apiBasePath')}statements/list*`).as(\n      'statements_list'\n    )\n\n    cy.visit(this.uri.statement)\n    cy.url().should('include', this.uri.statement)\n    cy.wait('@statements_list')\n    cy.get('[data-automation-key=plan_count]')\n      .contains(2)\n      .eq(0)\n      .click({ force: true })\n  })\n\n  describe('Statement Template', () => {\n    it('Check sql and default format', () => {\n      // sql is collapsed by default\n      cy.get('[data-e2e=expandText]').eq(0).should('have.text', 'Expand')\n      cy.get('[data-e2e=statement_query_detail_page_query]')\n        .eq(0)\n        .find('[data-e2e=syntax_highlighter_compact]')\n        .and('have.text', 'SELECT count (?) FROM `t` WHERE `b` < ?;')\n    })\n\n    it('Expand sql', () => {\n      // expand sql\n      cy.get('[data-e2e=expandText]').eq(0).click()\n\n      // sql is collapsed by default\n      cy.get('[data-e2e=collapseText]').eq(0).should('have.text', 'Collapse')\n      cy.get('[data-e2e=statement_query_detail_page_query]')\n        .eq(0)\n        .find('[data-e2e=syntax_highlighter_original]')\n        .and('have.text', 'SELECT\\n  count (?)\\nFROM\\n  `t`\\nWHERE\\n  `b` < ?;')\n    })\n\n    it('Copy formatted sql to clipboard', () => {\n      cy.window().then((win) => {\n        cy.stub(win, 'prompt').returns(win.prompt).as('copyToClipboardPrompt')\n      })\n\n      cy.get('[data-e2e=copy_formatted_sql_to_clipboard]')\n        .realClick()\n        .then(() => {\n          cy.task('getClipboard').should(\n            'eq',\n            'SELECT\\n  count (?)\\nFROM\\n  `t`\\nWHERE\\n  `b` < ?;'\n          )\n        })\n\n      cy.get('[data-e2e=copied_success]').should('exist')\n    })\n\n    it('Copy original sql to clipboard', () => {\n      cy.window().then((win) => {\n        cy.stub(win, 'prompt').returns(win.prompt).as('copyToClipboardPrompt')\n      })\n\n      cy.get('[data-e2e=copy_original_sql_to_clipboard]')\n        .realClick()\n        .then(() => {\n          cy.task('getClipboard').should(\n            'eq',\n            'select count ( ? ) from `t` where `b` < ? ;'\n          )\n        })\n\n      cy.get('[data-e2e=copied_success]').should('exist')\n    })\n  })\n\n  describe('Query Template', () => {\n    it('Check sql and default format', () => {\n      cy.wait('@statements_plan_detail').then((res) => {\n        const response = res.response.body\n        cy.get('.ant-descriptions-row')\n          .eq(3)\n          .within(() => {\n            cy.get('.ant-descriptions-item')\n              .eq(0)\n              .and('have.text', response.digest)\n          })\n      })\n    })\n  })\n\n  describe('Plans', () => {\n    it('Has multiple execution plans', () => {\n      cy.wait('@statements_plans').then((res) => {\n        const response = res.response.body\n        const plansDigest = []\n\n        response.forEach((plan) => plansDigest.push(plan.plan_digest))\n\n        cy.get('[data-e2e=statement_multiple_execution_plans]')\n          .should('be.visible')\n          .within(() => {\n            // check digest of each plan\n            cy.get('[data-automation-key=plan_digest]')\n              .should('have.length', 2)\n              .then(($plans) => {\n                return Cypress.$.makeArray($plans).map((plan) => plan.innerText)\n              })\n              .should('to.deep.equal', plansDigest)\n\n            // all plans are checked\n            cy.get('.ms-DetailsList-headerWrapper').within(() => {\n              cy.get('.ant-checkbox').should(\n                'have.class',\n                'ant-checkbox-checked'\n              )\n            })\n          })\n      })\n    })\n  })\n\n  describe('Detail tabs', () => {\n    it('Check tabs list', () => {\n      const tabList = [\n        'Basic',\n        'Time',\n        'Coprocessor Read',\n        'Transaction',\n        'Slow Query'\n      ]\n      cy.get('[data-e2e=tabs]')\n        .find('.ant-tabs-tab')\n        .should('have.length', 5)\n        .each(($tab, index) => {\n          cy.wrap($tab).should('have.text', tabList[index])\n        })\n    })\n  })\n\n  describe('Detail table tabs', () => {\n    it('Basic table rows count', () => {\n      cy.wait('@statements_plan_detail').then(() => {\n        cy.get('[data-e2e=statement_pages_detail_tabs_basic]').within(() => {\n          cy.get('.ms-List-cell').should('have.length', 13)\n        })\n      })\n    })\n\n    it('Time table rows count', () => {\n      cy.get('.ant-tabs-tab').eq(1).click()\n\n      cy.wait('@statements_plan_detail').then(() => {\n        cy.get('[data-e2e=statement_pages_detail_tabs_time]').within(() => {\n          cy.get('.ms-List-cell').should('have.length', 12)\n        })\n      })\n    })\n\n    it('Coprocessor table rows count', () => {\n      cy.get('.ant-tabs-tab').eq(2).click()\n      cy.wait('@statements_plan_detail').then(() => {\n        cy.get('[data-e2e=statement_pages_detail_tabs_copr]').within(() => {\n          cy.get('.ms-List-cell').should('have.length', 15)\n        })\n      })\n    })\n\n    it('Transaction table rows count', () => {\n      cy.get('.ant-tabs-tab').eq(3).click()\n      cy.wait('@statements_plan_detail').then(() => {\n        cy.get('[data-e2e=statement_pages_detail_tabs_txn]').within(() => {\n          cy.get('.ms-List-cell').should('have.length', 10)\n        })\n      })\n    })\n\n    it('Slow query table rows count', () => {\n      cy.get('.ant-tabs-tab').eq(4).click()\n      cy.wait('@statements_plan_detail').then(() => {\n        cy.get('[data-e2e=detail_tabs_slow_query]').within(() => {\n          cy.get('.ms-List-cell').should('have.length', 0)\n        })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/statement/list.compat_spec.js",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\nimport { skipOn } from '@cypress/skip-test'\n\ndescribe('Read-only user open statement config setting', () => {\n  skipOn(Cypress.env('FEATURE_VERSION') !== '6.0.0', () => {\n    // Create read only user\n    before(() => {\n      const workloads = [\n        'DROP USER IF EXISTS \"readOnlyUser\"@\"%\"',\n        'CREATE USER \"readOnlyUser\"@\"%\" IDENTIFIED BY \"test\";',\n        'GRANT PROCESS, CONFIG ON *.* TO \"readOnlyUser\"@\"%\";',\n        'GRANT SHOW DATABASES ON *.* TO \"readOnlyUser\"@\"%\";',\n        'GRANT DASHBOARD_CLIENT ON *.* TO \"readOnlyUser\"@\"%\";'\n      ]\n\n      workloads.forEach((query) => {\n        cy.task('queryDB', { query })\n      })\n\n      cy.fixture('uri.json').then(function (uri) {\n        this.uri = uri\n      })\n    })\n\n    beforeEach(function () {\n      // login with readOnlyUser\n      cy.visit(this.uri.login)\n      cy.get('[data-e2e=signin_username_input]').clear().type('readOnlyUser')\n      cy.get('[data-e2e=\"signin_password_input\"]').type('test{enter}')\n\n      cy.visit(this.uri.statement)\n      cy.url().should('include', this.uri.statement)\n    })\n\n    it('Unable to modify statement settings', function () {\n      cy.get('[data-e2e=statement_setting]').click({ force: true })\n\n      // switch is disabled\n      cy.get('[data-e2e=statemen_enbale_switcher]').should(\n        'have.class',\n        'ant-switch-disabled'\n      )\n\n      // max size is disabled\n      cy.get('[data-e2e=statement_setting_max_size]').within(() => {\n        cy.get('.ant-slider').should('have.class', 'ant-slider-disabled')\n      })\n\n      // refresh interval is disabled\n      cy.get('[data-e2e=statement_setting_refresh_interval]').within(() => {\n        cy.get('.ant-slider').should('have.class', 'ant-slider-disabled')\n      })\n      // internal query is disabled\n      cy.get('[data-e2e=statement_setting_internal_query]').within(() => {\n        cy.get('.ant-switch').should('have.class', 'ant-switch-disabled')\n      })\n\n      // save button is disableds\n      cy.get('[data-e2e=submit_btn]').should('have.attr', 'disabled')\n    })\n  })\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/topsql/topsql.spec.ts",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\nimport dayjs from 'dayjs'\nimport { skipOn, onlyOn } from '@cypress/skip-test'\n\nfunction setCustomTimeRange(timeRange) {\n  if (!document.querySelector('[data-e2e=\"timerange_selector_dropdown\"]')) {\n    cy.getByTestId('timerange-selector').click()\n  }\n  cy.getByTestId('timerange_selector_dropdown').should('be.visible')\n\n  cy.getByTestId('timerange_selector_dropdown')\n    .find('.ant-picker.ant-picker-range')\n    .type(timeRange)\n  cy.getByTestId('timerange_selector_dropdown').should('be.not.visible')\n}\n\nfunction clearCustomTimeRange() {\n  if (!document.querySelector('[data-e2e=\"timerange_selector_dropdown\"]')) {\n    cy.getByTestId('timerange-selector').click()\n  }\n  cy.getByTestId('timerange_selector_dropdown').should('be.visible')\n\n  cy.getByTestId('timerange_selector_dropdown')\n    .find('.ant-picker-clear')\n    .click()\n  cy.getByTestId('timerange_selector_dropdown').should('be.not.visible')\n}\n\nfunction enableTopSQL() {\n  cy.getByTestId('topsql_settings').click()\n  cy.wait('@getTopsqlConfig')\n\n  cy.getByTestId('topsql_settings_enable').click()\n  cy.getByTestId('topsql_settings_save').click()\n\n  // confirm the tips which about the data will be delayed\n  cy.get('.ant-modal-body').should('be.visible')\n  cy.get('.ant-modal-body .ant-btn-primary').click()\n}\n\nskipOn(Cypress.env('TIDB_VERSION') !== 'latest', () => {\n  describe('Top SQL page', function () {\n    before(() => {\n      cy.intercept(`${Cypress.env('apiBasePath')}/topsql/config`).as(\n        'getTopsqlConfig'\n      )\n\n      cy.fixture('uri.json').then((uri) => {\n        this.uri = uri\n\n        cy.login('root')\n        cy.visit(this.uri.topsql)\n\n        cy.wait('@getTopsqlConfig').then((interception) => {\n          if (!interception.response?.body.enable) {\n            enableTopSQL()\n          }\n        })\n\n        cy.visit(this.uri.overview)\n      })\n    })\n\n    beforeEach(() => {\n      cy.login('root')\n\n      cy.intercept(`${Cypress.env('apiBasePath')}/topsql/config`).as(\n        'getTopsqlConfig'\n      )\n      // mock summary and instance data from 2022-01-12 00:00:00 to 2022-01-12 05:00:00\n      cy.intercept(`${Cypress.env('apiBasePath')}/topsql/summary?*`, {\n        fixture:\n          'topsql_summary:end=1641934800&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641916800&top=5&window=123s.json'\n      }).as('getTopsqlSummary')\n      cy.intercept(\n        {\n          url: `${Cypress.env('apiBasePath')}/topsql/instances?*`\n        },\n        { fixture: 'topsql_instance:end=1641934800&start=1641916800.json' }\n      )\n\n      // clear the user preference before visit top sql page\n      cy.window().then((win) => win.sessionStorage.clear())\n      cy.visit(this.uri.topsql)\n\n      // consume the first screen intercepted request when page loaded\n      cy.wait('@getTopsqlSummary')\n      cy.wait('@getTopsqlConfig')\n    })\n\n    describe('Update time range', () => {\n      it('custom the time range, chart displays the data within the time range', () => {\n        setCustomTimeRange(\n          '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}'\n        )\n\n        cy.wait('@getTopsqlSummary')\n        cy.getByTestId('topsql_list_chart').matchImageSnapshot()\n      })\n\n      it('zoom out the time range, chart displays the data that extends the 50% time range', () => {\n        setCustomTimeRange(\n          '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}'\n        )\n        cy.wait('@getTopsqlSummary')\n\n        cy.get('.anticon-zoom-out').click()\n\n        cy.getByTestId('timerange-selector').should(\n          'contain',\n          '01-11 21:30:00 ~ 01-12 07:30:00'\n        )\n        cy.getByTestId('topsql_list_chart').matchImageSnapshot()\n      })\n    })\n\n    describe('Select instance', () => {\n      it('default time range with instance', () => {\n        setCustomTimeRange(\n          '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}'\n        )\n        cy.wait('@getTopsqlSummary')\n\n        cy.getByTestId('instance-selector').should(\n          'contain',\n          'tidb - 127.0.0.1:10080'\n        )\n      })\n\n      it('change time range, keep the selected instance', () => {\n        setCustomTimeRange(\n          '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}'\n        )\n        cy.wait('@getTopsqlSummary')\n\n        cy.getByTestId('instance-selector').should(\n          'contain',\n          'tidb - 127.0.0.1:10080'\n        )\n\n        // No `tidb - 127.0.0.1:10080` data in the time range\n        clearCustomTimeRange()\n        cy.wait('@getTopsqlSummary')\n\n        setCustomTimeRange(\n          '1970-01-01 08:00:00{enter}1970-01-01 09:00:00{enter}'\n        )\n        cy.wait('@getTopsqlSummary')\n\n        cy.getByTestId('instance-selector').should(\n          'contain',\n          'tidb - 127.0.0.1:10080'\n        )\n      })\n    })\n\n    describe('Refresh', () => {\n      it('click refresh button with the recent x time range, fetch the recent x time range data', () => {\n        cy.getByTestId('timerange-selector').click()\n        cy.getByTestId('timerange_selector_dropdown').should('be.visible')\n\n        const recent = 300\n        const now = dayjs().unix()\n        cy.clock(now * 1000)\n\n        cy.getByTestId(`timerange-${recent}`).click({ force: true })\n        cy.wait('@getTopsqlSummary')\n          .its('request.url')\n          .should('include', `start=${now - recent}`)\n\n        cy.getByTestId('auto-refresh-button').first().click()\n        cy.wait('@getTopsqlSummary')\n          .its('request.url')\n          .should('include', `start=${now - recent}`)\n      })\n\n      it(\"click refresh button after custom the time range, the data won't change\", () => {\n        setCustomTimeRange(\n          '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}'\n        )\n        cy.wait('@getTopsqlSummary')\n\n        cy.getByTestId('auto-refresh-button').first().click()\n        cy.getByTestId('timerange-selector').should(\n          'contain',\n          '01-12 00:00:00 ~ 01-12 05:00:00'\n        )\n        cy.wait('@getTopsqlSummary')\n        cy.getByTestId('topsql_list_chart').matchImageSnapshot()\n      })\n\n      it('set auto refresh, show auto refresh secs aside button', () => {\n        cy.getByTestId('auto-refresh-button').children().eq(1).click()\n        cy.getByTestId('auto_refresh_time_30').click()\n        cy.getByTestId('auto-refresh-button').should('contain', '30 s')\n      })\n\n      it('set auto refresh, it will be refreshed automatically after the time', () => {\n        cy.getByTestId('auto-refresh-button').children().eq(1).click()\n        cy.getByTestId('auto_refresh_time_30').should('be.visible')\n        // eslint-disable-next-line cypress/no-unnecessary-waiting\n        cy.wait(1000) // wait auto_refresh_time_30 item can be clicked before clock freeze the animate\n\n        cy.clock()\n        cy.getByTestId('auto_refresh_time_30').click()\n        for (let i = 0; i < 35; i++) {\n          cy.tick(1000)\n          // eslint-disable-next-line cypress/no-unnecessary-waiting\n          cy.wait(0) // yield to react hooks\n        }\n        cy.clock().invoke('restore')\n        cy.wait('@getTopsqlSummary')\n          .its('response.statusCode')\n          .should('eq', 200)\n      })\n    })\n\n    describe('Chart and table', () => {\n      it('when the time range is large, the chart interval is large', () => {\n        cy.intercept(`${Cypress.env('apiBasePath')}/topsql/summary?*`, {\n          fixture:\n            'topsql_summary_large_timerange:end=1641916800&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641484800&top=5&window=2929s.json'\n        }).as('getTopsqlSummaryLargeTimerange')\n\n        setCustomTimeRange(\n          '2022-01-07 00:00:00{enter}2022-01-12 00:00:00{enter}'\n        )\n        cy.wait('@getTopsqlSummaryLargeTimerange')\n\n        cy.getByTestId('topsql_list_chart').matchImageSnapshot()\n      })\n\n      it('when the time range is small, the chart interval is small', () => {\n        cy.intercept(`${Cypress.env('apiBasePath')}/topsql/summary?*`, {\n          fixture:\n            'topsql_summary_small_timerange:end=1641920460&instance=127.0.0.1%3A10080&instance_type=tidb&start=1641920400&top=5&window=1s.json'\n        }).as('getTopsqlSummarySmallTimerange')\n\n        setCustomTimeRange(\n          '2022-01-12 01:00:00{enter}2022-01-12 01:01:00{enter}'\n        )\n        cy.wait('@getTopsqlSummarySmallTimerange')\n\n        cy.getByTestId('topsql_list_chart').matchImageSnapshot()\n      })\n\n      it('the last item in the table list is others', () => {\n        setCustomTimeRange(\n          '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}'\n        )\n        cy.wait('@getTopsqlSummary')\n\n        cy.getByTestId('topsql_list_table')\n          .find('.ms-List-cell')\n          .children()\n          .eq(5)\n          .find('[data-e2e=\"topsql_listtable_row_others\"]')\n      })\n\n      it('table has top 5 records and the others record', () => {\n        setCustomTimeRange(\n          '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}'\n        )\n        cy.wait('@getTopsqlSummary')\n\n        cy.getByTestId('topsql_list_table')\n          .find('.ms-List-cell')\n          .children()\n          .should('have.length', 6)\n\n        cy.getByTestId('topsql_list_table')\n          .find('.ms-List-cell')\n          .each((item, index) => {\n            cy.wrap(item).trigger('mouseover')\n            cy.getByTestId('topsql_list_chart').matchImageSnapshot(\n              `Top SQL page -- Chart and table -- table has top 5 records and the others record - ${index}`\n            )\n          })\n      })\n\n      it('table can only be single selected', () => {\n        setCustomTimeRange(\n          '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}'\n        )\n        cy.wait('@getTopsqlSummary')\n\n        cy.getByTestId('topsql_list_table')\n          .find('.ms-List-cell')\n          .each((item) => {\n            cy.wrap(item).click()\n            cy.getByTestId('topsql_list_table')\n              .find('.ms-DetailsRow-check[aria-checked=\"true\"]')\n              .should('have.length', 1)\n          })\n      })\n    })\n\n    describe('Top SQL settings', () => {\n      it('close Top SQL by settings panel, the chart and table will still work', () => {\n        cy.getByTestId('topsql_settings').click()\n        cy.wait('@getTopsqlConfig')\n\n        cy.getByTestId('topsql_settings_enable').click()\n        cy.getByTestId('topsql_settings_save').click()\n        cy.get('.ant-btn-primary.ant-btn-dangerous').click()\n        cy.getByTestId('topsql_not_enabled_alert').should('exist')\n\n        setCustomTimeRange(\n          '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}'\n        )\n        cy.wait('@getTopsqlSummary')\n          .its('response.statusCode')\n          .should('eq', 200)\n\n        enableTopSQL()\n        cy.wait('@getTopsqlConfig')\n        cy.getByTestId('topsql_not_enabled_alert').should('not.exist')\n      })\n    })\n\n    describe('SQL statement details', () => {\n      it('click one table row, show the list detail table and information contents', () => {\n        setCustomTimeRange(\n          '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}'\n        )\n        cy.wait('@getTopsqlSummary')\n\n        cy.getByTestId('topsql_list_table').find('.ms-List-cell').eq(0).click()\n        cy.getByTestId('topsql_listdetail_table').should('exist')\n\n        // content\n        cy.getByTestId('sql_text').should('exist')\n        cy.getByTestId('sql_digest').should('exist')\n        cy.getByTestId('plan_text').should('not.exist')\n        cy.getByTestId('plan_digest').should('not.exist')\n\n        // table columns\n        cy.get('[data-item-key=\"cpuTime\"]').should('exist')\n        cy.get('[data-item-key=\"plan\"]').should('exist')\n        cy.get('[data-item-key=\"exec_count_per_sec\"]').should('exist')\n        cy.get('[data-item-key=\"latency\"]').should('exist')\n      })\n\n      it('if the list detail table has more than one plan, only the real plans can be selected', () => {\n        setCustomTimeRange(\n          '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}'\n        )\n        cy.wait('@getTopsqlSummary')\n\n        cy.getByTestId('topsql_list_table').find('.ms-List-cell').eq(0).click()\n\n        cy.getByTestId('topsql_listdetail_table')\n          .find('.ms-List-cell')\n          .eq(0)\n          .click()\n        cy.getByTestId('topsql_listdetail_table')\n          .find('.ms-DetailsRow-check[aria-checked=\"true\"]')\n          .should('have.length', 0)\n\n        cy.getByTestId('topsql_listdetail_table')\n          .find('.ms-List-cell')\n          .eq(1)\n          .click()\n        cy.getByTestId('topsql_listdetail_table')\n          .find('.ms-DetailsRow-check[aria-checked=\"true\"]')\n          .should('have.length', 0)\n\n        cy.getByTestId('topsql_listdetail_table')\n          .find('.ms-List-cell')\n          .eq(2)\n          .click()\n        cy.getByTestId('topsql_listdetail_table')\n          .find('.ms-DetailsRow-check[aria-checked=\"true\"]')\n          .should('have.length', 1)\n        cy.getByTestId('sql_text').should('exist')\n        cy.getByTestId('sql_digest').should('exist')\n        cy.getByTestId('plan_text').should('exist')\n        cy.getByTestId('plan_digest').should('exist')\n      })\n\n      it(\"if there's only one plan in the list detail table, show the plan information directly\", () => {\n        setCustomTimeRange(\n          '2022-01-12 00:00:00{enter}2022-01-12 05:00:00{enter}'\n        )\n        cy.wait('@getTopsqlSummary')\n\n        cy.getByTestId('topsql_list_table').find('.ms-List-cell').eq(1).click()\n\n        cy.getByTestId('sql_text').should('exist')\n        cy.getByTestId('sql_digest').should('exist')\n        cy.getByTestId('plan_text').should('exist')\n        cy.getByTestId('plan_digest').should('exist')\n      })\n    })\n  })\n})\n\nonlyOn(Cypress.env('TIDB_VERSION') === '5.0.0', () => {\n  describe('Ngm not supported', function () {\n    before(() => {\n      cy.fixture('uri.json').then((uri) => (this.uri = uri))\n    })\n\n    beforeEach(() => {\n      cy.login('root')\n    })\n\n    it('can not see top sql menu', () => {\n      cy.getByTestId('menu_item_topsql').should('not.exist')\n    })\n  })\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/topsql/topsql.without_ngm_spec.js",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\nimport { skipOn } from '@cypress/skip-test'\n\ndescribe('TopSQL without ngm', function () {\n  skipOn(Cypress.env('TIDB_VERSION') === '^5.0', () => {\n    before(() => {\n      cy.fixture('uri.json').then((uri) => (this.uri = uri))\n    })\n\n    beforeEach(() => {\n      cy.login('root')\n\n      cy.visit(this.uri.topsql)\n    })\n\n    describe('Ngm not deployed', () => {\n      it('show global notification about ngm not deployed', () => {\n        cy.get('.ant-notification-notice-message').should(\n          'contain',\n          'System Health Check Failed'\n        )\n\n        cy.get('[data-e2e=\"ngm_not_started\"]').should('exist')\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/topsql/topsql_security.spec.js",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\ndescribe('Top SQL security', function () {\n  it(\"can't access the Top SQL page without login, then redirect to login page\", function () {\n    cy.on('uncaught:exception', function () {\n      return false\n    })\n    cy.fixture('uri.json').then(function (uri) {\n      cy.visit(uri.topsql)\n      cy.url().should('include', uri.login)\n    })\n  })\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/integration/utils.js",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\n/**\n * Delete the downloads folder to make sure the test has \"clean\"\n * slate before starting.\n */\nexport const deleteDownloadsFolder = () => {\n  const downloadsFolder = Cypress.config('downloadsFolder')\n\n  cy.task('deleteFolder', downloadsFolder)\n}\n\n/**\n * @param {string[]} list List parsed from CSV file\n */\nexport const validateSlowQueryCSVList = (list) => {\n  expect(list).to.have.length(4)\n\n  // FIXME: this check makes it extremely hard for adding new tests.\n\n  expect(list[0].query).to.equal('SELECT sleep(1.2);')\n  expect(list[1].query).to.equal('SELECT sleep(1.5);')\n  expect(list[2].query).to.equal('SELECT sleep(2);')\n  expect(list[3].query).to.equal('SELECT sleep(1);')\n}\n\nexport const validateStatementCSVList = (allStatementList) => {\n  const defaultExecStmtList = [\n    'show databases',\n    'select distinct `stmt_type` from `information_schema` . `cluster_statements_summary_history` order by `stmt_type` asc',\n    'select `version` ( )'\n  ]\n\n  const allStatementDigestText = []\n  allStatementList.forEach((stmt) => {\n    allStatementDigestText.push(stmt.digest_text)\n  })\n  expect(allStatementDigestText).to.include.members(defaultExecStmtList)\n}\n\nexport const restartTiUP = () => {\n  // Restart tiup\n  cy.exec(\n    `bash ../../../scripts/start_tiup.sh ${Cypress.env(\n      'TIDB_VERSION'\n    )} false restart`,\n    { log: true }\n  )\n\n  // Wait TiUP Playground\n  cy.exec(\n    'bash ../../../scripts/wait_tiup_playground.sh 1 300 &> wait_tiup.log',\n    {\n      timeout: 300000\n    }\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/plugins/index.js",
    "content": "/// <reference types=\"cypress\" />\n// ***********************************************************\n// This example plugins/index.js can be used to load plugins\n//\n// You can change the location of this file or turn off loading\n// the plugins file with the 'pluginsFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/plugins-guide\n// ***********************************************************\n\n// This function is called when a project is opened or re-opened (e.g. due to\n// the project's config changing)\n\nimport mysql from 'mysql2'\nimport { rmdir } from 'fs'\nimport { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin'\nimport codecovTaskPlugin from '@cypress/code-coverage/task'\nimport clipboardy from 'clipboardy'\n\nfunction queryTestDB(query, password, database) {\n  const dbConfig = {\n    host: '127.0.0.1',\n    port: '4000',\n    user: 'root',\n    database: database,\n    password: password\n  }\n  // creates a new mysql connection\n  const connection = mysql.createConnection(dbConfig)\n  // exec query + disconnect to db as a Promise\n  return new Promise((resolve, reject) => {\n    connection.query(query, (error, results) => {\n      setTimeout(() => {\n        if (error) {\n          reject(error)\n        } else {\n          connection.end()\n          return resolve(results)\n        }\n      }, 500) // wait a few more moments for statements and slow query to finish.\n    })\n  })\n}\n\nfunction deleteTestFolder(folderPath) {\n  return new Promise((resolve, reject) => {\n    rmdir(folderPath, { maxRetries: 10, recursive: true }, (err) => {\n      if (err && err.code !== 'ENOENT') {\n        console.error(err)\n\n        return reject(err)\n      }\n\n      resolve(null)\n    })\n  })\n}\n\n/**\n * @type {Cypress.PluginConfig}\n */\n// eslint-disable-next-line no-unused-vars\nmodule.exports = (on, config) => {\n  // `on` is used to hook into various events Cypress emits\n  // `config` is the resolved Cypress config\n\n  codecovTaskPlugin(on, config)\n  addMatchImageSnapshotPlugin(on, config)\n\n  config.baseUrl =\n    (process.env.SERVER_URL || 'http://localhost:3001/dashboard') + '#'\n\n  config.env.apiBasePath = '/dashboard/api/'\n\n  on('task', {\n    // Usage: cy.task('queryDB', { ...queryData })\n    queryDB: ({ query, password = '', database = 'mysql' }) => {\n      return queryTestDB(query, password, database)\n    },\n\n    // Usage: cy.task('deleteFolder', deleteFolderPath)\n    deleteFolder: (folderPath) => {\n      return deleteTestFolder(folderPath)\n    },\n\n    // Usage: cy.task('getClipboard')\n    getClipboard: () => {\n      return clipboardy.readSync()\n    }\n  })\n\n  return config\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/support/commands.js",
    "content": "// ***********************************************\n// This example commands.js shows you how to\n// create various custom commands and overwrite\n// existing commands.\n//\n// For more comprehensive examples of custom\n// commands please read more here:\n// https://on.cypress.io/custom-commands\n// ***********************************************\n//\n//\n// -- This is a parent command --\nCypress.Commands.add('login', (username, password = '') => {\n  // cy.login will be called inside beforeEach,\n  // cy.session stores cookies and localStorage when user first login,\n  // the cookies and localStorage will be reused in the feature beforeEach test.\n  cy.session(\n    [username, password],\n    () => {\n      // root login\n      cy.visit('/')\n      cy.get('[data-e2e=signin_submit]').click()\n\n      // Wait for the post-login redirect to ensure that the\n      // session actually exists to be cached\n      cy.url().should('include', '/overview')\n    },\n    {\n      validate() {\n        cy.request('/whoami').its('status').should('eq', 200)\n      }\n    }\n  )\n})\n\n// -- This will overwrite an existing command --\nCypress.Commands.overwrite('request', (originalFn, ...options) => {\n  const optionsObject = options[0]\n  const token = localStorage.getItem('dashboard_auth_token')\n\n  if (!!token && optionsObject === Object(optionsObject)) {\n    optionsObject.headers = {\n      authorization: 'Bearer ' + token,\n      ...optionsObject.headers\n    }\n\n    return originalFn(optionsObject)\n  }\n\n  return originalFn(...options)\n})\n\n// We overwrite the command, so it does not take a sceenshot if we run the tests inside the test runner\nCypress.Commands.overwrite(\n  'matchImageSnapshot',\n  (originalFn, snapshotName, options) => {\n    if (Cypress.env('ALLOW_SCREENSHOT')) {\n      originalFn(snapshotName, options)\n    } else {\n      cy.log(`Screenshot comparison is disabled`)\n    }\n  }\n)\n\nCypress.Commands.add('getByTestId', (selector, ...args) => {\n  return cy.get(`[data-e2e=\"${selector}\"]`, ...args)\n})\n\nCypress.Commands.add('getByTestIdLike', (selector, ...args) => {\n  return cy.get(`[data-e2e*=\"${selector}\"]`, ...args)\n})\n\n//\n//\n// -- This is a child command --\n// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })\n//\n//\n// -- This is a dual command --\n// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })\n//\n//\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/support/index.js",
    "content": "// ***********************************************************\n// This example support/index.js is processed and\n// loaded automatically before your test files.\n//\n// This is a great place to put global configuration and\n// behavior that modifies Cypress.\n//\n// You can change the location of this file or turn off\n// automatically serving support files with the\n// 'supportFile' configuration option.\n//\n// You can read more here:\n// https://on.cypress.io/configuration\n// ***********************************************************\n\n// Import commands.js using ES2015 syntax:\nimport '@cypress/code-coverage/support'\nimport '@cypress/skip-test/support'\nimport 'cypress-real-events/support'\nimport { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command'\n\naddMatchImageSnapshotCommand()\n\nrequire('./commands')\n\n// https://github.com/cypress-io/cypress/issues/8418\nCypress.on(\n  'uncaught:exception',\n  (err) => !err.message.includes('ResizeObserver loop limit exceeded')\n)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig.json\",\n  \"include\": [\"./**/*.ts\"],\n  \"compilerOptions\": {\n    \"types\": [\"cypress\"],\n    \"lib\": [\"es2015\", \"dom\"],\n    \"isolatedModules\": false,\n    \"allowJs\": true,\n    \"noEmit\": true\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/types/global.d.ts",
    "content": "/// <reference types=\"cypress\" />\n\ndeclare namespace Cypress {\n  interface Chainable {\n    login(username: string, password?: string): void\n\n    getByTestId(dataTestAttribute: string, args?: any): Chainable<Element>\n    getByTestIdLike(\n      dataTestPrefixAttribute: string,\n      args?: any\n    ): Chainable<Element>\n\n    matchImageSnapshot(nameOrOptions?: string | Options): void\n    matchImageSnapshot(name: string, options: Options): void\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress/types/mocha.d.ts",
    "content": "/// <reference types=\"mocha\" />\nimport 'mocha'\n\ndeclare module 'mocha' {\n  interface Suite {\n    uri: any\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/cypress.json",
    "content": "{\n  \"defaultCommandTimeout\": 10000,\n  \"responseTimeout\": 60000,\n  \"requestTimeout\": 60000,\n  \"screenshotOnRunFailure\": true,\n  \"video\": true,\n  \"env\": {\n    \"FEATURE_VERSION\": \"6.0.0\",\n    \"TIDB_VERSION\": \"latest\",\n    \"ALLOW_SCREENSHOT\": false\n  },\n  \"experimentalSessionSupport\": true,\n  \"ignoreTestFiles\": [\"**/__snapshots__/*\", \"**/__image_snapshots__/*\"]\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/gulpfile.js",
    "content": "const { task, series, parallel } = require('gulp')\nconst shell = require('gulp-shell')\n\ntask('distro:gen', shell.task('../../../scripts/distro/write_strings.sh'))\n\ntask(\n  'speedscope:copy',\n  shell.task(\n    'mkdir -p public/speedscope && cp node_modules/@duorou_xu/speedscope/dist/release/* public/speedscope/'\n  )\n)\n\ntask('tsc:watch', shell.task('tsc --watch'))\ntask('tsc:check', shell.task('tsc'))\n\n// https://www.npmjs.com/package/eslint-watch\ntask('lint:watch', shell.task('esw --watch --cache --ext .tsx,.ts .'))\ntask('lint:check', shell.task('esw --cache --ext tsx,ts .'))\n\ntask('esbuild:dev', shell.task('NODE_ENV=development node builder.js'))\ntask('esbuild:build', shell.task('NODE_ENV=production node builder.js'))\n\ntask(\n  'dev',\n  series(\n    parallel('distro:gen', 'speedscope:copy'),\n    parallel('tsc:watch', 'lint:watch', 'esbuild:dev')\n  )\n)\n\ntask(\n  'build',\n  series(\n    parallel('distro:gen', 'speedscope:copy'),\n    parallel('tsc:check', 'lint:check', 'esbuild:build')\n  )\n)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/package.json",
    "content": "{\n  \"name\": \"@pingcap/tidb-dashboard-for-op\",\n  \"private\": \"true\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"dev\": \"gulp dev\",\n    \"build\": \"gulp build\",\n    \"run:e2e-test:compat-features\": \"TZ=Asia/Shanghai cypress run --spec cypress/integration/**/*.compat_spec.[jt]s\",\n    \"run:e2e-test:common-features\": \"TZ=Asia/Shanghai cypress run --spec cypress/integration/**/*.spec.[jt]s\",\n    \"run:e2e-test:without-ngm\": \"TZ=Asia/Shanghai cypress run --spec cypress/integration/**/*.without_ngm_spec.[jt]s\",\n    \"run:e2e-test:specify\": \"TZ=Asia/Shanghai cypress run\",\n    \"open:cypress\": \"TZ=Asia/Shanghai cypress open\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"@ant-design/icons\": \"^4.7.0\",\n    \"@duorou_xu/speedscope\": \"1.14.1\",\n    \"@fortawesome/fontawesome-free\": \"^6.1.1\",\n    \"@g07cha/flexbox-react\": \"^5.0.0\",\n    \"@pingcap/tidb-dashboard-client\": \"workspace:^1.0.0\",\n    \"@pingcap/tidb-dashboard-lib\": \"workspace:^1.0.0\",\n    \"ahooks\": \"^3.1.9\",\n    \"antd\": \"^4.18.7\",\n    \"axios\": \"^1.12.0\",\n    \"bulma\": \"^0.9.4\",\n    \"classnames\": \"^2.3.1\",\n    \"compare-versions\": \"^5.0.1\",\n    \"eventemitter2\": \"^6.4.5\",\n    \"i18next\": \"^23.7.11\",\n    \"jsencrypt\": \"^3.3.2\",\n    \"nprogress\": \"^0.2.0\",\n    \"rc-animate\": \"^3.1.0\",\n    \"react\": \"^17.0.2\",\n    \"react-dom\": \"^17.0.2\",\n    \"react-i18next\": \"^11.15.4\",\n    \"react-markdown\": \"^8.0.3\",\n    \"react-router-dom\": \"6\",\n    \"react-spring\": \"^8.0.27\",\n    \"react-use\": \"^15.3.3\",\n    \"single-spa\": \"^5.9.4\",\n    \"single-spa-react\": \"^4.6.1\"\n  },\n  \"devDependencies\": {\n    \"@baurine/esbuild-plugin-babel\": \"^0.3.0\",\n    \"@baurine/esbuild-plugin-postcss3\": \"^0.4.3\",\n    \"@cypress/code-coverage\": \"^3.9.12\",\n    \"@cypress/skip-test\": \"^2.6.1\",\n    \"@types/node\": \"^16.9.1\",\n    \"@types/react\": \"^17.0.20\",\n    \"@types/react-dom\": \"^17.0.9\",\n    \"autoprefixer\": \"^10.4.2\",\n    \"babel-plugin-istanbul\": \"^6.1.1\",\n    \"chalk\": \"4.1.2\",\n    \"chokidar\": \"^3.5.2\",\n    \"clipboardy\": \"2.3.0\",\n    \"cypress\": \"8.5.0\",\n    \"cypress-image-snapshot\": \"^4.0.1\",\n    \"cypress-real-events\": \"^1.7.0\",\n    \"dayjs\": \"^1.10.8\",\n    \"dotenv\": \"^16.0.1\",\n    \"esbuild\": \"^0.14.23\",\n    \"esbuild-plugin-svgr\": \"^1.0.0\",\n    \"esbuild-plugin-yaml\": \"^0.0.1\",\n    \"eslint-plugin-cypress\": \"^2.12.1\",\n    \"eslint-watch\": \"^8.0.0\",\n    \"fs-extra\": \"^10.0.0\",\n    \"gulp\": \"^4.0.2\",\n    \"gulp-shell\": \"^0.8.0\",\n    \"http-proxy-middleware\": \"^2.0.6\",\n    \"live-server\": \"^1.2.1\",\n    \"mysql2\": \"^3.15.0\",\n    \"neat-csv\": \"5.1.0\",\n    \"typescript\": \"^4.7.3\"\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/process-shim.js",
    "content": "export let process = {\n  // cwd: () => '',\n  env: {} // to avoid `process.env` undefined in runtime\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/public/.gitignore",
    "content": "/speedscope\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/public/diagnoseReport.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"x-distro-strings-res\" content=\"__DISTRO_STRINGS_RES__\" />\n    <meta\n      name=\"x-distro-assets-res-timestamp\"\n      content=\"__DISTRO_ASSETS_RES_TIMESTAMP__\"\n    />\n    <link\n      rel=\"icon\"\n      href=\"%PUBLIC_URL%/distro-res/favicon.ico?t=__DISTRO_ASSETS_RES_TIMESTAMP__\"\n    />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <script src=\"./data.js\"></script>\n    <link\n      rel=\"stylesheet\"\n      href=\"%PUBLIC_URL%/diagnoseReport.css?t=%TIME_PLACE_HOLDER%\"\n    />\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n    <script\n      type=\"module\"\n      src=\"%PUBLIC_URL%/diagnoseReport.js?t=%TIME_PLACE_HOLDER%\"\n    ></script>\n  </body>\n</html>\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/public/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"x-public-path-prefix\" content=\"__PUBLIC_PATH_PREFIX__\" />\n    <meta name=\"x-distro-strings-res\" content=\"__DISTRO_STRINGS_RES__\" />\n    <meta\n      name=\"x-distro-assets-res-timestamp\"\n      content=\"__DISTRO_ASSETS_RES_TIMESTAMP__\"\n    />\n    <link\n      rel=\"icon\"\n      href=\"%PUBLIC_URL%/distro-res/favicon.ico?t=__DISTRO_ASSETS_RES_TIMESTAMP__\"\n    />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta\n      http-equiv=\"Cache-control\"\n      content=\"no-cache, no-store, must-revalidate\"\n    />\n    <style>\n      body {\n        margin: 0;\n        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,\n          Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n          sans-serif;\n        -webkit-font-smoothing: antialiased;\n        -moz-osx-font-smoothing: grayscale;\n        background: #fff;\n      }\n\n      #dashboard_page_spinner {\n        position: absolute;\n        top: 50%;\n        left: 50%;\n      }\n\n      .dot-flashing {\n        position: relative;\n        width: 10px;\n        height: 10px;\n        border-radius: 5px;\n        background-color: #aaa;\n        -webkit-animation: dot-flashing 1s infinite linear alternate;\n        animation: dot-flashing 1s infinite linear alternate;\n        -webkit-animation-delay: 0.5s;\n        animation-delay: 0.5s;\n      }\n\n      .dot-flashing::before,\n      .dot-flashing::after {\n        content: '';\n        display: inline-block;\n        position: absolute;\n        top: 0;\n      }\n\n      .dot-flashing::before {\n        left: -15px;\n        width: 10px;\n        height: 10px;\n        border-radius: 5px;\n        background-color: #aaa;\n        -webkit-animation: dot-flashing 1s infinite alternate;\n        animation: dot-flashing 1s infinite alternate;\n        -webkit-animation-delay: 0s;\n        animation-delay: 0s;\n      }\n\n      .dot-flashing::after {\n        left: 15px;\n        width: 10px;\n        height: 10px;\n        border-radius: 5px;\n        background-color: #aaa;\n        -webkit-animation: dot-flashing 1s infinite alternate;\n        animation: dot-flashing 1s infinite alternate;\n        -webkit-animation-delay: 1s;\n        animation-delay: 1s;\n      }\n\n      @-webkit-keyframes dot-flashing {\n        0% {\n          background-color: #aaa;\n        }\n        50%,\n        100% {\n          background-color: #ddd;\n        }\n      }\n\n      @keyframes dot-flashing {\n        0% {\n          background-color: #aaa;\n        }\n        50%,\n        100% {\n          background-color: #ddd;\n        }\n      }\n    </style>\n    <link\n      rel=\"stylesheet\"\n      href=\"%PUBLIC_URL%/dashboardApp.css?t=%TIME_PLACE_HOLDER%\"\n    />\n  </head>\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"dashboard_page_spinner\"><div class=\"dot-flashing\"></div></div>\n    <div id=\"root\"></div>\n    <script\n      type=\"module\"\n      src=\"%PUBLIC_URL%/dashboardApp.js?t=%TIME_PLACE_HOLDER%\"\n    ></script>\n  </body>\n</html>\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/public/test-portal/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <title>TiDB Dashboard iframe Test</title>\n    <script>\n      function changeApp(app) {\n        console.log('app:', app)\n        const dashboard = document.getElementById('dashboard')\n        dashboard.src = `http://localhost:3001/dashboard/#/${app}`\n      }\n\n      window.onload = function () {\n        const dashboard = document.getElementById('dashboard')\n        const token =\n          'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1OTIwMjUzMjgsIm9yaWdfaWF0IjoxNTkxOTM4OTI4LCJwIjoibkZyZGh1OXlxdnNOSnBXS1daUUlUTkRPakJFMTc4eExQaXc3Y1g4eGxrNFlxMXBRWXVUU1BLVHdNQXIwM2VDajhvTnJuWDc5ZEFDQWZtblBzZXcwaXhnQWdaR1kveU1pMXNqUUc4L1VkMy9McDJKUTN5elMifQ.ynPM1S2jOBvnEcRJgZK-FEaUuhNCI16GCUXKdYs9T18'\n        dashboard.contentWindow.postMessage(\n          {\n            type: 'DASHBOARD_PORTAL_EVENT',\n            token,\n            lang: 'en',\n            hideNav: true,\n            redirectPath: '/statement'\n          },\n          '*'\n        )\n      }\n    </script>\n  </head>\n  <body>\n    <div style=\"margin: 20px\">\n      <h1>iframe test</h1>\n      <div>\n        <a href=\"#\" onclick=\"changeApp('statement'); return false;\"\n          >SQL Statement</a\n        >\n        |\n        <a href=\"#\" onclick=\"changeApp('slow_query'); return false;\"\n          >Slow Query</a\n        >\n        |\n        <a href=\"#\" onclick=\"changeApp('keyviz'); return false;\">Key Viz</a>\n      </div>\n      <div style=\"border: 1px solid #52c41a; height: 80vh\">\n        <iframe\n          id=\"dashboard\"\n          width=\"100%\"\n          height=\"100%\"\n          src=\"http://localhost:3001/dashboard/#/portal\"\n        ></iframe>\n      </div>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/ClusterInfo/context.ts",
    "content": "import {\n  IClusterInfoDataSource,\n  IClusterInfoContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client from '~/client'\n\nclass DataSource implements IClusterInfoDataSource {\n  clusterInfoGetHostsInfo(options?: ReqConfig) {\n    return client.getInstance().clusterInfoGetHostsInfo(options)\n  }\n\n  getStoreLocationTopology(options?: ReqConfig) {\n    return client.getInstance().getStoreLocationTopology(options)\n  }\n\n  getTiDBTopology(options?: ReqConfig) {\n    return client.getInstance().getTiDBTopology(options)\n  }\n\n  getStoreTopology(options?: ReqConfig) {\n    return client.getInstance().getStoreTopology(options)\n  }\n\n  getPDTopology(options?: ReqConfig) {\n    return client.getInstance().getPDTopology(options)\n  }\n\n  getTiCDCTopology(options?: ReqConfig) {\n    return client.getInstance().getTiCDCTopology(options)\n  }\n\n  getTiProxyTopology(options?: ReqConfig) {\n    return client.getInstance().getTiProxyTopology(options)\n  }\n\n  getTSOTopology(options?: ReqConfig) {\n    return client.getInstance().getTSOTopology(options)\n  }\n\n  getSchedulingTopology(options?: ReqConfig) {\n    return client.getInstance().getSchedulingTopology(options)\n  }\n\n  topologyTidbAddressDelete(address: string, options?: ReqConfig) {\n    return client.getInstance().topologyTidbAddressDelete({ address }, options)\n  }\n\n  clusterInfoGetStatistics(options?: ReqConfig) {\n    return client.getInstance().clusterInfoGetStatistics(options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IClusterInfoContext = {\n  ds,\n  cfg: { apiPathBase: client.getBasePath() }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/ClusterInfo/index.tsx",
    "content": "import React from 'react'\nimport {\n  ClusterInfoApp,\n  ClusterInfoProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <ClusterInfoProvider value={ctx}>\n      <ClusterInfoApp />\n    </ClusterInfoProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/ClusterInfo/meta.ts",
    "content": "import { ClusterOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'cluster_info',\n  routerPrefix: '/cluster_info',\n  icon: ClusterOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Configuration/context.ts",
    "content": "import {\n  IConfigurationDataSource,\n  IConfigurationContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { ConfigurationEditRequest } from '~/client'\n\nclass DataSource implements IConfigurationDataSource {\n  configurationEdit(request: ConfigurationEditRequest, options?: ReqConfig) {\n    return client.getInstance().configurationEdit({ request }, options)\n  }\n\n  configurationGetAll(options?: ReqConfig) {\n    return client.getInstance().configurationGetAll(options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IConfigurationContext = {\n  ds\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Configuration/index.tsx",
    "content": "import React from 'react'\nimport {\n  ConfigurationApp,\n  ConfigurationProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <ConfigurationProvider value={ctx}>\n      <ConfigurationApp />\n    </ConfigurationProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Configuration/meta.ts",
    "content": "import { ToolOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'configuration',\n  routerPrefix: '/configuration',\n  icon: ToolOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/ContinuousProfiling/context.ts",
    "content": "import {\n  IConProfilingDataSource,\n  IConProfilingContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { ConprofNgMonitoringConfig } from '~/client'\n\nimport publicPathBase from '~/utils/publicPathPrefix'\n\nclass DataSource implements IConProfilingDataSource {\n  continuousProfilingActionTokenGet(q: string, options?: ReqConfig) {\n    return client\n      .getInstance()\n      .continuousProfilingActionTokenGet({ q }, options)\n  }\n\n  continuousProfilingComponentsGet(options?: ReqConfig) {\n    return client.getInstance().continuousProfilingComponentsGet(options)\n  }\n\n  continuousProfilingConfigGet(options?: ReqConfig) {\n    return client.getInstance().continuousProfilingConfigGet(options)\n  }\n\n  continuousProfilingConfigPost(\n    request: ConprofNgMonitoringConfig,\n    options?: ReqConfig\n  ) {\n    return client\n      .getInstance()\n      .continuousProfilingConfigPost({ request }, options)\n  }\n\n  continuousProfilingDownloadGet(ts: number, options?: ReqConfig) {\n    return client.getInstance().continuousProfilingDownloadGet({ ts }, options)\n  }\n\n  continuousProfilingEstimateSizeGet(options?: ReqConfig) {\n    return client.getInstance().continuousProfilingEstimateSizeGet(options)\n  }\n\n  continuousProfilingGroupProfileDetailGet(ts: number, options?: ReqConfig) {\n    return client\n      .getInstance()\n      .continuousProfilingGroupProfileDetailGet({ ts }, options)\n  }\n\n  continuousProfilingGroupProfilesGet(\n    beginTime?: number,\n    endTime?: number,\n    options?: ReqConfig\n  ) {\n    return client\n      .getInstance()\n      .continuousProfilingGroupProfilesGet({ beginTime, endTime }, options)\n  }\n\n  continuousProfilingSingleProfileViewGet(\n    address?: string,\n    component?: string,\n    profileType?: string,\n    ts?: number,\n    options?: ReqConfig\n  ) {\n    return client\n      .getInstance()\n      .continuousProfilingSingleProfileViewGet(\n        { address, component, profileType, ts },\n        options\n      )\n  }\n\n  getTiDBTopology(options?: ReqConfig) {\n    return client.getInstance().getTiDBTopology(options)\n  }\n  getStoreTopology(options?: ReqConfig) {\n    return client.getInstance().getStoreTopology(options)\n  }\n  getPDTopology(options?: ReqConfig) {\n    return client.getInstance().getPDTopology(options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IConProfilingContext = {\n  ds,\n  cfg: {\n    apiPathBase: client.getBasePath(),\n    publicPathBase\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/ContinuousProfiling/index.tsx",
    "content": "import React from 'react'\nimport {\n  ConProfilingApp,\n  ConProfilingProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <ConProfilingProvider value={ctx}>\n      <ConProfilingApp />\n    </ConProfilingProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/ContinuousProfiling/meta.ts",
    "content": "import { AimOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'conprof',\n  routerPrefix: '/continuous_profiling',\n  icon: AimOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Deadlock/context.ts",
    "content": "import {\n  IDeadlockDataSource,\n  IDeadlockContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client from '~/client'\n\nclass DataSource implements IDeadlockDataSource {\n  deadlockListGet(options?: ReqConfig) {\n    return client.getInstance().deadlockListGet(options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IDeadlockContext = {\n  ds\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Deadlock/index.tsx",
    "content": "import React from 'react'\nimport { DeadlockApp, DeadlockProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <DeadlockProvider value={ctx}>\n      <DeadlockApp />\n    </DeadlockProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Deadlock/meta.ts",
    "content": "import { SyncOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'deadlock',\n  routerPrefix: '/deadlock',\n  icon: SyncOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/DebugAPI/context.ts",
    "content": "import {\n  IDebugAPIDataSource,\n  IDebugAPIContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { EndpointRequestPayload } from '~/client'\n\nclass DataSource implements IDebugAPIDataSource {\n  debugAPIGetEndpoints(options?: ReqConfig) {\n    return client.getInstance().debugAPIGetEndpoints(options)\n  }\n\n  debugAPIRequestEndpoint(req: EndpointRequestPayload, options?: ReqConfig) {\n    return client.getInstance().debugAPIRequestEndpoint(\n      {\n        req: {\n          ...req,\n          // To compatible with the old tidb-dashboard backend api before 5.4.0\n          // By PR https://github.com/pingcap/tidb-dashboard/pull/1103 (release to v2021.12.30.1 and PD 5.4.0)\n          // It changes `id` to `api_id`, `params` to `param_values`\n          id: req.api_id,\n          params: req.param_values\n        } as any\n      },\n      options\n    )\n  }\n\n  infoListDatabases(options?: ReqConfig) {\n    return client.getInstance().infoListDatabases(options)\n  }\n\n  infoListTables(databaseName?: string, options?: ReqConfig) {\n    return client.getInstance().infoListTables({ databaseName }, options)\n  }\n\n  getTiDBTopology(options?: ReqConfig) {\n    return client.getInstance().getTiDBTopology(options)\n  }\n\n  getStoreTopology(options?: ReqConfig) {\n    return client.getInstance().getStoreTopology(options)\n  }\n\n  getPDTopology(options?: ReqConfig) {\n    return client.getInstance().getPDTopology(options)\n  }\n\n  getTiProxyTopology(options?: ReqConfig) {\n    return client.getInstance().getTiProxyTopology(options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IDebugAPIContext = {\n  ds,\n  cfg: { apiPathBase: client.getBasePath() }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/DebugAPI/index.tsx",
    "content": "import React from 'react'\nimport { DebugAPIApp, DebugAPIProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <DebugAPIProvider value={ctx}>\n      <DebugAPIApp />\n    </DebugAPIProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/DebugAPI/meta.ts",
    "content": "import { ApiOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'debug_api',\n  routerPrefix: '/debug_api',\n  icon: ApiOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Diagnose/context.ts",
    "content": "import {\n  IDiagnoseDataSource,\n  IDiagnoseContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { DiagnoseGenDiagnosisReportRequest } from '~/client'\n\nclass DataSource implements IDiagnoseDataSource {\n  diagnoseDiagnosisPost(\n    request: DiagnoseGenDiagnosisReportRequest,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().diagnoseDiagnosisPost({ request }, options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IDiagnoseContext = {\n  ds\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Diagnose/index.tsx",
    "content": "import React from 'react'\nimport { DiagnoseApp, DiagnoseProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <DiagnoseProvider value={ctx}>\n      <DiagnoseApp />\n    </DiagnoseProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Diagnose/meta.ts",
    "content": "import { SafetyCertificateOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'diagnose',\n  routerPrefix: '/diagnose',\n  icon: SafetyCertificateOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/InstanceProfiling/context.ts",
    "content": "import {\n  IInstanceProfilingDataSource,\n  IInstanceProfilingContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { ProfilingStartRequest } from '~/client'\n\nimport publicPathBase from '~/utils/publicPathPrefix'\n\nclass DataSource implements IInstanceProfilingDataSource {\n  getActionToken(id?: string, action?: string, options?: ReqConfig) {\n    return client.getInstance().getActionToken({ id, action }, options)\n  }\n  getProfilingGroupDetail(groupId: string, options?: ReqConfig) {\n    return client.getInstance().getProfilingGroupDetail({ groupId }, options)\n  }\n  getProfilingGroups(options?: ReqConfig) {\n    return client.getInstance().getProfilingGroups(options)\n  }\n  startProfiling(req: ProfilingStartRequest, options?: ReqConfig) {\n    return client.getInstance().startProfiling({ req }, options)\n  }\n  continuousProfilingConfigGet(options?: ReqConfig) {\n    return client.getInstance().continuousProfilingConfigGet(options)\n  }\n\n  getTiDBTopology(options?: ReqConfig) {\n    return client.getInstance().getTiDBTopology(options)\n  }\n  getStoreTopology(options?: ReqConfig) {\n    return client.getInstance().getStoreTopology(options)\n  }\n  getPDTopology(options?: ReqConfig) {\n    return client.getInstance().getPDTopology(options)\n  }\n  getTiCDCTopology(options?: ReqConfig) {\n    return client.getInstance().getTiCDCTopology(options)\n  }\n  getTiProxyTopology(options?: ReqConfig) {\n    return client.getInstance().getTiProxyTopology(options)\n  }\n  getTSOTopology(options?: ReqConfig) {\n    return client.getInstance().getTSOTopology(options)\n  }\n  getSchedulingTopology(options?: ReqConfig) {\n    return client.getInstance().getSchedulingTopology(options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IInstanceProfilingContext = {\n  ds,\n  cfg: { apiPathBase: client.getBasePath(), publicPathBase }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/InstanceProfiling/index.tsx",
    "content": "import React from 'react'\nimport {\n  InstanceProfilingApp,\n  InstanceProfilingProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <InstanceProfilingProvider value={ctx}>\n      <InstanceProfilingApp />\n    </InstanceProfilingProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/InstanceProfiling/meta.ts",
    "content": "import { AimOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'instance_profiling',\n  routerPrefix: '/instance_profiling',\n  icon: AimOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/KeyViz/context.ts",
    "content": "import {\n  IKeyVizDataSource,\n  IKeyVizContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\nimport client, { ConfigKeyVisualConfig } from '~/client'\n\nclass DataSource implements IKeyVizDataSource {\n  keyvisualConfigGet(options?: ReqConfig) {\n    return client.getInstance().keyvisualConfigGet(options)\n  }\n\n  keyvisualConfigPut(request: ConfigKeyVisualConfig, options?: ReqConfig) {\n    return client.getInstance().keyvisualConfigPut({ request }, options)\n  }\n  keyvisualHeatmapsGet(\n    startkey?: string,\n    endkey?: string,\n    starttime?: number,\n    endtime?: number,\n    type?:\n      | 'written_bytes'\n      | 'read_bytes'\n      | 'written_keys'\n      | 'read_keys'\n      | 'integration',\n    options?: ReqConfig\n  ) {\n    return client.getInstance().keyvisualHeatmapsGet(\n      {\n        startkey,\n        endkey,\n        starttime,\n        type\n      },\n      options\n    )\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IKeyVizContext = {\n  ds\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/KeyViz/index.tsx",
    "content": "import React from 'react'\nimport { KeyVizApp, KeyVizProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <KeyVizProvider value={ctx}>\n      <KeyVizApp />\n    </KeyVizProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/KeyViz/meta.ts",
    "content": "import { EyeOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'keyviz',\n  routerPrefix: '/keyviz',\n  icon: EyeOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Monitoring/context.ts",
    "content": "import {\n  IMonitoringDataSource,\n  IMonitoringContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client from '~/client'\n\nimport { getMonitoringItems } from './metricsQueries'\n\nclass DataSource implements IMonitoringDataSource {\n  metricsQueryGet(params: {\n    endTimeSec?: number\n    query?: string\n    startTimeSec?: number\n    stepSec?: number\n  }) {\n    return client\n      .getInstance()\n      .metricsQueryGet(params, {\n        handleError: 'custom'\n      } as ReqConfig)\n      .then((res) => res.data)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IMonitoringContext = {\n  ds,\n  cfg: {\n    getMetricsQueries: (pdVersion: string | undefined) =>\n      getMonitoringItems(pdVersion),\n    promAddrConfigurable: true,\n    metricsReferenceLink:\n      'https://docs.pingcap.com/tidb/stable/dashboard-monitoring'\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Monitoring/index.tsx",
    "content": "import React from 'react'\nimport { MonitoringApp, MonitoringProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <MonitoringProvider value={ctx}>\n      <MonitoringApp />\n    </MonitoringProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Monitoring/meta.ts",
    "content": "import { LineChartOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'monitoring',\n  routerPrefix: '/monitoring',\n  icon: LineChartOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Monitoring/metricsQueries.ts",
    "content": "import {\n  ColorType,\n  TransformNullValue,\n  MetricsQueryType\n} from '@pingcap/tidb-dashboard-lib'\n\nimport { compare } from 'compare-versions'\n\nfunction transformColorBySQLType(legendLabel: string) {\n  switch (legendLabel) {\n    case 'Select':\n      return ColorType.BLUE_3\n    case 'Commit':\n      return ColorType.GREEN_2\n    case 'Insert':\n      return ColorType.GREEN_3\n    case 'Update':\n      return ColorType.GREEN_4\n    case 'general':\n      return ColorType.PINK\n    default:\n      return undefined\n  }\n}\n\nfunction transformColorByExecTimeOverview(legendLabel: string) {\n  switch (legendLabel) {\n    case 'tso_wait':\n      return ColorType.RED_5\n    case 'Commit':\n      return ColorType.GREEN_4\n    case 'Prewrite':\n      return ColorType.GREEN_3\n    case 'PessimisticLock':\n      return ColorType.RED_4\n    case 'Get':\n      return ColorType.BLUE_3\n    case 'BatchGet':\n      return ColorType.BLUE_4\n    case 'Cop':\n      return ColorType.BLUE_1\n    case 'ScanLock':\n    case 'Scan':\n      return ColorType.PURPLE\n    case 'execute time':\n      return ColorType.YELLOW\n    default:\n      return undefined\n  }\n}\n\nconst getMonitoringItems = (\n  pdVersion: string | undefined\n): MetricsQueryType[] => {\n  function loadTiKVStoragePromql() {\n    const PDVersion = pdVersion?.replace('v', '')\n\n    if (PDVersion && PDVersion !== 'N/A' && compare(PDVersion, '5.4.1', '<')) {\n      return 'sum(tikv_engine_size_bytes) by (instance)'\n    }\n    return 'sum(tikv_store_size_bytes{type=\"used\"}) by (instance)'\n  }\n\n  const monitoringItems: MetricsQueryType[] = [\n    {\n      category: 'database_time',\n      metrics: [\n        {\n          title: 'Database Time by SQL Types',\n          queries: [\n            {\n              promql: `sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval]))`,\n              name: 'database time',\n              color: ColorType.YELLOW,\n              type: 'line'\n            },\n            {\n              promql: `sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval])) by (sql_type)`,\n              name: '{sql_type}',\n              color: (seriesName: string) =>\n                transformColorBySQLType(seriesName),\n              type: 'bar_stacked'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Database Time by SQL Phase',\n          queries: [\n            {\n              promql: `sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval]))`,\n              name: 'database time',\n              color: ColorType.YELLOW,\n              type: 'line'\n            },\n            {\n              promql: `sum(rate(tidb_session_parse_duration_seconds_sum{sql_type=\"general\"}[$__rate_interval]))`,\n              name: 'parse',\n              color: ColorType.RED_2,\n              type: 'bar_stacked'\n            },\n            {\n              promql: `sum(rate(tidb_session_compile_duration_seconds_sum{sql_type=\"general\"}[$__rate_interval]))`,\n              name: 'compile',\n              color: ColorType.ORANGE,\n              type: 'bar_stacked'\n            },\n            {\n              promql: `sum(rate(tidb_session_execute_duration_seconds_sum{sql_type=\"general\"}[$__rate_interval]))`,\n              name: 'execute',\n              color: ColorType.GREEN_3,\n              type: 'bar_stacked'\n            },\n            {\n              promql: `sum(rate(tidb_server_get_token_duration_seconds_sum[$__rate_interval]))/1000000`,\n              name: 'get token',\n              color: ColorType.RED_3,\n              type: 'bar_stacked'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'SQL Execute Time Overview',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_tikvclient_request_seconds_sum{store!=\"0\"}[$__rate_interval])) by (type)',\n              name: '{type}',\n              color: (seriesName: string) =>\n                transformColorByExecTimeOverview(seriesName),\n              type: 'bar_stacked'\n            },\n            {\n              promql:\n                'sum(rate(pd_client_cmd_handle_cmds_duration_seconds_sum{type=\"wait\"}[$__rate_interval]))',\n              name: 'tso_wait',\n              color: ColorType.RED_5,\n              type: 'bar_stacked'\n            }\n          ],\n          unit: 's'\n        }\n      ]\n    },\n    {\n      category: 'application_connection',\n      metrics: [\n        {\n          title: 'Connection Count',\n          queries: [\n            {\n              promql: 'sum(tidb_server_connections)',\n              name: 'Total',\n              type: 'line'\n            },\n            {\n              promql: 'sum(tidb_server_tokens)',\n              name: 'active connections',\n              type: 'line'\n            }\n          ],\n          unit: 'short',\n          nullValue: TransformNullValue.AS_ZERO\n        },\n        {\n          title: 'Disconnection',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_server_disconnection_total[$__rate_interval])) by (instance, result)',\n              name: '{instance}-{result}',\n              type: 'area_stack'\n            }\n          ],\n          unit: 'short',\n          nullValue: TransformNullValue.AS_ZERO\n        }\n      ]\n    },\n    {\n      category: 'sql_count',\n      metrics: [\n        {\n          title: 'Query Per Second',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_executor_statement_total[$__rate_interval]))',\n              name: 'Total',\n              type: 'line'\n            },\n            {\n              promql:\n                'sum(rate(tidb_executor_statement_total[$__rate_interval])) by (type)',\n              name: '{type}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'short'\n        },\n        {\n          title: 'Failed Queries',\n          queries: [\n            {\n              promql:\n                'increase(tidb_server_execute_error_total[$__rate_interval])',\n              name: '{type} @ {instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'short'\n        },\n        {\n          title: 'Command Per Second',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_server_query_total[$__rate_interval])) by (type)',\n              name: '{type}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'short'\n        },\n        {\n          title: 'Queries Using Plan Cache OPS',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_server_plan_cache_total[$__rate_interval])) by (type)',\n              name: 'avg - hit',\n              type: 'line'\n            },\n            {\n              promql:\n                'sum(rate(tidb_server_plan_cache_miss_total[$__rate_interval]))',\n              name: 'avg - miss',\n              type: 'line'\n            }\n          ],\n          unit: 'short',\n          nullValue: TransformNullValue.AS_ZERO\n        }\n      ]\n    },\n    {\n      category: 'latency_break_down',\n      metrics: [\n        {\n          title: 'Query Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval])) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\"internal\"}[$__rate_interval]))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket{sql_type!=\"internal\"}[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            },\n            {\n              promql:\n                'sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval])) by (sql_type) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\"internal\"}[$__rate_interval])) by (sql_type)',\n              name: 'avg-{sql_type}',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket{sql_type!=\"internal\"}[$__rate_interval])) by (le,sql_type))',\n              name: '99-{sql_type}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average Idle Connection Duration',\n          queries: [\n            {\n              promql: `(sum(rate(tidb_server_conn_idle_duration_seconds_sum{in_txn='1'}[$__rate_interval])) / sum(rate(tidb_server_conn_idle_duration_seconds_count{in_txn='1'}[$__rate_interval])))`,\n              name: 'avg-in-txn',\n              type: 'line'\n            },\n            {\n              promql: `(sum(rate(tidb_server_conn_idle_duration_seconds_sum{in_txn='0'}[$__rate_interval])) / sum(rate(tidb_server_conn_idle_duration_seconds_count{in_txn='0'}[$__rate_interval])))`,\n              name: 'avg-not-in-txn',\n              type: 'line'\n            }\n          ],\n          unit: 's',\n          nullValue: TransformNullValue.AS_ZERO\n        },\n        {\n          title: 'Get Token Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_server_get_token_duration_seconds_sum[$__rate_interval])) / sum(rate(tidb_server_get_token_duration_seconds_count[$__rate_interval]))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_server_get_token_duration_seconds_bucket[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'µs'\n        },\n        {\n          title: 'Parse Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tidb_session_parse_duration_seconds_sum{sql_type=\"general\"}[$__rate_interval])) / sum(rate(tidb_session_parse_duration_seconds_count{sql_type=\"general\"}[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_session_parse_duration_seconds_bucket{sql_type=\"general\"}[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Compile Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tidb_session_compile_duration_seconds_sum{sql_type=\"general\"}[$__rate_interval])) / sum(rate(tidb_session_compile_duration_seconds_count{sql_type=\"general\"}[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_session_compile_duration_seconds_bucket{sql_type=\"general\"}[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Execute Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tidb_session_execute_duration_seconds_sum{sql_type=\"general\"}[$__rate_interval])) / sum(rate(tidb_session_execute_duration_seconds_count{sql_type=\"general\"}[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_session_execute_duration_seconds_bucket{sql_type=\"general\"}[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        }\n      ]\n    },\n    {\n      category: 'transaction',\n      metrics: [\n        {\n          title: 'Transaction Per Second',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_session_transaction_duration_seconds_count{scope=~\"general\"}[$__rate_interval])) by (type, txn_mode)',\n              name: '{type}-{txn_mode}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'short'\n        },\n        {\n          title: 'Transaction Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_session_transaction_duration_seconds_sum{scope=~\"general\"}[$__rate_interval])) by (txn_mode)/ sum(rate(tidb_session_transaction_duration_seconds_count{scope=~\"general\"}[$__rate_interval])) by (txn_mode)',\n              name: 'avg-{txn_mode}',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tidb_session_transaction_duration_seconds_bucket[$__rate_interval])) by (le, txn_mode))',\n              name: '99-{txn_mode}',\n              type: 'line'\n            }\n          ],\n          unit: 's'\n        }\n      ]\n    },\n    {\n      category: 'core_path_duration',\n      metrics: [\n        {\n          title: 'Avg TiDB KV Request Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tidb_tikvclient_request_seconds_sum{store!=\"0\"}[$__rate_interval])) by (type)/ sum(rate(tidb_tikvclient_request_seconds_count{store!=\"0\"}[$__rate_interval])) by (type)',\n              name: '{type}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Avg TiKV GRPC Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tikv_grpc_msg_duration_seconds_sum{store!=\"0\"}[$__rate_interval])) by (type)/ sum(rate(tikv_grpc_msg_duration_seconds_count{store!=\"0\"}[$__rate_interval])) by (type)',\n              name: '{type}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 PD TSO Wait/RPC Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(pd_client_cmd_handle_cmds_duration_seconds_sum{type=\"wait\"}[$__rate_interval])) / sum(rate(pd_client_cmd_handle_cmds_duration_seconds_count{type=\"wait\"}[$__rate_interval])))',\n              name: 'wait - avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(pd_client_cmd_handle_cmds_duration_seconds_bucket{type=\"wait\"}[$__rate_interval])) by (le))',\n              name: 'wait - 99',\n              type: 'line'\n            },\n            {\n              promql:\n                '(sum(rate(pd_client_request_handle_requests_duration_seconds_sum{type=\"tso\"}[$__rate_interval])) / sum(rate(pd_client_request_handle_requests_duration_seconds_count{type=\"tso\"}[$__rate_interval])))',\n              name: 'rpc - avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(pd_client_request_handle_requests_duration_seconds_bucket{type=\"tso\"}[$__rate_interval])) by (le))',\n              name: 'rpc - 99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 Storage Async Write Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tikv_storage_engine_async_request_duration_seconds_sum{type=\"write\"}[$__rate_interval])) / sum(rate(tikv_storage_engine_async_request_duration_seconds_count{type=\"write\"}[$__rate_interval]))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tikv_storage_engine_async_request_duration_seconds_bucket{type=\"write\"}[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 Store Duration',\n          queries: [\n            {\n              promql:\n                'sum(rate(tikv_raftstore_store_duration_secs_sum[$__rate_interval])) / sum(rate(tikv_raftstore_store_duration_secs_count[$__rate_interval]))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tikv_raftstore_store_duration_secs_bucket[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 Apply Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tikv_raftstore_apply_duration_secs_sum[$__rate_interval])) / sum(rate(tikv_raftstore_apply_duration_secs_count[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tikv_raftstore_apply_duration_secs_bucket[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 Append Log Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tikv_raftstore_append_log_duration_seconds_sum[$__rate_interval])) / sum(rate(tikv_raftstore_append_log_duration_seconds_count[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tikv_raftstore_append_log_duration_seconds_bucket[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 Commit Log Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tikv_raftstore_commit_log_duration_seconds_sum[$__rate_interval])) / sum(rate(tikv_raftstore_commit_log_duration_seconds_count[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tikv_raftstore_commit_log_duration_seconds_bucket[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'Average / P99 Apply Log Duration',\n          queries: [\n            {\n              promql:\n                '(sum(rate(tikv_raftstore_apply_log_duration_seconds_sum[$__rate_interval])) / sum(rate(tikv_raftstore_apply_log_duration_seconds_count[$__rate_interval])))',\n              name: 'avg',\n              type: 'line'\n            },\n            {\n              promql:\n                'histogram_quantile(0.99, sum(rate(tikv_raftstore_apply_log_duration_seconds_bucket[$__rate_interval])) by (le))',\n              name: '99',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        }\n      ]\n    },\n    {\n      category: 'server',\n      metrics: [\n        {\n          title: 'TiDB Uptime',\n          queries: [\n            {\n              promql: '(time() - process_start_time_seconds{job=\"tidb\"})',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'TiDB CPU Usage',\n          queries: [\n            {\n              promql: 'rate(process_cpu_seconds_total{job=\"tidb\"}[30s])',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'percentunit'\n        },\n        {\n          title: 'TiDB Memory Usage',\n          queries: [\n            {\n              promql: 'process_resident_memory_bytes{job=\"tidb\"}',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'bytes'\n        },\n        {\n          title: 'TiKV Uptime',\n          queries: [\n            {\n              promql: '(time() - process_start_time_seconds{job=\"tikv\"})',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          unit: 's'\n        },\n        {\n          title: 'TiKV CPU Usage',\n          queries: [\n            {\n              promql:\n                'sum(rate(tikv_thread_cpu_seconds_total[$__rate_interval])) by (instance)',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          unit: 'percentunit'\n        },\n        {\n          title: 'TiKV Memory Usage',\n          queries: [\n            {\n              promql: 'process_resident_memory_bytes{job=~\".*tikv\"}',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          unit: 'bytes'\n        },\n        {\n          title: 'TiKV IO MBps',\n          queries: [\n            {\n              promql:\n                'sum(rate(tikv_engine_flow_bytes{db=\"kv\", type=\"wal_file_bytes\"}[$__rate_interval])) by (instance) + sum(rate(tikv_engine_flow_bytes{db=\"raft\", type=\"wal_file_bytes\"}[$__rate_interval])) by (instance) + sum(rate(raft_engine_write_size_sum[$__rate_interval])) by (instance)',\n              name: '{instance}-write',\n              type: 'line'\n            },\n            {\n              promql:\n                'sum(rate(tikv_engine_flow_bytes{db=\"kv\", type=~\"bytes_read|iter_bytes_read\"}[$__rate_interval])) by (instance)',\n              name: '{instance}-read',\n              type: 'line'\n            }\n          ],\n          unit: 'Bps'\n        },\n        {\n          title: 'TiKV Storage Usage',\n          queries: [\n            {\n              promql: loadTiKVStoragePromql(),\n              name: '{instance}',\n              type: 'area_stack'\n            }\n          ],\n          unit: 'decbytes'\n        },\n        {\n          title: 'TiFlash Uptime',\n          queries: [\n            {\n              promql: 'tiflash_system_asynchronous_metric_Uptime',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'TiFlash CPU Usage',\n          queries: [\n            {\n              promql:\n                'rate(tiflash_proxy_process_cpu_seconds_total{job=\"tiflash\"}[$__rate_interval])',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'percentunit'\n        },\n        {\n          title: 'TiFlash Memory',\n          queries: [\n            {\n              promql:\n                'tiflash_proxy_process_resident_memory_bytes{job=\"tiflash\"}',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'bytes'\n        },\n        {\n          title: 'TiFlash IO MBps',\n          queries: [\n            {\n              promql:\n                'sum(rate(tiflash_system_profile_event_WriteBufferFromFileDescriptorWriteBytes[$__rate_interval])) by (instance) + sum(rate(tiflash_system_profile_event_PSMWriteBytes[$__rate_interval])) by (instance) + sum(rate(tiflash_system_profile_event_WriteBufferAIOWriteBytes[$__rate_interval])) by (instance)',\n              name: '{instance}-write',\n              type: 'line'\n            },\n            {\n              promql:\n                'sum(rate(tiflash_system_profile_event_ReadBufferFromFileDescriptorReadBytes[$__rate_interval])) by (instance) + sum(rate(tiflash_system_profile_event_PSMReadBytes[$__rate_interval])) by (instance) + sum(rate(tiflash_system_profile_event_ReadBufferAIOReadBytes[$__rate_interval])) by (instance)',\n              name: '{instance}-read',\n              type: 'line'\n            }\n          ],\n          unit: 'Bps'\n        },\n        {\n          title: 'TiFlash Storage Usage',\n          queries: [\n            {\n              promql:\n                'sum(tiflash_system_current_metric_StoreSizeUsed) by (instance)',\n              name: '{instance}',\n              type: 'area_stack'\n            }\n          ],\n          unit: 'bytes'\n        },\n        {\n          title: 'TiProxy Uptime',\n          queries: [\n            {\n              promql: '(time() - process_start_time_seconds{job=\"tiproxy\"})',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 's'\n        },\n        {\n          title: 'TiProxy CPU Usage',\n          queries: [\n            {\n              promql: 'rate(process_cpu_seconds_total{job=\"tiproxy\"}[30s])',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'percentunit'\n        },\n        {\n          title: 'TiProxy Memory Usage',\n          queries: [\n            {\n              promql: 'process_resident_memory_bytes{job=\"tiproxy\"}',\n              name: '{instance}',\n              type: 'line'\n            }\n          ],\n          nullValue: TransformNullValue.AS_ZERO,\n          unit: 'bytes'\n        }\n      ]\n    }\n  ]\n\n  return monitoringItems\n}\n\nexport { getMonitoringItems }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/OptimizerTrace/context.ts",
    "content": "import {\n  IOptimizerTraceDataSource,\n  IOptimizerTraceContext\n  // ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\n// import client from '~/client'\n\nclass DataSource implements IOptimizerTraceDataSource {}\n\nexport const ctx: IOptimizerTraceContext = {\n  ds: new DataSource()\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/OptimizerTrace/index.tsx",
    "content": "import React from 'react'\nimport {\n  OptimizerTraceApp,\n  OptimizerTraceProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <OptimizerTraceProvider value={ctx}>\n      <OptimizerTraceApp />\n    </OptimizerTraceProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/OptimizerTrace/meta.ts",
    "content": "export default {\n  id: 'optimizer_trace',\n  routerPrefix: '/optimizer_trace',\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Overview/context.ts",
    "content": "import {\n  IOverviewDataSource,\n  IOverviewContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client from '~/client'\nimport { overviewMetrics } from './metricsQueries'\n\nclass DataSource implements IOverviewDataSource {\n  getTiDBTopology(options?: ReqConfig) {\n    return client.getInstance().getTiDBTopology(options)\n  }\n\n  getStoreTopology(options?: ReqConfig) {\n    return client.getInstance().getStoreTopology(options)\n  }\n\n  getPDTopology(options?: ReqConfig) {\n    return client.getInstance().getPDTopology(options)\n  }\n\n  getTiCDCTopology(options?: ReqConfig) {\n    return client.getInstance().getTiCDCTopology(options)\n  }\n\n  getTiProxyTopology(options?: ReqConfig) {\n    return client.getInstance().getTiProxyTopology(options)\n  }\n\n  getTSOTopology(options?: ReqConfig) {\n    return client.getInstance().getTSOTopology(options)\n  }\n\n  getSchedulingTopology(options?: ReqConfig) {\n    return client.getInstance().getSchedulingTopology(options)\n  }\n\n  metricsQueryGet(params: {\n    endTimeSec?: number\n    query?: string\n    startTimeSec?: number\n    stepSec?: number\n  }) {\n    return client\n      .getInstance()\n      .metricsQueryGet(params, {\n        handleError: 'custom'\n      } as ReqConfig)\n      .then((res) => res.data)\n  }\n\n  getGrafanaTopology(options?: ReqConfig) {\n    return client.getInstance().getGrafanaTopology(options)\n  }\n\n  getAlertManagerTopology(options?: ReqConfig) {\n    return client.getInstance().getAlertManagerTopology(options)\n  }\n\n  getAlertManagerCounts(address: string, options?: ReqConfig) {\n    return client.getInstance().getAlertManagerCounts({ address }, options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IOverviewContext = {\n  ds,\n  cfg: {\n    apiPathBase: client.getBasePath(),\n    metricsQueries: overviewMetrics,\n    promAddrConfigurable: true,\n    showMetrics: true,\n    metricsReferenceLink:\n      'https://docs.pingcap.com/tidb/stable/dashboard-monitoring'\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Overview/index.tsx",
    "content": "import React from 'react'\nimport { OverviewApp, OverviewProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <OverviewProvider value={ctx}>\n      <OverviewApp />\n    </OverviewProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Overview/meta.ts",
    "content": "import { AppstoreOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'overview',\n  routerPrefix: '/overview',\n  icon: AppstoreOutlined,\n  isDefaultRouter: true,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Overview/metricsQueries.ts",
    "content": "import {\n  TransformNullValue,\n  OverviewMetricsQueryType\n} from '@pingcap/tidb-dashboard-lib'\n\nconst overviewMetrics: OverviewMetricsQueryType[] = [\n  {\n    title: 'total_requests',\n    queries: [\n      {\n        promql: 'sum(rate(tidb_executor_statement_total[$__rate_interval]))',\n        name: 'Total',\n        type: 'line'\n      },\n      {\n        promql:\n          'sum(rate(tidb_executor_statement_total[$__rate_interval])) by (type)',\n        name: '{type}',\n        type: 'line'\n      }\n    ],\n    nullValue: TransformNullValue.AS_ZERO,\n    unit: 'short'\n  },\n  {\n    title: 'latency',\n    queries: [\n      {\n        promql:\n          'sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval])) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\"internal\"}[$__rate_interval]))',\n        name: 'avg',\n        type: 'line'\n      },\n      {\n        promql:\n          'histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket{sql_type!=\"internal\"}[$__rate_interval])) by (le))',\n        name: '99',\n        type: 'line'\n      },\n      {\n        promql:\n          'sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\"internal\"}[$__rate_interval])) by (sql_type) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\"internal\"}[$__rate_interval])) by (sql_type)',\n        name: 'avg-{sql_type}',\n        type: 'line'\n      },\n      {\n        promql:\n          'histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket{sql_type!=\"internal\"}[$__rate_interval])) by (le,sql_type))',\n        name: '99-{sql_type}',\n        type: 'line'\n      }\n    ],\n    nullValue: TransformNullValue.AS_ZERO,\n    unit: 's'\n  },\n  {\n    title: 'cpu',\n    queries: [\n      {\n        promql: 'rate(process_cpu_seconds_total{job=\"tidb\"}[$__rate_interval])',\n        name: '{instance}',\n        type: 'line'\n      }\n    ],\n    nullValue: TransformNullValue.AS_ZERO,\n    unit: 'percentunit'\n  },\n  {\n    title: 'memory',\n    queries: [\n      {\n        promql: 'process_resident_memory_bytes{job=\"tidb\"}',\n        name: '{instance}',\n        type: 'line'\n      }\n    ],\n    nullValue: TransformNullValue.AS_ZERO,\n    unit: 'bytes'\n  },\n  {\n    title: 'io',\n    queries: [\n      {\n        promql:\n          'sum(rate(tikv_engine_flow_bytes{db=\"kv\", type=\"wal_file_bytes\"}[$__rate_interval])) by (instance) + sum(rate(tikv_engine_flow_bytes{db=\"raft\", type=\"wal_file_bytes\"}[$__rate_interval])) by (instance) + sum(rate(raft_engine_write_size_sum[$__rate_interval])) by (instance)',\n        name: '{instance}-write',\n        type: 'line'\n      },\n      {\n        promql:\n          'sum(rate(tikv_engine_flow_bytes{db=\"kv\", type=~\"bytes_read|iter_bytes_read\"}[$__rate_interval])) by (instance)',\n        name: '{instance}-read',\n        type: 'line'\n      }\n    ],\n    unit: 'Bps'\n  }\n]\n\nexport { overviewMetrics }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/QueryEditor/context.ts",
    "content": "import {\n  IQueryEditorDataSource,\n  IQueryEditorContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { QueryeditorRunRequest } from '~/client'\n\nclass DataSource implements IQueryEditorDataSource {\n  queryEditorRun(request: QueryeditorRunRequest, options?: ReqConfig) {\n    return client.getInstance().queryEditorRun({ request }, options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IQueryEditorContext = {\n  ds\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/QueryEditor/index.tsx",
    "content": "import React from 'react'\nimport {\n  QueryEditorApp,\n  QueryEditorProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <QueryEditorProvider value={ctx}>\n      <QueryEditorApp />\n    </QueryEditorProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/QueryEditor/meta.ts",
    "content": "import { ConsoleSqlOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'query_editor',\n  routerPrefix: '/query_editor',\n  icon: ConsoleSqlOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/ResourceManager/context-impl.ts",
    "content": "import {\n  IResourceManagerDataSource,\n  IResourceManagerContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\nimport { AxiosPromise } from 'axios'\n\nimport client, {\n  ResourcemanagerCalibrateResponse,\n  ResourcemanagerGetConfigResponse,\n  ResourcemanagerResourceInfoRowDef\n} from '~/client'\n\nclass DataSource implements IResourceManagerDataSource {\n  getConfig(\n    options?: ReqConfig\n  ): AxiosPromise<ResourcemanagerGetConfigResponse> {\n    return client.getInstance().resourceManagerConfigGet(options)\n  }\n  getInformation(\n    options?: ReqConfig\n  ): AxiosPromise<ResourcemanagerResourceInfoRowDef[]> {\n    return client.getInstance().resourceManagerInformationGet(options)\n  }\n\n  getCalibrateByHardware(\n    params: { workload: string },\n    options?: ReqConfig | undefined\n  ): AxiosPromise<ResourcemanagerCalibrateResponse> {\n    return client\n      .getInstance()\n      .resourceManagerCalibrateHardwareGet(params, options)\n  }\n  getCalibrateByActual(\n    params: { startTime: number; endTime: number },\n    options?: ReqConfig | undefined\n  ): AxiosPromise<ResourcemanagerCalibrateResponse> {\n    return client\n      .getInstance()\n      .resourceManagerCalibrateActualGet(params, options)\n  }\n\n  metricsQueryGet(params: {\n    endTimeSec?: number\n    query?: string\n    startTimeSec?: number\n    stepSec?: number\n  }) {\n    return client\n      .getInstance()\n      .metricsQueryGet(params, {\n        handleError: 'custom'\n      } as ReqConfig)\n      .then((res) => res.data)\n  }\n}\n\nexport const getResourceManagerContext: () => IResourceManagerContext = () => {\n  return {\n    ds: new DataSource(),\n    cfg: {}\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/ResourceManager/index.tsx",
    "content": "import React from 'react'\nimport {\n  ResourceManagerApp,\n  ResourceManagerProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { getResourceManagerContext } from './context-impl'\n\nexport default function () {\n  return (\n    <ResourceManagerProvider value={getResourceManagerContext()}>\n      <ResourceManagerApp />\n    </ResourceManagerProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/ResourceManager/meta.ts",
    "content": "import { HddOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'resource_manager',\n  routerPrefix: '/resource_manager',\n  icon: HddOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/SearchLogs/context.ts",
    "content": "import {\n  ISearchLogsDataSource,\n  ISearchLogsContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { LogsearchCreateTaskGroupRequest } from '~/client'\n\nclass DataSource implements ISearchLogsDataSource {\n  logsDownloadAcquireTokenGet(id?: Array<string>, options?: ReqConfig) {\n    return client.getInstance().logsDownloadAcquireTokenGet({ id }, options)\n  }\n\n  // logsDownloadGet(token: string, options?: ReqConfig) {\n  //   return client.getInstance().logsDownloadGet({ token }, options)\n  // }\n\n  logsTaskgroupPut(\n    request: LogsearchCreateTaskGroupRequest,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().logsTaskgroupPut({ request }, options)\n  }\n\n  logsTaskgroupsGet(options?: ReqConfig) {\n    return client.getInstance().logsTaskgroupsGet(options)\n  }\n\n  logsTaskgroupsIdCancelPost(id: string, options?: ReqConfig) {\n    return client.getInstance().logsTaskgroupsIdCancelPost({ id }, options)\n  }\n\n  logsTaskgroupsIdDelete(id: string, options?: ReqConfig) {\n    return client.getInstance().logsTaskgroupsIdDelete({ id }, options)\n  }\n\n  logsTaskgroupsIdGet(id: string, options?: ReqConfig) {\n    return client.getInstance().logsTaskgroupsIdGet({ id }, options)\n  }\n\n  logsTaskgroupsIdPreviewGet(id: string, options?: ReqConfig) {\n    return client.getInstance().logsTaskgroupsIdPreviewGet({ id }, options)\n  }\n\n  logsTaskgroupsIdRetryPost(id: string, options?: ReqConfig) {\n    return client.getInstance().logsTaskgroupsIdRetryPost({ id }, options)\n  }\n\n  getTiDBTopology(options?: ReqConfig) {\n    return client.getInstance().getTiDBTopology(options)\n  }\n  getStoreTopology(options?: ReqConfig) {\n    return client.getInstance().getStoreTopology(options)\n  }\n  getPDTopology(options?: ReqConfig) {\n    return client.getInstance().getPDTopology(options)\n  }\n  getTiCDCTopology(options?: ReqConfig) {\n    return client.getInstance().getTiCDCTopology(options)\n  }\n  getTiProxyTopology(options?: ReqConfig) {\n    return client.getInstance().getTiProxyTopology(options)\n  }\n  getTSOTopology(options?: ReqConfig) {\n    return client.getInstance().getTSOTopology(options)\n  }\n  getSchedulingTopology(options?: ReqConfig) {\n    return client.getInstance().getSchedulingTopology(options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: ISearchLogsContext = {\n  ds,\n  cfg: { apiPathBase: client.getBasePath() }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/SearchLogs/index.tsx",
    "content": "import React from 'react'\nimport { SearchLogsApp, SearchLogsProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <SearchLogsProvider value={ctx}>\n      <SearchLogsApp />\n    </SearchLogsProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/SearchLogs/meta.ts",
    "content": "import { FileSearchOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'search_logs',\n  routerPrefix: '/search_logs',\n  icon: FileSearchOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/SlowQuery/context.ts",
    "content": "import {\n  ISlowQueryDataSource,\n  ISlowQueryContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client from '~/client'\n\nclass DataSource implements ISlowQueryDataSource {\n  getDatabaseList(beginTime: number, endTime: number, options?: ReqConfig) {\n    return client.getInstance().infoListDatabases(options)\n  }\n\n  infoListResourceGroupNames(options?: ReqConfig) {\n    return client.getInstance().resourceManagerInformationGroupNamesGet(options)\n  }\n\n  slowQueryAvailableFieldsGet(options?: ReqConfig) {\n    return client.getInstance().slowQueryAvailableFieldsGet(options)\n  }\n\n  slowQueryListGet(\n    beginTime?: number,\n    db?: Array<string>,\n    desc?: boolean,\n    digest?: string,\n    endTime?: number,\n    fields?: string,\n    limit?: number,\n    orderBy?: string,\n    plans?: Array<string>,\n    resourceGroup?: Array<string>,\n    text?: string,\n    showInternal?: boolean,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().slowQueryListGet(\n      {\n        beginTime,\n        db,\n        desc,\n        digest,\n        endTime,\n        fields,\n        limit,\n        orderBy,\n        plans,\n        resourceGroup,\n        text\n      },\n      options\n    )\n  }\n\n  slowQueryDetailGet(\n    connectId?: string,\n    digest?: string,\n    timestamp?: number,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().slowQueryDetailGet(\n      {\n        connectId,\n        digest,\n        timestamp\n      },\n      options\n    )\n  }\n\n  slowQueryDownloadTokenPost(request: any, options?: ReqConfig) {\n    return client.getInstance().slowQueryDownloadTokenPost({ request }, options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: ISlowQueryContext = {\n  ds,\n  cfg: {\n    apiPathBase: client.getBasePath(),\n    enableExport: true,\n    showDBFilter: true,\n    showDigestFilter: false,\n    showResourceGroupFilter: true\n    // instantQuery: false,\n    // timeRangeSelector: {\n    //   recentSeconds: [3 * 24 * 60 * 60],\n    //   customAbsoluteRangePicker: true\n    // }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/SlowQuery/index.tsx",
    "content": "import React from 'react'\nimport { SlowQueryApp, SlowQueryProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <SlowQueryProvider value={ctx}>\n      <SlowQueryApp />\n    </SlowQueryProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/SlowQuery/meta.ts",
    "content": "import { RocketOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'slow_query',\n  routerPrefix: '/slow_query',\n  icon: RocketOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Statement/context.ts",
    "content": "import {\n  IStatementDataSource,\n  IStatementContext,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, {\n  StatementEditableConfig,\n  StatementGetStatementsRequest\n} from '~/client'\nimport auth from '~/utils/auth'\n\nclass DataSource implements IStatementDataSource {\n  getDatabaseList(\n    beginTime: number,\n    endTime: number,\n    options?: ReqConfig | undefined\n  ) {\n    return client.getInstance().infoListDatabases(options)\n  }\n\n  infoListResourceGroupNames(options?: ReqConfig) {\n    return client.getInstance().resourceManagerInformationGroupNamesGet(options)\n  }\n\n  statementsAvailableFieldsGet(options?: ReqConfig) {\n    return client.getInstance().statementsAvailableFieldsGet(options)\n  }\n\n  statementsConfigGet(options?: ReqConfig) {\n    return client.getInstance().statementsConfigGet(options)\n  }\n\n  statementsConfigPost(request: StatementEditableConfig, options?: ReqConfig) {\n    return client.getInstance().statementsConfigPost({ request }, options)\n  }\n\n  statementsDownloadGet(token: string, options?: ReqConfig) {\n    return client.getInstance().statementsDownloadGet({ token }, options)\n  }\n\n  statementsDownloadTokenPost(\n    request: StatementGetStatementsRequest,\n    options?: ReqConfig\n  ) {\n    return client\n      .getInstance()\n      .statementsDownloadTokenPost({ request }, options)\n  }\n\n  statementsListGet(\n    beginTime?: number,\n    endTime?: number,\n    fields?: string,\n    schemas?: Array<string>,\n    resourceGroups?: Array<string>,\n    stmtTypes?: Array<string>,\n    text?: string,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().statementsListGet(\n      {\n        beginTime,\n        endTime,\n        fields,\n        schemas,\n        resourceGroups,\n        stmtTypes,\n        text\n      },\n      options\n    )\n  }\n\n  statementsPlanDetailGet(\n    beginTime?: number,\n    digest?: string,\n    endTime?: number,\n    plans?: Array<string>,\n    schemaName?: string,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().statementsPlanDetailGet(\n      {\n        beginTime,\n        digest,\n        endTime,\n        plans,\n        schemaName\n      },\n      options\n    )\n  }\n\n  statementsPlansGet(\n    beginTime?: number,\n    digest?: string,\n    endTime?: number,\n    schemaName?: string,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().statementsPlansGet(\n      {\n        beginTime,\n        digest,\n        endTime,\n        schemaName\n      },\n      options\n    )\n  }\n\n  statementsStmtTypesGet(options?: ReqConfig) {\n    return client.getInstance().statementsStmtTypesGet(options)\n  }\n\n  statementsTimeRangesGet(options?: ReqConfig) {\n    return client.getAxiosInstance().get('/statements/time_ranges', {\n      ...options,\n      headers: {\n        ...options?.headers,\n        Authorization: auth.getAuthTokenAsBearer() || ''\n      }\n    })\n  }\n\n  statementsPlanBindStatusGet(\n    sqlDigest: string,\n    beginTime: number,\n    endTime: number,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().statementsPlanBindingGet(\n      {\n        sqlDigest,\n        beginTime,\n        endTime\n      },\n      options\n    )\n  }\n\n  statementsPlanBindCreate(planDigest: string, options?: ReqConfig) {\n    return client.getInstance().statementsPlanBindingPost(\n      {\n        planDigest\n      },\n      options\n    )\n  }\n\n  statementsPlanBindDelete(sqlDigest: string, options?: ReqConfig) {\n    return client.getInstance().statementsPlanBindingDelete(\n      {\n        sqlDigest\n      },\n      options\n    )\n  }\n\n  // slow query\n  slowQueryAvailableFieldsGet(options?: ReqConfig) {\n    return client.getInstance().slowQueryAvailableFieldsGet(options)\n  }\n\n  slowQueryListGet(\n    beginTime?: number,\n    db?: Array<string>,\n    desc?: boolean,\n    digest?: string,\n    endTime?: number,\n    fields?: string,\n    limit?: number,\n    orderBy?: string,\n    plans?: Array<string>,\n    resourceGroup?: Array<string>,\n    text?: string,\n    showInternal?: boolean,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().slowQueryListGet(\n      {\n        beginTime,\n        db,\n        desc,\n        digest,\n        endTime,\n        fields,\n        limit,\n        orderBy,\n        plans,\n        resourceGroup,\n        text\n      },\n      options\n    )\n  }\n\n  slowQueryDetailGet(\n    connectId?: string,\n    digest?: string,\n    timestamp?: number,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().slowQueryDetailGet(\n      {\n        connectId,\n        digest,\n        timestamp\n      },\n      options\n    )\n  }\n\n  slowQueryDownloadTokenPost(request: any, options?: ReqConfig) {\n    return client.getInstance().slowQueryDownloadTokenPost({ request }, options)\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: IStatementContext = {\n  ds,\n  cfg: {\n    apiPathBase: client.getBasePath(),\n    enableExport: true,\n    enablePlanBinding: true\n    // instantQuery: false\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Statement/index.tsx",
    "content": "import React from 'react'\nimport { StatementApp, StatementProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <StatementProvider value={ctx}>\n      <StatementApp />\n    </StatementProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/Statement/meta.ts",
    "content": "import { ThunderboltOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'statement',\n  routerPrefix: '/statement',\n  icon: ThunderboltOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/SystemReport/context.ts",
    "content": "import {\n  ISystemReportDataSource,\n  ISystemReportContext,\n  ReqConfig,\n  ISystemReportConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, {\n  DiagnoseGenerateReportRequest,\n  DiagnoseGenerateMetricsRelationRequest\n} from '~/client'\n\nimport publicPathBase from '~/utils/publicPathPrefix'\n\nclass DataSource implements ISystemReportDataSource {\n  diagnoseReportsGet(options?: ReqConfig) {\n    return client.getInstance().diagnoseReportsGet(options)\n  }\n\n  diagnoseReportsPost(\n    request: DiagnoseGenerateReportRequest,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().diagnoseReportsPost({ request }, options)\n  }\n\n  diagnoseGenerateMetricsRelationship(\n    request: DiagnoseGenerateMetricsRelationRequest,\n    options?: ReqConfig\n  ) {\n    return client\n      .getInstance()\n      .diagnoseGenerateMetricsRelationship({ request }, options)\n  }\n  diagnoseReportsIdStatusGet(id: string, options?: ReqConfig) {\n    return client.getInstance().diagnoseReportsIdStatusGet({ id }, options)\n  }\n}\n\nclass SystemReportConfig implements ISystemReportConfig {\n  public apiPathBase = client.getBasePath()\n\n  public publicPathBase = publicPathBase\n\n  public fullReportLink(reportId: string): string {\n    /* Not using client basePath intentionally so that it can be handled by dev server */\n    return `${publicPathBase}/api/diagnose/reports/${reportId}/detail`\n  }\n}\n\nexport const ctx: ISystemReportContext = {\n  ds: new DataSource(),\n  cfg: new SystemReportConfig()\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/SystemReport/index.tsx",
    "content": "import React from 'react'\nimport {\n  SystemReportApp,\n  SystemReportProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <SystemReportProvider value={ctx}>\n      <SystemReportApp />\n    </SystemReportProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/SystemReport/meta.ts",
    "content": "import { SnippetsOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'system_report',\n  routerPrefix: '/system_report',\n  icon: SnippetsOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/TopSQL/context.ts",
    "content": "import {\n  ITopSQLDataSource,\n  ITopSQLContext,\n  ITopSQLConfig,\n  ReqConfig\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { TopsqlEditableConfig } from '~/client'\nimport auth from '~/utils/auth'\n\ntype TikvNetworkIoCollectionConfig = {\n  enable: boolean\n  is_multi_value?: boolean\n}\n\ntype TikvNetworkIoCollectionUpdateResponse = {\n  warnings: any[]\n}\n\nclass DataSource implements ITopSQLDataSource {\n  topsqlConfigGet(options?: ReqConfig) {\n    return client.getInstance().topsqlConfigGet(options)\n  }\n\n  topsqlConfigPost(request: TopsqlEditableConfig, options?: ReqConfig) {\n    return client.getInstance().topsqlConfigPost({ request }, options)\n  }\n\n  topsqlTikvNetworkIoCollectionGet(options?: ReqConfig) {\n    return client\n      .getAxiosInstance()\n      .get<TikvNetworkIoCollectionConfig>(\n        '/topsql/tikv_network_io_collection',\n        {\n          ...options,\n          headers: {\n            ...options?.headers,\n            Authorization: auth.getAuthTokenAsBearer() || ''\n          }\n        } as any\n      )\n  }\n\n  topsqlTikvNetworkIoCollectionPost(\n    request: TikvNetworkIoCollectionConfig,\n    options?: ReqConfig\n  ) {\n    return client\n      .getAxiosInstance()\n      .post<TikvNetworkIoCollectionUpdateResponse>(\n        '/topsql/tikv_network_io_collection',\n        request,\n        {\n          ...options,\n          headers: {\n            ...options?.headers,\n            Authorization: auth.getAuthTokenAsBearer() || ''\n          }\n        } as any\n      )\n  }\n\n  topsqlInstancesGet(\n    end?: string,\n    start?: string,\n    dataSource?: string,\n    options?: ReqConfig\n  ) {\n    const requestParameters: any = { start, end }\n    if (dataSource !== undefined) {\n      requestParameters.dataSource = dataSource\n    }\n    return client.getInstance().topsqlInstancesGet(requestParameters, options)\n  }\n\n  topsqlSummaryGet(\n    end?: string,\n    groupBy?: string,\n    instance?: string,\n    instanceType?: string,\n    orderBy?: string,\n    start?: string,\n    top?: string,\n    window?: string,\n    dataSource?: string,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().topsqlSummaryGet(\n      {\n        end,\n        groupBy,\n        instance,\n        instanceType,\n        orderBy,\n        start,\n        top,\n        window,\n        dataSource\n      },\n      options\n    )\n  }\n}\n\nconst ds = new DataSource()\n\nexport const ctx: ITopSQLContext = {\n  ds,\n  cfg: {\n    checkNgm: true,\n    showSetting: true,\n    showLimit: true,\n    showGroupBy: true,\n    showGroupByRegion: true,\n    showOrderBy: true\n  } as ITopSQLConfig\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/TopSQL/index.tsx",
    "content": "import React from 'react'\nimport { TopSQLApp, TopSQLProvider } from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <TopSQLProvider value={ctx}>\n      <TopSQLApp />\n    </TopSQLProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/TopSQL/meta.ts",
    "content": "import { BarChartOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'topsql',\n  routerPrefix: '/topsql',\n  icon: BarChartOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/UserProfile/context.ts",
    "content": "import {\n  IUserProfileDataSource,\n  IUserProfileContext,\n  ReqConfig,\n  IUserProfileEvent\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, {\n  SsoCreateImpersonationRequest,\n  SsoSetConfigRequest,\n  CodeShareRequest,\n  MetricsPutCustomPromAddressRequest\n} from '~/client'\nimport auth from '~/utils/auth'\n\nclass DataSource implements IUserProfileDataSource {\n  userGetSignOutInfo(redirectUrl?: string, options?: ReqConfig) {\n    return client.getInstance().userGetSignOutInfo({ redirectUrl }, options)\n  }\n\n  userSSOCreateImpersonation(\n    request: SsoCreateImpersonationRequest,\n    options?: ReqConfig\n  ) {\n    return client.getInstance().userSSOCreateImpersonation({ request }, options)\n  }\n\n  userSSOGetConfig(options?: ReqConfig) {\n    return client.getInstance().userSSOGetConfig(options)\n  }\n\n  userSSOListImpersonations(options?: ReqConfig) {\n    return client.getInstance().userSSOListImpersonations(options)\n  }\n\n  userSSOSetConfig(request: SsoSetConfigRequest, options?: ReqConfig) {\n    return client.getInstance().userSSOSetConfig({ request }, options)\n  }\n\n  userShareSession(request: CodeShareRequest, options?: ReqConfig) {\n    return client.getInstance().userShareSession({ request }, options)\n  }\n\n  userRevokeSession(options?: ReqConfig) {\n    return client.getInstance().userRevokeSession(options)\n  }\n\n  metricsGetPromAddress(options?: ReqConfig) {\n    return client.getInstance().metricsGetPromAddress(options)\n  }\n\n  metricsSetCustomPromAddress(\n    request: MetricsPutCustomPromAddressRequest,\n    options?: ReqConfig\n  ) {\n    return client\n      .getInstance()\n      .metricsSetCustomPromAddress({ request }, options)\n  }\n}\n\nclass EventHandler implements IUserProfileEvent {\n  logOut(): void {\n    auth.clearAuthToken()\n  }\n}\n\nexport const ctx: IUserProfileContext = {\n  ds: new DataSource(),\n  event: new EventHandler()\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/UserProfile/index.tsx",
    "content": "import React from 'react'\nimport {\n  UserProfileApp,\n  UserProfileProvider\n} from '@pingcap/tidb-dashboard-lib'\nimport { ctx } from './context'\n\nexport default function () {\n  return (\n    <UserProfileProvider value={ctx}>\n      <UserProfileApp />\n    </UserProfileProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/apps/UserProfile/meta.ts",
    "content": "import { UserOutlined } from '@ant-design/icons'\n\nexport default {\n  id: 'user_profile',\n  routerPrefix: '/user_profile',\n  icon: UserOutlined,\n  reactRoot: () => import('.')\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/client/apiBasePath.ts",
    "content": "import publicPathPrefix from '~/utils/publicPathPrefix'\n\nexport const API_HOST = (function () {\n  let apiPrefix\n  if (process.env.NODE_ENV === 'development') {\n    if (process.env.REACT_APP_DASHBOARD_API_URL) {\n      apiPrefix = `${process.env.REACT_APP_DASHBOARD_API_URL}/dashboard`\n    } else {\n      apiPrefix = `http://${window.location.hostname}:12333/dashboard`\n    }\n  } else {\n    apiPrefix = publicPathPrefix\n  }\n\n  return apiPrefix\n})()\n\nexport function getApiBasePath(): string {\n  return `${API_HOST}/api`\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/client/index.tsx",
    "content": "import React from 'react'\nimport i18next from 'i18next'\nimport axios, { AxiosInstance } from 'axios'\nimport { message, Modal, notification } from 'antd'\nimport * as singleSpa from 'single-spa'\n\nimport { routing, i18n } from '@pingcap/tidb-dashboard-lib'\nimport {\n  Configuration,\n  DefaultApi as DashboardApi\n} from '@pingcap/tidb-dashboard-client'\n\nimport auth from '~/utils/auth'\n\nimport { getApiBasePath } from './apiBasePath'\nimport translations from './translations'\n\nexport * from '@pingcap/tidb-dashboard-client'\n\n//////////////////////////////\n\nconst client = {\n  _init(\n    apiBasePath: string,\n    apiInstance: DashboardApi,\n    axiosInstance: AxiosInstance\n  ) {\n    this.apiBasePath = apiBasePath\n    this.apiInstance = apiInstance\n    this.axiosInstance = axiosInstance\n  },\n\n  getInstance(): DashboardApi {\n    return this.apiInstance\n  },\n\n  getBasePath(): string {\n    return this.apiBasePath\n  },\n\n  getAxiosInstance(): AxiosInstance {\n    return this.axiosInstance\n  }\n}\n\nexport default client\n\n//////////////////////////////\n\ntype HandleError = 'default' | 'custom'\n\nfunction applyErrorHandlerInterceptor(instance: AxiosInstance) {\n  instance.interceptors.response.use(undefined, async function (err) {\n    const { response, config } = err\n    const handleError = config.handleError as HandleError\n    const method = (config.method as string).toLowerCase()\n\n    let errCode: string\n    let content: string\n    if (err.message === 'Network Error') {\n      errCode = 'common.network'\n    } else {\n      errCode = response?.data?.code\n    }\n    if (i18next.exists(`error.${errCode ?? ''}`)) {\n      // If there is a translation for the code, use the translation.\n      // TODO: Better to display error details somewhere.\n      content = i18next.t(`error.${errCode}`)\n    } else {\n      content = String(\n        response?.data?.message || err.message || 'Internal error'\n      )\n    }\n    err.message = content\n    err.errCode = errCode\n\n    if (\n      errCode === 'common.unauthenticated' ||\n      errCode === 'error.api.unauthorized' // compatible with old tidb-dashboard backend\n    ) {\n      // Handle unauthorized error in a unified way\n      if (!routing.isLocationMatch('/') && !routing.isSignInPage()) {\n        message.error({ content, key: errCode })\n      }\n      auth.clearAuthToken()\n      singleSpa.navigateToUrl('#' + routing.signInRoute)\n      err.handled = true\n    } else if (handleError === 'default') {\n      if (method === 'get') {\n        const fullUrl = config.url as string\n        const API = fullUrl.replace(client.getBasePath(), '').split('?')[0]\n        notification.error({\n          key: API,\n          message: i18next.t('error.title'),\n          description: (\n            <span>\n              API: {API}\n              <br />\n              {content}\n            </span>\n          )\n        })\n      } else if (['post', 'put', 'delete', 'patch'].includes(method)) {\n        Modal.error({\n          title: i18next.t('error.title'),\n          content: content,\n          zIndex: 2000 // higher than popover\n        })\n      }\n      err.handled = true\n    }\n\n    return Promise.reject(err)\n  })\n}\n\nfunction initAxios(apiBasePath: string) {\n  const instance = axios.create({\n    baseURL: apiBasePath\n  })\n  applyErrorHandlerInterceptor(instance)\n\n  return instance\n}\n\nfunction init() {\n  i18n.addTranslations(translations)\n\n  const apiBasePath = getApiBasePath()\n  const axiosInstance = initAxios(apiBasePath)\n  const dashboardApi = new DashboardApi(\n    new Configuration({\n      apiKey: () => auth.getAuthTokenAsBearer() || '',\n      baseOptions: {\n        handleError: 'default'\n      }\n    }),\n    '',\n    axiosInstance\n  )\n\n  client._init(apiBasePath, dashboardApi, axiosInstance)\n}\n\ninit()\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/client/translations/en.yaml",
    "content": "error:\n  title: Error\n  common:\n    network: Network connection error\n    unauthenticated: Please sign in again (session is expired)\n    forbidden: The current user is not authorized to perform this action\n  api:\n    user:\n      signin:\n        invalid_code: Authorization Code is invalid or expired\n        insufficient_priv: The user does not have sufficient privileges to access {{distro.tidb}} Dashboard.\n    slow_query:\n      export_no_data: No slow queires can be exported\n    statement:\n      export_no_data: No statements can be exported\n    continuous_profiling:\n      ng_monitoring_not_ready: |\n        To use or learn more about \"Continuous Profiling\" feature, please search for \"Continuous Profiling\" in the {{distro.tidb}} official docs for more information.\n        If it doesn't resove the issue, please contact the product's technical support.\n    feature_not_supported: The cluster of this version doesn't support or can't use this feature, please contact with technical support to get more information.\n  tidb:\n    no_alive_tidb: No alive {{distro.tidb}} instance\n    pd_access_failed: Failed to access {{distro.pd}} node\n    tidb_conn_failed: Failed to connect to {{distro.tidb}}\n    tidb_auth_failed: '{{distro.tidb}} authentication failed'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/client/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/client/translations/zh.yaml",
    "content": "error:\n  title: 错误\n  common:\n    network: 网络连接失败\n    unauthenticated: 会话已过期，请重新登录\n    forbidden: 当前用户没有权限进行该操作\n  api:\n    user:\n      signin:\n        invalid_code: 授权码无效或已过期\n        insufficient_priv: 该用户缺少足够的权限访问 {{distro.tidb}} Dashboard。\n    slow_query:\n      export_no_data: 没有可导出的慢查询日志\n    statement:\n      export_no_data: 没有可导出的语句\n    continuous_profiling:\n      ng_monitoring_not_ready: |\n        想使用或深入了解“持续性能分析”功能，请在 {{distro.tidb}} 官方文档搜索“持续性能分析”查看更多内容。\n        若未能解决问题，请联系本产品技术支持。\n    feature_not_supported: 当前版本的集群不支持或无法使用该功能，请联系技术支持了解详细情况。\n  tidb:\n    no_alive_tidb: 没有正在运行的 {{distro.tidb}} 实例\n    pd_access_failed: 无法访问 {{distro.pd}} 节点\n    tidb_conn_failed: 无法连接到 {{distro.tidb}}\n    tidb_auth_failed: '{{distro.tidb}} 登录验证失败'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/index.ts",
    "content": "import '../styles/style.less'\nimport '@pingcap/tidb-dashboard-lib/dist/index.css'\n\nimport './main'\n\nimport '../styles/override.less'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/layout/main/Sider/Banner.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.banner {\n  background: @primary-color;\n  color: #fff;\n  cursor: pointer;\n  overflow: hidden;\n  user-select: none;\n  position: relative;\n  margin-bottom: 20px;\n  flex-shrink: 0;\n}\n\n.bannerLeft {\n  padding: 20px 16px 20px 24px;\n}\n\n.bannerRight {\n  position: absolute;\n  top: 0;\n  height: 100%;\n  transition: background-color 0.2s @ease-out;\n  display: flex;\n}\n\n.banner:hover .bannerRight {\n  background: lighten(@primary-color, 5%);\n}\n\n.bannerLogo {\n  margin: 5px 0;\n}\n\n.bannerContent {\n  margin-left: 15px;\n}\n\n.bannerTitle {\n  font-size: 1rem;\n}\n\n.bannerVersion {\n  font-size: 0.9rem;\n  opacity: 0.7;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/layout/main/Sider/Banner.tsx",
    "content": "import React, { useMemo, useRef } from 'react'\nimport { CaretRightOutlined, CaretLeftOutlined } from '@ant-design/icons'\nimport { useSize } from 'ahooks'\nimport Flexbox from '@g07cha/flexbox-react'\nimport { useSpring, animated } from 'react-spring'\nimport { useTranslation, TFunction } from 'react-i18next'\n\nimport { InfoInfoResponse } from '~/client'\n\nimport {\n  // store\n  store\n} from '@pingcap/tidb-dashboard-lib'\n\nimport { lightLogoSvg } from '~/utils/distro/assetsRes'\n\nimport styles from './Banner.module.less'\n\nconst toggleWidth = 40\nconst toggleHeight = 50\n\nfunction parseVersion(i: InfoInfoResponse, t: TFunction) {\n  if (!i.version) {\n    return null\n  }\n  if (i.version.standalone !== 'No') {\n    // For Standalone == Yes / Unknown, display internal version\n    if (i.version.internal_version === 'nightly') {\n      let vPrefix = i.version.internal_version\n      if (i.version.build_git_hash) {\n        vPrefix += `-${i.version.build_git_hash.substr(0, 8)}`\n      }\n      // e.g. nightly-xxxxxxxx\n      return vPrefix\n    }\n    if (i.version.internal_version) {\n      // e.g. v2020.07.01.1\n      if (i.version.internal_version.startsWith('v')) {\n        return i.version.internal_version\n      } else {\n        return `v${i.version.internal_version}`\n      }\n    }\n    return null\n  }\n\n  if (i.version.pd_version) {\n    // e.g. PD v4.0.1\n    return `${t('distro.pd')} ${i.version.pd_version}`\n  }\n}\n\nexport default function ToggleBanner({\n  fullWidth,\n  collapsedWidth,\n  collapsed,\n  onToggle\n}) {\n  const { t } = useTranslation()\n  const bannerRef = useRef(null)\n  const bannerSize = useSize(bannerRef)\n  const transBanner = useSpring({\n    opacity: collapsed ? 0 : 1,\n    height: collapsed ? toggleHeight : bannerSize?.height ?? 0\n  })\n  const transButton = useSpring({\n    left: collapsed ? 0 : fullWidth - toggleWidth,\n    width: collapsed ? collapsedWidth : toggleWidth\n  })\n\n  const appInfo = store.useState((s) => s.appInfo)\n\n  const version = useMemo(() => {\n    if (appInfo) {\n      return parseVersion(appInfo, t)\n    }\n    return null\n  }, [appInfo, t])\n\n  return (\n    <div className={styles.banner} onClick={onToggle}>\n      <animated.div\n        style={transBanner}\n        className={styles.bannerLeftAnimationWrapper}\n      >\n        <div\n          ref={bannerRef}\n          className={styles.bannerLeft}\n          style={{ width: fullWidth - toggleWidth }}\n        >\n          <Flexbox flexDirection=\"row\">\n            <div className={styles.bannerLogo}>\n              <img src={lightLogoSvg} style={{ height: 30 }} />\n            </div>\n            <div className={styles.bannerContent}>\n              <div className={styles.bannerTitle}>\n                {t('distro.tidb')} Dashboard\n              </div>\n              <div className={styles.bannerVersion}>\n                {version || 'Version unknown'}\n              </div>\n            </div>\n          </Flexbox>\n        </div>\n      </animated.div>\n      <animated.div style={transButton} className={styles.bannerRight}>\n        {collapsed ? (\n          <CaretRightOutlined style={{ margin: 'auto' }} />\n        ) : (\n          <CaretLeftOutlined style={{ margin: 'auto' }} />\n        )}\n      </animated.div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/layout/main/Sider/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n@sider-background: rgb(246, 246, 246);\n@sider-highlight-height: 36px;\n@sider-ribbon-height: 30px;\n\n.sider {\n  position: fixed;\n  left: 0;\n  top: 0;\n  height: 100%;\n  z-index: 1;\n  background: @sider-background;\n  overflow-x: hidden;\n  overflow-y: auto;\n  transition: none;\n  user-select: none;\n\n  :global {\n    /* cancel text animations */\n    .ant-menu-item .ant-menu-item-icon,\n    .ant-menu-submenu-title .ant-menu-item-icon,\n    .ant-menu-item .anticon,\n    .ant-menu-submenu-title .anticon {\n      transition-duration: 0.1s;\n    }\n\n    .ant-menu-item .ant-menu-item-icon + span,\n    .ant-menu-submenu-title .ant-menu-item-icon + span,\n    .ant-menu-item .anticon + span,\n    .ant-menu-submenu-title .anticon + span {\n      transition-duration: 0.1s;\n    }\n\n    .ant-menu-item,\n    .ant-menu-submenu-title {\n      transition-duration: 0.1s;\n    }\n\n    .ant-menu-title-content {\n      transition-duration: 0.1s;\n    }\n\n    .ant-layout-sider-children {\n      display: flex;\n      flex-direction: column;\n      padding-bottom: 20px;\n\n      .ant-menu.ant-menu-inline-collapsed {\n        width: @menu-collapsed-width; // 80px\n      }\n    }\n\n    .ant-menu {\n      border-right: 0;\n      background: none;\n    }\n\n    .ant-menu-submenu-selected {\n      color: #000;\n    }\n\n    .ant-menu-item {\n      background: none !important;\n\n      &::after {\n        left: 0;\n        top: 0;\n        height: @sider-ribbon-height;\n        margin-top: -(@sider-ribbon-height / 2);\n        top: 50%;\n        border: 0px;\n        width: 5px;\n        border-radius: 0 5px 5px 0;\n        background: @primary-color;\n      }\n\n      &::before {\n        content: '';\n        position: absolute;\n        left: 0;\n        right: 20px;\n        height: @sider-highlight-height;\n        top: 50%;\n        margin-top: -(@sider-highlight-height / 2);\n        border-radius: 0 (@sider-highlight-height / 2)\n          (@sider-highlight-height / 2) 0;\n        z-index: -1;\n      }\n\n      &:hover::before {\n        background: rgba(lighten(@primary-color, 20%), 0.2);\n      }\n\n      a {\n        color: #666;\n        transition-duration: 0s;\n\n        &:hover {\n          color: #000;\n        }\n      }\n\n      &.ant-menu-item-selected {\n        &::before {\n          background: rgba(darken(@sider-background, 30%), 0.15);\n        }\n\n        a {\n          color: #000;\n        }\n      }\n    }\n\n    .ant-menu-submenu-title {\n      background: none !important;\n    }\n\n    .ant-menu-inline-collapsed .ant-menu-item {\n      &::before {\n        right: 10px;\n        left: 10px;\n        border-radius: (@sider-highlight-height / 2);\n      }\n    }\n  }\n}\n\n:global {\n  .ant-menu-inline-collapsed-tooltip {\n    a {\n      color: @text-color-dark; // hsla(0, 0%, 100%, 0.85)\n    }\n    .anticon {\n      display: none;\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/layout/main/Sider/index.tsx",
    "content": "import React, { useState, useMemo } from 'react'\nimport {\n  ExperimentOutlined,\n  BugOutlined,\n  AimOutlined\n  // PullRequestOutlined\n} from '@ant-design/icons'\nimport { Layout, Menu } from 'antd'\nimport { Link } from 'react-router-dom'\nimport { useEventListener } from 'ahooks'\nimport { useTranslation } from 'react-i18next'\nimport { useSpring, animated } from 'react-spring'\nimport Banner from './Banner'\nimport styles from './index.module.less'\n\nimport { store, useIsFeatureSupport } from '@pingcap/tidb-dashboard-lib'\n\nfunction useAppMenuItem(\n  registry,\n  appId,\n  enable: boolean = true,\n  title?: string,\n  hideIcon?: boolean\n) {\n  const { t } = useTranslation()\n  const app = registry.apps[appId]\n  if (!enable || !app) {\n    return null\n  }\n  return (\n    <Menu.Item key={appId} data-e2e={`menu_item_${appId}`}>\n      <Link to={app.indexRoute} id={appId}>\n        {!hideIcon && app.icon ? <app.icon /> : null}\n        <span>{title ? title : t(`${appId}.nav_title`, appId)}</span>\n      </Link>\n    </Menu.Item>\n  )\n}\n\nfunction useActiveAppId(registry) {\n  const [appId, set] = useState('')\n  useEventListener(\n    'single-spa:routing-event',\n    () => {\n      const activeApp = registry.getActiveApp()\n      if (activeApp) {\n        set(activeApp.id)\n      }\n    },\n    {\n      target: window\n    }\n  )\n  return appId\n}\n\nfunction Sider({\n  registry,\n  fullWidth,\n  defaultCollapsed,\n  collapsed,\n  collapsedWidth,\n  onToggle,\n  animationDelay\n}) {\n  const { t } = useTranslation()\n  const activeAppId = useActiveAppId(registry)\n\n  const whoAmI = store.useState((s) => s.whoAmI)\n  const appInfo = store.useState((s) => s.appInfo)\n\n  const supportConProf = useIsFeatureSupport('conprof')\n  const profilingSubMenuItems = [\n    useAppMenuItem(registry, 'instance_profiling', true, '', true),\n    useAppMenuItem(registry, 'conprof', supportConProf, '', true)\n  ]\n\n  const profilingSubMenu = (\n    <Menu.SubMenu\n      key=\"profiling\"\n      title={\n        <span>\n          <AimOutlined />\n          <span>{t('profiling.nav_title')}</span>\n        </span>\n      }\n    >\n      {profilingSubMenuItems}\n    </Menu.SubMenu>\n  )\n\n  const debugSubMenuItems = [\n    profilingSubMenu,\n    useAppMenuItem(registry, 'debug_api')\n  ]\n\n  const debugSubMenu = (\n    <Menu.SubMenu\n      key=\"debug\"\n      title={\n        <span>\n          <BugOutlined />\n          <span>{t('nav.sider.debug')}</span>\n        </span>\n      }\n    >\n      {debugSubMenuItems}\n    </Menu.SubMenu>\n  )\n\n  // const conflictSubMenuItems = [useAppMenuItem(registry, 'deadlock')]\n\n  // const conflictSubMenu = (\n  //   <Menu.SubMenu\n  //     key=\"conflict\"\n  //     title={\n  //       <span>\n  //         <PullRequestOutlined />\n  //         <span>{t('nav.sider.conflict')}</span>\n  //       </span>\n  //     }\n  //   >\n  //     {conflictSubMenuItems}\n  //   </Menu.SubMenu>\n  // )\n\n  const experimentalSubMenuItems = [\n    useAppMenuItem(registry, 'query_editor'),\n    useAppMenuItem(registry, 'configuration')\n  ]\n  const experimentalSubMenu = (\n    <Menu.SubMenu\n      key=\"experimental\"\n      title={\n        <span>\n          <ExperimentOutlined />\n          <span>{t('nav.sider.experimental')}</span>\n        </span>\n      }\n    >\n      {experimentalSubMenuItems}\n    </Menu.SubMenu>\n  )\n\n  const supportTopSQL = useIsFeatureSupport('topsql')\n  const supportResourceManager = useIsFeatureSupport('resource_manager')\n  const menuItems = [\n    useAppMenuItem(registry, 'overview'),\n    useAppMenuItem(registry, 'cluster_info'),\n    // topSQL\n    useAppMenuItem(registry, 'topsql', supportTopSQL),\n    useAppMenuItem(registry, 'statement'),\n    useAppMenuItem(registry, 'slow_query'),\n    useAppMenuItem(registry, 'keyviz'),\n    useAppMenuItem(registry, 'system_report'),\n    // warning: \"diagnose\" app doesn't release yet\n    // useAppMenuItem(registry, 'diagnose'),\n    useAppMenuItem(registry, 'monitoring'),\n    useAppMenuItem(registry, 'search_logs'),\n    useAppMenuItem(registry, 'resource_manager', supportResourceManager),\n    // useAppMenuItem(registry, '__APP_NAME__'),\n    // NOTE: Don't remove above comment line, it is a placeholder for code generator\n    debugSubMenu\n    // conflictSubMenu\n  ]\n\n  if (appInfo?.enable_experimental) {\n    menuItems.push(experimentalSubMenu)\n  }\n\n  let displayName = whoAmI?.display_name || '...'\n\n  const extraMenuItems = [\n    useAppMenuItem(registry, 'dashboard_settings'),\n    useAppMenuItem(registry, 'user_profile', true, displayName)\n  ]\n\n  const transSider = useSpring({\n    width: collapsed ? collapsedWidth : fullWidth\n  })\n\n  const defaultOpenKeys = useMemo(() => {\n    if (defaultCollapsed) {\n      return []\n    } else {\n      return ['debug', 'experimental', 'profiling']\n    }\n  }, [defaultCollapsed])\n\n  return (\n    <animated.div style={transSider}>\n      <Layout.Sider\n        className={styles.sider}\n        width={fullWidth}\n        trigger={null}\n        collapsible\n        collapsed={collapsed}\n        collapsedWidth={fullWidth}\n        defaultCollapsed={defaultCollapsed}\n        theme=\"light\"\n      >\n        <Banner\n          collapsed={collapsed}\n          onToggle={onToggle}\n          fullWidth={fullWidth}\n          collapsedWidth={collapsedWidth}\n        />\n        <Menu\n          subMenuOpenDelay={animationDelay}\n          subMenuCloseDelay={animationDelay + 0.1}\n          mode=\"inline\"\n          selectedKeys={[activeAppId]}\n          style={{ flexGrow: 1 }}\n          defaultOpenKeys={defaultOpenKeys}\n        >\n          {menuItems}\n        </Menu>\n        <Menu\n          subMenuOpenDelay={animationDelay}\n          subMenuCloseDelay={animationDelay}\n          mode=\"inline\"\n          selectedKeys={[activeAppId]}\n        >\n          {extraMenuItems}\n        </Menu>\n      </Layout.Sider>\n    </animated.div>\n  )\n}\n\nexport default Sider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/layout/main/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.container {\n  height: 100vh;\n}\n\n.content {\n  position: relative;\n  z-index: 3;\n  background: #fff;\n  min-height: 100vh;\n\n  &:before,\n  &:after {\n    // Handle margin collapse\n    content: ' ';\n    display: table;\n  }\n}\n\n.contentBack {\n  position: fixed;\n  z-index: 2;\n  background: #fff;\n  top: 0;\n  height: 100%;\n  right: 0;\n  box-shadow: 0 0 20px rgba(#000, 0.1);\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/layout/main/index.tsx",
    "content": "import React, { useState, useCallback, useEffect } from 'react'\n// import { Root } from '@lib/components'\nimport { HashRouter as Router } from 'react-router-dom'\nimport { useSpring, animated } from 'react-spring'\n// import { useVersionedLocalStorageState } from '@lib/utils/useVersionedLocalStorageState'\n\nimport {\n  Root,\n  useVersionedLocalStorageState\n} from '@pingcap/tidb-dashboard-lib'\n\nimport Sider from './Sider'\nimport styles from './index.module.less'\n\nconst siderWidth = 260\nconst siderCollapsedWidth = 80\nconst collapsedContentOffset = siderCollapsedWidth - siderWidth\nconst contentOffsetTrigger = collapsedContentOffset * 0.99\n\nfunction triggerResizeEvent() {\n  const event = document.createEvent('HTMLEvents')\n  event.initEvent('resize', true, false)\n  window.dispatchEvent(event)\n}\n\nconst useContentLeftOffset = (collapsed) => {\n  const [offset, setOffset] = useState(siderWidth)\n  const onAnimationStart = useCallback(() => {\n    if (!collapsed) {\n      setOffset(siderWidth)\n    }\n  }, [collapsed])\n  const onAnimationFrame = useCallback(\n    ({ x }) => {\n      if (collapsed && x < contentOffsetTrigger) {\n        setOffset(siderCollapsedWidth)\n      }\n    },\n    [collapsed]\n  )\n  useEffect(triggerResizeEvent, [offset])\n  return {\n    contentLeftOffset: offset,\n    onAnimationStart,\n    onAnimationFrame\n  }\n}\n\nexport default function App({ registry }) {\n  const [collapsed, setCollapsed] = useVersionedLocalStorageState(\n    'layout.sider.collapsed',\n    { defaultValue: false }\n  )\n  const [defaultCollapsed] = useState(collapsed)\n  const { contentLeftOffset, onAnimationStart, onAnimationFrame } =\n    useContentLeftOffset(collapsed)\n  const transContentBack = useSpring({\n    x: collapsed ? collapsedContentOffset : 0,\n    onStart: onAnimationStart,\n    onFrame: onAnimationFrame\n  })\n  const transContainer = useSpring({\n    opacity: 1,\n    from: { opacity: 0 },\n    delay: 100\n  })\n\n  const handleToggle = useCallback(() => {\n    setCollapsed((c) => !c)\n  }, [setCollapsed])\n\n  const { appOptions } = registry\n\n  return (\n    <Root>\n      <Router>\n        <animated.div className={styles.container} style={transContainer}>\n          {!appOptions.hideNav && (\n            <>\n              <Sider\n                registry={registry}\n                fullWidth={siderWidth}\n                onToggle={handleToggle}\n                defaultCollapsed={defaultCollapsed}\n                collapsed={collapsed}\n                collapsedWidth={siderCollapsedWidth}\n                animationDelay={0}\n              />\n              <animated.div\n                className={styles.contentBack}\n                style={{\n                  left: `${siderWidth}px`,\n                  transform: transContentBack.x.interpolate(\n                    (x) => `translate3d(${x}px, 0, 0)`\n                  )\n                }}\n              ></animated.div>\n            </>\n          )}\n          <div\n            className={styles.content}\n            style={\n              appOptions.hideNav\n                ? {}\n                : {\n                    marginLeft: contentLeftOffset\n                  }\n            }\n          >\n            <div id=\"__spa_content__\"></div>\n          </div>\n        </animated.div>\n      </Router>\n    </Root>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/layout/signin/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n@import 'antd/es/button/style/mixin.less';\n\n@content-width: 400px;\n\n.container {\n  position: fixed;\n  left: 0;\n  top: 0;\n  width: 100%;\n  height: 100%;\n  display: flex;\n  flex-direction: row;\n  align-items: stretch;\n}\n\n.contantContainer {\n  min-width: @content-width;\n  width: 38%;\n  background: #fff;\n  position: relative;\n}\n\n.dialogContainer {\n  height: 100%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  box-sizing: border-box;\n  padding: 30px 65px;\n  overflow-y: auto;\n}\n\n.dialog {\n  max-width: 300px;\n  width: 100%;\n  margin: auto;\n}\n\n.landingContainer {\n  flex-grow: 1;\n}\n\n.landing {\n  background-size: cover;\n  background-position: center left;\n  height: 100%;\n}\n\n.logo {\n  height: 40px;\n  margin-bottom: 80px;\n}\n\n.signInButton {\n  margin-top: 10px;\n  margin-bottom: 20px;\n  font-size: 1rem;\n}\n\n.extraLink {\n  font-size: 0.9rem;\n  margin: 15px 0;\n  a {\n    color: @gray-7;\n    &:hover {\n      color: @gray-7;\n    }\n  }\n\n  &.clickable {\n    a:hover {\n      color: @link-hover-color;\n    }\n  }\n}\n\n.alternativeFormLayer {\n  position: absolute;\n  left: 0;\n  top: 0;\n  width: 100%;\n  height: 100%;\n  background: #fff;\n  z-index: 2;\n}\n\n.alternativeCloseButton {\n  font-size: 1rem;\n  border: 0;\n  background: #fff;\n  color: @text-color;\n  cursor: pointer;\n  padding: @padding-xss;\n  padding-right: 0;\n\n  &:hover,\n  &:active,\n  &:focus {\n    color: @link-hover-color;\n    outline: 0;\n  }\n}\n\n.alternativeButton {\n  .btn;\n  .btn-default;\n\n  height: auto;\n  width: 100%;\n  color: @text-color;\n\n  margin-bottom: @padding-sm;\n  text-align: left;\n  padding: @padding-md;\n  padding-right: 50px + @padding-md;\n  position: relative;\n  word-wrap: normal;\n  white-space: normal;\n  line-height: 1;\n\n  &:hover,\n  &:active,\n  &:focus {\n    color: @text-color;\n  }\n\n  .title {\n    color: @heading-color;\n    margin-bottom: @padding-sm;\n  }\n\n  .icon {\n    font-size: 1.3rem;\n    position: absolute;\n    right: 0;\n    width: 50px;\n    text-align: center;\n    opacity: 0;\n    transform: translateX(-5px);\n    transition: opacity 0.2s linear, transform 0.2s linear;\n    color: @gray-6;\n\n    // For vertical align\n    top: 50%;\n    margin-top: -20px;\n    line-height: 40px;\n  }\n\n  &:hover,\n  &:active,\n  &:focus {\n    .icon {\n      opacity: 1;\n      transform: none;\n    }\n  }\n}\n\n.container :global {\n  .formAnimation {\n    animation: 0.2s @ease-out-circ 0.5s antZoomBigIn;\n    animation-fill-mode: both;\n    animation-iteration-count: 1;\n  }\n  .landingAnimation {\n    animation: 0.5s linear 0 antFadeIn;\n    animation-fill-mode: both;\n    animation-iteration-count: 1;\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/layout/signin/index.tsx",
    "content": "import CSSMotion from 'rc-animate/es/CSSMotion'\nimport cx from 'classnames'\nimport * as singleSpa from 'single-spa'\nimport React, {\n  useState,\n  useEffect,\n  useRef,\n  useCallback,\n  useMemo,\n  ReactNode\n} from 'react'\nimport {\n  DownOutlined,\n  GlobalOutlined,\n  LockOutlined,\n  UserOutlined,\n  KeyOutlined,\n  ArrowRightOutlined,\n  CloseOutlined\n} from '@ant-design/icons'\nimport { Form, Input, InputRef, Button, message, Typography, Modal } from 'antd'\nimport { useTranslation } from 'react-i18next'\nimport { useMount } from 'react-use'\nimport Flexbox from '@g07cha/flexbox-react'\nimport { useMemoizedFn } from 'ahooks'\nimport JSEncrypt from 'jsencrypt'\n\nimport {\n  // distro\n  isDistro,\n  // store\n  useIsFeatureSupport,\n  // components\n  Root,\n  AppearAnimate,\n  LanguageDropdown\n} from '@pingcap/tidb-dashboard-lib'\n\nimport client, { UserAuthenticateForm } from '~/client'\nimport auth from '~/utils/auth'\nimport { getAuthURL } from '~/utils/authSSO'\nimport { landingSvg, logoSvg } from '~/utils/distro/assetsRes'\n\nimport styles from './index.module.less'\n\nenum DisplayFormType {\n  uninitialized,\n  tidbCredential,\n  shareCode,\n  sso\n}\n\nfunction AlternativeAuthLink({ onClick }) {\n  const { t } = useTranslation()\n  return (\n    <div className={cx(styles.extraLink, styles.clickable)}>\n      <a onClick={onClick}>\n        <LockOutlined /> {t('signin.form.use_alternative')}\n      </a>\n    </div>\n  )\n}\n\nfunction LanguageDrop() {\n  return (\n    <div className={styles.extraLink}>\n      <LanguageDropdown>\n        <a>\n          <GlobalOutlined /> Switch Language <DownOutlined />\n        </a>\n      </LanguageDropdown>\n    </div>\n  )\n}\n\ninterface IAlternativeFormButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  title: string\n  description: string\n  className?: string\n}\n\nfunction AlternativeFormButton({\n  title,\n  description,\n  className,\n  ...restProps\n}: IAlternativeFormButtonProps) {\n  return (\n    <button className={cx(className, styles.alternativeButton)} {...restProps}>\n      <div className={styles.title}>{title}</div>\n      <div>\n        <Typography.Text type=\"secondary\">\n          <small>{description}</small>\n        </Typography.Text>\n      </div>\n      <div className={styles.icon}>\n        <ArrowRightOutlined />\n      </div>\n    </button>\n  )\n}\n\nfunction AlternativeAuthForm({\n  className,\n  onClose,\n  onSwitchForm,\n  supportedAuthTypes,\n  ...restProps\n}) {\n  const { t } = useTranslation()\n\n  return (\n    <div className={cx(className, styles.alternativeFormLayer)} {...restProps}>\n      <div className={styles.dialogContainer}>\n        <div className={styles.dialog}>\n          <Form>\n            <Form.Item>\n              <h2>\n                <Flexbox\n                  flexDirection=\"row\"\n                  justifyContent=\"space-between\"\n                  alignItems=\"center\"\n                >\n                  <div>{t('signin.form.alternative.title')}</div>\n                  <button\n                    className={styles.alternativeCloseButton}\n                    onClick={onClose}\n                  >\n                    <CloseOutlined />\n                  </button>\n                </Flexbox>\n              </h2>\n            </Form.Item>\n            <Form.Item>\n              <AlternativeFormButton\n                title={t('signin.form.tidb_auth.switch.title')}\n                description={t('signin.form.tidb_auth.switch.description')}\n                onClick={() => onSwitchForm(DisplayFormType.tidbCredential)}\n              />\n            </Form.Item>\n            <Form.Item>\n              <AlternativeFormButton\n                title={t('signin.form.code_auth.switch.title')}\n                description={t('signin.form.code_auth.switch.description')}\n                onClick={() => onSwitchForm(DisplayFormType.shareCode)}\n              />\n            </Form.Item>\n            {Boolean(supportedAuthTypes.indexOf(auth.AuthTypes.SSO) > -1) && (\n              <Form.Item>\n                <AlternativeFormButton\n                  title={t('signin.form.sso.switch.title')}\n                  description={t('signin.form.sso.switch.description')}\n                  onClick={() => onSwitchForm(DisplayFormType.sso)}\n                />\n              </Form.Item>\n            )}\n            <LanguageDrop />\n          </Form>\n        </div>\n      </div>\n    </div>\n  )\n}\n\nfunction useSignInSubmit(\n  successRoute,\n  fnLoginForm: (form) => UserAuthenticateForm,\n  onSuccess: (form) => void,\n  onFailure: () => void\n) {\n  const { t } = useTranslation()\n  const [loading, setLoading] = useState(false)\n  const [error, setError] = useState<string | ReactNode | null>(null)\n\n  const clearErrorMsg = useCallback(() => {\n    setError(null)\n  }, [])\n\n  const handleSubmit = useMemoizedFn(async (form) => {\n    try {\n      clearErrorMsg()\n      setLoading(true)\n      const r = await client\n        .getInstance()\n        .userLogin({ message: fnLoginForm(form) }, {\n          handleError: 'custom'\n        } as any)\n      auth.setAuthToken(r.data.token)\n      message.success(t('signin.message.success'))\n      singleSpa.navigateToUrl(successRoute)\n      onSuccess(form)\n    } catch (e) {\n      const { handled, message, errCode } = e as any\n      if (!handled) {\n        const errMsg = t('signin.message.error', { msg: message })\n        if (errCode !== 'api.user.signin.insufficient_priv') {\n          setError(errMsg)\n        } else {\n          // only add help link for TiDB distro when meeting insufficient_privileges error\n          const errComp = (\n            <>\n              {errMsg}\n              {!isDistro() && (\n                <>\n                  {' '}\n                  <a\n                    href={t('signin.message.access_doc_link')}\n                    target=\"_blank\"\n                    rel=\"noopener noreferrer\"\n                  >\n                    {t('signin.message.access_doc')}\n                  </a>\n                </>\n              )}\n            </>\n          )\n          setError(errComp)\n        }\n        onFailure()\n      }\n    } finally {\n      setLoading(false)\n    }\n  })\n\n  return { handleSubmit, loading, errorMsg: error, clearErrorMsg }\n}\n\nconst LAST_LOGIN_USERNAME_KEY = 'dashboard_last_login_username'\n\nfunction TiDBSignInForm({ successRoute, onClickAlternative, publicKey }) {\n  const supportNonRootLogin = useIsFeatureSupport('nonRootLogin')\n\n  const { t } = useTranslation()\n\n  const [refForm] = Form.useForm()\n  const refPassword = useRef<InputRef>(null)\n\n  const { handleSubmit, loading, errorMsg, clearErrorMsg } = useSignInSubmit(\n    successRoute,\n    (form) => {\n      let password = form.password ?? ''\n      if (!!publicKey) {\n        const jsEncrypt = new JSEncrypt()\n        if (publicKey.startsWith('-----BEGIN PUBLIC KEY-----')) {\n          // if publicKey is generated by `ExportPublicKeyAsString(s.rsaPublicKey)`, it has header and footer, so we use it directly\n          jsEncrypt.setPublicKey(publicKey)\n        } else {\n          // if publicKey is generated by `DumpPublicKeyBase64(s.rsaPublicKey)`, it has no header and footer, so we need to add them\n          jsEncrypt.setPublicKey(\n            '-----BEGIN PUBLIC KEY-----' +\n              publicKey +\n              '-----END PUBLIC KEY-----'\n          )\n        }\n        password = jsEncrypt.encrypt(password)\n      }\n      return {\n        username: form.username,\n        password,\n        type: auth.AuthTypes.SQLUser\n      }\n    },\n    (form) => {\n      localStorage.setItem(LAST_LOGIN_USERNAME_KEY, form.username)\n    },\n    () => {\n      refForm.setFieldsValue({ password: '' })\n      setTimeout(() => {\n        refPassword.current?.focus()\n      }, 0)\n    }\n  )\n\n  useMount(() => {\n    refPassword?.current?.focus()\n  })\n\n  const lastLoginUsername = useMemo(() => {\n    return localStorage.getItem(LAST_LOGIN_USERNAME_KEY) || ''\n  }, [])\n\n  return (\n    <div className={styles.dialogContainer}>\n      <div className={styles.dialog}>\n        <Form\n          name=\"tidb_signin\"\n          onFinish={handleSubmit}\n          layout=\"vertical\"\n          initialValues={{ username: lastLoginUsername }}\n          form={refForm}\n        >\n          <img src={logoSvg} className={styles.logo} />\n          <Form.Item>\n            <h2>{t('signin.form.tidb_auth.title')}</h2>\n          </Form.Item>\n          <Form.Item\n            name=\"username\"\n            label={t('signin.form.username')}\n            rules={[{ required: true }]}\n            tooltip={!supportNonRootLogin && t('signin.form.username_tooltip')}\n          >\n            <Input\n              data-e2e=\"signin_username_input\"\n              onInput={clearErrorMsg}\n              prefix={<UserOutlined />}\n              disabled={!supportNonRootLogin}\n            />\n          </Form.Item>\n          <Form.Item\n            data-e2e=\"signin_password_form_item\"\n            name=\"password\"\n            label={t('signin.form.password')}\n            {...(errorMsg && {\n              help: errorMsg,\n              validateStatus: 'error'\n            })}\n          >\n            <Input\n              prefix={<KeyOutlined />}\n              type=\"password\"\n              disabled={loading}\n              onInput={clearErrorMsg}\n              ref={refPassword}\n              data-e2e=\"signin_password_input\"\n            />\n          </Form.Item>\n          <Form.Item>\n            <Button\n              data-e2e=\"signin_submit\"\n              type=\"primary\"\n              htmlType=\"submit\"\n              size=\"large\"\n              loading={loading}\n              className={styles.signInButton}\n              block\n            >\n              {t('signin.form.button')}\n            </Button>\n          </Form.Item>\n          <AlternativeAuthLink onClick={onClickAlternative} />\n          <LanguageDrop />\n        </Form>\n      </div>\n    </div>\n  )\n}\n\nfunction CodeSignInForm({ successRoute, onClickAlternative }) {\n  const { t } = useTranslation()\n\n  const [refForm] = Form.useForm()\n  const refPassword = useRef<InputRef>(null)\n\n  const { handleSubmit, loading, errorMsg, clearErrorMsg } = useSignInSubmit(\n    successRoute,\n    (form) => ({\n      password: form.code,\n      type: auth.AuthTypes.SharingCode\n    }),\n    () => {},\n    () => {\n      refForm.setFieldsValue({ code: '' })\n      setTimeout(() => {\n        refPassword.current?.focus()\n      }, 0)\n    }\n  )\n\n  useMount(() => {\n    refPassword?.current?.focus()\n  })\n\n  return (\n    <div className={styles.dialogContainer}>\n      <div className={styles.dialog}>\n        <Form onFinish={handleSubmit} layout=\"vertical\" form={refForm}>\n          <img src={logoSvg} className={styles.logo} />\n          <Form.Item>\n            <h2>{t('signin.form.code_auth.title')}</h2>\n          </Form.Item>\n          <Form.Item\n            name=\"code\"\n            label={t('signin.form.code_auth.code')}\n            {...(errorMsg && {\n              help: errorMsg,\n              validateStatus: 'error'\n            })}\n          >\n            <Input\n              prefix={<KeyOutlined />}\n              type=\"password\"\n              onInput={clearErrorMsg}\n              disabled={loading}\n              ref={refPassword}\n              allowClear\n            />\n          </Form.Item>\n          <Form.Item>\n            <Button\n              type=\"primary\"\n              htmlType=\"submit\"\n              size=\"large\"\n              loading={loading}\n              className={styles.signInButton}\n              block\n            >\n              {t('signin.form.button')}\n            </Button>\n          </Form.Item>\n          <AlternativeAuthLink onClick={onClickAlternative} />\n          <LanguageDrop />\n        </Form>\n      </div>\n    </div>\n  )\n}\n\nfunction SSOSignInForm({ successRoute, onClickAlternative }) {\n  const { t } = useTranslation()\n  const [isLoading, setIsLoading] = useState(false)\n\n  const handleSignIn = useCallback(async () => {\n    setIsLoading(true)\n    try {\n      const url = await getAuthURL()\n      window.location.href = url\n      // Do not hide loading status when url is resolved, since we are now jumping\n    } catch (e) {\n      setIsLoading(false)\n    }\n  }, [])\n\n  return (\n    <div className={styles.dialogContainer}>\n      <div className={styles.dialog}>\n        <Form>\n          <img src={logoSvg} className={styles.logo} />\n          <Form.Item>\n            <Button\n              type=\"primary\"\n              htmlType=\"submit\"\n              size=\"large\"\n              loading={isLoading}\n              className={styles.signInButton}\n              block\n              onClick={handleSignIn}\n            >\n              {t('signin.form.sso.button')}\n            </Button>\n          </Form.Item>\n          <AlternativeAuthLink onClick={onClickAlternative} />\n          <LanguageDrop />\n        </Form>\n      </div>\n    </div>\n  )\n}\n\nfunction App({ registry }) {\n  const successRoute = useMemo(\n    () => `#${registry.getDefaultRouter()}`,\n    [registry]\n  )\n  const [alternativeVisible, setAlternativeVisible] = useState(false)\n  const [formType, setFormType] = useState(DisplayFormType.uninitialized)\n  const [supportedAuthTypes, setSupportedAuthTypes] = useState<Array<number>>([\n    0\n  ])\n  const [publicKey, setPublicKey] = useState('')\n\n  const handleClickAlternative = useCallback(() => {\n    setAlternativeVisible(true)\n  }, [])\n\n  const handleAlternativeClose = useCallback(() => {\n    setAlternativeVisible(false)\n  }, [])\n\n  const handleSwitchForm = useCallback((k: DisplayFormType) => {\n    setFormType(k)\n    setAlternativeVisible(false)\n  }, [])\n\n  useEffect(() => {\n    async function run() {\n      try {\n        const resp = await client\n          .getInstance()\n          .userGetLoginInfo({ handleError: 'custom' } as any)\n        const loginInfo = resp.data\n        if (\n          (loginInfo.supported_auth_types?.indexOf(auth.AuthTypes.SSO) ?? -1) >\n          -1\n        ) {\n          setFormType(DisplayFormType.sso)\n        } else {\n          setFormType(DisplayFormType.tidbCredential)\n        }\n        setSupportedAuthTypes(loginInfo.supported_auth_types ?? [])\n        if (!!loginInfo.sql_auth_public_key) {\n          setPublicKey(loginInfo.sql_auth_public_key)\n        }\n      } catch (e) {\n        if ((e as any).response?.status === 404) {\n          setFormType(DisplayFormType.tidbCredential)\n        } else {\n          Modal.error({\n            title: 'Initialize Sign in failed',\n            content: '' + e,\n            okText: 'Reload',\n            onOk: () => window.location.reload()\n          })\n        }\n      }\n    }\n    run()\n  }, [])\n\n  return (\n    <Root>\n      <div className={styles.container}>\n        <AppearAnimate\n          className={styles.contantContainer}\n          motionName=\"formAnimation\"\n        >\n          <CSSMotion visible={alternativeVisible} motionName=\"fade\">\n            {({ style, className }) => (\n              <AlternativeAuthForm\n                style={style}\n                className={className}\n                onClose={handleAlternativeClose}\n                onSwitchForm={handleSwitchForm}\n                supportedAuthTypes={supportedAuthTypes}\n              />\n            )}\n          </CSSMotion>\n          {formType === DisplayFormType.tidbCredential && (\n            <TiDBSignInForm\n              successRoute={successRoute}\n              onClickAlternative={handleClickAlternative}\n              publicKey={publicKey}\n            />\n          )}\n          {formType === DisplayFormType.shareCode && (\n            <CodeSignInForm\n              successRoute={successRoute}\n              onClickAlternative={handleClickAlternative}\n            />\n          )}\n          {formType === DisplayFormType.sso && (\n            <SSOSignInForm\n              successRoute={successRoute}\n              onClickAlternative={handleClickAlternative}\n            />\n          )}\n        </AppearAnimate>\n        <AppearAnimate\n          motionName=\"landingAnimation\"\n          className={styles.landingContainer}\n        >\n          <div\n            style={{\n              backgroundImage: `url(${landingSvg})`\n            }}\n            className={styles.landing}\n          />\n        </AppearAnimate>\n      </div>\n    </Root>\n  )\n}\n\nexport default App\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/layout/translations/en.yaml",
    "content": "signin:\n  message:\n    error: 'Sign in failed: {{ msg }}'\n    success: Sign in successfully\n    access_doc: Help\n    access_doc_link: https://docs.pingcap.com/tidb/stable/dashboard-user\n  form:\n    username: Username\n    username_tooltip: Sign in user can be customized in TiDB 5.3 or later versions\n    password: Password\n    button: Sign In\n    tidb_auth:\n      title: SQL User Sign In\n      switch:\n        title: SQL User\n        description: I know the username and password to connect to the database\n    code_auth:\n      title: Authorization Code Sign In\n      switch:\n        title: Authorization Code\n        description: I was invited by others with an authorization code\n      code: Code\n    sso:\n      button: Sign In via Company Account (SSO)\n      switch:\n        title: SSO\n        description: I want to sign in use my company account\n    use_alternative: Use Alternative Authentication\n    alternative:\n      title: Select Authentication\nnav:\n  user:\n    signout: Sign Out\n  sider:\n    debug: Advanced Debugging\n    conflict: Conflict Diagnosing\n    experimental: Experimental Features\nhealth_check:\n  failed_notification_title: System Health Check Failed\n  ngm_not_started: A required component `NgMonitoring` is not started in this cluster. Some features may not work.\n  help_text: Help\n  help_url: https://docs.pingcap.com/tidb/dev/dashboard-faq#a-required-component-ngmonitoring-is-not-started-error-is-shown\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/layout/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/layout/translations/zh.yaml",
    "content": "signin:\n  message:\n    error: '登录失败: {{ msg }}'\n    success: 登录成功\n    access_doc: 帮助\n    access_doc_link: https://docs.pingcap.com/zh/tidb/stable/dashboard-user\n  form:\n    username: 用户名\n    username_tooltip: 升级到 TiDB 5.3 及更高版本后可自定义登录用户\n    password: 密码\n    button: 登录\n    tidb_auth:\n      title: SQL 用户登录\n      switch:\n        title: SQL 用户\n        description: 我知道数据库的登录用户名和密码\n    code_auth:\n      title: 授权码登录\n      switch:\n        title: 授权码\n        description: 其他人通过授权码邀请我使用\n      code: 授权码\n    sso:\n      button: 使用公司账号 SSO 登录\n      switch:\n        title: SSO\n        description: 使用公司账号登录\n    use_alternative: 使用其他登录方式\n    alternative:\n      title: 选择登录方式\n\nnav:\n  user:\n    signout: 登出\n  sider:\n    debug: 高级调试\n    conflict: 冲突诊断\n    experimental: 实验性功能\nhealth_check:\n  failed_notification_title: 系统健康检查失败\n  ngm_not_started: 集群中未启动必要组件 `NgMonitoring`，部分功能将不可用。\n  help_text: 帮助\n  help_url: https://docs.pingcap.com/zh/tidb/dev/dashboard-faq#界面提示-集群中未启动必要组件-ngmonitoring\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/main.tsx",
    "content": "import React from 'react'\n\nimport * as singleSpa from 'single-spa'\nimport i18next from 'i18next'\nimport { Modal, notification } from 'antd'\nimport NProgress from 'nprogress'\nimport './nprogress.less'\n\nimport {\n  routing,\n  i18n,\n  // telemetry\n  telemetry,\n  // store\n  NgmState,\n  // distro\n  distro,\n  isDistro\n} from '@pingcap/tidb-dashboard-lib'\n\nimport { InfoInfoResponse } from '~/client'\nimport auth from '~/utils/auth'\nimport { handleSSOCallback, isSSOCallback } from '~/utils/authSSO'\nimport { mustLoadAppInfo, reloadWhoAmI } from '~/utils/store'\nimport { loadAppOptions, saveAppOptions } from '~/utils/appOptions'\nimport AppRegistry from '~/utils/registry'\n\nimport AppOverview from '~/apps/Overview/meta'\nimport AppMonitoring from '~/apps/Monitoring/meta'\nimport AppClusterInfo from '~/apps/ClusterInfo/meta'\nimport AppTopSQL from '~/apps/TopSQL/meta'\nimport AppSlowQuery from '~/apps/SlowQuery/meta'\nimport AppStatement from '~/apps/Statement/meta'\nimport AppKeyViz from '~/apps/KeyViz/meta'\nimport AppSystemReport from '~/apps/SystemReport/meta'\nimport AppSearchLogs from '~/apps/SearchLogs/meta'\nimport AppInstanceProfiling from '~/apps/InstanceProfiling/meta'\nimport AppConProfiling from '~/apps/ContinuousProfiling/meta'\nimport AppDebugAPI from '~/apps/DebugAPI/meta'\nimport AppQueryEditor from '~/apps/QueryEditor/meta'\nimport AppConfiguration from '~/apps/Configuration/meta'\nimport AppUserProfile from '~/apps/UserProfile/meta'\nimport AppDiagnose from '~/apps/Diagnose/meta'\nimport AppOptimizerTrace from '~/apps/OptimizerTrace/meta'\nimport AppDeadlock from '~/apps/Deadlock/meta'\nimport AppResourceManager from '~/apps/ResourceManager/meta'\n\nimport LayoutMain from './layout/main'\nimport LayoutSignIn from './layout/signin'\n\nimport translations from './layout/translations'\n\n// for update distro strings resource\nimport '~/utils/distro/stringsRes'\n\nfunction removeSpinner() {\n  const spinner = document.getElementById('dashboard_page_spinner')\n  if (spinner) {\n    spinner.remove()\n  }\n}\n\nasync function webPageStart() {\n  const options = loadAppOptions()\n  if (options.lang) {\n    i18next.changeLanguage(options.lang)\n  }\n  i18n.addTranslations(translations)\n\n  let info: InfoInfoResponse\n\n  try {\n    info = await mustLoadAppInfo()\n  } catch (e) {\n    Modal.error({\n      title: `Failed to connect to server`,\n      content: '' + e,\n      okText: 'Reload',\n      onOk: () => window.location.reload()\n    })\n    removeSpinner()\n    return\n  }\n\n  telemetry.init(\n    process.env.REACT_APP_MIXPANEL_HOST,\n    process.env.REACT_APP_MIXPANEL_TOKEN\n  )\n  if (info?.enable_telemetry) {\n    // mixpanel\n    // close mixpanel telemetry for tidb-dashboard op\n    // telemetry.enable(info.version?.internal_version!)\n    let preRoute = ''\n    window.addEventListener('single-spa:routing-event', () => {\n      const curRoute = routing.getPathInLocationHash()\n      if (curRoute !== preRoute) {\n        telemetry.trackRouteChange(curRoute)\n        preRoute = curRoute\n      }\n    })\n  }\n\n  if (!options.skipNgmCheck && info?.ngm_state === NgmState.NotStarted) {\n    notification.error({\n      key: 'ngm_not_started',\n      message: i18next.t('health_check.failed_notification_title'),\n      description: (\n        <span>\n          {i18next.t('health_check.ngm_not_started')}\n          {!isDistro() && (\n            <>\n              {' '}\n              <a\n                href={i18next.t('health_check.help_url')}\n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n              >\n                {i18next.t('health_check.help_text')}\n              </a>\n            </>\n          )}\n        </span>\n      ),\n      duration: null\n    })\n  }\n\n  const registry = new AppRegistry(options)\n\n  NProgress.configure({\n    showSpinner: false\n  })\n  window.addEventListener('single-spa:before-routing-event', () => {\n    NProgress.set(0.2)\n  })\n  window.addEventListener('single-spa:routing-event', () => {\n    NProgress.done(true)\n  })\n\n  singleSpa.registerApplication(\n    'layout',\n    AppRegistry.newReactSpaApp(() => LayoutMain, 'root'),\n    () => {\n      return !routing.isSignInPage()\n    },\n    { registry }\n  )\n\n  singleSpa.registerApplication(\n    'signin',\n    AppRegistry.newReactSpaApp(() => LayoutSignIn, 'root'),\n    () => {\n      return routing.isSignInPage()\n    },\n    { registry }\n  )\n\n  registry\n    .register(AppUserProfile)\n    .register(AppOverview)\n    .register(AppClusterInfo)\n    .register(AppKeyViz)\n    .register(AppTopSQL)\n    .register(AppStatement)\n    .register(AppSystemReport)\n    .register(AppSlowQuery)\n    .register(AppDiagnose)\n    .register(AppMonitoring)\n    .register(AppSearchLogs)\n    .register(AppInstanceProfiling)\n    .register(AppConProfiling)\n    .register(AppQueryEditor)\n    .register(AppConfiguration)\n    .register(AppDebugAPI)\n    .register(AppOptimizerTrace)\n    .register(AppDeadlock)\n    .register(AppResourceManager)\n\n  try {\n    const ok = await reloadWhoAmI()\n\n    if (routing.isLocationMatch('/') && ok) {\n      singleSpa.navigateToUrl('#' + registry.getDefaultRouter())\n    }\n  } catch (e) {\n    // If there are auth errors, redirection will happen any way. So we continue.\n  }\n\n  window.addEventListener('single-spa:app-change', () => {\n    if (!routing.isSignInPage()) {\n      if (!auth.getAuthTokenAsBearer()) {\n        singleSpa.navigateToUrl('#' + routing.signInRoute)\n      }\n    }\n  })\n\n  window.addEventListener('single-spa:first-mount', () => {\n    removeSpinner()\n  })\n\n  singleSpa.start()\n}\n\nasync function main() {\n  document.title = `${distro().tidb} Dashboard`\n\n  if (routing.isPortalPage()) {\n    // the portal page is only used to receive options\n    function handlePortalEvent(event) {\n      const { type, token, lang, hideNav, skipNgmCheck, redirectPath } =\n        event.data\n      // the event type must be \"DASHBOARD_PORTAL_EVENT\"\n      if (type !== 'DASHBOARD_PORTAL_EVENT') {\n        return\n      }\n\n      auth.setAuthToken(token)\n      saveAppOptions({ hideNav, lang, skipNgmCheck })\n      window.location.hash = `#${redirectPath}`\n      window.location.reload()\n\n      window.removeEventListener('message', handlePortalEvent)\n    }\n\n    window.addEventListener('message', handlePortalEvent)\n    return\n  }\n\n  if (isSSOCallback()) {\n    await handleSSOCallback()\n    return\n  }\n\n  await webPageStart()\n}\n\nmain()\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/dashboardApp/nprogress.less",
    "content": "@progress-color: #ffc53d;\n\n#nprogress {\n  pointer-events: none;\n}\n\n#nprogress .bar {\n  background: @progress-color;\n\n  position: fixed;\n  z-index: 1031;\n  top: 0;\n  left: 0;\n\n  width: 100%;\n  height: 2px;\n}\n\n/* Fancy blur effect */\n#nprogress .peg {\n  display: block;\n  position: absolute;\n  right: 0px;\n  width: 100px;\n  height: 100%;\n  box-shadow: 0 0 10px @progress-color, 0 0 5px @progress-color;\n  opacity: 1;\n  transform: rotate(3deg) translate(0px, -4px);\n}\n\n/* Remove these to get rid of the spinner */\n#nprogress .spinner {\n  display: block;\n  position: fixed;\n  z-index: 1031;\n  top: 15px;\n  right: 15px;\n}\n\n#nprogress .spinner-icon {\n  width: 18px;\n  height: 18px;\n  box-sizing: border-box;\n\n  border: solid 2px transparent;\n  border-top-color: @progress-color;\n  border-left-color: @progress-color;\n  border-radius: 50%;\n  animation: nprogress-spinner 400ms linear infinite;\n}\n\n.nprogress-custom-parent {\n  overflow: hidden;\n  position: relative;\n}\n\n.nprogress-custom-parent #nprogress .spinner,\n.nprogress-custom-parent #nprogress .bar {\n  position: absolute;\n}\n\n@keyframes nprogress-spinner {\n  0% {\n    transform: rotate(0deg);\n  }\n  100% {\n    transform: rotate(360deg);\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/diagnoseReportApp/components/DiagnosisReport.tsx",
    "content": "import React, { useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { i18n } from '@pingcap/tidb-dashboard-lib'\n\nimport DiagnosisTable from './DiagnosisTable'\nimport { ExpandContext, TableDef } from '../types'\n\nfunction LangDropdown() {\n  const { i18n: i18next } = useTranslation()\n  return (\n    <div className=\"select\">\n      <select\n        onChange={(e) => i18next.changeLanguage(e.target.value)}\n        defaultValue={i18next.language}\n      >\n        {Object.keys(i18n.ALL_LANGUAGES).map((langKey) => (\n          <option value={langKey} key={langKey}>\n            {i18n.ALL_LANGUAGES[langKey]}\n          </option>\n        ))}\n      </select>\n    </div>\n  )\n}\n\ntype Props = {\n  diagnosisTables: TableDef[]\n}\n\nfunction TablesNavMenu({ diagnosisTables }: Props) {\n  const { t } = useTranslation()\n  return (\n    <div className=\"dropdown is-hoverable\">\n      <div className=\"dropdown-trigger\">\n        <a className=\"navbar-link\">{t('diagnosis.all_tables')}</a>\n      </div>\n      <div className=\"dropdown-menu\">\n        <div\n          className=\"dropdown-content\"\n          style={{\n            maxHeight: 500,\n            overflowY: 'scroll'\n          }}\n        >\n          {diagnosisTables.map((item) => (\n            <React.Fragment key={item.title}>\n              <h2 style={{ paddingLeft: 16 }}>\n                {item.category[0] &&\n                  t(`diagnosis.tables.category.${item.category[0]}`)}\n              </h2>\n              <a\n                style={{ paddingLeft: 32 }}\n                className=\"dropdown-item\"\n                href={`#${item.title}`}\n              >\n                {t(`diagnosis.tables.title.${item.title}`)}\n              </a>\n            </React.Fragment>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\nexport default function DiagnosisReport({ diagnosisTables }: Props) {\n  const [expandAll, setExpandAll] = useState(false)\n  const { t } = useTranslation()\n\n  return (\n    <section className=\"section\">\n      <div className=\"container\">\n        <h1 className=\"title is-size-1\">{t('diagnosis.title')}</h1>\n        <div className=\"actions\">\n          <LangDropdown />\n          <button\n            className=\"button is-link is-light\"\n            style={{ marginRight: 12, marginLeft: 12 }}\n            onClick={() => setExpandAll(true)}\n          >\n            {t('diagnosis.expand_all')}\n          </button>\n          <button\n            className=\"button is-link is-light\"\n            onClick={() => setExpandAll(false)}\n          >\n            {t('diagnosis.fold_all')}\n          </button>\n          <TablesNavMenu diagnosisTables={diagnosisTables} />\n        </div>\n\n        <ExpandContext.Provider value={expandAll}>\n          {diagnosisTables.map((item, idx) => (\n            <DiagnosisTable diagnosis={item} key={idx} />\n          ))}\n        </ExpandContext.Provider>\n      </div>\n    </section>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/diagnoseReportApp/components/DiagnosisTable.tsx",
    "content": "import React, { useContext, useState, useEffect } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport ReactMarkdown from 'react-markdown'\n\nimport { distro } from '@pingcap/tidb-dashboard-lib'\n\nimport { TableDef, ExpandContext, TableRowDef } from '../types'\n\nconst lowerDistro = Object.keys(distro).reduce((accu, cur) => {\n  if (typeof distro[cur] === 'string') {\n    accu[cur] = distro[cur].toLowerCase()\n  }\n  return accu\n}, {})\n\nconst distroRegs = Object.keys(lowerDistro).reduce((accu, cur) => {\n  accu[cur] = new RegExp(cur, 'ig')\n  return accu\n}, {})\n\nfunction replaceDistro(oriStr: string): string {\n  let retStr = oriStr\n  Object.keys(lowerDistro).forEach((key) => {\n    retStr = retStr.replace(distroRegs[key], lowerDistro[key])\n  })\n  return retStr\n}\n\nfunction DiagnosisRow({ row }: { row: TableRowDef }) {\n  const outsideExpand = useContext(ExpandContext)\n  const [internalExpand, setInternalExpand] = useState(false)\n  const { t, i18n } = useTranslation()\n\n  // when outsideExpand changes, reset the internalExpand to the same as outsideExpand\n  useEffect(() => {\n    setInternalExpand(outsideExpand)\n  }, [outsideExpand])\n\n  function showRowName(rowName: string) {\n    const i18nKey = `diagnosis.tables.table.name.${rowName}`\n    if (i18n.exists(i18nKey)) {\n      return t(i18nKey)\n    }\n    return replaceDistro(rowName)\n  }\n\n  function showOthers(val: string | number) {\n    if (typeof val === 'string') {\n      return replaceDistro(val)\n    }\n    return val\n  }\n\n  return (\n    <>\n      <tr>\n        {(row.values || []).map((val, valIdx) => (\n          <td key={valIdx}>\n            {valIdx === 0 ? showRowName(val) : showOthers(val)}\n            {valIdx === 0 &&\n              t(`diagnosis.tables.table.comment.${val}`, '') !== '' && (\n                <div className=\"dropdown is-hoverable is-up\">\n                  <div className=\"dropdown-trigger\">\n                    <span className=\"icon has-text-info\">\n                      <i className=\"fas fa-info-circle\"></i>\n                    </span>\n                  </div>\n                  <div className=\"dropdown-menu\">\n                    <div className=\"dropdown-content\">\n                      <div className=\"dropdown-item\">\n                        <p>{t(`diagnosis.tables.table.comment.${val}`)}</p>\n                      </div>\n                    </div>\n                  </div>\n                </div>\n              )}\n            {valIdx === 0 && (row.sub_values || []).length > 0 && (\n              <>\n                &nbsp;&nbsp;&nbsp;\n                <span\n                  className=\"subvalues-toggle\"\n                  onClick={() => setInternalExpand(!internalExpand)}\n                >\n                  {internalExpand ? t('diagnosis.fold') : t('diagnosis.expand')}\n                </span>\n              </>\n            )}\n          </td>\n        ))}\n      </tr>\n      {(row.sub_values || []).map((subVals, subValsIdx) => (\n        <tr\n          key={subValsIdx}\n          className={`subvalues ${!internalExpand && 'fold'}`}\n        >\n          {subVals.map((subVal, subValIdx) => (\n            <td key={subValIdx}>\n              {subValIdx === 0 && '|-- '}\n              {showOthers(subVal)}\n            </td>\n          ))}\n        </tr>\n      ))}\n    </>\n  )\n}\n\ntype Props = {\n  diagnosis: TableDef\n}\n\nexport default function DiagnosisTable({ diagnosis }: Props) {\n  const { category, title, column, rows } = diagnosis\n  const { t } = useTranslation()\n\n  return (\n    <div className=\"report-container\" id={title}>\n      {(category || []).map((c, idx) => (\n        <h1 className={`title is-size-${idx + 2}`} key={idx}>\n          {c && t(`diagnosis.tables.category.${c}`)}\n        </h1>\n      ))}\n      <h3 className=\"is-size-4\">{t(`diagnosis.tables.title.${title}`)}</h3>\n      <ReactMarkdown>\n        {t(`diagnosis.tables.comment.${title}`, '')}\n      </ReactMarkdown>\n      <table\n        className=\"table is-bordered is-hoverable is-narrow is-fullwidth\"\n        style={{ position: 'relative' }}\n      >\n        <thead>\n          <tr>\n            {column.map((col, colIdx) => (\n              <th className=\"table-header-row\" key={colIdx}>\n                {col}\n              </th>\n            ))}\n          </tr>\n        </thead>\n        <tbody>\n          {(rows || []).map((row, rowIdx) => (\n            <DiagnosisRow key={rowIdx} row={row} />\n          ))}\n        </tbody>\n      </table>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/diagnoseReportApp/index.css",
    "content": ".report-container {\n  margin-bottom: 16px;\n}\n\ntr.subvalues {\n  background-color: lightcyan;\n}\n\ntr.subvalues.fold {\n  display: none;\n}\n\n.subvalues-toggle {\n  display: inline-block;\n  width: 60px;\n  cursor: pointer;\n  color: #2160c4;\n}\n\n.actions {\n  padding: 8px 0;\n  background-color: white;\n\n  position: sticky;\n  top: 0;\n  z-index: 2;\n}\n\n.table-header-row {\n  position: sticky;\n  top: 55px;\n  z-index: 1;\n\n  background-color: #888;\n  color: white !important;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/diagnoseReportApp/index.tsx",
    "content": "import React from 'react'\nimport ReactDOM from 'react-dom'\n\nimport 'bulma/css/bulma.css'\nimport '@fortawesome/fontawesome-free/js/all.js'\n\nimport { i18n, distro } from '@pingcap/tidb-dashboard-lib'\n\nimport DiagnosisReport from './components/DiagnosisReport'\nimport translations from './translations'\n\n// for update distro strings resource\nimport '~/utils/distro/stringsRes'\n\nimport './index.css'\n\nfunction refineDiagnosisData() {\n  const diagnosisData = window.__diagnosis_data__ || []\n\n  let preCategory = ''\n  diagnosisData.forEach((d) => {\n    if (d.category.join('') === preCategory) {\n      d.category = []\n    } else {\n      preCategory = d.category.join('')\n    }\n  })\n  return diagnosisData\n}\n\ni18n.addTranslations(translations)\ndocument.title = `${distro().tidb} Dashboard Diagnosis Report`\n\nReactDOM.render(\n  <DiagnosisReport diagnosisTables={refineDiagnosisData()} />,\n  document.getElementById('root')\n)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/diagnoseReportApp/react-app-env.d.ts",
    "content": "/// <reference types=\"react-scripts\" />\n\n// https://stackoverflow.com/questions/12709074/how-do-you-explicitly-set-a-new-property-on-window-in-typescript\ninterface Window {\n  __diagnosis_data__: any\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/diagnoseReportApp/translations/en.yaml",
    "content": "diagnosis:\n  title: '{{distro.tidb}} Cluster System Report'\n  expand_all: Expand All\n  fold_all: Collapse All\n  expand: Expand\n  fold: Collapse\n  all_tables: Report Overview\n  tables:\n    category:\n      header: Basic Info\n      diagnose: Diagnose\n      load: Load Info\n      overview: Component Info\n      TiDB: '{{distro.tidb}} Component'\n      PD: '{{distro.pd}} Component'\n      TiKV: '{{distro.tikv}} Component'\n      config: Configuration Info\n      error: Error Info\n    title:\n      compare_diagnose: Diagnostics Comparison\n      compare_report_time_range: Comparison Time Range\n      top_10_slow_query_in_time_range_t1: Top 10 Slow Queries in Time Range t1\n      top_10_slow_query_in_time_range_t2: top 10 Slow Queries in Time Range t2\n      top_10_slow_query_group_by_digest_in_time_range_t1: Top 10 Slow Queries Group by Digest in Time Range t1\n      top_10_slow_query_group_by_digest_in_time_range_t2: Top 10 slow queries group by digest in Time Range t2\n      slow_query_with_diff_plan in_in_time_range_t1: Slow Queries with Different Plan in Time Range t1\n      slow_query_with_diff_plan in_in_time_range_t2: Slow queries with Different Plan in Time Range t2\n      diagnose_in_time_range_t1: Diagnostics in Time Range t1\n      diagnose_in_time_range_t2: Diagnostics in Time Range t2\n      max_diff_item: Maximum Different Item\n      slow_query_t2: Slow Queries In Time Range t2\n      generate_report_error: Report Generation Error\n      report_time_range: Report Time Range\n      diagnose: Diagnosis Result\n      total_time_consume: Time Consumed by Each Component\n      total_error: Errors Occurred in Each Component\n      time_consume: Time Consumed\n      tidb_time_consume: Time Consumed by {{distro.tidb}} Component\n      transaction: '{{distro.tidb}} Transaction'\n      tidb_connection_count: '{{distro.tidb}} Server Connections'\n      statistics_info: Statistics Info\n      ddl_owner: DDL Owner\n      scheduler_initial_config: Scheduler Initial Config\n      scheduler_change_config: Scheduler Config Change History\n      tidb_gc_initial_config: '{{distro.tidb}} GC Initial Config'\n      tidb_gc_change_config: '{{distro.tidb}} GC Config Change History'\n      tikv_rocksdb_initial_config: '{{distro.tikv}} RocksDB Initial Config'\n      tikv_rocksdb_change_config: '{{distro.tikv}} RocksDB Config Change History'\n      tikv_raftstore_initial_config: '{{distro.tikv}} RaftStore Initial Config'\n      tikv_raftstore_change_config: '{{distro.tikv}} RaftStore Config Change History'\n      pd_time_consume: Time Consumed by {{distro.pd}} Component\n      balance_leader_region: Scheduled Leader/Region Count\n      approximate_region_size: Approximate Region Size\n      tikv_engine_size: '{{distro.tikv}} Engine Size'\n      tikv_time_consume: Time Consumed by {{distro.tikv}} Component\n      scheduler_info: Scheduler Info\n      gc_info: GC Info\n      task_info: Task Info\n      snapshot_info: Snapshot Info\n      coprocessor_info: Coprocessor Info\n      raft_info: Raft Info\n      tikv_error: '{{distro.tikv}} Error'\n      tidb_current_config: \"{{distro.tidb}}'s Current Config\"\n      pd_current_config: \"{{distro.pd}}'s Current Config\"\n      tikv_current_config: \"{{distro.tikv}}'s Current Config\"\n      node_load_info: Server Load Info\n      process_cpu_usage: Instance CPU Usage\n      process_memory_usage: Instance Memory Usage\n      tidb/pd_goroutines_count: '{{distro.tidb}}/{{distro.pd}} Goroutines Count'\n      tikv_thread_cpu_usage: '{{distro.tikv}} Thread CPU Usage'\n      store_status: Store Status\n      cluster_status: Cluster Status\n      etcd_status: etcd Status\n      cluster_info: Cluster Topology Info\n      cache_hit: Cache Hit\n      cluster_hardware: Cluster Hardware Info\n      rocksdb_time_consume: Time Consumed by RocksDB\n      top_10_slow_query: Top 10 Slow Queries\n      top_10_slow_query_group_by_digest: Top 10 Slow Queries Group By Digest\n      slow_query_with_diff_plan: Slow Queries with Different Plan\n    comment:\n      compare_diagnose: Automatically diagnose the cluster problem by comparing with the reference time.\n      max_diff_item: The maximum different metrics between two time ranges.\n      diagnose: Automatically diagnose the cluster problem and record the problem in the table below.\n      total_time_consume: This table contains the event time consumed in {{distro.tidb}}/{{distro.tikv}}/{{distro.pd}}. METRIC_NAME is the event name; LABEL is the event label, such as instance, event type, etc; TIME_RATIO is the TOTAL_TIME of this event divided by the TOTAL_TIME of the events whose TIME_RATIO is 1; TOTAL_TIME is the total time cost of this event; TOTAL_COUNT is the total count of this event; P999 is the max time of 0.999 quantile; P99 is the max time of 0.99 quantile; P90 is the max time of 0.90 quantile; P80 is the max time of 0.80 quantile.\n      total_error: This table contains the total count of each error event. METRIC_NAME is the error event name; LABEL is the event label, such as instance, event type, etc; TOTAL_COUNT is the total count of this event.\n      tidb_time_consume: This table contains the event time consumed in {{distro.tidb}}. METRIC_NAME is the event name; LABEL is the event label, such as instance, event type, etc; TIME_RATIO is the TOTAL_TIME of this event divided by the TOTAL_TIME of the events whose TIME_RATIO is 1; TOTAL_TIME is the total time cost of this event; TOTAL_COUNT is the total count of this event; P999 is the max time of 0.999 quantile; P99 is the max time of 0.99 quantile; P90 is the max time of 0.90 quantile; P80 is the max time of 0.80 quantile.\n      transaction: This table contains the {{distro.tidb}} transaction statistics information. METRIC_NAME is the object name; LABEL is the object label, such as instance, event type, etc; TOTAL_VALUE is the total size/value of this object; TOTAL_COUNT is the total count of this object; P999 is the max size/value of 0.999 quantile; P99 is the max size/value of 0.99 quantile; P90 is the max size/value of 0.90 quantile; P80 is the max size/value of 0.80 quantile.\n      tidb_connection_count: The number of connections of each {{distro.tidb}} server.\n      ddl_owner: This table contains the DDL Owner info. Note that if no DDL request has been executed, the Owner info maybe null below, but it doesn't indicate that no DDL Owner exists.\n      scheduler_initial_config: The initial config value of {{distro.pd}} scheduler. The initial time is the start time of this report.\n      scheduler_change_config: The config change history of {{distro.pd}} scheduler. APPROXIMATE_CHANGE_TIME is the minimum start effective time.\n      tidb_gc_initial_config: The initial config value of {{distro.tidb}} GC. The initial time is the start time of this report.\n      tidb_gc_change_config: The config change history of {{distro.tidb}} GC. APPROXIMATE_CHANGE_TIME is the minimum start effective time.\n      tikv_rocksdb_initial_config: The initial config value of {{distro.tikv}} RocksDB. The initial time is the start time of this report.\n      tikv_rocksdb_change_config: The config change history of {{distro.tikv}} RocksDB. APPROXIMATE_CHANGE_TIME is the minimum start effective time.\n      tikv_raftstore_initial_config: The initial config value of {{distro.tikv}} RaftStore. The initial time is the start time of this report.\n      tikv_raftstore_change_config: The config change history of {{distro.tikv}} RaftStore. APPROXIMATE_CHANGE_TIME is the minimum start effective time.\n      pd_time_consume: This table contains the event time consumed in {{distro.pd}}. METRIC_NAME is the event name; LABEL is the event label, such as instance, event type, etc; TIME_RATIO is the TOTAL_TIME of this event divided by the TOTAL_TIME of the events whose TIME_RATIO is 1; TOTAL_TIME is the total time cost of this event; TOTAL_COUNT is the total count of this event; P999 is the max time of 0.999 quantile; P99 is the max time of 0.99 quantile; P90 is the max time of 0.90 quantile; P80 is the max time of 0.80 quantile.\n      tikv_time_consume: This table contains the event time consumed in {{distro.tikv}}. METRIC_NAME is the event name; LABEL is the event label, such as instance, event type, etc; TIME_RATIO is the TOTAL_TIME of this event divided by the TOTAL_TIME of the events whose TIME_RATIO is 1; TOTAL_TIME is the total time cost of this event; TOTAL_COUNT is the total count of this event; P999 is the max time of 0.999 quantile; P99 is the max time of 0.99 quantile; P90 is the max time of 0.90 quantile; P80 is the max time of 0.80 quantile.\n    table:\n      name:\n        tidb_transaction: Transaction\n        tidb_kv_request: KV request\n        tidb_slow_query: Slow query\n        tidb_ddl_handle_job: DDL job\n        tidb_ddl_batch_add_index: Batch add index\n        tidb_load_schema: Schema load\n        tidb_meta_operation: '{{distro.tidb}} meta operation'\n        tidb_auto_id_request: '{{distro.tidb}} auto ID request'\n        tidb_statistics_auto_analyze: '{{distro.tidb}} auto analyze'\n        tidb_gc: '{{distro.tidb}} GC'\n        pd_client_cmd: '{{distro.pd}} client cmd'\n        pd_handle_request: '{{distro.pd}} request'\n        pd_handle_transactions: etcd transactions\n        tikv_cop_request: Coprocessor request\n        tikv_cop_handle: Coprocessor handling request\n        tikv_handle_snapshot: Snapshot handling\n        tikv_send_snapshot: Snapshot sending\n        tikv_commit_log: Raft commit log\n        tidb_transaction_retry_num: '{{distro.tidb}} transaction retry'\n        tidb_txn_region_num: Transaction Region count\n        tidb_txn_kv_write_num: Transaction KV write count\n        tidb_txn_kv_write_size: Transaction KV write size\n        tidb_load_safepoint_total_num: Safepoint load\n        tikv_scheduler_stage_total_num: Scheduler stage\n        tikv_worker_handled_tasks_total_num: '{{distro.tikv}} worker handled tasks'\n        tikv_worker_pending_tasks_total_num: '{{distro.tikv}} worker pending tasks'\n        tikv_futurepool_handled_tasks_total_num: future_pool handled tasks\n        tikv_futurepool_pending_tasks_total_num: future_pool pending tasks\n        tikv_snapshot_kv_count: Snapshot KV\n        tikv_snapshot_size: Snapshot size\n        tikv_cop_scan_keys_num: '{{distro.tikv}} Coprocessor scan keys'\n        tikv_cop_total_response_total_size: '{{distro.tikv}} Coprocessor response'\n        tikv_cop_scan_num: '{{distro.tikv}} Coprocessor scan'\n        tikv_raft_sent_messages_total_num: Raft sent messages\n        tikv_flush_messages_total_num: Raft flush messages\n        tikv_receive_messages_total_num: Raft receive messages\n        tikv_raft_dropped_messages_total: Raft dropped messages\n        tikv_raft_proposals_total_num: Raft proposals\n        tikv_grpc_error_total_count: gRPC errors\n        tikv_critical_error_total_count: '{{distro.tikv}} critical errors'\n        tikv_coprocessor_request_error_total_count: Coprocessor request errors\n        node_disk_write_latency: Disk write latency\n        node_disk_read_latency: Disk read latency\n        sched_worker: Scheduler worker\n        tikv_memtable_hit: memtable hit\n        tikv_block_all_cache_hit: All block cache hit\n        tikv_block_index_cache_hit: Index block cache hit\n        tikv_block_filter_cache_hit: Filter block cache hit\n        tikv_block_data_cache_hit: Data block cache hit\n        tikv_block_bloom_prefix_cache_hit: Bloom prefix block cache hit\n      comment:\n        tidb_query: The time cost of SQL queries. The label is [sql_type].\n        tidb_get_token(us): The time cost of a session getting token to execute the SQL query. The label is [instance].\n        tidb_parse: The time cost of parsing SQL queries. The label is [sql_type].\n        tidb_compile: The time cost of building the query plan. The label is [sql_type].\n        tidb_execute: The time cost of executing the SQL query, which does not include the time to get the results of the query. The label is [sql_type].\n        tidb_distsql_execution: The time cost of distsql execution. The label is [type].\n        tidb_cop: The processing time of KV storage Coprocessor. The label is [instance].\n        tidb_transaction: The time cost of a transaction executing durations, including retry. The label is [sql_type].\n        tidb_transaction_local_latch_wait: The time cost of waiting for local latch. The label is [instance].\n        tidb_kv_backoff: The time cost of {{distro.tidb}} transaction latch waiting for key value storage. The label is [type].\n        tidb_kv_request: The time cost of KV requests durations. The label is [type].\n        tidb_slow_query: The time cost of {{distro.tidb}} slow queries. The label is [instance].\n        tidb_slow_query_cop_process: The total Coprocessor processing time of {{distro.tidb}} slow queries. The label is [instance].\n        tidb_slow_query_cop_wait: The total Coprocessor waiting time of {{distro.tidb}} slow queries. The label is [instance].\n        tidb_ddl_handle_job: The time cost of processing {{distro.tidb}} DDL jobs. The label is [type].\n        tidb_ddl_worker: The time cost of DDL worker handling jobs. The label is [action].\n        tidb_ddl_update_self_version: The time cost of updating {{distro.tidb}} schema syncer version. The label is [result].\n        tidb_owner_handle_syncer: The time cost of {{distro.tidb}} DDL owner operations on etcd. The label is [type].\n        tidb_ddl_batch_add_index: The time cost of {{distro.tidb}} batch adding index. The label is [type].\n        tidb_ddl_deploy_syncer: The time cost of {{distro.tidb}} DDL schema syncer statistics, including init, start, watch, and clear. The label is [type].\n        tidb_load_schema: The time cost of {{distro.tidb}} loading schema. The label is [type].\n        tidb_meta_operation: The time cost of {{distro.tidb}} meta operations, including get/set schema and DDL jobs. The label is [instance].\n        tidb_auto_id_request: The time cost of handling requests for {{distro.tidb}} auto ID. The label is [type].\n        tidb_statistics_auto_analyze: The time cost of {{distro.tidb}} auto analyze. The label is [type].\n        tidb_gc: The time cost of KV storage garbage collection. The label is [instance].\n        tidb_gc_push_task: The time cost of KV storage range worker processing one task. The label is [instance].\n        tidb_batch_client_unavailable: The time cost of KV storage batch processing unavailable. The label is [type].\n        tidb_batch_client_wait: The time cost of {{distro.tidb}} KV storage batch processing client requests that are waiting. The label is [instance].\n        pd_start_tso_wait: The time cost of waiting for the start timestamp oracle. The label is [instance].\n        pd_tso_rpc: The time cost from sending TSO request to receiving the response. The label is [instance].\n        pd_tso_wait: The time cost from the client starting to wait for the timestamp to receiving the timestamp. The label is [instance].\n        pd_client_cmd: The time cost of {{distro.pd}} client command. The label is [type].\n        pd_handle_request: The time cost of {{distro.pd}} handling request. The label is [type].\n        pd_grpc_completed_commands: The time cost of {{distro.pd}} completing each kind of gRPC commands. The label is [grpc_method].\n        pd_operator_finish: The time cost of {{distro.pd}} completing each kind of scheduling commands. The label is [type].\n        pd_operator_step_finish: The time cost of {{distro.pd}} completing operating steps. The label is [type].\n        pd_handle_transactions: The time cost of {{distro.pd}} handling etcd transactions. The label is [result].\n        pd_region_heartbeat: The time cost of heartbeats in each {{distro.tikv}} instance. The label is [address].\n        etcd_wal_fsync: The time cost of etcd writing WAL into the persistent storage. The label is [instance].\n        pd_peer_round_trip: The latency of the network. The label is [To].\n        tikv_grpc_message: The time cost of handling {{distro.tikv}} gRPC messages. The label is [type].\n        tikv_cop_request: The time cost of Coprocessor handling read requests. The label is [req].\n        tikv_cop_handle: The time cost of handling Coprocessor requests. The label is [req].\n        tikv_cop_wait: The time cost of Coprocessor requests that wait for being handled. The label is [req].\n        tikv_scheduler_command: The time cost of executing commit command. The label is [type].\n        tikv_scheduler_latch_wait: The waiting time of {{distro.tikv}} latch in commit command. The label is [type].\n        tikv_handle_snapshot: The time cost of handling snapshots. The label is [type].\n        tikv_send_snapshot: The time cost of sending snapshots. The label is [instance].\n        tikv_storage_async_request: The time cost of processing asynchronous snapshot requests. The label is [type].\n        tikv_raft_append_log: The time cost of Raft appends log. The label is [instance].\n        tikv_raft_apply_log: The time cost of Raft apply log. The label is [instance].\n        tikv_raft_apply_wait: The time cost of Raft apply wait. The label is [instance].\n        tikv_raft_process: The time cost of peer processes in Raft. The label is [instance].\n        tikv_raft_propose_wait: The waiting time of each proposal. The label is [type].\n        tikv_raft_store_events: The time cost of raftstore events. The label is [type].\n        tikv_commit_log: The time cost of Raft commits log. The label is [instance].\n        tikv_check_split: The time cost of running split check. The label is [instance].\n        tikv_ingest_sst: The time cost of ingesting SST files. The label is [instance].\n        tikv_gc_tasks: The time cost of executing GC tasks. The label is [task].\n        tikv_pd_request: The time cost of {{distro.tikv}} sending requests to {{distro.pd}}. The label is [type].\n        tikv_lock_manager_deadlock_detect:\n        tikv_lock_manager_waiter_lifetime:\n        tikv_backup_range:\n        tikv_backup:\n        tidb_transaction_retry_num: '{{distro.tidb}} transaction retry count. The label is [instance].'\n        tidb_transaction_statement_num: The total number of {{distro.tidb}} statements within a transaction. Internal means the internal transaction of {{distro.tidb}}. The label is [sql_type].\n        tidb_txn_region_num: The number of Regions that each transaction operates. The label is [instance].\n        tidb_txn_kv_write_num: The number of KV writes per transaction execution. The label is [instance].\n        tidb_txn_kv_write_size: The KV write size per transaction execution. The label is [instance].\n        tidb_load_safepoint_total_num: The total count of safe point loading. The label is [instance].\n        tidb_lock_resolver_total_num: The total count of lock resolve. The label is [instance].\n        pseudo_estimation_total_count: The total count of {{distro.tidb}} Optimizer using pseudo estimation. The label is [instance, type].\n        dump_feedback_total_count: The total count of operations that {{distro.tidb}} dumping statistics back to KV storage. The label is [instance, type].\n        store_query_feedback_total_count: The total count of {{distro.tidb}} store querying feedback. The label is [instance, type].\n        update_stats_total_count: The total count of {{distro.tidb}} updating statistics using feed back. The label is [instance].\n        balance-leader-in: balance-leader-in is the total count of Leader moving into the {{distro.tikv}} store. The label is [address].\n        balance-leader-out: balance-leader-out is the total count of Leader moving out of the {{distro.tikv}} store. The label is [address].\n        balance-region-in: balance-region-in is the total count of Regions moving into the {{distro.tikv}} store. The label is [address].\n        balance-region-out: balance-region-in is the total count of Regions moving into the {{distro.tikv}} store. The label is [address].\n        Approximate Region size: The approximate Region size. The label is [instance].\n        store size: The storage size. The label is [instance, type].\n        tikv_scheduler_keys_read: The number of keys read by a command. The label is [instance, type].\n        tikv_scheduler_keys_written: The number of keys written by a command. The label is [instance, type].\n        tikv_scheduler_scan_details_total_num: The keys scan details of each CF when executing a command. The label is [instance,req,tag].\n        tikv_scheduler_stage_total_num: The total number of scheduler states. The label is [instance,type,stage].\n        tikv_gc_keys_total_num: The total number of keys in CF affected during GC. The label is [instance,cf,tag].\n        tidb_gc_worker_action_total_num: The total count of KV storage garbage collection. The label is [instance,type].\n        tikv_worker_handled_tasks_total_num: The total number of tasks handled by worker. The label is [instance,name].\n        tikv_worker_pending_tasks_total_num: The total number of pending and running tasks of worker. The label is [instance,name].\n        tikv_futurepool_handled_tasks_total_num: The total number of tasks handled by future_pool. The label is [instance,name].\n        tikv_futurepool_pending_tasks_total_num: The total number of pending and running tasks of future_pool. The label is [instance,name].\n        tikv_snapshot_kv_count: tikv_snapshot_kv_count. The label is [instance].\n        tikv_snapshot_size: The number of KV pairs within a snapshot. The label is [instance].\n        tikv_snapshot_state_total_count: tikv_snapshot_size. The label is [instance,type].\n        tikv_cop_scan_keys_num: The total number of {{distro.tikv}} Coprocessor scan keys. The label is [instance,req].\n        tikv_cop_total_response_total_size: '{{distro.tikv}} coprocessor response total size. The label is [instance].'\n        tikv_cop_scan_num: The total number of {{distro.tikv}} coprocessor scan operations. The label is [instance,req,tag,cf].\n        tikv_raft_sent_messages_total_num: The total number of sent Raft messages. The label is [instance,type].\n        tikv_flush_messages_total_num: The total number of flushed Raft messages. The label is [instance].\n        tikv_receive_messages_total_num: The total number of received Raft messages. The label is [instance].\n        tikv_raft_dropped_messages_total: The total number of dropped Raft messages. The label is [instance,type].\n        tikv_raft_proposals_total_num: The total number of raft proposals. The label is [instance,type].\n        tikv_grpc_error_total_count: The total number of the gRPC message failures. The label is [instance,type].\n        tikv_critical_error_total_count: The total number of the {{distro.tikv}} critical errors. The label is [instance,type].\n        tikv_scheduler_is_busy_total_count: The total number of Scheduler Busy events that make the {{distro.tikv}} instance temporarily unavailable. The label is [instance,db,type,stage].\n        tikv_channel_full_total_count: The total number of channel full errors, which will make the {{distro.tikv}} instance temporarily unavailable. The label is [instance,db,type].\n        tikv_coprocessor_request_error_total_count: The total number of Coprocessor errors. The label is [instance,reason].\n        tikv_engine_write_stall: Indicates occurrences of Write Stall events that make the {{distro.tikv}} instance temporarily unavailable. The label is [instance,db].\n        tikv_server_report_failures_total_count: The total number of reported failure messages. The label is [instance].\n        tikv_storage_async_request_error: The total number of storage request errors. The label is [instance,status,type].\n        tikv_lock_manager_detect_error_total_count: The total number of {{distro.tikv}} lock manager detect error. The label is [instance,type].\n        tikv_backup_errors_total_count: The total number of {{distro.tikv}} lock manager detected errors. The label is [instance,error].\n        node_disk_write_latency: The disk write latency in each node. The label is [instance,device].\n        node_disk_read_latency: The disk read latency in each node. The label is [instance,device].\n        grpc: The CPU utilization of each {{distro.tikv}} gRPC. The label is [instance].\n        raftstore: The CPU utilization of {{distro.tikv}} raftstore thread. The label is [instance].\n        Async apply: The CPU utilization of {{distro.tikv}} async apply thread. The label is [instance].\n        sched_worker: The CPU utilization of {{distro.tikv}} scheduler worker thread. The label is [instance].\n        snapshot: The CPU utilization of {{distro.tikv}} snapshot. The label is [instance].\n        unified read pool: The CPU utilization of {{distro.tikv}} unified read pool thread. The label is [instance].\n        storage read pool: The CPU utilization of {{distro.tikv}} storage read pool thread. The label is [instance].\n        storage read pool normal: The CPU utilization of {{distro.tikv}} storage read pool normal thread. The label is [instance].\n        storage read pool high: The CPU utilization of {{distro.tikv}} storage read pool high thread. The label is [instance].\n        storage read pool low: The CPU utilization of {{distro.tikv}} storage read pool low thread. The label is [instance].\n        cop: The CPU utilization of {{distro.tikv}} Coprocessor. The label is [instance].\n        cop normal: The CPU utilization of {{distro.tikv}} Coprocessor normal thread. The label is [instance].\n        cop high: The CPU utilization of {{distro.tikv}} Coprocessor high thread. The label is [instance].\n        cop low: The CPU utilization of {{distro.tikv}} Coprocessor low thread. The label is [instance].\n        rocksdb: The CPU utilization {{distro.tikv}} RocksDB. The label is [instance].\n        gc: The CPU utilization of {{distro.tikv}} GC. The label is [instance].\n        split_check: The CPU utilization of {{distro.tikv}} split_check. The label is [instance].\n        region_score: The Region score of store. The label is [address].\n        leader_score: The Leader score of store. The label is [address].\n        region_count: The Region count of store. The label is [address].\n        leader_count: The Leader score of store. The label is [address].\n        region_size: The Region size of store. The label is [address].\n        leader_size: The Leader size of store. The label is [address].\n        tikv_memtable_hit: The hit rate of memtable. The label is [instance].\n        tikv_block_all_cache_hit: The hit rate of all block cache. The label is [instance].\n        tikv_block_index_cache_hit: The hit rate of index block cache. The label is [instance].\n        tikv_block_filter_cache_hit: The hit rate of filter block cache. The label is [instance].\n        tikv_block_data_cache_hit: The hit rate of data block cache. The label is [instance].\n        tikv_block_bloom_prefix_cache_hit: The hit rate of bloom_prefix block cache. The label is [instance].\n        get duration: The time consumed when RocksDB executing get operations. The label is [instance].\n        seek duration: The time consumed when RocksDB executing seek operations. The label is [instance].\n        write duration: The time consumed when RocksDB executing write operations. The label is [instance].\n        WAL sync duration: The time consumed when RocksDB executing WAL sync operations. The label is [instance].\n        compaction duration: The time consumed when RocksDB executing compaction operations. The label is [instance].\n        SST read duration: The time consumed when RocksDB reading SST files. The label is [instance].\n        write stall duration: The time cost of write stall. The label is [instance].\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/diagnoseReportApp/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/diagnoseReportApp/translations/zh.yaml",
    "content": "diagnosis:\n  title: '{{distro.tidb}} 集群系统报告'\n  expand_all: 展开所有\n  fold_all: 收起所有\n  expand: 展开\n  fold: 收起\n  all_tables: 报告信息总览\n  tables:\n    category:\n      header: 基本信息\n      diagnose: 诊断\n      load: 负载\n      overview: 各组件信息总览\n      TiDB: '{{distro.tidb}} 组件'\n      PD: '{{distro.pd}} 组件'\n      TiKV: '{{distro.tikv}} 组件'\n      config: 配置\n      error: 错误\n    title:\n      compare_diagnose: 诊断对比\n      compare_report_time_range: 对比报告区间\n      top_10_slow_query_in_time_range_t1: t1 中的 Top 10 慢查询\n      top_10_slow_query_in_time_range_t2: t2 中的 Top 10 慢查询\n      top_10_slow_query_group_by_digest_in_time_range_t1: 按 SQL 指纹聚合的 t1 Top 10 慢查询\n      top_10_slow_query_group_by_digest_in_time_range_t2: 按 SQL 指纹聚合的 t2 Top 10 慢查询\n      slow_query_with_diff_plan_in_time_range_t1: t1 中的不同执行计划的慢查询\n      slow_query_with_diff_plan_in_time_range_t2: t2 中的不同执行计划的慢查询\n      diagnose_in_time_range_t1: t1 中的诊断信息\n      diagnose_in_time_range_t2: t2 中的诊断信息\n      max_diff_item: 最大不同项\n      slow_query_t2: t2 中的慢查询\n      generate_report_error: 生成报告的报错\n      report_time_range: 报告区间\n      diagnose: 诊断结果\n      total_time_consume: 各组件总耗时\n      total_error: 各组件总报错数\n      time_consume: 耗时\n      tidb_time_consume: '{{distro.tidb}} 中事件耗时'\n      transaction: '{{distro.tidb}} 事务'\n      tidb_connection_count: '{{distro.tidb}} 连接数'\n      statistics_info: 统计信息\n      ddl_owner: DDL Owner\n      scheduler_initial_config: 调度器初始配置\n      scheduler_change_config: 调度器配置修改历史\n      tidb_gc_initial_config: '{{distro.tidb}} GC 初始配置'\n      tidb_gc_change_config: '{{distro.tidb}} GC 配置修改历史'\n      tikv_rocksdb_initial_config: '{{distro.tikv}} RocksDB 初始配置'\n      tikv_rocksdb_change_config: '{{distro.tikv}} RocksDB 配置修改历史'\n      tikv_raftstore_initial_config: '{{distro.tikv}} RaftStore 初始配置'\n      tikv_raftstore_change_config: '{{distro.tikv}} RaftStore 配置修改历史'\n      pd_time_consume: '{{distro.pd}} 中事件耗时'\n      balance_leader_region: Leader/Region 调度数\n      approximate_region_size: Approximate Region 大小\n      tikv_engine_size: '{{distro.tikv}} 实例存储大小'\n      tikv_time_consume: '{{distro.tikv}} 中事件耗时'\n      scheduler_info: '{{distro.tikv}} 调度器信息'\n      gc_info: GC 信息\n      task_info: '{{distro.tikv}} 任务信息'\n      snapshot_info: '{{distro.tikv}} 快照信息'\n      coprocessor_info: Coprocessor 信息\n      raft_info: Raft 信息\n      tikv_error: '{{distro.tikv}} 错误'\n      tidb_current_config: '{{distro.tidb}} 当前配置'\n      pd_current_config: '{{distro.pd}} 当前配置'\n      tikv_current_config: '{{distro.tikv}} 当前配置'\n      node_load_info: 服务器负载信息\n      process_cpu_usage: 各实例 CPU 使用率\n      process_memory_usage: 各实例内存消耗\n      tidb/pd_goroutines_count: '{{distro.tidb}}/{{distro.pd}} 的 Goroutines 数量'\n      tikv_thread_cpu_usage: '{{distro.tikv}} 的 CPU 使用情况'\n      store_status: '{{distro.tikv}} 节点的存储状态'\n      cluster_status: 集群状态\n      etcd_status: etcd 状态\n      cluster_info: 集群拓扑信息\n      cache_hit: 缓存命中率\n      cluster_hardware: 集群硬件信息\n      rocksdb_time_consume: RocksDB 事件耗时\n      top_10_slow_query: Top 10 慢查询\n      top_10_slow_query_group_by_digest: 按 SQL 指纹聚合的 Top 10 慢查询\n      slow_query_with_diff_plan: 不同执行计划的慢查询\n    comment:\n      compare_diagnose: 通过与参考时间的比较，自动诊断集群问题。\n      max_diff_item: 两段时间中的最大不同项。\n      diagnose: 该表显示的是自动诊断的结果，即集群中出现的问题。\n      total_time_consume: 该表显示的是 {{distro.tidb}}/{{distro.tikv}}/{{distro.pd}} 组件中各事件的耗时。METRIC_NAME 是事件名称；LABEL 是事件标签，如实例、事件类型等；TIME_RATIO 是该事件的总时间除以 TIME_RATIO 为 1 的事件的总时间；TOTAL_TIME 是该事件的总耗时；TOTAL_COUNT 是该事件的总计数；P999 是 0.999 分位数的最大时间；P99 是 0.99 分位数的最大时间；P90 是 0.90 分位数的最大时间；P80 是 0.80 分位数的最大时间。\n      total_error: 该表显示的是各错误事件的数量。METRIC_NAME 是错误事件名称；LABEL 是事件标签，如实例、事件类型；TOTAL_COUNT 是该错误事件的总数。\n      tidb_time_consume: 该表显示的是 {{distro.tidb}} 组件中各事件的耗时。METRIC_NAME 是事件名称；LABEL 是事件标签，如实例、事件类型等；TIME_RATIO 是该事件的总时间除以 TIME_RATIO 为 1 的事件的总时间；TOTAL_TIME 是该事件的总耗时；TOTAL_COUNT 是该事件的总计数；P999 是 0.999 分位数的最大时间；P99 是 0.99 分位数的最大时间；P90 是 0.90 分位数的最大时间；P80 是 0.80 分位数的最大时间。\n      transaction: 该表显示了 {{distro.tidb}} 事务的统计信息。METRIC_NAME 是对象名；LABEL 是对象标签，如实例、事件类型等；TOTAL_VALUE 是该对象的总大小；TOTAL_COUNT 是该对象的总计数；P999 为 0.999 分位数的最大值；P99 是 0.99 分位数的最大值；P90 是 0.90 分位数的最大值；P80 是 0.80 分位数的最大值。\n      tidb_connection_count: '{{distro.tidb}} 服务器的连接数。'\n      ddl_owner: DDL Owner 的信息。注意：如果没有 DDL 请求被执行，下面的 Owner 信息可能为空，这并不表示 DDL Owner 不存在。\n      scheduler_initial_config: '{{distro.pd}} 调度器的初始配置值。初始时间是报表的开始时间。'\n      scheduler_change_config: '{{distro.pd}} 调度器的配置更改历史。APPROXIMATE_CHANGE_TIME 为最近的有效更改时间。'\n      tidb_gc_initial_config: '{{distro.tidb}} GC 的初始配置值。初始时间是报表的开始时间。'\n      tidb_gc_change_config: '{{distro.tidb}} GC 的配置更改历史。APPROXIMATE_CHANGE_TIME 为最近的有效更改时间。'\n      tikv_rocksdb_initial_config: '{{distro.tikv}} RocksDB 的初始配置值。初始时间是报表的开始时间。'\n      tikv_rocksdb_change_config: '{{distro.tikv}} RocksDB 的配置更改历史。APPROXIMATE_CHANGE_TIME 为最近的有效更改时间。'\n      tikv_raftstore_initial_config: '{{distro.tikv}} RaftStore 的初始配置值。初始时间是报表的开始时间。'\n      tikv_raftstore_change_config: '{{distro.tikv}} RaftStore 的配置更改历史。APPROXIMATE_CHANGE_TIME 为最近的有效更改时间。'\n      pd_time_consume: 该表显示的是 {{distro.pd}} 组件中各事件的耗时。METRIC_NAME 是事件名称；LABEL 是事件标签，如实例、事件类型等；TIME_RATIO 是该事件的总时间除以 TIME_RATIO 为 1 的事件的总时间；TOTAL_TIME 是该事件的总耗时；TOTAL_COUNT 是该事件的总计数；P999 是 0.999 分位数的最大时间；P99 是 0.99 分位数的最大时间；P90 是 0.90 分位数的最大时间；P80 是 0.80 分位数的最大时间。\n      tikv_time_consume: 该表显示的是 {{distro.tikv}} 组件中各事件的耗时。METRIC_NAME 是事件名称；LABEL 是事件标签，如实例、事件类型等；TIME_RATIO 是该事件的总时间除以 TIME_RATIO 为 1 的事件的总时间；TOTAL_TIME 是该事件的总耗时；TOTAL_COUNT 是该事件的总计数；P999 是 0.999 分位数的最大时间；P99 是 0.99 分位数的最大时间；P90 是 0.90 分位数的最大时间；P80 是 0.80 分位数的最大时间。\n    table:\n      name:\n        tidb_transaction: '{{distro.tidb}} 事务'\n        tidb_kv_request: '{{distro.tidb}} KV 请求'\n        tidb_slow_query: 慢查询\n        tidb_ddl_handle_job: DDL 任务\n        tidb_ddl_batch_add_index: 批量索引添加\n        tidb_load_schema: Schema 加载\n        tidb_meta_operation: '{{distro.tidb}} 元操作'\n        tidb_auto_id_request: '{{distro.tidb}} 自增 ID 请求'\n        tidb_statistics_auto_analyze: '{{distro.tidb}} 自动分析'\n        tidb_gc: 垃圾回收\n        pd_client_cmd: '{{distro.pd}} 客户端命令'\n        pd_handle_request: '{{distro.pd}} 请求'\n        pd_handle_transactions: etcd 事务\n        pd_peer_round_trip: 网络延迟\n        tikv_cop_request: Coprocessor 读请求\n        tikv_cop_handle: Coprocessor 请求\n        tikv_handle_snapshot: 快照处理\n        tikv_send_snapshot: 快照发送\n        tikv_commit_log: Raft 提交日志\n        tidb_transaction_retry_num: '{{distro.tidb}} 事务重试数'\n        tidb_txn_region_num: 事务操作的 Region 数量\n        tidb_txn_kv_write_num: 事务执行的 KV 写入数量\n        tidb_txn_kv_write_size: 事务执行的 KV 写入大小\n        tidb_load_safepoint_total_num: 安全点装载总数量\n        tikv_scheduler_stage_total_num: 调度程序状态的总数量\n        tikv_worker_handled_tasks_total_num: worker 处理的任务总数量\n        tikv_worker_pending_tasks_total_num: 工作进程的挂起和运行任务的总数量\n        tikv_futurepool_handled_tasks_total_num: future_pool 处理的任务总数量\n        tikv_futurepool_pending_tasks_total_num: future_pool 总挂起和运行任务数量\n        tikv_snapshot_kv_count: 快照的 KV 数量\n        tikv_snapshot_size: 快照大小\n        tikv_cop_scan_keys_num: '{{distro.tikv}} Coprocessor 扫描键总数量'\n        tikv_cop_total_response_total_size: '{{distro.tikv}} Coprocessor 响应总大小'\n        tikv_cop_scan_num: '{{distro.tikv}} Coprocessor 扫描操作总数量'\n        tikv_raft_sent_messages_total_num: 发送的 Raft 消息的总数量\n        tikv_flush_messages_total_num: 持久化 Raft 消息的总数量\n        tikv_receive_messages_total_num: 接受 Raft 消息的总数量\n        tikv_raft_dropped_messages_total: 丢弃 Raft 消息的总数量\n        tikv_raft_proposals_total_num: Raft proposal 的总数量\n        tikv_grpc_error_total_count: gRPC 消息失败的总数量\n        tikv_critical_error_total_count: '{{distro.tikv}} 临界误差的总数量'\n        tikv_coprocessor_request_error_total_count: Coprocessor 错误总数量\n        node_disk_write_latency: 磁盘写延迟\n        node_disk_read_latency: 磁盘读取延迟\n        sched_worker: 调度器工作线程\n        tikv_memtable_hit: memtable 命中率\n        tikv_block_all_cache_hit: 所有块缓存命中率\n        tikv_block_index_cache_hit: 索引块缓存命中率\n        tikv_block_filter_cache_hit: 过滤块缓存命中率\n        tikv_block_data_cache_hit: 数据块缓存命中率\n        tikv_block_bloom_prefix_cache_hit: bloom_prefix 块缓存命中率\n      comment:\n        tidb_query: SQL 查询耗时，标签是\"SQL 类型\"。\n        tidb_get_token(us): 会话获取令牌以执行 SQL 查询的耗时，标签是\"实例\"。\n        tidb_parse: 解析 SQL 的耗时，标签是\"SQL 类型\"。\n        tidb_compile: 构建查询计划的时间，标签是\"SQL 类型\"。\n        tidb_execute: 执行 SQL 的时间，不包括获得查询结果的时间，标签是\"SQL 类型\"。\n        tidb_distsql_execution: 执行 distsql 的耗时，标签是\"类型\"。\n        tidb_cop: KV storage Coprocessor 处理的耗时，标签是\"实例\"。\n        tidb_transaction: 事务执行 durations 的时间成本，包括重试，标签是\"SQL 类型\"。\n        tidb_transaction_local_latch_wait: 事务执行时本地锁占用的时间，标签是\"实例\"。\n        tidb_kv_backoff: '{{distro.tidb}} 事务锁等待键值存储的时间，标签是\"类型\"。'\n        tidb_kv_request: KV 请求 durations 的耗时，标签是\"类型\"。\n        tidb_slow_query: '{{distro.tidb}} 慢查询的时间开销，标签是\"实例\"。'\n        tidb_slow_query_cop_process: '{{distro.tidb}} 的慢查询总 cop 处理的耗时，标签是\"实例\"。'\n        tidb_slow_query_cop_wait: '{{distro.tidb}} 的慢查询总 cop 的等待时间，标签是\"实例\"。'\n        tidb_ddl_handle_job: 处理 {{distro.tidb}} DDL 任务的耗时，标签是\"类型\"。\n        tidb_ddl_worker: DDL worker 处理任务的耗时，标签是\"实例\"。\n        tidb_ddl_update_self_version: '{{distro.tidb}} schema 同步器版本更新的耗时，标签是\"结果\"。'\n        tidb_owner_handle_syncer: 在 etcd 上执行 {{distro.tidb}} DDL 所有者操作的耗时，标签是\"类型\"。\n        tidb_ddl_batch_add_index: '{{distro.tidb}} 批量添加索引的耗时，标签是\"类型\"。'\n        tidb_ddl_deploy_syncer: '{{distro.tidb}} DDL schema 同步器统计的时间成本，包括 init、start、watch、clear，标签是\"类型\"。'\n        tidb_load_schema: 加载 {{distro.tidb}} schema 的时间成本，标签是\"类型\"。\n        tidb_meta_operation: '{{distro.tidb}} 元操作的时间成本，包括 get/set 模式和 DDL 作业，标签是\"实例\"。'\n        tidb_auto_id_request: '{{distro.tidb}} 自增 ID 处理 ID 请求的耗时，标签是\"类型\"。'\n        tidb_statistics_auto_analyze: 自动分析 {{distro.tidb}} 的耗时，标签是\"类型\"。\n        tidb_gc: KV 存储垃圾回收的时间，标签是\"实例\"。\n        tidb_gc_push_task: KV 存储范围内 worker 处理一项任务的耗时，标签是\"实例\"。\n        tidb_batch_client_unavailable: KV 存储批量处理不可用的耗时，标签是\"类型\"。\n        tidb_batch_client_wait: KV 存储批量处理客户端等待请求的耗时，标签是\"实例\"。\n        pd_start_tso_wait: 等待获取开始时间戳 timestamp 的耗时，标签是\"实例\"。\n        pd_tso_rpc: 发送 TSO 请求直到收到响应的时间，标签是\"实例\"。\n        pd_tso_wait: 客户端开始等待 timestamp 直到收到 timestamp 结果的耗时，标签是\"实例\"。\n        pd_client_cmd: '{{distro.pd}} 客户端命令的耗时，标签是\"类型\"。'\n        pd_handle_request: '{{distro.pd}} 处理请求的耗时，标签是\"类型\"。'\n        pd_grpc_completed_commands: '{{distro.pd}} 完成各种 gRPC 命令的耗时，标签是\"gRPC 方法\"。'\n        pd_operator_finish: '{{distro.pd}} 完成各种调度命令的时间，标签是\"类型\"。'\n        pd_operator_step_finish: '{{distro.pd}} 完成操作步骤的耗时，标签是\"类型\"。'\n        pd_handle_transactions: '{{distro.pd}} 处理 etcd 事务的耗时，标签是\"结果\"。'\n        pd_region_heartbeat: 每个 {{distro.tikv}} 实例中心跳的耗时，标签是\"服务地址\"。\n        etcd_wal_fsync: etcd 将 WAL 写入持久存储器的耗时，标签是\"实例\"。\n        pd_peer_round_trip: 网络的延迟，标签是\"实例\"。\n        tikv_grpc_message: gRPC 报文的 {{distro.tikv}} 处理耗时，标签是\"类型\"。\n        tikv_cop_request: Coprocessor 处理读请求的时间开销，标签是\"请求\"。\n        tikv_cop_handle: 处理 Coprocessor 请求的时间开销，标签是\"请求\"。\n        tikv_cop_wait: Coprocessor 请求等待处理的耗时，标签是\"请求\"。\n        tikv_scheduler_command: 执行 commit 命令的耗时，标签是\"类型\"。\n        tikv_scheduler_latch_wait: 提交命令中 {{distro.tikv}} 锁存器等待的时间开销，标签是\"类型\"。\n        tikv_handle_snapshot: 处理快照的时间开销，标签是\"类型\"。\n        tikv_send_snapshot: 发送快照的时间开销，标签是\"实例\"。\n        tikv_storage_async_request: 处理异步快照请求的时间开销，标签是\"类型\"。\n        tikv_raft_append_log: Raft appends log 的时间开销，标签是\"实例\"。\n        tikv_raft_apply_log: Raft apply log 的时间开销，标签是\"实例\"。\n        tikv_raft_apply_wait: Raft apply wait 的时间开销，标签是\"实例\"。\n        tikv_raft_process: Peer processes in Raft 的时间开销，标签是\"实例\"。\n        tikv_raft_propose_wait: 每一个 Raft 提议的等待时间，标签是\"类型\"。\n        tikv_raft_store_events: RaftStore events 的时间开销，标签是\"类型\"。\n        tikv_commit_log: Raft 提交日志的时间开销，标签是\"实例\"。\n        tikv_check_split: 运行分割检查的耗时，标签是\"实例\"。\n        tikv_ingest_sst: Ingest SST 文件的耗时，标签是\"实例\"。\n        tikv_gc_tasks: 执行 GC 任务的耗时，标签是\"任务\"。\n        tikv_pd_request: '{{distro.tikv}} 向 {{distro.pd}} 发送请求的耗时，标签是\"类型\"。'\n        tikv_lock_manager_deadlock_detect:\n        tikv_lock_manager_waiter_lifetime:\n        tikv_backup_range:\n        tikv_backup:\n        tidb_transaction_retry_num: '{{distro.tidb}} 事务重试次数，标签是\"实例\"。'\n        tidb_transaction_statement_num: 一个事务中 {{distro.tidb}} 语句数的总数量。Internal 是指 {{distro.tidb}} 内部事务，标签是\"实例\"。'\n        tidb_txn_region_num: 每个事务进行操作的区域数量，标签是\"实例\"。\n        tidb_txn_kv_write_num: 每个事务执行的 KV 写入数量，标签是\"实例\"。\n        tidb_txn_kv_write_size: 每个事务执行的 KV 写入大小，标签是\"实例\"。\n        tidb_load_safepoint_total_num: 安全点装载总数量，标签是\"实例\"。\n        tidb_lock_resolver_total_num: lock resolve 的总数量，标签是\"实例\"。\n        pseudo_estimation_total_count: 使用伪估计的 {{distro.tidb}} 优化器的总数量，标签是\"实例\"，\"类型\"。\n        dump_feedback_total_count: '{{distro.tidb}} 转储统计数据回 KV 存储的操作总数量，标签是\"实例\"。'\n        store_query_feedback_total_count: '{{distro.tidb}} 存储查询反馈的总数量，标签是\"实例\"。'\n        update_stats_total_count: 使用反馈更新统计数据的 {{distro.tidb}} 总数量，标签是\"实例\"。\n        blance-leader-in: Leader 移动到 {{distro.tikv}} 存储的总数量，标签是\"实例\"。\n        blance-leader-out: Leader 移出 {{distro.tikv}} 存储的总数量，标签是\"实例\"。\n        blance-region-in: 移动到 {{distro.tikv}} 存储的 Region 总数量，标签是\"实例\"。\n        blance-region-out: 移出 {{distro.tikv}} 存储的的 Region 总数量，标签是\"实例\"。\n        Approximate Region size: 近似 Region 大小，标签是\"实例\"。\n        store size: 存储大小，标签是\"实例\"。\n        tikv_scheduler_keys_read: 由一条命令读取的键数量，标签是\"实例\"，\"类型\"。\n        tikv_scheduler_keys_written: 由一条命令写入的键数量，标签是\"实例\"，\"类型\"。\n        tikv_scheduler_scan_details_total_num: 在执行一条命令时，扫描每个 CF 的详细信息的总数量，标签是\"实例\"。\n        tikv_scheduler_stage_total_num: 调度程序状态的总数量，标签是\"实例\"，\"阶段\"，\"类型\"。\n        tikv_gc_keys_total_num: GC 期间 CF 中受影响的键的总数量，标签是\"实例\"。\n        tidb_gc_worker_action_total_num: KV 存储垃圾回收总量，标签是\"实例\"，\"类型\"。\n        tikv_worker_handled_tasks_total_num: worker 处理的任务总数量，标签是\"实例\"。\n        tikv_worker_pending_tasks_total_num: 工作进程的挂起和运行任务的总数量，标签是\"实例\"。\n        tikv_futurepool_handled_tasks_total_num: future_pool 处理的任务总数量，标签是\"实例\"。\n        tikv_futurepool_pending_tasks_total_num: future_pool 的总挂起和运行任务，标签是\"实例\"。\n        tikv_snapshot_kv_count: tikv_snapshot_kv_count，标签是\"实例\"。\n        tikv_snapshot_size: 快照内 KV 的数量，标签是\"实例\"。\n        tikv_snapshot_state_total_count: '{{distro.tikv}} 的快照大小，标签是\"实例\"，\"类型\"。'\n        tikv_cop_scan_keys_num: '{{distro.tikv}} Coprocessor 扫描键总数量，标签是\"实例\"。'\n        tikv_cop_total_response_total_size: '{{distro.tikv}} Coprocessor 响应总大小，标签是\"实例\"。'\n        tikv_cop_scan_num: '{{distro.tikv}} Coprocessor 扫描操作总数量，标签是\"实例\"。'\n        tikv_raft_sent_messages_total_num: 发送的 Raft 消息的总数量，标签是\"实例\"，\"类型\"。\n        tikv_flush_messages_total_num: Raft 上刷新了的信息总数量，标签是\"实例\"。\n        tikv_receive_messages_total_num: Raft 收到的的信息总数量，标签是\"实例\"。\n        tikv_raft_dropped_messages_total: Raft 丢掉的的信息总数量，标签是\"实例\"，\"类型\"。\n        tikv_raft_proposals_total_num: Raft 提议的的总数量，标签是\"实例\"，\"类型\"。\n        tikv_grpc_error_total_count: gRPC 消息失败的总数量，标签是\"实例\"，\"类型\"。\n        tikv_critical_error_total_count: '{{distro.tikv}} 临界误差的总数量，标签是\"实例\"，\"类型\"。'\n        tikv_scheduler_is_busy_total_count: 使 {{distro.tikv}} 实例暂时不可用的调度器繁忙事件的总数量，标签是\"实例\"。\n        tikv_channel_full_total_count: 通道完全错误的总数量，它将使 {{distro.tikv}} 实例暂时不可用，标签是\"实例\"。\n        tikv_coprocessor_request_error_total_count: Coprocessor 错误的总数量，标签是\"实例\"，\"原因\"。\n        tikv_engine_write_stall: 指示使 {{distro.tikv}} 实例暂时不可用的写失速事件，标签是\"实例\"。\n        tikv_server_report_failures_total_count: 报告失败消息的总数量，标签是\"实例\"。\n        tikv_storage_async_request_error: 存储请求错误的总数量，标签是\"实例\"，\"状态\"，\"类型\"。\n        tikv_lock_manager_detect_error_total_count: '{{distro.tikv}} 锁管理器检测错误的总数量，标签是\"实例\"，\"类型\"。'\n        tikv_backup_errors_total_count: '{{distro.tikv}} 锁管理的总错误，标签是\"实例\"，\"错误\"。'\n        node_disk_write_latency: 每个节点的磁盘写延迟，标签是\"实例\"，\"设备\"。\n        node_disk_read_latency: 每个节点的磁盘读取延迟，标签是\"实例\"，\"设备\"。\n        grpc: 每个 {{distro.tikv}} gRPC 的 CPU 利用率，标签是\"实例\"。'\n        raftstore: '{{distro.tikv}} RaftStore 线程的 CPU 利用率，标签是\"实例\"。'\n        Async apply: '{{distro.tikv}} 异步应用线程的 CPU 利用率，标签是\"实例\"。'\n        sched_worker: '{{distro.tikv}} 调度器工作线程的 CPU 利用率，标签是\"实例\"。'\n        snapshot: '{{distro.tikv}} 快照的 CPU 利用率，标签是\"实例\"。'\n        unified read pool: '{{distro.tikv}} 统一读池线程的 CPU 利用率，标签是\"实例\"。'\n        storage read pool: '{{distro.tikv}} 存储读池线程的 CPU 利用率，标签是\"实例\"。'\n        storage read pool normal: '{{distro.tikv}} 存储读池普通线程的 CPU 利用率，标签是\"实例\"。'\n        storage read pool high: '{{distro.tikv}} 存储较高读线程的 CPU 利用率，标签是\"实例\"。'\n        storage read pool low: '{{distro.tikv}} 存储较低读线程的 CPU 利用率，标签是\"实例\"。'\n        cop: '{{distro.tikv}} Coprocessor 的 CPU 利用率，标签是\"实例\"。'\n        cop normal: '{{distro.tikv}} Coprocessor 普通线程的 CPU 利用率，标签是\"实例\"。'\n        cop high: '{{distro.tikv}} Coprocessor 高线程的 CPU 利用率，标签是\"实例\"。'\n        cop low: '{{distro.tikv}} Coprocessor 低线程的 CPU 利用率，标签是\"实例\"。'\n        rocksdb: '{{distro.tikv}} RocksDB 的 CPU 利用率，标签是\"实例\"。'\n        gc: '{{distro.tikv}} GC 的 CPU 利用率，标签是\"实例\"。'\n        split_check: '{{distro.tikv}} split_chec 的 CPU 利用率，标签是\"实例\"。'\n        region_score: store 的 Region 得分，标签是\"服务地址\"。\n        leader_score: store 的 Leader 得分，标签是\"服务地址\"。\n        region_count: store 的 Region 数量，标签是\"服务地址\"。\n        leader_count: store 的 Leader 数量，标签是\"服务地址\"。\n        region_size: store 的 Region 大小，标签是\"服务地址\"。\n        leader_size: store 的 Leader 大小，标签是\"服务地址\"。\n        tikv_memtable_hit: memtable 的命中率，标签是\"实例\"。\n        tikv_block_all_cache_hit: 所有块缓存的命中率，标签是\"实例\"。\n        tikv_block_index_cache_hit: 索引块缓存的命中率，标签是\"实例\"。\n        tikv_block_filter_cache_hit: 过滤块缓存的命中率，标签是\"实例\"。\n        tikv_block_data_cache_hit: 数据块缓存的命中率，标签是\"实例\"。\n        tikv_block_bloom_prefix_cache_hit: bloom_prefix 块缓存的命中率，标签是\"实例\"。\n        get duration: RocksDB 执行 get 操作的耗时，标签是\"实例\"。\n        seek duration: RocksDB 执行 seek 操作的耗时，标签是\"实例\"。\n        write duration: RocksDB 执行写操作的耗时，标签是\"实例\"。\n        WAL sync duration: RocksDB 执行 WAL 同步操作的耗时，标签是\"实例\"。\n        compaction duration: RocksDB 执行压缩操作的耗时，标签是\"实例\"。\n        SST read duration: RocksDB 读取 SST 文件的耗时，标签是\"实例\"。\n        write stall duration: 由写停顿引起的时间，标签是\"实例\"。\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/diagnoseReportApp/types.ts",
    "content": "import { createContext } from 'react'\n\nexport interface TableRowDef {\n  values: string[]\n  sub_values: string[][]\n  comment: string\n}\n\nexport interface TableDef {\n  category: string[]\n  title: string\n  comment: string\n  column: string[]\n  rows: TableRowDef[]\n}\n\nexport const ExpandContext = createContext(false)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/react-app-env.d.ts",
    "content": "declare module '*.module.css' {\n  const classes: { readonly [key: string]: string }\n  export default classes\n}\n\ndeclare module '*.module.less' {\n  const classes: { readonly [key: string]: string }\n  export default classes\n}\n\ndeclare module '*.yaml' {\n  const classes: { readonly [key: string]: string }\n  export default classes\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/styles/override.less",
    "content": "// reset ::selection pseudo element values\n::selection {\n  color: currentColor;\n  background-color: #b3d6ff;\n}\n\nhtml {\n  font-size: 14px;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/styles/style.less",
    "content": "@root-entry-name: default;\n\n@import 'antd/lib/style/components.less';\n// it is expected to import 'antd/es/style/components.less' but it doesn't exist this file\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/utils/appOptions.ts",
    "content": "export type AppOptions = {\n  hideNav: boolean\n  skipNgmCheck: boolean\n  lang: string\n}\n\nconst defAppOptions: AppOptions = {\n  hideNav: false,\n  skipNgmCheck: false,\n  lang: ''\n}\n\nconst optionsKey = 'dashboard_app_options'\n\nexport function saveAppOptions(options: AppOptions) {\n  localStorage.setItem(optionsKey, JSON.stringify(options))\n}\n\nexport function loadAppOptions(): AppOptions {\n  const s = localStorage.getItem(optionsKey)\n  if (s === null) {\n    return defAppOptions\n  }\n  const opt = JSON.parse(s)\n  if (!!opt && opt.constructor === Object) {\n    return opt\n  }\n  return defAppOptions\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/utils/auth.ts",
    "content": "import { EventEmitter2 } from 'eventemitter2'\n\nconst tokenKey = 'dashboard_auth_token'\n\nexport const authEvents = new EventEmitter2()\n\nexport const EVENT_TOKEN_CHANGED = 'tokenChanged'\n\nexport function getAuthToken() {\n  return localStorage.getItem(tokenKey)\n}\n\nexport function setAuthToken(token) {\n  const lastToken = getAuthToken()\n  if (lastToken !== token) {\n    localStorage.setItem(tokenKey, token)\n    authEvents.emit(EVENT_TOKEN_CHANGED, token)\n  }\n}\n\nexport function clearAuthToken() {\n  const lastToken = getAuthToken()\n  if (lastToken) {\n    localStorage.removeItem(tokenKey)\n    authEvents.emit(EVENT_TOKEN_CHANGED, null)\n  }\n}\n\nexport function getAuthTokenAsBearer() {\n  const token = getAuthToken()\n  if (!token) {\n    return null\n  }\n  return `Bearer ${token}`\n}\n\nexport enum AuthTypes {\n  SQLUser = 0,\n  SharingCode = 1,\n  SSO = 2\n}\n\nexport default {\n  authEvents,\n  EVENT_TOKEN_CHANGED,\n  getAuthToken,\n  setAuthToken,\n  clearAuthToken,\n  getAuthTokenAsBearer,\n  AuthTypes\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/utils/authSSO.ts",
    "content": "import { Modal } from 'antd'\nimport { ReqConfig } from '@pingcap/tidb-dashboard-lib'\nimport client from '~/client'\nimport { AuthTypes, setAuthToken } from './auth'\n\nfunction newRandomString(length: number) {\n  let text = ''\n  const possible =\n    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'\n  for (let i = 0; i < length; i++) {\n    text += possible.charAt(Math.floor(Math.random() * possible.length))\n  }\n  return text\n}\n\nfunction getBaseURL() {\n  return `${window.location.protocol}//${window.location.host}${window.location.pathname}`\n}\n\nfunction getRedirectURL() {\n  return `${getBaseURL()}?sso_callback=1`\n}\n\nexport async function getAuthURL() {\n  const codeVerifier = newRandomString(128)\n  const state = newRandomString(32)\n\n  sessionStorage.setItem('sso.codeVerifier', codeVerifier)\n  sessionStorage.setItem('sso.state', state)\n  const resp = await client\n    .getInstance()\n    .userSSOGetAuthURL({ codeVerifier, redirectUrl: getRedirectURL(), state })\n  return resp.data\n}\n\nexport function isSSOCallback() {\n  const p = new URLSearchParams(window.location.search)\n  return p.has('sso_callback')\n}\n\nasync function handleSSOCallbackInner() {\n  const p = new URLSearchParams(window.location.search)\n  if (p.get('state') !== sessionStorage.getItem('sso.state')) {\n    throw new Error(\n      'Invalid OIDC state: You may see this error when your SSO sign in is outdated.'\n    )\n  }\n  const r = await client.getInstance().userLogin(\n    {\n      message: {\n        type: AuthTypes.SSO,\n        extra: JSON.stringify({\n          code: p.get('code'),\n          code_verifier: sessionStorage.getItem('sso.codeVerifier'),\n          redirect_url: getRedirectURL()\n        })\n      }\n    },\n    { handleError: 'custom' } as ReqConfig\n  )\n\n  sessionStorage.removeItem('sso.codeVerifier')\n  sessionStorage.removeItem('sso.state')\n\n  setAuthToken(r.data.token)\n  window.location.replace(getBaseURL())\n}\n\nexport async function handleSSOCallback() {\n  try {\n    await handleSSOCallbackInner()\n  } catch (e) {\n    Modal.error({\n      title: 'SSO Sign In Failed',\n      content: '' + e,\n      okText: 'Sign In Again',\n      onOk: () => window.location.replace(getBaseURL())\n    })\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/utils/distro/assetsRes.ts",
    "content": "import publicPathPrefix from '../publicPathPrefix'\n\nlet timestamp = document\n  .querySelector('meta[name=\"x-distro-assets-res-timestamp\"]')\n  ?.getAttribute('content')\n\nif (timestamp === '__DISTRO_ASSETS_RES_TIMESTAMP__') {\n  timestamp = new Date().valueOf() + ''\n}\n\nconst logoSvg = `${publicPathPrefix}/distro-res/logo.svg?t=${timestamp}`\nconst lightLogoSvg = `${publicPathPrefix}/distro-res/logo-icon-light.svg?t=${timestamp}`\nconst landingSvg = `${publicPathPrefix}/distro-res/landing.png?t=${timestamp}`\n\nexport { logoSvg, lightLogoSvg, landingSvg }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/utils/distro/stringsRes.ts",
    "content": "import { updateDistro } from '@pingcap/tidb-dashboard-lib'\n\nimport defDistroStringsRes from './strings_res.json'\n\nlet distro = defDistroStringsRes\n\n// it is a base64 encoded string\nlet distroStringsRes = document\n  .querySelector('meta[name=\"x-distro-strings-res\"]')\n  ?.getAttribute('content')\n\nif (distroStringsRes && distroStringsRes !== '__DISTRO_STRINGS_RES__') {\n  try {\n    const distroObj = JSON.parse(atob(distroStringsRes))\n    distro = {\n      ...defDistroStringsRes,\n      ...distroObj\n    }\n  } catch (error) {\n    console.log(error)\n  }\n}\n\nupdateDistro(distro)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/utils/distro/strings_res.json",
    "content": "{\"tidb\":\"TiDB\",\"tikv\":\"TiKV\",\"pd\":\"PD\",\"tiflash\":\"TiFlash\",\"ticdc\":\"TiCDC\",\"tiproxy\":\"TiProxy\",\"tso\":\"TSO\",\"scheduling\":\"Scheduling\"}"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/utils/publicPathPrefix.ts",
    "content": "const DEF_PUBLIC_PATH_PREFIX = '/dashboard'\n\nlet prefix =\n  document\n    .querySelector('meta[name=\"x-public-path-prefix\"]')\n    ?.getAttribute('content') || DEF_PUBLIC_PATH_PREFIX\n\nif (prefix === '__PUBLIC_PATH_PREFIX__') {\n  prefix = DEF_PUBLIC_PATH_PREFIX\n}\n\nexport default prefix\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/utils/registry.ts",
    "content": "import React from 'react'\nimport ReactDOM from 'react-dom'\nimport singleSpaReact from 'single-spa-react'\nimport * as singleSpa from 'single-spa'\n\nimport { i18n, routing } from '@pingcap/tidb-dashboard-lib'\n\nimport { AppOptions } from './appOptions'\n\nexport default class AppRegistry {\n  public defaultRouter = ''\n  public apps = {}\n  public constructor(public appOptions: AppOptions) {}\n\n  static newReactSpaApp = function (rootComponentAsyncLoader, targetDomId) {\n    const reactLifecycles = singleSpaReact({\n      React,\n      ReactDOM,\n      loadRootComponent: async () => {\n        const component = await rootComponentAsyncLoader()\n        if (component.default) {\n          return component.default\n        }\n        return component\n      },\n      domElementGetter: () => document.getElementById(targetDomId)!\n    })\n    return {\n      bootstrap: [reactLifecycles.bootstrap],\n      mount: [reactLifecycles.mount],\n      unmount: [reactLifecycles.unmount]\n    }\n  }\n\n  /**\n   * Register a TiDB Dashboard application.\n   *\n   * This function is a light encapsulation over single-spa's registerApplication\n   * which provides some extra registry capabilities.\n   *\n   * @param {{\n   *  id: string,\n   *  reactRoot: Function,\n   *  routerPrefix: string,\n   *  indexRoute: string,\n   *  isDefaultRouter: boolean,\n   *  icon: string,\n   * }} app\n   */\n  register(app) {\n    if (app.translations) {\n      i18n.addTranslations(app.translations)\n    }\n\n    singleSpa.registerApplication(\n      app.id,\n      AppRegistry.newReactSpaApp(app.reactRoot, '__spa_content__'),\n      () => {\n        return routing.isLocationMatchPrefix(app.routerPrefix)\n      },\n      {\n        registry: this,\n        app\n      }\n    )\n    if (!app.indexRoute) {\n      app.indexRoute = app.routerPrefix\n    }\n    if (!this.defaultRouter || app.isDefaultRouter) {\n      this.defaultRouter = app.indexRoute\n    }\n    this.apps[app.id] = app\n    return this\n  }\n\n  /**\n   * Get the default router for initial routing.\n   */\n  getDefaultRouter() {\n    return this.defaultRouter || '/'\n  }\n\n  /**\n   * Get the registry of the current active app.\n   */\n  getActiveApp() {\n    const mountedApps = singleSpa.getMountedApps()\n    for (let i = 0; i < mountedApps.length; i++) {\n      const app = mountedApps[i]\n      if (this.apps[app] !== undefined) {\n        return this.apps[app]\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/src/utils/store.ts",
    "content": "import { store, ReqConfig } from '@pingcap/tidb-dashboard-lib'\nimport client, { InfoInfoResponse } from '~/client'\n\nimport { authEvents, EVENT_TOKEN_CHANGED, getAuthToken } from './auth'\n\nexport async function reloadWhoAmI(): Promise<boolean> {\n  if (!getAuthToken()) {\n    store.update((s) => {\n      s.whoAmI = undefined\n    })\n    return false\n  }\n\n  try {\n    const resp = await client.getInstance().infoWhoami({\n      handleError: 'custom'\n    } as ReqConfig)\n    store.update((s) => {\n      s.whoAmI = resp.data\n    })\n    return true\n  } catch (ex) {\n    store.update((s) => {\n      s.whoAmI = undefined\n    })\n    return false\n  }\n}\n\nexport async function mustLoadAppInfo(): Promise<InfoInfoResponse> {\n  const resp = await client.getInstance().infoGet({\n    handleError: 'custom'\n  } as ReqConfig)\n  store.update((s) => {\n    s.appInfo = resp.data\n  })\n  return resp.data\n}\n\nauthEvents.on(EVENT_TOKEN_CHANGED, async () => {\n  await reloadWhoAmI()\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-for-op/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"noEmit\": true,\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": false,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"src/*\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/builder.js",
    "content": "const chalk = require('chalk')\nconst { watch } = require('chokidar')\n\nconst esbuild = require('esbuild')\nconst postCssPlugin = require('@baurine/esbuild-plugin-postcss3')\nconst autoprefixer = require('autoprefixer')\nconst { yamlPlugin } = require('esbuild-plugin-yaml')\n\nconst { lessModifyVars, lessGlobalVars } = require('../../less-vars')\n\n// customized plugin: log time\nconst logTime = (_options = {}) => ({\n  name: 'logTime',\n  setup(build) {\n    let time\n\n    build.onStart(() => {\n      time = new Date()\n      console.log(`Build started`)\n    })\n\n    build.onEnd(() => {\n      console.log(`Build ended: ${chalk.yellow(`${new Date() - time}ms`)}`)\n    })\n  }\n})\n\nconst { dependencies } = require('./package.json')\n\nconst esbuildParams = {\n  color: true,\n  entryPoints: ['src/index.ts'],\n  outfile: 'dist/index.js',\n  target: ['esnext'],\n  format: 'esm',\n  bundle: true,\n  sourcemap: true,\n  logLevel: 'error',\n  incremental: true,\n  platform: 'browser',\n  external: Object.keys(dependencies),\n  plugins: [\n    postCssPlugin.default({\n      lessOptions: {\n        modifyVars: lessModifyVars,\n        globalVars: lessGlobalVars,\n        javascriptEnabled: true\n      },\n      enableCache: true,\n      plugins: [autoprefixer]\n    }),\n    yamlPlugin(),\n    logTime()\n  ]\n}\n\nasync function main() {\n  const builder = await esbuild.build(esbuildParams)\n\n  function rebuild() {\n    builder.rebuild().catch((err) => console.log(err))\n  }\n\n  const isDev = process.env.NODE_ENV !== 'production'\n  if (isDev) {\n    watch('src/**/*', { ignoreInitial: true }).on('all', () => {\n      rebuild()\n    })\n  } else {\n    process.exit(0)\n  }\n}\n\nmain()\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/gulpfile.js",
    "content": "const { task, parallel } = require('gulp')\nconst shell = require('gulp-shell')\n\n// below way doesn't work\n// task('tsc:dev', parallel(shell.task('tsc -w'), shell.task('tsc-alias -w')))\n\n// https://stackoverflow.com/a/47305304/2998877\ntask('tsc:dev', shell.task('tsc-watch --onCompilationComplete \"tsc-alias\"'))\ntask('tsc:build', shell.task('tsc && tsc-alias'))\n\n// https://www.npmjs.com/package/eslint-watch\ntask('lint:watch', shell.task('esw --watch --cache --ext .tsx,.ts src/'))\ntask('lint:check', shell.task('esw --cache --ext tsx,ts src/'))\n\ntask('esbuild:dev', shell.task('NODE_ENV=development node builder.js'))\ntask('esbuild:build', shell.task('NODE_ENV=production node builder.js'))\n\ntask('dev', parallel('tsc:dev', 'lint:watch', 'esbuild:dev'))\ntask('build', parallel('tsc:build', 'lint:check', 'esbuild:build'))\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/package.json",
    "content": "{\n  \"name\": \"@pingcap/tidb-dashboard-lib\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"scripts\": {\n    \"dev\": \"gulp dev\",\n    \"build\": \"rimraf dist && gulp build\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"@ahooksjs/use-url-state\": \"^3.5.0\",\n    \"@ant-design/colors\": \"^6.0.0\",\n    \"@ant-design/icons\": \"^4.7.0\",\n    \"@baurine/grafana-value-formats\": \"^1.0.4\",\n    \"@baurine/sql-formatter-plus\": \"^1.5.3\",\n    \"@elastic/charts\": \"^46.10.1\",\n    \"@g07cha/flexbox-react\": \"^5.0.0\",\n    \"@tanstack/react-query\": \"4\",\n    \"@uifabric/utilities\": \"^7.33.5\",\n    \"ace-builds\": \"^1.6.0\",\n    \"ahooks\": \"^3.1.9\",\n    \"antd\": \"^4.18.7\",\n    \"axios\": \"^1.12.0\",\n    \"classnames\": \"^2.3.1\",\n    \"d3\": \"^5.16.0\",\n    \"d3-flextree\": \"2.1.2\",\n    \"d3-graphviz\": \"4.1.0\",\n    \"dayjs\": \"^1.10.8\",\n    \"hsluv\": \"^0.1.0\",\n    \"i18next\": \"^23.7.11\",\n    \"i18next-browser-languagedetector\": \"^6.1.3\",\n    \"lodash\": \"^4.17.21\",\n    \"metrics-chart\": \"^0.32.0\",\n    \"mixpanel-browser\": \"^2.45.0\",\n    \"moize\": \"^5.4.7\",\n    \"office-ui-fabric-react\": \"^7.183.1\",\n    \"pullstate\": \"^1.23.0\",\n    \"rc-picker\": \"^2.6.4\",\n    \"rc-trigger\": \"^5.2.11\",\n    \"rc-util\": \"^5.19.3\",\n    \"react\": \"^17.0.2\",\n    \"react-ace\": \"^10.1.0\",\n    \"react-copy-to-clipboard\": \"^5.0.2\",\n    \"react-csv\": \"^2.2.2\",\n    \"react-dom\": \"^17.0.2\",\n    \"react-error-boundary\": \"^3.1.4\",\n    \"react-highlight-words\": \"^0.18.0\",\n    \"react-i18next\": \"^11.15.4\",\n    \"react-intersection-observer\": \"^9.2.2\",\n    \"react-json-view\": \"1.21.3\",\n    \"react-markdown\": \"^8.0.3\",\n    \"react-router\": \"6\",\n    \"react-router-dom\": \"6\",\n    \"react-split\": \"^2.0.14\",\n    \"react-spring\": \"^9.4.4\",\n    \"react-syntax-highlighter\": \"^16.0.0\",\n    \"react-use\": \"^15.3.3\",\n    \"sql-formatter\": \"^4.0.2\",\n    \"string-template\": \"^1.0.0\",\n    \"url\": \"^0.11.0\",\n    \"uuid\": \"^8.3.2\",\n    \"visual-plan\": \"^0.0.7\"\n  },\n  \"devDependencies\": {\n    \"@baurine/esbuild-plugin-postcss3\": \"^0.4.3\",\n    \"@openapitools/openapi-generator-cli\": \"^2.4.26\",\n    \"@types/d3\": \"^5.7.2\",\n    \"@types/d3-graphviz\": \"^2.6.7\",\n    \"@types/lodash\": \"^4.14.180\",\n    \"@types/node\": \"^16.9.1\",\n    \"@types/react\": \"^17.0.20\",\n    \"@types/react-dom\": \"^17.0.9\",\n    \"autoprefixer\": \"^10.4.2\",\n    \"chalk\": \"4.1.2\",\n    \"chokidar\": \"^3.5.2\",\n    \"esbuild\": \"^0.14.23\",\n    \"esbuild-plugin-svgr\": \"^1.0.0\",\n    \"esbuild-plugin-yaml\": \"^0.0.1\",\n    \"eslint-watch\": \"^8.0.0\",\n    \"fs-extra\": \"^10.0.0\",\n    \"gulp\": \"^4.0.2\",\n    \"gulp-shell\": \"^0.8.0\",\n    \"rimraf\": \"^3.0.2\",\n    \"tsc-alias\": \"^1.6.9\",\n    \"tsc-watch\": \"^5.0.3\",\n    \"typescript\": \"^4.7.3\"\n  },\n  \"resolutions\": {\n    \"d3-selection\": \"1.4.1\"\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/DiskTable.tsx",
    "content": "import { Tooltip, Typography } from 'antd'\nimport React, { useContext, useMemo } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\nimport { WarningOutlined } from '@ant-design/icons'\n\nimport { HostinfoInfo, HostinfoPartitionInfo } from '@lib/client'\nimport { Bar, CardTable } from '@lib/components'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport {\n  InstanceKind,\n  InstanceKinds,\n  instanceKindName\n} from '@lib/utils/instanceTable'\n\nimport { ClusterInfoContext } from '../context'\n\ninterface IExpandedDiskItem extends HostinfoPartitionInfo {\n  key: string\n  host?: string\n  instancesCount: Record<InstanceKind, number>\n}\n\nfunction expandDisksItems(rows: HostinfoInfo[]): IExpandedDiskItem[] {\n  const expanded: IExpandedDiskItem[] = []\n  rows.forEach((row) => {\n    const instancesPerPartition: Record<\n      string,\n      Record<InstanceKind, number>\n    > = {}\n\n    let partitions = 0\n\n    Object.values(row.instances ?? {}).forEach((i) => {\n      if (!i) {\n        return\n      }\n      if (!instancesPerPartition[i.partition_path_lower!]) {\n        instancesPerPartition[i.partition_path_lower!] = {\n          pd: 0,\n          tidb: 0,\n          tikv: 0,\n          tiflash: 0,\n          ticdc: 0,\n          tiproxy: 0,\n          tso: 0,\n          scheduling: 0\n        }\n      }\n      instancesPerPartition[i.partition_path_lower!][i.type!]++\n    })\n\n    for (let pathL in row.partitions) {\n      const instancesCount = instancesPerPartition[pathL]\n      if (!instancesCount) {\n        // This partition does not have deployed instances, skip\n        continue\n      }\n      const partition = row.partitions[pathL]\n      expanded.push({\n        key: `${row.host} ${pathL}`,\n        host: row.host,\n        instancesCount,\n        ...partition\n      })\n      partitions++\n    }\n\n    if (partitions === 0) {\n      // Supply dummy item..\n      expanded.push({\n        key: row.host ?? '',\n        host: row.host,\n        instancesCount: {\n          pd: 0,\n          tidb: 0,\n          tikv: 0,\n          tiflash: 0,\n          ticdc: 0,\n          tiproxy: 0,\n          tso: 0,\n          scheduling: 0\n        }\n      })\n    }\n  })\n  return expanded\n}\n\nexport default function HostTable() {\n  const { t } = useTranslation()\n\n  const ctx = useContext(ClusterInfoContext)\n\n  const { data, isLoading, error } = useClientRequest(\n    ctx!.ds.clusterInfoGetHostsInfo\n  )\n\n  const diskData = useMemo(() => expandDisksItems(data?.hosts ?? []), [data])\n\n  const columns: IColumn[] = useMemo(\n    () => [\n      {\n        name: t('cluster_info.list.disk_table.columns.host'),\n        key: 'host',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (row: IExpandedDiskItem) => {\n          if (!row.free) {\n            return (\n              <Tooltip\n                title={t('cluster_info.list.host_table.instanceUnavailable')}\n              >\n                <Typography.Text type=\"warning\">\n                  <WarningOutlined /> {row.host}\n                </Typography.Text>\n              </Tooltip>\n            )\n          }\n          return (\n            <Tooltip title={row.host}>\n              <span>{row.host}</span>\n            </Tooltip>\n          )\n        }\n      },\n      {\n        name: t('cluster_info.list.disk_table.columns.mount_dir'),\n        key: 'mount_dir',\n        minWidth: 150,\n        maxWidth: 200,\n        onRender: (row: IExpandedDiskItem) => {\n          if (!row.path) {\n            return\n          }\n          return (\n            <Tooltip title={row.path}>\n              <span>{row.path}</span>\n            </Tooltip>\n          )\n        }\n      },\n      {\n        name: t('cluster_info.list.disk_table.columns.fs'),\n        key: 'fs',\n        minWidth: 50,\n        maxWidth: 100,\n        onRender: (row: IExpandedDiskItem) => {\n          return row.fstype?.toUpperCase() ?? ''\n        }\n      },\n      {\n        name: t('cluster_info.list.disk_table.columns.disk_size'),\n        key: 'disk_size',\n        minWidth: 60,\n        maxWidth: 100,\n        onRender: (row: IExpandedDiskItem) => {\n          if (!row.total) {\n            return\n          }\n          return getValueFormat('bytes')(row.total, 1)\n        }\n      },\n      {\n        name: t('cluster_info.list.disk_table.columns.disk_usage'),\n        key: 'disk_usage',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (row: IExpandedDiskItem) => {\n          if (!row.total || !row.free) {\n            return\n          }\n          const total = row.total\n          const free = row.free\n          const used = total - free\n          const usedPercent = (used / total).toFixed(3)\n          const tooltipContent = (\n            <span>\n              Used: {getValueFormat('bytes')(used, 1)} (\n              {getValueFormat('percentunit')(+usedPercent, 1)})\n            </span>\n          )\n          return (\n            <Tooltip title={tooltipContent}>\n              <Bar value={used} capacity={total} />\n            </Tooltip>\n          )\n        }\n      },\n      {\n        name: t('cluster_info.list.disk_table.columns.instances'),\n        key: 'instances',\n        minWidth: 100,\n        maxWidth: 200,\n        onRender: (row: IExpandedDiskItem) => {\n          const item = InstanceKinds.map((ik) => {\n            if (row.instancesCount[ik] > 0) {\n              return `${row.instancesCount[ik]} ${instanceKindName(ik)}`\n            } else {\n              return ''\n            }\n          })\n          const content = item.filter((v) => v.length > 0).join(', ')\n          return (\n            <Tooltip title={content}>\n              <span>{content}</span>\n            </Tooltip>\n          )\n        }\n      }\n    ],\n    [t]\n  )\n\n  return (\n    <CardTable\n      cardNoMargin\n      loading={isLoading}\n      columns={columns}\n      items={diskData}\n      errors={[error, data?.warning]}\n    />\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/HostTable.tsx",
    "content": "import { Tooltip, Typography } from 'antd'\nimport React, { useContext, useMemo } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { red } from '@ant-design/colors'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\nimport { HostinfoInfo } from '@lib/client'\nimport { Bar, CardTable, Pre } from '@lib/components'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport {\n  InstanceKind,\n  InstanceKinds,\n  instanceKindName\n} from '@lib/utils/instanceTable'\nimport { WarningOutlined } from '@ant-design/icons'\nimport { ClusterInfoContext } from '../context'\n\ninterface IExpandedHostItem extends HostinfoInfo {\n  key: string\n  instancesCount: Record<InstanceKind, number>\n}\n\nfunction expandHostItems(rows: HostinfoInfo[]): IExpandedHostItem[] {\n  const expanded: IExpandedHostItem[] = []\n  rows.forEach((row) => {\n    const instancesCount: Record<InstanceKind, number> = {\n      pd: 0,\n      tidb: 0,\n      tikv: 0,\n      tiflash: 0,\n      ticdc: 0,\n      tiproxy: 0,\n      tso: 0,\n      scheduling: 0\n    }\n\n    Object.values(row.instances ?? {}).forEach((i) => {\n      if (!i) {\n        return\n      }\n      instancesCount[i.type!]++\n    })\n\n    expanded.push({\n      key: row.host ?? '',\n      instancesCount,\n      ...row\n    })\n  })\n  return expanded\n}\n\nexport default function HostTable() {\n  const { t } = useTranslation()\n\n  const ctx = useContext(ClusterInfoContext)\n\n  const { data, isLoading, error } = useClientRequest(\n    ctx!.ds.clusterInfoGetHostsInfo\n  )\n\n  const hostData = useMemo(() => expandHostItems(data?.hosts ?? []), [data])\n\n  const columns: IColumn[] = useMemo(\n    () => [\n      {\n        name: t('cluster_info.list.host_table.columns.host'),\n        key: 'host',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (row: IExpandedHostItem) => {\n          if (!row.cpu_info) {\n            // We assume that CPU info must be successfully retrieved.\n            return (\n              <Tooltip\n                title={t('cluster_info.list.host_table.instanceUnavailable')}\n              >\n                <Typography.Text type=\"warning\">\n                  <WarningOutlined /> {row.host}\n                </Typography.Text>\n              </Tooltip>\n            )\n          }\n          return (\n            <Tooltip title={row.host}>\n              <span>{row.host}</span>\n            </Tooltip>\n          )\n        }\n      },\n      {\n        name: t('cluster_info.list.host_table.columns.cpu'),\n        key: 'cpu',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (row: IExpandedHostItem) => {\n          const { cpu_info: c } = row\n          if (!c) {\n            return\n          }\n          const tooltipContent = `\nPhysical Cores: ${c.physical_cores}\nLogical Cores:  ${c.logical_cores}`\n          return (\n            <Tooltip title={<Pre>{tooltipContent.trim()}</Pre>}>\n              <span>{`${c.physical_cores!} (${c.logical_cores!} vCore)`}</span>\n            </Tooltip>\n          )\n        }\n      },\n      {\n        name: t('cluster_info.list.host_table.columns.cpu_arch'),\n        key: 'cpu-arch',\n        minWidth: 60,\n        maxWidth: 100,\n        onRender: (row: IExpandedHostItem) => {\n          const { cpu_info: c } = row\n          if (!c || !c.arch) {\n            return <span>{'Unknow'}</span>\n          }\n          return <span>{`${c.arch}`}</span>\n        }\n      },\n      {\n        name: t('cluster_info.list.host_table.columns.cpu_usage'),\n        key: 'cpu_usage',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (row: IExpandedHostItem) => {\n          if (!row.cpu_usage) {\n            return\n          }\n          const system = row.cpu_usage.system ?? 0\n          const idle = row.cpu_usage.idle ?? 1\n          const user = 1 - system - idle\n          const tooltipContent = `\nUser:   ${getValueFormat('percentunit')(user)}\nSystem: ${getValueFormat('percentunit')(system)}`\n          return (\n            <Tooltip title={<Pre>{tooltipContent.trim()}</Pre>}>\n              <Bar\n                value={[user, system]}\n                colors={[null, red[4]]}\n                capacity={1}\n              />\n            </Tooltip>\n          )\n        }\n      },\n      {\n        name: t('cluster_info.list.host_table.columns.memory'),\n        key: 'memory',\n        minWidth: 60,\n        maxWidth: 100,\n        onRender: (row: IExpandedHostItem) => {\n          if (!row.memory_usage) {\n            return\n          }\n          return getValueFormat('bytes')(row.memory_usage.total ?? 0, 1)\n        }\n      },\n      {\n        name: t('cluster_info.list.host_table.columns.memory_usage'),\n        key: 'memory_usage',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (row: IExpandedHostItem) => {\n          if (!row.memory_usage) {\n            return\n          }\n          const { total, used } = row.memory_usage\n          const usedPercent = (used! / total!).toFixed(3)\n          const title = (\n            <div>\n              Used: {getValueFormat('bytes')(used!, 1)} (\n              {getValueFormat('percentunit')(+usedPercent, 1)})\n            </div>\n          )\n          return (\n            <Tooltip title={title}>\n              <Bar value={used!} capacity={total!} />\n            </Tooltip>\n          )\n        }\n      },\n      {\n        name: t('cluster_info.list.host_table.columns.instances'),\n        key: 'instances',\n        minWidth: 100,\n        maxWidth: 200,\n        onRender: (row: IExpandedHostItem) => {\n          const item = InstanceKinds.map((ik) => {\n            if (row.instancesCount[ik] > 0) {\n              return `${row.instancesCount[ik]} ${instanceKindName(ik)}`\n            } else {\n              return ''\n            }\n          })\n          const content = item.filter((v) => v.length > 0).join(', ')\n          return (\n            <Tooltip title={content}>\n              <span>{content}</span>\n            </Tooltip>\n          )\n        }\n      }\n    ],\n    [t]\n  )\n\n  return (\n    <CardTable\n      cardNoMargin\n      loading={isLoading}\n      columns={columns}\n      items={hostData}\n      errors={[error, data?.warning]}\n    />\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/InstanceTable.tsx",
    "content": "import { DeleteOutlined } from '@ant-design/icons'\nimport { useMemoizedFn } from 'ahooks'\nimport { Divider, Popconfirm, Tooltip } from 'antd'\nimport React, { useCallback, useContext, useMemo } from 'react'\nimport { useTranslation } from 'react-i18next'\n\nimport { CardTable, InstanceStatusBadge } from '@lib/components'\nimport DateTime from '@lib/components/DateTime'\nimport {\n  buildInstanceTable,\n  IInstanceTableItem,\n  InstanceStatus\n} from '@lib/utils/instanceTable'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { ClusterInfoContext } from '../context'\n\nfunction StatusColumn({\n  node,\n  onHideTiDB\n}: {\n  node: IInstanceTableItem\n  onHideTiDB: (node) => void\n}) {\n  const { t } = useTranslation()\n\n  const onConfirm = useMemoizedFn(() => {\n    onHideTiDB && onHideTiDB(node)\n  })\n\n  return (\n    <span>\n      {node.instanceKind === 'tidb' && node.status !== InstanceStatus.Up && (\n        <>\n          <Popconfirm\n            title={t(\n              'cluster_info.list.instance_table.actions.hide_db.confirm'\n            )}\n            onConfirm={onConfirm}\n          >\n            <Tooltip\n              title={t(\n                'cluster_info.list.instance_table.actions.hide_db.tooltip'\n              )}\n            >\n              <a>\n                <DeleteOutlined />\n              </a>\n            </Tooltip>\n          </Popconfirm>\n          <Divider type=\"vertical\" />\n        </>\n      )}\n      <InstanceStatusBadge status={node.status} />\n    </span>\n  )\n}\n\nexport default function ListPage() {\n  const { t } = useTranslation()\n\n  const ctx = useContext(ClusterInfoContext)\n\n  const {\n    data: dataTiDB,\n    isLoading: loadingTiDB,\n    error: errTiDB,\n    sendRequest\n  } = useClientRequest(ctx!.ds.getTiDBTopology)\n\n  const {\n    data: dataStores,\n    isLoading: loadingStores,\n    error: errStores\n  } = useClientRequest(ctx!.ds.getStoreTopology)\n\n  const {\n    data: dataPD,\n    isLoading: loadingPD,\n    error: errPD\n  } = useClientRequest(ctx!.ds.getPDTopology)\n\n  const {\n    data: dataTiCDC,\n    isLoading: loadingTiCDC,\n    error: errTiCDC\n  } = useClientRequest(ctx!.ds.getTiCDCTopology)\n\n  const {\n    data: dataTiProxy,\n    isLoading: loadingTiProxy,\n    error: errTiProxy\n  } = useClientRequest(ctx!.ds.getTiProxyTopology)\n\n  const {\n    data: dataTSO,\n    isLoading: loadingTSO,\n    error: errTSO\n  } = useClientRequest(ctx!.ds.getTSOTopology)\n\n  const {\n    data: dataScheduling,\n    isLoading: loadingScheduling,\n    error: errScheduling\n  } = useClientRequest(ctx!.ds.getSchedulingTopology)\n\n  // query TiCDC and TiProxy components returns 404 under TiDB 7.6.0\n  // filter out the 404 error\n  const errors = [\n    errTiDB,\n    errStores,\n    errPD,\n    errTiCDC,\n    errTiProxy,\n    errTSO,\n    errScheduling\n  ].filter((e) => e?.response?.status !== 404)\n\n  const [tableData, groupData] = useMemo(\n    () =>\n      buildInstanceTable({\n        dataPD,\n        dataTiDB,\n        dataTiKV: dataStores?.tikv,\n        dataTiFlash: dataStores?.tiflash,\n        dataTiCDC,\n        dataTiProxy,\n        dataTSO,\n        dataScheduling,\n        includeTiFlash: true\n      }),\n    [\n      dataTiDB,\n      dataStores,\n      dataPD,\n      dataTiCDC,\n      dataTiProxy,\n      dataTSO,\n      dataScheduling\n    ]\n  )\n\n  const handleHideTiDB = useCallback(\n    async (node) => {\n      await ctx!.ds.topologyTidbAddressDelete(`${node.ip}:${node.port}`)\n      sendRequest()\n    },\n    [sendRequest, ctx]\n  )\n\n  const columns = useMemo(\n    () => [\n      {\n        name: t('cluster_info.list.instance_table.columns.node'),\n        key: 'node',\n        minWidth: 100,\n        maxWidth: 160,\n        onRender: ({ ip, port }) => {\n          const fullName = `${ip}:${port}`\n          return (\n            <Tooltip title={fullName}>\n              <span>{fullName}</span>\n            </Tooltip>\n          )\n        }\n      },\n      {\n        name: t('cluster_info.list.instance_table.columns.status'),\n        key: 'status',\n        minWidth: 100,\n        maxWidth: 120,\n        onRender: (node) => (\n          <StatusColumn node={node} onHideTiDB={handleHideTiDB} />\n        )\n      },\n      {\n        name: t('cluster_info.list.instance_table.columns.up_time'),\n        key: 'start_timestamp',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: ({ start_timestamp: ts }) => {\n          if (ts !== undefined && ts !== 0) {\n            return <DateTime.Calendar unixTimestampMs={ts * 1000} />\n          }\n        }\n      },\n      {\n        name: t('cluster_info.list.instance_table.columns.version'),\n        fieldName: 'version',\n        key: 'version',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: ({ version }) => (\n          <Tooltip title={version}>\n            <span>{version}</span>\n          </Tooltip>\n        )\n      },\n      {\n        name: t('cluster_info.list.instance_table.columns.git_hash'),\n        fieldName: 'git_hash',\n        key: 'git_hash',\n        minWidth: 100,\n        maxWidth: 200,\n        onRender: ({ git_hash }) => (\n          <Tooltip title={git_hash}>\n            <span>{git_hash}</span>\n          </Tooltip>\n        )\n      },\n      {\n        name: t('cluster_info.list.instance_table.columns.deploy_path'),\n        fieldName: 'deploy_path',\n        key: 'deploy_path',\n        minWidth: 150,\n        maxWidth: 300,\n        onRender: ({ deploy_path }) => (\n          <Tooltip title={deploy_path}>\n            <span>{deploy_path}</span>\n          </Tooltip>\n        )\n      }\n    ],\n    [t, handleHideTiDB]\n  )\n\n  return (\n    <CardTable\n      disableSelectionZone\n      cardNoMargin\n      loading={\n        loadingTiDB ||\n        loadingStores ||\n        loadingPD ||\n        loadingTiCDC ||\n        loadingTiProxy ||\n        loadingTSO ||\n        loadingScheduling\n      }\n      columns={columns}\n      items={tableData}\n      groups={groupData}\n      errors={errors}\n    />\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/Statistics.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n// FIXME: We should not provide padding for CardTab content, so that user\n// can control whether a padding is needed. For example, to a <Card>.\n.content {\n  margin-left: -@padding-page;\n  margin-right: -@padding-page;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/Statistics.tsx",
    "content": "import React, { useContext } from 'react'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { ClusterinfoClusterStatisticsPartial } from '@lib/client'\nimport { AnimatedSkeleton, ErrorBar, Descriptions, Card } from '@lib/components'\nimport { useTranslation } from 'react-i18next'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\nimport { Alert } from 'antd'\n\nimport styles from './Statistics.module.less'\nimport { InstanceKinds, instanceKindName } from '@lib/utils/instanceTable'\nimport { ClusterInfoContext } from '../context'\n\nfunction PartialInfo({ data }: { data?: ClusterinfoClusterStatisticsPartial }) {\n  const { t } = useTranslation()\n  return (\n    <Descriptions>\n      <Descriptions.Item\n        span={2}\n        label={t('cluster_info.list.statistics.field.instances')}\n      >\n        {data?.number_of_instances ?? 'Unknown'}\n      </Descriptions.Item>\n      <Descriptions.Item\n        span={2}\n        label={t('cluster_info.list.statistics.field.hosts')}\n      >\n        {data?.number_of_hosts ?? 'Unknown'}\n      </Descriptions.Item>\n      <Descriptions.Item\n        span={2}\n        label={t('cluster_info.list.statistics.field.memory_capacity')}\n      >\n        {getValueFormat('bytes')(data?.total_memory_capacity_bytes ?? 0, 1)}\n      </Descriptions.Item>\n      <Descriptions.Item\n        span={2}\n        label={t('cluster_info.list.statistics.field.physical_cores')}\n      >\n        {data?.total_physical_cores ?? 'Unknown'}\n      </Descriptions.Item>\n      <Descriptions.Item\n        span={2}\n        label={t('cluster_info.list.statistics.field.logical_cores')}\n      >\n        {data?.total_logical_cores ?? 'Unknown'}\n      </Descriptions.Item>\n    </Descriptions>\n  )\n}\n\nexport default function Statistics() {\n  const ctx = useContext(ClusterInfoContext)\n\n  const { data, isLoading, error } = useClientRequest(\n    ctx!.ds.clusterInfoGetStatistics\n  )\n  const { t } = useTranslation()\n\n  return (\n    <AnimatedSkeleton showSkeleton={isLoading}>\n      {error && <ErrorBar errors={[error]} />}\n      {data && (\n        <div className={styles.content}>\n          {(data.probe_failure_hosts ?? 0) > 0 && (\n            <Card>\n              <Alert\n                message={t(\n                  'cluster_info.list.statistics.message.instance_down',\n                  { n: data.probe_failure_hosts ?? 0 }\n                )}\n                type=\"warning\"\n                showIcon\n              />\n            </Card>\n          )}\n          <Card title={t('cluster_info.list.statistics.summary_title')}>\n            <Descriptions>\n              <Descriptions.Item\n                span={2}\n                label={t('cluster_info.list.statistics.field.version')}\n              >\n                {(data.versions ?? []).join(', ')}\n              </Descriptions.Item>\n            </Descriptions>\n            <PartialInfo data={data.total_stats} />\n          </Card>\n          <Card>\n            <Alert\n              message={t('cluster_info.list.statistics.message.sub_statistics')}\n              type=\"info\"\n              showIcon\n            />\n          </Card>\n          {InstanceKinds.map((ik) => {\n            const d = data.stats_by_instance_kind?.[ik]\n            const instNum = d?.number_of_instances ?? 0\n            return instNum > 0 ? (\n              <Card title={instanceKindName(ik)} key={ik}>\n                <PartialInfo data={d} />\n              </Card>\n            ) : null\n          })}\n        </div>\n      )}\n    </AnimatedSkeleton>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/StoreLocation.tsx",
    "content": "import React, { useContext, useMemo } from 'react'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { AnimatedSkeleton, ErrorBar } from '@lib/components'\nimport StoreLocationTree, {\n  buildTreeData,\n  getShortStrMap\n} from './StoreLocationTree'\nimport { ClusterInfoContext } from '../context'\n\nexport default function StoreLocation() {\n  const ctx = useContext(ClusterInfoContext)\n\n  const { data, isLoading, error, sendRequest } = useClientRequest(\n    ctx!.ds.getStoreLocationTopology\n  )\n  const treeData = useMemo(() => buildTreeData(data), [data])\n  const shortStrMap = useMemo(() => getShortStrMap(data), [data])\n\n  return (\n    <div>\n      <ErrorBar errors={[error]} />\n      <AnimatedSkeleton showSkeleton={isLoading}>\n        <StoreLocationTree\n          dataSource={treeData}\n          shortStrMap={shortStrMap}\n          getMinHeight={\n            () => document.documentElement.clientHeight - 80 - 48 * 2 // 48 = margin of cardInner\n          }\n          onReload={sendRequest}\n        />\n      </AnimatedSkeleton>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/StoreLocationTree/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.tooltip {\n  opacity: 0;\n  position: absolute;\n  top: 0;\n  left: 0;\n  padding: @padding-xs @padding-md;\n  background: white;\n  text-align: center;\n  line-height: @line-height-base;\n  border-radius: @border-radius-base;\n  z-index: 10;\n  transition: all 0.1s ease-out;\n  pointer-events: none;\n  box-shadow: @box-shadow-base;\n\n  &::before {\n    content: '';\n    position: absolute;\n    bottom: 0;\n    left: 50%;\n    width: 12px;\n    height: 12px;\n    background: white;\n    border: 1px solid #ddd;\n    border-top-color: transparent;\n    border-left-color: transparent;\n    transform: translate(-50%, 50%) rotate(45deg);\n    transform-origin: center center;\n    z-index: 10;\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/StoreLocationTree/index.stories.tsx",
    "content": "import React from 'react'\nimport StoreLocationTree, {\n  buildTreeData,\n  trimDuplicate,\n  getShortStrMap\n} from '.'\n\nexport default {\n  title: 'StoreLocationTree'\n}\n\nconst dataSource1 = {\n  name: 'Stores',\n  value: '',\n  children: [\n    {\n      name: 'zone',\n      value: 'sh',\n      children: [\n        {\n          name: 'rack',\n          value: 'r1',\n          children: [\n            {\n              name: 'host',\n              value: 'h1',\n              children: [\n                {\n                  name: 'TiKV',\n                  value: '127.0.0.1:20160',\n                  children: []\n                }\n              ]\n            },\n            {\n              name: 'host',\n              value: 'h2',\n              children: [\n                {\n                  name: 'TiKV',\n                  value: '127.0.0.1:20162',\n                  children: []\n                }\n              ]\n            }\n          ]\n        }\n      ]\n    },\n    {\n      name: 'zone',\n      value: 'bj',\n      children: [\n        {\n          name: 'rack',\n          value: 'r1',\n          children: [\n            {\n              name: 'host',\n              value: 'h1',\n              children: [\n                {\n                  name: 'TiKV',\n                  value: '127.0.0.1:20161',\n                  children: []\n                }\n              ]\n            }\n          ]\n        },\n        {\n          name: 'TiFlash',\n          value: '127.0.0.1:3930',\n          children: []\n        }\n      ]\n    }\n  ]\n}\n\nexport const Normal = () => <StoreLocationTree dataSource={dataSource1} />\n\nconst dataSource2 = {\n  name: 'Stores',\n  value: '',\n  children: [\n    {\n      name: 'failure-domain.beta.kubernetes.io/region',\n      value: 'us-west1',\n      children: [\n        {\n          name: 'failure-domain.beta.kubernetes.io/zone',\n          value: 'us-west1-a',\n          children: [\n            {\n              name: 'kubernetes.io/hostname',\n              value:\n                'shoot--stating--a13df0bd-56f54530-z1-111111-tkq7r.internal',\n              children: [\n                {\n                  name: 'TiFlash',\n                  value: 'db-tiflash-0.db-tiflash-peer.tidb1373',\n                  children: []\n                }\n              ]\n            },\n            {\n              name: 'kubernetes.io/hostname',\n              value:\n                'shoot--stating--a13df0bd-b8cdec65-z1-22222-fdsaf.internal',\n              children: [\n                {\n                  name: 'TiKV',\n                  value: 'db-tikv-0.db-tikv-peer.tidb1373',\n                  children: []\n                }\n              ]\n            }\n          ]\n        },\n        {\n          name: 'failure-domain.beta.kubernetes.io/zone',\n          value: 'us-west1-b',\n          children: [\n            {\n              name: 'kubernetes.io/hostname',\n              value:\n                'shoot--stating--a13df0bd-xxxxxxxxxx-z1-33333-xxxxx.internal',\n              children: [\n                {\n                  name: 'TiKV',\n                  value: 'db-tikv-1.db-tikv-peer.tidb1373',\n                  children: []\n                }\n              ]\n            }\n          ]\n        },\n        {\n          name: 'failure-domain.beta.kubernetes.io/zone',\n          value: 'us-west1-c',\n          children: [\n            {\n              name: 'kubernetes.io/hostname',\n              value: 'shoot--stating--a13df0bd-yyyyy-z1-33333-mmmm.internal',\n              children: [\n                {\n                  name: 'TiKV',\n                  value: 'db-tikv-2.db-tikv-peer.tidb1373',\n                  children: []\n                }\n              ]\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}\n\nexport const Kubernetes = () => <StoreLocationTree dataSource={dataSource2} />\n\n/////////////////////////////\n\nconst arr1 = [\n  'aaa-bbbb-111a.abc.123',\n  'aaa-bbbb-222a.abc.123',\n  'aaa-bbbb-333a.abc.123'\n]\nconst arr2 = ['aaa-111a.abc.123', 'aaa-222a.abc.123', 'aaa-333a.abc.123']\nconst arr3 = []\nconst arr4 = ['abc']\nconst arr5 = ['abcd', 'abce']\nconsole.log(trimDuplicate(arr1))\nconsole.log(trimDuplicate(arr2))\nconsole.log(trimDuplicate(arr3))\nconsole.log(trimDuplicate(arr4))\nconsole.log(trimDuplicate(arr5))\n\n/////////////////////////////\n\nconst data1 = {\n  location_labels: [\n    'failure-domain.beta.kubernetes.io/region',\n    'failure-domain.beta.kubernetes.io/zone',\n    'kubernetes.io/hostname'\n  ],\n  stores: [\n    {\n      address: 'db-tiflash-0.db-tiflash-peer.tidb1373',\n      labels: {\n        engine: 'tiflash',\n        'failure-domain.beta.kubernetes.io/region': 'us-west1',\n        'failure-domain.beta.kubernetes.io/zone': 'us-west1-a',\n        'kubernetes.io/hostname':\n          'shoot--stating--a13df0bd-56f54530-z1-111111-tkq7r.internal'\n      }\n    },\n    {\n      address: 'db-tikv-0.db-tikv-peer.tidb1373',\n      labels: {\n        engine: '',\n        'failure-domain.beta.kubernetes.io/region': 'us-west1',\n        'failure-domain.beta.kubernetes.io/zone': 'us-west1-a',\n        'kubernetes.io/hostname':\n          'shoot--stating--a13df0bd-b8cdec65-z1-22222-fdsaf.internal'\n      }\n    },\n    {\n      address: 'db-tikv-1.db-tikv-peer.tidb1373',\n      labels: {\n        engine: '',\n        'failure-domain.beta.kubernetes.io/region': 'us-west1',\n        'failure-domain.beta.kubernetes.io/zone': 'us-west1-b',\n        'kubernetes.io/hostname':\n          'shoot--stating--a13df0bd-xxxxxxxxxx-z1-33333-xxxxx.internal'\n      }\n    },\n    {\n      address: 'db-tikv-2.db-tikv-peer.tidb1373',\n      labels: {\n        engine: '',\n        'failure-domain.beta.kubernetes.io/region': 'us-west1',\n        'failure-domain.beta.kubernetes.io/zone': 'us-west1-c',\n        'kubernetes.io/hostname':\n          'shoot--stating--a13df0bd-yyyyy-z1-33333-mmmm.internal'\n      }\n    }\n  ]\n}\n\nconst dataSource = buildTreeData(data1)\nconst shortStrMap = getShortStrMap(data1)\nconsole.log(shortStrMap)\n\nexport const KubernetesByShort = () => (\n  <StoreLocationTree dataSource={dataSource} shortStrMap={shortStrMap} />\n)\n\n/////////////////////////////\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/components/StoreLocationTree/index.tsx",
    "content": "import React, { useRef, useEffect } from 'react'\nimport * as d3 from 'd3'\nimport {\n  ZoomInOutlined,\n  ZoomOutOutlined,\n  ReloadOutlined\n} from '@ant-design/icons'\nimport { Space } from 'antd'\nimport { cyan, magenta, grey } from '@ant-design/colors'\nimport { useTranslation } from 'react-i18next'\n\nimport { TopologyStoreLocation } from '@lib/client'\n\nimport styles from './index.module.less'\nimport { instanceKindName } from '@lib/utils/instanceTable'\n\n//////////////////////////////////////\n\ntype ShortStrMap = Record<string, string>\n\nexport function getShortStrMap(\n  data: TopologyStoreLocation | undefined\n): ShortStrMap {\n  let allShortStrMap: ShortStrMap = {}\n\n  if (data === undefined) {\n    return allShortStrMap\n  }\n\n  // location labels\n  // failure-domain.beta.kubernetes.io/region => region\n  data.location_labels?.forEach((label) => {\n    if (label.indexOf('/') >= 0) {\n      const shortStr = label.split('/').pop()\n      if (shortStr) {\n        allShortStrMap[label] = shortStr\n      }\n    }\n  })\n\n  // location labels value\n  data.location_labels?.forEach((label) => {\n    // get label values\n    const labelValues: string[] = []\n    data.stores?.forEach((store) => {\n      const val = store.labels?.[label]\n      if (val) {\n        labelValues.push(val)\n      }\n    })\n    const shortStrMap = trimDuplicate(labelValues)\n    allShortStrMap = Object.assign(allShortStrMap, shortStrMap)\n  })\n\n  // tikv & tiflash nodes address\n  const addresses = (data.stores || []).map((s) => s.address!)\n  addresses.forEach((addr) => {\n    if (addr.startsWith('db-')) {\n      const shortStr = addr.split('.').shift()\n      if (shortStr) {\n        allShortStrMap[addr] = shortStr\n      }\n    }\n  })\n\n  return allShortStrMap\n}\n\n// input: ['aaa-111a.abc.123', 'aaa-222a.abc.123', 'aaa-333a.abc.123'], items in the array have either the same prefix or suffix, or both.\n// output:\n// {\n//   \"aaa-111a.abc.123\":\"111a\",\n//   \"aaa-222a.abc.123\":\"222a\",\n//   \"aaa-333a.abc.123\":\"333a\"\n// }\nexport function trimDuplicate(strArr: string[]): ShortStrMap {\n  const shortStrMap: ShortStrMap = {}\n  const strSet = new Set(strArr)\n  if (strSet.size < 2) {\n    return shortStrMap\n  }\n\n  let i = 0\n  let c\n  const charSet = new Set()\n  // calc the prefix length\n  let headDotOrMinusPos = -1\n  while (true) {\n    charSet.clear()\n    for (let str of strSet) {\n      c = str[i]\n      if (c === undefined) {\n        break\n      }\n      charSet.add(c)\n    }\n    if (c === undefined) {\n      break\n    }\n    if (charSet.size > 1) {\n      break\n    }\n    if (c === '.' || c === '-') {\n      headDotOrMinusPos = i\n    }\n    i++\n  }\n\n  // calc the suffix length\n  i = 0\n  let tailDotOrMinusPos = -1\n  while (true) {\n    charSet.clear()\n    for (let str of strSet) {\n      c = str[str.length - 1 - i]\n      if (c === undefined) {\n        break\n      }\n      charSet.add(c)\n    }\n    if (c === undefined) {\n      break\n    }\n    if (charSet.size > 1) {\n      break\n    }\n    if (c === '.' || c === '-') {\n      tailDotOrMinusPos = i\n    }\n    i++\n  }\n\n  if (headDotOrMinusPos === -1 && tailDotOrMinusPos === -1) {\n    return shortStrMap\n  }\n  strSet.forEach((s) => {\n    const startIdx = headDotOrMinusPos + 1\n    const endIdx =\n      tailDotOrMinusPos === -1 ? s.length : s.length - 1 - tailDotOrMinusPos\n    const short = s.slice(startIdx, endIdx)\n    shortStrMap[s] = short\n  })\n\n  return shortStrMap\n}\n\n//////////////////////////////////////\n\nconst NODE_STORES = 'Stores'\nconst NODE_TIFLASH = () => instanceKindName('tiflash')\nconst NODE_TIKV = () => instanceKindName('tikv')\n\ntype TreeNode = {\n  name: string\n  value: string\n  children: TreeNode[]\n}\n\nexport function buildTreeData(\n  data: TopologyStoreLocation | undefined\n): TreeNode {\n  const treeData: TreeNode = { name: NODE_STORES, value: '', children: [] }\n\n  if ((data?.location_labels?.length || 0) > 0) {\n    const locationLabels: string[] = data?.location_labels || []\n\n    for (const store of data?.stores || []) {\n      // reset curNode, point to tree nodes beginning\n      let curNode = treeData\n      for (const curLabel of locationLabels) {\n        const curLabelVal = store.labels![curLabel]\n        if (curLabelVal === undefined) {\n          continue\n        }\n        let subNode: TreeNode | undefined = curNode.children.find(\n          (el) => el.name === curLabel && el.value === curLabelVal\n        )\n        if (subNode === undefined) {\n          subNode = { name: curLabel, value: curLabelVal, children: [] }\n          curNode.children.push(subNode)\n        }\n        // make curNode point to subNode\n        curNode = subNode\n      }\n      const storeType =\n        store.labels!['engine'] === 'tiflash' ? NODE_TIFLASH() : NODE_TIKV()\n      curNode.children.push({\n        name: storeType,\n        value: store.address!,\n        children: []\n      })\n    }\n  }\n  return treeData\n}\n\n//////////////////////////////////////\n\ninterface ITooltipConfig {\n  enable: boolean\n  offsetX: number\n  offsetY: number\n}\n\nexport interface IStoreLocationProps {\n  dataSource: any\n  shortStrMap?: ShortStrMap\n  getMinHeight?: () => number\n  onReload?: () => void\n}\n\nconst MAX_STR_LENGTH = 16\n\nconst margin = { left: 60, right: 40, top: 80, bottom: 100 }\nconst dx = 40\n\nconst diagonal = d3\n  .linkHorizontal()\n  .x((d: any) => d.y)\n  .y((d: any) => d.x)\n\nfunction calcHeight(root) {\n  let x0 = Infinity\n  let x1 = -x0\n  root.each((d) => {\n    if (d.x > x1) x1 = d.x\n    if (d.x < x0) x0 = d.x\n  })\n  return x1 - x0\n}\n\nexport default function StoreLocationTree({\n  dataSource,\n  shortStrMap = {},\n  getMinHeight,\n  onReload\n}: IStoreLocationProps) {\n  const divRef = useRef<HTMLDivElement>(null)\n  const { t } = useTranslation()\n\n  const tooltipConfig = useRef<ITooltipConfig>()\n  tooltipConfig.current = {\n    enable: true,\n    offsetX: 0,\n    offsetY: 0\n  }\n\n  useEffect(() => {\n    let divWidth = divRef.current?.clientWidth || 0\n    const root = d3.hierarchy(dataSource) as any\n    root.descendants().forEach((d, i) => {\n      d.id = i\n      d._children = d.children\n      // collapse all nodes default\n      // if (d.depth) d.children = null\n    })\n    const dy = divWidth / (root.height + 2)\n    let tree = d3.tree().nodeSize([dx, dy])\n\n    const div = d3.select(divRef.current)\n    div.select('svg#slt').remove()\n    const svg = div\n      .append('svg')\n      .attr('id', 'slt')\n      .attr('width', divWidth)\n      .attr('height', dx + margin.top + margin.bottom)\n      .style('font', '14px sans-serif')\n      .style('user-select', 'none')\n\n    const bound = svg\n      .append('g')\n      .attr('transform', `translate(${margin.left}, ${margin.top})`)\n    const gLink = bound\n      .append('g')\n      .attr('fill', 'none')\n      .attr('stroke', '#ddd')\n      .attr('stroke-width', 2)\n    const gNode = bound\n      .append('g')\n      .attr('cursor', 'pointer')\n      .attr('pointer-events', 'all')\n\n    // tooltip\n    const tooltip = d3.select('#store-location-tooltip')\n    // zoom\n    const zoom = d3\n      .zoom()\n      .scaleExtent([0.1, 5])\n      .filter(function () {\n        // ref: https://godbasin.github.io/2018/02/07/d3-tree-notes-4-zoom-amd-drag/\n        // only zoom when pressing CTRL\n        const isWheelEvent = d3.event instanceof WheelEvent\n        return !isWheelEvent || (isWheelEvent && d3.event.ctrlKey)\n      })\n      .on('start', () => {\n        // hide tooltip if it shows\n        tooltip.style('opacity', 0)\n        tooltipConfig.current!.enable = false\n      })\n      .on('zoom', () => {\n        const t = d3.event.transform\n        bound.attr(\n          'transform',\n          `translate(${t.x + margin.left}, ${t.y + margin.top}) scale(${t.k})`\n        )\n        // this will cause unexpected result when dragging\n        // svg.attr('transform', d3.event.transform)\n      })\n      .on('end', () => {\n        const t = d3.event.transform\n        tooltipConfig.current = {\n          enable: t.k === 1, // disable tooltip if zoom\n          offsetX: t.x,\n          offsetY: t.y\n        }\n      })\n    svg.call(zoom as any)\n\n    // zoom actions\n    d3.select('#slt-zoom-in').on('click', function () {\n      zoom.scaleBy(svg.transition().duration(500) as any, 1.2)\n    })\n    d3.select('#slt-zoom-out').on('click', function () {\n      zoom.scaleBy(svg.transition().duration(500) as any, 0.8)\n    })\n    d3.select('#slt-zoom-reset').on('click', function () {\n      // https://stackoverflow.com/a/51981636/2998877\n      svg\n        .transition()\n        .duration(500)\n        .call(zoom.transform as any, d3.zoomIdentity)\n      onReload?.()\n    })\n\n    update(root)\n\n    function update(source) {\n      // use altKey to slow down the animation, interesting!\n      const duration = d3.event && d3.event.altKey ? 2500 : 500\n      const nodes = root.descendants().reverse()\n      const links = root.links()\n\n      // compute the new tree layout\n      // it modifies root self\n      tree(root)\n      const boundHeight = calcHeight(root)\n      // node.x represent the y axes position actually\n      // [root.y, root.x] is [0, 0], we need to move it to [0, boundHeight/2]\n      root.descendants().forEach((d, i) => {\n        d.x += boundHeight / 2\n      })\n      if (root.x0 === undefined) {\n        // initial root.x0, root.y0, only need to set it once\n        root.x0 = root.x\n        root.y0 = root.y\n      }\n\n      const contentHeight = boundHeight + margin.top + margin.bottom\n\n      const transition = svg\n        .transition()\n        .duration(duration)\n        .attr('width', divWidth)\n        .attr('height', Math.max(getMinHeight?.() || 0, contentHeight))\n\n      // update the nodes\n      const node = gNode.selectAll('g').data(nodes, (d: any) => d.id)\n\n      // enter any new nodes at the parent's previous position\n      const nodeEnter = node\n        .enter()\n        .append('g')\n        .attr('transform', (_d) => `translate(${source.y0},${source.x0})`)\n        .attr('fill-opacity', 0)\n        .attr('stroke-opacity', 0)\n        .on('click', (d: any) => {\n          d.children = d.children ? null : d._children\n          update(d)\n        })\n        .on('mouseenter', onMouseEnter)\n        .on('mouseleave', onMouseLeave)\n\n      function onMouseEnter(datum) {\n        if (!tooltipConfig.current?.enable) {\n          return\n        }\n\n        const { name, value } = datum.data\n        if (\n          shortStrMap[name] === undefined &&\n          shortStrMap[value] === undefined\n        ) {\n          return\n        }\n\n        tooltip.select('#store-location-tooltip-name').text(name)\n        tooltip.select('#store-location-tooltip-value').text(value)\n\n        const x = datum.y + margin.left + tooltipConfig.current.offsetX\n        const y = datum.x + margin.top - 20 + tooltipConfig.current.offsetY\n        tooltip.style(\n          'transform',\n          `translate(calc(-50% + ${x}px), calc(-100% + ${y}px))`\n        )\n\n        tooltip.style('opacity', 1)\n      }\n      function onMouseLeave() {\n        tooltip.style('opacity', 0)\n      }\n\n      // circle\n      nodeEnter\n        .append('circle')\n        .attr('r', 8)\n        .attr('fill', '#fff')\n        .attr('stroke', (d: any) => {\n          if (d._children) {\n            return grey[1]\n          }\n          if (d.data.name === NODE_TIFLASH()) {\n            return magenta[4]\n          }\n          return cyan[5]\n        })\n        .attr('stroke-width', 3)\n\n      // text for root node\n      nodeEnter\n        .filter(({ data: { name } }: any) => name === NODE_STORES)\n        .append('text')\n        .attr('dy', '0.31em')\n        .attr('x', -15)\n        .attr('text-anchor', 'end')\n        .text(({ data: { name } }: any) => name)\n\n      // text for non-root and non-leaf nodes\n      const middleNodeText = nodeEnter\n        .filter(\n          ({ data: { name } }: any) =>\n            name !== NODE_STORES &&\n            name !== NODE_TIFLASH() &&\n            name !== NODE_TIKV()\n        )\n        .append('text')\n      middleNodeText\n        .append('tspan')\n        .text(({ data: { name } }: any) => shortStrMap[name] ?? name)\n        .attr('x', -15)\n        .attr('dy', '-0.2em')\n        .attr('text-anchor', 'end')\n      middleNodeText\n        .append('tspan')\n        .text(({ data: { value } }: any) => {\n          if (value.length <= MAX_STR_LENGTH) {\n            return value\n          }\n          let shortStr = shortStrMap[value] ?? value\n          if (shortStr.length > MAX_STR_LENGTH) {\n            const midIdx = Math.round(MAX_STR_LENGTH / 2) - 1\n            shortStr =\n              shortStr.slice(0, midIdx) +\n              '..' +\n              shortStr.slice(shortStr.length - midIdx, shortStr.length)\n          }\n          return shortStr\n        })\n        .attr('x', -15)\n        .attr('dy', '1em')\n        .attr('text-anchor', 'end')\n\n      // text for leaf nodes\n      const leafNodeText = nodeEnter\n        .filter(\n          ({ data: { name } }: any) =>\n            name === NODE_TIFLASH() || name === NODE_TIKV()\n        )\n        .append('text')\n      leafNodeText\n        .append('tspan')\n        .text(({ data: { name } }: any) => name)\n        .attr('x', 15)\n        .attr('dy', '-0.2em')\n      leafNodeText\n        .append('tspan')\n        .text(({ data: { value } }: any) => shortStrMap[value] ?? value)\n        .attr('x', 15)\n        .attr('dy', '1em')\n\n      // transition nodes to their new position\n      node\n        .merge(nodeEnter as any)\n        .transition(transition as any)\n        .attr('transform', (d: any) => `translate(${d.y},${d.x})`)\n        .attr('fill-opacity', 1)\n        .attr('stroke-opacity', 1)\n\n      // transition exiting nodes to the parent's new position\n      node\n        .exit()\n        .transition(transition as any)\n        .remove()\n        .attr('transform', (d) => `translate(${source.y},${source.x})`)\n        .attr('fill-opacity', 0)\n        .attr('stroke-opacity', 0)\n\n      // update the links\n      const link = gLink.selectAll('path').data(links, (d: any) => d.target.id)\n\n      // enter any new links at the parent's previous position\n      const linkEnter = link\n        .enter()\n        .append('path')\n        .attr('d', (_d) => {\n          const o = { x: source.x0, y: source.y0 }\n          return diagonal({ source: o, target: o } as any)\n        })\n\n      // transition links to their new position\n      link\n        .merge(linkEnter as any)\n        .transition(transition as any)\n        .attr('d', diagonal as any)\n\n      // transition exiting nodes to the parent's new position\n      link\n        .exit()\n        .transition(transition as any)\n        .remove()\n        .attr('d', (_d) => {\n          const o = { x: source.x, y: source.y }\n          return diagonal({ source: o, target: o } as any)\n        })\n\n      // stash the old positions for transition\n      root.eachBefore((d) => {\n        d.x0 = d.x\n        d.y0 = d.y\n      })\n    }\n\n    function resizeHandler() {\n      divWidth = divRef.current?.clientWidth || 0\n      const dy = divWidth / (root.height + 2)\n      tree = d3.tree().nodeSize([dx, dy])\n      update(root)\n    }\n\n    window.addEventListener('resize', resizeHandler)\n    return () => {\n      window.removeEventListener('resize', resizeHandler)\n    }\n  }, [dataSource, getMinHeight, onReload, shortStrMap])\n\n  return (\n    <div ref={divRef} style={{ position: 'relative' }}>\n      <Space\n        style={{\n          cursor: 'pointer',\n          fontSize: 18,\n          position: 'absolute'\n        }}\n      >\n        <ReloadOutlined id=\"slt-zoom-reset\" />\n        <ZoomInOutlined id=\"slt-zoom-in\" />\n        <ZoomOutOutlined id=\"slt-zoom-out\" />\n        <span\n          style={{\n            fontStyle: 'italic',\n            fontSize: 12,\n            display: 'block',\n            margin: '0 auto'\n          }}\n        >\n          *{t('cluster_info.list.store_topology.tooltip')}\n        </span>\n      </Space>\n\n      <div id=\"store-location-tooltip\" className={styles.tooltip}>\n        <div id=\"store-location-tooltip-name\"></div>\n        <div id=\"store-location-tooltip-value\"></div>\n      </div>\n    </div>\n  )\n}\n\n// refs:\n// https://observablehq.com/@d3/tidy-tree\n// https://observablehq.com/@d3/collapsible-tree\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport {\n  ClusterinfoGetHostsInfoResponse,\n  TopologyStoreLocation,\n  TopologyTiDBInfo,\n  ClusterinfoStoreTopologyResponse,\n  TopologyPDInfo,\n  ClusterinfoClusterStatistics,\n  TopologyTiCDCInfo,\n  TopologyTiProxyInfo,\n  TopologyTSOInfo,\n  TopologySchedulingInfo\n} from '@lib/client'\n\nimport { IContextConfig, ReqConfig } from '@lib/types'\n\nexport interface IClusterInfoDataSource {\n  clusterInfoGetHostsInfo(\n    options?: ReqConfig\n  ): AxiosPromise<ClusterinfoGetHostsInfoResponse>\n\n  getStoreLocationTopology(\n    options?: ReqConfig\n  ): AxiosPromise<TopologyStoreLocation>\n\n  getTiDBTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTiDBInfo>>\n\n  getStoreTopology(\n    options?: ReqConfig\n  ): AxiosPromise<ClusterinfoStoreTopologyResponse>\n\n  getPDTopology(options?: ReqConfig): AxiosPromise<Array<TopologyPDInfo>>\n\n  getTiCDCTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTiCDCInfo>>\n\n  getTiProxyTopology(\n    options?: ReqConfig\n  ): AxiosPromise<Array<TopologyTiProxyInfo>>\n\n  getTSOTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTSOInfo>>\n\n  getSchedulingTopology(\n    options?: ReqConfig\n  ): AxiosPromise<Array<TopologySchedulingInfo>>\n\n  topologyTidbAddressDelete(\n    address: string,\n    options?: ReqConfig\n  ): AxiosPromise<void>\n\n  clusterInfoGetStatistics(\n    options?: ReqConfig\n  ): AxiosPromise<ClusterinfoClusterStatistics>\n}\n\nexport interface IClusterInfoContext {\n  ds: IClusterInfoDataSource\n  cfg: IContextConfig\n}\n\nexport const ClusterInfoContext = createContext<IClusterInfoContext | null>(\n  null\n)\n\nexport const ClusterInfoProvider = ClusterInfoContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { HashRouter as Router, Route, Routes, Navigate } from 'react-router-dom'\n\nimport { Root } from '@lib/components'\nimport ListPage from './pages/List'\n\nimport { addTranslations } from '@lib/utils/i18n'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\nimport { ClusterInfoContext } from './context'\nimport translations from './translations'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route\n        path=\"/cluster_info\"\n        element={<Navigate to=\"/cluster_info/instance\" replace />}\n      />\n      <Route path=\"/cluster_info/:tabKey\" element={<ListPage />} />\n    </Routes>\n  )\n}\n\nconst App = () => {\n  const ctx = useContext(ClusterInfoContext)\n  if (ctx === null) {\n    throw new Error('ClusterInfoContext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport default App\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/pages/List.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.card_tab_navs {\n  padding-left: @padding-page; // 48px\n  padding-right: @padding-page; // 48px\n  height: @padding-page; // 48px\n  margin-bottom: @padding-md; // 16px\n  border-bottom: 1px solid @gray-4;\n\n  :global {\n    .ant-tabs-ink-bar {\n      height: @outline-width; // 2px\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/pages/List.tsx",
    "content": "import { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'\nimport React from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useNavigate, useParams } from 'react-router-dom'\n\nimport { Card } from '@lib/components'\nimport CardTabs from '@lib/components/CardTabs'\n\nimport InstanceTable from '../components/InstanceTable'\nimport HostTable from '../components/HostTable'\nimport DiskTable from '../components/DiskTable'\nimport StoreLocation from '../components/StoreLocation'\nimport Statistics from '../components/Statistics'\n\nimport styles from './List.module.less'\n\nfunction renderTabBar(props, DefaultTabBar) {\n  return (\n    <Sticky stickyPosition={StickyPositionType.Header}>\n      <DefaultTabBar {...props} className={styles.card_tab_navs} />\n    </Sticky>\n  )\n}\n\nexport default function ListPage() {\n  const { tabKey } = useParams()\n  const navigate = useNavigate()\n  const { t } = useTranslation()\n\n  const tabs = [\n    {\n      key: 'instance',\n      title: t('cluster_info.list.instance_table.title'),\n      content: () => <InstanceTable />\n    },\n    {\n      key: 'host',\n      title: t('cluster_info.list.host_table.title'),\n      content: () => <HostTable />\n    },\n    {\n      key: 'disk',\n      title: t('cluster_info.list.disk_table.title'),\n      content: () => <DiskTable />\n    },\n    {\n      key: 'store_topology',\n      title: t('cluster_info.list.store_topology.title'),\n      content: () => <StoreLocation />\n    },\n    {\n      key: 'statistics',\n      title: t('cluster_info.list.statistics.title'),\n      content: () => <Statistics />\n    }\n  ]\n\n  return (\n    <ScrollablePane style={{ height: '100vh' }}>\n      <Card>\n        <CardTabs\n          defaultActiveKey={tabKey}\n          onChange={(key) => {\n            navigate(`/cluster_info/${key}`)\n          }}\n          renderTabBar={renderTabBar}\n          animated={false}\n          tabs={tabs}\n        />\n      </Card>\n    </ScrollablePane>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/status/status.ts",
    "content": "export const STATUS_UNREACHABLE = 0\nexport const STATUS_UP = 1\nexport const STATUS_TOMBSTONE = 2\nexport const STATUS_OFFLINE = 3\nexport const STATUS_DOWN = 4\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/translations/en.yaml",
    "content": "cluster_info:\n  nav_title: Cluster Info\n  list:\n    instance_table:\n      title: Instances\n      columns:\n        node: Address\n        version: Version\n        status: Status\n        up_time: Up Time\n        deploy_path: Deployment Directory\n        git_hash: Git Hash\n      actions:\n        hide_db:\n          tooltip: Hide\n          confirm: Do you want to hide this {{distro.tidb}} instance?\n    host_table:\n      title: Hosts\n      columns:\n        host: Host Address\n        cpu: CPU\n        cpu_arch: CPU Arch\n        cpu_usage: CPU Usage\n        memory: Memory\n        memory_usage: Memory Usage\n        instances: Instances\n      instanceUnavailable: Failed to get the host information\n    disk_table:\n      title: Disks\n      columns:\n        host: Host Address\n        mount_dir: Mount Directory\n        fs: File System\n        disk_size: Disk Capacity\n        disk_usage: Disk Usage\n        instances: Instances\n    store_topology:\n      title: Store Topology\n      tooltip: You can also zoom in or out by pressing CTRL and scrolling mouse wheel\n    statistics:\n      title: Statistics\n      summary_title: Cluster Summary\n      field:\n        version: Version\n        instances: '# Instances'\n        hosts: '# Hosts that instances deployed'\n        memory_capacity: Σ Memory capacity (of all hosts)\n        physical_cores: Σ CPU physical cores (of all hosts)\n        logical_cores: Σ CPU logical cores (of all hosts)\n      message:\n        instance_down: 'Some instances are down in {{n}} host(s) so that host related information may be inccurate.'\n        sub_statistics: Sub-statistics below are counted by instance kinds. The sum of host metrics in sub-statistics can be larger \"Cluster Summary\" when different instances are deployed in the same host.\n  error:\n    load: 'Load component {{comp}} error: {{cause}}'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ClusterInfo/translations/zh.yaml",
    "content": "cluster_info:\n  nav_title: 集群信息\n  list:\n    instance_table:\n      title: 实例\n      columns:\n        node: 地址\n        version: 版本\n        status: 状态\n        up_time: 启动时间\n        deploy_path: 部署路径\n        git_hash: Git 哈希值\n      actions:\n        hide_db:\n          tooltip: 隐藏\n          confirm: 您确认要隐藏该 {{distro.tidb}} 实例吗?\n    host_table:\n      title: 主机\n      columns:\n        host: 主机地址\n        cpu: CPU\n        cpu_arch: CPU 架构\n        cpu_usage: CPU 使用率\n        memory: 物理内存\n        memory_usage: 内存使用率\n        instances: 实例\n      instanceUnavailable: 获取主机信息失败\n    disk_table:\n      title: 磁盘\n      columns:\n        host: 主机地址\n        mount_dir: 磁盘挂载点\n        fs: 文件系统\n        disk_size: 磁盘容量\n        disk_usage: 磁盘使用率\n        instances: 实例\n    store_topology:\n      title: 存储拓扑\n      tooltip: 按住 Ctrl 键并滑动鼠标滚轮也可以缩放\n    statistics:\n      title: 统计\n      summary_title: 集群总计\n      field:\n        version: 版本\n        instances: 总实例数量\n        hosts: 实例部署的总机器数量\n        memory_capacity: 内存总量总和 (按实例部署的机器计算)\n        physical_cores: CPU 物理核心数总和 (按实例部署的机器计算)\n        logical_cores: CPU 逻辑核心数总和 (按实例部署的机器计算)\n      message:\n        instance_down: '由于有 {{n}} 台机器上的所有实例都未启动或无法访问，因此统计中关于机器的指标可能会不准确。'\n        sub_statistics: 子统计按不同实例类型分别计算。当一个机器上部署了不同类型实例时，以下子统计的机器指标累加起来会超过“集群总计”数量。\n  error:\n    load: '加载组件 {{comp}} 失败: {{cause}}'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Configuration/InlineEditor.tsx",
    "content": "import React, { useState, useCallback, useEffect } from 'react'\nimport { EditOutlined } from '@ant-design/icons'\nimport { Input, Popover, Button, Space, Tooltip } from 'antd'\nimport { useMemoizedFn } from 'ahooks'\n\ninterface IInlineEditorProps {\n  title?: string\n  value: any\n  displayValue: string\n  onSave?: (newValue: any) => Promise<boolean | void>\n}\n\nfunction valueWithSameType(newValue, oldValue) {\n  if (typeof oldValue === 'string') {\n    return newValue\n  } else if (typeof oldValue === 'number') {\n    // Note: `Number()` is more strict than `parseFloat()`.\n    const v = Number(newValue)\n    if (isNaN(v)) {\n      throw new Error(`\"${newValue}\" is not a number`)\n    }\n    return v\n  } else if (typeof oldValue === 'boolean') {\n    switch (String(newValue).toLowerCase().trim()) {\n      case 'true':\n      case 'yes':\n      case '1':\n        return true\n      case 'false':\n      case 'no':\n      case '0':\n        return false\n      default:\n        throw new Error(`\"${newValue}\" is not a boolean`)\n    }\n  } else {\n    // Otherwise, return as string\n    return newValue\n  }\n}\n\nfunction InlineEditor({\n  value,\n  displayValue,\n  title,\n  onSave\n}: IInlineEditorProps) {\n  const [isVisible, setIsVisible] = useState(false)\n  const [inputVal, setInputVal] = useState(displayValue)\n  const [isPosting, setIsPosting] = useState(false)\n\n  const handleCancel = useCallback(() => {\n    setIsVisible(false)\n    setInputVal(displayValue)\n  }, [displayValue])\n\n  const handleSave = useMemoizedFn(async () => {\n    if (!onSave) {\n      setIsVisible(false)\n      return\n    }\n    try {\n      setIsPosting(true)\n      // PD only accept modified config in the same value type,\n      // i.e. true => false, but not true => \"false\"\n      const r = await onSave(valueWithSameType(inputVal, value))\n      if (r !== false) {\n        // When onSave returns non-false, input value is not reverted and only popup is hidden\n        setIsVisible(false)\n      } else {\n        // When onSave returns false, popup is not hidden and value is reverted\n        setInputVal(displayValue)\n      }\n    } catch (e) {\n      setInputVal(displayValue)\n      setIsVisible(false)\n    } finally {\n      setIsPosting(false)\n    }\n  })\n\n  const handleInputValueChange = useCallback((e) => {\n    setInputVal(e.target.value)\n  }, [])\n\n  useEffect(() => {\n    setInputVal(displayValue)\n  }, [displayValue])\n\n  const renderPopover = useMemoizedFn(() => {\n    return (\n      <Space direction=\"vertical\" style={{ width: '100%' }}>\n        <div>\n          <Input\n            value={inputVal}\n            size=\"small\"\n            onChange={handleInputValueChange}\n            disabled={isPosting}\n          />\n        </div>\n        <div>\n          <Space>\n            <Button\n              type=\"primary\"\n              size=\"small\"\n              onClick={handleSave}\n              disabled={isPosting}\n            >\n              Save\n            </Button>\n            <Button size=\"small\" onClick={handleCancel} disabled={isPosting}>\n              Cancel\n            </Button>\n          </Space>\n        </div>\n      </Space>\n    )\n  })\n\n  return (\n    <Popover\n      trigger=\"click\"\n      placement=\"rightTop\"\n      content={renderPopover}\n      title={`Edit ${title ?? ''}`}\n      visible={isVisible}\n      onVisibleChange={setIsVisible}\n    >\n      <a>\n        <EditOutlined />{' '}\n        <Tooltip title={displayValue}>\n          <code>{displayValue}</code>\n        </Tooltip>\n      </a>\n    </Popover>\n  )\n}\n\nexport default InlineEditor\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Configuration/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport {\n  ConfigurationEditRequest,\n  ConfigurationEditResponse,\n  ConfigurationAllConfigItems\n} from '@lib/client'\n\nimport { ReqConfig } from '@lib/types'\n\nexport interface IConfigurationDataSource {\n  configurationEdit(\n    request: ConfigurationEditRequest,\n    options?: ReqConfig\n  ): AxiosPromise<ConfigurationEditResponse>\n\n  configurationGetAll(\n    options?: ReqConfig\n  ): AxiosPromise<ConfigurationAllConfigItems>\n}\n\nexport interface IConfigurationContext {\n  ds: IConfigurationDataSource\n}\n\nexport const ConfigurationContext = createContext<IConfigurationContext | null>(\n  null\n)\n\nexport const ConfigurationProvider = ConfigurationContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Configuration/index.tsx",
    "content": "import React, {\n  useMemo,\n  useCallback,\n  useRef,\n  useState,\n  useEffect,\n  useContext\n} from 'react'\nimport { Routes, Route, HashRouter as Router } from 'react-router-dom'\nimport { IGroup, IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'\nimport { Modal, Spin, Tooltip, Input } from 'antd'\nimport { useMemoizedFn, useDebounce } from 'ahooks'\nimport { useTranslation } from 'react-i18next'\nimport { LoadingOutlined } from '@ant-design/icons'\n\nimport { Root, CardTable, Card, Pre } from '@lib/components'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { ConfigurationItem } from '@lib/client'\nimport { addTranslations } from '@lib/utils/i18n'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\n\nimport InlineEditor from './InlineEditor'\nimport { ConfigurationContext } from './context'\nimport translations from './translations'\n\naddTranslations(translations)\n\ninterface IRow extends ConfigurationItem {\n  kind: string\n}\n\ninterface IValueProps {\n  item: IRow\n  onSaved?: () => void\n}\n\nconst loadingSpinner = <LoadingOutlined style={{ fontSize: 48 }} spin />\n\nfunction Value({ item, onSaved }: IValueProps) {\n  const ctx = useContext(ConfigurationContext)\n\n  const handleSave = useMemoizedFn(async (newValue) => {\n    try {\n      const resp = await ctx!.ds.configurationEdit({\n        id: item.id,\n        kind: item.kind,\n        new_value: newValue\n      })\n      if ((resp?.data?.warnings?.length ?? 0) > 0) {\n        Modal.warning({\n          title: 'Edit configuration is partially done',\n          content: (\n            <Pre>{resp.data.warnings?.map((w) => w.message).join('\\n\\n')}</Pre>\n          )\n        })\n      }\n    } catch (e) {\n      return false\n    }\n    onSaved?.()\n  })\n\n  const stringValue = String(item.value)\n\n  if (item.is_multi_value) {\n    return (\n      <span>\n        <i>(multiple values)</i>{' '}\n        <Tooltip title={stringValue}>\n          <code>{stringValue}</code>\n        </Tooltip>\n      </span>\n    )\n  } else if (!item.is_editable) {\n    return (\n      <Tooltip title={stringValue}>\n        <code>{stringValue}</code>\n      </Tooltip>\n    )\n  } else {\n    // Note: We preserve the original value so that newValue's type can be inferred.\n    return (\n      <InlineEditor\n        value={item.value}\n        displayValue={stringValue}\n        title={item.id}\n        onSave={handleSave}\n      />\n    )\n  }\n}\n\nfunction getKey(item: IRow) {\n  return `${item.kind}.${item.id}`\n}\n\nfunction Configuration() {\n  const ctx = useContext(ConfigurationContext)\n  if (ctx === null) {\n    throw new Error('ConfigurationContext must not be null')\n  }\n\n  const { data, isLoading, error, sendRequest } = useClientRequest(\n    ctx!.ds.configurationGetAll\n  )\n\n  const { t } = useTranslation()\n  const [filterValueLower, setFilterValueLower] = useState('')\n  const debouncedFilterValue = useDebounce(filterValueLower, { wait: 200 })\n\n  const handleSaved = useCallback(() => {\n    sendRequest()\n  }, [sendRequest])\n\n  const handleFilterChange = useCallback((e) => {\n    setFilterValueLower(e.target.value.toLowerCase())\n  }, [])\n\n  const errors = useMemo(() => {\n    if (error) {\n      return [error]\n    }\n    if (data?.errors) {\n      return data.errors\n    }\n    return []\n  }, [data, error])\n\n  const [rows, setRows] = useState<IRow[]>([])\n  const [groups, setGroups] = useState<IGroup[]>([])\n  const lastSavedGroups = useRef<IGroup[]>([])\n\n  // When data is changed, re-calculate rows and groups.\n  useEffect(() => {\n    if (!data) {\n      setRows([])\n      setGroups([])\n      lastSavedGroups.current = []\n      return\n    }\n\n    const newRows: IRow[] = []\n    const newGroups: IGroup[] = []\n    let startIndex = 0\n    for (const configKind of [\n      'tidb_variable',\n      'pd_config',\n      'tikv_config',\n      'tidb_config'\n    ]) {\n      const items = data?.items?.[configKind] ?? []\n      for (const item of items) {\n        if (debouncedFilterValue.length > 0) {\n          if (\n            item.id?.toLowerCase().indexOf(debouncedFilterValue) === -1 &&\n            String(item.value).toLowerCase().indexOf(debouncedFilterValue) ===\n              -1\n          ) {\n            continue\n          }\n        }\n        newRows.push({\n          ...item,\n          kind: configKind\n        })\n      }\n      newGroups.push({\n        key: configKind,\n        name: t(`configuration.common.kind.${configKind}`),\n        startIndex: startIndex,\n        count: newRows.length - startIndex\n      })\n      startIndex = newRows.length\n    }\n\n    setRows(newRows)\n\n    // DetailsList internally changes the group element and add new fields. When assigning new\n    // fresh groups, group states will be changed, result in UI state not preserved.\n    // Thus, we update to use new groups only when groups are different.\n    if (JSON.stringify(lastSavedGroups.current) === JSON.stringify(newGroups)) {\n      // Update group reference, otherwise DetailsList won't update\n      setGroups((g) => [...g])\n    } else {\n      setGroups(newGroups)\n      lastSavedGroups.current = JSON.parse(JSON.stringify(newGroups))\n    }\n  }, [data, debouncedFilterValue, t])\n\n  const columns = useMemo(() => {\n    const columns: IColumn[] = [\n      {\n        key: 'key',\n        name: 'Config',\n        minWidth: 300,\n        maxWidth: 300,\n        onRender: (item) => {\n          return (\n            <Tooltip title={item.id}>\n              <code>{item.id}</code>\n            </Tooltip>\n          )\n        }\n      },\n      {\n        key: 'value',\n        name: 'Value',\n        onRender: (item) => {\n          return <Value item={item} onSaved={handleSaved} />\n        },\n        minWidth: 300,\n        maxWidth: 300\n      }\n    ]\n    return columns\n  }, [handleSaved])\n\n  return (\n    <Root>\n      <ScrollablePane style={{ height: '100vh' }}>\n        <Sticky stickyPosition={StickyPositionType.Header} isScrollSynced>\n          <div style={{ display: 'flow-root' }}>\n            <Card>\n              <Input\n                placeholder=\"Filter\"\n                onChange={handleFilterChange}\n                data-e2e=\"search_config\"\n              />\n            </Card>\n          </div>\n        </Sticky>\n        <Spin indicator={loadingSpinner} spinning={isLoading && !!data}>\n          <Card noMarginTop>\n            <CardTable\n              disableSelectionZone\n              cardNoMargin\n              loading={isLoading}\n              columns={columns}\n              items={rows}\n              groups={groups}\n              errors={errors}\n              extendLastColumn\n              getKey={getKey}\n              groupProps={{\n                showEmptyGroups: true\n              }}\n            />\n          </Card>\n        </Spin>\n      </ScrollablePane>\n    </Root>\n  )\n}\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/configuration\" element={<Configuration />} />\n    </Routes>\n  )\n}\n\nexport default function () {\n  return (\n    <Router>\n      <AppRoutes />\n    </Router>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Configuration/translations/en.yaml",
    "content": "configuration:\n  nav_title: Configurations\n  common:\n    kind:\n      tidb_variable: '{{distro.tidb}} Variables'\n      pd_config: '{{distro.pd}} Configurations'\n      tikv_config: '{{distro.tikv}} Configurations'\n      tidb_config: '{{distro.tidb}} Configurations'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Configuration/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Configuration/translations/zh.yaml",
    "content": "configuration:\n  nav_title: 实例配置\n  common:\n    kind:\n      tidb_variable: '{{distro.tidb}} 变量'\n      pd_config: '{{distro.pd}} 配置'\n      tikv_config: '{{distro.tikv}} 配置'\n      tidb_config: '{{distro.tidb}} 配置'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ContinuousProfiling/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport {\n  ConprofComponent,\n  ConprofNgMonitoringConfig,\n  ConprofEstimateSizeRes,\n  ConprofGroupProfileDetail,\n  ConprofGroupProfiles,\n  TopologyTiDBInfo,\n  ClusterinfoStoreTopologyResponse,\n  TopologyPDInfo\n} from '@lib/client'\n\nimport { IContextConfig, ReqConfig } from '@lib/types'\n\nexport interface IConProfilingDataSource {\n  continuousProfilingActionTokenGet(\n    q: string,\n    options?: ReqConfig\n  ): AxiosPromise<string>\n\n  continuousProfilingComponentsGet(\n    options?: ReqConfig\n  ): AxiosPromise<Array<ConprofComponent>>\n\n  continuousProfilingConfigGet(\n    options?: ReqConfig\n  ): AxiosPromise<ConprofNgMonitoringConfig>\n\n  continuousProfilingConfigPost(\n    request: ConprofNgMonitoringConfig,\n    options?: ReqConfig\n  ): AxiosPromise<string>\n\n  continuousProfilingEstimateSizeGet(\n    options?: ReqConfig\n  ): AxiosPromise<ConprofEstimateSizeRes>\n\n  continuousProfilingGroupProfileDetailGet(\n    ts: number,\n    options?: ReqConfig\n  ): AxiosPromise<ConprofGroupProfileDetail>\n\n  continuousProfilingGroupProfilesGet(\n    beginTime?: number,\n    endTime?: number,\n    options?: ReqConfig\n  ): AxiosPromise<Array<ConprofGroupProfiles>>\n\n  getTiDBTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTiDBInfo>>\n\n  getStoreTopology(\n    options?: ReqConfig\n  ): AxiosPromise<ClusterinfoStoreTopologyResponse>\n\n  getPDTopology(options?: ReqConfig): AxiosPromise<Array<TopologyPDInfo>>\n}\n\nexport interface IConProfilingConfig extends IContextConfig {\n  publicPathBase: string\n\n  checkNgm?: boolean // default value is true\n  showSetting?: boolean // default value is true\n  enableDownloadGroup?: boolean // default value is true\n  enableDotGraph?: boolean // default value is true\n  enablePreviewGoroutine?: boolean // default value is true\n  listDuration?: number // unit hour, 1 means 1 hour, 2 means 2 hours, default value is 2 hours\n  maxDays?: number // default value is unlimited\n\n  clusterId?: string\n  deployType?: string\n}\n\nexport interface IConProfilingContext {\n  ds: IConProfilingDataSource\n  cfg: IConProfilingConfig\n}\n\nexport const ConProfilingContext = createContext<IConProfilingContext | null>(\n  null\n)\n\nexport const ConProfilingProvider = ConProfilingContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ContinuousProfiling/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { HashRouter as Router, Route, Routes } from 'react-router-dom'\n\nimport { Root, ParamsPageWrapper, NgmNotStartedGuard } from '@lib/components'\nimport { addTranslations } from '@lib/utils/i18n'\n\nimport { Detail, List } from './pages'\nimport { ConProfilingContext } from './context'\nimport translations from './translations'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n  const ctx = useContext(ConProfilingContext)\n  const checkNgm = ctx?.cfg.checkNgm ?? true\n\n  return (\n    <Routes>\n      <Route\n        path=\"/continuous_profiling\"\n        element={\n          checkNgm ? (\n            <NgmNotStartedGuard>\n              <List />\n            </NgmNotStartedGuard>\n          ) : (\n            <List />\n          )\n        }\n      />\n      <Route\n        path=\"/continuous_profiling/detail\"\n        element={\n          checkNgm ? (\n            <NgmNotStartedGuard>\n              <ParamsPageWrapper>\n                <Detail />\n              </ParamsPageWrapper>\n            </NgmNotStartedGuard>\n          ) : (\n            <ParamsPageWrapper>\n              <Detail />\n            </ParamsPageWrapper>\n          )\n        }\n      />\n    </Routes>\n  )\n}\n\nconst App = () => {\n  const ctx = useContext(ConProfilingContext)\n  if (ctx === null) {\n    throw new Error('ConProfilingContext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport default App\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ContinuousProfiling/pages/ConProfSettingForm.tsx",
    "content": "import React, { useState, useCallback, useMemo, useContext } from 'react'\nimport {\n  Form,\n  Skeleton,\n  Switch,\n  Input,\n  Space,\n  Button,\n  Modal,\n  Select\n} from 'antd'\nimport { ExclamationCircleOutlined } from '@ant-design/icons'\nimport { useTranslation, TFunction } from 'react-i18next'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\n\nimport { ConprofContinuousProfilingConfig } from '@lib/client'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { DrawerFooter, ErrorBar, InstanceSelect } from '@lib/components'\nimport { useIsWriteable } from '@lib/utils/store'\nimport { telemetry } from '../utils/telemetry'\nimport { ConProfilingContext } from '../context'\n\nconst ONE_DAY_SECONDS = 24 * 60 * 60\nconst RETENTION_SECONDS = [\n  3 * ONE_DAY_SECONDS,\n  5 * ONE_DAY_SECONDS,\n  10 * ONE_DAY_SECONDS\n]\n\nfunction translateSecToDay(seconds: number, t: TFunction) {\n  // in our case, the seconds value must be the multiple of one day seconds\n  if (seconds % ONE_DAY_SECONDS !== 0) {\n    console.warn(`${seconds} is not the mulitple of one day seconds`)\n  }\n  const day = seconds / ONE_DAY_SECONDS\n  return t('conprof.settings.profile_retention_duration_option', {\n    d: day\n  })\n}\n\ninterface Props {\n  onClose: () => void\n  onConfigUpdated: () => any\n}\n\nfunction ConProfSettingForm({ onClose, onConfigUpdated }: Props) {\n  const ctx = useContext(ConProfilingContext)\n\n  const [submitting, setSubmitting] = useState(false)\n  const { t } = useTranslation()\n  const isWriteable = useIsWriteable()\n\n  const {\n    data: initialConfig,\n    isLoading: loading,\n    error\n  } = useClientRequest(() =>\n    ctx!.ds.continuousProfilingConfigGet({ handleError: 'custom' })\n  )\n\n  const { data: estimateSize } = useClientRequest(() =>\n    ctx!.ds.continuousProfilingEstimateSizeGet({ handleError: 'custom' })\n  )\n\n  const dataRetentionSeconds = useMemo(() => {\n    const curRetentionSec =\n      initialConfig?.continuous_profiling?.data_retention_seconds\n    if (\n      curRetentionSec &&\n      RETENTION_SECONDS.indexOf(curRetentionSec) === -1 &&\n      // filter out the duration that is not multiple of ONE_DAY_SECONDS\n      curRetentionSec % ONE_DAY_SECONDS === 0\n    ) {\n      return RETENTION_SECONDS.concat(curRetentionSec).sort()\n    }\n    return RETENTION_SECONDS\n  }, [initialConfig])\n\n  const handleSubmit = useCallback(\n    (values) => {\n      async function updateConfig(values) {\n        const newConfig: ConprofContinuousProfilingConfig = {\n          enable: values.enable,\n          data_retention_seconds: values.data_retention_seconds\n        }\n        try {\n          setSubmitting(true)\n          await ctx!.ds.continuousProfilingConfigPost({\n            continuous_profiling: newConfig\n          })\n          telemetry.saveSettings(newConfig)\n          onClose()\n          onConfigUpdated()\n        } finally {\n          setSubmitting(false)\n        }\n      }\n\n      if (!values.enable) {\n        // confirm\n        Modal.confirm({\n          title: t('conprof.settings.close_feature'),\n          icon: <ExclamationCircleOutlined />,\n          content: t('conprof.settings.close_feature_confirm'),\n          okText: t('conprof.settings.actions.close'),\n          cancelText: t('conprof.settings.actions.cancel'),\n          okButtonProps: { danger: true },\n          onOk: () => updateConfig(values)\n        })\n      } else {\n        updateConfig(values)\n      }\n    },\n    [t, onClose, onConfigUpdated, ctx]\n  )\n\n  return (\n    <>\n      {error && <ErrorBar errors={[error]} />}\n      {loading && <Skeleton active={true} paragraph={{ rows: 5 }} />}\n      {!loading && initialConfig && (\n        <Form\n          layout=\"vertical\"\n          initialValues={initialConfig.continuous_profiling}\n          onFinish={handleSubmit}\n        >\n          <Form.Item\n            valuePropName=\"checked\"\n            label={t('conprof.settings.switch')}\n            extra={t('conprof.settings.switch_tooltip')}\n          >\n            <Form.Item noStyle name=\"enable\" valuePropName=\"checked\">\n              <Switch disabled={!isWriteable} />\n            </Form.Item>\n          </Form.Item>\n          <Form.Item\n            noStyle\n            shouldUpdate={(prev, cur) => prev.enable !== cur.enable}\n          >\n            {({ getFieldValue }) =>\n              getFieldValue('enable') && (\n                <>\n                  <Form.Item\n                    label={t('conprof.settings.profile_targets')}\n                    extra={t('conprof.settings.profile_targets_tooltip', {\n                      n: estimateSize?.instance_count || '?',\n                      size: estimateSize?.profile_size\n                        ? getValueFormat('decbytes')(\n                            estimateSize.profile_size,\n                            0\n                          )\n                        : '?'\n                    })}\n                  >\n                    <InstanceSelect\n                      defaultSelectAll={true}\n                      enableTiFlash={true}\n                      disabled={true}\n                      style={{ width: 200 }}\n                      getTiDBTopology={ctx!.ds.getTiDBTopology}\n                      getStoreTopology={ctx!.ds.getStoreTopology}\n                      getPDTopology={ctx!.ds.getPDTopology}\n                    />\n                  </Form.Item>\n\n                  <Form.Item\n                    label={t('conprof.settings.profile_retention_duration')}\n                    extra={t(\n                      'conprof.settings.profile_retention_duration_tooltip'\n                    )}\n                  >\n                    <Input.Group>\n                      <Form.Item noStyle name=\"data_retention_seconds\">\n                        <Select style={{ width: 180 }}>\n                          {dataRetentionSeconds.map((val) => (\n                            <Select.Option key={val} value={val}>\n                              {translateSecToDay(val, t)}\n                            </Select.Option>\n                          ))}\n                        </Select>\n                      </Form.Item>\n                    </Input.Group>\n                  </Form.Item>\n                </>\n              )\n            }\n          </Form.Item>\n          <DrawerFooter>\n            <Space>\n              <Button\n                type=\"primary\"\n                htmlType=\"submit\"\n                loading={submitting}\n                disabled={!isWriteable}\n              >\n                {t('statement.settings.actions.save')}\n              </Button>\n              <Button onClick={onClose}>\n                {t('statement.settings.actions.cancel')}\n              </Button>\n            </Space>\n          </DrawerFooter>\n        </Form>\n      )}\n    </>\n  )\n}\n\nexport default ConProfSettingForm\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ContinuousProfiling/pages/Detail.tsx",
    "content": "import { Badge, Button, Modal, Space } from 'antd'\nimport React, { useCallback, useContext, useMemo } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { Link } from 'react-router-dom'\nimport { ArrowLeftOutlined } from '@ant-design/icons'\nimport { useMemoizedFn } from 'ahooks'\nimport { upperFirst } from 'lodash'\nimport { IGroup } from 'office-ui-fabric-react/lib/DetailsList'\n\nimport { ConprofProfileDetail } from '@lib/client'\nimport { Card, CardTable, DateTime, Descriptions, Head } from '@lib/components'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { instanceKindName, InstanceKinds } from '@lib/utils/instanceTable'\nimport useQueryParams from '@lib/utils/useQueryParams'\nimport { telemetry } from '../utils/telemetry'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport { ConProfilingContext } from '../context'\n\nenum Action {\n  VIEW_FLAMEGRAPH = 'view_flamegraph',\n  VIEW_GRAPH = 'view_graph',\n  VIEW_TEXT = 'view_text',\n  DOWNLOAD = 'download'\n}\n\nconst profileTypeSortOrder: { [key: string]: number } = {\n  profile: 1,\n  heap: 2,\n  goroutine: 3,\n  mutex: 4\n}\n\nexport default function Page() {\n  const ctx = useContext(ConProfilingContext)\n\n  const enableDownloadGroup = ctx?.cfg.enableDownloadGroup ?? true\n  const enableDotGraph = ctx?.cfg.enableDotGraph ?? true\n  const enablePreviewGoroutine = ctx?.cfg.enablePreviewGoroutine ?? true\n\n  const { t } = useTranslation()\n  const { ts } = useQueryParams()\n\n  const {\n    data: groupProfileDetail,\n    isLoading: groupDetailLoading,\n    error: groupDetailError\n  } = useClientRequest((reqConfig) =>\n    ctx!.ds.continuousProfilingGroupProfileDetailGet(ts, reqConfig)\n  )\n\n  const profileDuration = groupProfileDetail?.profile_duration_secs || 0\n\n  const [tableData, groupData] = useMemo(() => {\n    const newRows: ConprofProfileDetail[] = []\n    const newGroups: IGroup[] = []\n\n    let startIndex = 0\n    const profiles = groupProfileDetail?.target_profiles || []\n    profiles.sort((a, b) => {\n      if (a.target!.component! > b.target!.component!) {\n        return 1\n      } else {\n        return (\n          (profileTypeSortOrder[a.profile_type!] ?? 0) -\n          (profileTypeSortOrder[b.profile_type!] ?? 0)\n        )\n      }\n    })\n    for (const kind of InstanceKinds) {\n      profiles.forEach((p) => {\n        if (p.target?.component === kind) {\n          newRows.push(p)\n        }\n      })\n\n      if (newRows.length - startIndex > 0) {\n        newGroups.push({\n          key: instanceKindName(kind),\n          name: instanceKindName(kind),\n          startIndex: startIndex,\n          count: newRows.length - startIndex\n        })\n        startIndex = newRows.length\n      }\n    }\n\n    return [newRows, newGroups]\n  }, [groupProfileDetail])\n\n  const handleClick = useMemoizedFn(\n    async (action: string, rec: ConprofProfileDetail) => {\n      const { profile_type, target } = rec\n      const { component, address } = target!\n      let dataFormat = ''\n      if (component === 'tikv' && profile_type === 'heap') {\n        switch (action) {\n          case Action.VIEW_FLAMEGRAPH:\n            // tikv heap flamegraph uses Brendan Gregg's collapsed stack format which is text based\n            dataFormat = 'text'\n            break\n          case Action.VIEW_GRAPH:\n            dataFormat = 'svg'\n            break\n          case Action.DOWNLOAD:\n            dataFormat = 'jeprof'\n            break\n          default:\n        }\n      } else if (component === 'tiflash' && profile_type === 'heap') {\n        switch (action) {\n          case Action.VIEW_FLAMEGRAPH:\n            dataFormat = 'text'\n            break\n          case Action.VIEW_GRAPH:\n            dataFormat = 'svg'\n            break\n          case Action.DOWNLOAD:\n            dataFormat = 'jeprof'\n            break\n          default:\n        }\n      } else {\n        switch (action) {\n          case Action.VIEW_GRAPH:\n            dataFormat = 'svg'\n            break\n          case Action.VIEW_TEXT:\n            dataFormat = 'text'\n            break\n          case Action.VIEW_FLAMEGRAPH:\n          case Action.DOWNLOAD:\n            dataFormat = 'protobuf'\n            break\n          default:\n        }\n      }\n      const res = await ctx!.ds.continuousProfilingActionTokenGet(\n        `ts=${ts}&profile_type=${profile_type}&component=${component}&address=${address}&data_format=${dataFormat}`\n      )\n      const token = res.data\n      if (!token) {\n        return\n      }\n\n      telemetry.clickAction({\n        action,\n        profile_type: rec.profile_type!,\n        component: component!\n      })\n\n      if (action === Action.VIEW_GRAPH || action === Action.VIEW_TEXT) {\n        const profileURL = `${\n          ctx!.cfg.apiPathBase\n        }/continuous_profiling/single_profile/view?token=${token}`\n        window.open(profileURL, '_blank')\n        return\n      }\n\n      if (action === Action.VIEW_FLAMEGRAPH) {\n        // view flamegraph by speedscope\n        const speedscopeTitle = `${rec.target?.component}_${rec.target?.address}_${rec.profile_type}`\n        const profileURL = `${\n          ctx!.cfg.apiPathBase\n        }/continuous_profiling/single_profile/view?token=${token}`\n        const speedscopeURL = `${\n          ctx!.cfg.publicPathBase\n        }/speedscope/#profileURL=${encodeURIComponent(\n          profileURL + `&output_type=${dataFormat}`\n        )}&title=${speedscopeTitle}`\n        window.open(speedscopeURL, '_blank')\n        return\n      }\n\n      if (action === Action.DOWNLOAD) {\n        window.location.href = `${\n          ctx!.cfg.apiPathBase\n        }/continuous_profiling/download?token=${token}`\n        return\n      }\n    }\n  )\n\n  const handleDownloadGroup = useCallback(async () => {\n    const res = await ctx!.ds.continuousProfilingActionTokenGet(\n      `ts=${ts}&data_format=protobuf`\n    )\n    const token = res.data\n    if (!token) {\n      return\n    }\n    telemetry.downloadProfilingGroupResult()\n    window.location.href = `${\n      ctx!.cfg.apiPathBase\n    }/continuous_profiling/download?token=${token}`\n  }, [ts, ctx])\n\n  const columns = useMemo(\n    () => [\n      {\n        name: t('conprof.detail.table.columns.instance'),\n        key: 'instance',\n        minWidth: 100,\n        maxWidth: 200,\n        onRender: (record) => record.target.address\n      },\n      {\n        name: t('conprof.detail.table.columns.content'),\n        key: 'content',\n        minWidth: 100,\n        maxWidth: 100,\n        onRender: (record) => {\n          const profileType = record.profile_type\n          // in the cloud ngm, the `profile` is `cpu`\n          if (profileType === 'profile' || profileType === 'cpu') {\n            return `CPU - ${profileDuration}s`\n          }\n          return upperFirst(profileType)\n        }\n      },\n      {\n        name: t('conprof.detail.table.columns.status'),\n        key: 'status',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (record) => {\n          if (record.state === 'finished' || record.state === 'success') {\n            return (\n              <Badge\n                status=\"success\"\n                text={t('conprof.detail.table.status.finished')}\n              />\n            )\n          }\n          if (record.state === 'failed') {\n            return (\n              <Badge\n                status=\"error\"\n                text={t('conprof.detail.table.status.error')}\n              />\n            )\n          }\n          return <Badge text={t('conprof.list.table.status.unknown')} />\n        }\n      },\n      {\n        name: t('conprof.detail.table.columns.view_as.title'),\n        key: 'view_as',\n        minWidth: 250,\n        maxWidth: 400,\n        onRender: (record) => {\n          if (record.state === 'failed') {\n            return (\n              <a\n                onClick={() => {\n                  Modal.error({\n                    title: 'Profile Error',\n                    content: record.error\n                  })\n                }}\n              >\n                {t('conprof.detail.table.columns.view_as.error')}\n              </a>\n            )\n          }\n\n          if (record.state !== 'finished' && record.state !== 'success') {\n            return <></>\n          }\n\n          const rec = record as ConprofProfileDetail\n          let actionsKey: string[] = []\n          if (rec.profile_type === 'goroutine') {\n            if (enablePreviewGoroutine) {\n              actionsKey = [Action.VIEW_TEXT]\n            } else {\n              actionsKey = [Action.DOWNLOAD]\n            }\n          } else {\n            if (enableDotGraph) {\n              actionsKey = [\n                Action.VIEW_FLAMEGRAPH,\n                Action.VIEW_GRAPH,\n                Action.DOWNLOAD\n              ]\n            } else {\n              actionsKey = [Action.VIEW_FLAMEGRAPH, Action.DOWNLOAD]\n            }\n          }\n\n          return (\n            <Space>\n              {actionsKey.map((action) => {\n                return (\n                  <a onClick={() => handleClick(action, record)} key={action}>\n                    {t(`conprof.detail.table.columns.view_as.${action}`)}\n                  </a>\n                )\n              })}\n            </Space>\n          )\n        }\n      }\n    ],\n    [t, profileDuration, handleClick]\n  )\n\n  return (\n    <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>\n      <Head\n        title={t('conprof.detail.head.title')}\n        back={\n          <Link to={`/continuous_profiling`}>\n            <ArrowLeftOutlined /> {t('conprof.detail.head.back')}\n          </Link>\n        }\n        titleExtra={\n          enableDownloadGroup && (\n            <Button type=\"primary\" onClick={handleDownloadGroup}>\n              {t('conprof.detail.download')}\n            </Button>\n          )\n        }\n      />\n      <div style={{ height: '100%', position: 'relative' }}>\n        <ScrollablePane>\n          {groupProfileDetail && (\n            <Card noMarginTop noMarginBottom>\n              <Descriptions>\n                <Descriptions.Item\n                  span={2}\n                  label={t('conprof.detail.head.start_at')}\n                >\n                  <DateTime.Long\n                    unixTimestampMs={groupProfileDetail.ts! * 1000}\n                  />\n                </Descriptions.Item>\n              </Descriptions>\n            </Card>\n          )}\n          <CardTable\n            cardNoMarginTop\n            cardNoMarginBottom\n            disableSelectionZone\n            loading={groupDetailLoading}\n            columns={columns}\n            items={tableData}\n            groups={groupData}\n            errors={[groupDetailError]}\n            hideLoadingWhenNotEmpty\n            extendLastColumn\n            compact\n          />\n        </ScrollablePane>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ContinuousProfiling/pages/List.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.list {\n  &_container {\n    display: flex;\n    flex-direction: column;\n    height: 100vh;\n  }\n\n  &_toolbar {\n    @media only screen and (max-width: @screen-md) {\n      flex-direction: column;\n    }\n  }\n}\n\n.alert_container {\n  margin-left: @padding-page;\n  margin-right: @padding-page;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ContinuousProfiling/pages/List.tsx",
    "content": "import {\n  Badge,\n  Tooltip,\n  Space,\n  Drawer,\n  Result,\n  Button,\n  Alert,\n  Form\n} from 'antd'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport React, { useContext, useMemo, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useNavigate } from 'react-router-dom'\nimport { useMemoizedFn } from 'ahooks'\nimport {\n  LoadingOutlined,\n  QuestionCircleOutlined,\n  ReloadOutlined,\n  SettingOutlined\n} from '@ant-design/icons'\nimport dayjs, { Dayjs } from 'dayjs'\n\nimport { Card, CardTable, Toolbar, DatePicker } from '@lib/components'\nimport DateTime from '@lib/components/DateTime'\nimport openLink from '@lib/utils/openLink'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { instanceKindName } from '@lib/utils/instanceTable'\n\nimport ConProfSettingForm from './ConProfSettingForm'\n\nimport styles from './List.module.less'\nimport { telemetry } from '../utils/telemetry'\nimport { isDistro } from '@lib/utils/distro'\nimport { ConProfilingContext } from '../context'\nimport { useURLTimeRange } from '@lib/hooks/useURLTimeRange'\n\nexport default function Page() {\n  const ctx = useContext(ConProfilingContext)\n  const showSetting = ctx?.cfg.showSetting ?? true\n  const durationHour = ctx?.cfg.listDuration ?? 2\n  const maxDays = ctx?.cfg.maxDays\n\n  const { timeRange, setTimeRange } = useURLTimeRange()\n  const endTime = timeRange.type === 'recent' ? '' : `${timeRange.value[1]}`\n  const setEndTime = (v) => {\n    if (!v) {\n      setTimeRange({ type: 'recent', value: durationHour * 60 * 60 })\n    } else {\n      const endUnix = v.unix()\n      setTimeRange({\n        type: 'absolute',\n        value: [endUnix - durationHour * 60 * 60, endUnix]\n      })\n    }\n  }\n\n  const rangeEndTime: Dayjs | undefined = useMemo(() => {\n    let _rangeEndTime: Dayjs | undefined\n    if (typeof endTime === 'string') {\n      if (endTime === '') {\n        _rangeEndTime = undefined\n      } else {\n        _rangeEndTime = dayjs(parseInt(endTime) * 1000)\n      }\n    } else {\n      _rangeEndTime = endTime\n    }\n    return _rangeEndTime\n  }, [endTime])\n\n  const {\n    data: historyTable,\n    isLoading: listLoading,\n    error: historyError,\n    sendRequest: reloadGroupProfiles\n  } = useClientRequest(() => {\n    let _rangeEndTime: Dayjs\n    if (rangeEndTime === undefined) {\n      _rangeEndTime = dayjs()\n    } else {\n      _rangeEndTime = rangeEndTime\n    }\n    const _rangeStartTime = _rangeEndTime.subtract(durationHour, 'h')\n\n    return ctx!.ds.continuousProfilingGroupProfilesGet(\n      _rangeStartTime.unix(),\n      _rangeEndTime.unix(),\n      {\n        handleError: 'custom'\n      }\n    )\n  })\n\n  const { t } = useTranslation()\n  const navigate = useNavigate()\n\n  const handleRowClick = useMemoizedFn(\n    (rec, _idx, ev: React.MouseEvent<HTMLElement>) => {\n      telemetry.clickProfilingListRecord(rec)\n      openLink(`/continuous_profiling/detail?ts=${rec.ts}`, ev, navigate)\n    }\n  )\n\n  const historyTableColumns = useMemo(\n    () => [\n      {\n        name: t('conprof.list.table.columns.targets'),\n        key: 'targets',\n        minWidth: 250,\n        maxWidth: 300,\n        onRender: (rec) => {\n          const { tikv, tidb, pd, tiflash, ticdc, tiproxy } = rec.component_num\n          let s = `${tikv} ${instanceKindName(\n            'tikv'\n          )}, ${tidb} ${instanceKindName('tidb')}, ${pd} ${instanceKindName(\n            'pd'\n          )}, ${tiflash} ${instanceKindName('tiflash')}`\n          // to be compatible with old version\n          // this field doesn't not exist in the old version\n          if (ticdc !== undefined) {\n            s = `${s}, ${ticdc} ${instanceKindName('ticdc')}`\n          }\n          if (tiproxy !== undefined) {\n            s = `${s}, ${tiproxy} ${instanceKindName('tiproxy')}`\n          }\n          return s\n        }\n      },\n      {\n        name: t('conprof.list.table.columns.status'),\n        key: 'status',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (rec) => {\n          if (rec.state === 'running') {\n            return (\n              <Badge\n                status=\"processing\"\n                text={t('conprof.list.table.status.running')}\n              />\n            )\n          }\n          if (rec.state === 'finished' || rec.state === 'success') {\n            // all success\n            return (\n              <Badge\n                status=\"success\"\n                text={t('conprof.list.table.status.finished')}\n              />\n            )\n          }\n          if (\n            rec.state === 'finished_with_error' ||\n            rec.state === 'partial failed'\n          ) {\n            // partial failed\n            return (\n              <Badge\n                status=\"warning\"\n                text={t('conprof.list.table.status.partial_finished')}\n              />\n            )\n          }\n          if (rec.state === 'failed') {\n            // all failed\n            return (\n              <Badge\n                status=\"error\"\n                text={t('conprof.list.table.status.failed')}\n              />\n            )\n          }\n          return <Badge text={t('conprof.list.table.status.unknown')} />\n        }\n      },\n      {\n        name: t('conprof.list.table.columns.start_at'),\n        key: 'ts',\n        minWidth: 200,\n        maxWidth: 250,\n        onRender: (rec) => {\n          return <DateTime.Long unixTimestampMs={rec.ts * 1000} />\n        }\n      },\n      {\n        name: t('conprof.list.table.columns.duration'),\n        key: 'duration',\n        minWidth: 100,\n        maxWidth: 150,\n        fieldName: 'profile_duration_secs'\n      }\n    ],\n    [t]\n  )\n\n  const [showSettings, setShowSettings] = useState(false)\n\n  const { data: ngMonitoringConfig, sendRequest: reloadConfig } =\n    useClientRequest(ctx!.ds.continuousProfilingConfigGet)\n  const conprofIsDisabled = useMemo(\n    () => ngMonitoringConfig?.continuous_profiling?.enable === false,\n    [ngMonitoringConfig]\n  )\n\n  function refresh() {\n    reloadConfig()\n    reloadGroupProfiles()\n    telemetry.clickReloadIcon(rangeEndTime)\n  }\n\n  function handleFinish(fieldsValues) {\n    setEndTime(fieldsValues['rangeEndTime'] || '')\n    setTimeout(() => {\n      reloadGroupProfiles()\n      telemetry.clickQueryButton(rangeEndTime)\n    }, 0)\n  }\n\n  return (\n    <div className={styles.list_container}>\n      <Card>\n        <Toolbar className={styles.list_toolbar}>\n          <Space>\n            <Form\n              layout=\"inline\"\n              onFinish={handleFinish}\n              initialValues={{ rangeEndTime }}\n            >\n              <Form.Item\n                name=\"rangeEndTime\"\n                label={t('conprof.list.toolbar.range_end')}\n              >\n                <DatePicker\n                  disabledDate={(current) => {\n                    if (maxDays === undefined) {\n                      return false\n                    }\n                    return (\n                      current < dayjs().subtract(maxDays, 'd').startOf('d') ||\n                      current > dayjs().endOf('d')\n                    )\n                  }}\n                  showTime\n                  onOpenChange={(open) =>\n                    open && telemetry.openTimeRangePicker()\n                  }\n                  onChange={(v) => telemetry.selectTimeRange(v?.toString())}\n                />\n              </Form.Item>\n              <Form.Item label={t('conprof.list.toolbar.range_duration')}>\n                <span>-{durationHour}h</span>\n              </Form.Item>\n              <Form.Item>\n                <Button type=\"primary\" htmlType=\"submit\" loading={listLoading}>\n                  {t('conprof.list.toolbar.query')}\n                </Button>\n              </Form.Item>\n            </Form>\n          </Space>\n          <Space>\n            <Tooltip\n              mouseEnterDelay={0}\n              mouseLeaveDelay={0}\n              title={t('conprof.list.toolbar.refresh')}\n              placement=\"bottom\"\n            >\n              {listLoading ? (\n                <LoadingOutlined />\n              ) : (\n                <ReloadOutlined onClick={refresh} />\n              )}\n            </Tooltip>\n            {showSetting && (\n              <Tooltip\n                mouseEnterDelay={0}\n                mouseLeaveDelay={0}\n                title={t('conprof.list.toolbar.settings')}\n                placement=\"bottom\"\n              >\n                <SettingOutlined\n                  onClick={() => {\n                    setShowSettings(true)\n                    telemetry.clickSettings('settingIcon')\n                  }}\n                />\n              </Tooltip>\n            )}\n            {!isDistro() && (\n              <Tooltip\n                mouseEnterDelay={0}\n                mouseLeaveDelay={0}\n                title={t('conprof.settings.help')}\n                placement=\"bottom\"\n              >\n                <QuestionCircleOutlined\n                  onClick={() => {\n                    window.open(t('conprof.settings.help_url'), '_blank')\n                  }}\n                />\n              </Tooltip>\n            )}\n          </Space>\n        </Toolbar>\n      </Card>\n\n      {conprofIsDisabled && historyTable && historyTable.length > 0 && (\n        <div className={styles.alert_container}>\n          <Alert\n            message={t('conprof.settings.disabled_with_history')}\n            type=\"info\"\n            showIcon\n          />\n        </div>\n      )}\n\n      {conprofIsDisabled && historyTable?.length === 0 ? (\n        <Result\n          title={t('conprof.settings.disabled_result.title')}\n          subTitle={t('conprof.settings.disabled_result.sub_title')}\n          extra={\n            <Space>\n              <Button\n                type=\"primary\"\n                onClick={() => {\n                  setShowSettings(true)\n                  telemetry.clickSettings('firstTimeTips')\n                }}\n              >\n                {t('conprof.settings.open_settings')}\n              </Button>\n              {!isDistro() && (\n                <Button\n                  onClick={() => {\n                    window.open(t('conprof.settings.help_url'), '_blank')\n                  }}\n                >\n                  {t('conprof.settings.help')}\n                </Button>\n              )}\n            </Space>\n          }\n        />\n      ) : (\n        <div style={{ height: '100%', position: 'relative' }}>\n          <ScrollablePane>\n            <CardTable\n              cardNoMarginTop\n              cardNoMarginBottom\n              loading={listLoading}\n              items={historyTable || []}\n              columns={historyTableColumns}\n              errors={[historyError]}\n              onRowClicked={handleRowClick}\n            />\n          </ScrollablePane>\n        </div>\n      )}\n\n      <Drawer\n        title={t('conprof.settings.title')}\n        width={300}\n        closable={true}\n        visible={showSettings}\n        onClose={() => setShowSettings(false)}\n        destroyOnClose={true}\n      >\n        <ConProfSettingForm\n          onClose={() => setShowSettings(false)}\n          onConfigUpdated={reloadConfig}\n        />\n      </Drawer>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ContinuousProfiling/pages/index.ts",
    "content": "import List from './List'\nimport Detail from './Detail'\n\nexport { List, Detail }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ContinuousProfiling/translations/en.yaml",
    "content": "conprof:\n  nav_title: Continuous Profiling\n  list:\n    toolbar:\n      refresh: Refresh\n      settings: Settings\n      range_end: Range End Time\n      range_duration: Range Duration\n      query: Query\n    control_form:\n      title: Start Profiling Instances\n      enable_tooltip: This feature is enabled, you can disable it in the settings\n      disable_tooltip: This feature is not enabled, you can enable it in the settings\n    table:\n      title: Profiling History\n      columns:\n        targets: Instances\n        start_at: Start At\n        duration: Duration (sec)\n        status: Status\n      status:\n        running: Running\n        finished: Finished\n        failed: Failed\n        partial_finished: Partial Finished\n        unknown: Unknown\n      actions:\n        detail: Detail\n  detail:\n    head:\n      back: History\n      title: Profiling Detail\n      start_at: Start At\n    download: Download Profiling Result\n    table:\n      columns:\n        instance: Instance\n        kind: Component\n        content: Content\n        status: Status\n        view_as:\n          title: View As\n          error: Error Information\n          view_flamegraph: FlameGraph\n          view_graph: DotGraph\n          download: RawData\n          view_text: RawData\n      status:\n        finished: Finished\n        error: Error\n  settings:\n    title: Settings\n    disabled_result:\n      title: Feature Not Enabled\n      sub_title: Continuous Profiling feature is not enabled. You can modify settings to enable the feature and wait for new data being collected.\n    disabled_with_history: Continuous Profiling feature is not enabled, but you still can view history result. You can modify settings to enable the feature.\n    open_settings: Open Settings\n    switch: Enable Feature\n    switch_tooltip: After being enabled, Continuous Profiling will generate instance profiling results continuously.\n    profile_targets: Profiling Targets\n    profile_targets_tooltip: |\n      After being enabled, Continuous Profiling will profile all instances including newly created instances. There are {{n}} instances, and Continuous Profiling will produce {{size}} results every day.\n    profile_duration: Profiling Duration\n    profile_duration_tooltip:\n    profile_interval: Profiling Interval\n    profile_interval_tooltip:\n    profile_retention_duration: Retention Duration\n    profile_retention_duration_tooltip: |\n      The profiling results are persisted in the disk. Those exceeding the retention duration are deleted. This setting works for all results.\n    profile_retention_duration_option: '{{d}} days'\n    close_feature: Disable Continuous Profiling Feature\n    close_feature_confirm: Are you sure want to disable this feature, it will stop continuous profiling, history result will be kept.\n    actions:\n      close: Disable\n      cancel: Cancel\n    help: Help\n    help_url: https://docs.pingcap.com/tidb/dev/continuous-profiling\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ContinuousProfiling/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ContinuousProfiling/translations/zh.yaml",
    "content": "conprof:\n  nav_title: 持续分析\n  toolbar:\n    refresh: Refresh\n  list:\n    toolbar:\n      refresh: 刷新\n      settings: 设置\n      range_end: 区间结束时间\n      range_duration: 区间长度\n      query: 查询\n    control_form:\n      title: 开始性能分析\n      enable_tooltip: 该功能已开启，你可以在设置中关闭\n      disable_tooltip: 该功能未启用，你可以在设置中启用\n    table:\n      title: 性能分析历史\n      columns:\n        targets: 实例\n        start_at: 开始时间\n        duration: 时长（秒）\n        status: 状态\n      status:\n        running: 分析中\n        finished: 完成\n        failed: 失败\n        partial_finished: 部分完成\n        unknown: 未知\n      actions:\n        detail: 详情\n  detail:\n    head:\n      back: 历史记录\n      title: 性能分析详情\n      start_at: 开始时间\n    download: 下载性能分析结果\n    table:\n      columns:\n        instance: 实例\n        kind: 组件\n        content: 内容\n        status: 状态\n        view_as:\n          title: 查看方式\n          error: 错误信息\n          view_flamegraph: 火焰图\n          view_graph: 关系图\n          download: 原始数据\n          view_text: 原始数据\n      status:\n        finished: 完成\n        error: 错误\n  settings:\n    title: 设置\n    disabled_result:\n      title: 该功能未启用\n      sub_title: 持续性能分析功能未启用。您可以修改设置打开该功能后等待新数据收集。\n    disabled_with_history: 持续性能分析功能未启用，但仍然可以查看历史数据。您可以修改设置打开该功能。\n    open_settings: 打开设置\n    switch: 启用功能\n    switch_tooltip: 是否启用持续分析功能，启用后，会持续产出实例性能分析结果。\n    profile_targets: 分析范围\n    profile_targets_tooltip: |\n      分析所有实例，在新实例创建后，也会自动加入分析范围。\n      目前有 {{n}} 个实例，预计每日生成 {{size}} 分析结果文件。\n    profile_duration: 分析时长\n    profile_duration_tooltip:\n    profile_interval: 执行周期\n    profile_interval_tooltip:\n    profile_retention_duration: 保留时间\n    profile_retention_duration_tooltip: 分析结果会持久化到磁盘中，超过保留时间会被回收。该配置对所有结果生效，包括历史结果。\n    profile_retention_duration_option: '{{d}} 天'\n    close_feature: 关闭持续分析功能\n    close_feature_confirm: 确认要关闭该功能吗？关闭后将停止持续分析，历史结果会继续保留。\n    actions:\n      close: 确认\n      cancel: 取消\n    help: 帮助\n    help_url: https://docs.pingcap.com/zh/tidb/dev/continuous-profiling\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ContinuousProfiling/utils/telemetry.ts",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\nimport { ConprofContinuousProfilingConfig } from '@lib/client'\nimport { mixpanel } from '@lib/utils/telemetry'\nimport { Dayjs } from 'dayjs'\n\nexport const telemetry = {\n  clickSettings(type: 'firstTimeTips' | 'settingIcon') {\n    mixpanel.track('Conprof: Click Settings', { type })\n  },\n  saveSettings(settings: ConprofContinuousProfilingConfig) {\n    mixpanel.track('Conprof: Save Settings', { settings })\n  },\n  openTimeRangePicker() {\n    mixpanel.track('Conprof: Open Time Range Picker')\n  },\n  selectTimeRange(date: string = 'now') {\n    mixpanel.track('Conprof: Select Time Range', { date })\n  },\n  clickQueryButton(endTime?: Dayjs) {\n    mixpanel.track('Conprof: Click Query Button', {\n      endTime: endTime?.toString() || 'now'\n    })\n  },\n  clickReloadIcon(endTime?: Dayjs) {\n    mixpanel.track('Conprof: Click Reload Icon', {\n      endTime: endTime?.toString() || 'now'\n    })\n  },\n  clickProfilingListRecord(record) {\n    mixpanel.track('Conprof: Click Profiling List Record', {\n      record\n    })\n  },\n  // conprof detail\n  clickAction(data: {\n    action: string\n    component: string\n    profile_type: string\n  }) {\n    mixpanel.track('Conprof Detail: Click Action', data)\n  },\n  downloadProfilingGroupResult() {\n    mixpanel.track('Conprof Detail: Download Profiling Group Result')\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Deadlock/components/DeadlockChainGraph.tsx",
    "content": "import React, { useMemo } from 'react'\nimport { DeadlockModel } from '@lib/client'\n\ninterface NodeMeta {\n  x: number\n  y: number\n  connectInX: number\n  connectInY: number\n  connectOutX: number\n  connectOutY: number\n}\n\nfunction calcCircularLayout(\n  center: { x: number; y: number },\n  circularRadius: number,\n  nodeSize: number,\n  nodeRadius: number\n): Array<NodeMeta> {\n  let result: Array<NodeMeta> = []\n  const outAngle = (2 * Math.PI) / nodeSize\n  const halfInnerAngle = (Math.PI * (nodeSize - 2)) / nodeSize / 2\n  let currentNodeConnectInX = center.x - Math.sin(halfInnerAngle) * nodeRadius\n  let currentNodeConnectInY =\n    center.y + circularRadius - Math.cos(halfInnerAngle) * nodeRadius\n  let currentNodeConnectOutX = center.x + Math.sin(halfInnerAngle) * nodeRadius\n  let currentNodeConnectOutY =\n    center.y + circularRadius - Math.cos(halfInnerAngle) * nodeRadius\n  let angle = 0\n  for (let i = 0; i < nodeSize; ++i) {\n    angle += outAngle\n    const x = center.x + circularRadius * Math.sin(angle)\n    const y = center.y + circularRadius * Math.cos(angle)\n\n    result.push({\n      x: x,\n      y: y,\n      connectInX: currentNodeConnectInX,\n      connectInY: currentNodeConnectInY,\n      connectOutX: currentNodeConnectOutX,\n      connectOutY: currentNodeConnectOutY\n    })\n\n    const newNodeConnectInX =\n      (currentNodeConnectInX - center.x) * Math.cos(outAngle) -\n      (currentNodeConnectInY - center.y) * Math.sin(outAngle) +\n      center.x\n    const newNodeConnectInY =\n      (currentNodeConnectInX - center.x) * Math.sin(outAngle) +\n      (currentNodeConnectInY - center.y) * Math.cos(outAngle) +\n      center.y\n    currentNodeConnectInX = newNodeConnectInX\n    currentNodeConnectInY = newNodeConnectInY\n\n    const newNodeConnectOutX =\n      (currentNodeConnectOutX - center.x) * Math.cos(outAngle) -\n      (currentNodeConnectOutY - center.y) * Math.sin(outAngle) +\n      center.x\n    const newNodeConnectOutY =\n      (currentNodeConnectOutX - center.x) * Math.sin(outAngle) +\n      (currentNodeConnectOutY - center.y) * Math.cos(outAngle) +\n      center.y\n    currentNodeConnectOutX = newNodeConnectOutX\n    currentNodeConnectOutY = newNodeConnectOutY\n  }\n  return result\n}\n\ninterface Prop {\n  deadlockChain: DeadlockModel[]\n}\n\nfunction DeadlockChainGraph(prop: Prop) {\n  const data = useMemo(() => {\n    return {\n      nodes: prop.deadlockChain.map((it) => {\n        return { id: it.try_lock_trx_id }\n      }),\n      links: prop.deadlockChain.map((d, i) => ({\n        source: i,\n        target: prop.deadlockChain.findIndex(\n          (it) => it.trx_holding_lock === d.try_lock_trx_id\n        ),\n        type: 'blocked',\n        key: d.key\n      }))\n    }\n  }, [prop.deadlockChain])\n  const nodeRadius = 30\n  const layout = useMemo(\n    () => calcCircularLayout({ x: 150, y: 150 }, 100, data.nodes.length, 30),\n    [data.nodes.length]\n  )\n  return (\n    <svg className=\"container\" height={300} width={300}>\n      <defs>\n        <marker\n          id=\"triangle\"\n          markerUnits=\"strokeWidth\"\n          markerWidth=\"5\"\n          markerHeight=\"4\"\n          refX=\"5\"\n          refY=\"2\"\n          orient=\"auto\"\n        >\n          <path d=\"M 0 0 L 5 2 L 0 4 z\" />\n        </marker>\n      </defs>\n      {data.links.map((link, index) => (\n        <path\n          d={`M ${layout[link.source].connectOutX},${\n            layout[link.source].connectOutY\n          } A 100,100 ${-360 / data.nodes.length} 0,0 ${\n            layout[link.target].connectInX\n          },${layout[link.target].connectInY}`}\n          key={`line-${index}`}\n          fill=\"none\"\n          stroke=\"#4679BD\"\n          markerEnd=\"url(#triangle)\"\n        />\n      ))}\n      {data.nodes.map((n, i) => (\n        <g key={n.id}>\n          <circle\n            cx={layout[i].x}\n            cy={layout[i].y}\n            r={nodeRadius}\n            fill=\"white\"\n            stroke=\"#000\"\n          />\n          <text textAnchor=\"middle\" x={layout[i].x} y={layout[i].y + 5}>\n            {n.id?.toString().slice(n.id.toString().length - 6)}\n          </text>\n        </g>\n      ))}\n    </svg>\n  )\n}\n\nexport default DeadlockChainGraph\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Deadlock/components/index.ts",
    "content": "export { default as DeadlockChainGraph } from './DeadlockChainGraph'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Deadlock/context/index.ts",
    "content": "import { createContext } from 'react'\nimport { AxiosPromise } from 'axios'\n\nimport { DeadlockModel } from '@lib/client'\nimport { ReqConfig } from '@lib/types'\n\nexport interface IDeadlockDataSource {\n  deadlockListGet(options?: ReqConfig): AxiosPromise<Array<DeadlockModel>>\n}\n\nexport interface IDeadlockContext {\n  ds: IDeadlockDataSource\n}\n\nexport const DeadlockContext = createContext<IDeadlockContext | null>(null)\n\nexport const DeadlockProvider = DeadlockContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Deadlock/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { HashRouter as Router, Routes, Route } from 'react-router-dom'\n\nimport { Root } from '@lib/components'\nimport useCache, { CacheContext } from '@lib/utils/useCache'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\nimport { addTranslations } from '@lib/utils/i18n'\n\nimport { List, Detail } from './pages'\nimport { DeadlockContext } from './context'\nimport translations from './translations'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/deadlock\" element={<List />} />\n      <Route path=\"/deadlock/detail\" element={<Detail />} />\n    </Routes>\n  )\n}\n\nexport default function () {\n  const cache = useCache(32)\n\n  const ctx = useContext(DeadlockContext)\n  if (ctx === null) {\n    throw new Error('DeadlockContext must not be null')\n  }\n\n  return (\n    <Root>\n      <CacheContext.Provider value={cache}>\n        <Router>\n          <AppRoutes />\n        </Router>\n      </CacheContext.Provider>\n    </Root>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Deadlock/pages/Detail.tsx",
    "content": "import React, { useContext, useState } from 'react'\nimport { DeadlockModel } from '@lib/client'\nimport { useLocation } from 'react-router-dom'\nimport { useEffectOnce } from 'react-use'\nimport { useTranslation } from 'react-i18next'\n\nimport { CardTable, HighlightSQL } from '@lib/components'\nimport { CacheContext } from '@lib/utils/useCache'\nimport DeadlockChainGraph from '../components/DeadlockChainGraph'\nimport { DeadlockContext } from '../context'\n\nfunction Detail() {\n  const ctx = useContext(DeadlockContext)\n  const { t } = useTranslation()\n  const cache = useContext(CacheContext)\n  const instance = new URLSearchParams(useLocation().search).get('instance')\n  const id = new URLSearchParams(useLocation().search).get('id')\n  let [isLoading, setIsLoading] = useState(true)\n  let [items, setItems] = useState<DeadlockModel[]>([])\n  useEffectOnce(() => {\n    setIsLoading(true)\n    if (cache?.get(`deadlock-${instance}-${id}`) !== undefined) {\n      setItems(cache.get(`deadlock-${instance}-${id}`))\n      setIsLoading(false)\n    } else {\n      ctx!.ds.deadlockListGet().then(({ data }) => {\n        data.forEach((it) => {\n          let items = cache?.get(`deadlock-${it.instance}-${it.id}`) || []\n          items.push(it)\n          cache?.set(`deadlock-${it.instance}-${it.id}`, items)\n        })\n        setItems(\n          data.filter(\n            (it) =>\n              it.id?.toString() === id && it.instance?.toString() === instance\n          )\n        )\n        setIsLoading(false)\n      })\n    }\n  })\n  const columns = [\n    {\n      name: t('deadlock.fields.try_lock_trx_id'),\n      key: 'try_lock_trx_id',\n      minWidth: 100,\n      onRender: (it) => it.try_lock_trx_id\n    },\n    {\n      name: t('deadlock.fields.current_sql'),\n      key: 'current_sql',\n      minWidth: 350,\n      onRender: (it) => (\n        <HighlightSQL sql={it.current_sql} compact maxLen={1000} />\n      )\n    },\n    {\n      name: t('deadlock.fields.key'),\n      key: 'key',\n      minWidth: 300,\n      onRender: (it) => it.key\n    },\n    {\n      name: t('deadlock.fields.trx_holding_lock'),\n      key: 'trx_holding_lock',\n      minWidth: 150,\n      onRender: (it) => it.trx_holding_lock\n    }\n  ]\n  return (\n    <>\n      <DeadlockChainGraph deadlockChain={items} />\n      <CardTable\n        loading={isLoading}\n        columns={columns}\n        items={items}\n        orderBy=\"try_lock_trx_id\"\n        desc={false}\n        data-e2e=\"detail_tabs_deadlock\"\n      />\n    </>\n  )\n}\n\nexport default Detail\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Deadlock/pages/List.tsx",
    "content": "import { DeadlockModel } from '@lib/client'\nimport {\n  AnimatedSkeleton,\n  AutoRefreshButton,\n  Card,\n  CardTable\n} from '@lib/components'\nimport openLink from '@lib/utils/openLink'\nimport { useMemoizedFn } from 'ahooks'\nimport { CacheContext } from '@lib/utils/useCache'\nimport React, { useMemo, useState, useContext } from 'react'\nimport { useNavigate } from 'react-router-dom'\nimport { useEffectOnce } from 'react-use'\nimport { useTranslation } from 'react-i18next'\nimport { DeadlockContext } from '../context'\n\nfunction List() {\n  const ctx = useContext(DeadlockContext)\n\n  const { t } = useTranslation()\n  const cache = useContext(CacheContext)\n  let [isLoading, setIsLoading] = useState(true)\n  let [items, setItems] = useState<DeadlockModel[]>([])\n  const navigate = useNavigate()\n\n  const pullItems = async () => {\n    cache?.clear()\n    setIsLoading(true)\n    const { data } = await ctx!.ds.deadlockListGet()\n    data.forEach((it) => {\n      let items = cache?.get(`deadlock-${it.instance}-${it.id}`) || []\n      items.push(it)\n      cache?.set(`deadlock-${it.instance}-${it.id}`, items)\n    })\n    setItems(data)\n    setIsLoading(false)\n  }\n  const handleRowClick = useMemoizedFn(\n    (record, index, ev: React.MouseEvent<HTMLElement>) => {\n      openLink(\n        `/deadlock/detail?id=${record.id}&instance=${record.instance}`,\n        ev,\n        navigate\n      )\n    }\n  )\n  useEffectOnce(() => {\n    setIsLoading(true)\n    cache?.clear()\n    ctx!.ds\n      .deadlockListGet()\n      .then((res) => {\n        setItems(res.data)\n        res.data.forEach((it) => {\n          let items = cache?.get(`deadlock-${it.instance}-${it.id}`) || []\n          items.push(it)\n          cache?.set(`deadlock-${it.instance}-${it.id}`, items)\n        })\n      })\n      .catch((e) => {\n        console.error(e)\n      })\n      .finally(() => {\n        setIsLoading(false)\n      })\n  })\n  const summary = useMemo(() => {\n    let result = new Map()\n    for (const item of items) {\n      let summaryEntry = result.get(`${item.instance}-${item.id}`) || {\n        id: item.id,\n        instance: item.instance,\n        occur_time: item.occur_time,\n        items: []\n      }\n      summaryEntry.items.push(item)\n      result.set(`${item.instance}-${item.id}`, summaryEntry)\n    }\n    return Array.from(result.values())\n  }, [items])\n\n  const columns = [\n    {\n      name: t('deadlock.fields.instance'),\n      key: 'instance',\n      minWidth: 100,\n      onRender: (it) => it.instance\n    },\n    { name: 'ID', key: 'id', minWidth: 100, onRender: (it) => it.id },\n    {\n      name: 'Transaction Count',\n      key: t('deadlock.fields.count'),\n      minWidth: 300,\n      onRender: (it) => it.items.length\n    },\n    {\n      name: 'Occur time',\n      key: t('deadlock.fields.occur_time'),\n      minWidth: 300,\n      onRender: (it) => new Date(it.occur_time).toLocaleString()\n    }\n  ]\n  return (\n    <div>\n      <Card noMarginBottom>\n        <AutoRefreshButton disabled={isLoading} onRefresh={pullItems} />\n      </Card>\n      <AnimatedSkeleton showSkeleton={isLoading}>\n        <CardTable\n          loading={isLoading}\n          columns={columns}\n          items={summary}\n          orderBy={'occur_time'}\n          desc={false}\n          onRowClicked={handleRowClick}\n          data-e2e=\"deadlock_list\"\n        />\n      </AnimatedSkeleton>\n    </div>\n  )\n}\n\nexport default List\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Deadlock/pages/index.ts",
    "content": "import List from './List'\nimport Detail from './Detail'\nexport { List, Detail }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Deadlock/translations/en.yaml",
    "content": "deadlock:\n  nav_title: Deadlock Information\n  fields:\n    instance: Instance\n    count: Transaction count\n    occur_time: Occur time\n    try_lock_trx_id: Transaction acquiring the lock\n    trx_holding_lock: Transaction holding the lock\n    current_sql: SQL for acquiring the lock\n    key: Locked key\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Deadlock/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Deadlock/translations/zh.yaml",
    "content": "deadlock:\n  nav_title: 死锁信息\n  fields:\n    instance: 实例\n    count: 事务数量\n    occur_time: 发生时间\n    try_lock_trx_id: 尝试加锁事务号\n    trx_holding_lock: 持锁事务号\n    current_sql: 尝试加锁 SQL\n    key: 被锁住的 key\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/apilist/ApiForm.tsx",
    "content": "import React, { useCallback, useContext, useMemo, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { Form, Button, Space, Row, Col } from 'antd'\nimport { isNull, isUndefined } from 'lodash'\nimport { DownloadOutlined, UndoOutlined } from '@ant-design/icons'\nimport {\n  EndpointAPIDefinition,\n  EndpointAPIParamDefinition,\n  TopologyPDInfo,\n  TopologyStoreInfo,\n  TopologyTiDBInfo\n} from '@lib/client'\nimport { ApiFormWidgetConfig, createFormWidget } from './widgets'\nimport { distro } from '@lib/utils/distro'\nimport { DebugAPIContext } from '../context'\nimport { useIsWriteable } from '@lib/utils'\n\nexport interface Topology {\n  tidb: TopologyTiDBInfo[]\n  tikv: TopologyStoreInfo[]\n  tiflash: TopologyStoreInfo[]\n  pd: TopologyPDInfo[]\n  tiproxy: TopologyPDInfo[]\n}\n\nexport default function ApiForm({\n  endpoint,\n  topology\n}: {\n  endpoint: EndpointAPIDefinition\n  topology: Topology\n}) {\n  const ctx = useContext(DebugAPIContext)\n\n  const isWriteable = useIsWriteable()\n\n  const { t } = useTranslation()\n  const { id, path_params, query_params, component } = endpoint\n  const endpointHostParamKey = useMemo(\n    () => `${distro()[component!]?.toLowerCase()}_instance`,\n    [component]\n  )\n  const pathParams = (path_params ?? []).map((p) => {\n    p.required = true\n    return p\n  })\n  const params = [...pathParams, ...(query_params ?? [])]\n  const [loading, setLoading] = useState(false)\n  const [form] = Form.useForm()\n  const formPaths = [...params.map((p) => p.name!), endpointHostParamKey]\n\n  const download = useCallback(\n    async (values: any) => {\n      try {\n        setLoading(true)\n        const { [endpointHostParamKey]: host, ...p } = values\n        const [hostname, port] = host.split(':')\n        const param_values = Object.entries(p).reduce((prev, [k, v]) => {\n          if (!(isUndefined(v) || isNull(v) || v === '')) {\n            prev[k] = v\n          } else {\n            // handle the null value params\n            // fill it with the default value if it has\n            const param = params.find((p) => p.name === k)\n            const defVal = (param?.ui_props as any)?.default_val\n            if (!!defVal) {\n              prev[k] = defVal\n            }\n          }\n          return prev\n        }, {})\n        const resp = await ctx!.ds.debugAPIRequestEndpoint({\n          api_id: id,\n          host: hostname,\n          port: Number(port),\n          param_values\n        })\n        const token = resp.data\n        window.location.href = `${\n          ctx!.cfg.apiPathBase\n        }/debug_api/download?token=${token}`\n      } catch (e) {\n        console.error(e)\n      } finally {\n        setLoading(false)\n      }\n    },\n    [id, endpointHostParamKey, ctx]\n  )\n\n  const endpointParam = useMemo<EndpointAPIParamDefinition>(\n    () => ({\n      name: endpointHostParamKey,\n      required: true,\n      ui_kind: 'host'\n    }),\n    [endpointHostParamKey]\n  )\n  const EndpointHost = () => (\n    <ApiFormItem\n      key={endpointParam.name}\n      form={form}\n      endpoint={endpoint}\n      param={endpointParam}\n      topology={topology}\n    />\n  )\n\n  return (\n    <Form layout=\"vertical\" form={form} onFinish={download}>\n      <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>\n        <FormItemCol>\n          <EndpointHost />\n        </FormItemCol>\n        {params.map((param) => (\n          <FormItemCol key={param.name}>\n            <ApiFormItem\n              form={form}\n              endpoint={endpoint}\n              param={param}\n              topology={topology}\n            ></ApiFormItem>\n          </FormItemCol>\n        ))}\n      </Row>\n      <Form.Item>\n        <Space>\n          <Button\n            type=\"primary\"\n            loading={loading}\n            icon={<DownloadOutlined />}\n            htmlType=\"submit\"\n            disabled={!isWriteable}\n          >\n            {t('debug_api.form.download')}\n          </Button>\n          <Button\n            icon={<UndoOutlined />}\n            htmlType=\"button\"\n            onClick={() => form.resetFields(formPaths)}\n          >\n            {t('debug_api.form.reset')}\n          </Button>\n        </Space>\n      </Form.Item>\n    </Form>\n  )\n}\n\nfunction FormItemCol(props: React.HTMLAttributes<HTMLDivElement>) {\n  return (\n    <Col span={24} md={12} xl={8} xxl={6}>\n      {props.children}\n    </Col>\n  )\n}\n\nfunction ApiFormItem(widgetConfig: ApiFormWidgetConfig) {\n  const { param } = widgetConfig\n  return (\n    <Form.Item\n      rules={[{ required: !!param.required }]}\n      name={param.name}\n      label={param.name}\n    >\n      {createFormWidget(widgetConfig)}\n    </Form.Item>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/apilist/ApiList.module.less",
    "content": ".collapse_panel {\n  &:not(:last-child) {\n    border-bottom: 1px solid #eee;\n  }\n\n  :global {\n    .ant-collapse-header {\n      padding-left: 40px !important;\n    }\n\n    .ant-collapse-arrow {\n      position: absolute;\n      top: 17px;\n      left: 16px;\n    }\n  }\n}\n\n.header {\n  user-select: none;\n  h4 {\n    margin-bottom: 0;\n  }\n  p {\n    margin-bottom: 0;\n  }\n}\n\n.schema {\n  color: #999;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/apilist/ApiList.tsx",
    "content": "import React, { useContext, useEffect, useMemo, useState } from 'react'\nimport { Collapse, Space, Input, Empty, Alert } from 'antd'\nimport { useTranslation, TFunction } from 'react-i18next'\nimport { SearchOutlined } from '@ant-design/icons'\nimport { debounce } from 'lodash'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'\n\nimport { AnimatedSkeleton, Card, Root } from '@lib/components'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { EndpointAPIDefinition } from '@lib/client'\n\nimport style from './ApiList.module.less'\nimport ApiForm, { Topology } from './ApiForm'\nimport { buildQueryString } from './widgets'\nimport { distro } from '@lib/utils/distro'\nimport { DebugAPIContext } from '../context'\n\nconst getEndpointTranslationKey = (endpoint: EndpointAPIDefinition) =>\n  `debug_api.${endpoint.component}.endpoints.${endpoint.id}`\n\nconst useFilterEndpoints = (endpoints?: EndpointAPIDefinition[]) => {\n  const [keywords, setKeywords] = useState('')\n  const nonNullEndpoints = useMemo(() => endpoints || [], [endpoints])\n  const [filteredEndpoints, setFilteredEndpoints] =\n    useState<EndpointAPIDefinition[]>(nonNullEndpoints)\n  const { t } = useTranslation()\n\n  useEffect(() => {\n    const k = keywords.trim()\n    if (!!k) {\n      setFilteredEndpoints(\n        nonNullEndpoints.filter((e) => {\n          return (\n            e.id?.includes(k) ||\n            e.path?.includes(k) ||\n            t(getEndpointTranslationKey(e)).includes(k)\n          )\n        })\n      )\n    } else {\n      setFilteredEndpoints(nonNullEndpoints)\n    }\n  }, [nonNullEndpoints, keywords, t])\n\n  return {\n    endpoints: filteredEndpoints,\n    filterBy: debounce(setKeywords, 100)\n  }\n}\n\nexport default function Page() {\n  const ctx = useContext(DebugAPIContext)\n\n  const { t, i18n } = useTranslation()\n  const { data: endpointData, isLoading: isEndpointLoading } = useClientRequest(\n    ctx!.ds.debugAPIGetEndpoints\n  )\n  const { endpoints, filterBy } = useFilterEndpoints(endpointData)\n\n  // TODO: refine with components/InstanceSelect\n  const { data: tidbTopology = [], isLoading: isTiDBLoading } =\n    useClientRequest(ctx!.ds.getTiDBTopology)\n  const { data: pdTopology = [], isLoading: isPDLoading } = useClientRequest(\n    ctx!.ds.getPDTopology\n  )\n  const { data: storeTopology, isLoading: isStoreLoading } = useClientRequest(\n    ctx!.ds.getStoreTopology\n  )\n  const { data: tiproxyTopology = [], isLoading: isTiProxyLoading } =\n    useClientRequest(ctx!.ds.getTiProxyTopology)\n  const topology: Topology = {\n    tidb: tidbTopology!,\n    tikv: storeTopology?.tikv || [],\n    tiflash: storeTopology?.tiflash || [],\n    pd: pdTopology!,\n    tiproxy: tiproxyTopology!\n  }\n  const isTopologyLoading =\n    isTiDBLoading || isPDLoading || isStoreLoading || isTiProxyLoading\n\n  const groups = useMemo(\n    () =>\n      endpoints.reduce((prev, endpoint) => {\n        const groupName = endpoint.component!\n        if (!prev[groupName]) {\n          prev[groupName] = []\n        }\n        prev[groupName].push(endpoint)\n        return prev\n      }, {} as { [group: string]: EndpointAPIDefinition[] }),\n    [endpoints]\n  )\n  const sortedGroups = useMemo(\n    () =>\n      ['tidb', 'tikv', 'tiflash', 'pd', 'tiproxy']\n        .filter((sortKey) => groups[sortKey])\n        .map((sortKey) => groups[sortKey]),\n    [groups]\n  )\n\n  function EndpointGroup({ group }: { group: EndpointAPIDefinition[] }) {\n    return (\n      <Card\n        noMarginLeft\n        noMarginRight\n        noMarginTop\n        title={t(`debug_api.${group[0].component!}.name`)}\n      >\n        <Collapse ghost>\n          {group.map((endpoint) => {\n            const descTranslationKey = `debug_api.${endpoint.component}.endpoints.${endpoint.id}_desc`\n            const descExists = i18n.exists(descTranslationKey)\n\n            return (\n              <Collapse.Panel\n                className={style.collapse_panel}\n                header={\n                  <CustomHeader endpoint={endpoint} translation={{ t }} />\n                }\n                key={endpoint.id!}\n              >\n                {descExists && (\n                  <Alert\n                    style={{ marginBottom: 16 }}\n                    message={t(descTranslationKey)}\n                    type=\"info\"\n                    showIcon\n                  />\n                )}\n                <ApiForm endpoint={endpoint} topology={topology} />\n              </Collapse.Panel>\n            )\n          })}\n        </Collapse>\n      </Card>\n    )\n  }\n\n  return (\n    <Root>\n      <ScrollablePane style={{ height: '100vh' }}>\n        <Card noMarginBottom>\n          <Alert\n            message={t(`debug_api.warning_header.title`)}\n            description={t(`debug_api.warning_header.body`)}\n            type=\"warning\"\n            showIcon\n          />\n        </Card>\n        <Sticky stickyPosition={StickyPositionType.Header} isScrollSynced>\n          <div style={{ display: 'flow-root' }}>\n            <Card>\n              <Input\n                placeholder={t(`debug_api.keyword_search`)}\n                prefix={<SearchOutlined />}\n                onChange={(e) => filterBy(e.target.value)}\n              />\n            </Card>\n          </div>\n        </Sticky>\n        <Card noMarginTop>\n          <AnimatedSkeleton\n            showSkeleton={isEndpointLoading || isTopologyLoading}\n          >\n            {endpoints.length ? (\n              sortedGroups.map((g) => (\n                <EndpointGroup key={g[0].component!} group={g} />\n              ))\n            ) : (\n              <Empty description={t('debug_api.endpoints_not_found')} />\n            )}\n          </AnimatedSkeleton>\n        </Card>\n      </ScrollablePane>\n    </Root>\n  )\n}\n\nfunction CustomHeader({\n  endpoint,\n  translation\n}: {\n  endpoint: EndpointAPIDefinition\n  translation: {\n    t: TFunction\n  }\n}) {\n  const { t } = translation\n  return (\n    <div className={style.header}>\n      <Space direction=\"vertical\">\n        <Space>\n          <h4>{t(getEndpointTranslationKey(endpoint))}</h4>\n        </Space>\n        <Schema endpoint={endpoint} />\n      </Space>\n    </div>\n  )\n}\n\n// e.g. http://{tidb_ip}/stats/dump/{db}/{table}?queryName={queryName}\nfunction Schema({ endpoint }: { endpoint: EndpointAPIDefinition }) {\n  const query = buildQueryString(endpoint.query_params ?? [])\n  return (\n    <p className={style.schema}>\n      {`http://{${distro()[endpoint.component!]?.toLowerCase()}_instance}${\n        endpoint.path\n      }${query}`}\n    </p>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/apilist/index.ts",
    "content": "import ApiList from './ApiList'\n\nexport { ApiList }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/apilist/widgets/Database.tsx",
    "content": "import React, { useCallback, useContext, useState } from 'react'\nimport { Select } from 'antd'\nimport { useTranslation } from 'react-i18next'\n\nimport type { ApiFormWidget } from './index'\nimport { useLimitSelection } from './useLimitSelection'\nimport { DebugAPIContext } from '../../context'\n\nexport const DatabaseWidget: ApiFormWidget = ({ value, onChange }) => {\n  const ctx = useContext(DebugAPIContext)\n\n  const { t } = useTranslation()\n  const tips = t(`debug_api.widgets.db_dropdown`)\n\n  const [loading, setLoading] = useState(false)\n  const [options, setOptions] = useState<string[]>([])\n  const onFocus = useCallback(async () => {\n    if (options.length) {\n      return\n    }\n\n    setLoading(true)\n    try {\n      const rst = await ctx!.ds.infoListDatabases()\n      setOptions(rst.data)\n    } finally {\n      setLoading(false)\n    }\n  }, [setLoading, setOptions, options, ctx])\n\n  const memoOnChange = useCallback(\n    (tags: string[]) => onChange?.(tags[0]),\n    [onChange]\n  )\n  const { selectRef, onSelectChange } = useLimitSelection(1, memoOnChange)\n\n  return (\n    <Select\n      ref={selectRef}\n      mode=\"tags\"\n      dropdownStyle={{ visibility: loading ? 'hidden' : 'visible' }}\n      loading={loading}\n      placeholder={tips}\n      value={value ? [value] : []}\n      onFocus={onFocus}\n      onChange={onSelectChange}\n    >\n      {options.map((option) => (\n        <Select.Option key={option} value={option}>\n          {option}\n        </Select.Option>\n      ))}\n    </Select>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/apilist/widgets/Enum.tsx",
    "content": "import React from 'react'\nimport { Select } from 'antd'\n\nimport type { ApiFormWidget } from './index'\n\nexport const EnumWidget: ApiFormWidget = ({ param }) => {\n  return (\n    <Select>\n      {(\n        ((param.ui_props as any)?.items as {\n          value: string\n          display_as: string\n        }[]) ?? []\n      ).map((option) => (\n        <Select.Option key={option.value} value={option.value}>\n          {option.display_as\n            ? `${option.value} (${option.display_as})`\n            : option.value}\n        </Select.Option>\n      ))}\n    </Select>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/apilist/widgets/Host.tsx",
    "content": "import React from 'react'\nimport { Select } from 'antd'\nimport { useTranslation } from 'react-i18next'\n\nimport type { ApiFormWidget } from './index'\nimport { distro } from '@lib/utils/distro'\n\nconst portKeys: { [k: string]: string } = {\n  tidb: 'status_port',\n  tikv: 'status_port',\n  tiflash: 'status_port',\n  pd: 'port',\n  tiproxy: 'status_port'\n}\n\nexport const HostSelectWidget: ApiFormWidget = ({ endpoint, topology }) => {\n  const { t } = useTranslation()\n  const componentEndpoints = topology[endpoint.component!]\n  const portKey = portKeys[endpoint.component!]\n\n  return (\n    <Select\n      showSearch\n      placeholder={t(`debug_api.widgets.host_select_placeholder`, {\n        endpointType: distro()[endpoint.component!]?.toLowerCase()\n      })}\n    >\n      {componentEndpoints.map((d) => {\n        const val = `${d.ip}:${d[portKey]}`\n        return (\n          <Select.Option key={val} value={val}>\n            {val}\n          </Select.Option>\n        )\n      })}\n    </Select>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/apilist/widgets/Table.tsx",
    "content": "import React, { useCallback, useContext, useRef, useState } from 'react'\nimport { Select } from 'antd'\nimport { useTranslation } from 'react-i18next'\n\nimport { InfoTableSchema } from '@lib/client'\nimport type { ApiFormWidget } from './index'\nimport { useLimitSelection } from './useLimitSelection'\nimport { DebugAPIContext } from '../../context'\n\nexport const TableWidget: ApiFormWidget = ({ form, value, onChange }) => {\n  const ctx = useContext(DebugAPIContext)\n\n  const { t } = useTranslation()\n  const tips = t(`debug_api.widgets.table_dropdown`)\n\n  const [loading, setLoading] = useState(false)\n  const [options, setOptions] = useState<InfoTableSchema[]>([])\n  const prevDBValue = useRef<string>('')\n  const onFocus = useCallback(async () => {\n    // Hardcode associated with the db field\n    const dbValue = form.getFieldValue('db')\n    if (prevDBValue.current === dbValue) {\n      return\n    } else {\n      prevDBValue.current = dbValue\n    }\n    if (!dbValue) {\n      setOptions([])\n      return\n    }\n\n    setLoading(true)\n    try {\n      const rst = await ctx!.ds.infoListTables(dbValue)\n      setOptions(rst.data)\n    } finally {\n      setLoading(false)\n    }\n  }, [setLoading, setOptions, form, ctx])\n\n  const memoOnChange = useCallback(\n    (tags: string[]) => onChange?.(tags[0]),\n    [onChange]\n  )\n  const { selectRef, onSelectChange } = useLimitSelection(1, memoOnChange)\n\n  return (\n    <Select\n      ref={selectRef}\n      mode=\"tags\"\n      dropdownStyle={{ visibility: loading ? 'hidden' : 'visible' }}\n      loading={loading}\n      placeholder={tips}\n      value={value ? [value] : []}\n      onFocus={onFocus}\n      onChange={onSelectChange}\n    >\n      {options.map((option) => (\n        <Select.Option key={option.table_name!} value={option.table_name!}>\n          {option.table_name}\n        </Select.Option>\n      ))}\n    </Select>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/apilist/widgets/TableID.tsx",
    "content": "import React, { useCallback, useContext, useState } from 'react'\nimport { Select } from 'antd'\nimport { useTranslation } from 'react-i18next'\n\nimport { InfoTableSchema } from '@lib/client'\nimport type { ApiFormWidget } from './index'\nimport { useLimitSelection } from './useLimitSelection'\nimport { DebugAPIContext } from '../../context'\n\nconst filterOptionByNameAndID: any = (\n  inputValue: string,\n  // children means Select.Option children nodes\n  option: { children: string }\n) => {\n  return option.children.includes(inputValue)\n}\n\nexport const TableIDWidget: ApiFormWidget = ({ value, onChange }) => {\n  const ctx = useContext(DebugAPIContext)\n\n  const { t } = useTranslation()\n  const tips = t(`debug_api.widgets.table_id_dropdown`)\n\n  const [loading, setLoading] = useState(false)\n  const [options, setOptions] = useState<InfoTableSchema[]>([])\n  const onFocus = useCallback(async () => {\n    if (options.length) {\n      return\n    }\n    setLoading(true)\n    try {\n      const rst = await ctx!.ds.infoListTables()\n      setOptions(rst.data)\n    } finally {\n      setLoading(false)\n    }\n  }, [setLoading, setOptions, options, ctx])\n\n  const memoOnChange = useCallback(\n    (tags: string[]) => onChange?.(tags[0]),\n    [onChange]\n  )\n  const { selectRef, onSelectChange } = useLimitSelection(1, memoOnChange)\n\n  return (\n    <Select\n      ref={selectRef}\n      mode=\"tags\"\n      dropdownStyle={{ visibility: loading ? 'hidden' : 'visible' }}\n      loading={loading}\n      placeholder={tips}\n      value={value ? [value] : []}\n      onFocus={onFocus}\n      onChange={onSelectChange}\n      filterOption={filterOptionByNameAndID}\n    >\n      {options.map((option) => (\n        <Select.Option key={option.table_id!} value={option.table_id!}>\n          {`${option.table_name}: ${option.table_id}`}\n        </Select.Option>\n      ))}\n    </Select>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/apilist/widgets/Text.tsx",
    "content": "import React from 'react'\nimport { Input } from 'antd'\n\nimport type { ApiFormWidget } from './index'\n\nexport const TextWidget: ApiFormWidget = ({ param }) => {\n  const placeholder = ((param.ui_props as any)?.placeholder as string) ?? ''\n  return <Input placeholder={placeholder} />\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/apilist/widgets/index.tsx",
    "content": "import React from 'react'\nimport type { FormInstance } from 'antd/es/form/Form'\nimport type { Topology } from '../ApiForm'\nimport { TextWidget } from './Text'\nimport { EnumWidget } from './Enum'\nimport { HostSelectWidget } from './Host'\nimport { DatabaseWidget } from './Database'\nimport { TableWidget } from './Table'\nimport { TableIDWidget } from './TableID'\nimport { EndpointAPIDefinition, EndpointAPIParamDefinition } from '@lib/client'\n\nexport interface Widgets {\n  [type: string]: ApiFormWidget\n}\n\nexport interface ApiFormWidget {\n  (config: ApiFormWidgetConfig): JSX.Element\n}\n\nexport interface ApiFormWidgetConfig {\n  form: FormInstance\n  param: EndpointAPIParamDefinition\n  endpoint: EndpointAPIDefinition\n  topology: Topology\n  value?: string\n  onChange?: (v: string) => void\n}\n\n// For customized form controls. https://ant.design/components/form-cn/#components-form-demo-customized-form-controls\nconst createJSXElementWrapper =\n  (WidgetDef: ApiFormWidget) => (config: ApiFormWidgetConfig) =>\n    <WidgetDef {...config} />\n\nconst paramModelWidgets: Widgets = {\n  host: HostSelectWidget,\n  text: TextWidget,\n  dropdown: EnumWidget,\n  db_dropdown: createJSXElementWrapper(DatabaseWidget),\n  table_dropdown: createJSXElementWrapper(TableWidget),\n  table_id_dropdown: createJSXElementWrapper(TableIDWidget)\n}\n\nexport const createFormWidget = (config: ApiFormWidgetConfig) => {\n  const { param } = config\n  const widget = paramModelWidgets[param.ui_kind ?? 'text']\n  return widget(config)\n}\n\n// query string\n\nexport const buildQueryString = (params: EndpointAPIParamDefinition[]) => {\n  const query = params.reduce((prev, param, i) => {\n    if (i === 0) {\n      prev += '?'\n    } else {\n      prev += '&'\n    }\n    prev += `${param.name}={${param.name}}`\n    return prev\n  }, '')\n  return query\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/apilist/widgets/useLimitSelection.ts",
    "content": "import { useCallback, useRef } from 'react'\n\nexport const useLimitSelection = (limit: number, emit: Function) => {\n  const selectRef = useRef<any>(null)\n  const onSelectChange = useCallback(\n    (items: string[]) => {\n      // Limit the available options to one option\n      // There are no official limit props. https://github.com/ant-design/ant-design/issues/6626\n      if (items.length > limit) {\n        items.shift()\n      }\n      if (items.length === limit) {\n        selectRef.current.blur()\n      }\n      emit?.(items)\n    },\n    [emit, limit, selectRef]\n  )\n\n  return {\n    selectRef,\n    onSelectChange\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport {\n  EndpointAPIDefinition,\n  EndpointRequestPayload,\n  InfoTableSchema,\n  TopologyTiDBInfo,\n  ClusterinfoStoreTopologyResponse,\n  TopologyPDInfo,\n  TopologyTiProxyInfo\n} from '@lib/client'\n\nimport { IContextConfig, ReqConfig } from '@lib/types'\n\nexport interface IDebugAPIDataSource {\n  debugAPIGetEndpoints(\n    options?: ReqConfig\n  ): AxiosPromise<Array<EndpointAPIDefinition>>\n\n  debugAPIRequestEndpoint(\n    req: EndpointRequestPayload,\n    options?: ReqConfig\n  ): AxiosPromise<string>\n\n  infoListDatabases(options?: ReqConfig): AxiosPromise<Array<string>>\n\n  infoListTables(\n    databaseName?: string,\n    options?: ReqConfig\n  ): AxiosPromise<Array<InfoTableSchema>>\n\n  getTiDBTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTiDBInfo>>\n\n  getStoreTopology(\n    options?: ReqConfig\n  ): AxiosPromise<ClusterinfoStoreTopologyResponse>\n\n  getPDTopology(options?: ReqConfig): AxiosPromise<Array<TopologyPDInfo>>\n\n  getTiProxyTopology(\n    options?: ReqConfig\n  ): AxiosPromise<Array<TopologyTiProxyInfo>>\n}\n\nexport interface IDebugAPIContext {\n  ds: IDebugAPIDataSource\n  cfg: IContextConfig\n}\n\nexport const DebugAPIContext = createContext<IDebugAPIContext | null>(null)\n\nexport const DebugAPIProvider = DebugAPIContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { Routes, Route, HashRouter as Router } from 'react-router-dom'\n\nimport { Root } from '@lib/components'\nimport { addTranslations } from '@lib/utils/i18n'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\n\nimport { DebugAPIContext } from './context'\nimport { ApiList } from './apilist'\nimport translations from './translations'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/debug_api\" element={<ApiList />} />\n    </Routes>\n  )\n}\n\nexport default function () {\n  const ctx = useContext(DebugAPIContext)\n  if (ctx === null) {\n    throw new Error('DebugAPIContext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/translations/en.yaml",
    "content": "debug_api:\n  nav_title: Debug Data\n  keyword_search: Filter by keyword\n  endpoints_not_found: Endpoints not found\n  warning_header:\n    title: Warning\n    body: These debug endpoints and data are largely internal and intended for use by {{ distro.tidb }} developers. Please use this feature under the guidance of {{ distro.tidb }} technical support.\n  form:\n    download: Download\n    reset: Reset\n  widgets:\n    host_select_placeholder: Select an instance\n    db_dropdown: Select or input a database name\n    table_dropdown: Select or input a table name\n    table_id_dropdown: Select or input a table ID\n  tidb:\n    name: '{{distro.tidb}}'\n    endpoints:\n      tidb_stats_by_table: Statistics Data - of a Table\n      tidb_stats_by_table_timestamp: Statistics Data - of a Table and Timestamp\n      tidb_stats_by_table_timestamp_desc: The timestamp needs to be set within the GC safe point\n      tidb_settings: Current Config\n      tidb_schema: Schema Information - All / by TableID\n      tidb_schema_by_db: Schema Information - by Database\n      tidb_schema_by_table: Schema Information - by Database + Table\n      tidb_schema_by_table_id: Schema and Table Information - by TableID\n      tidb_ddl_history: DDL History\n      tidb_server_info: Server Information - Current\n      tidb_all_servers_info: Server Information - All Servers\n      tidb_all_regions_meta: Region - All\n      tidb_region_meta_by_id: Region - by RegionID\n      tidb_table_regions: Region - by Database + Table\n      tidb_hot_regions: Region - Hot\n      tidb_pprof: pprof\n      tidb_pprof_desc: The `seconds` parameter is only effective to `kind=profile` and `kind=trace`.\n\n      # for old tidb-dashboard backend api before 5.4.0\n      # get back from https://github.com/pingcap/tidb-dashboard/pull/1103/files#diff-fc455ec052d6f881db40d33377d3a6cd757100b476f10eb68ca5e72696e8a463L23\n      tidb_stats_dump: Statistics Data - of a Table\n      tidb_stats_dump_timestamp: Statistics Data - of a Table and Timestamp\n      tidb_stats_dump_timestamp_desc: The timestamp needs to be set within the GC safe point\n      tidb_config: Current Config\n      # tidb_schema: Schema Information - All / by TableID\n      tidb_schema_db: Schema Information - by Database\n      tidb_schema_db_table: Schema Information - by Database + Table\n      tidb_dbtable_tableid: Schema and Table Information - by TableID\n      tidb_info: Server Information - Current\n      tidb_info_all: Server Information - All Servers\n      tidb_regions_meta: Region - All\n      tidb_region_id: Region - by RegionID\n      # tidb_table_regions: Region - by Database + Table\n      # tidb_hot_regions: Region - Hot\n      # tidb_pprof: pprof\n      # tidb_pprof_desc: The `seconds` parameter is only effective to `kind=profile` and `kind=trace`.\n  tikv:\n    name: '{{distro.tikv}}'\n    endpoints:\n      tikv_config: Current Config\n      tikv_pprof_profile: CPU Profiling\n\n      # for old tidb-dashboard backend api before 5.4.0\n      # tikv_config: Current Config\n      tikv_profile: CPU Profiling\n  tiflash:\n    name: '{{distro.tiflash}}'\n    endpoints:\n      tiflash_config: Current Config\n      tiflash_pprof_profile: CPU Profiling\n\n      # for old tidb-dashboard backend api before 5.4.0\n      # tiflash_config: Current Config\n      tiflash_profile: CPU Profiling\n  pd:\n    name: '{{distro.pd}}'\n    endpoints:\n      pd_cluster_info: Cluster Information (pd-ctl cluster)\n      pd_cluster_status: Cluster Status\n      pd_configs_all: Current Config\n      pd_health: Cluster Health Information (pd-ctl health)\n      pd_hot_read: Hot - Read (pd-ctl hot read)\n      pd_hot_write: Hot - Write (pd-ctl hot write)\n      pd_hot_stores: Hot - Stores (pd-ctl hot store)\n      pd_labels_all: All Labels (pd-ctl label)\n      pd_members_all: All Members Information (pd-ctl member)\n      pd_leader: Leader Information (pd-ctl member leader show)\n      pd_operators: All Operators (pd-ctl operator show)\n      pd_regions_all: Regions - All (pd-ctl region)\n      pd_region_by_id: Region - by RegionID (pd-ctl region [id])\n      pd_region_by_key: Region - by Key Reside in (pd-ctl region key [key])\n      pd_regions_sibling_by_id: Regions - Sibling Regions by RegionID (pd-ctl region sibling [id])\n      pd_regions_store: Regions - All Regions of a Store (pd-ctl region store [store-id])\n      pd_regions_by_top_read: Regions - Top Read Flow (pd-ctl region topread)\n      pd_regions_by_top_write: Regions - Top Write Flow (pd-ctl region topread)\n      pd_regions_by_top_conf_ver: Regions - Top Conf Version (pd-ctl region topconfver)\n      pd_regions_by_top_version: Regions - Top Version (pd-ctl region topversion)\n      pd_regions_by_top_size: Regions - Top Size (pd-ctl region topsize)\n      pd_regions_by_state: Regions - by State (region check [state])\n      pd_schedulers_all: All Schedulers (pd-ctl scheduler show)\n      pd_stores_all: Stores - All (pd-ctl store)\n      pd_stores_by_label: Stores - by Label (pd-ctl label store [name] [value])\n      pd_store_by_id: Store - by StoreID (pd-ctl store [id])\n      pd_pprof: pprof\n      pd_pprof_desc: The `seconds` parameter is only effective to `kind=profile` and `kind=trace`.\n\n      # for old tidb-dashboard backend api before 5.4.0\n      pd_cluster: Cluster Information (pd-ctl cluster)\n      # pd_cluster_status: Cluster Status\n      pd_config_show_all: Current Config\n      # pd_health: Cluster Health Information (pd-ctl health)\n      # pd_hot_read: Hot - Read (pd-ctl hot read)\n      # pd_hot_write: Hot - Write (pd-ctl hot write)\n      # pd_hot_stores: Hot - Stores (pd-ctl hot store)\n      pd_labels: All Labels (pd-ctl label)\n      pd_members_show: All Members Information (pd-ctl member)\n      pd_leader_show: Leader Information (pd-ctl member leader show)\n      pd_operator_show: All Operators (pd-ctl operator show)\n      pd_regions: Regions - All (pd-ctl region)\n      pd_region_id: Region - by RegionID (pd-ctl region [id])\n      pd_region_key: Region - by Key Reside in (pd-ctl region key [key])\n      pd_region_scan: Regions - Scan All (pd-ctl region scan)\n      pd_region_sibling: Regions - Sibling Regions by RegionID (pd-ctl region sibling [id])\n      # pd_regions_store: Regions - All Regions of a Store (pd-ctl region store [store-id])\n      pd_region_start_key: Regions - All Regions Starting from a Key (pd-ctl region startkey [key])\n      pd_region_top_read: Regions - Top Read Flow (pd-ctl region topread)\n      pd_region_top_write: Regions - Top Write Flow (pd-ctl region topread)\n      pd_region_top_conf_ver: Regions - Top Conf Version (pd-ctl region topconfver)\n      pd_region_top_version: Regions - Top Version (pd-ctl region topversion)\n      pd_region_top_size: Regions - Top Size (pd-ctl region topsize)\n      pd_region_check: Regions - by State (region check [state])\n      pd_scheduler_show: All Schedulers (pd-ctl scheduler show)\n      pd_stores: Stores - All (pd-ctl store)\n      pd_label_stores: Stores - by Label (pd-ctl label store [name] [value])\n      pd_store_id: Store - by StoreID (pd-ctl store [id])\n      # pd_pprof: pprof\n      # pd_pprof_desc: The `seconds` parameter is only effective to `kind=profile` and `kind=trace`.\n  tiproxy:\n    name: '{{distro.tiproxy}}'\n    endpoints:\n      tiproxy_config: Current Config\n      tiproxy_pprof: pprof\n      tiproxy_pprof_desc: The `seconds` parameter is only effective to `kind=profile` and `kind=trace`.\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/DebugAPI/translations/zh.yaml",
    "content": "debug_api:\n  nav_title: 内部调试数据\n  keyword_search: 按关键字过滤接口\n  endpoints_not_found: 找不到对应接口\n  warning_header:\n    title: 警告\n    body: 本页面提供的调试接口主要面向 {{ distro.tidb }} 开发者、提供数据库内部运行数据。请在 {{ distro.tidb }} 技术支持的指导下使用本功能。\n  form:\n    download: 下载\n    reset: 重置\n  widgets:\n    host_select_placeholder: 选择实例\n    db_dropdown: 选择或输入一个数据库名称\n    table_dropdown: 选择或输入一个数据表名称\n    table_id_dropdown: 选择或输入一个数据表 ID\n  tidb:\n    endpoints:\n      tidb_stats_by_table_timestamp_desc: 时间戳应当在 GC Safe Point 以后\n      tidb_pprof_desc: seconds 参数仅对 kind=profile 和 kind=trace 生效\n\n      # for old tidb-dashboard backend api before 5.4.0\n      tidb_stats_dump_timestamp_desc: 时间戳应当在 GC Safe Point 以后\n  pd:\n    endpoints:\n      pd_pprof_desc: seconds 参数仅对 kind=profile 和 kind=trace 生效\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Diagnose/components/DiagnosisTable.tsx",
    "content": "import { Button } from 'antd'\nimport React, {\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n  useCallback,\n  useContext\n} from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { LoadingOutlined } from '@ant-design/icons'\n\nimport { DiagnoseTableDef } from '@lib/client'\nimport { CardTable, DateTime } from '@lib/components'\nimport { useClientRequest, RequestFactory } from '@lib/utils/useClientRequest'\n\nimport { diagnosisColumns } from '../utils/tableColumns'\nimport { DiagnoseContext } from '../context'\nimport { useIsWriteable } from '@lib/utils'\n\n// FIXME: use better naming\n// stableTimeRange: used to start diagnosing when triggering by clicking \"Start\" outside this component\n// unstableTimeRange: used to start diagnosing when triggering by clicking \"Start\" inside this component\nexport interface IDiagnosisTableProps {\n  stableTimeRange: [number, number]\n  unstableTimeRange: [number, number]\n  kind: string\n}\n\ntype ReqFnType = RequestFactory<DiagnoseTableDef>\n\n// Modified from SearchResult.tsx\nfunction Row({ renderer, props }) {\n  const [expanded, setExpanded] = useState(false)\n  const handleClick = useCallback(() => {\n    setExpanded((v) => !v)\n  }, [])\n\n  // https://stackoverflow.com/questions/53623294/how-to-conditionally-change-a-color-of-a-row-in-detailslist\n  const backgroundColor = props.item.is_sub ? 'lightcyan' : 'inhert'\n  return (\n    <div onClick={handleClick} style={{ cursor: 'pointer' }}>\n      {renderer({\n        ...props,\n        styles: { root: { backgroundColor } },\n        item: { ...props.item, expanded }\n      })}\n    </div>\n  )\n}\n\nexport default function DiagnosisTable({\n  stableTimeRange,\n  unstableTimeRange,\n  kind\n}: IDiagnosisTableProps) {\n  const ctx = useContext(DiagnoseContext)\n\n  const isWriteable = useIsWriteable()\n\n  const { t } = useTranslation()\n\n  const [internalTimeRange, setInternalTimeRange] = useState<[number, number]>([\n    0, 0\n  ])\n  useEffect(() => setInternalTimeRange(stableTimeRange), [stableTimeRange])\n  function handleStart() {\n    setInternalTimeRange(unstableTimeRange)\n  }\n  const timeChanged = useMemo(\n    () =>\n      internalTimeRange[0] !== unstableTimeRange[0] ||\n      internalTimeRange[1] !== unstableTimeRange[1],\n    [internalTimeRange, unstableTimeRange]\n  )\n\n  const reqFn = useRef<ReqFnType | null>(null)\n  useEffect(() => {\n    reqFn.current = (reqConfig) =>\n      ctx!.ds.diagnoseDiagnosisPost(\n        {\n          start_time: internalTimeRange[0],\n          end_time: internalTimeRange[1],\n          kind\n        },\n        reqConfig\n      )\n  }, [internalTimeRange, kind, ctx])\n\n  const { data, isLoading, error, sendRequest } = useClientRequest(\n    reqFn.current!,\n    { immediate: false }\n  )\n\n  useEffect(() => {\n    if (internalTimeRange[0] !== 0) {\n      sendRequest()\n    }\n  }, [internalTimeRange, sendRequest])\n\n  ////////////////\n\n  const allRows = useMemo(() => {\n    const _columnHeaders =\n      data?.column?.map((col) => col.toLocaleLowerCase()) || []\n    let _rows: any[] = []\n    data?.rows?.forEach((row, rowIdx) => {\n      // values (array)\n      let _newRow = { row_idx: rowIdx, is_sub: false, show_sub: false }\n      row.values?.forEach((v, v_idx) => {\n        const key = _columnHeaders[v_idx]\n        _newRow[key] = v\n      })\n\n      // subvalues (2 demensional array)\n      let _subRows: any[] = []\n      row.sub_values?.forEach((sub_v) => {\n        let _subRow = { row_idx: rowIdx, is_sub: true }\n        sub_v.forEach((v, idx) => {\n          const key = _columnHeaders[idx]\n          _subRow[key] = v\n        })\n        _subRows.push(_subRow)\n      })\n\n      _newRow['sub_rows'] = _subRows\n      _rows.push(_newRow)\n    })\n    return _rows\n  }, [data])\n\n  const [items, setItems] = useState(allRows)\n  useEffect(() => {\n    setItems(allRows)\n  }, [allRows])\n\n  const toggleShowSub = useCallback(\n    (rowIdx, showSub) => {\n      let newRows = [...items]\n      let curRowPos = newRows.findIndex(\n        (el) => el.row_idx === rowIdx && el.is_sub === false\n      )\n      if (curRowPos === -1) {\n        return\n      }\n      let curRow = newRows[curRowPos]\n\n      // update status\n      curRow.show_sub = showSub\n      if (showSub) {\n        // insert sub rows\n        newRows.splice(curRowPos + 1, 0, ...curRow.sub_rows)\n      } else {\n        // remove sub rows\n        newRows.splice(curRowPos + 1, curRow.sub_rows.length)\n      }\n      setItems(newRows)\n    },\n    [items]\n  )\n\n  const columns = useMemo(\n    () => diagnosisColumns(items, toggleShowSub),\n    [items, toggleShowSub]\n  )\n\n  ////////////////\n\n  const renderRow = useCallback((props, defaultRender) => {\n    if (!props) {\n      return null\n    }\n    return <Row renderer={defaultRender!} props={props} />\n  }, [])\n\n  ////////////////\n\n  function cardExtra() {\n    if (isLoading) {\n      return <LoadingOutlined />\n    }\n    if (timeChanged || error) {\n      return (\n        <Button onClick={handleStart} disabled={!isWriteable}>\n          {t('diagnose.generate.submit')}\n        </Button>\n      )\n    }\n    return null\n  }\n\n  function subTitle() {\n    if (internalTimeRange[0] > 0) {\n      return (\n        <span>\n          <DateTime.Calendar unixTimestampMs={internalTimeRange[0] * 1000} /> ~{' '}\n          <DateTime.Calendar unixTimestampMs={internalTimeRange[1] * 1000} />\n        </span>\n      )\n    }\n    return null\n  }\n\n  return (\n    <CardTable\n      title={t(`diagnose.table_title.${kind}_diagnosis`)}\n      subTitle={subTitle()}\n      cardExtra={cardExtra()}\n      errors={[error]}\n      columns={columns}\n      items={items}\n      onRenderRow={renderRow}\n      extendLastColumn\n    />\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Diagnose/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport {\n  DiagnoseGenDiagnosisReportRequest,\n  DiagnoseTableDef\n} from '@lib/client'\n\nimport { ReqConfig } from '@lib/types'\n\nexport interface IDiagnoseDataSource {\n  diagnoseDiagnosisPost(\n    request: DiagnoseGenDiagnosisReportRequest,\n    options?: ReqConfig\n  ): AxiosPromise<DiagnoseTableDef>\n}\n\nexport interface IDiagnoseContext {\n  ds: IDiagnoseDataSource\n}\n\nexport const DiagnoseContext = createContext<IDiagnoseContext | null>(null)\n\nexport const DiagnoseProvider = DiagnoseContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Diagnose/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { HashRouter as Router, Route, Routes } from 'react-router-dom'\n\nimport { Root } from '@lib/components'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\nimport { addTranslations } from '@lib/utils/i18n'\n\nimport { DiagnoseContext } from './context'\nimport { DiagnoseGenerator } from './pages'\nimport translations from './translations'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/diagnose\" element={<DiagnoseGenerator />} />\n    </Routes>\n  )\n}\n\nconst App = () => {\n  const ctx = useContext(DiagnoseContext)\n  if (ctx === null) {\n    throw new Error('DiagnoseContext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport default App\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Diagnose/pages/DiagnoseGenerator.tsx",
    "content": "import { Button, Form, Input, InputNumber, Select } from 'antd'\nimport dayjs, { Dayjs } from 'dayjs'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport React, { useState, useMemo } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\n\nimport { Card } from '@lib/components'\nimport { DatePicker } from '@lib/components'\nimport DiagnosisTable from '../components/DiagnosisTable'\nimport { useIsWriteable } from '@lib/utils'\n\nconst DURATION_MINS = [5, 10, 30, 60, 24 * 60]\nconst DEF_DURATION_MINS = 10\n\nfunction minsAgo(mins: number): Dayjs {\n  return dayjs().subtract(mins, 'm')\n}\n\nexport default function DiagnoseGenerator() {\n  const { t } = useTranslation()\n\n  const isWriteable = useIsWriteable()\n\n  const [duration, setDuration] = useState(DEF_DURATION_MINS)\n  const [startTime, setStartTime] = useState<Dayjs>(() => minsAgo(duration))\n  const timeRange: [number, number] = useMemo(() => {\n    const _startTime = dayjs(startTime).unix()\n    return [_startTime, _startTime + duration * 60]\n  }, [startTime, duration])\n\n  const [stableTimeRange, setStableTimeRange] = useState<[number, number]>([\n    0, 0\n  ])\n\n  function handleFinish() {\n    setStableTimeRange(timeRange)\n  }\n\n  const timeChanged = useMemo(\n    () =>\n      timeRange[0] !== stableTimeRange[0] ||\n      timeRange[1] !== stableTimeRange[1],\n    [timeRange, stableTimeRange]\n  )\n\n  return (\n    <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>\n      <Card title={t('diagnose.generate.title')}>\n        <Form\n          layout=\"inline\"\n          onFinish={handleFinish}\n          initialValues={{\n            rangeDuration: DEF_DURATION_MINS,\n            rangeDurationCustom: DEF_DURATION_MINS\n          }}\n        >\n          <Form.Item\n            rules={[{ required: true }]}\n            label={t('diagnose.generate.range_begin')}\n          >\n            <DatePicker\n              value={startTime}\n              showTime\n              onChange={(val) => setStartTime(val || minsAgo(duration))}\n            />\n          </Form.Item>\n          <Form.Item label={t('diagnose.generate.range_duration')} required>\n            <Input.Group compact>\n              <Form.Item\n                name=\"rangeDuration\"\n                rules={[{ required: true }]}\n                noStyle\n              >\n                <Select\n                  style={{ width: 120 }}\n                  onChange={(val) =>\n                    setDuration((val as number) || DEF_DURATION_MINS)\n                  }\n                >\n                  {DURATION_MINS.map((val) => (\n                    <Select.Option key={val} value={val}>\n                      {getValueFormat('m')(val, 0)}\n                    </Select.Option>\n                  ))}\n                  <Select.Option value={0}>\n                    {t('diagnose.time_duration.custom')}\n                  </Select.Option>\n                </Select>\n              </Form.Item>\n              <Form.Item\n                noStyle\n                shouldUpdate={(prev, cur) =>\n                  prev.rangeDuration !== cur.rangeDuration\n                }\n              >\n                {({ getFieldValue }) => {\n                  return (\n                    getFieldValue('rangeDuration') === 0 && (\n                      <Form.Item\n                        noStyle\n                        name=\"rangeDurationCustom\"\n                        rules={[{ required: true }]}\n                      >\n                        <InputNumber\n                          min={1}\n                          max={30 * 24 * 60}\n                          formatter={(value) => `${value} min`}\n                          parser={(value) =>\n                            parseInt(value?.replace(/[^\\d]/g, '') || '')\n                          }\n                          style={{ width: 120 }}\n                          onChange={(val) => setDuration(val as number)}\n                        />\n                      </Form.Item>\n                    )\n                  )\n                }}\n              </Form.Item>\n            </Input.Group>\n          </Form.Item>\n          {timeChanged && (\n            <Form.Item>\n              <Button type=\"primary\" htmlType=\"submit\" disabled={!isWriteable}>\n                {t('diagnose.generate.submit')}\n              </Button>\n            </Form.Item>\n          )}\n        </Form>\n      </Card>\n\n      <div style={{ height: '100%', position: 'relative' }}>\n        <ScrollablePane>\n          <DiagnosisTable\n            stableTimeRange={stableTimeRange}\n            unstableTimeRange={timeRange}\n            kind=\"config\"\n          />\n          <DiagnosisTable\n            stableTimeRange={stableTimeRange}\n            unstableTimeRange={timeRange}\n            kind=\"performance\"\n          />\n          <DiagnosisTable\n            stableTimeRange={stableTimeRange}\n            unstableTimeRange={timeRange}\n            kind=\"error\"\n          />\n        </ScrollablePane>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Diagnose/pages/index.ts",
    "content": "import DiagnoseGenerator from './DiagnoseGenerator'\n\nexport { DiagnoseGenerator }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Diagnose/translations/en.yaml",
    "content": "diagnose:\n  nav_title: Cluster Diagnostics\n  generate:\n    title: Cluster Diagnostics\n    range_begin: Range Start Time\n    range_duration: Range Duration\n    submit: Start\n  time_duration:\n    custom: Custom\n  table_title:\n    config_diagnosis: Config Diagnosis\n    error_diagnosis: Error Diagnosis\n    performance_diagnosis: Performance Diagnosis\n  fields:\n    rule: Rule\n    item: Item\n    type: Type\n    instance: Instance\n    status_address: Status Address\n    value: Value\n    reference: Reference\n    severity: Severity\n    details: Details\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Diagnose/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Diagnose/translations/zh.yaml",
    "content": "diagnose:\n  nav_title: 集群诊断\n  generate:\n    title: 集群诊断\n    range_begin: 区间起始时间\n    range_duration: 区间长度\n    submit: 开始\n  time_duration:\n    custom: 自定义\n  table_title:\n    config_diagnosis: 配置诊断\n    error_diagnosis: 故障诊断\n    performance_diagnosis: 性能诊断\n  fields:\n    rule: Rule\n    item: Item\n    type: Type\n    instance: Instance\n    status_address: Status Address\n    value: Value\n    reference: Reference\n    severity: Severity\n    details: Details\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Diagnose/utils/tableColumns.tsx",
    "content": "import { Tooltip } from 'antd'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport React from 'react'\nimport { PlusOutlined, MinusOutlined } from '@ant-design/icons'\n\nimport { TextWithInfo, TextWrap } from '@lib/components'\n\ntype ToggleShowSubFn = (rowIdx: number, showSub: boolean) => void\n\nfunction commonColumnName(fieldName: string): any {\n  return <TextWithInfo.TransKey transKey={`diagnose.fields.${fieldName}`} />\n}\n\nfunction commonColumn(fieldName: string, minWidth: number, maxWidth?: number) {\n  return {\n    name: commonColumnName(fieldName),\n    key: fieldName,\n    fieldName,\n    minWidth,\n    maxWidth,\n    onRender: (rec) => {\n      if (rec.expanded) {\n        return <TextWrap multiline={true}>{rec[fieldName]}</TextWrap>\n      } else {\n        return (\n          <Tooltip title={rec[fieldName]}>\n            <TextWrap>{rec[fieldName]}</TextWrap>\n          </Tooltip>\n        )\n      }\n    }\n  }\n}\n\nfunction ruleColumn(toggleShowSub: ToggleShowSubFn): IColumn {\n  const handleClick = (ev: React.MouseEvent<HTMLSpanElement>, rec) => {\n    ev.stopPropagation()\n    toggleShowSub(rec.row_idx, !rec.show_sub)\n  }\n  return {\n    ...commonColumn('rule', 150, 200),\n    onRender: (rec) => (\n      <TextWrap multiline={rec.expanded}>\n        {rec.is_sub && '|--'}\n        {!rec.is_sub &&\n          rec.sub_rows.length > 0 &&\n          (rec.show_sub ? (\n            <MinusOutlined onClick={(ev) => handleClick(ev, rec)} />\n          ) : (\n            <PlusOutlined onClick={(ev) => handleClick(ev, rec)} />\n          ))}{' '}\n        {rec.expanded ? (\n          rec.rule\n        ) : (\n          <Tooltip title={rec.rule}>{rec.rule}</Tooltip>\n        )}\n      </TextWrap>\n    )\n  }\n}\n\nfunction itemColumn(): IColumn {\n  return commonColumn('item', 100, 150)\n}\n\nfunction typeColumn(): IColumn {\n  return commonColumn('type', 60, 80)\n}\n\nfunction instanceColumn(): IColumn {\n  return commonColumn('instance', 100, 200)\n}\n\nfunction statusAddressColumn(): IColumn {\n  return commonColumn('status_address', 100, 200)\n}\n\nfunction valueColumn(): IColumn {\n  return commonColumn('value', 100, 150)\n}\n\nfunction referenceColumn(): IColumn {\n  return commonColumn('reference', 100, 150)\n}\n\nfunction severityColumn(): IColumn {\n  return commonColumn('severity', 100, 120)\n}\n\nfunction detailsColumn(): IColumn {\n  return commonColumn('details', 200)\n}\n\nfunction categoryColumn(): IColumn {\n  return commonColumn('category', 100, 200)\n}\n\nfunction tableColumn(): IColumn {\n  return commonColumn('table', 100, 200)\n}\n\nfunction errorColumn(): IColumn {\n  return commonColumn('error', 200)\n}\n\n//////////////////////////////////////////\n\nexport function diagnosisColumns(\n  rows: any[],\n  toggleShowSub: ToggleShowSubFn\n): IColumn[] {\n  if (rows.length > 0 && rows[0].error) {\n    return [categoryColumn(), tableColumn(), errorColumn()]\n  }\n  return [\n    ruleColumn(toggleShowSub),\n    itemColumn(),\n    typeColumn(),\n    instanceColumn(),\n    statusAddressColumn(),\n    valueColumn(),\n    referenceColumn(),\n    severityColumn(),\n    detailsColumn()\n  ]\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport {\n  ProfilingGroupDetailResponse,\n  ProfilingTaskGroupModel,\n  ProfilingStartRequest,\n  ConprofNgMonitoringConfig,\n  TopologyTiDBInfo,\n  ClusterinfoStoreTopologyResponse,\n  TopologyPDInfo,\n  TopologyTiCDCInfo,\n  TopologyTiProxyInfo,\n  TopologyTSOInfo,\n  TopologySchedulingInfo\n} from '@lib/client'\n\nimport { IContextConfig, ReqConfig } from '@lib/types'\n\nexport interface IInstanceProfilingDataSource {\n  getActionToken(\n    id?: string,\n    action?: string,\n    options?: ReqConfig\n  ): AxiosPromise<string>\n\n  getProfilingGroupDetail(\n    groupId: string,\n    options?: ReqConfig\n  ): AxiosPromise<ProfilingGroupDetailResponse>\n\n  getProfilingGroups(\n    options?: ReqConfig\n  ): AxiosPromise<Array<ProfilingTaskGroupModel>>\n\n  startProfiling(\n    req: ProfilingStartRequest,\n    options?: ReqConfig\n  ): AxiosPromise<ProfilingTaskGroupModel>\n\n  continuousProfilingConfigGet(\n    options?: ReqConfig\n  ): AxiosPromise<ConprofNgMonitoringConfig>\n\n  getTiDBTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTiDBInfo>>\n\n  getStoreTopology(\n    options?: ReqConfig\n  ): AxiosPromise<ClusterinfoStoreTopologyResponse>\n\n  getPDTopology(options?: ReqConfig): AxiosPromise<Array<TopologyPDInfo>>\n\n  getTiCDCTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTiCDCInfo>>\n\n  getTiProxyTopology(\n    options?: ReqConfig\n  ): AxiosPromise<Array<TopologyTiProxyInfo>>\n\n  getTSOTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTSOInfo>>\n\n  getSchedulingTopology(\n    options?: ReqConfig\n  ): AxiosPromise<Array<TopologySchedulingInfo>>\n}\n\nexport interface IInstanceProfilingContext {\n  ds: IInstanceProfilingDataSource\n  cfg: IContextConfig & { publicPathBase: string }\n}\n\nexport const InstanceProfilingContext =\n  createContext<IInstanceProfilingContext | null>(null)\n\nexport const InstanceProfilingProvider = InstanceProfilingContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { HashRouter as Router, Route, Routes } from 'react-router-dom'\n\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\nimport { Root, ParamsPageWrapper } from '@lib/components'\nimport { addTranslations } from '@lib/utils/i18n'\n\nimport { Detail, List } from './pages'\nimport translations from './translations'\nimport { InstanceProfilingContext } from './context'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/instance_profiling\" element={<List />} />\n      <Route\n        path=\"/instance_profiling/detail\"\n        element={\n          <ParamsPageWrapper>\n            <Detail />\n          </ParamsPageWrapper>\n        }\n      />\n    </Routes>\n  )\n}\n\nconst App = () => {\n  const ctx = useContext(InstanceProfilingContext)\n  if (ctx === null) {\n    throw new Error('InstanceProfilingContext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport default App\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/pages/Detail.tsx",
    "content": "import { Badge, Button, Modal, Progress, Space, Tooltip } from 'antd'\nimport React, { useCallback, useContext, useMemo } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useMemoizedFn } from 'ahooks'\nimport { Link } from 'react-router-dom'\nimport { ArrowLeftOutlined, QuestionCircleOutlined } from '@ant-design/icons'\nimport { upperFirst } from 'lodash'\n\nimport { ProfilingTaskModel } from '@lib/client'\nimport { CardTable, DateTime, Head, Descriptions, Card } from '@lib/components'\nimport { useClientRequestWithPolling } from '@lib/utils/useClientRequest'\nimport { instanceKindName, InstanceKinds } from '@lib/utils/instanceTable'\nimport useQueryParams from '@lib/utils/useQueryParams'\nimport { IGroup } from 'office-ui-fabric-react/lib/DetailsList'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport {\n  IInstanceProfilingDataSource,\n  InstanceProfilingContext\n} from '../context'\n\nenum ViewOptions {\n  FlameGraph = 'flamegraph',\n  Graph = 'graph',\n  Download = 'download',\n  Text = 'text'\n}\n\nenum taskState {\n  Error,\n  Running,\n  Success,\n  Skipped = 4\n}\n\nenum RawDataType {\n  Protobuf = 'protobuf',\n  Jeprof = 'jeprof',\n  Text = 'text'\n}\n\ninterface IRow {\n  kind: string\n}\n\nfunction mapData(data) {\n  if (!data) {\n    return data\n  }\n\n  data.tasks_status.forEach((task) => {\n    if (task.state === 1) {\n      let task_elapsed_secs = data.server_time - task.started_at\n      let progress =\n        task_elapsed_secs / data.task_group_status.profile_duration_secs\n      if (progress > 0.99) {\n        progress = 0.99\n      }\n      if (progress < 0) {\n        progress = 0\n      }\n      task.progress = progress\n    }\n\n    // set profiling output options for previous generated SVG files and protobuf files.\n    if (task.raw_data_type === RawDataType.Protobuf) {\n      task.view_options = [\n        ViewOptions.FlameGraph,\n        ViewOptions.Graph,\n        ViewOptions.Download\n      ]\n    } else if (task.raw_data_type === RawDataType.Jeprof) {\n      task.view_options = [\n        ViewOptions.FlameGraph,\n        ViewOptions.Graph,\n        ViewOptions.Download\n      ]\n    } else if (task.raw_data_type === RawDataType.Text) {\n      task.view_options = [ViewOptions.Text]\n    } else if (!task.raw_data_type) {\n      switch (task.target.kind) {\n        case 'tidb':\n        case 'pd':\n          task.view_options = [ViewOptions.Graph]\n          break\n        case 'tikv':\n        case 'tiflash':\n          task.view_options = [ViewOptions.FlameGraph]\n          break\n      }\n    }\n  })\n\n  return data\n}\n\nfunction isFinished(data) {\n  const groupState = data?.task_group_status?.state\n  return groupState === 2 || groupState === 3\n}\n\nasync function getActionToken(\n  id: string,\n  apiType: string,\n  fetcher: IInstanceProfilingDataSource['getActionToken']\n): Promise<string | undefined> {\n  const res = await fetcher(id, apiType)\n  const token = res.data\n  if (!token) {\n    return\n  }\n  return token\n}\n\ninterface IRecord extends ProfilingTaskModel {\n  view_options: ViewOptions[]\n}\n\nexport default function Page() {\n  const ctx = useContext(InstanceProfilingContext)\n\n  const { t } = useTranslation()\n  const { id } = useQueryParams()\n\n  const {\n    data: respData,\n    isLoading,\n    error\n  } = useClientRequestWithPolling(\n    (reqConfig) => ctx!.ds.getProfilingGroupDetail(id, reqConfig),\n    {\n      shouldPoll: (data) => !isFinished(data)\n    }\n  )\n\n  const data = useMemo(() => mapData(respData), [respData])\n\n  const profileDuration =\n    respData?.task_group_status?.profile_duration_secs || 0\n\n  const [tableData, groupData] = useMemo(() => {\n    const newRows: IRow[] = []\n    const newGroups: IGroup[] = []\n    let startIndex = 0\n    const tasks = data?.tasks_status ?? []\n    for (const kind of InstanceKinds) {\n      tasks.forEach((task) => {\n        if (task.target.kind === kind) {\n          newRows.push({\n            ...task,\n            kind: instanceKindName(kind)\n          })\n        }\n      })\n\n      if (newRows.length - startIndex > 0) {\n        newGroups.push({\n          key: instanceKindName(kind),\n          name: instanceKindName(kind),\n          startIndex: startIndex,\n          count: newRows.length - startIndex\n        })\n        startIndex = newRows.length\n      }\n    }\n    return [newRows, newGroups]\n  }, [data])\n\n  const openResult = useMemoizedFn(async (openAs: string, rec: IRecord) => {\n    let token: string | undefined\n    let profileURL: string\n\n    switch (openAs) {\n      case ViewOptions.Download:\n        token = await getActionToken(\n          rec.id + '',\n          'single_download',\n          ctx!.ds.getActionToken\n        )\n        if (!token) {\n          return\n        }\n\n        window.location.href = `${\n          ctx!.cfg.apiPathBase\n        }/profiling/single/download?token=${token}`\n        break\n      case ViewOptions.FlameGraph:\n        token = await getActionToken(\n          rec.id + '',\n          'single_view',\n          ctx!.ds.getActionToken\n        )\n        if (!token) {\n          return\n        }\n        profileURL = `${\n          ctx!.cfg.apiPathBase\n        }/profiling/single/view?token=${token}`\n        const titleOnTab = rec.target?.kind + '_' + rec.target?.display_name\n        const type =\n          rec.raw_data_type === RawDataType.Protobuf ? 'protobuf' : 'text'\n        profileURL = `${\n          ctx!.cfg.publicPathBase\n        }/speedscope/#profileURL=${encodeURIComponent(\n          // protobuf can be rendered to flamegraph by speedscope\n          profileURL + `&output_type=${type}`\n        )}&title=${titleOnTab}`\n\n        window.open(`${profileURL}`, '_blank')\n        break\n      case ViewOptions.Graph:\n      case ViewOptions.Text:\n        token = await getActionToken(\n          rec.id + '',\n          'single_view',\n          ctx!.ds.getActionToken\n        )\n        if (!token) {\n          return\n        }\n        profileURL = `${\n          ctx!.cfg.apiPathBase\n        }/profiling/single/view?token=${token}&output_type=${openAs}`\n\n        window.open(`${profileURL}`, '_blank')\n        break\n    }\n  })\n\n  const columns = useMemo(\n    () => [\n      {\n        name: t('instance_profiling.detail.table.columns.instance'),\n        key: 'instance',\n        minWidth: 100,\n        maxWidth: 200,\n        onRender: (record) => record.target.display_name\n      },\n      {\n        name: t('instance_profiling.detail.table.columns.content'),\n        key: 'content',\n        minWidth: 100,\n        maxWidth: 100,\n        onRender: (record) => {\n          if (record.profiling_type === 'cpu') {\n            return `CPU - ${profileDuration}s`\n          } else {\n            return upperFirst(record.profiling_type)\n          }\n        }\n      },\n      {\n        name: t('instance_profiling.detail.table.columns.status'),\n        key: 'status',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (record) => {\n          if (record.state === taskState.Running) {\n            return (\n              <Badge\n                status=\"processing\"\n                text={t('instance_profiling.detail.table.status.running')}\n              />\n            )\n          } else if (record.state === taskState.Error) {\n            return (\n              <Badge\n                status=\"error\"\n                text={t('instance_profiling.detail.table.status.error')}\n              />\n            )\n          } else if (record.state === taskState.Skipped) {\n            return (\n              <Tooltip\n                title={t(\n                  'instance_profiling.detail.table.status.skipped_tooltip'\n                )}\n              >\n                <Space>\n                  <Badge\n                    status=\"default\"\n                    text={t('instance_profiling.detail.table.status.skipped')}\n                  />\n                  <QuestionCircleOutlined />\n                </Space>\n              </Tooltip>\n            )\n          } else {\n            return (\n              <Badge\n                status=\"success\"\n                text={t('instance_profiling.detail.table.status.finished')}\n              />\n            )\n          }\n        }\n      },\n      {\n        name: t('instance_profiling.detail.table.columns.view_as.title'),\n        key: 'view_as',\n        minWidth: 250,\n        maxWidth: 400,\n        onRender: (record) => {\n          if (record.state === taskState.Error) {\n            return (\n              <a\n                onClick={() => {\n                  Modal.error({\n                    title: 'Profile Error',\n                    content: record.error\n                  })\n                }}\n              >\n                {t('instance_profiling.detail.table.columns.view_as.error')}\n              </a>\n            )\n          }\n\n          if (record.state === taskState.Running) {\n            return (\n              <div style={{ width: 150 }}>\n                <Progress\n                  percent={Math.round(record.progress * 100)}\n                  size=\"small\"\n                  width={200}\n                />\n              </div>\n            )\n          }\n\n          if (record.state !== taskState.Success) {\n            return <></>\n          }\n\n          const rec = record as IRecord\n          return (\n            <Space>\n              {rec.view_options.map((action) => {\n                return (\n                  <a onClick={() => openResult(action, record)} key={action}>\n                    {t(\n                      `instance_profiling.detail.table.columns.view_as.${action}`\n                    )}\n                  </a>\n                )\n              })}\n            </Space>\n          )\n        }\n      }\n    ],\n    [t, profileDuration, openResult]\n  )\n\n  const handleDownloadGroup = useCallback(async () => {\n    const token = await getActionToken(\n      id,\n      'group_download',\n      ctx!.ds.getActionToken\n    )\n    if (!token) {\n      return\n    }\n    window.location.href = `${\n      ctx!.cfg.apiPathBase\n    }/profiling/group/download?token=${token}`\n  }, [id, ctx])\n\n  return (\n    <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>\n      <Head\n        title={t('instance_profiling.detail.head.title')}\n        back={\n          <Link to={`/instance_profiling`}>\n            <ArrowLeftOutlined /> {t('instance_profiling.detail.head.back')}\n          </Link>\n        }\n        titleExtra={\n          <Button\n            disabled={!isFinished(data)}\n            type=\"primary\"\n            onClick={handleDownloadGroup}\n          >\n            {t('instance_profiling.detail.download')}\n          </Button>\n        }\n      />\n      <div style={{ height: '100%', position: 'relative' }}>\n        <ScrollablePane>\n          {respData && (\n            <Card noMarginTop noMarginBottom>\n              <Descriptions>\n                <Descriptions.Item\n                  span={2}\n                  label={t('instance_profiling.detail.head.start_at')}\n                >\n                  <DateTime.Calendar\n                    unixTimestampMs={\n                      respData.task_group_status!.started_at! * 1000\n                    }\n                  />\n                </Descriptions.Item>\n              </Descriptions>\n            </Card>\n          )}\n          <CardTable\n            cardNoMarginTop\n            cardNoMarginBottom\n            disableSelectionZone\n            loading={isLoading}\n            columns={columns}\n            items={tableData}\n            errors={[error]}\n            groups={groupData}\n            hideLoadingWhenNotEmpty\n            extendLastColumn\n            compact\n          />\n        </ScrollablePane>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/pages/List.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.list {\n  &_container {\n    display: flex;\n    flex-direction: column;\n    height: 100vh;\n  }\n}\n\n.alert_container {\n  margin-left: @padding-page;\n  margin-right: @padding-page;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/pages/List.tsx",
    "content": "import { Badge, Button, Form, Select, Modal, Alert, Space, Tooltip } from 'antd'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport React, {\n  useMemo,\n  useState,\n  useCallback,\n  useRef,\n  useContext\n} from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useNavigate } from 'react-router-dom'\nimport { useMemoizedFn } from 'ahooks'\n\nimport { ProfilingStartRequest, ModelRequestTargetNode } from '@lib/client'\n\nimport {\n  Card,\n  CardTable,\n  InstanceSelect,\n  IInstanceSelectRefProps,\n  MultiSelect,\n  Toolbar\n} from '@lib/components'\nimport DateTime from '@lib/components/DateTime'\nimport openLink from '@lib/utils/openLink'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { combineTargetStats } from '../utils'\n\nimport styles from './List.module.less'\nimport { upperFirst } from 'lodash'\nimport { QuestionCircleOutlined } from '@ant-design/icons'\nimport { isDistro } from '@lib/utils/distro'\nimport { InstanceProfilingContext } from '../context'\nimport { useIsWriteable } from '@lib/utils'\n\nconst profilingDurationsSec = [10, 30, 60, 120]\nconst defaultProfilingDuration = 30\nconst profilingTypeOptions = ['CPU', 'Heap', 'Goroutine', 'Mutex']\n\nexport default function Page() {\n  const ctx = useContext(InstanceProfilingContext)\n\n  const isWriteable = useIsWriteable()\n\n  const {\n    data: historyTable,\n    isLoading: listLoading,\n    error: historyError\n  } = useClientRequest(ctx!.ds.getProfilingGroups)\n  const { data: ngMonitoringConfig } = useClientRequest(\n    ctx!.ds.continuousProfilingConfigGet\n  )\n\n  const conprofEnable =\n    ngMonitoringConfig?.continuous_profiling?.enable ?? false\n\n  const { t } = useTranslation()\n  const navigate = useNavigate()\n  const instanceSelect = useRef<IInstanceSelectRefProps>(null)\n  const [submitting, setSubmitting] = useState(false)\n\n  const handleFinish = useCallback(\n    async (fieldsValue) => {\n      if (!fieldsValue.instances || fieldsValue.instances.length === 0) {\n        Modal.error({\n          content: 'Some required fields are not filled'\n        })\n        return\n      }\n      if (!instanceSelect.current) {\n        Modal.error({\n          content: 'Internal error: Instance select is not ready'\n        })\n        return\n      }\n      const targets: ModelRequestTargetNode[] = instanceSelect\n        .current!.getInstanceByKeys(fieldsValue.instances)\n        .map((instance) => {\n          let port\n          switch (instance.instanceKind) {\n            case 'pd':\n            case 'tso':\n            case 'scheduling':\n              port = instance.port\n              break\n            case 'tidb':\n            case 'tikv':\n            case 'tiflash':\n            case 'ticdc':\n            case 'tiproxy':\n              port = instance.status_port\n              break\n          }\n          return {\n            kind: instance.instanceKind,\n            display_name: instance.key,\n            ip: instance.ip,\n            port\n          }\n        })\n        .filter((i) => i.port != null)\n\n      // Default to all types if non is selected\n      const types = !fieldsValue.type?.length\n        ? [...profilingTypeOptions]\n        : fieldsValue.type\n      const req: ProfilingStartRequest = {\n        targets,\n        duration_secs: fieldsValue.duration,\n        requsted_profiling_types: types.map((type) => type.toLowerCase())\n      }\n      try {\n        setSubmitting(true)\n        const res = await ctx!.ds.startProfiling(req)\n        navigate(`/instance_profiling/detail?id=${res.data.id}`)\n      } finally {\n        setSubmitting(false)\n      }\n    },\n    [navigate, ctx]\n  )\n\n  const handleRowClick = useMemoizedFn(\n    (rec, _idx, ev: React.MouseEvent<HTMLElement>) => {\n      openLink(`/instance_profiling/detail?id=${rec.id}`, ev, navigate)\n    }\n  )\n\n  const historyTableColumns = useMemo(\n    () => [\n      {\n        name: t('instance_profiling.list.table.columns.targets'),\n        key: 'targets',\n        minWidth: 300,\n        maxWidth: 480,\n        onRender: (rec) => {\n          return combineTargetStats(rec.target_stats)\n        }\n      },\n      {\n        name: t(\n          'instance_profiling.list.table.columns.requsted_profiling_types'\n        ),\n        key: 'types',\n        minWidth: 150,\n        maxWidth: 250,\n        onRender: (rec) => {\n          return (rec.requsted_profiling_types ?? ['cpu'])\n            .map((p) => (p === 'cpu' ? 'CPU' : upperFirst(p)))\n            .join(',')\n        }\n      },\n      {\n        name: t('instance_profiling.list.table.columns.status'),\n        key: 'status',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (rec) => {\n          if (rec.state === 0) {\n            // all failed\n            return (\n              <Badge\n                status=\"error\"\n                text={t('instance_profiling.list.table.status.failed')}\n              />\n            )\n          } else if (rec.state === 1) {\n            // running\n            return (\n              <Badge\n                status=\"processing\"\n                text={t('instance_profiling.list.table.status.running')}\n              />\n            )\n          } else if (rec.state === 2) {\n            // all success\n            return (\n              <Badge\n                status=\"success\"\n                text={t('instance_profiling.list.table.status.finished')}\n              />\n            )\n          } else {\n            // partial success\n            return (\n              <Badge\n                status=\"warning\"\n                text={t(\n                  'instance_profiling.list.table.status.partial_finished'\n                )}\n              />\n            )\n          }\n        }\n      },\n      {\n        name: t('instance_profiling.list.table.columns.start_at'),\n        key: 'started_at',\n        minWidth: 160,\n        maxWidth: 220,\n        onRender: (rec) => {\n          return <DateTime.Calendar unixTimestampMs={rec.started_at * 1000} />\n        }\n      },\n      {\n        name: t('instance_profiling.list.table.columns.duration'),\n        key: 'duration',\n        minWidth: 100,\n        maxWidth: 150,\n        fieldName: 'profile_duration_secs'\n      }\n    ],\n    [t]\n  )\n\n  return (\n    <div className={styles.list_container}>\n      <Card>\n        <Toolbar>\n          <Space>\n            <Form\n              onFinish={handleFinish}\n              layout=\"inline\"\n              initialValues={{\n                instances: [],\n                duration: defaultProfilingDuration,\n                type: []\n              }}\n            >\n              <Form.Item\n                name=\"instances\"\n                // label={t('instance_profiling.list.control_form.instances.label')}\n                rules={[{ required: true }]}\n              >\n                <InstanceSelect\n                  disabled={conprofEnable}\n                  enableTiFlash={true}\n                  ref={instanceSelect}\n                  style={{ width: 320 }}\n                  defaultSelectAll\n                  getTiDBTopology={ctx!.ds.getTiDBTopology}\n                  getStoreTopology={ctx!.ds.getStoreTopology}\n                  getPDTopology={ctx!.ds.getPDTopology}\n                  getTiCDCTopology={ctx!.ds.getTiCDCTopology}\n                  getTiProxyTopology={ctx!.ds.getTiProxyTopology}\n                  getTSOTopology={ctx!.ds.getTSOTopology}\n                  getSchedulingTopology={ctx!.ds.getSchedulingTopology}\n                />\n              </Form.Item>\n              <Form.Item name=\"type\">\n                <MultiSelect.Plain\n                  disabled={conprofEnable}\n                  placeholder={t(\n                    'instance_profiling.list.control_form.profiling_type.placeholder'\n                  )}\n                  columnTitle={t(\n                    'instance_profiling.list.control_form.profiling_type.columnTitle'\n                  )}\n                  style={{ width: 200 }}\n                  items={profilingTypeOptions}\n                ></MultiSelect.Plain>\n              </Form.Item>\n              <Form.Item\n                name=\"duration\"\n                label={t('instance_profiling.list.control_form.duration.label')}\n                rules={[{ required: true }]}\n              >\n                <Select style={{ width: 120 }} disabled={conprofEnable}>\n                  {profilingDurationsSec.map((sec) => (\n                    <Select.Option value={sec} key={sec}>\n                      {sec}s\n                    </Select.Option>\n                  ))}\n                </Select>\n              </Form.Item>\n              <Form.Item>\n                <Button\n                  type=\"primary\"\n                  htmlType=\"submit\"\n                  loading={submitting}\n                  disabled={conprofEnable || !isWriteable}\n                >\n                  {t('instance_profiling.list.control_form.submit')}\n                </Button>\n              </Form.Item>\n            </Form>\n          </Space>\n          <Space>\n            {!isDistro() && (\n              <Tooltip\n                mouseEnterDelay={0}\n                mouseLeaveDelay={0}\n                title={t('instance_profiling.settings.help')}\n                placement=\"bottom\"\n              >\n                <QuestionCircleOutlined\n                  onClick={() => {\n                    window.open(\n                      t('instance_profiling.settings.help_url'),\n                      '_blank'\n                    )\n                  }}\n                />\n              </Tooltip>\n            )}\n          </Space>\n        </Toolbar>\n      </Card>\n\n      {conprofEnable && (\n        <div className={styles.alert_container}>\n          <Alert\n            type=\"warning\"\n            message={\n              <>\n                {t('instance_profiling.list.disable_warning')}{' '}\n                {!isDistro() && (\n                  <a\n                    target=\"_blank\"\n                    href={t('instance_profiling.settings.help_url')}\n                    rel=\"noreferrer\"\n                  >\n                    {t('instance_profiling.settings.help')}\n                  </a>\n                )}\n              </>\n            }\n            showIcon\n          />\n        </div>\n      )}\n\n      <div style={{ height: '100%', position: 'relative' }}>\n        <ScrollablePane>\n          <CardTable\n            cardNoMarginTop\n            cardNoMarginBottom\n            loading={listLoading}\n            items={historyTable || []}\n            columns={historyTableColumns}\n            errors={[historyError]}\n            onRowClicked={handleRowClick}\n          />\n        </ScrollablePane>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/pages/index.ts",
    "content": "import List from './List'\nimport Detail from './Detail'\n\nexport { List, Detail }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/translations/en.yaml",
    "content": "profiling:\n  nav_title: Profiling Instances\ninstance_profiling:\n  nav_title: Manual Profiling\n  settings:\n    help: Help\n    help_url: https://docs.pingcap.com/tidb/dev/dashboard-profiling\n  list:\n    control_form:\n      title: Start Profiling Instances\n      profiling_type:\n        placeholder: All Profiling Types\n        columnTitle: Profiling Type\n      duration:\n        label: Duration\n      submit: Start Profiling\n    disable_warning: You cannot start a profile now since continuous profiling is enabled. You can see latest profiling results in the continuous profiling page.\n    table:\n      title: Profiling History\n      columns:\n        targets: Instances\n        requsted_profiling_types: Profiling Types\n        start_at: Start At\n        duration: Duration (sec)\n        status: Status\n      status:\n        running: Running\n        finished: Finished\n        failed: Failed\n        partial_finished: Partial Finished\n        unknown: Unknown\n      actions:\n        detail: Detail\n  detail:\n    head:\n      back: History\n      title: Profiling Detail\n      start_at: Start At\n    download: Download Profiling Result\n    table:\n      columns:\n        instance: Instance\n        kind: Component\n        content: Content\n        status: Status\n        view_as:\n          title: View As\n          error: Error Information\n          flamegraph: FlameGraph\n          graph: DotGraph\n          download: RawData\n          text: RawData\n      status:\n        finished: Finished\n        skipped: Not Applicable\n        skipped_tooltip: This profiling kind is currently not supported\n        running: Running\n        error: Error\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/translations/zh.yaml",
    "content": "profiling:\n  nav_title: 实例性能分析\ninstance_profiling:\n  nav_title: 手动分析\n  settings:\n    help: 帮助\n    help_url: https://docs.pingcap.com/zh/tidb/dev/dashboard-profiling\n  list:\n    control_form:\n      title: 开始性能分析\n      profiling_type:\n        placeholder: 所有性能数据\n        columnTitle: 性能数据类型\n      duration:\n        label: 分析时长\n      submit: 开始分析\n    disable_warning: 当前已启用持续性能分析，因此不能再发起一个新的性能分析。可在持续性能分析页面查看当前及过往的分析结果。\n    table:\n      title: 性能分析历史\n      columns:\n        targets: 实例\n        requsted_profiling_types: 分析类型\n        start_at: 开始时间\n        duration: 时长（秒）\n        status: 状态\n      status:\n        running: 分析中\n        finished: 完成\n        failed: 失败\n        partial_finished: 部分完成\n        unknown: 未知\n      actions:\n        detail: 详情\n  detail:\n    head:\n      back: 历史记录\n      title: 性能分析详情\n      start_at: 开始时间\n    download: 下载性能分析结果\n    table:\n      columns:\n        instance: 实例\n        kind: 组件\n        content: 内容\n        status: 状态\n        view_as:\n          title: 查看方式\n          error: 错误信息\n          flamegraph: 火焰图\n          graph: 关系图\n          download: 原始数据\n          text: 原始数据\n      status:\n        finished: 完成\n        skipped: 不适用\n        skipped_tooltip: 该分析当前暂不支持\n        running: 分析中\n        error: 错误\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/utils/combineTargetStats.ts",
    "content": "import { ModelRequestTargetStatistics } from '@lib/client'\nimport { instanceKindName } from '@lib/utils/instanceTable'\n\nconst targetNameMap = {\n  num_tidb_nodes: () => instanceKindName('tidb'),\n  num_tikv_nodes: () => instanceKindName('tikv'),\n  num_pd_nodes: () => instanceKindName('pd'),\n  num_tiflash_nodes: () => instanceKindName('tiflash'),\n  num_ticdc_nodes: () => instanceKindName('ticdc'),\n  num_tiproxy_nodes: () => instanceKindName('tiproxy'),\n  num_tso_nodes: () => instanceKindName('tso'),\n  num_scheduling_nodes: () => instanceKindName('scheduling')\n}\n\nexport const combineTargetStats = (stats: ModelRequestTargetStatistics) =>\n  Object.entries(stats)\n    .reduce((prev, [key, stat]) => {\n      if (targetNameMap[key]) {\n        const targetName = targetNameMap[key]()\n        targetName && prev.push(`${stat} ${targetName}`)\n      }\n      return prev\n    }, [] as string[])\n    .join(', ')\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/InstanceProfiling/utils/index.ts",
    "content": "export * from './combineTargetStats'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/components/KeyViz.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.PD-Cluster-Legend {\n  position: relative;\n\n  .unit {\n    font-size: 12px;\n    color: #333;\n    position: absolute;\n    right: 0;\n    top: 5px;\n  }\n}\n\n.PD-KeyVis {\n  height: 100vh;\n  display: flex;\n  flex-direction: column;\n\n  .ui.dropdown .menu {\n    z-index: 9999;\n  }\n\n  .PD-KeyVis-Toolbar {\n    .group-icons-btn {\n      border: 0;\n      margin-top: -7px;\n      .button {\n        border-left: 0 !important;\n      }\n\n      .icon {\n        color: #1b1c1d;\n      }\n    }\n\n    .ant-select .anticon {\n      margin-right: 5px;\n    }\n  }\n\n  svg,\n  button {\n    user-select: none;\n  }\n\n  g.tick text {\n    font-family: 'Poppins';\n    font-size: 12px;\n    text-anchor: start;\n  }\n\n  .tooltip {\n    padding: 10px;\n    color: #eee;\n    background-color: #333;\n    box-shadow: 5px 5px 10px rgba(black, 0.5);\n    border-radius: 3px;\n    min-width: 200px;\n\n    div.value {\n      display: flex;\n      align-items: center;\n\n      div.value {\n        margin: 0;\n        padding: 7px;\n        font-weight: bold;\n        border-radius: 3px;\n      }\n\n      div.unit {\n        color: #999;\n        font-size: 0.9rem;\n        margin-left: 10px;\n      }\n    }\n\n    button {\n      line-height: 1;\n      background-color: transparent;\n      border: transparent solid 1px;\n      border-radius: 3px;\n      outline: none;\n      padding: 3px;\n      text-align: left;\n      color: #fff;\n      transition: background-color ease-in 100ms;\n\n      &:hover {\n        border: #888 solid 1px;\n        cursor: pointer;\n      }\n\n      &:active {\n        background-color: #888;\n        transition: none;\n      }\n    }\n\n    .time {\n      color: #aaa;\n      line-height: 1.2;\n      margin-top: 10px;\n      font-size: 0.9rem;\n    }\n\n    .overviewLabel {\n      margin: 20px 0;\n      .subLabel {\n        padding: 1px 3px;\n        display: block;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        white-space: nowrap;\n        max-width: 250px;\n      }\n    }\n\n    .keyContainer {\n      margin-top: 10px;\n      padding: 0 3px;\n      .desc {\n        text-transform: uppercase;\n        font-weight: bold;\n        font-size: 0.9rem;\n        color: #ccc;\n      }\n      .label,\n      .key {\n        display: block;\n        color: #888;\n        font-size: 0.8rem;\n        overflow: hidden;\n        text-overflow: ellipsis;\n        white-space: nowrap;\n        max-width: 250px;\n      }\n    }\n  }\n\n  .heatmap {\n    flex-grow: 1;\n    margin: @padding-page;\n    margin-top: -32px;\n  }\n}\n\n#PD-KeyVis-Brightness-Overlay {\n  background-color: @select-dropdown-bg;\n  padding: @padding-md;\n  border-radius: @border-radius-base;\n  outline: none;\n  box-shadow: @box-shadow-base;\n  box-sizing: border-box;\n}\n\n.PD-KeyVis-Select-Option .anticon {\n  display: none;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/components/KeyViz.tsx",
    "content": "import React, { useContext, useState } from 'react'\nimport { Button, Drawer, Result, Space } from 'antd'\nimport { useTranslation } from 'react-i18next'\nimport { useGetSet, useMount } from 'react-use'\nimport { useBoolean, useMemoizedFn } from 'ahooks'\n\nimport { ConfigKeyVisualConfig } from '@lib/client'\nimport { Heatmap } from '../heatmap'\nimport { HeatmapData, HeatmapRange, DataTag } from '../heatmap/types'\nimport { fetchHeatmap } from '../utils'\nimport KeyVizSettingForm from './KeyVizSettingForm'\nimport KeyVizToolbar from './KeyVizToolbar'\n\nimport './KeyViz.less'\nimport { useChange } from '@lib/utils/useChange'\nimport { isDistro } from '@lib/utils/distro'\nimport { IKeyVizDataSource, KeyVizContext } from '../context'\n\n// const CACHE_EXPRIE_SECS = 10\n\nclass HeatmapCache {\n  // cache: CacheEntry[] = []\n  // latestFetchIdx = 0\n\n  async fetch(\n    fetcher: IKeyVizDataSource['keyvisualHeatmapsGet'],\n    range: number | HeatmapRange,\n    metricType: DataTag\n  ): Promise<HeatmapData | undefined> {\n    // return fetchDummyHeatmap()\n    let selection\n    if (typeof range === 'number') {\n      const endTime = Math.ceil(new Date().getTime() / 1000)\n      // this.cache = this.cache.filter((entry) => entry.expireTime > endTime)\n      // const entry = this.cache.find(\n      //   (entry) => entry.dateRange === range && entry.metricType === metricType\n      // )\n      // if (entry) {\n      //   return entry.data\n      // } else {\n      selection = {\n        starttime: endTime - range,\n        endtime: endTime\n      }\n      // }\n    } else {\n      selection = range\n    }\n\n    // this.latestFetchIdx += 1\n    // const fetchIdx = this.latestFetchIdx\n    const data = await fetchHeatmap(fetcher, selection, metricType)\n    // if (fetchIdx === this.latestFetchIdx) {\n    // if (typeof range === 'number') {\n    //   this.cache.push({\n    //     dateRange: range,\n    //     metricType: metricType,\n    //     expireTime: new Date().getTime() / 1000 + CACHE_EXPRIE_SECS,\n    //     data: data,\n    //   })\n    // }\n    return data\n    // }\n    // return undefined\n  }\n}\n\n// Todo: define heatmap state, with auto check control, date range select, reset to zoom\n// fetchData ,  changeType, add loading state, change zoom level to reset autofetch,\n\ntype ChartState = {\n  heatmapData: HeatmapData\n  metricType: DataTag\n}\n\n// TODO: using global state is not a good idea\nlet _chart\nlet cache = new HeatmapCache()\n\nconst KeyViz = () => {\n  const ctx = useContext(KeyVizContext)\n\n  const [chartState, setChartState] = useState<ChartState>()\n  const [getSelection, setSelection] = useGetSet<HeatmapRange | null>(null)\n  const [isLoading, setLoading] = useState(true)\n  const [autoRefreshSeconds, setAutoRefreshSeconds] = useState(0)\n  const [getOnBrush, setOnBrush] = useGetSet(false)\n  const [getDateRange, setDateRange] = useGetSet(3600 * 6)\n  const [getBrightLevel, setBrightLevel] = useGetSet(1)\n  const [getMetricType, setMetricType] = useGetSet<DataTag>('written_bytes')\n  const [config, setConfig] = useState<ConfigKeyVisualConfig | null>(null)\n  const [\n    shouldShowSettings,\n    { setTrue: openSettings, setFalse: closeSettings }\n  ] = useBoolean(false)\n  const { t } = useTranslation()\n\n  const enabled = config?.auto_collection_disabled !== true\n\n  const updateServiceStatus = useMemoizedFn(async function () {\n    if (ctx?.cfg?.showSetting === false) {\n      return\n    }\n    try {\n      setLoading(true)\n      const resp = await ctx!.ds.keyvisualConfigGet()\n      const config = resp.data\n      setConfig(config)\n    } finally {\n      setLoading(false)\n    }\n  })\n\n  useMount(updateServiceStatus)\n\n  const updateHeatmap = useMemoizedFn(async () => {\n    try {\n      setLoading(true)\n      setOnBrush(false)\n      const metricType = getMetricType()\n      const data = await cache.fetch(\n        ctx!.ds.keyvisualHeatmapsGet,\n        getSelection() || getDateRange(),\n        metricType\n      )\n      setChartState({ heatmapData: data!, metricType })\n    } finally {\n      setLoading(false)\n    }\n  })\n\n  const onChangeBrightLevel = useMemoizedFn((val) => {\n    if (!_chart) return\n    setBrightLevel(val)\n    _chart.brightness(val)\n  })\n\n  const onChangeDateRange = useMemoizedFn((v: number) => {\n    setDateRange(v)\n    setSelection(null)\n  })\n\n  const onResetZoom = useMemoizedFn(() => {\n    setSelection(null)\n  })\n\n  const onToggleBrush = useMemoizedFn(() => {\n    const newOnBrush = !getOnBrush()\n    setAutoRefreshSeconds(0)\n    setOnBrush(newOnBrush)\n    _chart.brush(newOnBrush)\n  })\n\n  const onBrush = useMemoizedFn((selection: HeatmapRange) => {\n    setOnBrush(false)\n    setAutoRefreshSeconds(0)\n    setSelection(selection)\n  })\n\n  const onZoom = useMemoizedFn(() => {\n    setAutoRefreshSeconds(0)\n  })\n\n  const onChartInit = useMemoizedFn((chart) => {\n    _chart = chart\n    setLoading(false)\n    _chart.brightness(getBrightLevel())\n  })\n\n  useChange(() => {\n    if (autoRefreshSeconds > 0) {\n      onResetZoom()\n      setOnBrush(false)\n    }\n  }, [autoRefreshSeconds])\n\n  useChange(() => {\n    if (enabled) {\n      updateHeatmap()\n    }\n  }, [config, getSelection(), getDateRange(), getMetricType()])\n\n  const disabledPage = isLoading ? null : (\n    <Result\n      title={t('keyviz.settings.disabled_result.title')}\n      subTitle={t('keyviz.settings.disabled_result.sub_title')}\n      extra={\n        <Space>\n          <Button type=\"primary\" onClick={openSettings}>\n            {t('keyviz.settings.open_setting')}\n          </Button>\n          {!isDistro() && (\n            <Button\n              onClick={() => {\n                window.open(t('keyviz.settings.help_url'), '_blank')\n              }}\n            >\n              {t('keyviz.settings.help')}\n            </Button>\n          )}\n        </Space>\n      }\n    />\n  )\n\n  const mainPart = !enabled\n    ? disabledPage\n    : chartState && (\n        <Heatmap\n          data={chartState.heatmapData}\n          dataTag={chartState.metricType}\n          onBrush={onBrush}\n          onChartInit={onChartInit}\n          onZoom={onZoom}\n        />\n      )\n\n  return (\n    <div className=\"PD-KeyVis\">\n      <KeyVizToolbar\n        enabled={enabled}\n        isLoading={isLoading}\n        dateRange={getDateRange()}\n        metricType={getMetricType()}\n        brightLevel={getBrightLevel()}\n        onToggleBrush={onToggleBrush}\n        onResetZoom={onResetZoom}\n        autoRefreshSeconds={autoRefreshSeconds}\n        isOnBrush={getOnBrush()}\n        showHelp={ctx!.cfg?.showHelp ?? true}\n        showSetting={ctx!.cfg?.showSetting ?? true}\n        onChangeBrightLevel={onChangeBrightLevel}\n        onChangeMetric={setMetricType}\n        onChangeDateRange={onChangeDateRange}\n        onChangeAutoRefresh={setAutoRefreshSeconds}\n        onRefresh={updateHeatmap}\n        onShowSettings={openSettings}\n      />\n\n      {mainPart}\n\n      <Drawer\n        title={t('keyviz.settings.title')}\n        width={300}\n        closable={true}\n        visible={shouldShowSettings}\n        onClose={closeSettings}\n        destroyOnClose={true}\n      >\n        <KeyVizSettingForm\n          onClose={closeSettings}\n          onConfigUpdated={updateServiceStatus}\n        />\n      </Drawer>\n    </div>\n  )\n}\n\nexport default KeyViz\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/components/KeyVizSettingForm.tsx",
    "content": "import React, { useState, useMemo, useCallback, useContext } from 'react'\nimport {\n  Form,\n  Skeleton,\n  Switch,\n  Space,\n  Button,\n  Modal,\n  Radio,\n  Input\n} from 'antd'\nimport { ExclamationCircleOutlined } from '@ant-design/icons'\nimport { useTranslation } from 'react-i18next'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { DrawerFooter, ErrorBar } from '@lib/components'\nimport { useIsWriteable } from '@lib/utils/store'\nimport { KeyVizContext } from '../context'\n\nconst policyConfigurable = process.env.NODE_ENV === 'development'\n\ninterface Props {\n  onClose: () => void\n  onConfigUpdated: () => any\n}\n\ntype SeparatorStatus = {\n  validateStatus: 'warning' | 'success'\n  hasFeedback: boolean\n  help: string\n}\n\nconst negateSwitchProps = {\n  getValueProps: (value) => ({ checked: value !== true }),\n  getValueFromEvent: (checked) => !checked\n}\n\nfunction getSeparatorValidator(t) {\n  const separatorEmptyStatus: SeparatorStatus = {\n    validateStatus: 'warning',\n    hasFeedback: true,\n    help: t('keyviz.settings.separator_empty_warning')\n  }\n  const separatorNotEmptyStatus: SeparatorStatus = {\n    validateStatus: 'success',\n    hasFeedback: true,\n    help: ''\n  }\n  return (value: string | undefined) =>\n    value === undefined || value === ''\n      ? separatorEmptyStatus\n      : separatorNotEmptyStatus\n}\n\nfunction getPolicyOptions(t) {\n  return ['db', 'kv'].map((policy) => {\n    let label = t(`keyviz.settings.policy_${policy}`)\n    return (\n      <Radio.Button key={policy} value={policy}>\n        {label}\n      </Radio.Button>\n    )\n  })\n}\n\nfunction KeyVizSettingForm({ onClose, onConfigUpdated }: Props) {\n  const ctx = useContext(KeyVizContext)\n\n  const [submitting, setSubmitting] = useState(false)\n  const { t } = useTranslation()\n  const isWriteable = useIsWriteable()\n\n  const {\n    data: config,\n    isLoading: loading,\n    error\n  } = useClientRequest(ctx!.ds.keyvisualConfigGet)\n\n  const onUpdateServiceStatus = async (values) => {\n    try {\n      setSubmitting(true)\n      await ctx!.ds.keyvisualConfigPut(values)\n      onClose()\n      onConfigUpdated()\n    } finally {\n      setSubmitting(false)\n    }\n  }\n\n  const onSubmit = (values) => {\n    if (\n      config?.auto_collection_disabled !== true &&\n      values.auto_collection_disabled === true\n    ) {\n      Modal.confirm({\n        title: t('keyviz.settings.close_keyviz'),\n        icon: <ExclamationCircleOutlined />,\n        content: t('keyviz.settings.close_keyviz_warning'),\n        okText: t('keyviz.settings.actions.close'),\n        cancelText: t('keyviz.settings.actions.cancel'),\n        okButtonProps: { danger: true },\n        onOk: () => onUpdateServiceStatus(values)\n      })\n    } else {\n      onUpdateServiceStatus(values)\n    }\n  }\n\n  const [form] = Form.useForm()\n  const onValuesChange = useCallback(\n    (changedValues, values) => {\n      if (changedValues?.auto_collection_disabled !== true && !values?.policy) {\n        form.setFieldsValue({ policy: 'db' })\n      }\n      if (\n        config?.policy !== 'kv' &&\n        changedValues?.policy === 'kv' &&\n        !values?.policy_kv_separator\n      ) {\n        form.setFieldsValue({ policy_kv_separator: '/' })\n      }\n    },\n    [form, config]\n  )\n  const policyOptions = useMemo(() => getPolicyOptions(t), [t])\n  const validateSeparator = useMemo(() => getSeparatorValidator(t), [t])\n\n  return (\n    <>\n      {error && <ErrorBar errors={[error]} />}\n      {loading && <Skeleton active={true} paragraph={{ rows: 5 }} />}\n      {!loading && config && (\n        <Form\n          layout=\"vertical\"\n          form={form}\n          initialValues={config}\n          onFinish={onSubmit}\n          onValuesChange={onValuesChange}\n        >\n          <Form.Item noStyle shouldUpdate>\n            {({ getFieldValue }) => {\n              const enabled = getFieldValue('auto_collection_disabled') !== true\n              const policy = getFieldValue('policy')\n              const separator = getFieldValue('policy_kv_separator')\n              return (\n                <>\n                  <Form.Item\n                    name=\"auto_collection_disabled\"\n                    label={t('keyviz.settings.switch')}\n                    extra={t('keyviz.settings.switch_tooltip')}\n                    {...negateSwitchProps}\n                  >\n                    <Switch disabled={!isWriteable} />\n                  </Form.Item>\n                  <Form.Item\n                    name=\"policy\"\n                    label={t('keyviz.settings.policy')}\n                    style={{\n                      display:\n                        !policyConfigurable || !enabled ? 'none' : undefined\n                    }}\n                  >\n                    <Radio.Group disabled={!isWriteable}>\n                      {policyOptions}\n                    </Radio.Group>\n                  </Form.Item>\n                  <Form.Item\n                    name=\"policy_kv_separator\"\n                    label={t('keyviz.settings.separator')}\n                    style={{\n                      display:\n                        !policyConfigurable || !enabled || policy !== 'kv'\n                          ? 'none'\n                          : undefined\n                    }}\n                    {...validateSeparator(separator)}\n                  >\n                    <Input\n                      placeholder={t('keyviz.settings.separator_placeholder')}\n                      disabled={!isWriteable}\n                    />\n                  </Form.Item>\n                </>\n              )\n            }}\n          </Form.Item>\n          <DrawerFooter>\n            <Space>\n              <Button\n                type=\"primary\"\n                htmlType=\"submit\"\n                loading={submitting}\n                disabled={!isWriteable}\n              >\n                {t('keyviz.settings.actions.save')}\n              </Button>\n              <Button onClick={onClose}>\n                {t('keyviz.settings.actions.cancel')}\n              </Button>\n            </Space>\n          </DrawerFooter>\n        </Form>\n      )}\n    </>\n  )\n}\n\nexport default KeyVizSettingForm\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/components/KeyVizToolbar.tsx",
    "content": "import React, { Component } from 'react'\nimport {\n  AreaChartOutlined,\n  ArrowsAltOutlined,\n  BulbOutlined,\n  ClockCircleOutlined,\n  DownOutlined,\n  LoadingOutlined,\n  QuestionCircleOutlined,\n  SettingOutlined\n} from '@ant-design/icons'\nimport { Slider, Spin, Select, Dropdown, Button, Tooltip, Space } from 'antd'\nimport { withTranslation, WithTranslation } from 'react-i18next'\nimport Flexbox from '@g07cha/flexbox-react'\nimport { AutoRefreshButton, Card, Toolbar } from '@lib/components'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\nimport { isDistro } from '@lib/utils/distro'\nimport { telemetry as keyVizTelemetry } from '../utils/telemetry'\n\nexport interface IKeyVizToolbarProps {\n  enabled: boolean\n  isLoading: boolean\n  autoRefreshSeconds: number\n  isOnBrush: boolean\n  metricType: string\n  brightLevel: number\n  dateRange: number\n  showHelp: boolean\n  showSetting: boolean\n  onResetZoom: () => void\n  onToggleBrush: () => void\n  onChangeMetric: (string) => void\n  onChangeDateRange: (number) => void\n  onChangeBrightLevel: (number) => void\n  onChangeAutoRefresh: (number) => void\n  onRefresh: () => void\n  onShowSettings: () => any\n}\n\nclass KeyVizToolbar extends Component<IKeyVizToolbarProps & WithTranslation> {\n  state = {\n    exp: 0\n  }\n\n  handleRefreshClick = () => {\n    this.props.onRefresh()\n    keyVizTelemetry.clickManualRefresh()\n  }\n\n  handleAutoRefreshMenuClick = (key) => {\n    this.props.onChangeAutoRefresh(key)\n    keyVizTelemetry.clickAutoRefresh()\n  }\n\n  handleDateRange = (value) => {\n    this.props.onChangeDateRange(value)\n    keyVizTelemetry.changeTimeDuration(value)\n  }\n\n  handleMetricChange = (value) => {\n    this.props.onChangeMetric(value)\n    keyVizTelemetry.changeMetric(value)\n  }\n\n  handleBrightLevel = (exp: number) => {\n    this.props.onChangeBrightLevel(Math.pow(2, exp))\n    this.setState({ exp })\n    keyVizTelemetry.changeBright(exp)\n  }\n\n  handleBrightnessDropdown = () => {\n    setTimeout(() => {\n      this.handleBrightLevel(this.state.exp)\n    }, 0)\n  }\n\n  handleToggleBrush = () => {\n    this.props.onToggleBrush()\n    keyVizTelemetry.toggleBrush()\n  }\n\n  handleShowSetting = () => {\n    this.props.onShowSettings()\n    keyVizTelemetry.openSetting()\n  }\n\n  handleResetZoom = () => {\n    this.props.onResetZoom()\n    keyVizTelemetry.resetZoom()\n  }\n\n  render() {\n    const {\n      t,\n      enabled,\n      isLoading,\n      dateRange,\n      isOnBrush,\n      metricType,\n      autoRefreshSeconds,\n      showHelp,\n      showSetting\n    } = this.props\n\n    // in hours\n    const dateRangeOptions = [1, 6, 12, 24, 24 * 7]\n\n    const MetricOptions = [\n      { text: t('keyviz.toolbar.view_type.read_bytes'), value: 'read_bytes' },\n      {\n        text: t('keyviz.toolbar.view_type.write_bytes'),\n        value: 'written_bytes'\n      },\n      { text: t('keyviz.toolbar.view_type.read_keys'), value: 'read_keys' },\n      { text: t('keyviz.toolbar.view_type.write_keys'), value: 'written_keys' },\n      { text: t('keyviz.toolbar.view_type.all'), value: 'integration' }\n    ]\n\n    return (\n      <Card>\n        <Toolbar className=\"PD-KeyVis-Toolbar\">\n          <Space>\n            <Dropdown\n              disabled={!enabled}\n              overlay={\n                <div id=\"PD-KeyVis-Brightness-Overlay\">\n                  <div\n                    onClick={(e) => {\n                      e.stopPropagation()\n                    }}\n                  >\n                    <Flexbox flexDirection=\"column\">\n                      <div className=\"PD-Cluster-Legend\" />\n                      <Slider\n                        defaultValue={0}\n                        min={-6}\n                        max={6}\n                        step={0.1}\n                        onChange={(value) =>\n                          this.handleBrightLevel(value as number)\n                        }\n                      />\n                    </Flexbox>\n                  </div>\n                </div>\n              }\n              trigger={['click']}\n              onVisibleChange={this.handleBrightnessDropdown}\n            >\n              <Button icon={<BulbOutlined />}>\n                {t('keyviz.toolbar.brightness')}\n                <DownOutlined />\n              </Button>\n            </Dropdown>\n\n            <Button.Group>\n              <Button\n                disabled={!enabled}\n                onClick={this.handleToggleBrush}\n                icon={<ArrowsAltOutlined />}\n                type={isOnBrush ? 'primary' : 'default'}\n              >\n                {t('keyviz.toolbar.zoom.select')}\n              </Button>\n              <Button disabled={!enabled} onClick={this.handleResetZoom}>\n                {t('keyviz.toolbar.zoom.reset')}\n              </Button>\n            </Button.Group>\n\n            <Select\n              disabled={!enabled}\n              onChange={this.handleDateRange}\n              value={dateRange}\n              style={{ width: 150 }}\n            >\n              {dateRangeOptions.map((hour) => (\n                <Select.Option\n                  key={hour}\n                  value={hour * 60 * 60}\n                  className=\"PD-KeyVis-Select-Option\"\n                >\n                  <ClockCircleOutlined /> {getValueFormat('h')(hour, 0)}\n                </Select.Option>\n              ))}\n            </Select>\n\n            <Select\n              disabled={!enabled}\n              onChange={this.handleMetricChange}\n              value={metricType}\n              style={{ width: 160 }}\n            >\n              {MetricOptions.map((option) => (\n                <Select.Option\n                  key={option.text}\n                  value={option.value}\n                  className=\"PD-KeyVis-Select-Option\"\n                >\n                  <AreaChartOutlined /> {option.text}\n                </Select.Option>\n              ))}\n            </Select>\n\n            <AutoRefreshButton\n              value={autoRefreshSeconds}\n              onChange={this.handleAutoRefreshMenuClick}\n              onRefresh={this.handleRefreshClick}\n              disabled={!enabled || isLoading}\n            />\n\n            {this.props.isLoading && (\n              <Spin\n                indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}\n              />\n            )}\n          </Space>\n\n          <Space>\n            {showSetting && (\n              <Tooltip\n                mouseEnterDelay={0}\n                mouseLeaveDelay={0}\n                title={t('keyviz.settings.title')}\n              >\n                <SettingOutlined onClick={this.handleShowSetting} />\n              </Tooltip>\n            )}\n            {!isDistro() && showHelp && (\n              <Tooltip\n                mouseEnterDelay={0}\n                mouseLeaveDelay={0}\n                title={t('keyviz.settings.help')}\n                placement=\"bottom\"\n              >\n                <QuestionCircleOutlined\n                  onClick={() => {\n                    window.open(t('keyviz.settings.help_url'), '_blank')\n                    keyVizTelemetry.openHelp()\n                  }}\n                />\n              </Tooltip>\n            )}\n          </Space>\n        </Toolbar>\n      </Card>\n    )\n  }\n}\n\nexport default withTranslation()(KeyVizToolbar)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport { ConfigKeyVisualConfig, MatrixMatrix } from '@lib/client'\n\nimport { ReqConfig } from '@lib/types'\n\nexport interface IKeyVizDataSource {\n  keyvisualConfigGet(options?: ReqConfig): AxiosPromise<ConfigKeyVisualConfig>\n\n  keyvisualConfigPut(\n    request: ConfigKeyVisualConfig,\n    options?: ReqConfig\n  ): AxiosPromise<ConfigKeyVisualConfig>\n\n  keyvisualHeatmapsGet(\n    startkey?: string,\n    endkey?: string,\n    starttime?: number,\n    endtime?: number,\n    type?:\n      | 'written_bytes'\n      | 'read_bytes'\n      | 'written_keys'\n      | 'read_keys'\n      | 'integration',\n    options?: ReqConfig\n  ): AxiosPromise<MatrixMatrix>\n}\n\nexport interface IKeyVizConfig {\n  showHelp?: boolean\n  showSetting?: boolean\n}\n\nexport interface IKeyVizContext {\n  ds: IKeyVizDataSource\n  cfg?: IKeyVizConfig\n}\n\nexport const KeyVizContext = createContext<IKeyVizContext | null>(null)\n\nexport const KeyVizProvider = KeyVizContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/heatmap/axis/histogram.ts",
    "content": "import * as d3 from 'd3'\nimport { Section, scaleSections } from '.'\n\nconst fill = '#333'\nconst fillFocus = '#ccc'\nconst stroke = '#fff'\n\nexport function histogram(data: number[][]) {\n  let xRange: [number, number] = [0, 0]\n  let yRange: [number, number] = [0, 0]\n\n  histogram.xRange = function (val: [number, number]) {\n    xRange = val\n    return this\n  }\n\n  histogram.yRange = function (val: [number, number]) {\n    yRange = val\n    return this\n  }\n\n  function histogram(\n    xCtx: CanvasRenderingContext2D,\n    yCtx: CanvasRenderingContext2D,\n    xFocusDomain: [number, number] | null,\n    yFocusDomain: [number, number] | null,\n    xScale,\n    yScale\n  ) {\n    const xHeight = xCtx.canvas.height\n    const yWidth = yCtx.canvas.width\n\n    const xLen = data.length\n    const yLen = data[0].length\n\n    const xStartIdx = Math.max(0, Math.floor(xScale.invert(xRange[0])))\n    const xEndIdx = Math.min(xLen - 1, Math.ceil(xScale.invert(xRange[1])))\n    const yStartIdx = Math.max(0, Math.floor(yScale.invert(yRange[0])))\n    const yEndIdx = Math.min(yLen - 1, Math.ceil(yScale.invert(yRange[1])))\n\n    const xSum: Section<number>[] = []\n    const ySum: Section<number>[] = []\n\n    for (let x = xStartIdx; x < xEndIdx; x++) {\n      let sumVal = 0\n      for (let y = yStartIdx; y < yEndIdx; y++) {\n        sumVal += data[x][y]\n      }\n      xSum.push({ val: sumVal, startIdx: x, endIdx: x + 1 })\n    }\n    for (let y = yStartIdx; y < yEndIdx; y++) {\n      let sumVal = 0\n      for (let x = xStartIdx; x < xEndIdx; x++) {\n        sumVal += data[x][y]\n      }\n      ySum.push({ val: sumVal, startIdx: y, endIdx: y + 1 })\n    }\n\n    const xBins = scaleSections(\n      xSum,\n      xFocusDomain,\n      xRange,\n      xScale,\n      (origin, val) => origin + val\n    )\n    const yBins = scaleSections(\n      ySum,\n      yFocusDomain,\n      yRange,\n      yScale,\n      (origin, val) => origin + val\n    )\n\n    const xBinsMax = d3.max(xBins, (section) => section.val)!\n    const yBinsMax = d3.max(yBins, (section) => section.val)!\n\n    xCtx.clearRect(xRange[0], 0, xRange[1], xHeight)\n    xCtx.strokeStyle = stroke\n    xCtx.lineWidth = 1\n    for (const bin of xBins) {\n      const width = bin.endPos - bin.startPos\n      const height = (bin.val / xBinsMax) * xHeight\n      if (height < 1) continue\n      xCtx.fillStyle = bin.focus ? fillFocus : fill\n      xCtx.beginPath()\n      xCtx.rect(bin.startPos, xHeight - height, width, height)\n      xCtx.fill()\n      xCtx.stroke()\n      xCtx.closePath()\n    }\n\n    yCtx.clearRect(0, yRange[0], yWidth, yRange[1])\n    yCtx.strokeStyle = stroke\n    yCtx.lineWidth = 1\n    for (const bin of yBins) {\n      const width = (bin.val / yBinsMax) * yWidth\n      const height = bin.endPos - bin.startPos\n      if (width < 1) continue\n      yCtx.fillStyle = bin.focus ? fillFocus : fill\n      yCtx.beginPath()\n      yCtx.rect(yWidth - width, bin.startPos, width, height)\n      yCtx.fill()\n      yCtx.stroke()\n      yCtx.closePath()\n    }\n  }\n\n  return histogram\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/heatmap/axis/index.ts",
    "content": "import _ from 'lodash'\n\nexport type Section<T> = {\n  val: T\n  startIdx: number\n  endIdx: number\n}\n\nexport type DisplaySection<T> = {\n  val: T\n  startIdx: number\n  endIdx: number\n  startPos: number\n  endPos: number\n  focus: boolean\n}\n\nconst mergeWidth = 3\n\nexport function scaleSections<T>(\n  sections: Section<T>[],\n  focusDomain: [number, number] | null,\n  range: [number, number],\n  scale: (idx: number) => number,\n  merge: (origin: T, val: T) => T\n): DisplaySection<T>[] {\n  let result: DisplaySection<T>[] = []\n  let mergedSmallSection: DisplaySection<T> | null = null\n  let oneSectionRendered = false\n\n  for (const section of sections) {\n    const canvasStart = range[0]\n    const canvasEnd = range[1]\n    const startPos = scale(section.startIdx)\n    const endPos = scale(section.endIdx)\n    const commonStart = Math.max(startPos, canvasStart)\n    const commonEnd = Math.min(endPos, canvasEnd)\n    const focus = focusDomain\n      ? Math.min(scale(focusDomain[1]), endPos) -\n          Math.max(scale(focusDomain[0]), startPos) >\n        0\n      : false\n\n    if (mergedSmallSection) {\n      if (\n        mergedSmallSection.endPos - mergedSmallSection.startPos >= mergeWidth ||\n        commonStart - mergedSmallSection.startPos > mergeWidth ||\n        (!oneSectionRendered && section.startIdx % 2 === 0)\n      ) {\n        result.push(mergedSmallSection)\n        oneSectionRendered = true\n        mergedSmallSection = null\n      }\n    }\n\n    if (commonEnd - commonStart > 0) {\n      if (commonEnd - commonStart > mergeWidth) {\n        result.push(\n          _.assign(\n            { startPos: commonStart, endPos: commonEnd, focus: focus },\n            section\n          )\n        )\n        oneSectionRendered = true\n        mergedSmallSection = null\n      } else {\n        if (mergedSmallSection === null) {\n          mergedSmallSection = _.assign(\n            { startPos: commonStart, endPos: commonEnd, focus: focus },\n            section\n          )\n        } else {\n          mergedSmallSection.val = merge(mergedSmallSection.val, section.val)\n          mergedSmallSection.endPos = commonEnd\n          mergedSmallSection.focus = mergedSmallSection.focus || focus\n        }\n      }\n    }\n  }\n\n  return result\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/heatmap/axis/label-axis.ts",
    "content": "import _ from 'lodash'\nimport { Section, DisplaySection, scaleSections } from '.'\nimport { KeyAxisEntry } from '../types'\nimport { truncateString } from '../utils'\n\nconst labelAxisMargin = 4\nconst labelAxisWidth = 28\nconst labelTextPadding = 4\nconst minTextHeight = 17\nconst fill = '#333'\nconst fillFocus = '#ccc'\nconst stroke = '#fff'\nconst textFill = 'white'\nconst textFillFocus = '#333'\nconst font = '500 12px Poppins'\nconst focusFont = '700 12px Poppins'\n\ntype Label = Section<string>\ntype DisplayLabel = DisplaySection<string>\n\nexport function labelAxisGroup(keyAxis: KeyAxisEntry[]) {\n  // Remove the endkey of the last region, so that the row where the region is located is aligned with the startkey.\n  if (keyAxis.length > 1) {\n    keyAxis = keyAxis.slice(1)\n  }\n\n  let range: [number, number] = [0, 0]\n  const groups = aggrKeyAxisLabel(keyAxis)\n\n  labelAxisGroup.range = function (val) {\n    range = val\n    return this\n  }\n\n  function labelAxisGroup(\n    ctx: CanvasRenderingContext2D,\n    focusDomain: [number, number] | null,\n    scale: (idx: number) => number\n  ) {\n    const width = ctx.canvas.width\n    const height = ctx.canvas.height\n\n    let scaledGroups = groups.map((group) =>\n      scaleSections(group, focusDomain, range, scale, () => '')\n    )\n\n    ctx.clearRect(0, 0, width, height)\n    ctx.strokeStyle = stroke\n    ctx.lineWidth = 1\n    ctx.textBaseline = 'middle'\n    for (const [groupIdx, group] of scaledGroups.entries()) {\n      const marginLeft = groupIdx * (labelAxisWidth + labelAxisMargin)\n\n      for (const label of group) {\n        const width = labelAxisWidth\n        const height = label.endPos - label.startPos\n\n        ctx.fillStyle = label.focus ? fillFocus : fill\n        ctx.beginPath()\n        ctx.rect(marginLeft, label.startPos, width, height)\n        ctx.fill()\n        ctx.stroke()\n        ctx.closePath()\n\n        if (shouldShowLabelText(label)) {\n          ctx.font = label.focus ? focusFont : font\n          ctx.fillStyle = label.focus ? textFillFocus : textFill\n          ctx.translate(\n            marginLeft + labelAxisWidth / 2 + 2,\n            label.endPos - labelTextPadding\n          )\n          ctx.rotate(-Math.PI / 2)\n          ctx.fillText(fitLabelText(label), 0, 0)\n          ctx.resetTransform()\n          ctx.scale(window.devicePixelRatio, window.devicePixelRatio)\n        }\n      }\n    }\n  }\n\n  return labelAxisGroup\n}\n\nfunction shouldShowLabelText(label: DisplayLabel): boolean {\n  return (\n    label.endPos - label.startPos >= minTextHeight && label.val?.length !== 0\n  )\n}\n\nfunction fitLabelText(label: DisplayLabel): string {\n  const rectWidth = label.endPos - label.startPos\n  const textLen = Math.floor(rectWidth / 7.5)\n  return truncateString(label.val, textLen)\n}\n\nfunction aggrKeyAxisLabel(keyAxis: KeyAxisEntry[]): Label[][] {\n  let result: Label[][] = _.times(4, () => [])\n  let notEqual: boolean[] = _.times(keyAxis.length, () => false)\n\n  for (let groupIdx = 0; groupIdx < result.length; groupIdx++) {\n    let lastLabel: string | null = null\n    let startKeyIdx: number | null = null\n\n    for (let keyIdx = 0; keyIdx < keyAxis.length; keyIdx++) {\n      const label = keyAxis[keyIdx].labels[groupIdx]\n      // When the prefixes are equal and this column is null, it is considered equal to the previous row of labels.\n      notEqual[keyIdx] =\n        notEqual[keyIdx] || (label != null && label !== lastLabel)\n\n      if (notEqual[keyIdx]) {\n        if (startKeyIdx != null && lastLabel != null) {\n          result[groupIdx].push({\n            val: lastLabel,\n            startIdx: startKeyIdx,\n            endIdx: keyIdx\n          })\n          startKeyIdx = null\n        }\n\n        if (label != null) {\n          startKeyIdx = keyIdx\n        }\n\n        lastLabel = label\n      }\n    }\n\n    if (startKeyIdx != null && lastLabel != null) {\n      result[groupIdx].push({\n        val: lastLabel,\n        startIdx: startKeyIdx,\n        endIdx: keyAxis.length\n      })\n    }\n  }\n\n  return result\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/heatmap/buffer.ts",
    "content": "import * as d3 from 'd3'\n\nexport function createBuffer(\n  normalizedValues: Uint8Array,\n  width: number,\n  height: number,\n  rasterizedColors: Uint32Array\n): HTMLCanvasElement {\n  const canvas = d3\n    .create('canvas')\n    .attr('width', width)\n    .attr('height', height)\n    .node() as HTMLCanvasElement\n\n  console.time('createBuffer')\n\n  const ctx = canvas.getContext('2d') as CanvasRenderingContext2D\n  const imageDataBuffer = new ArrayBuffer(width * height * 4)\n  const imageDataPixels = new Uint32Array(imageDataBuffer)\n\n  const len = normalizedValues.length\n  for (let i = 0; i < len; i++) {\n    imageDataPixels[i] = rasterizedColors[normalizedValues[i]]\n  }\n\n  const imageData = ctx.createImageData(width, height)\n  imageData.data.set(new Uint8ClampedArray(imageDataBuffer))\n  ctx.putImageData(imageData, 0, 0)\n\n  console.timeEnd('createBuffer')\n\n  return canvas\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/heatmap/chart.ts",
    "content": "import * as d3 from 'd3'\nimport _ from 'lodash'\nimport dayjs from 'dayjs'\n\nimport { HeatmapRange, HeatmapData, DataTag } from './types'\nimport { createBuffer } from './buffer'\nimport { labelAxisGroup } from './axis/label-axis'\nimport { histogram } from './axis/histogram'\nimport { getColorScheme, ColorScheme, rasterizeLevel } from './color'\nimport { tagUnit, withUnit, clickToCopyBehavior } from './utils'\nimport legend from './legend'\nimport { tz } from '@lib/utils'\n\nconst margin = {\n  top: 25,\n  right: 40,\n  bottom: 70,\n  left: 100\n}\n\nconst tooltipOffset = {\n  horizontal: 20,\n  vertical: 20\n}\n\ntype TooltipStatus = {\n  pinned: boolean\n  hidden: boolean\n  x: number\n  y: number\n}\n\ntype FocusStatus = {\n  xDomain: [number, number]\n  yDomain: [number, number]\n}\n\nconst defaultTooltipStatus = { pinned: false, hidden: true, x: 0, y: 0 }\nconst heatmapCanvasPixelRatio = Math.max(2, window.devicePixelRatio)\n\nfunction normalizeData(d: number[][], maxValue: number) {\n  const height = d.length > 0 ? d[0].length : 0\n  const width = d.length\n  const len = width * height\n  const normalized = new Uint8Array(len)\n  const logMaxValue = Math.log(maxValue)\n  for (let cIdx = 0; cIdx < width; cIdx++) {\n    for (let rIdx = 0; rIdx < height; rIdx++) {\n      const addr = rIdx * width + cIdx\n      normalized[addr] =\n        (Math.log(d[cIdx][rIdx]) / logMaxValue) * rasterizeLevel\n    }\n  }\n  return normalized\n}\n\nexport async function heatmapChart(\n  container,\n  data: HeatmapData,\n  dataTag: DataTag,\n  onBrush: (range: HeatmapRange) => void,\n  onZoom: () => void\n) {\n  const maxValue =\n    d3.max(data.data[dataTag].map((array) => d3.max(array)!)) || 0\n  const normalizedData = normalizeData(data.data[dataTag], maxValue)\n\n  let colorScheme: ColorScheme\n  let brightness = 1\n  let bufferCanvas: HTMLCanvasElement\n  let zoomTransform = d3.zoomIdentity\n  let tooltipStatus: TooltipStatus = _.clone(defaultTooltipStatus)\n  let focusStatus: FocusStatus | null = null\n  let isBrushing = false\n  let width = 0\n  let height = 0\n  let canvasWidth = 0\n  let canvasHeight = 0\n\n  heatmapChart.brightness = function (val: number) {\n    brightness = val\n    updateBuffer()\n    heatmapChart()\n  }\n\n  heatmapChart.brush = function (enabled: boolean) {\n    isBrushing = enabled\n    heatmapChart()\n  }\n\n  heatmapChart.resetZoom = function () {\n    zoomTransform = d3.zoomIdentity\n    heatmapChart()\n  }\n\n  heatmapChart.size = function (newWidth, newHeight) {\n    const newCanvasWidth = newWidth - margin.left - margin.right\n    const newCanvasHeight = newHeight - margin.top - margin.bottom\n    // Sync transform on resize\n    if (canvasWidth !== 0 && canvasHeight !== 0) {\n      zoomTransform = d3.zoomIdentity\n        .translate(\n          (zoomTransform.x * newCanvasWidth) / canvasWidth,\n          (zoomTransform.y * newCanvasHeight) / canvasHeight\n        )\n        .scale(zoomTransform.k)\n    }\n    width = newWidth\n    height = newHeight\n    canvasWidth = newCanvasWidth\n    canvasHeight = newCanvasHeight\n    heatmapChart()\n  }\n\n  function updateBuffer() {\n    const d = data.data[dataTag]\n    const height = d.length > 0 ? d[0].length : 0\n    const width = d.length\n    const newColorScheme = getColorScheme(maxValue, brightness)\n    bufferCanvas = createBuffer(\n      normalizedData,\n      width,\n      height,\n      newColorScheme.rasterizedColors\n    )\n    colorScheme = newColorScheme\n  }\n\n  updateBuffer()\n  heatmapChart()\n\n  function heatmapChart() {\n    let xHistogramCanvas = container\n      .selectAll('canvas.x-histogram')\n      .data([null])\n    xHistogramCanvas = xHistogramCanvas\n      .enter()\n      .append('canvas')\n      .classed('x-histogram', true)\n      .style('position', 'absolute')\n      .style('z-index', '100')\n      .merge(xHistogramCanvas)\n      .attr('width', canvasWidth * window.devicePixelRatio)\n      .attr('height', canvasHeight * window.devicePixelRatio)\n      .style('width', canvasWidth + 'px')\n      .style('height', 30 + 'px')\n      .style('margin-top', height - 60 + 'px')\n      .style('margin-left', margin.left + 'px')\n    xHistogramCanvas\n      .node()\n      .getContext('2d')\n      .scale(window.devicePixelRatio, window.devicePixelRatio)\n\n    let yHistogramCanvas = container\n      .selectAll('canvas.y-histogram')\n      .data([null])\n    yHistogramCanvas = yHistogramCanvas\n      .enter()\n      .append('canvas')\n      .classed('y-histogram', true)\n      .style('position', 'absolute')\n      .style('z-index', '101')\n      .merge(yHistogramCanvas)\n      .attr('width', 30 * window.devicePixelRatio)\n      .attr('height', canvasHeight * window.devicePixelRatio)\n      .style('width', 30 + 'px')\n      .style('height', canvasHeight + 'px')\n      .style('margin-top', margin.top + 'px')\n      .style('margin-left', width - 30 + 'px')\n    yHistogramCanvas\n      .node()\n      .getContext('2d')\n      .scale(window.devicePixelRatio, window.devicePixelRatio)\n\n    let labelCanvas = container.selectAll('canvas.label').data([null])\n    labelCanvas = labelCanvas\n      .enter()\n      .append('canvas')\n      .classed('label', true)\n      .style('position', 'absolute')\n      .style('z-index', '102')\n      .merge(labelCanvas)\n      .style('width', 90 + 'px')\n      .style('height', canvasHeight + 'px')\n      .attr('width', 90 * window.devicePixelRatio)\n      .attr('height', canvasHeight * window.devicePixelRatio)\n      .style('margin-top', margin.top + 'px')\n    labelCanvas\n      .node()\n      .getContext('2d')\n      .scale(window.devicePixelRatio, window.devicePixelRatio)\n\n    let canvas = container.selectAll('canvas.heatmap').data([null])\n    canvas = canvas\n      .enter()\n      .append('canvas')\n      .classed('heatmap', true)\n      .style('position', 'absolute')\n      .style('z-index', '103')\n      .merge(canvas)\n      .attr('width', canvasWidth * heatmapCanvasPixelRatio)\n      .attr('height', canvasHeight * heatmapCanvasPixelRatio)\n      .style('width', canvasWidth + 'px')\n      .style('height', canvasHeight + 'px')\n      .style('margin-top', margin.top + 'px')\n      .style('margin-right', margin.right + 'px')\n      .style('margin-bottom', margin.bottom + 'px')\n      .style('margin-left', margin.left + 'px')\n    const ctx: CanvasRenderingContext2D = canvas.node().getContext('2d')\n    ctx.imageSmoothingEnabled = false\n    ctx.scale(heatmapCanvasPixelRatio, heatmapCanvasPixelRatio)\n\n    let axis = container.selectAll('svg').data([null])\n    axis = axis\n      .enter()\n      .append('svg')\n      .style('position', 'absolute')\n      .style('z-index', '200')\n      .merge(axis)\n      .style('width', width + 'px')\n      .style('height', height + 'px')\n\n    let tooltipLayer = container.selectAll('div').data([null])\n    tooltipLayer = tooltipLayer\n      .enter()\n      .append('div')\n      .style('position', 'absolute')\n      .style('z-index', '300')\n      .style('pointer-events', 'none')\n      .merge(tooltipLayer)\n      .style('width', width + 'px')\n      .style('height', height + 'px')\n\n    const xScale = d3\n      .scaleLinear()\n      .domain([0, data.timeAxis.length - 1])\n      .range([0, canvasWidth])\n\n    const yScale = d3\n      .scaleLinear()\n      .domain([0, data.keyAxis.length - 1])\n      .range([0, canvasHeight])\n\n    const xAxis = d3\n      .axisBottom(xScale)\n      .tickFormat((idx) =>\n        data.timeAxis[idx as number] !== undefined\n          ? // d3.timeFormat('%Y-%m-%d %H:%M:%S')(\n            //   new Date(data.timeAxis[idx as number] * 1000)\n            // )\n            dayjs(data.timeAxis[idx as number] * 1000)\n              .utcOffset(tz.getTimeZone())\n              .format('YYYY-MM-DD HH:mm:ss')\n          : ''\n      )\n      .ticks(width / 270)\n\n    const labelAxis = labelAxisGroup(data.keyAxis).range([0, canvasHeight])\n\n    const histogramAxis = histogram(data.data[dataTag])\n      .xRange([0, canvasWidth])\n      .yRange([0, canvasHeight])\n\n    let xAxisG = axis.selectAll('g.x-axis').data([null])\n    xAxisG = xAxisG\n      .enter()\n      .append('g')\n      .classed('x-axis', true)\n      .merge(xAxisG)\n      .attr('transform', 'translate(' + margin.left + ',' + (height - 20) + ')')\n\n    d3.zoom().transform(axis, zoomTransform)\n\n    const zoomBehavior = d3\n      .zoom()\n      .scaleExtent([1, 128])\n      .on('zoom', zooming)\n      .on('end', zoomEnd)\n\n    function constrainBoucing(transform) {\n      const bounceRatio = 0.8\n      const dragLeft = Math.max(0, transform.applyX(0))\n      const dragRight = Math.max(0, canvasWidth - transform.applyX(canvasWidth))\n      const dragTop = Math.max(0, transform.applyY(0))\n      const dragBottom = Math.max(\n        0,\n        canvasHeight - transform.applyY(canvasHeight)\n      )\n      return d3.zoomIdentity\n        .translate(\n          Math.floor(transform.x - (dragLeft - dragRight) * bounceRatio),\n          Math.floor(transform.y - (dragTop - dragBottom) * bounceRatio)\n        )\n        .scale(transform.k)\n    }\n\n    function constrainHard(transform) {\n      let dx0 = transform.invertX(0),\n        dx1 = transform.invertX(canvasWidth) - canvasWidth,\n        dy0 = transform.invertY(0),\n        dy1 = transform.invertY(canvasHeight) - canvasHeight\n      return transform.translate(\n        dx1 > dx0 ? (dx0 + dx1) / 2 : Math.min(0, dx0) || Math.max(0, dx1),\n        dy1 > dy0 ? (dy0 + dy1) / 2 : Math.min(0, dy0) || Math.max(0, dy1)\n      )\n    }\n\n    function zooming() {\n      onZoom()\n      if (d3.event.sourceEvent && d3.event.sourceEvent.type === 'mousemove') {\n        zoomTransform = constrainBoucing(d3.event.transform)\n        hideTooltips()\n      } else {\n        zoomTransform = constrainHard(d3.event.transform)\n        showTooltips()\n      }\n      render()\n    }\n\n    function zoomEnd() {\n      zoomTransform = constrainHard(zoomTransform)\n      axis.call(d3.zoom().transform, zoomTransform)\n      if (tooltipStatus.pinned) {\n        showTooltips()\n      }\n      render()\n    }\n\n    function focusPoint(x: number, y: number) {\n      focusStatus = { xDomain: [x, x + 0.001], yDomain: [y, y + 0.001] }\n    }\n\n    function hoverBehavior(axis) {\n      axis.on('mousemove', () => {\n        showTooltips()\n        render()\n      })\n\n      axis.on('mouseout', () => {\n        if (!tooltipStatus.pinned && !isBrushing) {\n          focusStatus = null\n          render()\n        }\n      })\n    }\n\n    function showTooltips() {\n      tooltipStatus.hidden = false\n\n      if (!tooltipStatus.pinned) {\n        const mouseCanvasOffset = d3.mouse(canvas.node())\n        if (isNaN(mouseCanvasOffset[0])) return\n\n        const xRescale = zoomTransform.rescaleX(xScale)\n        const yRescale = zoomTransform.rescaleY(yScale)\n        const x = xRescale.invert(mouseCanvasOffset[0])\n        const y = yRescale.invert(mouseCanvasOffset[1])\n\n        if (!isBrushing) focusPoint(x, y)\n\n        if (\n          mouseCanvasOffset[0] < 0 ||\n          mouseCanvasOffset[0] > canvasWidth ||\n          mouseCanvasOffset[1] < 0 ||\n          mouseCanvasOffset[1] > canvasHeight\n        ) {\n          hideTooltips()\n        } else {\n          tooltipStatus.x = x\n          tooltipStatus.y = y\n        }\n      }\n    }\n\n    function hideTooltips() {\n      tooltipStatus.hidden = true\n    }\n\n    function hideAxisTicksWithoutLabel() {\n      axis.selectAll('.tick text').each(function () {\n        if (this.innerHTML === '') {\n          this.parentNode.style.display = 'none'\n        }\n      })\n    }\n\n    axis.on('click', clicked)\n\n    function clicked() {\n      if (d3.event.defaultPrevented) return // zoom\n\n      const mouseCanvasOffset = d3.mouse(canvas.node())\n      if (\n        mouseCanvasOffset[0] < 0 ||\n        mouseCanvasOffset[0] > canvasWidth ||\n        mouseCanvasOffset[1] < 0 ||\n        mouseCanvasOffset[1] > canvasHeight\n      ) {\n        return\n      }\n\n      tooltipStatus.pinned = !tooltipStatus.pinned\n      showTooltips()\n      render()\n    }\n\n    axis.call(zoomBehavior)\n    axis.call(hoverBehavior)\n\n    function render() {\n      renderHeatmap()\n      // renderHighlight()\n      renderAxis()\n      renderBrush()\n      renderTooltip()\n      renderCross()\n      legend(colorScheme, dataTag)\n    }\n\n    function renderHeatmap() {\n      ctx.clearRect(0, 0, canvasWidth, canvasHeight)\n      ctx.drawImage(\n        bufferCanvas,\n        xScale.invert(zoomTransform.invertX(0)),\n        yScale.invert(zoomTransform.invertY(0)),\n        xScale.invert(canvasWidth * (1 / zoomTransform.k)),\n        yScale.invert(canvasHeight * (1 / zoomTransform.k)),\n        0,\n        0,\n        canvasWidth,\n        canvasHeight\n      )\n    }\n\n    // function renderHighlight() {\n    //   const selectedData = data.data[dataTag]\n    //   const xLen = selectedData.length\n    //   const yLen = selectedData[0].length\n    //   const xRescale = zoomTransform.rescaleX(xScale)\n    //   const yRescale = zoomTransform.rescaleY(yScale)\n    //   const xStartIdx = Math.max(0, Math.floor(xScale.invert(0)))\n    //   const xEndIdx = Math.min(xLen - 1, Math.ceil(xScale.invert(canvasWidth)))\n    //   const yStartIdx = Math.max(0, Math.floor(yScale.invert(0)))\n    //   const yEndIdx = Math.min(yLen - 1, Math.ceil(yScale.invert(canvasHeight)))\n\n    //   ctx.shadowColor = '#fff'\n    //   ctx.shadowBlur = 9 + zoomTransform.k // 10 + 1 * (zoomTransform.k - 1)\n    //   ctx.fillStyle = 'blue'\n    //   for (let x = xStartIdx; x < xEndIdx; x++) {\n    //     for (let y = yStartIdx; y < yEndIdx; y++) {\n    //       if (selectedData[x][y] > maxValue / 2) {\n    //         const left = xRescale(x)\n    //         const top = yRescale(y)\n    //         const right = xRescale(x + 1)\n    //         const bottom = yRescale(y + 1)\n    //         const width = right - left\n    //         const height = bottom - top\n    //         const xPadding = ((0.8 + 0.5 * (1 - 1 / zoomTransform.k)) * width) / height\n    //         const yPadding = ((0.8 + 0.5 * (1 - 1 / zoomTransform.k)) * height) / width\n    //         ctx.beginPath()\n    //         ctx.shadowOffsetX = (left + 1000) * heatmapCanvasPixelRatio\n    //         ctx.shadowOffsetY = (top + 1000) * heatmapCanvasPixelRatio\n    //         ctx.fillRect(-1000 - xPadding, -1000 - yPadding, right - left + xPadding * 2, bottom - top + yPadding * 2)\n    //         ctx.closePath()\n    //       }\n    //     }\n    //   }\n    // }\n\n    function renderAxis() {\n      const xRescale = zoomTransform.rescaleX(xScale)\n      const yRescale = zoomTransform.rescaleY(yScale)\n      histogramAxis(\n        xHistogramCanvas.node().getContext('2d'),\n        yHistogramCanvas.node().getContext('2d'),\n        focusStatus?.xDomain,\n        focusStatus?.yDomain,\n        xRescale,\n        yRescale\n      )\n      labelAxis(\n        labelCanvas.node().getContext('2d'),\n        focusStatus?.yDomain,\n        yRescale\n      )\n      xAxisG.call(xAxis.scale(xRescale))\n      hideAxisTicksWithoutLabel()\n    }\n\n    function renderBrush() {\n      if (isBrushing) {\n        const brush = d3\n          .brush()\n          .extent([\n            [0, 0],\n            [canvasWidth, canvasHeight]\n          ])\n          .on('start', brushStart)\n          .on('brush', brushing)\n          .on('end', brushEnd)\n\n        let brushSvg = axis.selectAll('g.brush').data([null])\n        brushSvg = brushSvg\n          .enter()\n          .append('g')\n          .classed('brush', true)\n          .merge(brushSvg)\n          .attr(\n            'transform',\n            'translate(' + margin.left + ',' + margin.top + ')'\n          )\n          .call(brush)\n\n        function brushStart() {\n          hideTooltips()\n          render()\n        }\n\n        function brushing() {\n          const selection = d3.event.selection\n          if (selection) {\n            const xRescale = zoomTransform.rescaleX(xScale)\n            const yRescale = zoomTransform.rescaleY(yScale)\n            focusStatus = {\n              xDomain: [\n                xRescale.invert(selection[0][0]),\n                xRescale.invert(selection[1][0])\n              ],\n              yDomain: [\n                yRescale.invert(selection[0][1]),\n                yRescale.invert(selection[1][1])\n              ]\n            }\n            render()\n          }\n        }\n\n        function brushEnd() {\n          brushSvg.remove()\n          isBrushing = false\n\n          const selection = d3.event.selection\n          if (selection) {\n            brush.move(brushSvg, null)\n            const xRescale = zoomTransform.rescaleX(xScale)\n            const yRescale = zoomTransform.rescaleY(yScale)\n            const startTime =\n              data.timeAxis[Math.floor(xRescale.invert(selection[0][0]))]\n            const endTime =\n              data.timeAxis[Math.ceil(xRescale.invert(selection[1][0]))]\n            const startKey =\n              data.keyAxis[Math.ceil(yRescale.invert(selection[1][1]))].key\n            const endKey =\n              data.keyAxis[Math.floor(yRescale.invert(selection[0][1]))].key\n\n            onBrush({\n              starttime: startTime,\n              endtime: endTime,\n              startkey: startKey,\n              endkey: endKey\n            })\n          }\n\n          showTooltips()\n          render()\n        }\n      } else {\n        axis.selectAll('g.brush').remove()\n      }\n    }\n\n    function getTooltipOverviewLabel(keyIdx) {\n      const startLabel = data.keyAxis[keyIdx]!.labels\n      const endLabel = data.keyAxis[keyIdx - 1]!.labels\n\n      if (!startLabel && !endLabel) {\n        return []\n      }\n      if (!startLabel) {\n        return endLabel\n      }\n      if (!endLabel || _.isEqual(startLabel, endLabel)) {\n        return startLabel\n      }\n\n      const startLen = startLabel.length\n      const endLen = endLabel.length\n\n      // Cross start boundary, only use end label\n      if (\n        startLen >= 1 &&\n        startLen + 1 === endLen &&\n        _.isEqual(startLabel, endLabel.slice(0, startLen))\n      ) {\n        return endLabel\n      }\n      // range\n      if (\n        startLen >= 3 &&\n        startLen === endLen &&\n        _.isEqual(\n          startLabel.slice(0, startLen - 1),\n          endLabel.slice(0, startLen - 1)\n        )\n      ) {\n        return [\n          ...startLabel.slice(0, startLen - 1),\n          `${startLabel[startLen - 1]} ~ ${endLabel[startLen - 1]}`\n        ]\n      }\n      // Cross end boundary, only use start label\n      return startLabel\n    }\n\n    function renderTooltip() {\n      if (tooltipStatus.hidden) {\n        tooltipLayer.selectAll('div').remove()\n        return\n      }\n\n      const timeIdx = Math.floor(tooltipStatus.x)\n      const keyIdx = Math.floor(tooltipStatus.y)\n\n      if (data.keyAxis[keyIdx] == null || data.keyAxis[keyIdx + 1] == null) {\n        return\n      }\n\n      if (\n        data.timeAxis[timeIdx] == null ||\n        data.timeAxis[timeIdx + 1] == null\n      ) {\n        return\n      }\n\n      const xRescale = zoomTransform.rescaleX(xScale)\n      const yRescale = zoomTransform.rescaleY(yScale)\n      const canvasOffset = [\n        xRescale(tooltipStatus.x)!,\n        yRescale(tooltipStatus.y)!\n      ]\n\n      let tooltipDiv = tooltipLayer.selectAll('div').data([null])\n      tooltipDiv = tooltipDiv\n        .enter()\n        .append('div')\n        .style('position', 'absolute')\n        // .style('width', tooltipSize.width + 'px')\n        // .style('height', tooltipSize.height + 'px')\n        .classed('tooltip', true)\n        .merge(tooltipDiv)\n        .style('pointer-events', tooltipStatus.pinned ? 'all' : 'none')\n\n      if (canvasOffset[0] < canvasWidth / 2) {\n        // Left half\n        const v = canvasOffset[0] + tooltipOffset.horizontal + margin.left\n        tooltipDiv.style('left', `${v}px`).style('right', 'auto')\n      } else {\n        // Right half\n        const v =\n          canvasWidth -\n          canvasOffset[0] +\n          tooltipOffset.horizontal +\n          margin.right\n        tooltipDiv.style('right', `${v}px`).style('left', 'auto')\n      }\n\n      if (canvasOffset[1] < canvasHeight / 2) {\n        // Top half\n        const v = canvasOffset[1] + tooltipOffset.vertical + margin.top\n        tooltipDiv.style('top', `${v}px`).style('bottom', 'auto')\n      } else {\n        // Bottom half\n        const v =\n          canvasHeight -\n          canvasOffset[1] +\n          tooltipOffset.vertical +\n          margin.bottom\n        tooltipDiv.style('bottom', `${v}px`).style('top', 'auto')\n      }\n\n      const value = data.data[dataTag]?.[timeIdx]?.[keyIdx]\n\n      let valueDiv = tooltipDiv.selectAll('div.value').data([null])\n      valueDiv = valueDiv\n        .enter()\n        .append('div')\n        .classed('value', true)\n        .merge(valueDiv)\n\n      let valueText = valueDiv.selectAll('div.value').data([null])\n      valueText\n        .enter()\n        .append('div')\n        .classed('value', true)\n        .merge(valueText)\n        .text(withUnit(value))\n        .style('color', colorScheme.label(value))\n        .style('background-color', colorScheme.background(value))\n\n      let unitText = valueDiv.selectAll('div.unit').data([null])\n      unitText\n        .enter()\n        .append('div')\n        .classed('unit', true)\n        .merge(unitText)\n        .text(tagUnit(dataTag))\n\n      const timeText = [timeIdx, timeIdx + 1]\n        .map((idx) =>\n          // d3.timeFormat('%Y-%m-%d\\n%H:%M:%S')(\n          //   new Date(data.timeAxis[idx] * 1000)\n          // )\n          dayjs(data.timeAxis[idx as number] * 1000)\n            .utcOffset(tz.getTimeZone())\n            .format('YYYY-MM-DD HH:mm:ss')\n        )\n        .join(' ~ ')\n\n      let timeDiv = tooltipDiv.selectAll('button.time').data([timeText])\n      timeDiv\n        .enter()\n        .append('button')\n        .classed('time', true)\n        .merge(timeDiv)\n        .call(clickToCopyBehavior, (d) => d)\n        .text((d) => d)\n\n      let overviewLabelDiv = tooltipDiv\n        .selectAll('div.overviewLabel')\n        .data([keyIdx + 1])\n      overviewLabelDiv = overviewLabelDiv\n        .enter()\n        .append('div')\n        .classed('overviewLabel', true)\n        .merge(overviewLabelDiv)\n\n      let overviewSubLabel = overviewLabelDiv\n        .selectAll('.subLabel')\n        .style('display', 'none')\n        .data((keyIdx) => getTooltipOverviewLabel(keyIdx))\n\n      overviewSubLabel\n        .enter()\n        .append('button')\n        .classed('subLabel', true)\n        .merge(overviewSubLabel)\n        .call(clickToCopyBehavior, (d) => d)\n        .text((d, idx) => {\n          // Prefix with spaces\n          return '\\u00A0'.repeat(idx * 2) + d\n        })\n        .style('display', 'block')\n\n      let keyContainer = tooltipDiv.selectAll('div.keyContainer').data([\n        {\n          desc: 'Start Key (Incl.):',\n          idx: keyIdx + 1\n        },\n        {\n          desc: 'End key (Excl.):',\n          idx: keyIdx\n        }\n      ])\n\n      keyContainer = keyContainer\n        .enter()\n        .append('div')\n        .classed('keyContainer', true)\n        .merge(keyContainer)\n\n      let descText = keyContainer.selectAll('.desc').data((d) => [d])\n      descText\n        .enter()\n        .append('div')\n        .classed('desc', true)\n        .merge(descText)\n        .text(({ desc }) => desc)\n\n      let keyText = keyContainer.selectAll('button.key').data((d) => [d])\n      keyText\n        .enter()\n        .append('button')\n        .classed('key', true)\n        .merge(keyText)\n        .call(clickToCopyBehavior, ({ idx }) => data.keyAxis[idx]!.key)\n        .text(({ idx }) => data.keyAxis[idx]!.key)\n    }\n\n    function renderCross() {\n      if (tooltipStatus.pinned) {\n        const xRescale = zoomTransform.rescaleX(xScale)\n        const yRescale = zoomTransform.rescaleY(yScale)\n        const canvasOffset = [\n          xRescale(tooltipStatus.x)!,\n          yRescale(tooltipStatus.y)!\n        ]\n        const crossCenterPadding = 3\n        const crossBorder = 1\n        const crossSize = 8\n        const crossWidth = 2\n\n        ctx.lineWidth = crossWidth + 2 * crossBorder\n        ctx.strokeStyle = '#111'\n        ctx.beginPath()\n        ctx.moveTo(canvasOffset[0], canvasOffset[1] - crossSize - crossBorder)\n        ctx.lineTo(\n          canvasOffset[0],\n          canvasOffset[1] - crossCenterPadding + crossBorder\n        )\n        ctx.moveTo(\n          canvasOffset[0],\n          canvasOffset[1] + crossCenterPadding - crossBorder\n        )\n        ctx.lineTo(canvasOffset[0], canvasOffset[1] + crossSize + crossBorder)\n        ctx.moveTo(canvasOffset[0] - crossSize - crossBorder, canvasOffset[1])\n        ctx.lineTo(\n          canvasOffset[0] - crossCenterPadding + crossBorder,\n          canvasOffset[1]\n        )\n        ctx.moveTo(\n          canvasOffset[0] + crossCenterPadding - crossBorder,\n          canvasOffset[1]\n        )\n        ctx.lineTo(canvasOffset[0] + crossSize + crossBorder, canvasOffset[1])\n        ctx.stroke()\n        ctx.lineWidth = crossWidth\n        ctx.strokeStyle = '#eee'\n        ctx.beginPath()\n        ctx.moveTo(canvasOffset[0], canvasOffset[1] - crossSize)\n        ctx.lineTo(canvasOffset[0], canvasOffset[1] - crossCenterPadding)\n        ctx.moveTo(canvasOffset[0], canvasOffset[1] + crossCenterPadding)\n        ctx.lineTo(canvasOffset[0], canvasOffset[1] + crossSize)\n        ctx.moveTo(canvasOffset[0] - crossSize, canvasOffset[1])\n        ctx.lineTo(canvasOffset[0] - crossCenterPadding, canvasOffset[1])\n        ctx.moveTo(canvasOffset[0] + crossCenterPadding, canvasOffset[1])\n        ctx.lineTo(canvasOffset[0] + crossSize, canvasOffset[1])\n        ctx.stroke()\n      }\n    }\n\n    render()\n  }\n\n  return heatmapChart\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/heatmap/color.ts",
    "content": "import * as d3 from 'd3'\n\nconst heatmapColor = d3.interpolateRgbBasis([\n  '#000000',\n  '#080808',\n  '#090909',\n  '#101010',\n  '#111111',\n  '#121212',\n  '#131313',\n  '#141414',\n  '#151515',\n  '#171717',\n  '#181818',\n  '#191919',\n  '#410c74',\n  '#72067b',\n  '#b00f53',\n  '#fcc734',\n  '#fbfc43',\n  '#ffffb0'\n])\n\nexport const rasterizeLevel = 100\n\nexport type ColorScale = (val: number) => d3.RGBColor\nexport type ColorScheme = {\n  background: ColorScale\n  label: ColorScale\n  maxValue: number\n  rasterizedColors: Uint32Array\n}\n\nexport function getColorScheme(\n  maxValue: number,\n  brightness: number\n): ColorScheme {\n  const logScale = (d3 as any).scaleSymlog().domain([0, maxValue / brightness])\n  const backgroundColorScale = (d: number) =>\n    d3.color(heatmapColor(logScale(d)))! as d3.RGBColor\n  const labelColorScale = (d: number) =>\n    d3.hsl(backgroundColorScale(d)).l > 0.5\n      ? (d3.color('black')! as d3.RGBColor)\n      : (d3.color('white')! as d3.RGBColor)\n\n  const rasterizedColors = new Uint32Array(rasterizeLevel + 1)\n  for (let i = 0; i <= rasterizeLevel; i++) {\n    const color = d3.color(\n      backgroundColorScale(Math.pow(maxValue, i / rasterizeLevel))\n    )\n    const colorRgb = color.rgb()\n    rasterizedColors[i] =\n      colorRgb.r | (colorRgb.g << 8) | (colorRgb.b << 16) | 0xff000000\n  }\n\n  return {\n    background: backgroundColorScale,\n    label: labelColorScale,\n    maxValue: maxValue,\n    rasterizedColors\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/heatmap/index.tsx",
    "content": "import React, { useRef, useEffect } from 'react'\nimport * as d3 from 'd3'\nimport { useEventListener } from 'ahooks'\nimport { heatmapChart } from './chart'\n\nfunction _Heatmap(props) {\n  const divRef: React.RefObject<HTMLDivElement> = useRef(null)\n  const chart = useRef<any>(null)\n\n  function updateChartSize() {\n    if (divRef.current == null) {\n      return\n    }\n    if (!chart.current) {\n      return\n    }\n    const container = divRef.current\n    const width = container.offsetWidth\n    const height = container.offsetHeight\n    chart.current.size(width, height)\n  }\n\n  useEffect(() => {\n    const init = async () => {\n      if (divRef.current != null) {\n        const container = divRef.current\n        chart.current = await heatmapChart(\n          d3.select(container),\n          props.data,\n          props.dataTag,\n          props.onBrush,\n          props.onZoom\n        )\n        props.onChartInit(chart.current)\n        updateChartSize()\n      }\n    }\n    init()\n  }, [props, props.data, props.dataTag])\n\n  useEventListener('resize', () => {\n    updateChartSize()\n  })\n\n  return <div className=\"heatmap\" ref={divRef} />\n}\n\nexport const Heatmap = React.memo(_Heatmap)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/heatmap/legend.ts",
    "content": "import * as d3 from 'd3'\nimport _ from 'lodash'\n\nimport { ColorScheme } from './color'\nimport { DataTag } from './types'\nimport { tagUnit, withUnit } from './utils'\n\nexport default function (colorScheme: ColorScheme, dataTag: DataTag) {\n  let marginRight = 120\n  let width = 500\n  let height = 50\n  let innerWidth = width - marginRight\n  let innerHeight = 26\n  let tickCount = 5\n\n  if (document.querySelector('.PD-Cluster-Legend') === null) {\n    return\n  }\n  let container = (d3 as any)\n    .select('.PD-Cluster-Legend')\n    .style('width', `${width}px`)\n    .style('height', `${height}px`)\n\n  let xScale = (d3 as any)\n    .scaleSymlog()\n    .domain([colorScheme.maxValue / 1000, colorScheme.maxValue])\n    .range([0, innerWidth])\n\n  let canvas = container.selectAll('canvas').data([null])\n  canvas = canvas\n    .enter()\n    .append('canvas')\n    .style('position', 'absolute')\n    .style('left', '0px')\n    .style('top', '0px')\n    .merge(canvas)\n    .attr('width', width)\n    .attr('height', height)\n\n  const ctx: CanvasRenderingContext2D = canvas.node().getContext('2d')\n\n  for (let x = 0; x < innerWidth; x++) {\n    ctx.fillStyle = colorScheme.background(xScale.invert(x)).toString()\n    ctx.fillRect(x, 0, 1, innerHeight)\n  }\n\n  let xAxis = d3\n    .axisBottom(xScale)\n    .tickValues(\n      _.range(0, tickCount + 1).map((d) =>\n        xScale.invert((innerWidth * d) / tickCount)\n      )\n    )\n    .tickSize(innerHeight)\n    .tickFormat((d) => withUnit(d as number))\n\n  let svg = container.selectAll('svg').data([null])\n  svg = svg\n    .enter()\n    .append('svg')\n    .style('position', 'absolute')\n    .style('left', '0px')\n    .style('top', '0px')\n    .merge(svg)\n    .attr('width', width)\n    .attr('height', height)\n\n  let xAxisG = svg.selectAll('g').data([null])\n  xAxisG\n    .enter()\n    .append('g')\n    .merge(xAxisG)\n    .call(xAxis)\n    .call((g) => {\n      g.selectAll('.tick text').attr('y', innerHeight + 6)\n      g.selectAll('.domain').remove()\n    })\n\n  let unitLabel = container.selectAll('div').data([null])\n  unitLabel\n    .enter()\n    .append('div')\n    .classed('unit', true)\n    .merge(unitLabel)\n    .text(tagUnit(dataTag))\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/heatmap/types.ts",
    "content": "import { DecoratorLabelKey, MatrixMatrix } from '@lib/client'\n\nexport type KeyAxisEntry = DecoratorLabelKey\nexport type HeatmapData = MatrixMatrix\n\nexport type DataTag =\n  | 'integration'\n  | 'written_bytes'\n  | 'read_bytes'\n  | 'written_keys'\n  | 'read_keys'\n\nexport type HeatmapRange = {\n  starttime?: number\n  endtime?: number\n  startkey?: string\n  endkey?: string\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/heatmap/utils.ts",
    "content": "import * as d3 from 'd3'\n\nimport { DataTag } from './types'\n\nexport function tagUnit(tag: DataTag): string {\n  switch (tag) {\n    case 'integration':\n      return 'bytes/min'\n    case 'read_bytes':\n      return 'bytes/min'\n    case 'written_bytes':\n      return 'bytes/min'\n    case 'read_keys':\n      return 'keys/min'\n    case 'written_keys':\n      return 'keys/min'\n  }\n}\n\nexport function withUnit(val: number): string {\n  val = val || 0\n  if (val > 1024 * 1024 * 1024) {\n    return (val / 1024 / 1024 / 1024).toFixed(2) + ' G'\n  } else if (val > 1024 * 1024) {\n    return (val / 1024 / 1024).toFixed(2) + ' M'\n  } else if (val > 1024) {\n    return (val / 1024).toFixed(2) + ' K'\n  } else {\n    return val.toFixed(2)\n  }\n}\n\nexport function truncateString(str: string, len: number): string {\n  if (str.length > len) {\n    return (\n      str.substr(0, len / 2 - 1) +\n      '....' +\n      str.substr(str.length - len / 2 + 1, str.length)\n    )\n  } else {\n    return str\n  }\n}\n\nexport function clickToCopyBehavior(selection, map) {\n  selection.each(function (d) {\n    d3.select(this).on('click', () => {\n      copyToClipboard(map(d))\n    })\n  })\n}\n\nfunction copyToClipboard(text: string) {\n  const input = d3.select('body').append('input').attr('value', text)\n  input.node()!.select()\n  document.execCommand('copy')\n  input.remove()\n}\n\nexport function doEventsOnYield(generator): Promise<undefined> {\n  return new Promise((resolve, reject) => {\n    let g = generator()\n    let advance = () => {\n      try {\n        let r = g.next()\n        if (r.done) resolve(undefined)\n      } catch (e) {\n        reject(e)\n      }\n      setTimeout(advance, 0)\n    }\n    advance()\n  })\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { Routes, Route, HashRouter as Router } from 'react-router-dom'\n\nimport { Root } from '@lib/components'\nimport { addTranslations } from '@lib/utils/i18n'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\n\nimport KeyViz from './components/KeyViz'\nimport translations from './translations'\nimport { KeyVizContext } from './context'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/keyviz\" element={<KeyViz />} />\n    </Routes>\n  )\n}\n\nexport default () => {\n  const ctx = useContext(KeyVizContext)\n  if (ctx === null) {\n    throw new Error('KeyVizContext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/translations/en.yaml",
    "content": "keyviz:\n  nav_title: Key Visualizer\n  toolbar:\n    brightness: Brightness\n    zoom:\n      select: Select & Zoom\n      reset: Reset\n    view_type:\n      read_bytes: Read (bytes)\n      write_bytes: Write (bytes)\n      read_keys: Read (keys)\n      write_keys: Write (keys)\n      all: All\n  settings:\n    title: Settings\n    disabled_result:\n      title: Feature Not Enabled\n      sub_title: |\n        Key Visualizer feature is not enabled so that visual reports cannot be viewed.\n        You can modify settings to enable the feature and wait for new data being collected.\n    open_setting: Open Settings\n    close_keyviz: Disable Key Visualizer Feature\n    close_keyviz_warning: Are you sure want to disable this feature? Current visual reports will be cleared.\n    switch: Enable Feature\n    switch_tooltip: Whether Key Visualizer feature is enabled. When enabled, there will be small overhead.\n    policy: Policy\n    policy_db: '{{distro.tidb}}'\n    policy_kv: Raw KV\n    separator: Separator\n    separator_placeholder: The separator used to split Key\n    separator_empty_warning: If left blank, Key will not be split\n    actions:\n      save: Save\n      close: Disable\n      cancel: Cancel\n    help: Help\n    help_url: https://docs.pingcap.com/tidb/dev/dashboard-key-visualizer\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/translations/zh.yaml",
    "content": "keyviz:\n  nav_title: 流量可视化\n  toolbar:\n    brightness: 调整亮度\n    zoom:\n      select: 框选\n      reset: 重置\n    view_type:\n      read_bytes: 读取字节量\n      write_bytes: 写入字节量\n      read_keys: 读取次数\n      write_keys: 写入次数\n      all: 所有\n  settings:\n    title: 设置\n    disabled_result:\n      title: 该功能未启用\n      sub_title: |\n        流量可视化功能未启用，因此无法查看可视化报告。\n        您可以修改设置打开该功能后等待新数据收集。\n    open_setting: 打开设置\n    close_keyviz: 关闭流量可视化功能\n    close_keyviz_warning: 确认要关闭该功能吗？关闭后现有历史记录也将被清空！\n    switch: 启用功能\n    switch_tooltip: 是否启用流量可视化功能，关闭后将不能使用流量可视化功能，但能减少一些 {{distro.pd}} 的 CPU 资源开销。\n    policy: 模式\n    policy_db: '{{distro.tidb}}'\n    policy_kv: 原生 KV\n    separator: 分隔符\n    separator_placeholder: 用于切分 Key 的分隔符\n    separator_empty_warning: 分隔符为空串时，Key 将不会被切分\n    actions:\n      save: 保存\n      close: 确认\n      cancel: 取消\n    help: 帮助\n    help_url: https://docs.pingcap.com/zh/tidb/dev/dashboard-key-visualizer\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/utils/api.ts",
    "content": "import { HeatmapData, HeatmapRange, DataTag } from '../heatmap/types'\nimport { IKeyVizDataSource } from '../context'\n\nexport async function fetchHeatmap(\n  fetcher: IKeyVizDataSource['keyvisualHeatmapsGet'],\n  selection?: HeatmapRange,\n  type: DataTag = 'written_bytes'\n): Promise<HeatmapData> {\n  const resp = await fetcher(\n    selection?.startkey,\n    selection?.endkey,\n    selection?.starttime,\n    selection?.endtime,\n    type\n  )\n  reverse(resp.data)\n  return resp.data\n}\n\n// Reverse the columns (key axis) of the matrix\n// so that the direction of the axis matches the first quadrant\nfunction reverse(data: HeatmapData) {\n  data.keyAxis.reverse()\n  for (const tag in data.data) {\n    const d = data.data[tag]\n    for (let col of d) {\n      col.reverse()\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/utils/index.ts",
    "content": "export * from './api'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/KeyViz/utils/telemetry.ts",
    "content": "import { mixpanel } from '@lib/utils/telemetry'\n\nexport const telemetry = {\n  changeLight() {\n    mixpanel.track('KeyViz: Change Light')\n  },\n  clickManualRefresh() {\n    mixpanel.track('KeyViz: Click Manual Refresh')\n  },\n  clickAutoRefresh() {\n    mixpanel.track('KeyViz: Clikc Auto Refresh')\n  },\n  changeTimeDuration(duration: number) {\n    mixpanel.track('KeyViz: Change Time Duration', { duration })\n  },\n  changeMetric(metric: string) {\n    mixpanel.track('KeyViz: Change Metric', { metric })\n  },\n  changeBright(bright: number) {\n    mixpanel.track('KeyViz: Change Bright', { bright })\n  },\n  toggleBrush() {\n    mixpanel.track('KeyViz: Toggle Brush')\n  },\n  resetZoom() {\n    mixpanel.track('KeyViz: Reset Zoom')\n  },\n  openSetting() {\n    mixpanel.track('KeyViz: Open Setting')\n  },\n  openHelp() {\n    mixpanel.track('KeyViz: Open Help')\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Monitoring/components/Monitoring.tsx",
    "content": "import { Space, Typography, Row, Col, Collapse, Tooltip } from 'antd'\nimport React, { useCallback, useContext, useRef, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { Stack } from 'office-ui-fabric-react'\nimport { LoadingOutlined, FileTextOutlined } from '@ant-design/icons'\nimport { useMemoizedFn } from 'ahooks'\nimport {\n  MetricsChart,\n  SyncChartPointer,\n  TimeRangeValue,\n  QueryConfig,\n  TransformNullValue\n} from 'metrics-chart'\nimport { Link } from 'react-router-dom'\nimport { debounce } from 'lodash'\n\nimport {\n  AutoRefreshButton,\n  Card,\n  DEFAULT_TIME_RANGE,\n  TimeRange,\n  Toolbar,\n  ErrorBar,\n  LimitTimeRange\n} from '@lib/components'\nimport { useTimeRangeValue } from '@lib/components/TimeRangeSelector/hook'\nimport { store } from '@lib/utils/store'\nimport { tz } from '@lib/utils'\nimport { telemetry } from '../utils/telemetry'\nimport { MonitoringContext } from '../context'\n\nexport default function Monitoring() {\n  const ctx = useContext(MonitoringContext)\n  const info = store.useState((s) => s.appInfo)\n  const pdVersion = info?.version?.pd_version\n  const [isSomeLoading, setIsSomeLoading] = useState(false)\n  const { t } = useTranslation()\n\n  const [timeRange, setTimeRange] = useState<TimeRange>(DEFAULT_TIME_RANGE)\n\n  const handleManualRefreshClick = () => {\n    telemetry.clickManualRefresh()\n    return setTimeRange((r) => ({ ...r }))\n  }\n\n  return (\n    <>\n      <Card>\n        <Toolbar>\n          <Space>\n            <LimitTimeRange\n              value={timeRange}\n              recent_seconds={ctx?.cfg.timeRangeSelector?.recent_seconds}\n              customAbsoluteRangePicker={\n                ctx?.cfg.timeRangeSelector?.customAbsoluteRangePicker\n              }\n              onChange={(v) => {\n                setTimeRange(v)\n                telemetry.selectTimeRange(v)\n              }}\n              onZoomOutClick={(start, end) =>\n                telemetry.clickZoomOut([start, end])\n              }\n            />\n            <AutoRefreshButton\n              onChange={telemetry.selectAutoRefreshOption}\n              onRefresh={handleManualRefreshClick}\n              disabled={isSomeLoading}\n            />\n            {ctx?.cfg.metricsReferenceLink && (\n              <Tooltip\n                placement=\"top\"\n                title={t('monitoring.panel_no_data_tips')}\n              >\n                <a\n                  href={ctx?.cfg.metricsReferenceLink}\n                  target=\"_blank\"\n                  rel=\"noopener noreferrer\"\n                >\n                  <FileTextOutlined\n                    onClick={() => telemetry.clickDocumentationIcon()}\n                  />\n                </a>\n              </Tooltip>\n            )}\n\n            {isSomeLoading && <LoadingOutlined />}\n          </Space>\n        </Toolbar>\n      </Card>\n      <SyncChartPointer>\n        <Stack tokens={{ childrenGap: 16 }}>\n          <Card noMarginTop>\n            {ctx?.cfg.getMetricsQueries(pdVersion).map((item) => (\n              <>\n                {item.category ? (\n                  <Collapse defaultActiveKey={['1']} ghost key={item.category}>\n                    <Collapse.Panel\n                      header={t(`monitoring.category.${item.category}`)}\n                      key=\"1\"\n                      style={{\n                        fontSize: 16,\n                        fontWeight: 500,\n                        padding: 0,\n                        marginLeft: -16\n                      }}\n                    >\n                      <MetricsChartWrapper\n                        metrics={item.metrics}\n                        timeRange={timeRange}\n                        setTimeRange={setTimeRange}\n                        setIsSomeLoading={setIsSomeLoading}\n                      />\n                    </Collapse.Panel>\n                  </Collapse>\n                ) : (\n                  <MetricsChartWrapper\n                    metrics={item.metrics}\n                    timeRange={timeRange}\n                    setTimeRange={setTimeRange}\n                    setIsSomeLoading={setIsSomeLoading}\n                  />\n                )}\n              </>\n            ))}\n          </Card>\n        </Stack>\n      </SyncChartPointer>\n    </>\n  )\n}\n\ninterface MetricsChartWrapperProps {\n  metrics: {\n    title: string\n    queries: QueryConfig[]\n    unit: string\n    nullValue?: TransformNullValue\n  }[]\n  timeRange: TimeRange\n  setTimeRange: (timeRange: TimeRange) => void\n  setIsSomeLoading: (isLoading: boolean) => void\n}\n\nconst MetricsChartWrapper: React.FC<MetricsChartWrapperProps> = ({\n  metrics,\n  timeRange,\n  setTimeRange,\n  setIsSomeLoading\n}) => {\n  const ctx = useContext(MonitoringContext)\n  const loadingCounter = useRef(0)\n  const [chartRange, setChartRange] = useTimeRangeValue(timeRange, setTimeRange)\n  const { t } = useTranslation()\n  const promAddrConfigurable = ctx?.cfg.promAddrConfigurable || false\n\n  // eslint-disable-next-line\n  const setIsSomeLoadingDebounce = useCallback(\n    debounce(setIsSomeLoading, 100, { leading: true }),\n    []\n  )\n\n  const handleOnBrush = (range: TimeRangeValue) => {\n    setChartRange(range)\n  }\n\n  const onLoadingStateChange = useMemoizedFn((loading: boolean) => {\n    loading\n      ? (loadingCounter.current += 1)\n      : loadingCounter.current > 0 && (loadingCounter.current -= 1)\n    setIsSomeLoadingDebounce(loadingCounter.current > 0)\n  })\n\n  const ErrorComponent = (error: Error) => (\n    <Space direction=\"vertical\">\n      <ErrorBar errors={[error]} />\n      {promAddrConfigurable && (\n        <Link to=\"/user_profile?blink=profile.prometheus\">\n          {t('monitoring.change_prom_button')}\n        </Link>\n      )}\n    </Space>\n  )\n\n  return (\n    <Row gutter={[16, 16]}>\n      {metrics.map((item) => (\n        <Col xl={12} sm={24} key={item.title}>\n          <Card\n            noMargin\n            style={{\n              border: '1px solid #f1f0f0',\n              padding: '10px 2rem',\n              backgroundColor: '#fcfcfd'\n            }}\n          >\n            <Typography.Title level={5} style={{ textAlign: 'center' }}>\n              {item.title}\n            </Typography.Title>\n            <MetricsChart\n              queries={item.queries}\n              range={chartRange}\n              nullValue={item.nullValue}\n              unit={item.unit!}\n              timezone={tz.getTimeZone()}\n              fetchPromeData={ctx!.ds.metricsQueryGet}\n              onLoading={onLoadingStateChange}\n              onBrush={handleOnBrush}\n              errorComponent={ErrorComponent}\n              onClickSeriesLabel={(seriesName) =>\n                telemetry.clickSeriesLabel(item.title, seriesName)\n              }\n            />\n          </Card>\n        </Col>\n      ))}\n    </Row>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Monitoring/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { MetricsQueryResponse } from '@lib/client'\n\nimport { QueryConfig, TransformNullValue } from 'metrics-chart'\n\nexport interface MetricsQueryType {\n  category?: string\n  metrics: {\n    title: string\n    queries: QueryConfig[]\n    unit: string\n    nullValue?: TransformNullValue\n  }[]\n}\n\ninterface IMetricConfig {\n  getMetricsQueries: (pdVersion: string | undefined) => MetricsQueryType[]\n  promAddrConfigurable?: boolean\n  timeRangeSelector?: {\n    recent_seconds: number[]\n    customAbsoluteRangePicker: boolean\n  }\n  metricsReferenceLink?: string\n}\n\nexport interface IMonitoringDataSource {\n  metricsQueryGet(params: {\n    endTimeSec?: number\n    query?: string\n    startTimeSec?: number\n    stepSec?: number\n  }): Promise<MetricsQueryResponse>\n}\n\nexport interface IMonitoringContext {\n  ds: IMonitoringDataSource\n  cfg: IMetricConfig\n}\n\nexport const MonitoringContext = createContext<IMonitoringContext | null>(null)\n\nexport const MonitoringProvider = MonitoringContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Monitoring/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { HashRouter as Router } from 'react-router-dom'\n\nimport { Root } from '@lib/components'\nimport { addTranslations } from '@lib/utils/i18n'\n\nimport translations from './translations'\nimport { MonitoringContext } from './context'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\nimport Monitoring from './components/Monitoring'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n  return <Monitoring />\n}\n\nexport default function () {\n  const ctx = useContext(MonitoringContext)\n  if (ctx === null) {\n    throw new Error('MonitoringContext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Monitoring/translations/en.yaml",
    "content": "monitoring:\n  nav_title: Monitoring\n  panel_no_data_tips: Documentation\n  change_prom_button: Change Prometheus Address\n  category:\n    application_connection: Application Connection\n    database_time: Database Time\n    sql_count: SQL Count\n    core_feature_usage: Core Feature Usage\n    latency_break_down: Latency Break Down\n    transaction: Transaction\n    core_path_duration: Core Path Duration\n    server: Server\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Monitoring/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Monitoring/translations/zh.yaml",
    "content": "monitoring:\n  nav_title: 监控指标\n  panel_no_data_tips: 文档\n  change_prom_button: 更改 prometheus 地址\n  category:\n    application_connection: 应用连接\n    database_time: 数据库时间\n    sql_count: SQL 数量\n    core_feature_usage: 核心功能使用情况\n    latency_break_down: 延迟及拆解\n    transaction: 事务\n    core_path_duration: 核心流程耗时\n    server: 服务\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Monitoring/utils/telemetry.ts",
    "content": "import { TimeRange } from '@lib/components'\nimport { mixpanel } from '@lib/utils/telemetry'\n\nexport const telemetry = {\n  // time range picker\n  clickZoomOut(timestamps: [number, number]) {\n    mixpanel.track('Monitoring: Click Zoom Out Button', { timestamps })\n  },\n  selectTimeRange(v: TimeRange) {\n    mixpanel.track('Monitoring: Select Time Range', v)\n  },\n  clickManualRefresh() {\n    mixpanel.track('Monitoring: Click Manual Refresh')\n  },\n  selectAutoRefreshOption(seconds: number) {\n    mixpanel.track('Monitoring: Select Auto Refresh Option', { seconds })\n  },\n  clickDocumentationIcon() {\n    mixpanel.track('Monitoring: Click Documentation Icon')\n  },\n  clickSeriesLabel(chartTitle: string, seriesName: string) {\n    mixpanel.track('Monitoring: Click to Hide Series', {\n      chartTitle,\n      seriesName\n    })\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/OptimizerTrace/components/LogicalOperatorTree.tsx",
    "content": "import React, { useEffect, useRef } from 'react'\nimport { graphviz } from 'd3-graphviz'\n\nimport styles from './OperatorTree.module.less'\n\ninterface LogicalOperatorTreeProps {\n  data: LogicalOperatorNode[]\n  labels?: any\n  className?: string\n\n  nodeName?: string\n  onSelect?: (name: string) => void\n}\n\nexport interface LogicalOperatorNode {\n  id: number\n  children: number[] // children id\n  type: string\n  cost: number\n  selected: boolean\n  property: string\n  info: string\n}\n\nexport default function LogicalOperatorTree({\n  data,\n  labels = {},\n  className,\n\n  nodeName,\n  onSelect\n}: LogicalOperatorTreeProps) {\n  const containerRef = useRef<HTMLDivElement>(null)\n\n  useEffect(() => {\n    const containerEl = containerRef.current\n    if (!containerEl) {\n      return\n    }\n\n    const define = data\n      .map(\n        (n) =>\n          `${n.id} ${createLabels({\n            label: `${n.type}_${n.id}`,\n            color: labels.color || '',\n            fillcolor: `${n.type}_${n.id}` === nodeName ? '#bfdbfe' : 'white',\n            tooltip: `info: ${n.info}`\n          })};\\n`\n      )\n      .join('')\n    const link = data\n      .map((n) =>\n        (n.children || [])\n          .map((c) => `${n.id} -> ${c} ${createLabels(labels)};\\n`)\n          .join('')\n      )\n      .join('')\n\n    graphviz(containerEl).renderDot(\n      `digraph {\nnode [shape=ellipse fontsize=8 fontname=\"Verdana\" style=\"filled\"];\n${define}\\n${link}\\n}`\n    )\n  }, [containerRef, data, labels, nodeName])\n\n  // find clicked node\n  function handleClick(e) {\n    const trigger = e.target\n    const parent = e.target.parentNode\n    if (\n      (trigger?.tagName === 'text' || trigger?.tagName === 'ellipse') &&\n      parent?.tagName === 'a'\n    ) {\n      for (const el of parent.children) {\n        if (el.tagName === 'text') {\n          onSelect?.(el.innerHTML)\n          break\n        }\n      }\n    }\n  }\n\n  return (\n    <div\n      ref={containerRef}\n      className={`${styles.operator_tree} ${className || ''}`}\n      onClick={handleClick}\n    ></div>\n  )\n}\n\nexport function createLabels(labels: { [props: string]: string } = {}): string {\n  const ls = Object.entries(labels).filter(([k, v]) => !!v)\n  if (!ls.length) {\n    return ''\n  }\n  return `[${ls.map(([k, v]) => `${k}=\"${v}\"`).join(' ')}]`\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/OptimizerTrace/components/OperatorTree.module.less",
    "content": ".operator_tree {\n  height: 300px;\n  width: 200px;\n\n  svg {\n    height: 100%;\n    width: 100%;\n  }\n}\n\n.cost_tree {\n  width: 100%;\n  height: 600px;\n}\n\n.tree_container {\n  position: relative;\n}\n\n.tree_container:hover .fullscreen_icon_box {\n  opacity: 1;\n}\n\n.fullscreen_icon_box {\n  position: absolute;\n  padding: 4px;\n  top: 0;\n  right: 0;\n  opacity: 0;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/OptimizerTrace/components/PhysicalCostTree.tsx",
    "content": "import React, { useEffect, useRef, useMemo, useState } from 'react'\nimport { graphviz } from 'd3-graphviz'\n\nimport { createLabels } from './LogicalOperatorTree'\n\nimport styles from './OperatorTree.module.less'\n\nexport interface PhyscialCostParam {\n  id: number // for generate graphviz\n\n  name: string\n  desc: string\n  cost: number\n  // null means this node can be replaced by a root node\n  // undefined means this node is a leaf node, it is converted from a number leaf node\n  params: null | undefined | { [x: string]: number | PhyscialCostParam }\n}\n\nexport interface PhysicalCostRoot {\n  id: number\n  type: string\n  cost: number\n  desc: string\n  params: { [x: string]: number | PhyscialCostParam }\n}\n\nexport interface PhysicalCostMap {\n  [x: string]: PhysicalCostRoot\n}\n\ninterface PhysicalCostTreeProps {\n  costs: PhysicalCostMap\n  name: string\n  className?: string\n}\n\nlet globalId = 1\n\nfunction buildCostParam(costs: PhysicalCostMap, param: PhyscialCostParam) {\n  param.id = globalId++\n  if (param.params === null) {\n    // if null, means this cost is a PhysicalCostRoot\n    const root = costs[param.name]\n    if (!root) {\n      throw new Error(`cost for ${param.name} not exist`)\n    }\n    param.params = root.params\n    // nested operator desc may be not correct, let's fix it by root.desc\n    param.desc = root.desc\n  }\n  if (param.params === undefined) {\n    // reach leaf node\n    return\n  }\n  // traverse\n  buildCostParams(costs, param.params)\n}\n\nfunction buildCostParams(\n  costs: PhysicalCostMap,\n  params: { [x: string]: number | PhyscialCostParam }\n) {\n  Object.keys(params).forEach((k) => {\n    const v = params[k]\n    if (typeof v === 'number') {\n      // convert the leaf node\n      // its orignal type is number\n      // convert to leaf PhysicalCostParam with `params: undefined`\n      params[k] = {\n        id: globalId++,\n        name: k,\n        desc: '',\n        params: undefined,\n        cost: v\n      }\n    } else {\n      buildCostParam(costs, v)\n    }\n  })\n}\n\nfunction buildCostTree(costs: PhysicalCostMap, root: PhysicalCostRoot) {\n  globalId = root.id + 1\n  buildCostParams(costs, root.params)\n}\n\n/////////////\n\ninterface BoolMap {\n  [x: string]: boolean\n}\n\ntype Expands = BoolMap\n\n/////////////\n\nfunction genGraphvizNodeParam(\n  param: PhyscialCostParam,\n  strArr: string[],\n  expands: Expands\n) {\n  let str = ''\n  if (param.params === undefined) {\n    // leaf node\n    str = `${param.id} ${createLabels({\n      label: `${param.name}\\n${param.cost.toFixed(4)}`,\n      fillcolor: '#cffafe',\n      tooltip: `${param.id}`\n    })};\\n`\n  } else {\n    str = `${param.id} ${createLabels({\n      label: `${param.name}\\ncost: ${param.cost.toFixed(4)}\\ndesc: ${\n        param.desc\n      }`,\n      fillcolor: 'white',\n      tooltip: `${param.id}`\n    })};\\n`\n  }\n  strArr.push(str)\n\n  if (param.params === null || param.params === undefined) {\n    return\n  }\n  if (expands[param.id] !== true) {\n    // not expand\n    return\n  }\n  genGraphvizNodeParams(param.params, strArr, expands)\n}\n\nfunction genGraphvizNodeParams(\n  params: { [x: string]: number | PhyscialCostParam },\n  strArr: string[],\n  expands: Expands\n) {\n  Object.values(params).forEach((p) => {\n    // number has already converted to PhyscialCostParam\n    // it doesn't exist alreay in fact\n    if (typeof p !== 'number') {\n      genGraphvizNodeParam(p, strArr, expands)\n    }\n  })\n}\n\nfunction genGraphvizNodes(root: PhysicalCostRoot, expands: Expands) {\n  const strArr: string[] = []\n\n  strArr.push(\n    `${root.id} ${createLabels({\n      label: `${root.type}_${root.id}\\ncost: ${root.cost.toFixed(4)}\\ndesc: ${\n        root.desc\n      }`,\n      fillcolor: 'white',\n      tooltip: `${root.id}`\n    })};\\n`\n  )\n  if (expands[root.id] === true) {\n    genGraphvizNodeParams(root.params, strArr, expands)\n  }\n\n  return strArr\n}\n\n//////////////////////\n\nfunction genGraphvizLineParam(\n  parentId: number,\n  param: PhyscialCostParam,\n  strArr: string[],\n  expands: Expands\n) {\n  if (expands[parentId] !== true) {\n    return\n  }\n\n  strArr.push(`${parentId} -> ${param.id};\\n`)\n\n  if (param.params === null || param.params === undefined) {\n    return\n  }\n\n  genGraphvizLineParams(param.id, param.params, strArr, expands)\n}\n\nfunction genGraphvizLineParams(\n  parentId: number,\n  params: { [x: string]: number | PhyscialCostParam },\n  strArr: string[],\n  expands: Expands\n) {\n  Object.values(params).forEach((p) => {\n    // number has already converted to PhyscialCostParam\n    // it doesn't exist alreay in fact\n    if (typeof p !== 'number') {\n      genGraphvizLineParam(parentId, p, strArr, expands)\n    }\n  })\n}\n\nfunction genGraphvizLines(root: PhysicalCostRoot, expands: Expands) {\n  const strArr: string[] = []\n  genGraphvizLineParams(root.id, root.params, strArr, expands)\n  return strArr\n}\n\n//////////////////////\n\nexport default function PhysicalCostTree({\n  costs,\n  name,\n  className\n}: PhysicalCostTreeProps) {\n  const costRoot = useMemo(() => {\n    const root = costs[name]\n\n    if (root) {\n      buildCostTree(costs, root)\n    }\n\n    return root\n  }, [costs, name])\n\n  const [nodeExpands, setNodeExpands] = useState<Expands>({})\n\n  const containerRef = useRef<HTMLDivElement>(null)\n\n  useEffect(() => {\n    setNodeExpands({})\n  }, [name])\n\n  useEffect(() => {\n    if (!costRoot) {\n      return\n    }\n    const containerEl = containerRef.current\n    if (!containerEl) {\n      return\n    }\n\n    const define = genGraphvizNodes(costRoot, nodeExpands).join('')\n    const link = genGraphvizLines(costRoot, nodeExpands).join('')\n    graphviz(containerEl).renderDot(\n      `digraph {\n  node [shape=ellipse fontsize=8 fontname=\"Verdana\" style=\"filled\"];\n  ${define}\\n${link}\\n}`\n    )\n  }, [containerRef, costRoot, nodeExpands])\n\n  function handleClick(e) {\n    const trigger = e.target\n    const parent = e.target.parentNode\n    // find clicked node\n    if (\n      (trigger?.tagName === 'text' || trigger?.tagName === 'ellipse') &&\n      parent?.tagName === 'a'\n    ) {\n      console.log('title:', parent.getAttribute('title'))\n      const id = parent.getAttribute('title')\n\n      // toggle\n      setNodeExpands({\n        ...nodeExpands,\n        [id]: !(nodeExpands[id] ?? false)\n      })\n    }\n  }\n\n  return (\n    <div>\n      {costRoot ? (\n        <div\n          ref={containerRef}\n          className={`${styles.operator_tree} ${styles.cost_tree} ${\n            className || ''\n          }`}\n          onClick={handleClick}\n        ></div>\n      ) : (\n        <p>Not exist</p>\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/OptimizerTrace/components/PhysicalOperatorTree.tsx",
    "content": "import React, { useEffect, useRef } from 'react'\nimport { graphviz } from 'd3-graphviz'\nimport { FullscreenOutlined } from '@ant-design/icons'\n\nimport { LogicalOperatorNode, createLabels } from './LogicalOperatorTree'\n\nimport styles from './OperatorTree.module.less'\n\nexport interface PhysicalOperatorNode extends LogicalOperatorNode {\n  parentNode: null | PhysicalOperatorNode\n  childrenNodes: PhysicalOperatorNode[]\n  mapping: string\n}\n\ninterface PhysicalOperatorTreeProps {\n  data: PhysicalOperatorNode\n  className?: string\n  onSelect?: (name: string) => void\n  nodeName: string\n}\n\nfunction convertTreeToArry(\n  node: PhysicalOperatorNode,\n  arr: PhysicalOperatorNode[]\n) {\n  arr.push(node)\n  if (node.childrenNodes) {\n    node.childrenNodes.forEach((n) => convertTreeToArry(n, arr))\n  }\n}\n\nexport default function PhysicalOperatorTree({\n  data,\n  className,\n  onSelect,\n  nodeName\n}: PhysicalOperatorTreeProps) {\n  const containerRef = useRef<HTMLDivElement>(null)\n\n  useEffect(() => {\n    const containerEl = containerRef.current\n    if (!containerEl) {\n      return\n    }\n\n    let allDatas: PhysicalOperatorNode[] = []\n    convertTreeToArry(data, allDatas)\n    const define = allDatas\n      .map(\n        (n) =>\n          `${n.id} ${createLabels({\n            label: `${n.type}_${n.id}\\ncost: ${n.cost.toFixed(4)}`,\n            color: n.selected ? '#4169E1' : '',\n            fillcolor: `${n.type}_${n.id}` === nodeName ? '#7dd3fc' : 'white',\n            tooltip: `info: ${n.info}`\n          })};\\n`\n      )\n      .join('')\n    const link = allDatas\n      .map((n) =>\n        (n.children || [])\n          .map(\n            (c) =>\n              `${n.id} -> ${c} ${createLabels({\n                color: n.selected ? '#4169E1' : ''\n              })};\\n`\n          )\n          .join('')\n      )\n      .join('')\n\n    graphviz(containerEl).renderDot(\n      `digraph {\n  node [shape=ellipse fontsize=8 fontname=\"Verdana\" style=\"filled\"];\n  ${define}\\n${link}\\n}`\n    )\n  }, [containerRef, data, nodeName])\n\n  // find clicked node\n  function handleClick(e) {\n    const trigger = e.target\n    const parent = e.target.parentNode\n    if (\n      (trigger?.tagName === 'text' || trigger?.tagName === 'ellipse') &&\n      parent?.tagName === 'a'\n    ) {\n      for (const el of parent.children) {\n        if (el.tagName === 'text') {\n          onSelect?.(el.innerHTML)\n          break\n        }\n      }\n    }\n  }\n\n  return (\n    <div\n      ref={containerRef}\n      className={`${styles.operator_tree} ${className || ''}`}\n      onClick={handleClick}\n    ></div>\n  )\n}\n\nexport function PhysicalOperatorTreeWithFullScreen({\n  onFullScreen,\n  ...rest\n}: PhysicalOperatorTreeProps & {\n  onFullScreen: () => void\n}) {\n  return (\n    <div className={styles.tree_container}>\n      <div className={styles.fullscreen_icon_box}>\n        <FullscreenOutlined onClick={() => onFullScreen()} />\n      </div>\n      <PhysicalOperatorTree {...rest} />\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/OptimizerTrace/context/index.ts",
    "content": "import { createContext } from 'react'\n\n// import { AxiosPromise } from 'axios'\n\n// import {} from '@lib/client'\n\n// import { ReqConfig } from '@lib/types'\n\nexport interface IOptimizerTraceDataSource {}\n\nexport interface IOptimizerTraceContext {\n  ds: IOptimizerTraceDataSource\n}\n\nexport const OptimizerTraceContext =\n  createContext<IOptimizerTraceContext | null>(null)\n\nexport const OptimizerTraceProvider = OptimizerTraceContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/OptimizerTrace/examples/new-format.json",
    "content": "{\n  \"logical\": {\n    \"final\": [\n      {\n        \"type\": \"DataSource\",\n        \"property\": \"\",\n        \"info\": \"table:customer\",\n        \"children\": [],\n        \"id\": 1,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"DataSource\",\n        \"property\": \"\",\n        \"info\": \"table:orders\",\n        \"children\": [],\n        \"id\": 2,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"Join\",\n        \"property\": \"\",\n        \"info\": \"inner join, equal:[eq(test.customer.c_custkey, test.orders.o_custkey)]\",\n        \"children\": [1, 2],\n        \"id\": 12,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"DataSource\",\n        \"property\": \"\",\n        \"info\": \"table:lineitem\",\n        \"children\": [],\n        \"id\": 4,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"Join\",\n        \"property\": \"\",\n        \"info\": \"inner join, equal:[eq(test.orders.o_orderkey, test.lineitem.l_orderkey)]\",\n        \"children\": [12, 4],\n        \"id\": 13,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"Aggregation\",\n        \"property\": \"\",\n        \"info\": \"group by:test.lineitem.l_orderkey, test.orders.o_orderdate, test.orders.o_shippriority, funcs:sum(mul(test.lineitem.l_extendedprice, minus(1, test.lineitem.l_discount))), firstrow(test.orders.o_orderdate), firstrow(test.orders.o_shippriority), firstrow(test.lineitem.l_orderkey)\",\n        \"children\": [13],\n        \"id\": 7,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"TopN\",\n        \"property\": \"\",\n        \"info\": \"Column#35:desc, test.orders.o_orderdate, offset:0, count:10\",\n        \"children\": [7],\n        \"id\": 11,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"Projection\",\n        \"property\": \"\",\n        \"info\": \"test.lineitem.l_orderkey, Column#35, test.orders.o_orderdate, test.orders.o_shippriority\",\n        \"children\": [11],\n        \"id\": 8,\n        \"cost\": 0,\n        \"selected\": false\n      }\n    ],\n    \"steps\": [\n      {\n        \"name\": \"column_prune\",\n        \"before\": [\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:customer\",\n            \"children\": [],\n            \"id\": 1,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:orders\",\n            \"children\": [],\n            \"id\": 2,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Join\",\n            \"property\": \"\",\n            \"info\": \"inner join\",\n            \"children\": [1, 2],\n            \"id\": 3,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:lineitem\",\n            \"children\": [],\n            \"id\": 4,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Join\",\n            \"property\": \"\",\n            \"info\": \"inner join\",\n            \"children\": [3, 4],\n            \"id\": 5,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Selection\",\n            \"property\": \"\",\n            \"info\": \"eq(test.customer.c_custkey, test.orders.o_custkey), eq(test.customer.c_mktsegment, 'AUTOMOBILE'), eq(test.lineitem.l_orderkey, test.orders.o_orderkey), gt(test.lineitem.l_shipdate, 1995-03-13 00:00:00.000000), lt(test.orders.o_orderdate, 1995-03-13 00:00:00.000000)\",\n            \"children\": [5],\n            \"id\": 6,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Aggregation\",\n            \"property\": \"\",\n            \"info\": \"group by:test.lineitem.l_orderkey, test.orders.o_orderdate, test.orders.o_shippriority, funcs:sum(mul(test.lineitem.l_extendedprice, minus(1, test.lineitem.l_discount))), firstrow(test.customer.c_custkey), firstrow(test.customer.c_name), firstrow(test.customer.c_address), firstrow(test.customer.c_nationkey), firstrow(test.customer.c_phone), firstrow(test.customer.c_acctbal), firstrow(test.customer.c_mktsegment), firstrow(test.customer.c_comment), firstrow(test.orders.o_orderkey), firstrow(test.orders.o_custkey), firstrow(test.orders.o_orderstatus), firstrow(test.orders.o_totalprice), firstrow(test.orders.o_orderdate), firstrow(test.orders.o_orderpriority), firstrow(test.orders.o_clerk), firstrow(test.orders.o_shippriority), firstrow(test.orders.o_comment), firstrow(test.lineitem.l_orderkey), firstrow(test.lineitem.l_partkey), firstrow(test.lineitem.l_suppkey), firstrow(test.lineitem.l_linenumber), firstrow(test.lineitem.l_quantity), firstrow(test.lineitem.l_extendedprice), firstrow(test.lineitem.l_discount), firstrow(test.lineitem.l_tax), firstrow(test.lineitem.l_returnflag), firstrow(test.lineitem.l_linestatus), firstrow(test.lineitem.l_shipdate), firstrow(test.lineitem.l_commitdate), firstrow(test.lineitem.l_receiptdate), firstrow(test.lineitem.l_shipinstruct), firstrow(test.lineitem.l_shipmode), firstrow(test.lineitem.l_comment), firstrow(test.lineitem._tidb_rowid)\",\n            \"children\": [6],\n            \"id\": 7,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Projection\",\n            \"property\": \"\",\n            \"info\": \"test.lineitem.l_orderkey, Column#35, test.orders.o_orderdate, test.orders.o_shippriority\",\n            \"children\": [7],\n            \"id\": 8,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Sort\",\n            \"property\": \"\",\n            \"info\": \"Column#35:desc, test.orders.o_orderdate\",\n            \"children\": [8],\n            \"id\": 9,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Limit\",\n            \"property\": \"\",\n            \"info\": \"offset:0, count:10\",\n            \"children\": [9],\n            \"id\": 10,\n            \"cost\": 0,\n            \"selected\": false\n          }\n        ],\n        \"steps\": [\n          {\n            \"action\": \"Aggregation_7's columns[test.lineitem._tidb_rowid,test.lineitem.l_comment,test.lineitem.l_shipmode,test.lineitem.l_shipinstruct,test.lineitem.l_receiptdate,test.lineitem.l_commitdate,test.lineitem.l_shipdate,test.lineitem.l_linestatus,test.lineitem.l_returnflag,test.lineitem.l_tax,test.lineitem.l_discount,test.lineitem.l_extendedprice,test.lineitem.l_quantity,test.lineitem.l_linenumber,test.lineitem.l_suppkey,test.lineitem.l_partkey,test.orders.o_comment,test.orders.o_clerk,test.orders.o_orderpriority,test.orders.o_totalprice,test.orders.o_orderstatus,test.orders.o_custkey,test.orders.o_orderkey,test.customer.c_comment,test.customer.c_mktsegment,test.customer.c_acctbal,test.customer.c_phone,test.customer.c_nationkey,test.customer.c_address,test.customer.c_name,test.customer.c_custkey] have been pruned\",\n            \"reason\": \"\",\n            \"type\": \"Aggregation\",\n            \"id\": 7,\n            \"index\": 0\n          },\n          {\n            \"action\": \"Aggregation_7's aggregation functions[firstrow(test.lineitem._tidb_rowid),firstrow(test.lineitem.l_comment),firstrow(test.lineitem.l_shipmode),firstrow(test.lineitem.l_shipinstruct),firstrow(test.lineitem.l_receiptdate),firstrow(test.lineitem.l_commitdate),firstrow(test.lineitem.l_shipdate),firstrow(test.lineitem.l_linestatus),firstrow(test.lineitem.l_returnflag),firstrow(test.lineitem.l_tax),firstrow(test.lineitem.l_discount),firstrow(test.lineitem.l_extendedprice),firstrow(test.lineitem.l_quantity),firstrow(test.lineitem.l_linenumber),firstrow(test.lineitem.l_suppkey),firstrow(test.lineitem.l_partkey),firstrow(test.orders.o_comment),firstrow(test.orders.o_clerk),firstrow(test.orders.o_orderpriority),firstrow(test.orders.o_totalprice),firstrow(test.orders.o_orderstatus),firstrow(test.orders.o_custkey),firstrow(test.orders.o_orderkey),firstrow(test.customer.c_comment),firstrow(test.customer.c_mktsegment),firstrow(test.customer.c_acctbal),firstrow(test.customer.c_phone),firstrow(test.customer.c_nationkey),firstrow(test.customer.c_address),firstrow(test.customer.c_name),firstrow(test.customer.c_custkey)] have been pruned\",\n            \"reason\": \"\",\n            \"type\": \"Aggregation\",\n            \"id\": 7,\n            \"index\": 1\n          },\n          {\n            \"action\": \"DataSource_1's columns[test.customer.c_comment,test.customer.c_acctbal,test.customer.c_phone,test.customer.c_nationkey,test.customer.c_address,test.customer.c_name] have been pruned\",\n            \"reason\": \"\",\n            \"type\": \"DataSource\",\n            \"id\": 1,\n            \"index\": 2\n          },\n          {\n            \"action\": \"DataSource_2's columns[test.orders.o_comment,test.orders.o_clerk,test.orders.o_orderpriority,test.orders.o_totalprice,test.orders.o_orderstatus] have been pruned\",\n            \"reason\": \"\",\n            \"type\": \"DataSource\",\n            \"id\": 2,\n            \"index\": 3\n          },\n          {\n            \"action\": \"DataSource_4's columns[test.lineitem._tidb_rowid,test.lineitem.l_comment,test.lineitem.l_shipmode,test.lineitem.l_shipinstruct,test.lineitem.l_receiptdate,test.lineitem.l_commitdate,test.lineitem.l_linestatus,test.lineitem.l_returnflag,test.lineitem.l_tax,test.lineitem.l_quantity,test.lineitem.l_linenumber,test.lineitem.l_suppkey,test.lineitem.l_partkey] have been pruned\",\n            \"reason\": \"\",\n            \"type\": \"DataSource\",\n            \"id\": 4,\n            \"index\": 4\n          }\n        ],\n        \"index\": 1\n      },\n      {\n        \"name\": \"predicate_push_down\",\n        \"before\": [\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:customer\",\n            \"children\": [],\n            \"id\": 1,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:orders\",\n            \"children\": [],\n            \"id\": 2,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Join\",\n            \"property\": \"\",\n            \"info\": \"inner join\",\n            \"children\": [1, 2],\n            \"id\": 3,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:lineitem\",\n            \"children\": [],\n            \"id\": 4,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Join\",\n            \"property\": \"\",\n            \"info\": \"inner join\",\n            \"children\": [3, 4],\n            \"id\": 5,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Selection\",\n            \"property\": \"\",\n            \"info\": \"eq(test.customer.c_custkey, test.orders.o_custkey), eq(test.customer.c_mktsegment, 'AUTOMOBILE'), eq(test.lineitem.l_orderkey, test.orders.o_orderkey), gt(test.lineitem.l_shipdate, 1995-03-13 00:00:00.000000), lt(test.orders.o_orderdate, 1995-03-13 00:00:00.000000)\",\n            \"children\": [5],\n            \"id\": 6,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Aggregation\",\n            \"property\": \"\",\n            \"info\": \"group by:test.lineitem.l_orderkey, test.orders.o_orderdate, test.orders.o_shippriority, funcs:sum(mul(test.lineitem.l_extendedprice, minus(1, test.lineitem.l_discount))), firstrow(test.orders.o_orderdate), firstrow(test.orders.o_shippriority), firstrow(test.lineitem.l_orderkey)\",\n            \"children\": [6],\n            \"id\": 7,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Projection\",\n            \"property\": \"\",\n            \"info\": \"test.lineitem.l_orderkey, Column#35, test.orders.o_orderdate, test.orders.o_shippriority\",\n            \"children\": [7],\n            \"id\": 8,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Sort\",\n            \"property\": \"\",\n            \"info\": \"Column#35:desc, test.orders.o_orderdate\",\n            \"children\": [8],\n            \"id\": 9,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Limit\",\n            \"property\": \"\",\n            \"info\": \"offset:0, count:10\",\n            \"children\": [9],\n            \"id\": 10,\n            \"cost\": 0,\n            \"selected\": false\n          }\n        ],\n        \"steps\": [\n          {\n            \"action\": \"The conditions[eq(test.customer.c_mktsegment, AUTOMOBILE)] are pushed down across DataSource_1\",\n            \"reason\": \"\",\n            \"type\": \"DataSource\",\n            \"id\": 1,\n            \"index\": 0\n          },\n          {\n            \"action\": \"The conditions[lt(test.orders.o_orderdate, 1995-03-13 00:00:00.000000)] are pushed down across DataSource_2\",\n            \"reason\": \"\",\n            \"type\": \"DataSource\",\n            \"id\": 2,\n            \"index\": 1\n          },\n          {\n            \"action\": \"The conditions[gt(test.lineitem.l_shipdate, 1995-03-13 00:00:00.000000)] are pushed down across DataSource_4\",\n            \"reason\": \"\",\n            \"type\": \"DataSource\",\n            \"id\": 4,\n            \"index\": 2\n          },\n          {\n            \"action\": \"Selection_6 is removed\",\n            \"reason\": \"The conditions[eq(test.customer.c_mktsegment, AUTOMOBILE),eq(test.customer.c_custkey, test.orders.o_custkey),eq(test.lineitem.l_orderkey, test.orders.o_orderkey),lt(test.orders.o_orderdate, 1995-03-13 00:00:00.000000),gt(test.lineitem.l_shipdate, 1995-03-13 00:00:00.000000)] in Selection_6 are pushed down\",\n            \"type\": \"Selection\",\n            \"id\": 6,\n            \"index\": 3\n          }\n        ],\n        \"index\": 10\n      },\n      {\n        \"name\": \"topn_push_down\",\n        \"before\": [\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:customer\",\n            \"children\": [],\n            \"id\": 1,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:orders\",\n            \"children\": [],\n            \"id\": 2,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Join\",\n            \"property\": \"\",\n            \"info\": \"inner join, equal:[eq(test.customer.c_custkey, test.orders.o_custkey)]\",\n            \"children\": [1, 2],\n            \"id\": 3,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:lineitem\",\n            \"children\": [],\n            \"id\": 4,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Join\",\n            \"property\": \"\",\n            \"info\": \"inner join, equal:[eq(test.orders.o_orderkey, test.lineitem.l_orderkey)]\",\n            \"children\": [3, 4],\n            \"id\": 5,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Aggregation\",\n            \"property\": \"\",\n            \"info\": \"group by:test.lineitem.l_orderkey, test.orders.o_orderdate, test.orders.o_shippriority, funcs:sum(mul(test.lineitem.l_extendedprice, minus(1, test.lineitem.l_discount))), firstrow(test.orders.o_orderdate), firstrow(test.orders.o_shippriority), firstrow(test.lineitem.l_orderkey)\",\n            \"children\": [5],\n            \"id\": 7,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Projection\",\n            \"property\": \"\",\n            \"info\": \"test.lineitem.l_orderkey, Column#35, test.orders.o_orderdate, test.orders.o_shippriority\",\n            \"children\": [7],\n            \"id\": 8,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Sort\",\n            \"property\": \"\",\n            \"info\": \"Column#35:desc, test.orders.o_orderdate\",\n            \"children\": [8],\n            \"id\": 9,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Limit\",\n            \"property\": \"\",\n            \"info\": \"offset:0, count:10\",\n            \"children\": [9],\n            \"id\": 10,\n            \"cost\": 0,\n            \"selected\": false\n          }\n        ],\n        \"steps\": [\n          {\n            \"action\": \"Limit_10 is converted into TopN_11\",\n            \"reason\": \"\",\n            \"type\": \"TopN\",\n            \"id\": 11,\n            \"index\": 0\n          },\n          {\n            \"action\": \"Sort_9 passes ByItems[Column#35 true,test.orders.o_orderdate] to TopN_11\",\n            \"reason\": \"TopN_11 is Limit originally\",\n            \"type\": \"Sort\",\n            \"id\": 9,\n            \"index\": 1\n          },\n          {\n            \"action\": \"TopN_11 is added as Aggregation_7's parent\",\n            \"reason\": \"TopN is pushed down\",\n            \"type\": \"TopN\",\n            \"id\": 11,\n            \"index\": 2\n          }\n        ],\n        \"index\": 15\n      },\n      {\n        \"name\": \"join_reorder\",\n        \"before\": [\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:customer\",\n            \"children\": [],\n            \"id\": 1,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:orders\",\n            \"children\": [],\n            \"id\": 2,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Join\",\n            \"property\": \"\",\n            \"info\": \"inner join, equal:[eq(test.customer.c_custkey, test.orders.o_custkey)]\",\n            \"children\": [1, 2],\n            \"id\": 3,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:lineitem\",\n            \"children\": [],\n            \"id\": 4,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Join\",\n            \"property\": \"\",\n            \"info\": \"inner join, equal:[eq(test.orders.o_orderkey, test.lineitem.l_orderkey)]\",\n            \"children\": [3, 4],\n            \"id\": 5,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Aggregation\",\n            \"property\": \"\",\n            \"info\": \"group by:test.lineitem.l_orderkey, test.orders.o_orderdate, test.orders.o_shippriority, funcs:sum(mul(test.lineitem.l_extendedprice, minus(1, test.lineitem.l_discount))), firstrow(test.orders.o_orderdate), firstrow(test.orders.o_shippriority), firstrow(test.lineitem.l_orderkey)\",\n            \"children\": [5],\n            \"id\": 7,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"TopN\",\n            \"property\": \"\",\n            \"info\": \"Column#35:desc, test.orders.o_orderdate, offset:0, count:10\",\n            \"children\": [7],\n            \"id\": 11,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Projection\",\n            \"property\": \"\",\n            \"info\": \"test.lineitem.l_orderkey, Column#35, test.orders.o_orderdate, test.orders.o_shippriority\",\n            \"children\": [11],\n            \"id\": 8,\n            \"cost\": 0,\n            \"selected\": false\n          }\n        ],\n        \"steps\": [\n          {\n            \"action\": \"join order becomes ((customer*orders)*lineitem) from original ((customer*orders)*lineitem)\",\n            \"reason\": \"join cost during reorder: [[((customer*orders)*lineitem), cost:1.2551220900400399e+08],[(customer*orders), cost:2.504670299506237e+07],[customer, cost:7500],[lineitem, cost:1.00001937e+08],[orders, cost:2.4925e+07]]\",\n            \"type\": \"Projection\",\n            \"id\": 8,\n            \"index\": 0\n          }\n        ],\n        \"index\": 17\n      },\n      {\n        \"name\": \"column_prune\",\n        \"before\": [\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:customer\",\n            \"children\": [],\n            \"id\": 1,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:orders\",\n            \"children\": [],\n            \"id\": 2,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Join\",\n            \"property\": \"\",\n            \"info\": \"inner join, equal:[eq(test.customer.c_custkey, test.orders.o_custkey)]\",\n            \"children\": [1, 2],\n            \"id\": 12,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"DataSource\",\n            \"property\": \"\",\n            \"info\": \"table:lineitem\",\n            \"children\": [],\n            \"id\": 4,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Join\",\n            \"property\": \"\",\n            \"info\": \"inner join, equal:[eq(test.orders.o_orderkey, test.lineitem.l_orderkey)]\",\n            \"children\": [12, 4],\n            \"id\": 13,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Aggregation\",\n            \"property\": \"\",\n            \"info\": \"group by:test.lineitem.l_orderkey, test.orders.o_orderdate, test.orders.o_shippriority, funcs:sum(mul(test.lineitem.l_extendedprice, minus(1, test.lineitem.l_discount))), firstrow(test.orders.o_orderdate), firstrow(test.orders.o_shippriority), firstrow(test.lineitem.l_orderkey)\",\n            \"children\": [13],\n            \"id\": 7,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"TopN\",\n            \"property\": \"\",\n            \"info\": \"Column#35:desc, test.orders.o_orderdate, offset:0, count:10\",\n            \"children\": [7],\n            \"id\": 11,\n            \"cost\": 0,\n            \"selected\": false\n          },\n          {\n            \"type\": \"Projection\",\n            \"property\": \"\",\n            \"info\": \"test.lineitem.l_orderkey, Column#35, test.orders.o_orderdate, test.orders.o_shippriority\",\n            \"children\": [11],\n            \"id\": 8,\n            \"cost\": 0,\n            \"selected\": false\n          }\n        ],\n        \"steps\": [\n          {\n            \"action\": \"Join_12's columns[test.orders.o_custkey,test.customer.c_mktsegment,test.customer.c_custkey] have been pruned\",\n            \"reason\": \"\",\n            \"type\": \"Join\",\n            \"id\": 12,\n            \"index\": 0\n          },\n          {\n            \"action\": \"Join_13's columns[test.lineitem.l_shipdate,test.orders.o_orderkey] have been pruned\",\n            \"reason\": \"\",\n            \"type\": \"Join\",\n            \"id\": 13,\n            \"index\": 1\n          }\n        ],\n        \"index\": 18\n      }\n    ]\n  },\n  \"physical\": {\n    \"costs\": {\n      \"HashAgg_22\": {\n        \"params\": {\n          \"IndexJoin_28\": {\n            \"name\": \"IndexJoin_28\",\n            \"cost\": 14953946825.125187,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"aggCost\": {\n            \"name\": \"aggCost\",\n            \"cost\": 97967583.88966256,\n            \"params\": {\n              \"aggTotalCost\": {\n                \"name\": \"aggTotalCost\",\n                \"cost\": 293902751.6689877,\n                \"params\": {\n                  \"aggCost\": {\n                    \"name\": \"aggCost\",\n                    \"cost\": 55628281.07299452,\n                    \"params\": {\n                      \"aggNum\": 4,\n                      \"cpuFactor\": 30,\n                      \"rows\": 463569.008941621\n                    },\n                    \"desc\": \"rows*aggNum*cpuFactor\"\n                  },\n                  \"groupCost\": {\n                    \"name\": \"groupCost\",\n                    \"cost\": 41721210.80474589,\n                    \"params\": {\n                      \"cpuFactor\": 30,\n                      \"groupItemNum\": 3,\n                      \"rows\": 463569.008941621\n                    },\n                    \"desc\": \"rows*groupItemNum*cpuFactor\"\n                  },\n                  \"hashBuildCost\": {\n                    \"name\": \"hashBuildCost\",\n                    \"cost\": 113110838.18175551,\n                    \"params\": {\n                      \"hashBuildCost\": {\n                        \"name\": \"hashBuildCost\",\n                        \"cost\": 41721210.80474589,\n                        \"params\": {\n                          \"buildRows\": 463569.008941621,\n                          \"cpuFactor\": 30,\n                          \"keyLengths\": 3\n                        },\n                        \"desc\": \"buildRows*keyLengths*cpuFactor\"\n                      },\n                      \"hashKeyCost\": {\n                        \"name\": \"hashKeyCost\",\n                        \"cost\": 41721210.80474589,\n                        \"params\": {\n                          \"buildRows\": 463569.008941621,\n                          \"cpuFactor\": 30,\n                          \"keyLengths\": 3\n                        },\n                        \"desc\": \"buildRows*keyLengths*cpuFactor\"\n                      },\n                      \"hashMemCost\": {\n                        \"name\": \"hashMemCost\",\n                        \"cost\": 29668416.572263744,\n                        \"params\": {\n                          \"buildRowSize\": 64,\n                          \"buildRows\": 463569.008941621,\n                          \"memFactor\": 1\n                        },\n                        \"desc\": \"buildRows*buildRowSize*memFactor\"\n                      }\n                    },\n                    \"desc\": \"(hashKeyCost) + (hashMemCost) + (hashBuildCost)\"\n                  },\n                  \"hashProbeCost\": {\n                    \"name\": \"hashProbeCost\",\n                    \"cost\": 83442421.60949178,\n                    \"params\": {\n                      \"hashKeyCost\": {\n                        \"name\": \"hashKeyCost\",\n                        \"cost\": 41721210.80474589,\n                        \"params\": {\n                          \"cpuFactor\": 30,\n                          \"keyLength\": 3,\n                          \"probeRows\": 463569.008941621\n                        },\n                        \"desc\": \"probeRows*keyLength*cpuFactor\"\n                      },\n                      \"hashProbeCost\": {\n                        \"name\": \"hashProbeCost\",\n                        \"cost\": 41721210.80474589,\n                        \"params\": {\n                          \"cpuFactor\": 30,\n                          \"keyLength\": 3,\n                          \"probeRows\": 463569.008941621\n                        },\n                        \"desc\": \"probeRows*keyLength*cpuFactor\"\n                      }\n                    },\n                    \"desc\": \"(hashKeyCost) + (hashProbeCost)\"\n                  }\n                },\n                \"desc\": \"(aggCost) + (groupCost) + (hashBuildCost) + (hashProbeCost)\"\n              },\n              \"concurrencyFactor\": 3\n            },\n            \"desc\": \"(aggTotalCost)/concurrencyFactor\"\n          }\n        },\n        \"type\": \"HashAgg\",\n        \"desc\": \"(IndexJoin_28) + (aggCost)\",\n        \"id\": 22,\n        \"cost\": 15051914409.014849\n      },\n      \"HashJoin_38\": {\n        \"params\": {\n          \"HashJoin_70\": {\n            \"name\": \"HashJoin_70\",\n            \"cost\": 5274198010.608428,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"TableReader_79\": {\n            \"name\": \"TableReader_79\",\n            \"cost\": 22387827472.21748,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"buildFilterCost\": {\n            \"name\": \"buildFilterCost\",\n            \"cost\": 0,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 0, \"rows\": 100001937 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          },\n          \"buildHashCost\": {\n            \"name\": \"buildHashCost\",\n            \"cost\": 18800364156,\n            \"params\": {\n              \"hashBuildCost\": {\n                \"name\": \"hashBuildCost\",\n                \"cost\": 3000058110,\n                \"params\": {\n                  \"buildRows\": 100001937,\n                  \"cpuFactor\": 30,\n                  \"keyLengths\": 1\n                },\n                \"desc\": \"buildRows*keyLengths*cpuFactor\"\n              },\n              \"hashKeyCost\": {\n                \"name\": \"hashKeyCost\",\n                \"cost\": 3000058110,\n                \"params\": {\n                  \"buildRows\": 100001937,\n                  \"cpuFactor\": 30,\n                  \"keyLengths\": 1\n                },\n                \"desc\": \"buildRows*keyLengths*cpuFactor\"\n              },\n              \"hashMemCost\": {\n                \"name\": \"hashMemCost\",\n                \"cost\": 12800247936,\n                \"params\": {\n                  \"buildRowSize\": 128,\n                  \"buildRows\": 100001937,\n                  \"memFactor\": 1\n                },\n                \"desc\": \"buildRows*buildRowSize*memFactor\"\n              }\n            },\n            \"desc\": \"(hashKeyCost) + (hashMemCost) + (hashBuildCost)\"\n          },\n          \"probeCost\": {\n            \"name\": \"probeCost\",\n            \"cost\": 1370435.9407484406,\n            \"params\": {\n              \"hashJoinConcurrency\": 5,\n              \"probeTotalCost\": {\n                \"name\": \"probeTotalCost\",\n                \"cost\": 6852179.703742203,\n                \"params\": {\n                  \"probeFilterCost\": {\n                    \"name\": \"probeFilterCost\",\n                    \"cost\": 0,\n                    \"params\": {\n                      \"cpuFactor\": 30,\n                      \"filters\": 0,\n                      \"rows\": 114202.99506237006\n                    },\n                    \"desc\": \"rows*filters*cpuFactor\"\n                  },\n                  \"probeHashCost\": {\n                    \"name\": \"probeHashCost\",\n                    \"cost\": 6852179.703742203,\n                    \"params\": {\n                      \"hashKeyCost\": {\n                        \"name\": \"hashKeyCost\",\n                        \"cost\": 3426089.8518711017,\n                        \"params\": {\n                          \"cpuFactor\": 30,\n                          \"keyLength\": 1,\n                          \"probeRows\": 114202.99506237006\n                        },\n                        \"desc\": \"probeRows*keyLength*cpuFactor\"\n                      },\n                      \"hashProbeCost\": {\n                        \"name\": \"hashProbeCost\",\n                        \"cost\": 3426089.8518711017,\n                        \"params\": {\n                          \"cpuFactor\": 30,\n                          \"keyLength\": 1,\n                          \"probeRows\": 114202.99506237006\n                        },\n                        \"desc\": \"probeRows*keyLength*cpuFactor\"\n                      }\n                    },\n                    \"desc\": \"(hashKeyCost) + (hashProbeCost)\"\n                  }\n                },\n                \"desc\": \"(probeFilterCost) + (probeHashCost)\"\n              }\n            },\n            \"desc\": \"(probeTotalCost)/hashJoinConcurrency\"\n          }\n        },\n        \"type\": \"HashJoin\",\n        \"desc\": \"(TableReader_79) + (HashJoin_70) + (buildHashCost) + (buildFilterCost) + (probeCost)\",\n        \"id\": 38,\n        \"cost\": 46463760074.76666\n      },\n      \"HashJoin_39\": {\n        \"params\": {\n          \"HashJoin_70\": {\n            \"name\": \"HashJoin_70\",\n            \"cost\": 5274198010.608428,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"TableReader_79\": {\n            \"name\": \"TableReader_79\",\n            \"cost\": 22387827472.21748,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"buildFilterCost\": {\n            \"name\": \"buildFilterCost\",\n            \"cost\": 0,\n            \"params\": {\n              \"cpuFactor\": 30,\n              \"filters\": 0,\n              \"rows\": 114202.99506237006\n            },\n            \"desc\": \"rows*filters*cpuFactor\"\n          },\n          \"buildHashCost\": {\n            \"name\": \"buildHashCost\",\n            \"cost\": 9593051.585239084,\n            \"params\": {\n              \"hashBuildCost\": {\n                \"name\": \"hashBuildCost\",\n                \"cost\": 3426089.8518711017,\n                \"params\": {\n                  \"buildRows\": 114202.99506237006,\n                  \"cpuFactor\": 30,\n                  \"keyLengths\": 1\n                },\n                \"desc\": \"buildRows*keyLengths*cpuFactor\"\n              },\n              \"hashKeyCost\": {\n                \"name\": \"hashKeyCost\",\n                \"cost\": 3426089.8518711017,\n                \"params\": {\n                  \"buildRows\": 114202.99506237006,\n                  \"cpuFactor\": 30,\n                  \"keyLengths\": 1\n                },\n                \"desc\": \"buildRows*keyLengths*cpuFactor\"\n              },\n              \"hashMemCost\": {\n                \"name\": \"hashMemCost\",\n                \"cost\": 2740871.881496881,\n                \"params\": {\n                  \"buildRowSize\": 24,\n                  \"buildRows\": 114202.99506237006,\n                  \"memFactor\": 1\n                },\n                \"desc\": \"buildRows*buildRowSize*memFactor\"\n              }\n            },\n            \"desc\": \"(hashKeyCost) + (hashMemCost) + (hashBuildCost)\"\n          },\n          \"probeCost\": {\n            \"name\": \"probeCost\",\n            \"cost\": 1200023244,\n            \"params\": {\n              \"hashJoinConcurrency\": 5,\n              \"probeTotalCost\": {\n                \"name\": \"probeTotalCost\",\n                \"cost\": 6000116220,\n                \"params\": {\n                  \"probeFilterCost\": {\n                    \"name\": \"probeFilterCost\",\n                    \"cost\": 0,\n                    \"params\": {\n                      \"cpuFactor\": 30,\n                      \"filters\": 0,\n                      \"rows\": 100001937\n                    },\n                    \"desc\": \"rows*filters*cpuFactor\"\n                  },\n                  \"probeHashCost\": {\n                    \"name\": \"probeHashCost\",\n                    \"cost\": 6000116220,\n                    \"params\": {\n                      \"hashKeyCost\": {\n                        \"name\": \"hashKeyCost\",\n                        \"cost\": 3000058110,\n                        \"params\": {\n                          \"cpuFactor\": 30,\n                          \"keyLength\": 1,\n                          \"probeRows\": 100001937\n                        },\n                        \"desc\": \"probeRows*keyLength*cpuFactor\"\n                      },\n                      \"hashProbeCost\": {\n                        \"name\": \"hashProbeCost\",\n                        \"cost\": 3000058110,\n                        \"params\": {\n                          \"cpuFactor\": 30,\n                          \"keyLength\": 1,\n                          \"probeRows\": 100001937\n                        },\n                        \"desc\": \"probeRows*keyLength*cpuFactor\"\n                      }\n                    },\n                    \"desc\": \"(hashKeyCost) + (hashProbeCost)\"\n                  }\n                },\n                \"desc\": \"(probeFilterCost) + (probeHashCost)\"\n              }\n            },\n            \"desc\": \"(probeTotalCost)/hashJoinConcurrency\"\n          }\n        },\n        \"type\": \"HashJoin\",\n        \"desc\": \"(HashJoin_70) + (TableReader_79) + (buildHashCost) + (buildFilterCost) + (probeCost)\",\n        \"id\": 39,\n        \"cost\": 28871641778.411148\n      },\n      \"HashJoin_69\": {\n        \"params\": {\n          \"TableReader_73\": {\n            \"name\": \"TableReader_73\",\n            \"cost\": 4568485875.962565,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"TableReader_76\": {\n            \"name\": \"TableReader_76\",\n            \"cost\": 405932034.6458629,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"buildFilterCost\": {\n            \"name\": \"buildFilterCost\",\n            \"cost\": 0,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 0, \"rows\": 24925000 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          },\n          \"buildHashCost\": {\n            \"name\": \"buildHashCost\",\n            \"cost\": 3090700000,\n            \"params\": {\n              \"hashBuildCost\": {\n                \"name\": \"hashBuildCost\",\n                \"cost\": 747750000,\n                \"params\": {\n                  \"buildRows\": 24925000,\n                  \"cpuFactor\": 30,\n                  \"keyLengths\": 1\n                },\n                \"desc\": \"buildRows*keyLengths*cpuFactor\"\n              },\n              \"hashKeyCost\": {\n                \"name\": \"hashKeyCost\",\n                \"cost\": 747750000,\n                \"params\": {\n                  \"buildRows\": 24925000,\n                  \"cpuFactor\": 30,\n                  \"keyLengths\": 1\n                },\n                \"desc\": \"buildRows*keyLengths*cpuFactor\"\n              },\n              \"hashMemCost\": {\n                \"name\": \"hashMemCost\",\n                \"cost\": 1595200000,\n                \"params\": {\n                  \"buildRowSize\": 64,\n                  \"buildRows\": 24925000,\n                  \"memFactor\": 1\n                },\n                \"desc\": \"buildRows*buildRowSize*memFactor\"\n              }\n            },\n            \"desc\": \"(hashKeyCost) + (hashMemCost) + (hashBuildCost)\"\n          },\n          \"probeCost\": {\n            \"name\": \"probeCost\",\n            \"cost\": 90000,\n            \"params\": {\n              \"hashJoinConcurrency\": 5,\n              \"probeTotalCost\": {\n                \"name\": \"probeTotalCost\",\n                \"cost\": 450000,\n                \"params\": {\n                  \"probeFilterCost\": {\n                    \"name\": \"probeFilterCost\",\n                    \"cost\": 0,\n                    \"params\": { \"cpuFactor\": 30, \"filters\": 0, \"rows\": 7500 },\n                    \"desc\": \"rows*filters*cpuFactor\"\n                  },\n                  \"probeHashCost\": {\n                    \"name\": \"probeHashCost\",\n                    \"cost\": 450000,\n                    \"params\": {\n                      \"hashKeyCost\": {\n                        \"name\": \"hashKeyCost\",\n                        \"cost\": 225000,\n                        \"params\": {\n                          \"cpuFactor\": 30,\n                          \"keyLength\": 1,\n                          \"probeRows\": 7500\n                        },\n                        \"desc\": \"probeRows*keyLength*cpuFactor\"\n                      },\n                      \"hashProbeCost\": {\n                        \"name\": \"hashProbeCost\",\n                        \"cost\": 225000,\n                        \"params\": {\n                          \"cpuFactor\": 30,\n                          \"keyLength\": 1,\n                          \"probeRows\": 7500\n                        },\n                        \"desc\": \"probeRows*keyLength*cpuFactor\"\n                      }\n                    },\n                    \"desc\": \"(hashKeyCost) + (hashProbeCost)\"\n                  }\n                },\n                \"desc\": \"(probeFilterCost) + (probeHashCost)\"\n              }\n            },\n            \"desc\": \"(probeTotalCost)/hashJoinConcurrency\"\n          }\n        },\n        \"type\": \"HashJoin\",\n        \"desc\": \"(TableReader_73) + (TableReader_76) + (buildHashCost) + (buildFilterCost) + (probeCost)\",\n        \"id\": 69,\n        \"cost\": 8065207910.608428\n      },\n      \"HashJoin_70\": {\n        \"params\": {\n          \"TableReader_73\": {\n            \"name\": \"TableReader_73\",\n            \"cost\": 4568485875.962565,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"TableReader_76\": {\n            \"name\": \"TableReader_76\",\n            \"cost\": 405932034.6458629,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"buildFilterCost\": {\n            \"name\": \"buildFilterCost\",\n            \"cost\": 0,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 0, \"rows\": 7500 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          },\n          \"buildHashCost\": {\n            \"name\": \"buildHashCost\",\n            \"cost\": 680100,\n            \"params\": {\n              \"hashBuildCost\": {\n                \"name\": \"hashBuildCost\",\n                \"cost\": 225000,\n                \"params\": {\n                  \"buildRows\": 7500,\n                  \"cpuFactor\": 30,\n                  \"keyLengths\": 1\n                },\n                \"desc\": \"buildRows*keyLengths*cpuFactor\"\n              },\n              \"hashKeyCost\": {\n                \"name\": \"hashKeyCost\",\n                \"cost\": 225000,\n                \"params\": {\n                  \"buildRows\": 7500,\n                  \"cpuFactor\": 30,\n                  \"keyLengths\": 1\n                },\n                \"desc\": \"buildRows*keyLengths*cpuFactor\"\n              },\n              \"hashMemCost\": {\n                \"name\": \"hashMemCost\",\n                \"cost\": 230100,\n                \"params\": {\n                  \"buildRowSize\": 30.68,\n                  \"buildRows\": 7500,\n                  \"memFactor\": 1\n                },\n                \"desc\": \"buildRows*buildRowSize*memFactor\"\n              }\n            },\n            \"desc\": \"(hashKeyCost) + (hashMemCost) + (hashBuildCost)\"\n          },\n          \"probeCost\": {\n            \"name\": \"probeCost\",\n            \"cost\": 299100000,\n            \"params\": {\n              \"hashJoinConcurrency\": 5,\n              \"probeTotalCost\": {\n                \"name\": \"probeTotalCost\",\n                \"cost\": 1495500000,\n                \"params\": {\n                  \"probeFilterCost\": {\n                    \"name\": \"probeFilterCost\",\n                    \"cost\": 0,\n                    \"params\": {\n                      \"cpuFactor\": 30,\n                      \"filters\": 0,\n                      \"rows\": 24925000\n                    },\n                    \"desc\": \"rows*filters*cpuFactor\"\n                  },\n                  \"probeHashCost\": {\n                    \"name\": \"probeHashCost\",\n                    \"cost\": 1495500000,\n                    \"params\": {\n                      \"hashKeyCost\": {\n                        \"name\": \"hashKeyCost\",\n                        \"cost\": 747750000,\n                        \"params\": {\n                          \"cpuFactor\": 30,\n                          \"keyLength\": 1,\n                          \"probeRows\": 24925000\n                        },\n                        \"desc\": \"probeRows*keyLength*cpuFactor\"\n                      },\n                      \"hashProbeCost\": {\n                        \"name\": \"hashProbeCost\",\n                        \"cost\": 747750000,\n                        \"params\": {\n                          \"cpuFactor\": 30,\n                          \"keyLength\": 1,\n                          \"probeRows\": 24925000\n                        },\n                        \"desc\": \"probeRows*keyLength*cpuFactor\"\n                      }\n                    },\n                    \"desc\": \"(hashKeyCost) + (hashProbeCost)\"\n                  }\n                },\n                \"desc\": \"(probeFilterCost) + (probeHashCost)\"\n              }\n            },\n            \"desc\": \"(probeTotalCost)/hashJoinConcurrency\"\n          }\n        },\n        \"type\": \"HashJoin\",\n        \"desc\": \"(TableReader_76) + (TableReader_73) + (buildHashCost) + (buildFilterCost) + (probeCost)\",\n        \"id\": 70,\n        \"cost\": 5274198010.608428\n      },\n      \"IndexFullScan_53\": {\n        \"params\": { \"rowSize\": 46, \"rows\": 300005811, \"scanFactor\": 100 },\n        \"type\": \"IndexFullScan\",\n        \"desc\": \"rows*log(rowSize)*scanFactor\",\n        \"id\": 53,\n        \"cost\": 165710068423.56305\n      },\n      \"IndexHashJoin_30\": {\n        \"params\": {\n          \"HashJoin_70\": {\n            \"name\": \"HashJoin_70\",\n            \"cost\": 5274198010.608428,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"buildFilterCost\": {\n            \"name\": \"buildFilterCost\",\n            \"cost\": 0,\n            \"params\": {\n              \"cpuFactor\": 30,\n              \"filters\": 0,\n              \"rows\": 114202.99506237006\n            },\n            \"desc\": \"rows*filters*cpuFactor\"\n          },\n          \"probeCost\": {\n            \"name\": \"probeCost\",\n            \"cost\": 9679748814.516758,\n            \"params\": {\n              \"indexLookupJoinConcurrency\": 5,\n              \"probeTotalCost\": {\n                \"name\": \"probeTotalCost\",\n                \"cost\": 48398744072.583786,\n                \"params\": {\n                  \"probeFilterCost\": {\n                    \"name\": \"probeFilterCost\",\n                    \"cost\": 0,\n                    \"params\": {\n                      \"cpuFactor\": 30,\n                      \"filters\": 0,\n                      \"rows\": 463569.00894162105\n                    },\n                    \"desc\": \"rows*filters*cpuFactor\"\n                  },\n                  \"probeReadCost\": {\n                    \"name\": \"probeReadCost\",\n                    \"cost\": 48398744072.583786,\n                    \"params\": {\n                      \"IndexLookUp_27\": {\n                        \"name\": \"IndexLookUp_27\",\n                        \"cost\": 12713872.533592913,\n                        \"params\": null,\n                        \"desc\": \"\"\n                      },\n                      \"batchRatio\": 30,\n                      \"buildRows\": 114202.99506237006\n                    },\n                    \"desc\": \"IndexLookUp_27*buildRows/batchRatio\"\n                  }\n                },\n                \"desc\": \"(probeReadCost) + (probeFilterCost)\"\n              }\n            },\n            \"desc\": \"(probeTotalCost)/indexLookupJoinConcurrency\"\n          }\n        },\n        \"type\": \"IndexHashJoin\",\n        \"desc\": \"(HashJoin_70) + (buildFilterCost) + (probeCost)\",\n        \"id\": 30,\n        \"cost\": 14953946825.125187\n      },\n      \"IndexHashJoin_48\": {\n        \"params\": {\n          \"TableReader_52\": {\n            \"name\": \"TableReader_52\",\n            \"cost\": 4568485875.962565,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"buildFilterCost\": {\n            \"name\": \"buildFilterCost\",\n            \"cost\": 0,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 0, \"rows\": 24925000 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          },\n          \"probeCost\": {\n            \"name\": \"probeCost\",\n            \"cost\": 2104786489666.7563,\n            \"params\": {\n              \"indexLookupJoinConcurrency\": 5,\n              \"probeTotalCost\": {\n                \"name\": \"probeTotalCost\",\n                \"cost\": 10523932448333.781,\n                \"params\": {\n                  \"probeFilterCost\": {\n                    \"name\": \"probeFilterCost\",\n                    \"cost\": 0,\n                    \"params\": { \"cpuFactor\": 30, \"filters\": 0, \"rows\": 24925 },\n                    \"desc\": \"rows*filters*cpuFactor\"\n                  },\n                  \"probeReadCost\": {\n                    \"name\": \"probeReadCost\",\n                    \"cost\": 10523932448333.781,\n                    \"params\": {\n                      \"TableReader_42\": {\n                        \"name\": \"TableReader_42\",\n                        \"cost\": 12666719.095286397,\n                        \"params\": null,\n                        \"desc\": \"\"\n                      },\n                      \"batchRatio\": 30,\n                      \"buildRows\": 24925000\n                    },\n                    \"desc\": \"TableReader_42*buildRows/batchRatio\"\n                  }\n                },\n                \"desc\": \"(probeReadCost) + (probeFilterCost)\"\n              }\n            },\n            \"desc\": \"(probeTotalCost)/indexLookupJoinConcurrency\"\n          }\n        },\n        \"type\": \"IndexHashJoin\",\n        \"desc\": \"(TableReader_52) + (buildFilterCost) + (probeCost)\",\n        \"id\": 48,\n        \"cost\": 2109354975542.719\n      },\n      \"IndexHashJoin_66\": {\n        \"params\": {\n          \"TableReader_73\": {\n            \"name\": \"TableReader_73\",\n            \"cost\": 4568485875.962565,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"buildFilterCost\": {\n            \"name\": \"buildFilterCost\",\n            \"cost\": 0,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 0, \"rows\": 24925000 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          },\n          \"probeCost\": {\n            \"name\": \"probeCost\",\n            \"cost\": 2104786489666.7563,\n            \"params\": {\n              \"indexLookupJoinConcurrency\": 5,\n              \"probeTotalCost\": {\n                \"name\": \"probeTotalCost\",\n                \"cost\": 10523932448333.781,\n                \"params\": {\n                  \"probeFilterCost\": {\n                    \"name\": \"probeFilterCost\",\n                    \"cost\": 0,\n                    \"params\": { \"cpuFactor\": 30, \"filters\": 0, \"rows\": 24925 },\n                    \"desc\": \"rows*filters*cpuFactor\"\n                  },\n                  \"probeReadCost\": {\n                    \"name\": \"probeReadCost\",\n                    \"cost\": 10523932448333.781,\n                    \"params\": {\n                      \"TableReader_60\": {\n                        \"name\": \"TableReader_60\",\n                        \"cost\": 12666719.095286397,\n                        \"params\": null,\n                        \"desc\": \"\"\n                      },\n                      \"batchRatio\": 30,\n                      \"buildRows\": 24925000\n                    },\n                    \"desc\": \"TableReader_60*buildRows/batchRatio\"\n                  }\n                },\n                \"desc\": \"(probeReadCost) + (probeFilterCost)\"\n              }\n            },\n            \"desc\": \"(probeTotalCost)/indexLookupJoinConcurrency\"\n          }\n        },\n        \"type\": \"IndexHashJoin\",\n        \"desc\": \"(TableReader_73) + (buildFilterCost) + (probeCost)\",\n        \"id\": 66,\n        \"cost\": 2109354975542.719\n      },\n      \"IndexJoin_28\": {\n        \"params\": {\n          \"HashJoin_70\": {\n            \"name\": \"HashJoin_70\",\n            \"cost\": 5274198010.608428,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"buildFilterCost\": {\n            \"name\": \"buildFilterCost\",\n            \"cost\": 0,\n            \"params\": {\n              \"cpuFactor\": 30,\n              \"filters\": 0,\n              \"rows\": 114202.99506237006\n            },\n            \"desc\": \"rows*filters*cpuFactor\"\n          },\n          \"probeCost\": {\n            \"name\": \"probeCost\",\n            \"cost\": 9679748814.516758,\n            \"params\": {\n              \"indexLookupJoinConcurrency\": 5,\n              \"probeTotalCost\": {\n                \"name\": \"probeTotalCost\",\n                \"cost\": 48398744072.583786,\n                \"params\": {\n                  \"probeFilterCost\": {\n                    \"name\": \"probeFilterCost\",\n                    \"cost\": 0,\n                    \"params\": {\n                      \"cpuFactor\": 30,\n                      \"filters\": 0,\n                      \"rows\": 463569.00894162105\n                    },\n                    \"desc\": \"rows*filters*cpuFactor\"\n                  },\n                  \"probeReadCost\": {\n                    \"name\": \"probeReadCost\",\n                    \"cost\": 48398744072.583786,\n                    \"params\": {\n                      \"IndexLookUp_27\": {\n                        \"name\": \"IndexLookUp_27\",\n                        \"cost\": 12713872.533592913,\n                        \"params\": null,\n                        \"desc\": \"(readIndexCost) + (readTableCost)\"\n                      },\n                      \"batchRatio\": 30,\n                      \"buildRows\": 114202.99506237006\n                    },\n                    \"desc\": \"IndexLookUp_27*buildRows/batchRatio\"\n                  }\n                },\n                \"desc\": \"(probeReadCost) + (probeFilterCost)\"\n              }\n            },\n            \"desc\": \"(probeTotalCost)/indexLookupJoinConcurrency\"\n          }\n        },\n        \"type\": \"IndexJoin\",\n        \"desc\": \"(HashJoin_70) + (buildFilterCost) + (probeCost)\",\n        \"id\": 28,\n        \"cost\": 14953946825.125187\n      },\n      \"IndexJoin_46\": {\n        \"params\": {\n          \"TableReader_52\": {\n            \"name\": \"TableReader_52\",\n            \"cost\": 4568485875.962565,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"buildFilterCost\": {\n            \"name\": \"buildFilterCost\",\n            \"cost\": 0,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 0, \"rows\": 24925000 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          },\n          \"probeCost\": {\n            \"name\": \"probeCost\",\n            \"cost\": 2104786489666.7563,\n            \"params\": {\n              \"indexLookupJoinConcurrency\": 5,\n              \"probeTotalCost\": {\n                \"name\": \"probeTotalCost\",\n                \"cost\": 10523932448333.781,\n                \"params\": {\n                  \"probeFilterCost\": {\n                    \"name\": \"probeFilterCost\",\n                    \"cost\": 0,\n                    \"params\": { \"cpuFactor\": 30, \"filters\": 0, \"rows\": 24925 },\n                    \"desc\": \"rows*filters*cpuFactor\"\n                  },\n                  \"probeReadCost\": {\n                    \"name\": \"probeReadCost\",\n                    \"cost\": 10523932448333.781,\n                    \"params\": {\n                      \"TableReader_42\": {\n                        \"name\": \"TableReader_42\",\n                        \"cost\": 12666719.095286397,\n                        \"params\": null,\n                        \"desc\": \"(Selection_41+netCost+seekCost)/distSqlScanConcurrency\"\n                      },\n                      \"batchRatio\": 30,\n                      \"buildRows\": 24925000\n                    },\n                    \"desc\": \"TableReader_42*buildRows/batchRatio\"\n                  }\n                },\n                \"desc\": \"(probeReadCost) + (probeFilterCost)\"\n              }\n            },\n            \"desc\": \"(probeTotalCost)/indexLookupJoinConcurrency\"\n          }\n        },\n        \"type\": \"IndexJoin\",\n        \"desc\": \"(TableReader_52) + (buildFilterCost) + (probeCost)\",\n        \"id\": 46,\n        \"cost\": 2109354975542.719\n      },\n      \"IndexJoin_64\": {\n        \"params\": {\n          \"TableReader_73\": {\n            \"name\": \"TableReader_73\",\n            \"cost\": 4568485875.962565,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"buildFilterCost\": {\n            \"name\": \"buildFilterCost\",\n            \"cost\": 0,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 0, \"rows\": 24925000 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          },\n          \"probeCost\": {\n            \"name\": \"probeCost\",\n            \"cost\": 2104786489666.7563,\n            \"params\": {\n              \"indexLookupJoinConcurrency\": 5,\n              \"probeTotalCost\": {\n                \"name\": \"probeTotalCost\",\n                \"cost\": 10523932448333.781,\n                \"params\": {\n                  \"probeFilterCost\": {\n                    \"name\": \"probeFilterCost\",\n                    \"cost\": 0,\n                    \"params\": { \"cpuFactor\": 30, \"filters\": 0, \"rows\": 24925 },\n                    \"desc\": \"rows*filters*cpuFactor\"\n                  },\n                  \"probeReadCost\": {\n                    \"name\": \"probeReadCost\",\n                    \"cost\": 10523932448333.781,\n                    \"params\": {\n                      \"TableReader_60\": {\n                        \"name\": \"TableReader_60\",\n                        \"cost\": 12666719.095286397,\n                        \"params\": null,\n                        \"desc\": \"(Selection_59+netCost+seekCost)/distSqlScanConcurrency\"\n                      },\n                      \"batchRatio\": 30,\n                      \"buildRows\": 24925000\n                    },\n                    \"desc\": \"TableReader_60*buildRows/batchRatio\"\n                  }\n                },\n                \"desc\": \"(probeReadCost) + (probeFilterCost)\"\n              }\n            },\n            \"desc\": \"(probeTotalCost)/indexLookupJoinConcurrency\"\n          }\n        },\n        \"type\": \"IndexJoin\",\n        \"desc\": \"(TableReader_73) + (buildFilterCost) + (probeCost)\",\n        \"id\": 64,\n        \"cost\": 2109354975542.719\n      },\n      \"IndexLookUp_27\": {\n        \"params\": {\n          \"readIndexCost\": {\n            \"name\": \"readIndexCost\",\n            \"cost\": 12667273.39536189,\n            \"params\": {\n              \"distSqlScanConcurrency\": 15,\n              \"readIndexTotalCost\": {\n                \"name\": \"readIndexTotalCost\",\n                \"cost\": 190009100.93042833,\n                \"params\": {\n                  \"IndexRangeScan_24\": {\n                    \"name\": \"IndexRangeScan_24\",\n                    \"cost\": 6726.317835356039,\n                    \"params\": null,\n                    \"desc\": \"rows*log(rowSize)*scanFactor\"\n                  },\n                  \"indexNetCost\": {\n                    \"name\": \"indexNetCost\",\n                    \"cost\": 2374.612592977475,\n                    \"params\": {\n                      \"networkFactor\": 8,\n                      \"rowSize\": 24.375,\n                      \"rows\": 12.177500476807563\n                    },\n                    \"desc\": \"rows*rowSize*networkFactor\"\n                  },\n                  \"indexSeekCost\": {\n                    \"name\": \"indexSeekCost\",\n                    \"cost\": 190000000,\n                    \"params\": { \"seekFactor\": 9500000, \"taskNums\": 20 },\n                    \"desc\": \"taskNums*seekFactor\"\n                  }\n                },\n                \"desc\": \"(indexNetCost) + (indexSeekCost) + (IndexRangeScan_24)\"\n              }\n            },\n            \"desc\": \"(readIndexTotalCost)/distSqlScanConcurrency\"\n          },\n          \"readTableCost\": {\n            \"name\": \"readTableCost\",\n            \"cost\": 46599.13823102407,\n            \"params\": {\n              \"indexLookupConcurrency\": 5,\n              \"readTableTotalCost\": {\n                \"name\": \"readTableTotalCost\",\n                \"cost\": 232995.69115512038,\n                \"params\": {\n                  \"doubleReadCost\": {\n                    \"name\": \"doubleReadCost\",\n                    \"cost\": 231737.83407364791,\n                    \"params\": {\n                      \"double-read-cpu\": {\n                        \"name\": \"double-read-cpu\",\n                        \"cost\": 365.3250143042269,\n                        \"params\": {\n                          \"cpuFactor\": 30,\n                          \"indexRows\": 12.177500476807563\n                        },\n                        \"desc\": \"indexRows*cpuFactor\"\n                      },\n                      \"doubleReadSeekCost\": {\n                        \"name\": \"doubleReadSeekCost\",\n                        \"cost\": 231372.5090593437,\n                        \"params\": {\n                          \"seekFactor\": 9500000,\n                          \"taskNums\": 0.024355000953615126\n                        },\n                        \"desc\": \"taskNums*seekFactor\"\n                      }\n                    },\n                    \"desc\": \"(double-read-cpu) + (doubleReadSeekCost)\"\n                  },\n                  \"readTableCost\": {\n                    \"name\": \"readTableCost\",\n                    \"cost\": 1257.857081472455,\n                    \"params\": {\n                      \"distSqlScanConcurrency\": 15,\n                      \"readTableTotalCost\": {\n                        \"name\": \"readTableTotalCost\",\n                        \"cost\": 18867.856222086826,\n                        \"params\": {\n                          \"Selection_26\": {\n                            \"name\": \"Selection_26\",\n                            \"cost\": 9466.825853991388,\n                            \"params\": null,\n                            \"desc\": \"(filterCost) + (TableRowIDScan_25)\"\n                          },\n                          \"tableNetCost\": {\n                            \"name\": \"tableNetCost\",\n                            \"cost\": 9401.030368095438,\n                            \"params\": {\n                              \"networkFactor\": 8,\n                              \"rowSize\": 96.5,\n                              \"rows\": 12.177500476807563\n                            },\n                            \"desc\": \"rows*rowSize*networkFactor\"\n                          },\n                          \"tableSeekCost\": {\n                            \"name\": \"tableSeekCost\",\n                            \"cost\": 0,\n                            \"params\": { \"seekFactor\": 9500000, \"taskNums\": 0 },\n                            \"desc\": \"taskNums*seekFactor\"\n                          }\n                        },\n                        \"desc\": \"(tableNetCost) + (tableSeekCost) + (Selection_26)\"\n                      }\n                    },\n                    \"desc\": \"(readTableTotalCost)/distSqlScanConcurrency\"\n                  }\n                },\n                \"desc\": \"(readTableCost) + (doubleReadCost)\"\n              }\n            },\n            \"desc\": \"(readTableTotalCost)/indexLookupConcurrency\"\n          }\n        },\n        \"type\": \"IndexLookUp\",\n        \"desc\": \"(readIndexCost) + (readTableCost)\",\n        \"id\": 27,\n        \"cost\": 12713872.533592913\n      },\n      \"IndexLookUp_56\": {\n        \"params\": {\n          \"readIndexCost\": {\n            \"name\": \"readIndexCost\",\n            \"cost\": 14960080104.57087,\n            \"params\": {\n              \"distSqlScanConcurrency\": 15,\n              \"readIndexTotalCost\": {\n                \"name\": \"readIndexTotalCost\",\n                \"cost\": 224401201568.56305,\n                \"params\": {\n                  \"IndexFullScan_53\": {\n                    \"name\": \"IndexFullScan_53\",\n                    \"cost\": 165710068423.56305,\n                    \"params\": null,\n                    \"desc\": \"rows*log(rowSize)*scanFactor\"\n                  },\n                  \"indexNetCost\": {\n                    \"name\": \"indexNetCost\",\n                    \"cost\": 58501133145,\n                    \"params\": {\n                      \"networkFactor\": 8,\n                      \"rowSize\": 24.375,\n                      \"rows\": 300005811\n                    },\n                    \"desc\": \"rows*rowSize*networkFactor\"\n                  },\n                  \"indexSeekCost\": {\n                    \"name\": \"indexSeekCost\",\n                    \"cost\": 190000000,\n                    \"params\": { \"seekFactor\": 9500000, \"taskNums\": 20 },\n                    \"desc\": \"taskNums*seekFactor\"\n                  }\n                },\n                \"desc\": \"(indexNetCost) + (indexSeekCost) + (IndexFullScan_53)\"\n              }\n            },\n            \"desc\": \"(readIndexTotalCost)/distSqlScanConcurrency\"\n          },\n          \"readTableCost\": {\n            \"name\": \"readTableCost\",\n            \"cost\": 1148279853898.03,\n            \"params\": {\n              \"indexLookupConcurrency\": 5,\n              \"readTableTotalCost\": {\n                \"name\": \"readTableTotalCost\",\n                \"cost\": 5741399269490.15,\n                \"params\": {\n                  \"doubleReadCost\": {\n                    \"name\": \"doubleReadCost\",\n                    \"cost\": 5709110583330,\n                    \"params\": {\n                      \"double-read-cpu\": {\n                        \"name\": \"double-read-cpu\",\n                        \"cost\": 9000174330,\n                        \"params\": { \"cpuFactor\": 30, \"indexRows\": 300005811 },\n                        \"desc\": \"indexRows*cpuFactor\"\n                      },\n                      \"doubleReadSeekCost\": {\n                        \"name\": \"doubleReadSeekCost\",\n                        \"cost\": 5700110409000,\n                        \"params\": {\n                          \"seekFactor\": 9500000,\n                          \"taskNums\": 600011.622\n                        },\n                        \"desc\": \"taskNums*seekFactor\"\n                      }\n                    },\n                    \"desc\": \"(double-read-cpu) + (doubleReadSeekCost)\"\n                  },\n                  \"readTableCost\": {\n                    \"name\": \"readTableCost\",\n                    \"cost\": 32288686160.150814,\n                    \"params\": {\n                      \"distSqlScanConcurrency\": 15,\n                      \"readTableTotalCost\": {\n                        \"name\": \"readTableTotalCost\",\n                        \"cost\": 484330292402.2622,\n                        \"params\": {\n                          \"Selection_55\": {\n                            \"name\": \"Selection_55\",\n                            \"cost\": 233225428595.2622,\n                            \"params\": null,\n                            \"desc\": \"(filterCost) + (TableRowIDScan_54)\"\n                          },\n                          \"tableNetCost\": {\n                            \"name\": \"tableNetCost\",\n                            \"cost\": 251104863807,\n                            \"params\": {\n                              \"networkFactor\": 8,\n                              \"rowSize\": 104.625,\n                              \"rows\": 300005811\n                            },\n                            \"desc\": \"rows*rowSize*networkFactor\"\n                          },\n                          \"tableSeekCost\": {\n                            \"name\": \"tableSeekCost\",\n                            \"cost\": 0,\n                            \"params\": { \"seekFactor\": 9500000, \"taskNums\": 0 },\n                            \"desc\": \"taskNums*seekFactor\"\n                          }\n                        },\n                        \"desc\": \"(tableNetCost) + (tableSeekCost) + (Selection_55)\"\n                      }\n                    },\n                    \"desc\": \"(readTableTotalCost)/distSqlScanConcurrency\"\n                  }\n                },\n                \"desc\": \"(readTableCost) + (doubleReadCost)\"\n              }\n            },\n            \"desc\": \"(readTableTotalCost)/indexLookupConcurrency\"\n          }\n        },\n        \"type\": \"IndexLookUp\",\n        \"desc\": \"(readIndexCost) + (readTableCost)\",\n        \"id\": 56,\n        \"cost\": 1163239934002.6008\n      },\n      \"IndexRangeScan_24\": {\n        \"params\": {\n          \"rowSize\": 46,\n          \"rows\": 12.177500476807563,\n          \"scanFactor\": 100\n        },\n        \"type\": \"IndexRangeScan\",\n        \"desc\": \"rows*log(rowSize)*scanFactor\",\n        \"id\": 24,\n        \"cost\": 6726.317835356039\n      },\n      \"MergeJoin_23\": {\n        \"params\": {\n          \"IndexJoin_46\": {\n            \"name\": \"IndexJoin_46\",\n            \"cost\": 2109354975542.719,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"Projection_57\": {\n            \"name\": \"Projection_57\",\n            \"cost\": 1165639980490.6008,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"filterCost\": {\n            \"name\": \"filterCost\",\n            \"cost\": 0,\n            \"params\": {\n              \"filterLeftRowsCost\": {\n                \"name\": \"filterLeftRowsCost\",\n                \"cost\": 0,\n                \"params\": {\n                  \"cpuFactor\": 30,\n                  \"filters\": 0,\n                  \"rows\": 114202.99506237006\n                },\n                \"desc\": \"rows*filters*cpuFactor\"\n              },\n              \"filterRightRowsCost\": {\n                \"name\": \"filterRightRowsCost\",\n                \"cost\": 0,\n                \"params\": { \"cpuFactor\": 30, \"filters\": 0, \"rows\": 100001937 },\n                \"desc\": \"rows*filters*cpuFactor\"\n              }\n            },\n            \"desc\": \"(filterLeftRowsCost) + (filterRightRowsCost)\"\n          },\n          \"groupRowsCost\": {\n            \"name\": \"groupRowsCost\",\n            \"cost\": 3003484199.851871,\n            \"params\": {\n              \"groupLeftRowsCost\": {\n                \"name\": \"groupLeftRowsCost\",\n                \"cost\": 3426089.8518711017,\n                \"params\": {\n                  \"cpuFactor\": 30,\n                  \"groupItemNum\": 1,\n                  \"rows\": 114202.99506237006\n                },\n                \"desc\": \"rows*groupItemNum*cpuFactor\"\n              },\n              \"groupRightRowsCost\": {\n                \"name\": \"groupRightRowsCost\",\n                \"cost\": 3000058110,\n                \"params\": {\n                  \"cpuFactor\": 30,\n                  \"groupItemNum\": 1,\n                  \"rows\": 100001937\n                },\n                \"desc\": \"rows*groupItemNum*cpuFactor\"\n              }\n            },\n            \"desc\": \"(groupLeftRowsCost) + (groupRightRowsCost)\"\n          }\n        },\n        \"type\": \"MergeJoin\",\n        \"desc\": \"(IndexJoin_46) + (Projection_57) + (filterCost) + (groupRowsCost)\",\n        \"id\": 23,\n        \"cost\": 3277998440233.172\n      },\n      \"Selection_26\": {\n        \"params\": {\n          \"TableRowIDScan_25\": {\n            \"name\": \"TableRowIDScan_25\",\n            \"cost\": 9101.50083968716,\n            \"params\": null,\n            \"desc\": \"rows*log(rowSize)*scanFactor\"\n          },\n          \"filterCost\": {\n            \"name\": \"filterCost\",\n            \"cost\": 365.3250143042269,\n            \"params\": {\n              \"cpuFactor\": 30,\n              \"filters\": 1,\n              \"rows\": 12.177500476807563\n            },\n            \"desc\": \"rows*filters*cpuFactor\"\n          }\n        },\n        \"type\": \"Selection\",\n        \"desc\": \"(filterCost) + (TableRowIDScan_25)\",\n        \"id\": 26,\n        \"cost\": 9466.825853991388\n      },\n      \"Selection_41\": {\n        \"params\": {\n          \"TableRangeScan_40\": {\n            \"name\": \"TableRangeScan_40\",\n            \"cost\": 756.2852959583925,\n            \"params\": null,\n            \"desc\": \"rows*log(rowSize)*scanFactor\"\n          },\n          \"filterCost\": {\n            \"name\": \"filterCost\",\n            \"cost\": 30,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 1, \"rows\": 1 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          }\n        },\n        \"type\": \"Selection\",\n        \"desc\": \"(filterCost) + (TableRangeScan_40)\",\n        \"id\": 41,\n        \"cost\": 786.2852959583925\n      },\n      \"Selection_51\": {\n        \"params\": {\n          \"TableFullScan_50\": {\n            \"name\": \"TableFullScan_50\",\n            \"cost\": 53325688139.43848,\n            \"params\": null,\n            \"desc\": \"rows*log(rowSize)*scanFactor\"\n          },\n          \"filterCost\": {\n            \"name\": \"filterCost\",\n            \"cost\": 2250000000,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 1, \"rows\": 75000000 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          }\n        },\n        \"type\": \"Selection\",\n        \"desc\": \"(filterCost) + (TableFullScan_50)\",\n        \"id\": 51,\n        \"cost\": 55575688139.43848\n      },\n      \"Selection_55\": {\n        \"params\": {\n          \"TableRowIDScan_54\": {\n            \"name\": \"TableRowIDScan_54\",\n            \"cost\": 224225254265.2622,\n            \"params\": null,\n            \"desc\": \"rows*log(rowSize)*scanFactor\"\n          },\n          \"filterCost\": {\n            \"name\": \"filterCost\",\n            \"cost\": 9000174330,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 1, \"rows\": 300005811 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          }\n        },\n        \"type\": \"Selection\",\n        \"desc\": \"(filterCost) + (TableRowIDScan_54)\",\n        \"id\": 55,\n        \"cost\": 233225428595.2622\n      },\n      \"Selection_59\": {\n        \"params\": {\n          \"TableRangeScan_58\": {\n            \"name\": \"TableRangeScan_58\",\n            \"cost\": 756.2852959583925,\n            \"params\": null,\n            \"desc\": \"rows*log(rowSize)*scanFactor\"\n          },\n          \"filterCost\": {\n            \"name\": \"filterCost\",\n            \"cost\": 30,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 1, \"rows\": 1 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          }\n        },\n        \"type\": \"Selection\",\n        \"desc\": \"(filterCost) + (TableRangeScan_58)\",\n        \"id\": 59,\n        \"cost\": 786.2852959583925\n      },\n      \"Selection_72\": {\n        \"params\": {\n          \"TableFullScan_71\": {\n            \"name\": \"TableFullScan_71\",\n            \"cost\": 53325688139.43848,\n            \"params\": null,\n            \"desc\": \"rows*log(rowSize)*scanFactor\"\n          },\n          \"filterCost\": {\n            \"name\": \"filterCost\",\n            \"cost\": 2250000000,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 1, \"rows\": 75000000 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          }\n        },\n        \"type\": \"Selection\",\n        \"desc\": \"(filterCost) + (TableFullScan_71)\",\n        \"id\": 72,\n        \"cost\": 55575688139.43848\n      },\n      \"Selection_75\": {\n        \"params\": {\n          \"TableFullScan_74\": {\n            \"name\": \"TableFullScan_74\",\n            \"cost\": 5672139719.687943,\n            \"params\": null,\n            \"desc\": \"rows*log(rowSize)*scanFactor\"\n          },\n          \"filterCost\": {\n            \"name\": \"filterCost\",\n            \"cost\": 225000000,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 1, \"rows\": 7500000 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          }\n        },\n        \"type\": \"Selection\",\n        \"desc\": \"(filterCost) + (TableFullScan_74)\",\n        \"id\": 75,\n        \"cost\": 5897139719.687943\n      },\n      \"Selection_78\": {\n        \"params\": {\n          \"TableFullScan_77\": {\n            \"name\": \"TableFullScan_77\",\n            \"cost\": 224225254265.2622,\n            \"params\": null,\n            \"desc\": \"rows*log(rowSize)*scanFactor\"\n          },\n          \"filterCost\": {\n            \"name\": \"filterCost\",\n            \"cost\": 9000174330,\n            \"params\": { \"cpuFactor\": 30, \"filters\": 1, \"rows\": 300005811 },\n            \"desc\": \"rows*filters*cpuFactor\"\n          }\n        },\n        \"type\": \"Selection\",\n        \"desc\": \"(filterCost) + (TableFullScan_77)\",\n        \"id\": 78,\n        \"cost\": 233225428595.2622\n      },\n      \"TableFullScan_50\": {\n        \"params\": { \"rowSize\": 138.15, \"rows\": 75000000, \"scanFactor\": 100 },\n        \"type\": \"TableFullScan\",\n        \"desc\": \"rows*log(rowSize)*scanFactor\",\n        \"id\": 50,\n        \"cost\": 53325688139.43848\n      },\n      \"TableFullScan_71\": {\n        \"params\": { \"rowSize\": 138.15, \"rows\": 75000000, \"scanFactor\": 100 },\n        \"type\": \"TableFullScan\",\n        \"desc\": \"rows*log(rowSize)*scanFactor\",\n        \"id\": 71,\n        \"cost\": 53325688139.43848\n      },\n      \"TableFullScan_74\": {\n        \"params\": {\n          \"rowSize\": 189.07999999999998,\n          \"rows\": 7500000,\n          \"scanFactor\": 100\n        },\n        \"type\": \"TableFullScan\",\n        \"desc\": \"rows*log(rowSize)*scanFactor\",\n        \"id\": 74,\n        \"cost\": 5672139719.687943\n      },\n      \"TableFullScan_77\": {\n        \"params\": {\n          \"rowSize\": 177.79000000000002,\n          \"rows\": 300005811,\n          \"scanFactor\": 100\n        },\n        \"type\": \"TableFullScan\",\n        \"desc\": \"rows*log(rowSize)*scanFactor\",\n        \"id\": 77,\n        \"cost\": 224225254265.2622\n      },\n      \"TableRangeScan_40\": {\n        \"params\": {\n          \"rowSize\": 189.07999999999998,\n          \"rows\": 1,\n          \"scanFactor\": 100\n        },\n        \"type\": \"TableRangeScan\",\n        \"desc\": \"rows*log(rowSize)*scanFactor\",\n        \"id\": 40,\n        \"cost\": 756.2852959583925\n      },\n      \"TableRangeScan_58\": {\n        \"params\": {\n          \"rowSize\": 189.07999999999998,\n          \"rows\": 1,\n          \"scanFactor\": 100\n        },\n        \"type\": \"TableRangeScan\",\n        \"desc\": \"rows*log(rowSize)*scanFactor\",\n        \"id\": 58,\n        \"cost\": 756.2852959583925\n      },\n      \"TableReader_42\": {\n        \"params\": {\n          \"Selection_41\": {\n            \"name\": \"Selection_41\",\n            \"cost\": 786.2852959583925,\n            \"params\": null,\n            \"desc\": \"(filterCost) + (TableRangeScan_40)\"\n          },\n          \"distSqlScanConcurrency\": 15,\n          \"netCost\": {\n            \"name\": \"netCost\",\n            \"cost\": 0.14400000000000002,\n            \"params\": { \"networkFactor\": 8, \"rowSize\": 18, \"rows\": 0.001 },\n            \"desc\": \"rows*rowSize*networkFactor\"\n          },\n          \"seekCost\": {\n            \"name\": \"seekCost\",\n            \"cost\": 190000000,\n            \"params\": { \"seekFactor\": 9500000, \"taskNums\": 20 },\n            \"desc\": \"taskNums*seekFactor\"\n          }\n        },\n        \"type\": \"TableReader\",\n        \"desc\": \"(Selection_41+netCost+seekCost)/distSqlScanConcurrency\",\n        \"id\": 42,\n        \"cost\": 12666719.095286397\n      },\n      \"TableReader_52\": {\n        \"params\": {\n          \"Selection_51\": {\n            \"name\": \"Selection_51\",\n            \"cost\": 55575688139.43848,\n            \"params\": null,\n            \"desc\": \"(filterCost) + (TableFullScan_50)\"\n          },\n          \"distSqlScanConcurrency\": 15,\n          \"netCost\": {\n            \"name\": \"netCost\",\n            \"cost\": 12761600000,\n            \"params\": { \"networkFactor\": 8, \"rowSize\": 64, \"rows\": 24925000 },\n            \"desc\": \"rows*rowSize*networkFactor\"\n          },\n          \"seekCost\": {\n            \"name\": \"seekCost\",\n            \"cost\": 190000000,\n            \"params\": { \"seekFactor\": 9500000, \"taskNums\": 20 },\n            \"desc\": \"taskNums*seekFactor\"\n          }\n        },\n        \"type\": \"TableReader\",\n        \"desc\": \"(Selection_51+netCost+seekCost)/distSqlScanConcurrency\",\n        \"id\": 52,\n        \"cost\": 4568485875.962565\n      },\n      \"TableReader_60\": {\n        \"params\": {\n          \"Selection_59\": {\n            \"name\": \"Selection_59\",\n            \"cost\": 786.2852959583925,\n            \"params\": null,\n            \"desc\": \"(filterCost) + (TableRangeScan_58)\"\n          },\n          \"distSqlScanConcurrency\": 15,\n          \"netCost\": {\n            \"name\": \"netCost\",\n            \"cost\": 0.14400000000000002,\n            \"params\": { \"networkFactor\": 8, \"rowSize\": 18, \"rows\": 0.001 },\n            \"desc\": \"rows*rowSize*networkFactor\"\n          },\n          \"seekCost\": {\n            \"name\": \"seekCost\",\n            \"cost\": 190000000,\n            \"params\": { \"seekFactor\": 9500000, \"taskNums\": 20 },\n            \"desc\": \"taskNums*seekFactor\"\n          }\n        },\n        \"type\": \"TableReader\",\n        \"desc\": \"(Selection_59+netCost+seekCost)/distSqlScanConcurrency\",\n        \"id\": 60,\n        \"cost\": 12666719.095286397\n      },\n      \"TableReader_73\": {\n        \"params\": {\n          \"Selection_72\": {\n            \"name\": \"Selection_72\",\n            \"cost\": 55575688139.43848,\n            \"params\": null,\n            \"desc\": \"(filterCost) + (TableFullScan_71)\"\n          },\n          \"distSqlScanConcurrency\": 15,\n          \"netCost\": {\n            \"name\": \"netCost\",\n            \"cost\": 12761600000,\n            \"params\": { \"networkFactor\": 8, \"rowSize\": 64, \"rows\": 24925000 },\n            \"desc\": \"rows*rowSize*networkFactor\"\n          },\n          \"seekCost\": {\n            \"name\": \"seekCost\",\n            \"cost\": 190000000,\n            \"params\": { \"seekFactor\": 9500000, \"taskNums\": 20 },\n            \"desc\": \"taskNums*seekFactor\"\n          }\n        },\n        \"type\": \"TableReader\",\n        \"desc\": \"(Selection_72+netCost+seekCost)/distSqlScanConcurrency\",\n        \"id\": 73,\n        \"cost\": 4568485875.962565\n      },\n      \"TableReader_76\": {\n        \"params\": {\n          \"Selection_75\": {\n            \"name\": \"Selection_75\",\n            \"cost\": 5897139719.687943,\n            \"params\": null,\n            \"desc\": \"(filterCost) + (TableFullScan_74)\"\n          },\n          \"distSqlScanConcurrency\": 15,\n          \"netCost\": {\n            \"name\": \"netCost\",\n            \"cost\": 1840800,\n            \"params\": { \"networkFactor\": 8, \"rowSize\": 30.68, \"rows\": 7500 },\n            \"desc\": \"rows*rowSize*networkFactor\"\n          },\n          \"seekCost\": {\n            \"name\": \"seekCost\",\n            \"cost\": 190000000,\n            \"params\": { \"seekFactor\": 9500000, \"taskNums\": 20 },\n            \"desc\": \"taskNums*seekFactor\"\n          }\n        },\n        \"type\": \"TableReader\",\n        \"desc\": \"(Selection_75+netCost+seekCost)/distSqlScanConcurrency\",\n        \"id\": 76,\n        \"cost\": 405932034.6458629\n      },\n      \"TableReader_79\": {\n        \"params\": {\n          \"Selection_78\": {\n            \"name\": \"Selection_78\",\n            \"cost\": 233225428595.2622,\n            \"params\": null,\n            \"desc\": \"(filterCost) + (TableFullScan_77)\"\n          },\n          \"distSqlScanConcurrency\": 15,\n          \"netCost\": {\n            \"name\": \"netCost\",\n            \"cost\": 102401983488,\n            \"params\": { \"networkFactor\": 8, \"rowSize\": 128, \"rows\": 100001937 },\n            \"desc\": \"rows*rowSize*networkFactor\"\n          },\n          \"seekCost\": {\n            \"name\": \"seekCost\",\n            \"cost\": 190000000,\n            \"params\": { \"seekFactor\": 9500000, \"taskNums\": 20 },\n            \"desc\": \"taskNums*seekFactor\"\n          }\n        },\n        \"type\": \"TableReader\",\n        \"desc\": \"(Selection_78+netCost+seekCost)/distSqlScanConcurrency\",\n        \"id\": 79,\n        \"cost\": 22387827472.21748\n      },\n      \"TableRowIDScan_25\": {\n        \"params\": {\n          \"rowSize\": 177.79000000000002,\n          \"rows\": 12.177500476807563,\n          \"scanFactor\": 100\n        },\n        \"type\": \"TableRowIDScan\",\n        \"desc\": \"rows*log(rowSize)*scanFactor\",\n        \"id\": 25,\n        \"cost\": 9101.50083968716\n      },\n      \"TableRowIDScan_54\": {\n        \"params\": {\n          \"rowSize\": 177.79000000000002,\n          \"rows\": 300005811,\n          \"scanFactor\": 100\n        },\n        \"type\": \"TableRowIDScan\",\n        \"desc\": \"rows*log(rowSize)*scanFactor\",\n        \"id\": 54,\n        \"cost\": 224225254265.2622\n      },\n      \"TopN_17\": {\n        \"params\": {\n          \"HashAgg_22\": {\n            \"name\": \"HashAgg_22\",\n            \"cost\": 15051914409.014849,\n            \"params\": null,\n            \"desc\": \"\"\n          },\n          \"topNCPUCost\": {\n            \"name\": \"topNCPUCost\",\n            \"cost\": 92396574.8833357,\n            \"params\": {\n              \"ByItemNums\": 2,\n              \"cpuFactor\": 30,\n              \"log2(n)\": 3.321928094887362,\n              \"rows\": 463569.008941621\n            },\n            \"desc\": \"rows*log2(n)*ByItemNums*cpuFactor\"\n          },\n          \"topNMemCost\": {\n            \"name\": \"topNMemCost\",\n            \"cost\": 640,\n            \"params\": { \"memFactor\": 1, \"n\": 10, \"rowSize\": 64 },\n            \"desc\": \"n*rowSize*memFactor\"\n          }\n        },\n        \"type\": \"TopN\",\n        \"desc\": \"(HashAgg_22) + (topNCPUCost) + (topNMemCost)\",\n        \"id\": 17,\n        \"cost\": 15144311623.898184\n      }\n    },\n    \"candidates\": {\n      \"14\": {\n        \"type\": \"Projection\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"test.lineitem.l_orderkey, Column#35, test.orders.o_orderdate, test.orders.o_shippriority\",\n        \"children\": [17],\n        \"id\": 14,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"Projection_8\"\n      },\n      \"17\": {\n        \"type\": \"TopN\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"Column#35:desc, test.orders.o_orderdate, offset:0, count:10\",\n        \"children\": [22],\n        \"id\": 17,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"TopN_11\"\n      },\n      \"22\": {\n        \"type\": \"HashAgg\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"group by:test.lineitem.l_orderkey, test.orders.o_orderdate, test.orders.o_shippriority, funcs:sum(mul(test.lineitem.l_extendedprice, minus(1, test.lineitem.l_discount)))->Column#35, funcs:firstrow(test.orders.o_orderdate)->test.orders.o_orderdate, funcs:firstrow(test.orders.o_shippriority)->test.orders.o_shippriority, funcs:firstrow(test.lineitem.l_orderkey)->test.lineitem.l_orderkey\",\n        \"children\": [28],\n        \"id\": 22,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"Aggregation_7\"\n      },\n      \"23\": {\n        \"type\": \"MergeJoin\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"inner join, left key:test.orders.o_orderkey, right key:test.lineitem.l_orderkey\",\n        \"children\": [46, 57],\n        \"id\": 23,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"Join_13\"\n      },\n      \"24\": {\n        \"type\": \"IndexRangeScan\",\n        \"property\": \"\",\n        \"info\": \"table:lineitem, index:PRIMARY(L_ORDERKEY, L_LINENUMBER), range: decided by [eq(test.lineitem.l_orderkey, test.orders.o_orderkey)], keep order:false\",\n        \"children\": null,\n        \"id\": 24,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"\"\n      },\n      \"25\": {\n        \"type\": \"TableRowIDScan\",\n        \"property\": \"\",\n        \"info\": \"table:lineitem, keep order:false\",\n        \"children\": null,\n        \"id\": 25,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"\"\n      },\n      \"26\": {\n        \"type\": \"Selection\",\n        \"property\": \"\",\n        \"info\": \"gt(test.lineitem.l_shipdate, 1995-03-13 00:00:00.000000)\",\n        \"children\": [25],\n        \"id\": 26,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"\"\n      },\n      \"27\": {\n        \"type\": \"IndexLookUp\",\n        \"property\": \"\",\n        \"info\": \"\",\n        \"children\": [24, 26],\n        \"id\": 27,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"\"\n      },\n      \"28\": {\n        \"type\": \"IndexJoin\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"inner join, inner:IndexLookUp_27, outer key:test.orders.o_orderkey, inner key:test.lineitem.l_orderkey, equal cond:eq(test.orders.o_orderkey, test.lineitem.l_orderkey)\",\n        \"children\": [70, 27],\n        \"id\": 28,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"Join_13\"\n      },\n      \"30\": {\n        \"type\": \"IndexHashJoin\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"inner join, inner:IndexLookUp_27, outer key:test.orders.o_orderkey, inner key:test.lineitem.l_orderkey, equal cond:eq(test.orders.o_orderkey, test.lineitem.l_orderkey)\",\n        \"children\": [70, 27],\n        \"id\": 30,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"Join_13\"\n      },\n      \"38\": {\n        \"type\": \"HashJoin\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"inner join, equal:[eq(test.orders.o_orderkey, test.lineitem.l_orderkey)]\",\n        \"children\": [70, 79],\n        \"id\": 38,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"Join_13\"\n      },\n      \"39\": {\n        \"type\": \"HashJoin\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"inner join, equal:[eq(test.orders.o_orderkey, test.lineitem.l_orderkey)]\",\n        \"children\": [70, 79],\n        \"id\": 39,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"Join_13\"\n      },\n      \"40\": {\n        \"type\": \"TableRangeScan\",\n        \"property\": \"\",\n        \"info\": \"table:customer, range: decided by [test.orders.o_custkey], keep order:false\",\n        \"children\": null,\n        \"id\": 40,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"41\": {\n        \"type\": \"Selection\",\n        \"property\": \"\",\n        \"info\": \"eq(test.customer.c_mktsegment, 'AUTOMOBILE')\",\n        \"children\": [40],\n        \"id\": 41,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"42\": {\n        \"type\": \"TableReader\",\n        \"property\": \"\",\n        \"info\": \"data:Selection_41\",\n        \"children\": [41],\n        \"id\": 42,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"46\": {\n        \"type\": \"IndexJoin\",\n        \"property\": \"Prop{cols: [{test.orders.o_orderkey false}], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"inner join, inner:TableReader_42, outer key:test.orders.o_custkey, inner key:test.customer.c_custkey, equal cond:eq(test.orders.o_custkey, test.customer.c_custkey)\",\n        \"children\": [42, 52],\n        \"id\": 46,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"Join_12\"\n      },\n      \"48\": {\n        \"type\": \"IndexHashJoin\",\n        \"property\": \"Prop{cols: [{test.orders.o_orderkey false}], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"inner join, inner:TableReader_42, outer key:test.orders.o_custkey, inner key:test.customer.c_custkey, equal cond:eq(test.orders.o_custkey, test.customer.c_custkey)\",\n        \"children\": [42, 52],\n        \"id\": 48,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"Join_12\"\n      },\n      \"50\": {\n        \"type\": \"TableFullScan\",\n        \"property\": \"\",\n        \"info\": \"table:orders, keep order:true\",\n        \"children\": null,\n        \"id\": 50,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"51\": {\n        \"type\": \"Selection\",\n        \"property\": \"\",\n        \"info\": \"lt(test.orders.o_orderdate, 1995-03-13 00:00:00.000000)\",\n        \"children\": [50],\n        \"id\": 51,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"52\": {\n        \"type\": \"TableReader\",\n        \"property\": \"Prop{cols: [{test.orders.o_orderkey false}], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"data:Selection_51\",\n        \"children\": [51],\n        \"id\": 52,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"DataSource_2\"\n      },\n      \"53\": {\n        \"type\": \"IndexFullScan\",\n        \"property\": \"\",\n        \"info\": \"table:lineitem, index:PRIMARY(L_ORDERKEY, L_LINENUMBER), keep order:true\",\n        \"children\": null,\n        \"id\": 53,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"54\": {\n        \"type\": \"TableRowIDScan\",\n        \"property\": \"\",\n        \"info\": \"table:lineitem, keep order:false\",\n        \"children\": null,\n        \"id\": 54,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"55\": {\n        \"type\": \"Selection\",\n        \"property\": \"\",\n        \"info\": \"gt(test.lineitem.l_shipdate, 1995-03-13 00:00:00.000000)\",\n        \"children\": [54],\n        \"id\": 55,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"56\": {\n        \"type\": \"IndexLookUp\",\n        \"property\": \"\",\n        \"info\": \"\",\n        \"children\": [53, 55],\n        \"id\": 56,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"57\": {\n        \"type\": \"Projection\",\n        \"property\": \"Prop{cols: [{test.lineitem.l_orderkey false}], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"test.lineitem.l_orderkey, test.lineitem.l_extendedprice, test.lineitem.l_discount, test.lineitem.l_shipdate\",\n        \"children\": [56],\n        \"id\": 57,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"DataSource_4\"\n      },\n      \"58\": {\n        \"type\": \"TableRangeScan\",\n        \"property\": \"\",\n        \"info\": \"table:customer, range: decided by [test.orders.o_custkey], keep order:false\",\n        \"children\": null,\n        \"id\": 58,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"59\": {\n        \"type\": \"Selection\",\n        \"property\": \"\",\n        \"info\": \"eq(test.customer.c_mktsegment, 'AUTOMOBILE')\",\n        \"children\": [58],\n        \"id\": 59,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"60\": {\n        \"type\": \"TableReader\",\n        \"property\": \"\",\n        \"info\": \"data:Selection_59\",\n        \"children\": [59],\n        \"id\": 60,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"64\": {\n        \"type\": \"IndexJoin\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"inner join, inner:TableReader_60, outer key:test.orders.o_custkey, inner key:test.customer.c_custkey, equal cond:eq(test.orders.o_custkey, test.customer.c_custkey)\",\n        \"children\": [60, 73],\n        \"id\": 64,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"Join_12\"\n      },\n      \"66\": {\n        \"type\": \"IndexHashJoin\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"inner join, inner:TableReader_60, outer key:test.orders.o_custkey, inner key:test.customer.c_custkey, equal cond:eq(test.orders.o_custkey, test.customer.c_custkey)\",\n        \"children\": [60, 73],\n        \"id\": 66,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"Join_12\"\n      },\n      \"69\": {\n        \"type\": \"HashJoin\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"inner join, equal:[eq(test.customer.c_custkey, test.orders.o_custkey)]\",\n        \"children\": [76, 73],\n        \"id\": 69,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"Join_12\"\n      },\n      \"70\": {\n        \"type\": \"HashJoin\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"inner join, equal:[eq(test.customer.c_custkey, test.orders.o_custkey)]\",\n        \"children\": [76, 73],\n        \"id\": 70,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"Join_12\"\n      },\n      \"71\": {\n        \"type\": \"TableFullScan\",\n        \"property\": \"\",\n        \"info\": \"table:orders, keep order:false\",\n        \"children\": null,\n        \"id\": 71,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"\"\n      },\n      \"72\": {\n        \"type\": \"Selection\",\n        \"property\": \"\",\n        \"info\": \"lt(test.orders.o_orderdate, 1995-03-13 00:00:00.000000)\",\n        \"children\": [71],\n        \"id\": 72,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"\"\n      },\n      \"73\": {\n        \"type\": \"TableReader\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"data:Selection_72\",\n        \"children\": [72],\n        \"id\": 73,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"DataSource_2\"\n      },\n      \"74\": {\n        \"type\": \"TableFullScan\",\n        \"property\": \"\",\n        \"info\": \"table:customer, keep order:false\",\n        \"children\": null,\n        \"id\": 74,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"\"\n      },\n      \"75\": {\n        \"type\": \"Selection\",\n        \"property\": \"\",\n        \"info\": \"eq(test.customer.c_mktsegment, 'AUTOMOBILE')\",\n        \"children\": [74],\n        \"id\": 75,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"\"\n      },\n      \"76\": {\n        \"type\": \"TableReader\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"data:Selection_75\",\n        \"children\": [75],\n        \"id\": 76,\n        \"cost\": 0,\n        \"selected\": true,\n        \"mapping\": \"DataSource_1\"\n      },\n      \"77\": {\n        \"type\": \"TableFullScan\",\n        \"property\": \"\",\n        \"info\": \"table:lineitem, keep order:false\",\n        \"children\": null,\n        \"id\": 77,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"78\": {\n        \"type\": \"Selection\",\n        \"property\": \"\",\n        \"info\": \"gt(test.lineitem.l_shipdate, 1995-03-13 00:00:00.000000)\",\n        \"children\": [77],\n        \"id\": 78,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"\"\n      },\n      \"79\": {\n        \"type\": \"TableReader\",\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"data:Selection_78\",\n        \"children\": [78],\n        \"id\": 79,\n        \"cost\": 0,\n        \"selected\": false,\n        \"mapping\": \"DataSource_4\"\n      }\n    },\n    \"final\": [\n      {\n        \"type\": \"TableFullScan\",\n        \"property\": \"\",\n        \"info\": \"table:customer, keep order:false\",\n        \"children\": [],\n        \"id\": 74,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"Selection\",\n        \"property\": \"\",\n        \"info\": \"eq(test.customer.c_mktsegment, 'AUTOMOBILE')\",\n        \"children\": [74],\n        \"id\": 75,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"TableReader\",\n        \"property\": \"\",\n        \"info\": \"data:Selection_75\",\n        \"children\": [75],\n        \"id\": 76,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"TableFullScan\",\n        \"property\": \"\",\n        \"info\": \"table:orders, keep order:false\",\n        \"children\": [],\n        \"id\": 71,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"Selection\",\n        \"property\": \"\",\n        \"info\": \"lt(test.orders.o_orderdate, 1995-03-13 00:00:00.000000)\",\n        \"children\": [71],\n        \"id\": 72,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"TableReader\",\n        \"property\": \"\",\n        \"info\": \"data:Selection_72\",\n        \"children\": [72],\n        \"id\": 73,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"HashJoin\",\n        \"property\": \"\",\n        \"info\": \"inner join, equal:[eq(test.customer.c_custkey, test.orders.o_custkey)]\",\n        \"children\": [76, 73],\n        \"id\": 70,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"IndexRangeScan\",\n        \"property\": \"\",\n        \"info\": \"table:lineitem, index:PRIMARY(L_ORDERKEY, L_LINENUMBER), range: decided by [eq(test.lineitem.l_orderkey, test.orders.o_orderkey)], keep order:false\",\n        \"children\": [],\n        \"id\": 24,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"TableRowIDScan\",\n        \"property\": \"\",\n        \"info\": \"table:lineitem, keep order:false\",\n        \"children\": [],\n        \"id\": 25,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"Selection\",\n        \"property\": \"\",\n        \"info\": \"gt(test.lineitem.l_shipdate, 1995-03-13 00:00:00.000000)\",\n        \"children\": [25],\n        \"id\": 26,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"IndexLookUp\",\n        \"property\": \"\",\n        \"info\": \"\",\n        \"children\": [24, 26],\n        \"id\": 27,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"IndexJoin\",\n        \"property\": \"\",\n        \"info\": \"inner join, inner:IndexLookUp_27, outer key:test.orders.o_orderkey, inner key:test.lineitem.l_orderkey, equal cond:eq(test.orders.o_orderkey, test.lineitem.l_orderkey)\",\n        \"children\": [70, 27],\n        \"id\": 28,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"HashAgg\",\n        \"property\": \"\",\n        \"info\": \"group by:test.lineitem.l_orderkey, test.orders.o_orderdate, test.orders.o_shippriority, funcs:sum(mul(test.lineitem.l_extendedprice, minus(1, test.lineitem.l_discount)))->Column#35, funcs:firstrow(test.orders.o_orderdate)->test.orders.o_orderdate, funcs:firstrow(test.orders.o_shippriority)->test.orders.o_shippriority, funcs:firstrow(test.lineitem.l_orderkey)->test.lineitem.l_orderkey\",\n        \"children\": [28],\n        \"id\": 22,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"TopN\",\n        \"property\": \"\",\n        \"info\": \"Column#35:desc, test.orders.o_orderdate, offset:0, count:10\",\n        \"children\": [22],\n        \"id\": 17,\n        \"cost\": 0,\n        \"selected\": false\n      },\n      {\n        \"type\": \"Projection\",\n        \"property\": \"\",\n        \"info\": \"test.lineitem.l_orderkey, Column#35, test.orders.o_orderdate, test.orders.o_shippriority\",\n        \"children\": [17],\n        \"id\": 14,\n        \"cost\": 0,\n        \"selected\": false\n      }\n    ]\n  },\n  \"final\": [\n    {\n      \"type\": \"TableFullScan\",\n      \"property\": \"\",\n      \"info\": \"table:customer, keep order:false\",\n      \"children\": [],\n      \"id\": 74,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"Selection\",\n      \"property\": \"\",\n      \"info\": \"eq(test.customer.c_mktsegment, 'AUTOMOBILE')\",\n      \"children\": [74],\n      \"id\": 75,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"TableReader\",\n      \"property\": \"\",\n      \"info\": \"data:Selection_75\",\n      \"children\": [75],\n      \"id\": 76,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"TableFullScan\",\n      \"property\": \"\",\n      \"info\": \"table:orders, keep order:false\",\n      \"children\": [],\n      \"id\": 71,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"Selection\",\n      \"property\": \"\",\n      \"info\": \"lt(test.orders.o_orderdate, 1995-03-13 00:00:00.000000)\",\n      \"children\": [71],\n      \"id\": 72,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"TableReader\",\n      \"property\": \"\",\n      \"info\": \"data:Selection_72\",\n      \"children\": [72],\n      \"id\": 73,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"HashJoin\",\n      \"property\": \"\",\n      \"info\": \"inner join, equal:[eq(test.customer.c_custkey, test.orders.o_custkey)]\",\n      \"children\": [76, 73],\n      \"id\": 70,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"IndexRangeScan\",\n      \"property\": \"\",\n      \"info\": \"table:lineitem, index:PRIMARY(L_ORDERKEY, L_LINENUMBER), range: decided by [eq(test.lineitem.l_orderkey, test.orders.o_orderkey)], keep order:false\",\n      \"children\": [],\n      \"id\": 24,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"TableRowIDScan\",\n      \"property\": \"\",\n      \"info\": \"table:lineitem, keep order:false\",\n      \"children\": [],\n      \"id\": 25,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"Selection\",\n      \"property\": \"\",\n      \"info\": \"gt(test.lineitem.l_shipdate, 1995-03-13 00:00:00.000000)\",\n      \"children\": [25],\n      \"id\": 26,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"IndexLookUp\",\n      \"property\": \"\",\n      \"info\": \"\",\n      \"children\": [24, 26],\n      \"id\": 27,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"IndexJoin\",\n      \"property\": \"\",\n      \"info\": \"inner join, inner:IndexLookUp_27, outer key:test.orders.o_orderkey, inner key:test.lineitem.l_orderkey, equal cond:eq(test.orders.o_orderkey, test.lineitem.l_orderkey)\",\n      \"children\": [70, 27],\n      \"id\": 28,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"Projection\",\n      \"property\": \"\",\n      \"info\": \"mul(test.lineitem.l_extendedprice, minus(1, test.lineitem.l_discount))->Column#44, test.orders.o_orderdate, test.orders.o_shippriority, test.lineitem.l_orderkey, test.lineitem.l_orderkey, test.orders.o_orderdate, test.orders.o_shippriority\",\n      \"children\": [28],\n      \"id\": 82,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"HashAgg\",\n      \"property\": \"\",\n      \"info\": \"group by:Column#48, Column#49, Column#50, funcs:sum(Column#44)->Column#35, funcs:firstrow(Column#45)->test.orders.o_orderdate, funcs:firstrow(Column#46)->test.orders.o_shippriority, funcs:firstrow(Column#47)->test.lineitem.l_orderkey\",\n      \"children\": [82],\n      \"id\": 22,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"TopN\",\n      \"property\": \"\",\n      \"info\": \"Column#35:desc, test.orders.o_orderdate, offset:0, count:10\",\n      \"children\": [22],\n      \"id\": 17,\n      \"cost\": 0,\n      \"selected\": false\n    },\n    {\n      \"type\": \"Projection\",\n      \"property\": \"\",\n      \"info\": \"test.lineitem.l_orderkey, Column#35, test.orders.o_orderdate, test.orders.o_shippriority\",\n      \"children\": [17],\n      \"id\": 14,\n      \"cost\": 0,\n      \"selected\": false\n    }\n  ],\n  \"isFastPlan\": false\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/OptimizerTrace/examples/old-format.json",
    "content": "{\n  \"logical\": {\n    \"final\": [\n      {\n        \"id\": 1,\n        \"type\": \"DataSource\",\n        \"children\": [],\n        \"cost\": 0,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"table:t1\"\n      },\n      {\n        \"id\": 3,\n        \"type\": \"DataSource\",\n        \"children\": [],\n        \"cost\": 0,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"table:t2\"\n      },\n      {\n        \"id\": 14,\n        \"type\": \"TopN\",\n        \"children\": [3],\n        \"cost\": 0,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"test.t2.b, offset:0, count:1\"\n      },\n      {\n        \"id\": 5,\n        \"type\": \"Aggregation\",\n        \"children\": [14],\n        \"cost\": 0,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"funcs:min(test.t2.b)\"\n      },\n      {\n        \"id\": 13,\n        \"type\": \"Selection\",\n        \"children\": [5],\n        \"cost\": 0,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"not(isnull(Column#7))\"\n      },\n      {\n        \"id\": 8,\n        \"type\": \"Apply\",\n        \"children\": [1, 13],\n        \"cost\": 0,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"inner join, other cond:gt(test.t1.b, Column#7)\"\n      },\n      {\n        \"id\": 9,\n        \"type\": \"Projection\",\n        \"children\": [8],\n        \"cost\": 0,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"test.t1.b\"\n      }\n    ],\n    \"steps\": [\n      {\n        \"index\": 1,\n        \"before\": [\n          {\n            \"id\": 1,\n            \"type\": \"DataSource\",\n            \"children\": [],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"table:t1\"\n          },\n          {\n            \"id\": 3,\n            \"type\": \"DataSource\",\n            \"children\": [],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"table:t2\"\n          },\n          {\n            \"id\": 4,\n            \"type\": \"Selection\",\n            \"children\": [3],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"lt(test.t2.a, test.t1.a)\"\n          },\n          {\n            \"id\": 5,\n            \"type\": \"Aggregation\",\n            \"children\": [4],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"funcs:min(test.t2.b), firstrow(test.t2.a), firstrow(test.t2.b), firstrow(test.t2._tidb_rowid)\"\n          },\n          {\n            \"id\": 6,\n            \"type\": \"Projection\",\n            \"children\": [5],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"Column#7\"\n          },\n          {\n            \"id\": 7,\n            \"type\": \"MaxOneRow\",\n            \"children\": [6],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"\"\n          },\n          {\n            \"id\": 8,\n            \"type\": \"Apply\",\n            \"children\": [1, 7],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"left outer join\"\n          },\n          {\n            \"id\": 2,\n            \"type\": \"Selection\",\n            \"children\": [8],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"gt(test.t1.b, Column#7)\"\n          },\n          {\n            \"id\": 9,\n            \"type\": \"Projection\",\n            \"children\": [2],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"test.t1.b\"\n          }\n        ],\n        \"name\": \"column_prune\",\n        \"steps\": [\n          {\n            \"action\": \"Aggregation_5's columns[test.t2._tidb_rowid,test.t2.b,test.t2.a] have been pruned\",\n            \"reason\": \"\",\n            \"id\": 5,\n            \"type\": \"Aggregation\",\n            \"index\": 0\n          },\n          {\n            \"action\": \"Aggregation_5's aggregation functions[firstrow(test.t2._tidb_rowid),firstrow(test.t2.b),firstrow(test.t2.a)] have been pruned\",\n            \"reason\": \"\",\n            \"id\": 5,\n            \"type\": \"Aggregation\",\n            \"index\": 1\n          },\n          {\n            \"action\": \"DataSource_3's columns[test.t2._tidb_rowid] have been pruned\",\n            \"reason\": \"\",\n            \"id\": 3,\n            \"type\": \"DataSource\",\n            \"index\": 2\n          },\n          {\n            \"action\": \"DataSource_1's columns[test.t1._tidb_rowid] have been pruned\",\n            \"reason\": \"\",\n            \"id\": 1,\n            \"type\": \"DataSource\",\n            \"index\": 3\n          }\n        ]\n      },\n      {\n        \"index\": 4,\n        \"before\": [\n          {\n            \"id\": 1,\n            \"type\": \"DataSource\",\n            \"children\": [],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"table:t1\"\n          },\n          {\n            \"id\": 3,\n            \"type\": \"DataSource\",\n            \"children\": [],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"table:t2\"\n          },\n          {\n            \"id\": 4,\n            \"type\": \"Selection\",\n            \"children\": [3],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"lt(test.t2.a, test.t1.a)\"\n          },\n          {\n            \"id\": 5,\n            \"type\": \"Aggregation\",\n            \"children\": [4],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"funcs:min(test.t2.b)\"\n          },\n          {\n            \"id\": 6,\n            \"type\": \"Projection\",\n            \"children\": [5],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"Column#7\"\n          },\n          {\n            \"id\": 7,\n            \"type\": \"MaxOneRow\",\n            \"children\": [6],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"\"\n          },\n          {\n            \"id\": 8,\n            \"type\": \"Apply\",\n            \"children\": [1, 7],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"left outer join\"\n          },\n          {\n            \"id\": 2,\n            \"type\": \"Selection\",\n            \"children\": [8],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"gt(test.t1.b, Column#7)\"\n          },\n          {\n            \"id\": 9,\n            \"type\": \"Projection\",\n            \"children\": [2],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"test.t1.b\"\n          }\n        ],\n        \"name\": \"decorrelate\",\n        \"steps\": [\n          {\n            \"action\": \"MaxOneRow_7 removed from plan tree\",\n            \"reason\": \"\",\n            \"id\": 7,\n            \"type\": \"MaxOneRow\",\n            \"index\": 0\n          },\n          {\n            \"action\": \"Projection_6 is moved as Apply_8's parent\",\n            \"reason\": \"Apply_8's join type is left outer join, not semi join\",\n            \"id\": 6,\n            \"type\": \"Projection\",\n            \"index\": 1\n          }\n        ]\n      },\n      {\n        \"index\": 6,\n        \"before\": [\n          {\n            \"id\": 1,\n            \"type\": \"DataSource\",\n            \"children\": [],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"table:t1\"\n          },\n          {\n            \"id\": 3,\n            \"type\": \"DataSource\",\n            \"children\": [],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"table:t2\"\n          },\n          {\n            \"id\": 4,\n            \"type\": \"Selection\",\n            \"children\": [3],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"lt(test.t2.a, test.t1.a)\"\n          },\n          {\n            \"id\": 5,\n            \"type\": \"Aggregation\",\n            \"children\": [4],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"funcs:min(test.t2.b)\"\n          },\n          {\n            \"id\": 8,\n            \"type\": \"Apply\",\n            \"children\": [1, 5],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"left outer join\"\n          },\n          {\n            \"id\": 6,\n            \"type\": \"Projection\",\n            \"children\": [8],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"test.t1.a, test.t1.b, Column#7\"\n          },\n          {\n            \"id\": 2,\n            \"type\": \"Selection\",\n            \"children\": [6],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"gt(test.t1.b, Column#7)\"\n          },\n          {\n            \"id\": 9,\n            \"type\": \"Projection\",\n            \"children\": [2],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"test.t1.b\"\n          }\n        ],\n        \"name\": \"projection_eliminate\",\n        \"steps\": [\n          {\n            \"action\": \"Projection_6 is eliminated\",\n            \"reason\": \"Projection_6's Exprs are all Columns\",\n            \"id\": 6,\n            \"type\": \"Projection\",\n            \"index\": 0\n          }\n        ]\n      },\n      {\n        \"index\": 7,\n        \"before\": [\n          {\n            \"id\": 1,\n            \"type\": \"DataSource\",\n            \"children\": [],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"table:t1\"\n          },\n          {\n            \"id\": 3,\n            \"type\": \"DataSource\",\n            \"children\": [],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"table:t2\"\n          },\n          {\n            \"id\": 4,\n            \"type\": \"Selection\",\n            \"children\": [3],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"lt(test.t2.a, test.t1.a)\"\n          },\n          {\n            \"id\": 5,\n            \"type\": \"Aggregation\",\n            \"children\": [4],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"funcs:min(test.t2.b)\"\n          },\n          {\n            \"id\": 8,\n            \"type\": \"Apply\",\n            \"children\": [1, 5],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"left outer join\"\n          },\n          {\n            \"id\": 2,\n            \"type\": \"Selection\",\n            \"children\": [8],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"gt(test.t1.b, Column#7)\"\n          },\n          {\n            \"id\": 9,\n            \"type\": \"Projection\",\n            \"children\": [2],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"test.t1.b\"\n          }\n        ],\n        \"name\": \"max_min_eliminate\",\n        \"steps\": [\n          {\n            \"action\": \"add Selection_10,add Sort_11,add Limit_12 during eliminating Aggregation_5 min function\",\n            \"reason\": \"Aggregation_5 has only one function[min] without group by, the columns in Aggregation_5 shouldn't be NULL and needs NULL to be filtered out, the columns in Aggregation_5 should be sorted\",\n            \"id\": 5,\n            \"type\": \"Aggregation\",\n            \"index\": 0\n          }\n        ]\n      },\n      {\n        \"index\": 8,\n        \"before\": [\n          {\n            \"id\": 1,\n            \"type\": \"DataSource\",\n            \"children\": [],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"table:t1\"\n          },\n          {\n            \"id\": 3,\n            \"type\": \"DataSource\",\n            \"children\": [],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"table:t2\"\n          },\n          {\n            \"id\": 4,\n            \"type\": \"Selection\",\n            \"children\": [3],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"lt(test.t2.a, test.t1.a)\"\n          },\n          {\n            \"id\": 10,\n            \"type\": \"Selection\",\n            \"children\": [4],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"not(isnull(test.t2.b))\"\n          },\n          {\n            \"id\": 11,\n            \"type\": \"Sort\",\n            \"children\": [10],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"test.t2.b\"\n          },\n          {\n            \"id\": 12,\n            \"type\": \"Limit\",\n            \"children\": [11],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"offset:0, count:1\"\n          },\n          {\n            \"id\": 5,\n            \"type\": \"Aggregation\",\n            \"children\": [12],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"funcs:min(test.t2.b)\"\n          },\n          {\n            \"id\": 8,\n            \"type\": \"Apply\",\n            \"children\": [1, 5],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"left outer join\"\n          },\n          {\n            \"id\": 2,\n            \"type\": \"Selection\",\n            \"children\": [8],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"gt(test.t1.b, Column#7)\"\n          },\n          {\n            \"id\": 9,\n            \"type\": \"Projection\",\n            \"children\": [2],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"test.t1.b\"\n          }\n        ],\n        \"name\": \"predicate_push_down\",\n        \"steps\": [\n          {\n            \"action\": \"The conditions[not(isnull(test.t1.b))] are pushed down across DataSource_1\",\n            \"reason\": \"\",\n            \"id\": 1,\n            \"type\": \"DataSource\",\n            \"index\": 0\n          },\n          {\n            \"action\": \"The conditions[lt(test.t2.a, test.t1.a),not(isnull(test.t2.b))] are pushed down across DataSource_3\",\n            \"reason\": \"\",\n            \"id\": 3,\n            \"type\": \"DataSource\",\n            \"index\": 1\n          },\n          {\n            \"action\": \"Selection_4 is removed\",\n            \"reason\": \"The conditions[lt(test.t2.a, test.t1.a)] in Selection_4 are pushed down\",\n            \"id\": 4,\n            \"type\": \"Selection\",\n            \"index\": 2\n          },\n          {\n            \"action\": \"Selection_10 is removed\",\n            \"reason\": \"The conditions[not(isnull(test.t2.b))] in Selection_10 are pushed down\",\n            \"id\": 10,\n            \"type\": \"Selection\",\n            \"index\": 3\n          },\n          {\n            \"action\": \"add Selection_13 to connect Apply_8 and Aggregation_5\",\n            \"reason\": \"\",\n            \"id\": 13,\n            \"type\": \"Selection\",\n            \"index\": 4\n          },\n          {\n            \"action\": \"Selection_2 is removed\",\n            \"reason\": \"The conditions[gt(test.t1.b, Column#7)] in Selection_2 are pushed down\",\n            \"id\": 2,\n            \"type\": \"Selection\",\n            \"index\": 5\n          }\n        ]\n      },\n      {\n        \"index\": 13,\n        \"before\": [\n          {\n            \"id\": 1,\n            \"type\": \"DataSource\",\n            \"children\": [],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"table:t1\"\n          },\n          {\n            \"id\": 3,\n            \"type\": \"DataSource\",\n            \"children\": [],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"table:t2\"\n          },\n          {\n            \"id\": 11,\n            \"type\": \"Sort\",\n            \"children\": [3],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"test.t2.b\"\n          },\n          {\n            \"id\": 12,\n            \"type\": \"Limit\",\n            \"children\": [11],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"offset:0, count:1\"\n          },\n          {\n            \"id\": 5,\n            \"type\": \"Aggregation\",\n            \"children\": [12],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"funcs:min(test.t2.b)\"\n          },\n          {\n            \"id\": 13,\n            \"type\": \"Selection\",\n            \"children\": [5],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"not(isnull(Column#7))\"\n          },\n          {\n            \"id\": 8,\n            \"type\": \"Apply\",\n            \"children\": [1, 13],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"inner join, other cond:gt(test.t1.b, Column#7)\"\n          },\n          {\n            \"id\": 9,\n            \"type\": \"Projection\",\n            \"children\": [8],\n            \"cost\": 0,\n            \"selected\": false,\n            \"property\": \"\",\n            \"info\": \"test.t1.b\"\n          }\n        ],\n        \"name\": \"topn_push_down\",\n        \"steps\": [\n          {\n            \"action\": \"Limit_12 is converted into TopN_14\",\n            \"reason\": \"\",\n            \"id\": 14,\n            \"type\": \"TopN\",\n            \"index\": 0\n          },\n          {\n            \"action\": \"Sort_11 passes ByItems[test.t2.b] to TopN_14\",\n            \"reason\": \"TopN_14 is Limit originally\",\n            \"id\": 11,\n            \"type\": \"Sort\",\n            \"index\": 1\n          },\n          {\n            \"action\": \"TopN_14 is added as DataSource_3's parent\",\n            \"reason\": \"TopN is pushed down\",\n            \"id\": 14,\n            \"type\": \"TopN\",\n            \"index\": 2\n          }\n        ]\n      }\n    ]\n  },\n  \"physical\": {\n    \"final\": [\n      {\n        \"id\": 20,\n        \"type\": \"TableReader\",\n        \"children\": [],\n        \"cost\": 50823.833333333336,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"data:Selection_19\"\n      },\n      {\n        \"id\": 32,\n        \"type\": \"TableReader\",\n        \"children\": [],\n        \"cost\": 41600.8168,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"data:TopN_31\"\n      },\n      {\n        \"id\": 24,\n        \"type\": \"TopN\",\n        \"children\": [32],\n        \"cost\": 41603.8188,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"test.t2.b, offset:0, count:1\"\n      },\n      {\n        \"id\": 23,\n        \"type\": \"StreamAgg\",\n        \"children\": [24],\n        \"cost\": 41606.8188,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"funcs:min(test.t2.b)->Column#7\"\n      },\n      {\n        \"id\": 21,\n        \"type\": \"Selection\",\n        \"children\": [23],\n        \"cost\": 41609.8188,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"not(isnull(Column#7))\"\n      },\n      {\n        \"id\": 17,\n        \"type\": \"Apply\",\n        \"children\": [20, 21],\n        \"cost\": 415756889.64533335,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"CARTESIAN inner join, other cond:gt(test.t1.b, Column#7)\"\n      },\n      {\n        \"id\": 15,\n        \"type\": \"Projection\",\n        \"children\": [17],\n        \"cost\": 415762901.64533335,\n        \"selected\": false,\n        \"property\": \"\",\n        \"info\": \"test.t1.b\"\n      }\n    ],\n    \"selected_candidates\": [\n      {\n        \"id\": 17,\n        \"type\": \"Apply\",\n        \"children\": null,\n        \"cost\": 415756889.64533335,\n        \"selected\": true,\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"CARTESIAN inner join, other cond:gt(test.t1.b, Column#7)\",\n        \"mapping\": \"Apply_8\"\n      },\n      {\n        \"id\": 20,\n        \"type\": \"TableReader\",\n        \"children\": null,\n        \"cost\": 50823.833333333336,\n        \"selected\": true,\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"data:Selection_19\",\n        \"mapping\": \"DataSource_1\"\n      },\n      {\n        \"id\": 21,\n        \"type\": \"Selection\",\n        \"children\": null,\n        \"cost\": 41609.8188,\n        \"selected\": true,\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"not(isnull(Column#7))\",\n        \"mapping\": \"Selection_13\"\n      },\n      {\n        \"id\": 23,\n        \"type\": \"StreamAgg\",\n        \"children\": null,\n        \"cost\": 41606.8188,\n        \"selected\": true,\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"funcs:min(test.t2.b)->Column#7\",\n        \"mapping\": \"Aggregation_5\"\n      },\n      {\n        \"id\": 24,\n        \"type\": \"TopN\",\n        \"children\": null,\n        \"cost\": 41603.8188,\n        \"selected\": true,\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"test.t2.b, offset:0, count:1\",\n        \"mapping\": \"TopN_14\"\n      },\n      {\n        \"id\": 15,\n        \"type\": \"Projection\",\n        \"children\": null,\n        \"cost\": 415762901.64533335,\n        \"selected\": true,\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"test.t1.b\",\n        \"mapping\": \"Projection_9\"\n      }\n    ],\n    \"discarded_candidates\": [\n      {\n        \"id\": 22,\n        \"type\": \"HashAgg\",\n        \"children\": null,\n        \"cost\": 41637.4198,\n        \"selected\": false,\n        \"property\": \"Prop{cols: [], TaskTp: rootTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"funcs:min(test.t2.b)->Column#7\",\n        \"mapping\": \"Aggregation_5\"\n      },\n      {\n        \"id\": 30,\n        \"type\": \"Selection\",\n        \"children\": null,\n        \"cost\": 600020,\n        \"selected\": false,\n        \"property\": \"Prop{cols: [], TaskTp: copSingleReadTask, expectedCount: 1.7976931348623157e+308}\",\n        \"info\": \"lt(test.t2.a, test.t1.a), not(isnull(test.t2.b))\",\n        \"mapping\": \"DataSource_3\"\n      }\n    ]\n  },\n  \"final\": [\n    {\n      \"id\": 20,\n      \"type\": \"TableReader\",\n      \"children\": [],\n      \"cost\": 50823.833333333336,\n      \"selected\": false,\n      \"property\": \"\",\n      \"info\": \"data:Selection_19\"\n    },\n    {\n      \"id\": 32,\n      \"type\": \"TableReader\",\n      \"children\": [],\n      \"cost\": 41600.8168,\n      \"selected\": false,\n      \"property\": \"\",\n      \"info\": \"data:TopN_31\"\n    },\n    {\n      \"id\": 24,\n      \"type\": \"TopN\",\n      \"children\": [32],\n      \"cost\": 41603.8188,\n      \"selected\": false,\n      \"property\": \"\",\n      \"info\": \"test.t2.b, offset:0, count:1\"\n    },\n    {\n      \"id\": 23,\n      \"type\": \"StreamAgg\",\n      \"children\": [24],\n      \"cost\": 41606.8188,\n      \"selected\": false,\n      \"property\": \"\",\n      \"info\": \"funcs:min(test.t2.b)->Column#7\"\n    },\n    {\n      \"id\": 21,\n      \"type\": \"Selection\",\n      \"children\": [23],\n      \"cost\": 41609.8188,\n      \"selected\": false,\n      \"property\": \"\",\n      \"info\": \"not(isnull(Column#7))\"\n    },\n    {\n      \"id\": 17,\n      \"type\": \"Apply\",\n      \"children\": [20, 21],\n      \"cost\": 415756889.64533335,\n      \"selected\": false,\n      \"property\": \"\",\n      \"info\": \"CARTESIAN inner join, other cond:gt(test.t1.b, Column#7)\"\n    },\n    {\n      \"id\": 15,\n      \"type\": \"Projection\",\n      \"children\": [17],\n      \"cost\": 415762901.64533335,\n      \"selected\": false,\n      \"property\": \"\",\n      \"info\": \"test.t1.b\"\n    }\n  ],\n  \"isFastPlan\": false\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/OptimizerTrace/index.module.less",
    "content": ".container {\n  overflow: auto;\n}\n.operator_tree {\n  flex-shrink: 0;\n}\n\n.logical_optimize {\n  display: flex;\n  align-items: center;\n}\n\n.arrow {\n  margin: 0 10px;\n}\n\n.physical_operator_tree_container {\n  display: flex;\n  align-items: center;\n}\n\n.physical_operator_tree_modal_container {\n  flex-shrink: 0;\n  width: 100%;\n  height: 600px;\n}\n\n.unselected_candidates {\n  border: dashed 1px #ccc;\n  margin-left: 10px;\n  padding: 5px;\n}\n\n.selected_candidates {\n  border: dashed 1px white;\n  margin-left: 10px;\n  padding: 5px;\n}\n\n.selected_candidates p {\n  color: white;\n}\n\n.steps {\n  width: 200px;\n}\n\n.step_info {\n  max-height: 90px;\n  width: 200px;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/OptimizerTrace/index.tsx",
    "content": "import React, { useCallback, useContext, useState } from 'react'\nimport { HashRouter as Router, Routes, Route } from 'react-router-dom'\nimport {\n  Button,\n  Upload,\n  Alert,\n  Tooltip,\n  Modal,\n  Space,\n  Dropdown,\n  Menu\n} from 'antd'\nimport {\n  UploadOutlined,\n  ArrowRightOutlined,\n  DownOutlined\n} from '@ant-design/icons'\nimport { ErrorBoundary } from 'react-error-boundary'\n\nimport { Card, Root } from '@lib/components'\nimport { addTranslations } from '@lib/utils/i18n'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\n\nimport LogicalOperatorTree, {\n  LogicalOperatorNode\n} from './components/LogicalOperatorTree'\nimport PhysicalOperatorTree, {\n  PhysicalOperatorNode,\n  PhysicalOperatorTreeWithFullScreen\n} from './components/PhysicalOperatorTree'\nimport PhysicalCostTree, {\n  PhysicalCostMap\n} from './components/PhysicalCostTree'\nimport { OptimizerTraceContext } from './context'\nimport translations from './translations'\n\nimport styles from './index.module.less'\n\nimport oldFormatTrace from './examples/old-format.json'\nimport newFormatTrace from './examples/new-format.json'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/optimizer_trace\" element={<OptimizerTrace />} />\n    </Routes>\n  )\n}\n\nexport default function OptimizeTraceApp() {\n  const ctx = useContext(OptimizerTraceContext)\n  if (ctx === null) {\n    throw new Error('OptimizerTraceContext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\ninterface OptimizerData {\n  logical: {\n    final: LogicalOperatorNode[]\n    steps: LogicalOptimizeActionStep[]\n  }\n  physical: {\n    final: LogicalOperatorNode\n\n    // old format\n    selected_candidates?: PhysicalOperatorNode[]\n    discarded_candidates?: PhysicalOperatorNode[]\n\n    // new format\n    candidates?: {\n      [x: string]: PhysicalOperatorNode\n    }\n    costs?: PhysicalCostMap\n  }\n  final: LogicalOperatorNode[]\n  isFastPlan: boolean\n}\n\ninterface LogicalOptimizeActionStep {\n  index: number\n  name: string\n  before: LogicalOperatorNode[]\n  steps: {\n    id: number\n    index: number\n    action: string\n    reason: string\n    type: string\n  }[]\n}\n\nfunction OptimizerTrace() {\n  const [importedData, setImportedData] = useState<OptimizerData | null>(null)\n  const [errorMsg, setErrorMsg] = useState('')\n  const [fileName, setFileName] = useState('')\n\n  const handleBeforeUpload = useCallback(async (file: File) => {\n    setErrorMsg('')\n    setFileName(file.name)\n\n    const t = await file.text()\n    setImportedData(JSON.parse(t))\n    return false\n  }, [])\n\n  function menuItemClick({ key }) {\n    setFileName(key)\n    if (key === 'old') {\n      setImportedData(oldFormatTrace as any)\n    } else if (key === 'new') {\n      setImportedData(newFormatTrace as any)\n    }\n  }\n\n  const menu = (\n    <Menu onClick={menuItemClick}>\n      <Menu.Item key=\"old\">Old Format</Menu.Item>\n      <Menu.Item key=\"new\">New Format</Menu.Item>\n    </Menu>\n  )\n\n  return (\n    <div>\n      <Card noMarginBottom style={{ height: '70px' }}>\n        <Space style={{ alignItems: 'flex-start' }}>\n          <Upload beforeUpload={handleBeforeUpload} accept=\".json\" maxCount={1}>\n            <Button icon={<UploadOutlined />}>Select File</Button>\n          </Upload>\n          <Dropdown overlay={menu}>\n            <Button>\n              <Space>\n                Examples\n                <DownOutlined />\n              </Space>\n            </Button>\n          </Dropdown>\n        </Space>\n      </Card>\n\n      {errorMsg && (\n        <Card noMarginTop>\n          <Alert showIcon type=\"error\" message=\"Error\" description={errorMsg} />\n        </Card>\n      )}\n\n      <ErrorBoundary\n        FallbackComponent={({ error, resetErrorBoundary }) => {\n          setImportedData(null)\n          setErrorMsg(error.message)\n          resetErrorBoundary()\n          return null\n        }}\n      >\n        {importedData && (\n          // reset all state after uploading a new file\n          <div key={fileName}>\n            <LogicalOptimization data={importedData} />\n            <PhysicalOptimization data={importedData} />\n            <Final data={importedData} />\n          </div>\n        )}\n      </ErrorBoundary>\n    </div>\n  )\n}\n\nfunction LogicalOptimization({ data }: { data: OptimizerData }) {\n  const logicalData = data.logical\n  const Steps = () => (\n    <>\n      {logicalData.steps.map((s) => {\n        const Action = () => (\n          <div className={styles.steps}>\n            <h3>{s.name}</h3>\n            {s.steps.map((actionStep, index) => {\n              const content = `action ${actionStep.index}: ${actionStep.action}\n              ${actionStep.reason && `, reason: ${actionStep.reason}`}`\n              return (\n                <Tooltip title={content}>\n                  <p key={index} className={styles.step_info}>\n                    {content}\n                  </p>\n                </Tooltip>\n              )\n            })}\n          </div>\n        )\n        return (\n          <React.Fragment key={s.index}>\n            <LogicalOperatorTree\n              className={styles.operator_tree}\n              data={s.before}\n            />\n            <ArrowRightOutlined\n              style={{ fontSize: '30px' }}\n              className={styles.arrow}\n            />\n            <Action />\n            <ArrowRightOutlined\n              style={{ fontSize: '30px' }}\n              className={styles.arrow}\n            />\n          </React.Fragment>\n        )\n      })}\n    </>\n  )\n\n  return (\n    <Card className={styles.container}>\n      <h2>Logical Optimization</h2>\n      <div className={styles.logical_optimize}>\n        <Steps />\n        <LogicalOperatorTree\n          className={styles.operator_tree}\n          data={logicalData.final}\n          labels={{ color: 'blue' }}\n        />\n      </div>\n    </Card>\n  )\n}\n\nfunction PhysicalOptimization({ data }: { data: OptimizerData }) {\n  const [physicalNodeName, setPhysicalNodeName] = useState('')\n  const [logicalNodeName, setLogicalNodeName] = useState('')\n\n  const [showCostTreeModal, setShowCostTreeModal] = useState(false)\n  const [fullScreenPhysicalNode, setFullScreenPhysicalNode] = useState<\n    PhysicalOperatorNode | undefined\n  >(undefined)\n\n  const physicalData = data.physical\n\n  let allCandidatesMap: { [x: string]: PhysicalOperatorNode } = {}\n\n  if (physicalData.candidates) {\n    // new format\n    allCandidatesMap = physicalData.candidates\n  } else {\n    // old format\n    const selectedCandidates = physicalData.selected_candidates || []\n    const discardedCandidates = physicalData.discarded_candidates || []\n\n    const allCandidates = [...selectedCandidates, ...discardedCandidates]\n    allCandidatesMap = allCandidates.reduce((acc, c) => {\n      acc[c.id] = c\n      return acc\n    }, {} as { [props: string]: PhysicalOperatorNode })\n  }\n\n  // convert to tree\n  Object.values(allCandidatesMap).forEach((c) => {\n    c.childrenNodes = (c.children || []).map((i) => allCandidatesMap[i])\n    // fix cost\n    c.cost = physicalData.costs?.[`${c.type}_${c.id}`]?.cost ?? c.cost\n  })\n\n  const operatorCandidates = Object.values(allCandidatesMap).reduce(\n    (acc, c) => {\n      if (c.mapping === '') {\n        return acc\n      }\n      if (!acc[c.mapping]) {\n        acc[c.mapping] = []\n      }\n      acc[c.mapping].push(c)\n      return acc\n    },\n    {} as { [props: string]: PhysicalOperatorNode[] }\n  )\n\n  function updatePhysicalNodeName(name: string) {\n    setPhysicalNodeName(name)\n    setShowCostTreeModal(true)\n  }\n\n  const OperatorCandidates = () => {\n    const selectedCandidates = operatorCandidates[logicalNodeName].filter(\n      (c) => c.selected\n    )\n    const unselectedCandidates = operatorCandidates[logicalNodeName].filter(\n      (c) => !c.selected\n    )\n    return (\n      <div className={styles.physical_operator_tree_container}>\n        {!!selectedCandidates.length && (\n          <div className={styles.selected_candidates}>\n            <p>selected candidates</p>\n            <div className={styles.physical_operator_tree_container}>\n              {selectedCandidates.map((c) => (\n                <PhysicalOperatorTreeWithFullScreen\n                  key={c.id}\n                  data={c}\n                  className={styles.operator_tree}\n                  onSelect={updatePhysicalNodeName}\n                  nodeName={physicalNodeName}\n                  onFullScreen={() => setFullScreenPhysicalNode(c)}\n                />\n              ))}\n            </div>\n          </div>\n        )}\n        {!!unselectedCandidates.length && (\n          <div className={styles.unselected_candidates}>\n            <p>unselected candidates</p>\n            <div className={styles.physical_operator_tree_container}>\n              {unselectedCandidates.map((c) => (\n                <PhysicalOperatorTreeWithFullScreen\n                  key={c.id}\n                  data={c}\n                  className={styles.operator_tree}\n                  onSelect={updatePhysicalNodeName}\n                  nodeName={physicalNodeName}\n                  onFullScreen={() => setFullScreenPhysicalNode(c)}\n                />\n              ))}\n            </div>\n          </div>\n        )}\n      </div>\n    )\n  }\n\n  return (\n    <Card className={styles.container}>\n      <h2>\n        Physical Optimization {logicalNodeName && `for ${logicalNodeName}`}\n      </h2>\n      <div className={styles.physical_operator_tree_container}>\n        <LogicalOperatorTree\n          className={styles.operator_tree}\n          data={data.logical.final}\n          nodeName={logicalNodeName}\n          onSelect={setLogicalNodeName}\n        />\n        <ArrowRightOutlined\n          style={{ fontSize: '30px' }}\n          className={styles.arrow}\n        />\n        {logicalNodeName && <OperatorCandidates />}\n      </div>\n\n      <Modal\n        title={`Physical Optimization for ${logicalNodeName}`}\n        style={{ top: 50 }}\n        width=\"60%\"\n        visible={fullScreenPhysicalNode !== undefined}\n        onCancel={() => setFullScreenPhysicalNode(undefined)}\n        footer={null}\n        destroyOnClose={true}\n      >\n        <PhysicalOperatorTree\n          data={fullScreenPhysicalNode!}\n          className={styles.physical_operator_tree_modal_container}\n          onSelect={updatePhysicalNodeName}\n          nodeName={physicalNodeName}\n        />\n      </Modal>\n\n      <Modal\n        title={`Cost for ${physicalNodeName}`}\n        style={{ top: 50 }}\n        width=\"90%\"\n        visible={showCostTreeModal}\n        onCancel={() => setShowCostTreeModal(false)}\n        footer={null}\n        destroyOnClose={true}\n      >\n        <PhysicalCostTree\n          costs={physicalData.costs ?? {}}\n          name={physicalNodeName}\n        />\n      </Modal>\n    </Card>\n  )\n}\n\nfunction Final({ data }: { data: OptimizerData }) {\n  const finalData = data.final\n\n  return (\n    <Card className={styles.container}>\n      <h2>Final</h2>\n      <LogicalOperatorTree\n        className={styles.operator_tree}\n        data={finalData}\n        labels={{ color: 'blue' }}\n      />\n    </Card>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/OptimizerTrace/translations/en.yaml",
    "content": "optimizer_trace:\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/OptimizerTrace/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/OptimizerTrace/translations/zh.yaml",
    "content": "optimizer_trace:\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Overview/components/Instances.tsx",
    "content": "import { Link } from 'react-router-dom'\nimport React, { useContext, useMemo } from 'react'\nimport { Card, AnimatedSkeleton, Descriptions } from '@lib/components'\nimport { useTranslation } from 'react-i18next'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { Typography, Row, Col, Space } from 'antd'\nimport {\n  STATUS_OFFLINE,\n  STATUS_TOMBSTONE,\n  STATUS_UP\n} from '@lib/apps/ClusterInfo/status/status'\nimport { RightOutlined, WarningOutlined } from '@ant-design/icons'\nimport { Stack } from 'office-ui-fabric-react/lib/Stack'\n\nimport styles from './Styles.module.less'\nimport { OverviewContext } from '../context'\n\nfunction ComponentItem(props: {\n  name: string\n  resp: { data?: { status?: number }[]; isLoading: boolean; error?: any }\n}) {\n  const { name, resp } = props\n  const [upNums, allNums] = useMemo(() => {\n    if (!resp.data) {\n      return [0, 0]\n    }\n    let up = 0\n    let all = 0\n    for (const instance of resp.data) {\n      all++\n      if (\n        instance.status === STATUS_UP ||\n        instance.status === STATUS_TOMBSTONE ||\n        instance.status === STATUS_OFFLINE\n      ) {\n        up++\n      }\n    }\n    return [up, all]\n  }, [resp])\n  // query TiCDC and TiProxy components returns 404 under TiDB 7.6.0\n  const notFoundError = resp.error?.response?.status === 404\n\n  return (\n    <AnimatedSkeleton showSkeleton={resp.isLoading} paragraph={{ rows: 1 }}>\n      {!resp.error && (\n        <Descriptions column={1}>\n          <Descriptions.Item label={name}>\n            <Typography.Text type={upNums === allNums ? undefined : 'danger'}>\n              <span className={styles.big}>{upNums}</span>\n              <small> / {allNums}</small>\n            </Typography.Text>\n          </Descriptions.Item>\n        </Descriptions>\n      )}\n      {resp.error && !notFoundError && (\n        <Typography.Text type=\"danger\">\n          <Space>\n            <WarningOutlined /> Error\n          </Space>\n        </Typography.Text>\n      )}\n    </AnimatedSkeleton>\n  )\n}\n\nexport default function Nodes() {\n  const { t } = useTranslation()\n\n  const ctx = useContext(OverviewContext)\n\n  const tidbResp = useClientRequest(ctx!.ds.getTiDBTopology)\n  const storeResp = useClientRequest(ctx!.ds.getStoreTopology)\n  const tiKVResp = {\n    ...storeResp,\n    data: storeResp.data?.tikv\n  }\n  const tiFlashResp = {\n    ...storeResp,\n    data: storeResp.data?.tiflash\n  }\n  const pdResp = useClientRequest(ctx!.ds.getPDTopology)\n  const tiCDCResp = useClientRequest(ctx!.ds.getTiCDCTopology)\n  const tiProxyResp = useClientRequest(ctx!.ds.getTiProxyTopology)\n  const tsoResp = useClientRequest(ctx!.ds.getTSOTopology)\n  const schedulingResp = useClientRequest(ctx!.ds.getSchedulingTopology)\n\n  return (\n    <Card\n      title={\n        <Link to=\"/cluster_info\">\n          {t('overview.instances.title')}\n          <RightOutlined />\n        </Link>\n      }\n      noMarginRight\n    >\n      <Stack tokens={{ childrenGap: 16 }}>\n        <Row>\n          <Col span={12}>\n            <ComponentItem name={t('distro.pd')} resp={pdResp} />\n          </Col>\n          <Col span={12}>\n            <ComponentItem name={t('distro.tidb')} resp={tidbResp} />\n          </Col>\n        </Row>\n        <Row>\n          <Col span={12}>\n            <ComponentItem name={t('distro.tikv')} resp={tiKVResp} />\n          </Col>\n          <Col span={12}>\n            <ComponentItem name={t('distro.tiflash')} resp={tiFlashResp} />\n          </Col>\n        </Row>\n        <Row>\n          <Col span={12}>\n            <ComponentItem name={t('distro.ticdc')} resp={tiCDCResp} />\n          </Col>\n          <Col span={12}>\n            <ComponentItem name={t('distro.tiproxy')} resp={tiProxyResp} />\n          </Col>\n        </Row>\n        <Row>\n          <Col span={12}>\n            <ComponentItem name={t('distro.tso')} resp={tsoResp} />\n          </Col>\n          <Col span={12}>\n            <ComponentItem\n              name={t('distro.scheduling')}\n              resp={schedulingResp}\n            />\n          </Col>\n        </Row>\n      </Stack>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Overview/components/Metrics.tsx",
    "content": "import { Space, Typography, Button, Tooltip } from 'antd'\nimport React, { useCallback, useContext, useRef, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useMemoizedFn } from 'ahooks'\nimport { MetricsChart, SyncChartPointer, TimeRangeValue } from 'metrics-chart'\nimport { Link } from 'react-router-dom'\nimport { Stack } from 'office-ui-fabric-react'\nimport { LoadingOutlined, FileTextOutlined } from '@ant-design/icons'\nimport { debounce } from 'lodash'\n\nimport {\n  AutoRefreshButton,\n  Card,\n  DEFAULT_TIME_RANGE,\n  TimeRange,\n  Toolbar,\n  ErrorBar,\n  LimitTimeRange\n} from '@lib/components'\nimport { useTimeRangeValue } from '@lib/components/TimeRangeSelector/hook'\n\nimport { OverviewContext } from '../context'\nimport { telemetry } from '../utils/telemetry'\n\nexport default function Metrics() {\n  const ctx = useContext(OverviewContext)\n  const promAddrConfigurable = ctx?.cfg.promAddrConfigurable || false\n\n  const [timeRange, setTimeRange] = useState<TimeRange>(DEFAULT_TIME_RANGE)\n  const [chartRange, setChartRange] = useTimeRangeValue(timeRange, setTimeRange)\n  const loadingCounter = useRef(0)\n  const [isSomeLoading, setIsSomeLoading] = useState(false)\n  const { t } = useTranslation()\n\n  // eslint-disable-next-line\n  const setIsSomeLoadingDebounce = useCallback(\n    debounce(setIsSomeLoading, 100, { leading: true }),\n    []\n  )\n\n  const onLoadingStateChange = useMemoizedFn((loading: boolean) => {\n    loading\n      ? (loadingCounter.current += 1)\n      : loadingCounter.current > 0 && (loadingCounter.current -= 1)\n    setIsSomeLoadingDebounce(loadingCounter.current > 0)\n  })\n\n  const handleManualRefreshClick = () => {\n    telemetry.clickManualRefresh()\n    return setTimeRange((r) => ({ ...r }))\n  }\n\n  const handleOnBrush = (range: TimeRangeValue) => {\n    setChartRange(range)\n  }\n\n  const ErrorComponent = (error: Error) => (\n    <Space direction=\"vertical\">\n      <ErrorBar errors={[error]} />\n      {promAddrConfigurable && (\n        <Link to=\"/user_profile?blink=profile.prometheus\">\n          {t('overview.change_prom_button')}\n        </Link>\n      )}\n    </Space>\n  )\n\n  return (\n    <>\n      <Card>\n        <Toolbar>\n          <Space>\n            <LimitTimeRange\n              value={timeRange}\n              recent_seconds={ctx?.cfg.timeRangeSelector?.recent_seconds}\n              customAbsoluteRangePicker={\n                ctx?.cfg.timeRangeSelector?.customAbsoluteRangePicker\n              }\n              onChange={(v) => {\n                setTimeRange(v)\n                telemetry.selectTimeRange(v)\n              }}\n              onZoomOutClick={(start, end) =>\n                telemetry.clickZoomOut([start, end])\n              }\n            />\n            <AutoRefreshButton\n              onChange={telemetry.selectAutoRefreshOption}\n              onRefresh={handleManualRefreshClick}\n              disabled={isSomeLoading}\n            />\n            {ctx?.cfg.metricsReferenceLink && (\n              <Tooltip placement=\"top\" title={t('overview.panel_no_data_tips')}>\n                <a\n                  href={ctx.cfg.metricsReferenceLink}\n                  target=\"_blank\"\n                  rel=\"noopener noreferrer\"\n                >\n                  <FileTextOutlined\n                    onClick={() => telemetry.clickDocumentationIcon()}\n                  />\n                </a>\n              </Tooltip>\n            )}\n            {isSomeLoading && <LoadingOutlined />}\n          </Space>\n          <Space>\n            <Link to={`/monitoring`}>\n              <Button type=\"primary\" onClick={telemetry.clickViewMoreMetrics}>\n                {t('overview.view_more_metrics')}\n              </Button>\n            </Link>\n          </Space>\n        </Toolbar>\n      </Card>\n      <SyncChartPointer>\n        <Stack tokens={{ childrenGap: 16 }}>\n          {ctx?.cfg.metricsQueries.map((item) => (\n            <Card noMarginTop noMarginBottom>\n              <Typography.Title level={5}>\n                {t(`overview.metrics.${item.title}`)}\n              </Typography.Title>\n              <MetricsChart\n                queries={item.queries}\n                range={chartRange}\n                nullValue={item.nullValue}\n                unit={item.unit!}\n                fetchPromeData={ctx!.ds.metricsQueryGet}\n                onLoading={onLoadingStateChange}\n                onBrush={handleOnBrush}\n                errorComponent={ErrorComponent}\n                onClickSeriesLabel={(seriesName) =>\n                  telemetry.clickSeriesLabel(item.title, seriesName)\n                }\n              />\n            </Card>\n          ))}\n        </Stack>\n      </SyncChartPointer>\n    </>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Overview/components/MonitorAlert.tsx",
    "content": "import React, { useContext, useEffect, useState } from 'react'\nimport { RightOutlined, WarningOutlined } from '@ant-design/icons'\nimport { Card, AnimatedSkeleton } from '@lib/components'\nimport { Link } from 'react-router-dom'\nimport { useTranslation } from 'react-i18next'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { Space, Typography } from 'antd'\nimport { Stack } from 'office-ui-fabric-react/lib/Stack'\nimport { OverviewContext } from '../context'\n\nexport default function MonitorAlert() {\n  const ctx = useContext(OverviewContext)\n\n  const { t } = useTranslation()\n  const [alertCounter, setAlertCounter] = useState(0)\n\n  const { data: amData, isLoading: amIsLoading } = useClientRequest(\n    ctx!.ds.getAlertManagerTopology\n  )\n  const { data: grafanaData, isLoading: grafanaIsLoading } = useClientRequest(\n    ctx!.ds.getGrafanaTopology\n  )\n\n  useEffect(() => {\n    if (!amData) {\n      return\n    }\n    async function fetch() {\n      let resp = await ctx!.ds.getAlertManagerCounts(\n        `${amData!.ip}:${amData!.port}`,\n        {\n          handleError: 'custom'\n        }\n      )\n      setAlertCounter(resp.data)\n    }\n    fetch()\n  }, [amData, ctx])\n\n  return (\n    <Card title={t('overview.monitor_alert.title')} noMarginRight>\n      <Stack tokens={{ childrenGap: 16 }}>\n        <AnimatedSkeleton\n          showSkeleton={grafanaIsLoading}\n          paragraph={{ rows: 1 }}\n        >\n          {!grafanaData && (\n            <Typography.Text type=\"warning\">\n              <Space>\n                <WarningOutlined />\n                {t('overview.monitor_alert.view_monitor_warn')}\n              </Space>\n            </Typography.Text>\n          )}\n          {grafanaData && (\n            <a\n              href={`http://${grafanaData.ip}:${grafanaData.port}`}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n            >\n              <Space>\n                {t('overview.monitor_alert.view_grafana_monitor')}\n                <RightOutlined />\n              </Space>\n            </a>\n          )}\n        </AnimatedSkeleton>\n        <AnimatedSkeleton showSkeleton={amIsLoading} paragraph={{ rows: 1 }}>\n          {!amData && (\n            <Typography.Text type=\"warning\">\n              <Space>\n                <WarningOutlined />\n                {t('overview.monitor_alert.view_alerts_warn')}\n              </Space>\n            </Typography.Text>\n          )}\n          {amData && (\n            <a\n              href={`http://${amData.ip}:${amData.port}`}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n            >\n              <Space>\n                <Typography.Text type={alertCounter > 0 ? 'danger' : undefined}>\n                  {alertCounter === 0\n                    ? t('overview.monitor_alert.view_zero_alerts')\n                    : t('overview.monitor_alert.view_alerts', {\n                        alertCount: alertCounter\n                      })}\n                </Typography.Text>\n                <RightOutlined />\n              </Space>\n            </a>\n          )}\n        </AnimatedSkeleton>\n        <div>\n          <Link to={`/diagnose`}>\n            <Space>\n              {t('overview.monitor_alert.run_diagnose')}\n              <RightOutlined />\n            </Space>\n          </Link>\n        </div>\n      </Stack>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Overview/components/Styles.module.less",
    "content": ".big {\n  font-size: larger;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Overview/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport {\n  TopologyPDInfo,\n  TopologyTiDBInfo,\n  TopologyGrafanaInfo,\n  TopologyAlertManagerInfo,\n  ClusterinfoStoreTopologyResponse,\n  MetricsQueryResponse,\n  TopologyTiCDCInfo,\n  TopologyTiProxyInfo,\n  TopologyTSOInfo,\n  TopologySchedulingInfo\n} from '@lib/client'\n\nimport { IContextConfig, ReqConfig } from '@lib/types'\nimport { QueryConfig, TransformNullValue } from 'metrics-chart'\nexport interface OverviewMetricsQueryType {\n  title: string\n  queries: QueryConfig[]\n  unit: string\n  nullValue?: TransformNullValue\n}\n\ninterface IMetricConfig {\n  metricsQueries: OverviewMetricsQueryType[]\n  promAddrConfigurable?: boolean\n  timeRangeSelector?: {\n    recent_seconds: number[]\n    customAbsoluteRangePicker: boolean\n  }\n  metricsReferenceLink?: string\n}\n\nexport interface IOverviewDataSource {\n  getTiDBTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTiDBInfo>>\n\n  getStoreTopology(\n    options?: ReqConfig\n  ): AxiosPromise<ClusterinfoStoreTopologyResponse>\n\n  getPDTopology(options?: ReqConfig): AxiosPromise<Array<TopologyPDInfo>>\n\n  getTiCDCTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTiCDCInfo>>\n\n  getTiProxyTopology(\n    options?: ReqConfig\n  ): AxiosPromise<Array<TopologyTiProxyInfo>>\n\n  getTSOTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTSOInfo>>\n\n  getSchedulingTopology(\n    options?: ReqConfig\n  ): AxiosPromise<Array<TopologySchedulingInfo>>\n\n  getGrafanaTopology(options?: ReqConfig): AxiosPromise<TopologyGrafanaInfo>\n\n  getAlertManagerTopology(\n    options?: ReqConfig\n  ): AxiosPromise<TopologyAlertManagerInfo>\n\n  getAlertManagerCounts(\n    address: string,\n    options?: ReqConfig\n  ): AxiosPromise<number>\n\n  metricsQueryGet(params: {\n    endTimeSec?: number\n    query?: string\n    startTimeSec?: number\n    stepSec?: number\n  }): Promise<MetricsQueryResponse>\n}\n\nexport type IOverviewConfig = IContextConfig &\n  IMetricConfig & {\n    showMetrics: boolean\n  }\n\nexport interface IOverviewContext {\n  ds: IOverviewDataSource\n  cfg: IOverviewConfig\n}\n\nexport const OverviewContext = createContext<IOverviewContext | null>(null)\n\nexport const OverviewProvider = OverviewContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Overview/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { HashRouter as Router } from 'react-router-dom'\nimport { Col, Row } from 'antd'\n\nimport { Root } from '@lib/components'\nimport { addTranslations } from '@lib/utils/i18n'\n\nimport translations from './translations'\nimport { OverviewContext } from './context'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\nimport Instances from './components/Instances'\nimport Metrics from './components/Metrics'\nimport MonitorAlert from './components/MonitorAlert'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n  const ctx = useContext(OverviewContext)\n\n  if (ctx?.cfg.showMetrics) {\n    return (\n      <Row>\n        <Col span={18}>\n          <Metrics />\n        </Col>\n        <Col span={6}>\n          <Instances />\n          <MonitorAlert />\n        </Col>\n      </Row>\n    )\n  }\n\n  return (\n    <Row>\n      <Col span={8}>\n        <Instances />\n      </Col>\n    </Row>\n  )\n}\n\nexport default function () {\n  const ctx = useContext(OverviewContext)\n  if (ctx === null) {\n    throw new Error('OverviewContext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Overview/translations/en.yaml",
    "content": "overview:\n  nav_title: Overview\n  panel_no_data_tips: Documentation\n  view_more_metrics: View More Metrics\n  change_prom_button: Change Prometheus Address\n  top_statements:\n    title: Time Consuming SQL Statements\n  recent_slow_query:\n    title: Recent Slow Queries\n  instances:\n    title: Alive Instances\n  monitor_alert:\n    title: Monitor & Alert\n    view_grafana_monitor: View Grafana Metrics\n    view_monitor_warn: Metrics unavailable\n    view_alerts: 'View {{alertCount}} Alerts'\n    view_zero_alerts: 'View Alerts'\n    view_alerts_warn: Alert unavailable\n    run_diagnose: Run Diagnostics\n  metrics:\n    total_requests: QPS\n    latency: Latency\n    cpu: TiDB CPU Usage\n    memory: TiDB Memory Usage\n    io: IO Usage\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Overview/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Overview/translations/zh.yaml",
    "content": "overview:\n  nav_title: 概况\n  panel_no_data_tips: 文档\n  view_more_metrics: 查看更多指标\n  change_prom_button: 更改 prometheus 地址\n  top_statements:\n    title: 最耗时的 SQL 语句\n  recent_slow_query:\n    title: 最近慢查询\n  instances:\n    title: 在线实例\n  monitor_alert:\n    title: 监控和告警\n    view_grafana_monitor: 查看监控\n    view_monitor_warn: 监控不可用\n    view_alerts: '查看 {{alertCount}} 条告警'\n    view_zero_alerts: 查看告警\n    view_alerts_warn: 告警不可用\n    run_diagnose: 运行诊断\n  metrics:\n    total_requests: QPS\n    latency: 延迟\n    cpu: TiDB CPU 使用\n    memory: TiDB 内存使用\n    io: IO 使用\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Overview/utils/telemetry.ts",
    "content": "import { TimeRange } from '@lib/components'\nimport { mixpanel } from '@lib/utils/telemetry'\n\nexport const telemetry = {\n  // time range picker\n  clickZoomOut(timestamps: [number, number]) {\n    mixpanel.track('Overview: Click Zoom Out Button', { timestamps })\n  },\n  selectTimeRange(v: TimeRange) {\n    mixpanel.track('Overview: Select Time Range', v)\n  },\n  clickManualRefresh() {\n    mixpanel.track('Overview: Click Manual Refresh')\n  },\n  selectAutoRefreshOption(seconds: number) {\n    mixpanel.track('Overview: Select Auto Refresh Option', { seconds })\n  },\n  clickDocumentationIcon() {\n    mixpanel.track('Overview: Click Documentation Icon')\n  },\n  clickViewMoreMetrics() {\n    mixpanel.track('Overview: Click View More Metrics Button')\n  },\n  clickSeriesLabel(chartTitle: string, seriesName: string) {\n    mixpanel.track('Overview: Click to Hide Series', { chartTitle, seriesName })\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/QueryEditor/Editor.module.less",
    "content": ".editorContainer {\n  flex-grow: 1;\n  position: relative;\n  overflow: hidden;\n\n  :global(.ace_editor) {\n    position: absolute;\n    z-index: 1;\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/QueryEditor/Editor.tsx",
    "content": "import React, { useRef } from 'react'\nimport AceEditor, { IAceEditorProps } from 'react-ace'\nimport { useSize } from 'ahooks'\n\nimport 'ace-builds/src-noconflict/mode-sql'\nimport 'ace-builds/src-noconflict/ext-searchbox'\n// import './editorThemes/oneHalfDark'\n// import './editorThemes/oneHalfLight'\n\nimport styles from './Editor.module.less'\n\ninterface IEditorProps extends IAceEditorProps {}\n\nfunction Editor({ ...props }: IEditorProps, ref: React.Ref<AceEditor>) {\n  const containerRef = useRef(null)\n  const containerSize = useSize(containerRef)\n  return (\n    <div className={styles.editorContainer} ref={containerRef}>\n      <AceEditor\n        mode=\"sql\"\n        // theme=\"oneHalfLight\"\n        name=\"query_editor\"\n        fontSize={14}\n        showPrintMargin={false}\n        showGutter={true}\n        highlightActiveLine={true}\n        width={`${containerSize?.width ?? 0}px`}\n        height={`${containerSize?.height ?? 0}px`}\n        ref={ref}\n        {...props}\n      />\n    </div>\n  )\n}\n\nexport default React.memo(React.forwardRef(Editor))\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/QueryEditor/ResultTable.module.less",
    "content": ".resultTable {\n  position: absolute;\n  top: @padding-page; // FIXME: This is hacky. Can we provide a component?\n  bottom: 0;\n  left: 0;\n  width: 100%;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/QueryEditor/ResultTable.tsx",
    "content": "import React, { useMemo } from 'react'\nimport { QueryeditorRunResponse } from '@lib/client'\nimport { CardTable } from '@lib/components'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\n\nimport styles from './ResultTable.module.less'\n\ninterface IResultTableProps {\n  results?: QueryeditorRunResponse\n}\n\nfunction ResultTable({ results }: IResultTableProps) {\n  const columns: IColumn[] = useMemo(() => {\n    if (!results) {\n      return []\n    }\n    if (results.error_msg) {\n      return [\n        {\n          name: 'Error',\n          key: 'error',\n          minWidth: 100,\n          fieldName: 'error',\n          isMultiline: true\n        }\n      ]\n    } else {\n      return (results.column_names ?? []).map((cn, idx) => ({\n        name: cn,\n        key: cn,\n        minWidth: 200,\n        maxWidth: 500,\n        fieldName: String(idx)\n      }))\n    }\n  }, [results])\n\n  const items = useMemo(() => {\n    if (!results) {\n      return []\n    }\n    if (results.error_msg) {\n      return [{ error: results.error_msg }]\n    } else {\n      return results.rows ?? []\n    }\n  }, [results])\n\n  return (\n    <div className={styles.resultTable}>\n      <ScrollablePane>\n        <CardTable\n          cardNoMarginTop\n          extendLastColumn\n          columns={columns}\n          items={items}\n        />\n      </ScrollablePane>\n    </div>\n  )\n}\n\nexport default ResultTable\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/QueryEditor/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport { QueryeditorRunRequest, QueryeditorRunResponse } from '@lib/client'\n\nimport { ReqConfig } from '@lib/types'\n\nexport interface IQueryEditorDataSource {\n  queryEditorRun(\n    request: QueryeditorRunRequest,\n    options?: ReqConfig\n  ): AxiosPromise<QueryeditorRunResponse>\n}\n\nexport interface IQueryEditorContext {\n  ds: IQueryEditorDataSource\n}\n\nexport const QueryEditorContext = createContext<IQueryEditorContext | null>(\n  null\n)\n\nexport const QueryEditorProvider = QueryEditorContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/QueryEditor/editorThemes/oneHalfDark.js",
    "content": "/* eslint-disable no-multi-str */\n\nconst ace = require('ace-builds/src-noconflict/ace')\n\nace.define(\n  'ace/theme/oneHalfDark',\n  ['require', 'exports', 'module', 'ace/lib/dom'],\n  function (require, exports, module) {\n    exports.isDark = true\n    exports.cssClass = 'ace-one-half-dark'\n    exports.cssText =\n      '.ace-one-half-dark .ace_gutter {\\\nbackground: #282c34;\\\ncolor: rgb(130,134,140)\\\n}\\\n.ace-one-half-dark .ace_print-margin {\\\nwidth: 1px;\\\nbackground: #e8e8e8\\\n}\\\n.ace-one-half-dark {\\\nbackground-color: #282c34;\\\ncolor: #dcdfe4\\\n}\\\n.ace-one-half-dark .ace_cursor {\\\ncolor: #a3b3cc\\\n}\\\n.ace-one-half-dark .ace_marker-layer .ace_selection {\\\nbackground: #474e5d\\\n}\\\n.ace-one-half-dark.ace_multiselect .ace_selection.ace_start {\\\nbox-shadow: 0 0 3px 0px #282c34;\\\nborder-radius: 2px\\\n}\\\n.ace-one-half-dark .ace_marker-layer .ace_step {\\\nbackground: rgb(198, 219, 174)\\\n}\\\n.ace-one-half-dark .ace_marker-layer .ace_bracket {\\\nmargin: -1px 0 0 -1px;\\\nborder: 1px solid #5c6370\\\n}\\\n.ace-one-half-dark .ace_marker-layer .ace_active-line {\\\nbackground: #313640\\\n}\\\n.ace-one-half-dark .ace_gutter-active-line {\\\nbackground-color: #313640\\\n}\\\n.ace-one-half-dark .ace_marker-layer .ace_selected-word {\\\nborder: 1px solid #474e5d\\\n}\\\n.ace-one-half-dark .ace_fold {\\\nbackground-color: #61afef;\\\nborder-color: #dcdfe4\\\n}\\\n.ace-one-half-dark .ace_keyword {\\\ncolor: #c678dd\\\n}\\\n.ace-one-half-dark .ace_constant {\\\ncolor: #e5c07b\\\n}\\\n.ace-one-half-dark .ace_constant.ace_numeric {\\\ncolor: #e5c07b\\\n}\\\n.ace-one-half-dark .ace_constant.ace_character.ace_escape {\\\ncolor: #56b6c2\\\n}\\\n.ace-one-half-dark .ace_support.ace_function {\\\ncolor: #61afef\\\n}\\\n.ace-one-half-dark .ace_support.ace_class {\\\ncolor: #e5c07b\\\n}\\\n.ace-one-half-dark .ace_storage {\\\ncolor: #c678dd\\\n}\\\n.ace-one-half-dark .ace_invalid.ace_illegal {\\\ncolor: #dcdfe4;\\\nbackground-color: #e06c75\\\n}\\\n.ace-one-half-dark .ace_invalid.ace_deprecated {\\\ncolor: #dcdfe4;\\\nbackground-color: #e5c07b\\\n}\\\n.ace-one-half-dark .ace_string {\\\ncolor: #98c379\\\n}\\\n.ace-one-half-dark .ace_string.ace_regexp {\\\ncolor: #98c379\\\n}\\\n.ace-one-half-dark .ace_comment {\\\ncolor: #5c6370\\\n}\\\n.ace-one-half-dark .ace_variable {\\\ncolor: #e06c75\\\n}\\\n.ace-one-half-dark .ace_meta.ace_selector {\\\ncolor: #c678dd\\\n}\\\n.ace-one-half-dark .ace_entity.ace_other.ace_attribute-name {\\\ncolor: #e5c07b\\\n}\\\n.ace-one-half-dark .ace_entity.ace_name.ace_function {\\\ncolor: #61afef\\\n}\\\n.ace-one-half-dark .ace_entity.ace_name.ace_tag {\\\ncolor: #e06c75\\\n}'\n\n    var dom = require('../lib/dom')\n    dom.importCssString(exports.cssText, exports.cssClass)\n  }\n)\n;(function () {\n  ace.require(['ace/theme/oneHalfDark'], function (m) {\n    if (typeof module == 'object' && typeof exports == 'object' && module) {\n      module.exports = m\n    }\n  })\n})()\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/QueryEditor/editorThemes/oneHalfLight.js",
    "content": "/* eslint-disable no-multi-str */\n\nconst ace = require('ace-builds/src-noconflict/ace')\n\nace.define(\n  'ace/theme/oneHalfLight',\n  ['require', 'exports', 'module', 'ace/lib/dom'],\n  function (require, exports, module) {\n    exports.isDark = false\n    exports.cssClass = 'ace-one-half-light'\n    exports.cssText =\n      '.ace-one-half-light .ace_gutter {\\\nbackground: #fafafa;\\\ncolor: rgb(153,154,158)\\\n}\\\n.ace-one-half-light .ace_print-margin {\\\nwidth: 1px;\\\nbackground: #e8e8e8\\\n}\\\n.ace-one-half-light {\\\nbackground-color: #fafafa;\\\ncolor: #383a42\\\n}\\\n.ace-one-half-light .ace_cursor {\\\ncolor: #383a42\\\n}\\\n.ace-one-half-light .ace_marker-layer .ace_selection {\\\nbackground: #bfceff\\\n}\\\n.ace-one-half-light.ace_multiselect .ace_selection.ace_start {\\\nbox-shadow: 0 0 3px 0px #fafafa;\\\nborder-radius: 2px\\\n}\\\n.ace-one-half-light .ace_marker-layer .ace_step {\\\nbackground: rgb(198, 219, 174)\\\n}\\\n.ace-one-half-light .ace_marker-layer .ace_bracket {\\\nmargin: -1px 0 0 -1px;\\\nborder: 1px solid #a0a1a7\\\n}\\\n.ace-one-half-light .ace_marker-layer .ace_active-line {\\\nbackground: #f0f0f0\\\n}\\\n.ace-one-half-light .ace_gutter-active-line {\\\nbackground-color: #f0f0f0\\\n}\\\n.ace-one-half-light .ace_marker-layer .ace_selected-word {\\\nborder: 1px solid #bfceff\\\n}\\\n.ace-one-half-light .ace_fold {\\\nbackground-color: #0184bc;\\\nborder-color: #383a42\\\n}\\\n.ace-one-half-light .ace_keyword,\\\n.ace-one-half-light .ace_meta.ace_selector,\\\n.ace-one-half-light .ace_storage {\\\ncolor: #a626a4\\\n}\\\n.ace-one-half-light .ace_constant,\\\n.ace-one-half-light .ace_constant.ace_numeric,\\\n.ace-one-half-light .ace_entity.ace_other.ace_attribute-name,\\\n.ace-one-half-light .ace_support.ace_class {\\\ncolor: #c18401\\\n}\\\n.ace-one-half-light .ace_constant.ace_character.ace_escape {\\\ncolor: #0997b3\\\n}\\\n.ace-one-half-light .ace_entity.ace_name.ace_function,\\\n.ace-one-half-light .ace_support.ace_function {\\\ncolor: #0184bc\\\n}\\\n.ace-one-half-light .ace_invalid.ace_illegal {\\\ncolor: #fafafa;\\\nbackground-color: #e06c75\\\n}\\\n.ace-one-half-light .ace_invalid.ace_deprecated {\\\ncolor: #fafafa;\\\nbackground-color: #e5c07b\\\n}\\\n.ace-one-half-light .ace_string,\\\n.ace-one-half-light .ace_string.ace_regexp {\\\ncolor: #50a14f\\\n}\\\n.ace-one-half-light .ace_comment {\\\ncolor: #a0a1a7\\\n}\\\n.ace-one-half-light .ace_entity.ace_name.ace_tag,\\\n.ace-one-half-light .ace_variable {\\\ncolor: #e45649\\\n}'\n\n    var dom = require('../lib/dom')\n    dom.importCssString(exports.cssText, exports.cssClass)\n  }\n)\n;(function () {\n  ace.require(['ace/theme/oneHalfLight'], function (m) {\n    if (typeof module == 'object' && typeof exports == 'object' && module) {\n      module.exports = m\n    }\n  })\n})()\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/QueryEditor/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.container {\n  height: 100vh;\n  display: flex;\n  flex-direction: column;\n\n  &:before,\n  &:after {\n    // Handle margin collapse\n    content: ' ';\n    display: table;\n  }\n}\n\n.contentContainer {\n  flex: 1;\n  min-height: 0;\n\n  > :global(.gutter.gutter-vertical) {\n    background-color: @gray-3;\n    cursor: row-resize;\n    background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=');\n    background-repeat: no-repeat;\n    background-position: center center;\n    margin: 0 @padding-page;\n  }\n\n  &.isCollapsed > :global(.gutter) {\n    display: none;\n  }\n}\n\n.successText {\n  color: @success-color;\n}\n\n.resultTableContainer {\n  position: relative;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/QueryEditor/index.tsx",
    "content": "import React, { useState, useCallback, useRef, useContext } from 'react'\nimport cx from 'classnames'\nimport { Root, Card } from '@lib/components'\nimport Split from 'react-split'\nimport { Button, Space, Typography } from 'antd'\nimport {\n  CaretRightOutlined,\n  LoadingOutlined,\n  WarningOutlined,\n  CheckOutlined\n} from '@ant-design/icons'\nimport { Routes, Route, HashRouter as Router } from 'react-router-dom'\n\nimport Editor from './Editor'\nimport ResultTable from './ResultTable'\n\nimport styles from './index.module.less'\nimport { QueryeditorRunResponse } from '@lib/client'\nimport ReactAce from 'react-ace/lib/ace'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\n\nimport translations from './translations'\nimport { addTranslations } from '@lib/utils/i18n'\nimport { QueryEditorContext } from './context'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\n\nconst MAX_DISPLAY_ROWS = 1000\n\naddTranslations(translations)\n\nfunction QueryEditor() {\n  const ctx = useContext(QueryEditorContext)\n\n  if (ctx === null) {\n    throw new Error('QueryEditorContext must not be null')\n  }\n\n  const [results, setResults] = useState<QueryeditorRunResponse | undefined>()\n  const [isRunning, setRunning] = useState(false)\n  const editor = useRef<ReactAce>(null)\n\n  const isResultsEmpty =\n    !results ||\n    (!results.error_msg && (!results.column_names?.length || !results.rows))\n\n  const handleRun = useCallback(async () => {\n    try {\n      setRunning(true)\n      setResults(undefined)\n      const resp = await ctx!.ds.queryEditorRun({\n        max_rows: MAX_DISPLAY_ROWS,\n        statements: editor.current?.editor.getValue()\n      })\n      setResults(resp.data)\n    } finally {\n      setRunning(false)\n    }\n    editor.current?.editor.focus()\n  }, [ctx])\n\n  return (\n    <Root>\n      <div className={styles.container}>\n        <Card>\n          <Space>\n            <Button\n              type=\"primary\"\n              icon={<CaretRightOutlined />}\n              onClick={handleRun}\n              disabled={isRunning}\n            >\n              Run\n            </Button>\n            {\n              <span>\n                {isRunning && <LoadingOutlined spin />}\n                {results && results.error_msg && (\n                  <Typography.Text type=\"danger\">\n                    <WarningOutlined /> Error (\n                    {getValueFormat('ms')(results.execution_ms || 0, 1)})\n                  </Typography.Text>\n                )}\n                {results && !results.error_msg && (\n                  <Typography.Text className={styles.successText}>\n                    <CheckOutlined /> Success (\n                    {getValueFormat('ms')(results.execution_ms || 0, 1)},\n                    {(results.actual_rows || 0) > (results.rows?.length || 0)\n                      ? `Displaying first ${results.rows?.length || 0} of ${\n                          results.actual_rows || 0\n                        } rows`\n                      : `${results.rows?.length || 0} rows`}\n                    )\n                  </Typography.Text>\n                )}\n              </span>\n            }\n          </Space>\n        </Card>\n        <Split\n          direction=\"vertical\"\n          dragInterval={30}\n          className={cx(styles.contentContainer, {\n            [styles.isCollapsed]: isResultsEmpty\n          })}\n          sizes={isResultsEmpty ? [100, 0] : [50, 50]}\n          minSize={isResultsEmpty ? 0 : 100}\n          expandToMin={false}\n        >\n          <Card noMarginTop noMarginBottom={!isResultsEmpty} flexGrow>\n            <Editor focus ref={editor} />\n          </Card>\n          <div className={styles.resultTableContainer}>\n            {!isResultsEmpty && <ResultTable results={results} />}\n          </div>\n        </Split>\n      </div>\n    </Root>\n  )\n}\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/query_editor\" element={<QueryEditor />} />\n    </Routes>\n  )\n}\n\nfunction App() {\n  return (\n    <Router>\n      <AppRoutes />\n    </Router>\n  )\n}\n\nexport default App\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/QueryEditor/translations/en.yaml",
    "content": "query_editor:\n  nav_title: Query Editor\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/QueryEditor/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/QueryEditor/translations/zh.yaml",
    "content": "query_editor:\n  nav_title: 查询编辑器\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ResourceManager/components/Configuration.tsx",
    "content": "import { Card, CardTable } from '@lib/components'\nimport React, { useMemo } from 'react'\nimport { useResourceManagerContext } from '../context'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { Space, Switch, Typography } from 'antd'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport { ResourcemanagerResourceInfoRowDef } from '@lib/client'\nimport { useTranslation } from 'react-i18next'\n\ntype ConfigurationProps = {\n  info: ResourcemanagerResourceInfoRowDef[]\n  loadingInfo: boolean\n}\n\nexport const Configuration: React.FC<ConfigurationProps> = ({\n  info,\n  loadingInfo\n}) => {\n  const ctx = useResourceManagerContext()\n  const { data: config, isLoading: loadingConfig } = useClientRequest(\n    ctx.ds.getConfig\n  )\n  const { t } = useTranslation()\n\n  const columns: IColumn[] = useMemo(() => {\n    return [\n      {\n        name: t('resource_manager.configuration.table_fields.resource_group'),\n        key: 'resource_group',\n        minWidth: 100,\n        maxWidth: 400,\n        onRender: (row: any) => {\n          return <span>{row.name}</span>\n        }\n      },\n      {\n        name: t('resource_manager.configuration.table_fields.ru_per_sec'),\n        key: 'ru_per_sec',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (row: any) => {\n          return <span>{row.ru_per_sec}</span>\n        }\n      },\n      {\n        name: t('resource_manager.configuration.table_fields.priority'),\n        key: 'priority',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (row: any) => {\n          return <span>{row.priority}</span>\n        }\n      },\n      {\n        name: t('resource_manager.configuration.table_fields.burstable'),\n        key: 'burstable',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (row: any) => {\n          return <span>{row.burstable}</span>\n        }\n      }\n    ]\n  }, [t])\n\n  return (\n    <Card title={t('resource_manager.configuration.title')}>\n      <Space direction=\"vertical\" style={{ paddingBottom: 8 }}>\n        <Typography.Text>\n          {t('resource_manager.configuration.enabled')}\n        </Typography.Text>\n        <Switch\n          loading={loadingConfig}\n          checked={config?.enable}\n          disabled={true}\n        />\n      </Space>\n\n      <CardTable\n        cardNoMargin\n        loading={loadingInfo}\n        columns={columns}\n        items={info}\n      />\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ResourceManager/components/EstimateCapacity.tsx",
    "content": "import {\n  Card,\n  CardTabs,\n  ErrorBar,\n  Pre,\n  TimeRangeSelector,\n  toTimeRangeValue\n} from '@lib/components'\nimport {\n  Alert,\n  Col,\n  Row,\n  Select,\n  Space,\n  Statistic,\n  Tooltip,\n  Typography\n} from 'antd'\nimport ReactMarkdown from 'react-markdown'\nimport React, { useEffect, useMemo } from 'react'\nimport { useResourceManagerContext } from '../context'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { InfoCircleOutlined } from '@ant-design/icons'\nimport { useResourceManagerUrlState } from '../uilts/url-state'\nimport { TIME_WINDOW_RECENT_SECONDS, WORKLOAD_TYPES } from '../uilts/helpers'\nimport { useTranslation } from 'react-i18next'\n\nconst { Option } = Select\nconst { Paragraph, Text, Link } = Typography\n\nconst CapacityWarning: React.FC<{ totalRU: number; estimatedRU: number }> = ({\n  totalRU,\n  estimatedRU\n}) => {\n  const { t } = useTranslation()\n\n  if (estimatedRU > 0 && totalRU > estimatedRU) {\n    return (\n      <div style={{ paddingTop: 16 }}>\n        <Alert\n          type=\"warning\"\n          showIcon\n          message={t('resource_manager.estimate_capacity.exceed_warning')}\n        />\n      </div>\n    )\n  }\n\n  return null\n}\n\nconst HardwareCalibrate: React.FC<{ totalRU: number }> = ({ totalRU }) => {\n  const ctx = useResourceManagerContext()\n  const { workload, setWorkload } = useResourceManagerUrlState()\n  const { data, isLoading, sendRequest, error } = useClientRequest(\n    (reqConfig) => ctx.ds.getCalibrateByHardware({ workload }, reqConfig)\n  )\n  useEffect(() => {\n    sendRequest()\n  }, [workload])\n  const estimatedRU = data?.estimated_capacity ?? 0\n  const { t } = useTranslation()\n\n  return (\n    <div>\n      <Space>\n        <Select style={{ width: 200 }} value={workload} onChange={setWorkload}>\n          {WORKLOAD_TYPES.map((item) => (\n            <Option value={item} key={item}>\n              {item}\n            </Option>\n          ))}\n        </Select>\n        <Tooltip\n          overlayStyle={{ maxWidth: 720 }}\n          title={\n            <ReactMarkdown>\n              {t('resource_manager.estimate_capacity.workload_select_tooltip')}\n            </ReactMarkdown>\n          }\n        >\n          <InfoCircleOutlined />\n        </Tooltip>\n      </Space>\n\n      <div style={{ paddingTop: 16 }}>\n        <Row gutter={16}>\n          <Col span={6}>\n            <Statistic\n              title={t('resource_manager.estimate_capacity.estimated_capacity')}\n              value={estimatedRU}\n              loading={isLoading}\n              suffix={\n                <Typography.Text type=\"secondary\" style={{ fontSize: 14 }}>\n                  RUs/sec\n                </Typography.Text>\n              }\n            />\n          </Col>\n          <Col span={6}>\n            <Statistic\n              title={t('resource_manager.estimate_capacity.total_ru')}\n              value={totalRU}\n            />\n          </Col>\n        </Row>\n      </div>\n\n      {error && (\n        <div style={{ paddingTop: 16 }}>\n          {' '}\n          <ErrorBar errors={[error]} />{' '}\n        </div>\n      )}\n\n      <CapacityWarning totalRU={totalRU} estimatedRU={estimatedRU} />\n    </div>\n  )\n}\n\nconst WorkloadCalibrate: React.FC<{ totalRU: number }> = ({ totalRU }) => {\n  const ctx = useResourceManagerContext()\n  const { timeRange, setTimeRange } = useResourceManagerUrlState()\n  const { data, isLoading, sendRequest, error } = useClientRequest(\n    (reqConfig) => {\n      const [start, end] = toTimeRangeValue(timeRange)\n      return ctx.ds.getCalibrateByActual(\n        { startTime: start, endTime: end },\n        reqConfig\n      )\n    }\n  )\n  useEffect(() => {\n    sendRequest()\n  }, [timeRange])\n  const estimatedRU = data?.estimated_capacity ?? 0\n\n  const { t } = useTranslation()\n\n  return (\n    <div>\n      <Space>\n        <TimeRangeSelector\n          recent_seconds={TIME_WINDOW_RECENT_SECONDS}\n          value={timeRange}\n          onChange={setTimeRange}\n        />\n\n        <Tooltip\n          title={\n            <Pre>\n              {t(\n                'resource_manager.estimate_capacity.time_window_select_tooltip'\n              )}\n            </Pre>\n          }\n        >\n          <InfoCircleOutlined />\n        </Tooltip>\n      </Space>\n\n      <div style={{ paddingTop: 16 }}>\n        <Row gutter={16}>\n          <Col span={6}>\n            <Statistic\n              title={t('resource_manager.estimate_capacity.estimated_capacity')}\n              value={estimatedRU}\n              loading={isLoading}\n              suffix={\n                <Typography.Text type=\"secondary\" style={{ fontSize: 14 }}>\n                  RUs/sec\n                </Typography.Text>\n              }\n            />\n          </Col>\n          <Col span={6}>\n            <Statistic\n              title={t('resource_manager.estimate_capacity.total_ru')}\n              value={totalRU}\n            />\n          </Col>\n        </Row>\n      </div>\n\n      {error && (\n        <div style={{ paddingTop: 16 }}>\n          {' '}\n          <ErrorBar errors={[error]} />{' '}\n        </div>\n      )}\n\n      <CapacityWarning totalRU={totalRU} estimatedRU={estimatedRU} />\n    </div>\n  )\n}\n\nexport const EstimateCapacity: React.FC<{ totalRU: number }> = ({\n  totalRU\n}) => {\n  const { t } = useTranslation()\n  const tabs = useMemo(() => {\n    return [\n      {\n        key: 'calibrate_by_hardware',\n        title: t('resource_manager.estimate_capacity.calibrate_by_hardware'),\n        content: () => <HardwareCalibrate totalRU={totalRU} />\n      },\n      {\n        key: 'calibrate_by_workload',\n        title: t('resource_manager.estimate_capacity.calibrate_by_workload'),\n        content: () => <WorkloadCalibrate totalRU={totalRU} />\n      }\n    ]\n  }, [totalRU, t])\n\n  return (\n    <Card title={t('resource_manager.estimate_capacity.title')}>\n      <Paragraph>\n        <blockquote>\n          {t('resource_manager.estimate_capacity.ru_desc_line_1')}\n          <br />\n          {t('resource_manager.estimate_capacity.ru_desc_line_2')}\n          <br />\n          <br />\n          <details>\n            <summary>\n              {t(\n                'resource_manager.estimate_capacity.change_resource_allocation'\n              )}\n            </summary>\n            <Typography>\n              <Text>\n                {t(\n                  'resource_manager.estimate_capacity.resource_allocation_line_1'\n                )}\n              </Text>\n              <div style={{ paddingTop: 8, paddingBottom: 8 }}>\n                <Text code>\n                  {`ALTER RESOURCE GROUP <resource group name> RU_PER_SEC=<#ru> [BURSTABLE];`}\n                </Text>\n              </div>\n              <Text>\n                {t(\n                  'resource_manager.estimate_capacity.resource_allocation_ref'\n                )}{' '}\n                <Link\n                  href=\"https://docs.pingcap.com/tidb/dev/tidb-resource-control\"\n                  target=\"_blank\"\n                >\n                  {t(\n                    'resource_manager.estimate_capacity.resource_allocation_user_manual'\n                  )}\n                </Link>\n                .\n              </Text>\n            </Typography>\n          </details>\n        </blockquote>\n      </Paragraph>\n\n      <CardTabs tabs={tabs} />\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ResourceManager/components/Metrics.tsx",
    "content": "import { TimeRangeSelector } from '@lib/components'\nimport { Space, Typography, Row, Col } from 'antd'\nimport React, { useCallback, useRef, useState } from 'react'\nimport { useMemoizedFn } from 'ahooks'\nimport { MetricsChart, SyncChartPointer, TimeRangeValue } from 'metrics-chart'\nimport { debounce } from 'lodash'\nimport { Card, TimeRange, ErrorBar } from '@lib/components'\nimport { tz } from '@lib/utils'\nimport { useTimeRangeValue } from '@lib/components/TimeRangeSelector/hook'\nimport { useResourceManagerContext } from '../context'\nimport { useResourceManagerUrlState } from '../uilts/url-state'\nimport { MetricConfig, metrics } from '../uilts/metricQueries'\nimport { useTranslation } from 'react-i18next'\n\nexport const Metrics: React.FC = () => {\n  const { timeRange, setTimeRange } = useResourceManagerUrlState()\n  const [, setIsSomeLoading] = useState(false)\n  const { t } = useTranslation()\n\n  return (\n    <Card title={t('resource_manager.metrics.title')}>\n      <div style={{ marginBottom: 24 }}>\n        <TimeRangeSelector value={timeRange} onChange={setTimeRange} />\n      </div>\n\n      <SyncChartPointer>\n        <MetricsChartWrapper\n          metrics={metrics}\n          timeRange={timeRange}\n          setTimeRange={setTimeRange}\n          setIsSomeLoading={setIsSomeLoading}\n        />\n      </SyncChartPointer>\n    </Card>\n  )\n}\n\ninterface MetricsChartWrapperProps {\n  metrics: MetricConfig[]\n  timeRange: TimeRange\n  setTimeRange: (timeRange: TimeRange) => void\n  setIsSomeLoading: (isLoading: boolean) => void\n}\n\nconst MetricsChartWrapper: React.FC<MetricsChartWrapperProps> = ({\n  metrics,\n  timeRange,\n  setTimeRange,\n  setIsSomeLoading\n}) => {\n  const ctx = useResourceManagerContext()\n  const loadingCounter = useRef(0)\n  const [chartRange, setChartRange] = useTimeRangeValue(timeRange, setTimeRange)\n\n  // eslint-disable-next-line\n  const setIsSomeLoadingDebounce = useCallback(\n    debounce(setIsSomeLoading, 100, { leading: true }),\n    []\n  )\n\n  const handleOnBrush = (range: TimeRangeValue) => {\n    setChartRange(range)\n  }\n\n  const onLoadingStateChange = useMemoizedFn((loading: boolean) => {\n    loading\n      ? (loadingCounter.current += 1)\n      : loadingCounter.current > 0 && (loadingCounter.current -= 1)\n    setIsSomeLoadingDebounce(loadingCounter.current > 0)\n  })\n\n  const ErrorComponent = (error: Error) => (\n    <Space direction=\"vertical\">\n      <ErrorBar errors={[error]} />\n    </Space>\n  )\n\n  return (\n    <Row gutter={[16, 16]}>\n      {metrics.map((item) => (\n        <Col xl={12} sm={24} key={item.title}>\n          <Card\n            noMargin\n            style={{\n              border: '1px solid #f1f0f0',\n              padding: '10px 2rem',\n              backgroundColor: '#fcfcfd'\n            }}\n          >\n            <Typography.Title level={5} style={{ textAlign: 'center' }}>\n              {item.title}\n            </Typography.Title>\n            <MetricsChart\n              queries={item.queries}\n              range={chartRange}\n              nullValue={item.nullValue}\n              unit={item.unit!}\n              timezone={tz.getTimeZone()}\n              fetchPromeData={ctx!.ds.metricsQueryGet}\n              onLoading={onLoadingStateChange}\n              onBrush={handleOnBrush}\n              errorComponent={ErrorComponent}\n            />\n          </Card>\n        </Col>\n      ))}\n    </Row>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ResourceManager/components/index.ts",
    "content": "export * from './Configuration'\nexport * from './EstimateCapacity'\nexport * from './Metrics'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ResourceManager/context/index.ts",
    "content": "import {\n  MetricsQueryResponse,\n  ResourcemanagerCalibrateResponse,\n  ResourcemanagerGetConfigResponse,\n  ResourcemanagerResourceInfoRowDef\n} from '@lib/client'\nimport { ReqConfig } from '@lib/types'\nimport { AxiosPromise } from 'axios'\nimport { createContext, useContext } from 'react'\n\nexport interface IResourceManagerDataSource {\n  getConfig(options?: ReqConfig): AxiosPromise<ResourcemanagerGetConfigResponse>\n  getInformation(\n    options?: ReqConfig\n  ): AxiosPromise<ResourcemanagerResourceInfoRowDef[]>\n\n  getCalibrateByHardware(\n    params: { workload: string },\n    options?: ReqConfig\n  ): AxiosPromise<ResourcemanagerCalibrateResponse>\n  getCalibrateByActual(\n    params: { startTime: number; endTime: number },\n    options?: ReqConfig\n  ): AxiosPromise<ResourcemanagerCalibrateResponse>\n\n  metricsQueryGet(params: {\n    endTimeSec?: number\n    query?: string\n    startTimeSec?: number\n    stepSec?: number\n  }): Promise<MetricsQueryResponse>\n}\n\nexport interface IResourceManagerConfig {}\n\nexport interface IResourceManagerContext {\n  ds: IResourceManagerDataSource\n  cfg: IResourceManagerConfig\n}\n\nexport const ResourceManagerContext =\n  createContext<IResourceManagerContext | null>(null)\n\nexport const ResourceManagerProvider = ResourceManagerContext.Provider\n\nexport const useResourceManagerContext = () => {\n  const ctx = useContext(ResourceManagerContext)\n  if (ctx === null) {\n    throw new Error('ResourceManagerContext must not be null')\n  }\n  return ctx\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ResourceManager/index.tsx",
    "content": "import React from 'react'\nimport { HashRouter as Router, Routes, Route } from 'react-router-dom'\n\nimport { Root } from '@lib/components'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\nimport { addTranslations } from '@lib/utils/i18n'\n\nimport translations from './translations'\nimport { useResourceManagerContext } from './context'\nimport { Home } from './pages'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/resource_manager\" element={<Home />} />\n    </Routes>\n  )\n}\n\nexport default function () {\n  useResourceManagerContext()\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ResourceManager/pages/index.tsx",
    "content": "import React, { useMemo } from 'react'\nimport { Configuration, EstimateCapacity, Metrics } from '../components'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { useResourceManagerContext } from '../context'\n\nexport const Home: React.FC = () => {\n  const ctx = useResourceManagerContext()\n\n  const { data: info, isLoading: loadingInfo } = useClientRequest(\n    ctx.ds.getInformation\n  )\n\n  const totalRU = useMemo(() => {\n    return (info ?? [])\n      .filter((item) => item.name !== 'default')\n      .reduce((acc, cur) => {\n        const ru = Number(cur.ru_per_sec)\n        if (!isNaN(ru)) {\n          return acc + ru\n        }\n        return acc\n      }, 0)\n  }, [info])\n\n  return (\n    <div>\n      <Configuration info={info ?? []} loadingInfo={loadingInfo} />\n      <EstimateCapacity totalRU={totalRU} />\n      <Metrics />\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ResourceManager/translations/en.yaml",
    "content": "resource_manager:\n  nav_title: Resource Manager\n  configuration:\n    title: Configuration\n    enabled: TiDB Resource Manager Enabled\n    table_fields:\n      resource_group: Resource Group\n      ru_per_sec: RUs/sec\n      priority: Priority\n      burstable: Burstable\n  estimate_capacity:\n    title: Estimate Capacity\n    ru_desc_line_1: Request Unit (RU) is a unified abstraction unit in TiDB for system resources, which is relavant to resource comsuption.\n    ru_desc_line_2: Please notice the \"estimated capacity\" refers to a result that is hardware specs or past statistics, and may deviate from actual capacity.\n    change_resource_allocation: Change the Resource Allocation\n    resource_allocation_line_1: 'To change the resource allocation for resource group:'\n    resource_allocation_ref: For detail information, please refer to\n    resource_allocation_user_manual: user manual\n    calibrate_by_hardware: Calibrate by Hardware\n    calibrate_by_workload: Calibrate by Workload\n    estimated_capacity: Estimated Capacity\n    total_ru: Total RU of user resource groups\n    exceed_warning: The total RU of all customized resource groups exceeds the \"estimated capacity\". The RU allocated to some resource groups could not be satisfied.\n    workload_select_tooltip: |\n      Select a workload type which is similar with your actual workload.\n\n      - **oltp_read_write**: applies to workloads with even data read and write. It is estimated based on a workload model similar to `sysbench oltp_read_write`\n      - **oltp_read_only**: applies to workloads with heavy data read. It is estimated based on a workload model similar to `sysbench oltp_read_only`\n      - **oltp_write_only**: applies to workloads with heavy data write. It is estimated based on a workload model similar to `sysbench oltp_write_only`\n      - **tpcc**: applies to workloads with heavy data write. It is estimated based on a workload model similar to `TPC-C`\n    time_window_select_tooltip: |\n      Select the time window with classic workload in the past, with which TiDB can come a better estimation of RU capacity.\n\n      Time window length: 10 mins ~ 24 hours\n  metrics:\n    title: Metrics\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ResourceManager/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ResourceManager/translations/zh.yaml",
    "content": "resource_manager:\n  nav_title: 资源管控\n  configuration:\n    title: 配置\n    enabled: 启用 TiDB 资源管控\n    table_fields:\n      resource_group: 资源分组\n      ru_per_sec: 每秒请求单元\n      priority: 优先级\n      burstable: 是否可突发\n  estimate_capacity:\n    title: 容量估算\n    ru_desc_line_1: 请求单元 (RU) 是一个统一的抽象单元，用于表示 TiDB 系统资源，与资源消耗相关。\n    ru_desc_line_2: 请注意，\"估算容量\" 是基于硬件配置或过去的统计数据，可能与实际容量有所偏差。\n    change_resource_allocation: 修改资源分配\n    resource_allocation_line_1: '执行以下命令修改资源分配：'\n    resource_allocation_ref: 想了解更多，请参考\n    resource_allocation_user_manual: 用户手册\n    calibrate_by_hardware: 通过硬件配置校准\n    calibrate_by_workload: 通过负载校准\n    estimated_capacity: 估算容量\n    total_ru: 用户资源分组总请求单元\n    exceed_warning: 所有自定义资源分组的总请求单元超过了 \"估算容量\"。部分资源分组的请求单元可能无法满足。\n    workload_select_tooltip: |\n      选择一个与实际工作负载相似的工作负载类型。\n\n      - **oltp_read_write**: 数据读写平衡的负载，根据类似 `sysbench oltp_read_write` 的负载模型预测\n      - **oltp_read_only**: 数据读取较重的负载，根据类似 `sysbench oltp_read_only` 的负载模型预测\n      - **oltp_write_only**: 数据写入较重的负载，根据类似 `sysbench oltp_write_only` 的负载模型预测\n      - **tpcc**: 数据写入较重的负载，根据类似 `TPC-C` 的负载模型预测\n    time_window_select_tooltip: |\n      选择一个过去的典型工作负载时间窗口，TiDB 会基于该时间窗口的统计数据来估算 RU 容量。\n\n      时间窗口长度：10 分钟 ~ 24 小时\n  metrics:\n    title: 监控指标\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ResourceManager/uilts/helpers.ts",
    "content": "import { TimeRange } from '@lib/components/TimeRangeSelector'\n\nexport const TIME_WINDOW_RECENT_SECONDS = [\n  15 * 60,\n  30 * 60,\n  60 * 60,\n  3 * 60 * 60,\n  6 * 60 * 60,\n  12 * 60 * 60,\n  24 * 60 * 60\n]\n\nexport const DEFAULT_TIME_WINDOW: TimeRange = {\n  type: 'recent',\n  value: TIME_WINDOW_RECENT_SECONDS[1]\n}\n\nexport const WORKLOAD_TYPES = [\n  'oltp_read_write',\n  'oltp_read_only',\n  'oltp_write_only',\n  'tpcc'\n]\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ResourceManager/uilts/metricQueries.ts",
    "content": "import { QueryConfig, TransformNullValue } from 'metrics-chart'\n\nexport type MetricConfig = {\n  title: string\n  queries: QueryConfig[]\n  unit: string\n  nullValue?: TransformNullValue\n}\n\nexport const metrics: MetricConfig[] = [\n  {\n    title: 'Total RU Consumed',\n    queries: [\n      {\n        promql:\n          'sum(rate(resource_manager_resource_unit_read_request_unit_sum[1m])) + sum(rate(resource_manager_resource_unit_write_request_unit_sum[1m]))',\n        name: 'Total RU',\n        type: 'line'\n      }\n    ],\n    unit: 'short',\n    nullValue: TransformNullValue.AS_ZERO\n  },\n  {\n    title: 'RU Consumed by Resource Groups',\n    queries: [\n      {\n        promql:\n          'sum(rate(resource_manager_resource_unit_read_request_unit_sum[1m])) by (name) + sum(rate(resource_manager_resource_unit_write_request_unit_sum[1m])) by (name)',\n        name: '{name}',\n        type: 'line'\n      }\n    ],\n    unit: 'short',\n    nullValue: TransformNullValue.AS_ZERO\n  },\n  {\n    title: 'TiDB CPU Usage',\n    queries: [\n      {\n        promql: 'rate(process_cpu_seconds_total{job=\"tidb\"}[30s])',\n        name: '{instance}',\n        type: 'line'\n      },\n      {\n        promql: 'tidb_server_maxprocs',\n        name: 'Limit-{instance}',\n        type: 'line'\n      }\n    ],\n    unit: 'percentunit',\n    nullValue: TransformNullValue.AS_ZERO\n  },\n  {\n    title: 'TiKV CPU Usage',\n    queries: [\n      {\n        promql:\n          'sum(rate(tikv_thread_cpu_seconds_total[$__rate_interval])) by (instance)',\n        name: '{instance}',\n        type: 'line'\n      },\n      {\n        promql: 'tikv_server_cpu_cores_quota',\n        name: 'Limit-{instance}',\n        type: 'line'\n      }\n    ],\n    unit: 'percentunit'\n  },\n  {\n    title: 'TiKV IO MBps',\n    queries: [\n      {\n        promql:\n          'sum(rate(tikv_engine_flow_bytes{db=\"kv\", type=\"wal_file_bytes\"}[$__rate_interval])) by (instance) + sum(rate(tikv_engine_flow_bytes{db=\"raft\", type=\"wal_file_bytes\"}[$__rate_interval])) by (instance) + sum(rate(raft_engine_write_size_sum[$__rate_interval])) by (instance)',\n        name: '{instance}-write',\n        type: 'line'\n      },\n      {\n        promql:\n          'sum(rate(tikv_engine_flow_bytes{db=\"kv\", type=~\"bytes_read|iter_bytes_read\"}[$__rate_interval])) by (instance)',\n        name: '{instance}-read',\n        type: 'line'\n      }\n    ],\n    unit: 'Bps'\n  }\n]\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/ResourceManager/uilts/url-state.ts",
    "content": "import useUrlState from '@ahooksjs/use-url-state'\nimport {\n  TimeRange,\n  toURLTimeRange,\n  urlToTimeRange\n} from '@lib/components/TimeRangeSelector'\nimport { useCallback, useMemo } from 'react'\nimport { DEFAULT_TIME_WINDOW, WORKLOAD_TYPES } from './helpers'\n\ntype UrlState = Partial<Record<'from' | 'to' | 'workload', string>>\n\nexport function useResourceManagerUrlState() {\n  const [queryParams, setQueryParams] = useUrlState<UrlState>()\n\n  const timeRange = useMemo(() => {\n    const { from, to } = queryParams\n    if (from && to) {\n      return urlToTimeRange({ from, to })\n    }\n    return DEFAULT_TIME_WINDOW\n  }, [queryParams.from, queryParams.to])\n\n  const setTimeRange = useCallback(\n    (newTimeRange: TimeRange) => {\n      setQueryParams({ ...toURLTimeRange(newTimeRange) })\n    },\n    [setQueryParams]\n  )\n\n  const workload = queryParams.workload || WORKLOAD_TYPES[0]\n  const setWorkload = useCallback(\n    (w: string) => {\n      setQueryParams({ workload: w || undefined })\n    },\n    [setQueryParams]\n  )\n\n  return {\n    timeRange,\n    setTimeRange,\n\n    workload,\n    setWorkload\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/component/IndexInsightList.tsx",
    "content": "import React, {\n  useEffect,\n  useContext,\n  useState,\n  useRef,\n  MutableRefObject\n} from 'react'\n\nimport IndexInsightTable, { useSQLTunedListGet } from './IndexInsightTable'\n\nimport {\n  Space,\n  Button,\n  Typography,\n  notification,\n  // Alert,\n  Modal,\n  Tooltip,\n  Drawer,\n  Checkbox\n} from 'antd'\nimport { InfoCircleOutlined } from '@ant-design/icons'\nimport { Card, Toolbar } from '@lib/components'\nimport { SQLAdvisorContext } from '../context'\nimport dayjs from 'dayjs'\nimport { PerfInsightTask, PerfInsightTaskStatus } from '../types'\n\ninterface IndexInsightListProps {\n  onHandleDeactivate?: () => void\n  isDeactivating?: boolean\n}\n\nconst CHECK_TASK_INTERVAL = 60 * 1000\n\nconst IndexInsightList = ({\n  onHandleDeactivate,\n  isDeactivating\n}: IndexInsightListProps) => {\n  const ctx = useContext(SQLAdvisorContext)\n  const [showDeactivateModal, setShowDeactivateModal] = useState<boolean>(false)\n  const { sqlTunedList, refreshSQLTunedList, loading } = useSQLTunedListGet()\n  // const [showAlert, setShowAlert] = useState<boolean>(false)\n  const [showSetting, setShowSetting] = useState(false)\n  const [showCheckUpModal, setShowCheckUpModal] = useState(false)\n  const [taskStatus, setTaskStatus] = useState<PerfInsightTaskStatus>()\n  const isTaskRunning = taskStatus === 'running' || taskStatus === 'created'\n  const latestTask = useRef<PerfInsightTask>(null) as MutableRefObject<\n    PerfInsightTask | undefined\n  >\n  const dontRemindCheckUpNotice = useRef(\n    JSON.parse(\n      localStorage.getItem('index_insight_dont_remind_checkup_notice') ||\n        'false'\n    )\n  )\n\n  const timer = useRef(0)\n  const checkStatusLoop = useRef(async () => {\n    clearTimeout(timer.current)\n\n    try {\n      const res = await ctx?.ds.tuningLatestGet()\n      latestTask.current = res\n\n      // No tasks\n      if (!res) {\n        return\n      }\n\n      setTaskStatus(res.status)\n\n      if (res.status === 'failed') {\n        notification.error({\n          message: 'Last Task Error',\n          description: res.last_failed_message || 'Unknown error'\n        })\n      }\n\n      if (res.status !== 'succeeded' && res.status !== 'failed') {\n        timer.current = window.setTimeout(async () => {\n          const nextRes = await checkStatusLoop.current()\n          // refresh when status change: !successed -> successed\n          if (nextRes?.status === 'succeeded') {\n            refreshSQLTunedList()\n          }\n        }, CHECK_TASK_INTERVAL)\n      }\n\n      return res\n    } catch (e) {\n      latestTask.current = undefined\n      setTaskStatus('failed')\n      throw e\n    }\n  })\n\n  useEffect(() => {\n    checkStatusLoop.current()\n    return () => window.clearTimeout(timer.current)\n  }, [ctx])\n\n  const handleCheckUpTask = async () => {\n    try {\n      await ctx?.ds.tuningTaskCreate(\n        (dayjs().unix() - 3 * 60 * 60) * 1000,\n        dayjs().unix() * 1000\n      )\n      notification.success({\n        message: 'Successed'\n      })\n    } catch (e: any) {\n      notification.error({\n        message: e.message\n      })\n    } finally {\n      checkStatusLoop.current()\n    }\n  }\n  const handleCancelTask = async () => {\n    try {\n      await ctx?.ds.tuningTaskCancel(latestTask.current!.task_id)\n      notification.success({\n        message: 'Successed'\n      })\n    } catch (e: any) {\n      notification.error({\n        message: e.message\n      })\n    } finally {\n      checkStatusLoop.current()\n    }\n  }\n\n  const hanleDeactivate = () => {\n    setShowDeactivateModal(false)\n    setShowSetting(false)\n    onHandleDeactivate?.()\n  }\n\n  const handleDeactivateModalCancel = () => {\n    setShowDeactivateModal(false)\n    setShowSetting(false)\n  }\n\n  return (\n    <>\n      <Card>\n        <Toolbar>\n          <Space align=\"center\">\n            <Typography.Title level={4}>Performance Insight</Typography.Title>\n          </Space>\n          <Space align=\"center\" style={{ marginTop: 0 }}>\n            <Tooltip\n              title=\"Each insight will cover diagnosis data from the past 3 hours.\"\n              placement=\"rightTop\"\n            >\n              <InfoCircleOutlined />\n            </Tooltip>\n            <Button\n              disabled={isTaskRunning}\n              onClick={() => {\n                // if index_insight_dont_remind_checkup_notice has been checked, don't show comfirm modal again, checkup directly.\n                if (!dontRemindCheckUpNotice.current) {\n                  setShowCheckUpModal(true)\n                } else {\n                  handleCheckUpTask()\n                }\n              }}\n              loading={isTaskRunning}\n            >\n              {isTaskRunning ? 'Task is Running' : 'Check Up'}\n            </Button>\n            {isTaskRunning && (\n              <Button onClick={handleCancelTask}>Cancel Task</Button>\n            )}\n            <Button onClick={() => setShowSetting(true)}>Setting</Button>\n          </Space>\n        </Toolbar>\n        <Drawer\n          title=\"Setting\"\n          width={300}\n          visible={showSetting}\n          closable={true}\n          onClose={() => setShowSetting(false)}\n          destroyOnClose={true}\n        >\n          <p>\n            After deactivation, the system will delete all historical insight\n            data.\n          </p>\n          <Button\n            onClick={() => setShowDeactivateModal(true)}\n            loading={isDeactivating}\n          >\n            Deactivate\n          </Button>\n        </Drawer>\n        <Modal\n          title=\"Deactivate Perfomance Insight\"\n          visible={showDeactivateModal}\n          onCancel={handleDeactivateModalCancel}\n          destroyOnClose={true}\n          onOk={hanleDeactivate}\n        >\n          <p>\n            After disabling, all insight data generated by this feature will be\n            deleted.\n          </p>\n        </Modal>\n        <Modal\n          title=\"Check Up Notice\"\n          visible={showCheckUpModal}\n          onCancel={() => setShowCheckUpModal(false)}\n          destroyOnClose={true}\n          footer={null}\n        >\n          <p>\n            When performing checks, system tables are queried. It is not\n            recommended to perform checks when the cluster is already under\n            heavy load.\n          </p>\n          <div style={{ textAlign: 'center' }}>\n            <Space direction=\"vertical\" align=\"center\">\n              <Checkbox\n                // FIXME: set value after confirmed\n                onChange={(e) =>\n                  (dontRemindCheckUpNotice.current = e.target.checked)\n                }\n              >\n                Don't remind me again.\n              </Checkbox>\n              <Button\n                onClick={async () => {\n                  await handleCheckUpTask()\n                  setShowCheckUpModal(false)\n                }}\n                type=\"primary\"\n              >\n                Comfirm\n              </Button>\n            </Space>\n          </div>\n        </Modal>\n        {/* {showAlert && (\n          <Alert\n            message=\"The SQL user being used during activation is no longer available, please deactivate the function first and then reactivate the function to use it.\"\n            type=\"warning\"\n            showIcon\n            closable\n          />\n        )} */}\n      </Card>\n      <IndexInsightTable\n        sqlTunedList={sqlTunedList}\n        loading={loading}\n        onHandlePaginationChange={refreshSQLTunedList}\n      />\n    </>\n  )\n}\n\nexport default IndexInsightList\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/component/IndexInsightListWithRegister.module.less",
    "content": ".container {\n  margin: 2rem;\n}\n\n.commandBlock {\n  display: flex;\n  justify-content: space-between;\n  background: #f1f1f1;\n  font-size: 12px;\n  padding: 1rem;\n}\n\n.instructionCard {\n  margin-bottom: 1rem;\n}\n\n.siteFormItemIcon {\n  fill: '#c1c1c1';\n}\n\n.loginFormButton {\n  width: 100%;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/component/IndexInsightListWithRegister.tsx",
    "content": "import React, { useCallback, useContext, useEffect, useState } from 'react'\nimport { SQLAdvisorContext } from '../context'\nimport {\n  Typography,\n  Form,\n  Input,\n  Button,\n  Card,\n  Row,\n  Col,\n  notification,\n  Skeleton,\n  Alert\n} from 'antd'\nimport styles from './IndexInsightListWithRegister.module.less'\nimport { HighlightSQL, CopyLink } from '@lib/components'\nimport { LockOutlined, UserOutlined } from '@ant-design/icons'\nimport IndexInsightList from './IndexInsightList'\nimport { DbassSecuritySettingImg } from '../utils/dbaasSecuritySetting'\n\nconst { Title } = Typography\n\nconst sql = [\n  `CREATE user 'yourusername'@'%' IDENTIFIED by 'yourpassword';`,\n  `GRANT SELECT ON information_schema.* TO 'yourusername'@'%';`,\n  `GRANT SELECT ON mysql.* TO 'yourusername'@'%';`,\n  `GRANT PROCESS, REFERENCES ON *.* TO 'yourusername'@'%';`,\n  `FLUSH PRIVILEGES;`\n]\n\nconst RegisterForm = ({ setIsUserDBRegistered }: UnRegisteredUserDBProps) => {\n  const ctx = useContext(SQLAdvisorContext)\n  const [isPosting, setIsPosting] = useState<boolean>(false)\n  const handleOnFinish = useCallback(\n    async (values: any) => {\n      setIsPosting(true)\n\n      const params = {\n        userName: values.username,\n        addr: values.addr,\n        port: values.port,\n        password: values.password\n      }\n\n      try {\n        const res = await ctx?.ds.activateDBConnection(params)\n        notification.success({\n          message: res\n        })\n        setIsUserDBRegistered(true)\n      } catch (e: any) {\n        notification.error({\n          message: e.message\n        })\n      } finally {\n        setIsPosting(false)\n      }\n    },\n    [ctx, setIsUserDBRegistered]\n  )\n\n  return (\n    <Form\n      name=\"normal_login\"\n      className=\"login-form\"\n      initialValues={{ remember: true }}\n      onFinish={handleOnFinish}\n    >\n      <Form.Item\n        name=\"username\"\n        rules={[{ required: true, message: 'Please input your Username!' }]}\n      >\n        <Input\n          prefix={<UserOutlined className={styles.siteFormItemIcon} />}\n          placeholder=\"Username\"\n        />\n      </Form.Item>\n      <Form.Item name=\"password\">\n        <Input\n          prefix={<LockOutlined className={styles.siteFormItemIcon} />}\n          type=\"password\"\n          placeholder=\"Password\"\n        />\n      </Form.Item>\n\n      <Form.Item>\n        <Button\n          type=\"primary\"\n          htmlType=\"submit\"\n          className={styles.loginFormButton}\n          loading={isPosting}\n        >\n          Active\n        </Button>\n      </Form.Item>\n    </Form>\n  )\n}\n\ninterface UnRegisteredUserDBProps {\n  setIsUserDBRegistered: (isUserDBRegistered: boolean) => void\n}\n\nconst UnRegisteredUserDB: React.FC<UnRegisteredUserDBProps> = ({\n  setIsUserDBRegistered\n}) => {\n  return (\n    <div className={styles.container}>\n      <Row gutter={32}>\n        <Col className=\"gutter-row\" span={16}>\n          <Card className={styles.instructionCard}>\n            <Title level={5}>Performance Insight (BETA):</Title>\n            <p>\n              Improve your database performance with ease by using our advanced\n              algorithms to analyze your collection metadata and slow query\n              logs. Trust our feature to identify areas where indexes or changes\n              to the schema can improve query performance and make the necessary\n              adjustments for optimal performance. Activate now for faster and\n              more efficient database performance.\n            </p>\n          </Card>\n          <Card className={styles.instructionCard}>\n            <Title level={5}>Permissions required:</Title>\n            <Alert\n              message={`Please replace your user name and password in the 'yourusername' and 'yourpassword' field.`}\n              type=\"warning\"\n              showIcon\n            />\n            <p style={{ paddingTop: 10 }}>\n              This feature requires read access to database `information_schema`\n              and `mysql`. You can create a new sql user on your SQL client to\n              activate this feature.\n            </p>\n            <div className={styles.commandBlock}>\n              <div>\n                {sql.map((s) => (\n                  <HighlightSQL key={s} sql={s} compact format={false} />\n                ))}\n              </div>\n              <CopyLink data={sql.join('\\n')} />\n            </div>\n          </Card>\n          <Card className={styles.instructionCard}>\n            <Title level={5}>Network required:</Title>\n            <p>\n              During the Beta phase, this feature requires users to{' '}\n              <strong>manually</strong> open the IP access list before it can be\n              enabled. In subsequent versions, a more user-friendly method of\n              enabling this feature will be supported.\n            </p>\n            <p>You should</p>\n            <p>\n              1. Open \"<strong>Allow Access From Anywhere</strong>\" on IP Access\n              List.\n            </p>\n            <p>\n              2. Click \"<strong>Apply</strong>\" Buttom to complete the change.\n            </p>\n            <img src={DbassSecuritySettingImg} style={{ width: '100%' }} />\n          </Card>\n        </Col>\n        <Col className=\"gutter-row\" span={8} style={{ marginTop: '10rem' }}>\n          <div style={{ position: 'fixed', width: '350px' }}>\n            <RegisterForm setIsUserDBRegistered={setIsUserDBRegistered} />\n          </div>\n        </Col>\n      </Row>\n    </div>\n  )\n}\n\nconst IndexInsightListWithRegister = () => {\n  const ctx = useContext(SQLAdvisorContext)\n  const [isLoading, setIsLoading] = useState<boolean>(true)\n  const [isUserDBRegistered, setIsUserDBRegistered] = useState<boolean>(false)\n  const [isDeactivating, setIsDeactivating] = useState<boolean>(false)\n\n  useEffect(() => {\n    const registerUserDBStatusGet = async () => {\n      try {\n        setIsLoading(true)\n        const status = await ctx?.ds.checkDBConnection()\n        setIsUserDBRegistered(status)\n      } catch (e) {\n        setIsUserDBRegistered(false)\n      } finally {\n        setIsLoading(false)\n      }\n    }\n\n    registerUserDBStatusGet()\n  }, [ctx])\n\n  const handleDeactivate = async () => {\n    try {\n      setIsDeactivating(true)\n      const res = await ctx?.ds.deactivateDBConnection()\n      setIsUserDBRegistered(false)\n      notification.success({\n        message: res\n      })\n    } catch (e: any) {\n      notification.error({\n        message: e.message\n      })\n    } finally {\n      setIsDeactivating(false)\n    }\n  }\n\n  return (\n    <>\n      {isLoading ? (\n        <Skeleton active paragraph={{ rows: 4 }} style={{ padding: '48px' }} />\n      ) : (\n        <>\n          {isUserDBRegistered ? (\n            <IndexInsightList\n              onHandleDeactivate={handleDeactivate}\n              isDeactivating={isDeactivating}\n            />\n          ) : (\n            <UnRegisteredUserDB setIsUserDBRegistered={setIsUserDBRegistered} />\n          )}\n        </>\n      )}\n    </>\n  )\n}\n\nexport default IndexInsightListWithRegister\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/component/IndexInsightTable.tsx",
    "content": "import React, { useEffect, useMemo, useState, useContext, useRef } from 'react'\n\nimport { Card, HighlightSQL, TextWrap } from '@lib/components'\nimport { Tooltip, Table } from 'antd'\n\nimport { Link } from 'react-router-dom'\nimport { getSuggestedCommand } from '../utils/suggestedCommandMaps'\nimport { SQLAdvisorContext } from '../context'\nimport { SQLTunedListProps } from '../types'\nimport dayjs from 'dayjs'\nimport tz from '@lib/utils/timezone'\n\nconst DEF_PAGINATION_PARAMS = {\n  pageNumber: 1,\n  pageSize: 20\n}\n\nexport const useSQLTunedListGet = () => {\n  const ctx = useContext(SQLAdvisorContext)\n  const [sqlTunedList, setSqlTunedList] = useState<SQLTunedListProps | null>(\n    null\n  )\n  const [loading, setLoading] = useState(false)\n\n  const sqlTunedListGet = useRef(\n    async (pageNumber?: number, pageSize?: number) => {\n      setLoading(true)\n      try {\n        const res = await ctx?.ds.tuningListGet(\n          pageNumber || DEF_PAGINATION_PARAMS.pageNumber,\n          pageSize || DEF_PAGINATION_PARAMS.pageSize\n        )\n        setSqlTunedList(res!)\n      } catch (e) {\n        console.log(e)\n      } finally {\n        setLoading(false)\n      }\n    }\n  )\n\n  useEffect(() => {\n    sqlTunedListGet.current()\n  }, [])\n\n  return { sqlTunedList, refreshSQLTunedList: sqlTunedListGet.current, loading }\n}\n\ninterface IndexInsightTableProps {\n  sqlTunedList: SQLTunedListProps | null\n  loading: boolean\n  onHandlePaginationChange?: (pageNumber: number, pageSize: number) => void\n}\n\nconst IndexInsightTable = ({\n  sqlTunedList,\n  loading,\n  onHandlePaginationChange\n}: IndexInsightTableProps) => {\n  const columns = useMemo(\n    () => [\n      {\n        title: 'Impact',\n        dataIndex: 'impact',\n        key: 'impact',\n        ellipsis: true,\n        render: (_, record) => {\n          return <>{record.impact}</>\n        }\n      },\n      {\n        title: 'Type',\n        dataIndex: 'insight_type',\n        key: 'type',\n        ellipsis: true,\n        render: (_, record) => {\n          return <>{record.insight_type}</>\n        }\n      },\n      {\n        title: 'Suggested Command',\n        dataIndex: 'suggested_command',\n        key: 'suggested_command',\n        ellipsis: true,\n        render: (_, record) => {\n          return (\n            <>\n              {record.suggested_command?.map((command, idx) => (\n                <Tooltip\n                  title={getSuggestedCommand(\n                    command.suggestion_key,\n                    command.params\n                  )}\n                  placement=\"topLeft\"\n                  key={idx}\n                >\n                  <span>\n                    {getSuggestedCommand(\n                      command.suggestion_key,\n                      command.params\n                    )}\n                  </span>\n                </Tooltip>\n              ))}\n            </>\n          )\n        }\n      },\n      {\n        title: 'Related Slow SQL',\n        dataIndex: 'sql_statement',\n        key: 'related_slow_sql',\n        ellipsis: true,\n        render: (_, record) => {\n          return (\n            <Tooltip\n              title={<HighlightSQL sql={record.sql_statement} theme=\"dark\" />}\n              placement=\"left\"\n            >\n              <TextWrap>\n                <HighlightSQL sql={record.sql_statement} compact />\n              </TextWrap>\n            </Tooltip>\n          )\n        }\n      },\n      {\n        title: `Check Up Time (UTC${\n          tz.getTimeZone() < 0 ? '-' : '+'\n        }${tz.getTimeZone()})`,\n        dataIndex: 'checked_time',\n        key: 'check_up_time',\n        ellipsis: true,\n        render: (_, record) => {\n          return (\n            <>\n              {dayjs(record.checked_time)\n                .utcOffset(tz.getTimeZone())\n                .format('YYYY-MM-DD HH:mm:ss')}\n            </>\n          )\n        }\n      },\n      {\n        title: 'Results',\n        dataIndex: 'detail',\n        key: 'detail',\n        render: (_, record) => {\n          return <Link to={`/sql_advisor/detail?id=${record.id}`}>Detail</Link>\n        }\n      }\n    ],\n    []\n  )\n\n  return (\n    <Card noMarginTop>\n      <Table\n        dataSource={sqlTunedList?.tuned_results!}\n        columns={columns}\n        loading={loading}\n        size=\"small\"\n        pagination={{\n          total: sqlTunedList?.count,\n          defaultCurrent: DEF_PAGINATION_PARAMS.pageNumber,\n          pageSize: DEF_PAGINATION_PARAMS.pageSize,\n          onChange: (pageNumber, pageSize) => {\n            onHandlePaginationChange?.(pageNumber, pageSize)\n          }\n        }}\n      />\n    </Card>\n  )\n}\n\nexport default IndexInsightTable\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/component/index.tsx",
    "content": "import IndexInsightTable from './IndexInsightTable'\nimport IndexInsightListWithRegister from './IndexInsightListWithRegister'\nimport IndexInsightList from './IndexInsightList'\n\nexport { IndexInsightTable, IndexInsightListWithRegister, IndexInsightList }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/context/index.tsx",
    "content": "import { createContext } from 'react'\nimport {\n  TuningDetailProps,\n  SQLTunedListProps,\n  PerfInsightTask\n} from '../types/'\n\nexport interface ISQLAdvisorDataSource {\n  tuningListGet(\n    pageNumber?: number,\n    pageSize?: number\n  ): Promise<SQLTunedListProps>\n\n  tuningDetailGet(id: number): Promise<TuningDetailProps>\n\n  tuningLatestGet(): Promise<PerfInsightTask>\n\n  tuningTaskCreate(startTime: number, endTime: number): Promise<any>\n\n  tuningTaskCancel(id: number): Promise<any>\n\n  activateDBConnection(params: {\n    userName: string\n    password: string\n  }): Promise<any>\n\n  deactivateDBConnection(): Promise<any>\n\n  checkDBConnection(): Promise<any>\n}\n\nexport interface ISQLAdvisorContext {\n  ds: ISQLAdvisorDataSource\n  orgId?: string\n  clusterId?: string\n  registerUserDB?: boolean\n}\n\nexport const SQLAdvisorContext = createContext<ISQLAdvisorContext | null>(null)\n\nexport const SQLAdvisorProvider = SQLAdvisorContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { HashRouter as Router, Routes, Route } from 'react-router-dom'\n\nimport { Root } from '@lib/components'\nimport { addTranslations } from '@lib/utils/i18n'\n\nimport { List, Detail } from './pages'\nimport { SQLAdvisorContext } from './context'\n\nimport translations from './translations'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/sql_advisor\" element={<List />} />\n      <Route path=\"/sql_advisor/detail\" element={<Detail />} />\n    </Routes>\n  )\n}\n\nexport default function () {\n  const ctx = useContext(SQLAdvisorContext)\n\n  if (ctx === null) {\n    throw new Error('SQLAdvisorContext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/pages/Detail/index.module.less",
    "content": ".InlineCodeBlock {\n  background: 1#f1f1f1;\n  padding: 1px 8px;\n  border: 1px solid #dedede;\n}\n\n.SuggestedCommand {\n  background: #f1f1f1;\n  padding: 3px 10px;\n  display: flex;\n  width: 100%;\n  flex-flow: row;\n  justify-content: space-between;\n}\n\n.HighImpact {\n  color: #f70000;\n}\n\n.MediumImpact {\n  color: #fc6f03;\n}\n\n.LowImpact {\n  color: #e5c613;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/pages/Detail/index.tsx",
    "content": "import React, { useState, useMemo, useEffect, useContext } from 'react'\n\nimport {\n  Head,\n  Descriptions,\n  Expand,\n  HighlightSQL,\n  CopyLink\n} from '@lib/components'\nimport { Link } from 'react-router-dom'\nimport { ArrowLeftOutlined } from '@ant-design/icons'\nimport useQueryParams from '@lib/utils/useQueryParams'\nimport { Space, Collapse, Tooltip, Table } from 'antd'\n\nimport { LoadingOutlined } from '@ant-design/icons'\nimport { getSuggestedCommand } from '../../utils/suggestedCommandMaps'\nimport { TuningDetailProps } from '../../types'\nimport { SQLAdvisorContext } from '../../context'\nimport dayjs from 'dayjs'\nimport tz from '@lib/utils/timezone'\nimport styles from './index.module.less'\n\nconst { Panel } = Collapse\n\nconst PanelMaps: Record<string, string> = {\n  basic: 'Basic Information',\n  table_clause: 'Existing Indexes',\n  table_healthies: 'Table Healthies'\n}\n\nexport default function SQLAdvisorDetail() {\n  const ctx = useContext(SQLAdvisorContext)\n  const { id } = useQueryParams()\n  const [sqlTunedDetail, setSqlTunedDetail] =\n    useState<TuningDetailProps | null>(null)\n  const [sqlExpanded, setSqlExpanded] = useState(false)\n  const [loading, setLoading] = useState(true)\n  const toggleSqlExpanded = () => setSqlExpanded((prev) => !prev)\n\n  const tableClausesColumns = useMemo(\n    () => [\n      {\n        title: 'Table',\n        dataIndex: 'table_name',\n        key: 'table_name',\n        ellipsis: true,\n        render: (_, row) => {\n          return (\n            <Tooltip title={row.table_name} placement=\"topLeft\">\n              {row.table_name}\n            </Tooltip>\n          )\n        }\n      },\n      {\n        title: 'Index Name',\n        dataIndex: 'index_name',\n        key: 'index_name',\n        ellipsis: true,\n        render: (_, row) => {\n          return <>{row.index_name}</>\n        }\n      },\n      {\n        title: 'Column',\n        dataIndex: 'columns',\n        key: 'columns',\n        ellipsis: true,\n        render: (_, row) => {\n          return <>{row.columns}</>\n        }\n      },\n      {\n        title: 'Clustered',\n        dataIndex: 'clusterd',\n        key: 'clusterd',\n        width: 100,\n        ellipsis: true,\n        render: (_, row) => {\n          return <>{row.clusterd ? 'Yes' : 'No'}</>\n        }\n      },\n      {\n        title: 'Visible',\n        dataIndex: 'visible',\n        key: 'visible',\n        ellipsis: true,\n        render: (_, row) => {\n          return <>{row.visible ? 'Yes' : 'No'}</>\n        }\n      }\n    ],\n    []\n  )\n\n  const tableHealthiesColumns = useMemo(\n    () => [\n      {\n        title: 'Table',\n        dataIndex: 'table_name',\n        key: 'table_name',\n        ellipsis: true,\n        render: (_, row) => {\n          return (\n            <Tooltip title={row.table_name} placement=\"topLeft\">\n              {row.table_name}\n            </Tooltip>\n          )\n        }\n      },\n      {\n        title: 'Healthy',\n        dataIndex: 'healthy',\n        key: 'healthy',\n        ellipsis: true,\n        render: (_, row) => {\n          return <>{row.healthy}</>\n        }\n      },\n      {\n        title: `Analyzed Time (UTC${\n          tz.getTimeZone() < 0 ? '-' : '+'\n        }${tz.getTimeZone()})`,\n        dataIndex: 'checked_time',\n        key: 'checked_time',\n        ellipsis: true,\n        render: () => {\n          return (\n            <>\n              {dayjs(sqlTunedDetail?.checked_time)\n                .utcOffset(tz.getTimeZone())\n                .format('YYYY-MM-DD HH:mm:ss')}\n            </>\n          )\n        }\n      }\n    ],\n    [sqlTunedDetail]\n  )\n\n  const existingIndexes = sqlTunedDetail?.table_clauses?.map(\n    (item) => item.index_list\n  )\n\n  const suggestedCommands = sqlTunedDetail?.suggested_command\n  const suggestedCommandsCopyData =\n    suggestedCommands &&\n    suggestedCommands.map((command) =>\n      getSuggestedCommand(command.suggestion_key, command.params)\n    )\n\n  const suggestedCMDExplanation =\n    suggestedCommands &&\n    suggestedCommands\n      .map((cmd) => {\n        const fields = cmd.cmd_explanation.fields\n        const table_name = cmd.cmd_explanation.table_name\n        const explanation = {\n          fields: fields,\n          table_name: table_name\n        }\n        return fields && table_name ? explanation : null\n      })\n      .filter((cmd) => cmd)\n\n  useEffect(() => {\n    const sqlTunedDetailGet = async () => {\n      try {\n        const res = await ctx?.ds.tuningDetailGet(id)\n        setSqlTunedDetail(res!)\n      } finally {\n        setLoading(false)\n      }\n    }\n\n    sqlTunedDetailGet()\n  }, [ctx, id])\n\n  return (\n    <div>\n      <Head\n        title={\n          sqlTunedDetail\n            ? `Performance Insight Detail - ${sqlTunedDetail.insight_type}`\n            : 'Performance Insight Detail'\n        }\n        back={\n          <Link to=\"/sql_advisor\">\n            <ArrowLeftOutlined />\n          </Link>\n        }\n      ></Head>\n      <div style={{ margin: 48 }}>\n        <div style={{ textAlign: 'center' }}>\n          {loading && <LoadingOutlined />}\n        </div>\n        {sqlTunedDetail && (\n          <Space direction=\"vertical\" style={{ display: 'flex' }}>\n            <Collapse defaultActiveKey={['1']} expandIconPosition=\"end\">\n              <Panel header=\"Basic Information\" key=\"1\">\n                <Descriptions>\n                  <Descriptions.Item\n                    span={2}\n                    label={\n                      <Space size=\"middle\">\n                        <span>SQL Statement</span>\n                        <Expand.Link\n                          expanded={sqlExpanded}\n                          onClick={toggleSqlExpanded}\n                        />\n                      </Space>\n                    }\n                  >\n                    <Expand\n                      expanded={sqlExpanded}\n                      collapsedContent={\n                        <HighlightSQL\n                          sql={sqlTunedDetail.sql_statement}\n                          compact\n                        />\n                      }\n                    >\n                      <HighlightSQL sql={sqlTunedDetail.sql_statement} />\n                    </Expand>\n                  </Descriptions.Item>\n                  <Descriptions.Item\n                    span={2}\n                    label={\n                      <Space>\n                        <span>SQL Digest</span>\n                        <CopyLink data={sqlTunedDetail.sql_digest} />\n                      </Space>\n                    }\n                  >\n                    {sqlTunedDetail.sql_digest}\n                  </Descriptions.Item>\n                  <Descriptions.Item\n                    span={2}\n                    label={\n                      <Space>\n                        <span>Plan Digest</span>\n                        <CopyLink data={sqlTunedDetail.plan_digest} />\n                      </Space>\n                    }\n                  >\n                    {sqlTunedDetail.plan_digest}\n                  </Descriptions.Item>\n                </Descriptions>\n                {suggestedCMDExplanation && suggestedCMDExplanation.length > 0 && (\n                  <>\n                    <p>The query is {sqlTunedDetail.insight_type} on</p>\n                    <ul>\n                      {suggestedCMDExplanation.map((cmdExp) => (\n                        <li>\n                          fields{' '}\n                          <span className={styles.InlineCodeBlock}>\n                            {cmdExp?.fields.join(', ')}\n                          </span>{' '}\n                          in the{' '}\n                          <span className={styles.InlineCodeBlock}>\n                            {cmdExp!.table_name}\n                          </span>{' '}\n                          table\n                        </li>\n                      ))}\n                    </ul>\n                    <p>\n                      which is expected to have a{' '}\n                      <span\n                        className={`${\n                          sqlTunedDetail.impact.toUpperCase() === 'HIGH'\n                            ? styles.HighImpact\n                            : sqlTunedDetail.impact.toUpperCase() === 'MEDIUM'\n                            ? styles.MiddleImpact\n                            : styles.LowImpact\n                        }`}\n                      >\n                        {sqlTunedDetail.impact.toUpperCase()}{' '}\n                      </span>\n                      on query performance.\n                    </p>\n                  </>\n                )}\n                <p>\n                  You can execute this command on create the corresponding\n                  index:{' '}\n                </p>\n                {suggestedCommands && suggestedCommandsCopyData && (\n                  <div className={styles.SuggestedCommand}>\n                    <div>\n                      {suggestedCommands.map((command) => (\n                        <div>\n                          {getSuggestedCommand(\n                            command!.suggestion_key,\n                            command!.params\n                          )}\n                        </div>\n                      ))}\n                    </div>\n                    <CopyLink data={suggestedCommandsCopyData.join('\\n')} />\n                  </div>\n                )}\n              </Panel>\n            </Collapse>\n            {sqlTunedDetail.table_clauses &&\n              existingIndexes &&\n              existingIndexes.flat().length > 0 && (\n                <Collapse\n                  defaultActiveKey={[PanelMaps.table_clause]}\n                  expandIconPosition=\"end\"\n                >\n                  <Panel\n                    header={PanelMaps.table_clause}\n                    key={PanelMaps.table_clause}\n                  >\n                    <Table\n                      columns={tableClausesColumns}\n                      dataSource={existingIndexes.flat()}\n                      size=\"small\"\n                      pagination={false}\n                    />\n                  </Panel>\n                </Collapse>\n              )}\n            {sqlTunedDetail.table_healthies &&\n              sqlTunedDetail.table_healthies.length > 0 && (\n                <Collapse\n                  defaultActiveKey={[PanelMaps.table_healthies]}\n                  expandIconPosition=\"end\"\n                >\n                  <Panel\n                    header={PanelMaps.table_healthies}\n                    key={PanelMaps.table_healthies}\n                  >\n                    <Table\n                      columns={tableHealthiesColumns}\n                      dataSource={sqlTunedDetail.table_healthies}\n                      size=\"small\"\n                      pagination={false}\n                    />\n                  </Panel>\n                </Collapse>\n              )}\n          </Space>\n        )}\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/pages/List/List.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.list {\n  &_container {\n    display: flex;\n    flex-direction: column;\n    height: 100vh;\n  }\n\n  &_toolbar {\n    @media only screen and (max-width: @screen-md) {\n      flex-direction: column;\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/pages/List/index.tsx",
    "content": "import React, { useContext } from 'react'\n\nimport styles from './List.module.less'\nimport { IndexInsightListWithRegister, IndexInsightList } from '../../component'\nimport { SQLAdvisorContext } from '../../context'\n\nexport default function SQLAdvisorOverview() {\n  const ctx = useContext(SQLAdvisorContext)\n\n  return (\n    <div className={styles.list_container}>\n      {ctx?.registerUserDB ? (\n        <IndexInsightListWithRegister />\n      ) : (\n        <IndexInsightList />\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/pages/index.tsx",
    "content": "import List from './List'\nimport Detail from './Detail'\n\nexport { List, Detail }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/translations/en.yaml",
    "content": "sql_advisor:\n  nav_title: SQL Advisor\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/translations/zh.yaml",
    "content": "sql_advisor:\n  nav_title: SQL 诊断\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/types/index.ts",
    "content": "export interface TuningDetailProps {\n  analyzed_time: string\n  checked_time: number\n  id: number\n  impact: string\n  insight_type: string\n  plan: string\n  plan_digest: string\n  sql_digest: string\n  sql_statement: string\n  suggested_command: {\n    cmd_explanation: {\n      table_name: string\n      fields: string[]\n    }\n    suggestion_key: string\n    params: string[]\n  }[]\n  table_clauses: {\n    table_name: string\n    where_clause: string[]\n    selected_fields: null\n    index_list: {\n      table_name: string\n      columns: string\n      index_name: string\n      clusterd: boolean\n      visible: boolean\n    }[]\n  }[]\n  table_healthies: {\n    table_name: string\n    healthy: string\n    analyzed_time: string\n  }[]\n  use_Stats: boolean\n  use_index: boolean\n}\n\nexport interface SQLTunedListProps {\n  tuned_results: TuningDetailProps[]\n  count: number\n}\n\nexport type PerfInsightTaskStatus =\n  | 'succeeded'\n  | 'created'\n  | 'running'\n  | 'failed'\n\nexport interface PerfInsightTask {\n  cluster_id: number\n  created_at: number\n  dbaas_cluster_id: number\n  dbaas_org_id: number\n  perform_insight_cluster_id: number\n  status: PerfInsightTaskStatus\n  task_id: number\n  type: 'unstable_plan_insight' | 'index_insight' | 'hint_insight'\n  update_at: number\n  last_failed_message: string\n}\n\nexport type TuningTaskStatus = boolean\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/utils/dbaasSecuritySetting.ts",
    "content": "export const DbassSecuritySettingImg = `data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAsLCwsLCwwNDQwREhASERgWFBQWGCUaHBocGiU4IykjIykjODI8MS4xPDJZRj4+RllnVlJWZ31wcH2dlZ3Nzf8BCwsLCwsLDA0NDBESEBIRGBYUFBYYJRocGhwaJTgjKSMjKSM4MjwxLjE8MllGPj5GWWdWUlZnfXBwfZ2Vnc3N///CABEIBZQJlgMBEQACEQEDEQH/xAAcAAEAAgMBAQEAAAAAAAAAAAAABAUBAwYCBwj/2gAIAQEAAAAA+uAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADHybkek+yAAHO/OvrHO9iCB8eqfof0MY4buqBb/DPvYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc58J/SnzPt9HCX/e89wd/wB58u+p/NPoPzXf0VRT8D9noPqHzzq7j5fQfafk317h+a76q+HfcPm8X7Tx/X89TdV2HzT14+lcBRd10gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPhVH3P1f81fW/mX134f8Abfn3074L+m/zz9g/Pv2Xfwn0H5b+iPzd97/Pn6RkU/wvf9Ivfh31347+hPzd+jPnUD6Z8O+ufKPqvxr7Z8f+wfCf0T+evttV9JAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACjqOy/P8A33xz6k6v4N+oqLf+eP07+c/svwD9TcZ87+l/LPv/AMvpon3Rxd8/Of2L5f32r6l+Y/1D82id38I+ucT9o/MP2TifuP5k/Q3IcbA/QuQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABr/OcmD9++YVED7v8mqYv6K/Ou+B9v+AfqbjOA+wfnb7Xc/m37n2zkvi0qZ99/PNtE+/fmn6RdfHftnxn6px3238v/pn892nMfob4ZdQvvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMa/XtjJ5zkBjJ8/8Akv6ZyPPpjz7AMZxo+U9R8a/Sm7HoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHH3FwAAAHLUHY24AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGM4zgyYZAYyMZBjLGcZxlgMgxkwzhkxnAzjLAZBjJhnBljOBnGWDIYZxkwzgzhnAz59MGQwzjJjLAAZMDIYZxkxlgM4DIwZGMmDOMjBlgyGDLGcZMGcZAYZxljJhkYyYM4zjIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYzjIAAAAAAAAAAAAAAAMZAAAAAAAR93oAAAAAAAAAAAAAAAAAAAAAAAAAAIvD9Pu92WKG+qJ2iLa1dnG02tFa8t2SHMiwZfv3uzRzN9bd0N1B57sNfK9RW3amtYGKuzteIutV/A1W8aBIj3FDfcR22qstI8S2ob+gu9lfI1RZsTZzvZeqS3rFtRxOohwtEXqaC69w93iDc0UHrOW0T9HUV2q0pYdpz3Vwd3i0o7ygv6ex0x7bmNNpizqd9X1QAAAAAAAAAAAAAAAAAAAAAAAAAHK9JzmvreZuoUSLix8bYVPmx6bTUdMq4mlo3T9s/kvcL3se2es98rrufFxwfXcV7vrzdyk/bWVGfoFMqpdd1NC6ml118yg6WttaPpOdiWmmdA6bmnYY5fR4ke+g5XsudorPLo+Y81l1VJ3Scx2nMxJm3oOcobbo+Z8YsJ3IujhWVNc8+kWd/VSKOLaw+irboAAAAAAAAAAAAAAAAAAAAAAAAADl+o4r32VNEsee2TpkSfXQt0+Hac90Fo+b/ROP27YPTzqTTrWtHa1ELutnFTdF9Ycvp8TJy45SZb0MWR1FJModFvo86OspqXZri9VQWUKdQ3yBpmdTx0Lvq3nt273t1xOu4uNZe5kf3S3+uH7nwdHX8u6g4iN0UWOrumteVk2XPW9bMg2uKTuqedSp9Bf01xbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFVax6TowAAAAAABVbp4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxj0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKHXf+wYZAAABXWIAAAAj7NgBCmgxkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABw3YcZ2nM9Z55+35v312qp09DRaui5yTbc10EXZJhS+e6OBBtqWB20Hgfo9DIu1fT9HGpOpq67pabTf0Ob3l7iwr+D7Wks7+g93iFR9Rx9/ZV2ylv/AJz9FrPF7ST5wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD5vZe9HccVs6zi+k9WtdzV7qc72XF9dXrXmt3jxp7Xi3Zcb3HA9+4u6jR7uwrIdXq7ep5vsYfH20/nOqj6Lixcbbw4V7zunsJ/EdnWUXvseK09JP5Xo+P33lF3IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABxfRUsPt+I3dlwl3IuK3nbiNSyek08j28Pn7OEn13bcR77Xh+34D6A4y5xAuLLg+motXbQuY62HyfUSNHJ9ro5ru3GXSotqqd0GeI7au5p2HGdRU+4HRc1d2FL0wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABVWtRZ8l0cvkry05vpK7lrLqOadHy0275K8s6rfJ98l0Wi1ic3cW6jtKN0ipgTttD11ZS9dSwOp5t0XJXVs5y4R7PnNXUK7n+rrt9RO20nW1e+Bo6WusQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADVCsgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYyAYyAAGMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjIAD5TK+lc50kHxQWl78zsuh0X9Xu9xJvvf4pb2nm1tN2wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD5R7ves43Vmy6Kb8xndXy+jx0FJZ648d1XOaZvS8R9HAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcQ3a1c6yv6rie2hcKtqPp+T+i8FXfReabrikhfRQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxnDIAADGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADGQBXwzIAAYAATpwYhgAMZABgGZG0AAAAAAAAAAAAAAAAAAAAAAPHsAAAAAAAAAAAAAYyAqvdkMGcBkYAAM4q/dkOc8gAAAABbTwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUN8AAAAACjvDzzgAAAAAJN4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAChvgAAAAAUt0eecAAAAABKuwAAAAAAAAAAAAAAAAAAAAAAEfRPAaos7IABp3AAAAAABp2R9U8ABQ3wBoqLjcAGjeAClujzzgA2TfUWM9So3gAAJV2AAAAAAAAAAAAAAAAAAAAAAAhTK70lQ5lXdQJ1Zu1y4c6HvkRPXjXv8eYl2AAAAAAEHxuj75eQAUN8Act0FXth2VfYQ2xo21XQ1U+2AKW6PPOAGZ3jXugp8KRjZo368I4Ak3gAAAAAAAAAAAAAAAAAAAAAACq1ztOFlX7Jlfjd4hWkOVGm1O/3jbotK7xaAAAAAABVWdHZS62zABQ3wBzVtRaJi8pIEnf0XORZk+p3dKAUt0eOdAMysw5kNOg7JkaVEkYQgBJvAAAAAAAAAAAAAAAAAAAAAAAESXC8eJceVDso8hX6pejfpkR5fnRqkyq3zaAAAAAAArNWm1kgAUN8AU8SXp0TrCsjJNvzSw21ky6AKW6PPOAGZvv1C0pG7V69+NmpiMAJN4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI0kAChvgAAAAAUt0eecAG3PjwA2+PMqNgASrsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFHeAAAAAApLs884AAAAACTeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAornYAAAAANVPejmgAAAAATrcAAAAAAAAAAAAAAAAAAAAAAefQAAAAAAAAAAAAA07gGui3YAAAAANV97EaswAAAAAb7YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFfYEKaj78gBV2gAAACBtibLErrEAAIktVWoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYyAA0fLfrOKCj7yjqb6080N7qqLblLCyi7baiunO9M52s7Oiu9mNO7VWe7TRrl0tvt4udKo+y3PmPb29LstqCZZ0Ftvob2FCvXEdup7Tneh+d93OAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOUtKvxb8h18GDf2nE3/L7ep5ezseY7blMXHLzb7dY1UCD0XKd24rdZxI91E0WnNdw4udpre3308atu4MmPK11lvzFrPmcn1Njs4fuHGrvbzXbgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD57Z187reH6NVXdnw91pr+0421n8x2vG6b+FbV0DsK2LTdpw3dOP03XuHLhwOhjXri58u5y4jfUdZmTRdHGoLiLawanouY6K34fuHHdJW1+ntQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADzS3lPP5Wb03Iukla+UtPVzSXHOSrylteWsJVJfWOOT6rk7i3c+6DlXVcb66aLaKb3Y7Shvq7dQuo5Gde8rPn87exazrtlRSTpk3m7r14tgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADkutAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADz6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKLa0zS3OcZ56r2dhnGVLNl0l5jHvGWM4zhkMZxUy5NHf01ntZHj3gZoI6dc44nt8M+fXn1jLGaC/1RJ2aO9xlipjb7mgvs4yw56/8AdBfM1m+VnLGcDOMjz6or2JGs2cZYM4yeYWm1wUV6y8+gAAAAAAAAAAAAAAAAAAAAAABzXS8tW7LJZRrzloW6/sOQ8PF1WxdlpRyo13WTOnpJVDq27LLdZcppLGDF7nlrGg29lz1jzvpYSIG3ouX0WcHqudg9Xx1nG3e4Uml6jnby/wCZ6aFU0vT8z3lFEx70LK7+fy7CJL6Xm6KymeFP77vnlT77Omr7GjSlhRSdUW8jT6HuqaBBdtWeKbT6mXPN6+25rO/TLgVtzUX/ADM7R1cwAAAAAAAAAAAAAAAAAAAAAAAcZd13vdF6j5x9Jcnr6vbB5fG9LkU86zoJ+2kvuc79807+ki+13t5Zt35l1HbU0ig8dm+a9lpgY8yuj98vp6WS4qy0aLmnt6zbPgQbei7vlLpAjedHZY+f++z51ZXfCztUmxt+FjWt1UKm26uq8Unvr6Lfim8yOr5qbBrJ0iZvoOvgVtd66/PznuKLRJ3QLm55i0i+YMa4obG0orDx0O4AAAAAAAAAAAAAAAAAAAAAAAYr7HXs8xKDrEfO8qZ+yvs6qd72QJettg2MbnOsrZuYFmqZu+HNq529D97/AFDoOrrJ6FP9NXv0a9muJK2VdrVWtdN14k662w21NvElvOPeraQPNjT2+YHmxqrTWbUPfFnqm10e9hjHuptqm10SFfMhWPnkuvrt8mvl+tmKq2xWzGduiLK3+NgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADVEAAAAAAAAAAAAGGQAMzwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACJ8w1gAAAAAAAAAAAAAAebv6KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA57lPpgAAAAAAAAAAAAAAGj5B9j9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc9y/0kAAAAAAAAAAAAAADz8a+x7AAAAAAAAAAAAAAAAAAAAAAAAAheQAAAAAAAAAKOh+khpigAAAAAAAAABulAAAefjX2PYAAAAAAAAAAAAAAAAAAAAAAAAY5ux3AAAAAAAAAAUFB9MFTonZwywAGcZwAM4M4ZMADOM4ZwZwZRofRgAA8fG/sewAAAAAAAAAAAAAAAAAAAAAAAAqpcoAAAAAAAAABz3CfVJBQX4AAAAAAAAAAFVvnAAB5+NfY9gAAAAAAAAAAAAAAAAAAAAAAABTW/oAAAAAAAAABz3EfSZ5QX4AAAAAAAAAAELRaAAB4+N/Y9gAAAAAAAAAAAAAAAAAAAAAAABT23oEWUjyEeREl6m0V0vcABH8abDOI0pGko8gAHPcR9JnlBfgjbdeJICNJAA81VuAaPW0BGkgAQdNoAAHj439j2AAAAAAAAAAAAAAAAAAAAAAAAFPbegg8p3NJUdTTeOl4fr6H3caLPnpMKzkxoMn3DsoXjPjoOYWUHo6ug7OhrOkrPGyfW3dPPi7rQc9xH0meUF+Dkp+rVe01nIg7LPVwH0Cs1WNNeUlpFbYt9QRJM6uu6jPnoXKSZ8Gx2aPWiTSSLmt9+kWbZhB12QAAePjf2PYAAAAAAAAAAAAAAAAAAAAAAAAU1xkOTi9HA0++g53p+aaJ0f10nNdLopLOnn19hWZ97peyy5rTts7PkNHRRYW2/oGvfmJb1U+8HPcR9JnlBfhq4ffZa/PR85E9TOn56sstF9QdLQaCT4dLW+Kr30POvEy32cPssr6gZVMyT0HOV0iXb85t68Ice0AADx8b+x7AAAAAAAAAAAAAAAAAAAAAAAACmt/Qc5f0cTTcw89HQx7DFXfTayr9bokmxgW3PT/HuXAv6Bfsc9d0sXVaxvfvbDs6ndjx0w57iPpM8oL8KSdWePW2HZ0llvt+b6HntXqTFsauxi6Jld19f4hTKydob7GFshRunoIHRV26pn3tBDle7nmZ1+ELRaAAB4+N/Y9gAAAAAAAAAAAAAAAAAAAAAAABT23oAAMc70YAAAAAAHPcR9JnlBfgAAAAAAB55zpQAAQNVoAAHn419j2AAAAAAAAAAAAAAAAAAAAAAAAFPbegAAAAAAAAAHPcR9JnlBfgAAAAAAAAAAQdVmAAHn419j2AAAAAAAAAAAAAAAAAAAAAAYyABW75YAAAAAAAAADnuE+q7ygvwAAAAAAAAAAKmTNAADz8a+x7AAAAAAAAAAAAAAAAAAAAAAAADHPTPZkAAABhkAAAAYUVL9KFdAmgAAAAAAAAABoj9EAADx8b+x7AAAAAAAAAAAAAAAAAAAAAAAAAjeTZrAAAAAAAAAKLnvpIeI4AAAAAAAMMgAGzeAAB5+NfY9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHPcv9JAAAAAAAAAAAAAAA8fG/sewAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADnuX+kgAAAAAAAAAAAAAAePjf2PYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABz3L/SQAAAAAAAAAAAAAAPHxv7HsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoOV+kgAAAAAAAAAAAAAAa/jv2LYAAAAAAAAAAAAAAAAAAAAAAAAKKldtmk2W6nuIUv0AAAAAAAAgfMJoAAAAAAAAAAAAAAGvb9QAAAAAAAAAAAAAAAAAAAAAAAADg8XjoeD9dFQVlrUdPQWdb1VB0nLdRagAAAAAAQ60AAAAobGp9VtjUW9X792MWgmy7eugJGrpLIAAABgAA92u8AAAAAAAAAAAAAAAAAAAAAAAAOIo+o6vz8pXndfP91gn8Xb9Hzl3SW3SAAAAAAAAAAAByXT8bomSoqN2/Idpw3nf66/hpsN3PKdaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACn3WTV4kQeZst9R02yvout5LqOX6KzAAAAAAAAAAADTyllfcX1HPTfcC7sqbhOo6ullVK2sYE8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiygAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1bQAAAAAAAAAAAABjIBhkAAAAAAAAAAAABjIBq0AAAAAAkbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGMgAAAAAAAAAAAAB59AIkgAAAAAHnaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEOSAAAAAB7AAAAAAAAAAAAADGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiSAAADGQxkA9gAAAAAAAAAAAAACAme0aSHjXA22KvlbkOZDmAAAAAAAAAAAFXunEeQAIU0A07nn1p3AAAAAAAAAIckFZYtHrOibF2emyP51p0OZ4849xJwPWQAAAAAAAAAAAAACq2z6jbpmxt8ayRo61zWezx41T/GhuxujT9oAAAAAAAAAKSbrzugyNe/Ra6IE/fUa5urfDsIWzxK8wN+JujTs8YsNoAAAAAAABDkhqi7PPvz61T4u2Mmx8avU2HM16NMvRNB7AAAAAAAAAAAAAAEGb5iY9e4vr3P1xI1lujQfO6dA9eN0+jmboU2NKkgAAAAAAAAArrCvsa/GiVjdJrdcuXXasyWNHqVrlwPGPdjU79XqVneAAAAAAAAQ5IQt8c9+PFjoxr8T4/nZuQvfrHh4ng9gAAAAAAAAAAAAADVGl5YxH3+vbW2NW0hStLZt1apMOZCnAAAAAAAAAAQ0vPjTIhTjVplZ0t0KbDkNW73qrp2/TnOPfsAAAAAAAAIkgDAMsGQYDOMg9gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDkgAAAAAewAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhyQAAAAAYz7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA1bQGI3sAAAZGAGNU0AAAAAAAAAAAAAAAADGQAAAABjIAAABhkAAAAAAAAAAAAAADz6AAAAAAMZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABDpdnRNG8ABDmQJ7znIAAAAAAAAAAeOYv5YBXWLx6iTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4Rc56HhfXY8Z0/PT/dT2XF9jx3VWHDdzz2afZu64AAAAAAAAAAOSt+V7jiux5ifH1Tm3n59R1CplZq+79AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4SptO69/JHU9fq5vq/mVpcUXUcp1F1w/ccVa0lzLtwAAAAAAAAAA5u452qtEayqO84rtuGjzXZcJPhu2oOjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzG3okWHvg0l7RytkLoudt6e26Hg7CR7ouzouoAAAAAAAAAACr5zb0/KX9BIRbTpOY4y+66BprFp0VJdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFJdgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHz76DznQ+lY5aT2biO19Q661oOm8ed2GfHsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcTZaobtavxzzuMchtStEOLYXfIe7GR7jdV6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOWm1OHZV0aH07nPdTvm6oMa26jgrady/V851FiAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAYz4zHlec+fbz6whyXnZ686d8aTFlgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIWAAAAMmAAAMmAZAwADIxl5zkYyBgAGRjLznIwADJgMjGTAMhgGTAZGMmAZDAyGAZMZAYyGAZBgyADBkwMgMZGMgGAepgP/xAAZAQEBAQEBAQAAAAAAAAAAAAAAAQIDBAX/2gAIAQIQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABqsAAFubANpkNZqbwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF3z1FktZ1nWdJWsXNTVxrNsbzWbKk0Z0kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtM9M3O8azvnvO8zWdY3nfNdJNTXPpi3O8azvGs7xvLIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVNzWU3zs6c953zus6xqsKbzrLPTnpN41jpm46YtYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABtcaMbMbXG+d1jphvEXcY2Y3JvGpcdOe1xowAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGtcwAAA1N8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAFsgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAAAAAAAAAAAAAAAAAAAAAAAFgAAAAAAAAAAAAAAAAAAAAAAAAAALvKRrOoudQmpvmstiGoudRrF3i5ahrM6ZQlsTWenNqLnWdSWWw1hqE1cWrjWVlTVxsYqaudZsTWdZ1CbRLNYAAAAAAAAAAAAAAAAAAAAAAAAAA3nTG8tEstTLWFtEjShGG0Z6Z3JhuLqc9KZ0xpZrNmsasGdMN1Gd89ahnTWamdY2TOt5zuWTbBZpJnU1c3NyAAAAAAAAAAAAAAAAAAAAAAAAABvG2NFJc6Imsx156LiapLLebZmbWRncmjGppFY1qVjUs1JTHRzu4hcbsQ1mkrG2DozVZm5neauWueppneNZgAAAAAAAAAAAAAAAAAAAAAAAAAAAABrLWQAAAAAABUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAauYAAAAAsAAAAFQAsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOmN41mbzuYauNM6Z3kFizV53py0yustYusaZ0zvMvTFmdMrrG8yrOnOs6kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdZLz6Mbyzd5NZ1ms7iufRjeOnJuLmaaYbxdyawuY6Ztzq85056rHRmbxtnXMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6Zt59HPplm6lrLfO6lS8+jn059eTplczpnTnd4bzLvm3zdMrNTDpzu2N40uN5msAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGs6mstZm8XcxtnUmsyjWUuplqamWrJrNuNXG5NZjeSbYXWTUXNluLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaZsKmoQAssqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANJLUjUlqCkqTWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFColjSXOpUBcgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO+wAAAABz4gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9DzgAAAAD0TgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHs8YAAAAAPZ4wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9fkAAAAAB7PGAAAAAAAAAAAAAAAAAAAAAAAAAAAAABbkBbIAAWAAAAAAAAAB6/IAa9fkyAGsgA9njAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3nUTWd89Z3E1nUlErXMAAAAAAAAAPX5AD6Xg9E68O3Hok6Y9nzvTx8wB7PGAAAAAAAAAAAAAAAAAAAAAAAAAAAAABu5VjbG0azZc7RLm3AAAAAAAAAAevyAH0fJ7t8Xi9vfjPn+/tw5enHzwD2eMAAAAAAAAAAAAAAAAAAAAAAAAAAAAADWdVLnWNZbZ1LnUmkztgAAAAAAAAAPX5AD1d+N6cOPo6THl+g4Z9PDygHs8YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAevyAAAAAA9njAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1+QAAAAAHs8YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAezyQAAAAAX1eQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7JAAAAAFvjgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALCwAAsAAAAVZCwAALFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC9eLWuermNRZpmyak1lrXPUilZqak6SaxHbnnTOpNSai5dOazWevOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG5U3hrE6Z3Mbmd42zvMk1bjfN0kqVNc3SLiaazZYrWWd5y6c3SSb5gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADrnUxvK5m5WOmZrHRm50xbrn05ujKy3LLpJk6TWE1lvNzq51nPTm6Y1XMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAazrO2NsLqM6mpnU3lcxvOplpnbG2UakGs1pjcmpNRcNWJrNZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAG0y1ka1Mwak1kAAA1JrOpAANVMunMADWVjWQ0Z3gDWWslQAAGs1AACoNZAAAAAAAAAAAAAAAAAAAAAAAAN43URndmZsubWdS5rGlGU3ZYvPc1MaUSzO0uNXO80suN5zvFusb56oTPSEzrUFc9NOernaJqVm53z1TFtJNXntLLc6zoxAAAAAAAAAAAAAAAAAAAAAAABuUZ6cm7iXaE1JrLWdc3XnoZbQmuepph1wow2zHSEsqW51z3laYdZjSZ6QmelzKsxpWNFMblsSaxbWHXnoJncVc6k1GYAAAAAAAAAAAAAAAAAAAAAAAFgusAUSosol1iiKJUUl1iiACks1miUlliwFTWVSxUUiyoCyyxSN4qWDWVEpLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgAAAAAAAAAAAAAADQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABn5wAAAAAAAAAAAAAACdvcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4eb6AAAAAAAAAAAAAAAE+X9SgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcPN9AAAAAAAAAAAAAAAB8r6lAAAAAAAAAAAAAAAAAAAAAAAADpQAAAAAAAAAPN4/oBrQAAAAAAAAAATAAAD5X1KAAAAAAAAAAAAAAAAAAAAAAAAPdwwAAAAAAAAABw8fvHo1xAAAAAAAAAADfbxAAAfK+pQAAAAAAAAAAAAAAAAAAAAAAADvjmAAAAAAAAAA4PTk9njAAAAAAAAAAA755AAA+V9SgAAAAAAAAAAAAAAAAAAAAAAAHp88AAAAAAAAAAcJ7OZ7PGAAAAAAAAAAB01xAAB8r6lAAAAAAAAAAAAAAAAAAAAAAAAPT54DeGstZ3i3I78sgAa1ribw1lrIAOE9nM9njBvNZAdOYAF7+cA1IA1kADpriAAD5X1KAAAAAAAAAAAAAAAAAAAAAAAAenzwO31Pi+v1/M9XT532vk+7Pk1w93Lt5+e+/OdeHTVeL6N83bwen3fI9np+d6dZ49/H6uPTPnHCezmezxg+nw1rx+rz47Z4X7fxO+uPp8nr4bY6+T19uXLv5PSvifS5+fvwzub5+vHm7SOnLgHTfAAAHyvqUAAAAAAAAAAAAAAAAAAAAAAAA9PngfU6fP9G8+D6PzPoN8t58H0fna93m9XDvx9OZjnjh9HfPz+f62/m9u+PB77rnnt5PV5/IOE9eD2eML9jPm1fB7+jl8/3ejz78Xt8Hs6Jzt8HfXonh91vLy5+xnz+L3XL1cceH398c/L7s/MDpriAAD5X1KAAAAAAAAAAAAAAAAAAAAAAAAenzwPf4vZ134+z5/t6ee+rwcvT3Y68+HXz+7hrPLv4vc8J7vJ6u2/LuZnXzerFvzxwns5ns8Yern3Jvh7OHPze7x+zUx04ejhvpy7fO6Xtjvytxw6zrrxez0fP759PDyezrynl+hw8gdNcQAAfK+pQAAAAAAAAAAAAAAAAAAAAAAAD0+eAABfb4QAAAAAAOE9nM9njAAAAAAAC+3wgAA6a4gAA+V9SgAAAAAAAAAAAAAAAAAAAAAAAHp88AAAAAAAAAAcJ7OZ7PGAAAAAAAAAAB01xAAB8r6lAAAAAAAAAAAAAAAAAAAAAAAAO+OYAAAAAAAAADg9OT2eMAAAAAAAAAADvjmAAD5X1KAAAAAAAAAAAAAAAAAAAAAAAAPdyyAAAAAAAAAB5/D7x368gAAAAAAAAAA108QAAHyvqUAAAAAAAAAAAAAAAAAAAAAAAANaAAAAAAAAAA83k+gF1QAAAAAAAAABnIAAD5X1KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABw830AAAAAAAAAAAAAAAHyvqUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADh5voAAAAAAAAAAAAAAAPlfUoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHDzfQAAAAAAAAAAAAAAAfK+pQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOHm+gAAAAAAAAAAAAAABPl/UoAAAAAAAAAAAAAAAAAAAAAAAApFQAAAAAAAABj52wAAAAAAAAAAAAAAJr6AAAAAAAAAAAAAAAAAAAAAAAAAUSiyywSwAAAAAABnmAAAAUACoAgAAAAAAXpQAAAAAAAAAAAAAAAAAAAAAAAAtkaSagshUAAAAAAAAAAACyhLLKJRLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFEVLLAAAAAAAAAAAAKllENSWVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGrmLAa1MNSNZ1kAAAAAAAAAAA3MlgA1kAsWWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANs7lzVwtYbRVyolwAAAAAAAAAB0zU1EuLrM3c2axqWWxnQZgAAAAAAAAAAAAAAAAAAAAAAAAAAAABvFtSxm3WJdWZ0MdJNZsyAAAAAAAAADeN41bJJu5zuxKZ1jZM7lZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtgWQqLDUWSlzrIAAAAAAAAAGmVLktyqazqCXWZViAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAAAAAAAAAAAFgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABRKllEssWUIAAAAAAAAAAFJZQlLAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC2RpIs0gSyiAAAAAAAAAAAstgSy2JRLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWAWFSiVKlgAAAAAAAAAAKlllJbIogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB15bwW6mHTEtmsWFlgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6QYt05tgsz0kGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABuajFtw01BZjpM7xvEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsAFlQLLAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH/8QAGwEBAQEAAwEBAAAAAAAAAAAAAAECAwQFBwb/2gAIAQMQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABJaAARZQMrQliygAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABJqBUWWWBKsqLLBLFBYWFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABlbmyyk1mypZrOs6MqllmoSyyyyyqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACXNlJpJrNmpLNRNJZLKudRc2azZrOomgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZKjUjWSzUms25tSGsms1LKudZLDQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASaAAACJoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlAAABFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACSlSwWCxNCFEWWWTTOpUsFzVioWXOkqWWVLAmksWKialSxZNSVYsJRZZYLJVhQAAAAAAAAAAAAAAAAAAAAAAAAAJZNQRSClQBcgGmVXNi1moaiWWNRLLLLBZY0kKmpBYBZrKrIsKiksLYiigAAAAAAAAAAAAAAAAAAAAAAAAAS5ahFCKS1jUFsCWLcqsilzSWxFJqQWWWVFuWpFC5FJUqNZtMrBUqWWVNQJSgAAAAAAAAAAAAAAAAAAAAAAAAAAAAEqUAAAAAAARQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEUAAAAAlAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzZrOkRpFixYCwE0xuKixYsWLFkstioubYM7ixQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGRrKyrJSWUQNZWazpmixGpKiypazUsW51I1lbmxZoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzY1lrNWSoqVBZrLWdZ0zRc2NSVKSppmpZazqRZZZZSgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEsubc2yyWxc25tis1ZLUFQTUmosXNqCxUmoE1BZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQixQABKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhYKiwABZQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAssqAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOpwaAAAAATn7QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOje4AAAAAOlruAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB5fqAAAAAA8v1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA830gAAAAAeZ6YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEUBFAAEoAAAAAAAAAeb6QBxdDv8oAY2ADzPTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzYWXO82WWWVKlibAAAAAAAAADzfSAPD9bp64O30+71m3Fyef6/S7XeAPM9MAAAAAAAAAAAAAAAAAAAAAAAAAAAAADM1DWWsrJqWXNCxoAAAAAAAAAPN9IA8X0PK4u1fR8rr8+va8fg7HZ6HL7QB5npgAAAAAAAAAAAAAAAAAAAAAAAAAAAAASyLLNSsrLLKi3LQAAAAAAAAAeb6QB53V7XHxdzs9HhvN6Hi3tb6Pa9IA8z0wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA830gAAAAAeZ6YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeb6QAAAAAPM9MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPK9LYAAAAAx5/pgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABjy+SgAAAAJj1NAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlAAAlAAAAEUlAAASgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGNpNSWpUSiypUmpQRRKzU0Y1YsWLLFZ0llzaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARFlS3NixZrKxViWaZVKJpmxpEoJYqWs6ZVNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADI1mpbmprNTWVE1E1nTKhKrNUymiVKJZbnTNRoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASxLctGassWXNS1my1Fi5VUUSwXNualTSSkpQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASWpRI0EWUAAAiyygAIKzoACVKlCFlAlSkUAAEsUAAiiUAAAAAAAAAAAAAAAAAAAAAAAAlyUXJbkWFhZbEFXIqaiNRCpblVksohZbLEs1EsW5WWyKRqI1CKQUmojUQqNQIssNAAAAAAAAAAAAAAAAAAAAAAABmpZrOmWkgWLKlmmahUCzURpjSDTK1mxZYqWazSGmVi3ItyWDURqEW5qKTURpmpUtgixZZoAAAAAAAAAAAAAAAAAAAAAAAEok0BKllJRKllSpUsoDOpUoASpZZQlSgBFlRZQAlAJZRLKzqVKJUoSgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABecAAAAAAAAAARQBFAiicAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABzfttAAAAAAAAAAAAAAAXzPyYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1ff8AxYAAAAAAAAAAAAAAG/oPz3IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHre3+OAAAAAAAAAAAAAAAv0T55kAAAAAAAAAAAAAAAAAAAAAAAAOCAAAAAAAAAAez+g/HBx8YAAAAAAAAAAa5gAAL9E+eZAAAAAAAAAAAAAAAAAAAAAAAAJ5Pc5AAAAAAAAAAPW/Rfjh0uPtAAAAAAAAAABx9X1QAAX6J88yAAAAAAAAAAAAAAAAAAAAAAAAdLm5wAAAAAAAAAHrd/8rynl+oAAAAAAAAAAB099kAAL9E+eZAAAAAAAAAAAAAAAAAAAAAAAAOh3dAAAAAAAAAAPW9L8f2DzfSAAAAAAAAAAA6+O2AAF+ifPMgAAAAAAAAAAAAAAAAAAAAAAAHQ7ugcPM4uVxcvDzYnITq8/IAJXFnj7VnFzOLlcfIAD1vS/H9g830gcW8zlAcPMABOn3QDj1oBx8gAHXx2wAAv0T55kAAAAAAAAAAAAAAAAAAAAAAAA6Hd0HV8L9N53ne50OL2/zH6DyuT0Mdvyefr9zm4epz66/b4Myer4ju9b1+j5P6TzPN93o532up6PQ7XByd0et6X4/sHm+kDwe3nj9Lo9vk6++3j8x+n6vH2uj6HQ7fDd8Po+d1ufsdXv9JPTvh83c6va3x3i5/O5e91dVw9jth18dsAAL9E+eZAAAAAAAAAAAAAAAAAAAAAAAAOh3dB4HB7HV4tet4/u+Jrg7nBv2PD9vHl9zodvqdno7vLzcnd8TG+93fz3F7HD0+X1fLznfJ1vQ6Pb9Met6X4/sHm+kGPznJ3ON6vlcE7Ps+V0e9x+l5fr+dwLzTPr9XHS16nlsdjv7/N673oeW3Oh2Of0vK6nNz93yt+8HXx2wAAv0T55kAAAAAAAAAAAAAAAAAAAAAAAA6Hd0Hj+p5vW4/S619ny+HtvO9fs9Ho75Or2e31O/wCR3McnL1vV8m+pZ5Po9DrcXf4eW663f8/lzn2x63pfj+web6QdDl6sb6/d87tc3e8n0vMxrl4O30u5wcfP1Pd4M9bm6fZzjsdngcHH6nndT1urvpdv0PN63Pvv+N2vTDr47YAAX6J88yAAAAAAAAAAAAAAAAAAAAAAAAdDu6ACUJ5PrkoEoAASksoHrel+P7B5vpABKAEsoAlAGfL9YAAHXx2wAAv0T55kAAAAAAAAAAAAAAAAAAAAAAAA6Hd0AAAAAAAAAA9b0vx/YPN9IAAAAAAAAAADr47YAAX6J88yAAAAAAAAAAAAAAAAAAAAAAAAdPk7AAAAAAAAAAD1u/8AleU8v1AAAAAAAAAAAOly9gAAL9E+eZAAAAAAAAAAAAAAAAAAAAAAAAJ5XPyAAAAAAAAAAev+l/HDp9fsgAAAAAAAAABxcXqgAAv0T55kAAAAAAAAAAAAAAAAAAAAAAAAOLIAAAAAAAAAHte9+ODPEAAAAAAAAAAGuUAAC/RPnmQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPW9v8cAAAAAAAAAAAAAABfonzzIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHre3+OAAAAAAAAAAAAAAAv0T55kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1vb/ABwAAAAAAAAAAAAAAF+ifPMgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAer7v40AAAAAAAAAAAAAADX0L57kAAAAAAAAAAAAAAAAAAAAAAAAIoAAAAAAAAAOz+36sAAAAsLFiiWFgAAAAAABycX4sAAAAAAAAAAAAAAAAAAAAAAAAIVFgCyygAAAAAAOftgAAACWazQsAAAAAAAAE6GQAAAAAAAAAAAAAAAAAAAAAAAAhULALBQAAAAAAAAAAAlgFlgsFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEqUlllAAAAAAAAAAAAlSygikUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABlalBI0zallAAAAAAAAAAAMtEoAlAJUsoAAAAAAAAAAAAAAAAAAAAAAAAAAAAADKxNQqGmQSgs0AAAAAAAAABigGpLcyrLCyVYFoAAAAAAAAAAAAAAAAAAAAAAAAAAAABmkBqJbJLYLm2WWgAAAAAAAAAzZYFuVuRYWayLBVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIVBRKEBYLKAAAAAAAAABCoUiossCyWwKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQqLLBZZUsCgAAAAAAAAABLLLAABQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIVCiCypYKAAAAAAAAAACWAWWCwUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABFBAWWLAsoAAAAAAAAAAIsAsKhQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABnUpEaZ0iygAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzUaiNMlQsKmgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmw1CoiotzUstAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf/xAA7EAABBAEDAwEFBwQCAgMBAAMDAAECBAUGEhQRExUwECAyNDUWITEzNmCAB0BQcCIjJEElQqA3F0eQ/9oACAEBAAEIAP8A8csZQn1eP9s0oSd2b+A8pRizylnNfm7sw4p9WaiWE/qFeqFaGQDZDZAM4fW1gG8+EM1LQocsLLvJZa+LD442QLiNd0cncFSB72TyVXEUp2bGR1/mrU2arHVuoRuzvpfXLXTtVv8AuFKwxzmhf1Lx5jCGzP1ZnWe1BXwIgTPp3VVPOcpgZsdnxN/iaZrZqGdA8P4D6smQWAyEh4cdU2Tpjtwo4mAGGPO6AMe/MtDTeOt4bERo287qSjgxM5y/1OyTz6iwev6tuUK9pZ3UtDARZjF/qfknn1Hg9e0bpI17l0/EpWbEaX9RrluzWrOs3rizi8lYpwwmSlkcLVvyb+pdyZmE+Z1+9S1OvQs6iPX0yDOPprWhsxk4Up621KZiX8IsRkp4i+K7DFapuZrDZK81D+pJy2gAMs7rk2EyR6IdPZu1mca1s6/qf1h4sUdCVcdZvne3cweLu15AL/8A44zIjPMQmlEUGJnNc0cdOdes39TclvWndWY/NQlCGsdQmwD1wQCRwlGVtK6ys5289Oep9RmzU4gJgNRGwE7Djr5mZdNPmnxGvbVzJVqy1NmC4PHRtwwWvWuczm2v6m3Jlfsad1vWzUmrmeTRZ3fL6/FWM4aOJ/qV3jMHIX/6kXKd+1XbF2mtY2tblmdd1KJZgqQ/qbk2+PTmsKGZZxN/vu3XDbrGrmzOh8tjiPOtWzWbxf8AwFh/6k2oTYeTFcrnrQODJ3i5fJmsExmM0PVpME+pKVCjf/8AjdKZaVzTgTnvXLGWyBbBMdonBVqA4WczonJgyJmxwIXYaQJC9ifquO9mr/1DkFpL9OUVJ3abu2H0JjGqQnf1fUDR0pKsDQP6kAteYfHtirt9tK1a13N1Q2KWKx2OEQNPPUXxmYtgbB3WuYavdnZIbLZUs1jgRoUK1Qa1PpwGdouNX8BmsQTdOnqvP0XZm03roF8zVrmrr08XhbBx4KnVv5IcLtrHaDPUcEa5p4nKwIDLY3GZihC1bqQiS1XhKjg8XQkxav8AUPEY7GtTnU0Li6GSNea7mKteppnKBBpv67jV/Ur6IJacxPmMmOo+Y0Lg3xx+HjDTrZKmSGtL86WDK48BifM5UFR7WiMGepMQrA5hOURAd37Hw2VXA1sL2mDoTLU3rgxWg3o3AXG/33n802CpNbfAa4Blrj02t4vHXYNG3q7FU8RlXDU0G5rWmr4ngNpHgMj/ANMXaDTaf9NYCfZPF4B8Rgclj4UyMC5WLIU4zFCcbmrMFirLgtmuVslpy3aq4ycRZKgScXZ4s7apMM+eyEx6R/TdBN+cyF+VBa7/AE6daA/UgFrb9M31of8AUlD2f1OxvSdPJNiM89bReYqLQWM5+cgV/ZntXxwN2FWemdRAz9WZIZPAYTKhJ37EGrWzQHq1z29EUDTwGIhmr7U5T/pe8Pxb+mo3/CYOLh3AqXzlVQ+CC/qj+Vjl/S75nJrUv0TJrTf13Gr+pX0QS/pv9fdWvl7CrfPAX9Qh9cBVdf0+swBqETJ3aMXlLJnjZyN08MI7Nh6PXL6IxV87lpXtDZmpCZYYDUN/E3ARaDtKMZN/vrMYsGWxx6RclhMphLD91tW59m2qpjsrmrXUeCxIsFixUoaw0nbq2y36lDWeaxoGrR6ZrU17esDj/DYsFJtWaVtU7ZrtOlqzOY+s1YNDF5XUV53ahjR0sUGg2dwVvCXCjnSzGpLUIUKmYx08VdlUJpL9N0U35zIX5cFrv9OHWgP1RVWocZ5DC3KkISvYe7Ca0NnTZWnanb1TjoZHD2asesmZ4r+ndGFLEPan7NbadfN1QnBGWWwdl3a1qfOWxOE2A0tey1gciXMZWt40lAuSw2W07daaL/UHURQuJYDT1/M3IGK4YdntLK4q5hrpAH0RqfJ37xalzX2HNfxwZ1sdlsjhDknUfIkymhD2zab+u41f1C+hQX9OvrhVbbpUsM1b54CyOPDk8ROoXJaezGHsuz1j6vzrNTWRqtSv26rU6ccjpQFSdyhl9OX2nKz/AFCz9mu4VprTd3J3AmmzNFmZv99kGObbZyw2IbrKQxCE20fsLiMYaW4gghDHoJOzP9zlxWLm7kIMQhR6DRABLHaQNSrX/KlXrzfrOMYwi0Y8Sr7CQhNts2CCH/JkajTsfmiACvHoLNa6q0DW6Y6FKxl8iKuKpWHRqhrw9pqtaw3QsMPixvugzNFujKY4Ti8ZthsU0t7RjGLM0UesCw3QoadSr94kbG44jvMrCHs2JgVY/fEg4EbpNxBH8CerWf8ADK2bdDG2LVap/Ua3yoNcLrzAQC5IO1jMZKbwoA41KsFyDGSPSbYbFM+5oxjFukf4J2v6dYm0cppYfAY3DDfjf2+T0FiMkTupv6ZUYO+/DaZxWDbui/8A18vXl/6evNceScElx5rjzT15pwSXYfonrzT15pwSXHn0XGmnBJOCScEmZceacEmZOCScEot1dwSi3V3BKLdX48lxprjzTgknBJmTgkuPNceacEk9eaevL/09ea48k4JMy481x5pwSTgknBJmT15rjzTgkuNNceacEk4JMy7Eui4804JMy48k4JRbq/HknBKLdX48k4JJwSZk4JJwSZk4JRbq7glFur8aacEl2JdE4H/9PXmuPJOCTMuxLouPNOCScEmZOCTMnrzXHmnBJmXGmuxJOCScEmZdiXROCTMnBJmTgkzJwSi3V+PJOCUW6vx5JwSTgkzJwSTgkzJwSi3V3BKLdX4004JLsS6JwP8A+nrzXHknBJmXYl0XHmnBJOCTMuPNPXmuPNOCTMnBJmXYl0TgknBJmXHknBJmTgkzJwSZk4JMy48k4JRbq/HkuNNOCTMnBJOCTMuPJcea4804JLjyTgkzJwSZlx5JwSZk9ea4804JJwSZlx5pwSXHmnBJmTgkzLsS6JwSZk4JMy48k4JMycEmZOCTMnBJmXHkpBlBceS4004JMycEot1dwSZlx5LjTXHknBJceScEmZOCTMuPJOCTMnry/wDXHmnBJOCTMuPJOCS4804JMycEot1fsSTgkzJwSZk4JMycEmZOCTMnBJmTgkzLjTTgkzLjyTglFuruCTMnBKLdXcEmZcaa48lx5JwSXYl0TgkzJwSZlx5JwSZk9eX/AK4804JJwSZlx5JwSXHmnBJmTglFur9iScEmZPXmuPNOCTMnBJmTgkzJwSZlxppwSZlx5LjTTgkzJwSi3V3BJmTgkuPJceScEk4JRZ3d6809ea48k4JMycEmZceacEk4JMy7CevNcaa4004JRbq/YknBJmT15rjzTgkzJwSZk4JMyevJceScElx5LjTUguzJwSi3V3BJmTgkuPJOCUWd3cEk4JRZ3fjyT15rjyTgkzJwSZk4JRbq7gknBJmXHknrzTgkuPNceS7EuievNPXmnBKLdXcEmZceacEmZPXkuPJOCTMuPJcaa4004JRbq7gkzJwSXHmuPJOCScEos7u9eX/p681x5JwSZk9eUW6u4JRbq7gknBJmXHknBL/9nEpwh038uuzKBgdWZv8ADSlGDdZfj68pRizvJnZ2Z2/v5SjBusvQaUZO7R9spxg3WUpxg3WXpNKDyeLJ5wjJovIgxfHEwX+6PvNOMndoqUowbrL162Ux0yuGAjVy79n+obWIqZWEGt5jCY+ndxggh03iaZxnBbzuNoFYZ6OVo5GP/jIhIChKc/tRiuu1DKMoozHYz+MpFcRauUp5MTPTNkqday1coNTYchIiazZDUDM5rGpMcFh7qt6tersWtdydLHRZ7NPO42/PtBnOA4SnOWqcU89rmv1KlKV18BqQbEMKz7Lt+tj4wnYQMlWtksBrWc/jKJXGWlkqWRG71efWHkA0iWbAqoCGLXsCPXGcUpNGLyd9R4mFYZnpZWlkYu9a1crUhd09XUuHPNhN1bp1U9S4gBXhKldrWR94V7LUcd05GbytLI4ay1fHfL11fugx9d5k07qKHUobVy9XpC7titqbEFlEftPqHFVCuIlHI0cgHuhvZahjujWKWax+Qftg1NlGogcAcVlamRA0Ru/RndPqQMM5GbahOOzptzCHn8bRq1Qmo3Kd4LFDdylLHMz2aWcx1+XbCrdwdMEzmfUOLgYQVcydLHMz2amext2bCHKUYReUp6pxTO0HDarmAxhQ1MCGccrhsCMAZoWbIqgJnMXUeJrbN+Ov0cgPeC5k6ONjus1M/jbc2AMhRgHIpW1PiXntTEH2+4p6mxUJ7V5CmKlzWrWQ2a8Dip5OneKcQJZOpWsgrTuZKljotK1Uz+MuT7A7t2vQB37D56lO61JrmXoY6UWs5vK1MjiStXrTbhg9hNSYkBXg9G9SugYoSZugJ7LEp57G3idkJjiFCUyfajFlm0Fp2UefnX9l7J0sfBnPV1DjLUmBDWn0sa1V8hTTkgMLTm+qMTv2qVqu1blQp3q9uoxgV8pSs2T1RmydWmYAi28hSx8WnbqZ/GXSMIc5xhF5PPVWLlPY+JnGWoctKKy8dmpMOtUCgY2LFMukcM8X6aesHga/j7Cu5WjjtvJqZ3HXiMERCDDCRCC1Nh3JsWnnbyGddXcxQxzxaxncpTyOIJx7NkFCkOyd89jWsQrK9laOOZns0s3jr8u2GwcNYMimDqfDPNoKZBwG5JfafENPahlGUcJjPqLFUyuMtW/Vvg31bmUpY3a9qnm8den2grUuSDVpFrLTk8VOAx18NDFQfIvTnqHFiDMkg5KlwmuoWqMM89qy14VCk5Z6dzQ7QK9Q/wDp3UbM2RwStTkGqUy0xVG9Od0mdFDHZXG3a61Q85eOpM1Gq1fjM9XweEuOHTdMQscI73RRx2fonr5GsKzqioIupK1eeKsO1x3No3c+BqAFjK3TAxjXz2TrwDfq+avWbebyNW/CBKuoTzMDHV0KhUjW40aNAWPrzrj0qw2Dl3f2Zuo17GmC1TKxfTLXJ4sMsdpo9paapihQHZe/AeM1DjbAMh+rqCz30e8sJ9KpKz+VNaRqhag9hyihW1WDs5m6GefjC1k8njr1MgVcvHhpITyo5XF06www02VvM24hwooXcnkLpdXVASqNZjj/AJaupMzxdn0szdi6rAo3tTMA2XpAt48o303ZlYw8HISO4c4tjbkcIxa1/ENhpue3j8fkqjXr1u1lL4LRap6mqej4NptReMawHZEjH7VC66pZmwhViKgAY6vFsAOIszl6sAZCq2XvWbeXyFa5ERKopuQA5vqWLNgLbrCVAgx9Z4YsLZLN37BtT0hPjWsxzFwtnE42Kr06wKsK0MDHi5m/QjCEPtedvZqL6PdWAphBjQyXahR1QCIMjPDVb0LNvN5IN2FaYdRVj3cSKAB5jEFrtUvXMf3cEUFKjl6VSpCneo1KD44oquKuPTxGQFLE13xV7Fzel0t6jvWVQHDI53IWDamoiljXsNnyOfTFQ70qlepXCMWBFC5eyF8urKYnqwtNSfpTru9iI3rlY1fK4CgJgU9ME3ZjJM2MqiPqLKkJqiAhyo2x6mk5fF01w6zV+KtLC7FnKh9mUnh69sNi5nsmG8CDh1O+/Bgdaq+QprUk5zHQqNClUHW4zYGDAu5THrC2PHBytWeOFOifFX59Gt6mRr1bz9strNZCnfrdA5yyYuJowQKNYFWFeOABGrm8qGKzH6nwy1UTtFxk2fP5CTMwcJjbFTk2riulwlG/3j5jIjtloFDqXeZ8bSUaNXjPWWkgsC7khLAijbvZC8bV9QL1oWo6k+gTWKqArUgMOrkarZS9ZtZjIVrnZNU1NXPkMfTJX8vhbYWqXMzR7+GatSq5jHQrRp3TsCjp45KeGyeOo1BM+OsjfPdypkYGpZmN8gbWAytoM4rVf0gyxrRbHUemm/z8ytM1AzLkbEtTUjHohavHM4S3VencOAA8OWAdHs3hgP8A6e1EN3yGDdjQYo5jejbJgHJSubjZ/JVSxWpaJLYKx6zarFEGx6YLVzEGFkaGRJhBvQvgY+cyoLj24y+11SazzO+IuMxoy+xXRYlnbGU2fGQdtT5V0Tv4DJ2bCfPEtlELG6hozuVK71x6qFADNPFTvzquS7i7TYi9bqWb2UhjiVIv7LNUzZQmIiWpA1Monx+RnhIPRvi72cy1e4tQDNWyVPJV8plC5XHHFVwzO2LpM5/yiLSLSHh3Z78JPq3HyWZq2cfkg5QJdSiKJh0MlQLk8M9YlXUEK4YAv4m7duOYhN5tPZO0SWdvlytN2qY6LccLO6xFrxVo1K1mQWKGWhk693PterPVoYqq9CgKuib9ku2DUcRRIDKYCuWV+7eaEy6cvWWLHNmvWABx+oa07WKIIOGyor4Igksz3MdnA32zx4XdOTKHHt/4FGD45umoc26m5tP5KyZ5Z0l0wRY1lqNnliLjNjGdqFVnL3cBmD2Vkb886ONCjlcO58QIdYWqhDAw7GCqWWNbyNrKTli8+96QbELVYRoai+j3Vi8x42mAF7HQPk8s+SLYK2Mz1i7ZzeRnk64eNkbNypTGaldzVK/TmBqQcjQ080BPqGqau47Omq1inRLE2cpHhmp1w6krShjq5w6aBMON3lM1jA5U9psjkZ5sbUaGpKjgwAQjF+VBQIfTWTtOTOXy5Sr/AONT+VAstXLax1oIsfmqlKoKrLTnIlnMiU+IjJs3mXfVY5SpUemoKM7Faqas2qoTCw46VgYNjMchXHbHZyd2zm8lPJU2jV1HCUsHXZtTwlKjTaOoKM7dOtOuPVQYibfgKZw8q7a1JWPDKda2cpwbCtEOlBTjWsXLFwdjB5Yt5i6hc7DDjM9SJfxwoDBqZmrwCbTcbLZvIktLIQd9R4V1qWDvZwzx9rkjiczcPbzN4uT4bh1Ljy2K9Y9RtTMUMYNpoRx2cmxxkPprJWu5nbpspV/8XUkZPp3pGp91Oqyk59O5KySfnCXTBDjcwe7SAA9bI5enk6k64oQydDBAYJc/Ss1nGXEYsz4ElG1Ryz4kMaORxmRt37U5Nay9vG3HHZtSHmclSnQWo4O+GOzY76dRitNf8J5zdpb/AIBym7MHvUwCPWv5inkak64qFOYcGOmbTl5qkIYuw+VIHLipv/p52Z26P06e3a3Xr7HjGX3P77M0W6Mtrdevs6MsjjynyuPM6vys16kiAxVK3K6fJ3U8Wl+PoNGMfwTxjLp1TszpmZm6N7OnsaMY/h7HjF+nVOzOmZmboy6N77M0fub3Gi0fwW2PXr7maAWxjLQQ44UxUK4yJ2Z26OzMzdGW2LO7stseu73mZot0b2OzO3R2Zm+5vZtj13e70Zn6+12Z26OzMzdG9u2PXr7zM0fwW1uvX0HZnbo/4ezo3Xr7HZnbo7Mze4zMzdGTRi34LbHru9jxjL8U7M/3OzMzdG95oxbq7Lo3/wCWp5wh9z8iK5EUx4v+Hfiu/Fd+K5EV34rvxXIiu/Fd+K78VyIrvxXfiu+y5EV34rkRXfiuRFd+KY8Xfo3finMzLkRXfZd+P4LkRXIiu/FMeH4N34rkRTHi/wCHfiu/FciK5EV34rvxXIiu/FciK78VyIrvxXIiu+y5EV34rkRXfiu+y78Ux4u/Ru/Fd+K5EV32Xfj+C78VyIrvxTHh+Dd+K5EUx4v+Hfiu/Fd+K5EV34rvxXfiu/FciK78V32XfiuRFd9l3V34rkRXfiu/Fd+KY8Xfo3fiu/FciK77Lvx/Bd+K5EV34pjw/Bu/FciKY8X/AA78V34rvxXIiu/Fd+K78V34rkRXfiuRFd+K5EV32XIiu/FciK78V34rkRTHi79G78V34rvxXfZd+P4LvxXIiu/FMeH4N34rkRTHi/4d+K78VyIrkRXfiu/Fd+K78VyIrvxXIiu/FciK77LkRXfiuRFd+K78VyIpjxd+jd+K78V34rvsu/H8F34pzMy78Ux4fg3fiuRFMeL/AId+K78VyIrkRXfiu/FciK78VyIrvxXIiuRFciK77LvxXIiuRFd+K78V34pjN+Dd+K78V34rvsu/H8F34rkRXfimPD8G78VyIpjxf8O/Fd+K5EVyIrvxXIiu70XIiuRFd+K78VyIrkRXfZd+K78VyIrvxXIiu/FMeLv0bvxXfiu/Fd9l34/gu/FciK78Ux4fg3IiuRFMeL/h34prEXfo3Iiu/Fd+K5EV32XfiuRFd5uvRd+K78V34rvsu/FciK5EV34rvxXIiu+3Xou/FciK5EVyIrvx/Bd+K5EV34pjw/BuRFciK77LvxTWIu/RuRFciK78VyIrvsu/FciK7zdei78V34rvxXfZd+K7q5EV34rvxXfiu+3Xou/Fd+K78VyIrvx/Bd+H/tpNL729Ij7WZmhBo/wnKLtf84M7SZn9L8T/AMKa/wAD+k3zMv4U1/gl6TfMS/hTX+B/Sb5iX8Ka/wAD+k3zMvdPqbF0rBAHHq7CuqVytah3A+9OcIR3TjKMotKP9wPK1Xvzot7IFFKTwj/cDKIjPsu2xUwTOSheFbrDMH0aeVqnvWKkPeYot/b/ANpV/gl6TfMy92qMZdX5GBJY3Hli8XxjPi9Qmoit3K9ALlO+r6W5UMjTyYmKE9kFUUiFlrWlF2aGMylLJQeYdV5UbAJQbTueB26dL2WbMawCnm+q6EKQjtS1bjyk7Umfqrmo8dQK4pV9V0LE4AdWtT0KxXFGhqKhkJ9gZjirjkUzawoQ+Gnla+VB3a1PM17F6xTjkLosbVeybGZKrkKTWAWMxXp3K9Sdu5XoBkU7ayx7Oq2Sr5IDFrX8tXxxADK2qKPMhWFeyFbHC7tgWsMc8maVUsC6tNMav3IUKs7E6GcDVzF+69OyK1TGeFvU+PqlcSpakx16fYb2XsnUxsGnYhrGgztuq2Q2hRMO9kauNF3bMNZY7r0cBQWQMQV/M08W8XODWGNnNoEhOE4NOF/PUMZNoFqaux5JMKbOzsztc1HQoG7T0dS0LxGrsUsAQkQj6vx8HZoUMrTyY91dZS/DFgGUmn84DFvc36geE9PGm1DN1MZh6ECUdT4+8RgMrmpsfRL2lR1Ljr0+y3svZCrjhd2zDWWOaXR6doFoDGDgP1NlkXL161+vSmq+YrW7VqiK9kqmLF3LMNY4/q24l8EaErQ45oDZ2V9YvJivVWNC7qLH48rilT1Rjrc2EjHiARCO+sMe1aJ1i8gPI052G/2XX+CXpN+e/ulyLY7U1+wiazkFurYGhcJcLlLmWi+Uz9WhNhV4i7Uaw2wuqOOHU2+1k6GLYFYFYMQitCbC6kqErau+kFWD+lUfZl/plxaRpC4z256hxoL2OKRaYtcrEQkQVXA4c5Tz1NlsTfqwHXrPOxp8S09kqOMgWtcJUw2SsCvB1dXKXFCcWOzuEnSFXapjaNIpT0rzvjtVU7UNSs57ONxy03/4drI45Un8hqezZWWZ8pn6uOlEAYi7TVYNh9TxALV8O7Yx0ECpVphiEFyLZjVPYJKnXcDiJhq0KuqDA9uG/UmYWorE6WJKWGnMcGrQEVaroBem14WKsPbx9Y8lkcXiCWh3LtrO6fatMRdFF/6rSAKGY1HZewWjXOFwlxWLhiRFFHAghlb93I2LtGtfrTAepX8PjZjWBuYyBLF2/mchgcjSLBaZLK3huj4W9VxFu4K8Wrh8w4TR1OAxsUWIsHnMLCmIEq+NxtY07tL2aL+O+tR/SLa0vSCHGhOtZAGN610WcuNVw5TQ0/dwWPpxmXUlzDXAMerjTytUKp5LtQy+pTDPOtWIKQSYvERw7HjHCfqLMLM/qzGezB/qLLrUL8fL0bdgeSwGVA4XxtAFILBDDo2tZrJnerQtHbT1vDVASsWtQ3cFfpSkKhZe3piZpaQo13rEtTZmb8P9l1/gf0m+Yl7tH9ZZFZmgLI0JAWl78jAnRPk5Ni9S1rs2nB472EVslqqBw6m3UszRyDhMIwolFfLHK6kpBBqZnfD2FgSRJh6XRZb6ZcWj7g51J1Xz94VPHG3YBjUMC7tpzG08mM9u5qeGOp0OwHGHiDAhJNvD54DGWSqjweVoyx1/KVccIbnPhMRej3VgN1bL3KIdW13PQ5DYY0MvnZXXzhPEZ2NxaTA8KBrM8rNsZqetdJGcJRabQLHK6ngUOqfnMV7DEbD6scxZlhGDkliLMbmqDmj7MXOMNV5aK1NUezh5xhpy6C3jYrVdwYqD1WxNeVXHVQyVwQ8nqbiWi0cHjgTO2iX3PddUixxWpLYzkKII5FJisxWygDyFpc0Mdft449y2GkCZzAsizGPJMenauNKx6lu1S05SBI1mjOg1Jj0hExGfFOSz+OrYQ9Wxj7WSq48Aj27GJw+RgxmwLvj9QkpB9mjJM1i9BakbphTrTv0aitZ/IAWarcrBThDTtPD5CmFpZEGmsdGEjV+wMAogQyQxeqLDmmQQxuSeLzNbJMZw4b9RZhZ6bVM9jLMyWqoQ96emTtYzeUOz5HHc1sdYyun8RxymjpGyUuKKxnnEOtneeVBK1jrIY6Zq4i7VYR7tbTVATms7KjYM0qujXZsQb/Ztf4Jek3zEvdHhQV8me/FS0/XHkPIwyOKrZEPZK+lG+CNDHVcWLtVrmPDcC4TPpSMX2ixuJqYyEmCYITgkItbTQqF2JhLL/TLiw2Fr5PDBm49JhGWJLTRZotFrWlq8iOeo2kqLVpwIGrGrQHVRdLhYsyVaemqtA7HJfxde+BwF+yu3/jDG4mrixvENmmO1WKCeJxAMQMkB5nC18oAQy0wDqAGGORxla+DtG+yjfBGjjauLF2q2SxNfJSrFmrmIrZYGyxDSsWkzEFg6lS9zA+zI6cp5CffenX4VQdVW9LVZzc9WjpurVM1gvsy2EpZXoVhaUgEkCWsViA4pjdvJ4qnlINE32UaTsxKdIFIMRjy2Eo5XoVD0oNyM9gYxhHEY7+naV9++m0kHdutQGMIoiha0tXkXv1KemaoDMc92gC4GQDfZXZ/xDjMPUxcZOL2X9LUrJXsCPjY2sbOsWjUhQqBqwy2KBlq0BkjBowaCu6WqGn361XS9QJWNY9mSxlTJi2G+ybP/AMZ0qIKQYiDWxAal+3bhksZVyYe0aGjqgJM5amGBRvWbQsthqWWZpv8AZVpv0PUpgoggOGXwlHK9CvjKL46rx1kdMVLJOSIWkgDIxbRgQLWJXbFUIYqtKuL/AGZX+CXpN8zL+xuAa5SOJ8TR8ZTaq38Fq3wP6TfMS/hTX+CXpN8xL+FNf4Jek3zEv4U1/gl6TfMy/hTX+CXpSYnJk0Oh10sLpYXSwuh10sLpYXQ66WF0sLpYTNYXSwulhdLC6WF0sLpYXSwulhdLC6WF0sLpYXSwulhdLC6WF0OulhdLC6WF0sLoddLC6WF0sLoddLC6WF0OulhdLC6WEzWF0sLpYXSwulhdLC6WF0sLpYXSwulhdLCdjszrpYXSwulhdLC6HXSwulhdLC6WF0OulhdLC6WF0OulhdLC6HXSwulhdLCZrC6WF0sLpYXQ66WF0sLpYXSwulhdLC6WE7HZnXSwulhdLC6WF0OulhdLC6WF0sLoddLC6WF0sLoddLC6WF0OulhdLC6WEzWF0sLpYXSwuh10sLpYXSwulhdLC6WF0sJ2OzOulhdLC6WF0sLoddLC6WF0sLpYXQ66WF0sLpYXQ66WF0sLoddLC6WF0sJmsLpYXSwulhdDrpYXSwulhdLC6WF0sLpYXSwulhdLC6WF0sLpYXSwulhdLC6WF0OulhdLC6WF0OulhdLC6WF0sLpYXSwulhdLC6WF0sLoddLC6WF0sLpYXSwulhdLC6WF0sLpYXSwulhdLC6WF0sLpYXSwuh10sLpYXSwuh10sLpYXSwulhdLC6WF0sLpYXSwulhdDrpYXSwulhdLC6WF0sLpYXSwulhdLC6WF0sLpYT99mXSwulhdLC6WF0sLpYXSwulhdLC6WF0sLpYXSwulhdLC6HXSwulhdLC6WF0sLpYXSwulhdLC6WF0sLpYXSwulhdLC6WE/fZl0OulhdLCfvt9y6WF0sLpYXSwulhdLC6WF0OulhdLC6WF0OulhdLC6WF0sLpYXSwmawulhdLC6WF0sLpYXSwulhdLC6WE/fZl0OulhVvgf0m+Zl/CkHwP6TfMy/hTX+CXpN8zL+FNf4Jek3zEv4U1/gl6TfMS91igb8YyjJusfQcgoP0lCcJt/x/sGlF3eLfwHA/QcvSb5mXu4PA0c3k8+9zNae+ztN8tiaV+J8ZXuTfWmAAV4PVsVrIInBfz2IwrtG3itSYfJNsq6vzsMZQkIOns5RzNauCCuYurl9aWatnJ6Hr0qpbeM0jk55fEitFPrDCUiuKeOyVDJAiWnkM7isU8Y3MTn8Rk2nGrkcpQxQ2JcxmqsJfIwAyyVEGRDjzX8nSwtdrV0FkNiuOwPF5SllBTnTLrLT1OfZnRv1MiBj1Z6hxdI1oRcTmcdlKxLVb7a6chNhLCkYuus3Mf8B6/wAD+k3zMvdweTyuPymf4F3KZrVVtsFPWLzo4LF46tXw2Lo0YUm0t1xWdz2HhpCsDK2cll7msqAqQqmXqayYZdLnKsHEIsPjZQV/JviNcWTtcz+WyoCVMbhcPDC4BqJqmW0dhIPVqaLswnqLMMC1PwOqr13JYyelstk2yFLUwjUs/QylgVzSGobNZ21mJ6fh8xDVAh5rLYXDjoZidXQ1xiWmNgtBCgPB4fG47EgCsXBsFra1jghx1a/r7JMfWj8LDBBWo4bGUscKqtMVB43VefqR/gPX+CXpN8zL3dEfUtTLWVA1SdHP0s9SfUeBr26NfXuLHV6XNK07hLWSzV+lcnonJX61vKX31iepjsVquga1pyxUBpXO1MpVr472VW6f1DtezPUy2MHka4MFqjB4jGBpl0uazZ1RmT2T6olicmermg9jN6po3cNmM/bwWTHyMzZoamuUB4LN0PI4K1VWhWs3D28jZy9Cy2qp4mGosS+VwJaYsbrSnj6rVMxgA28rm7efs4v9fZlaxxhsnjGiCjrmgKnANzSj3H1PnDXf4D1/gf0urNYk7+/sG8mlJOzSbpKMYxbpFdG6u/uvAby3eycYzbpJmZmZmdmdujxjGDdI+5OEJuzv7dkN27+BIPgf0m+Zl/Cmt8D+k3zMv4U1/gf0m+Zl7syRG3V+6eXwb7S32VvtLfaW+0t9pb7S32lvsrfaW+yt9lb7S32VvsrfZW+0t9lb7S32VvtLfZW+0t9lb7S32lvsrfaW+0t9lb7S32VvsrfaW+yt9pb7K32lvsrfZW+0t9lb7K32lvsrfaW+yt9pb7K32VvsrfaW+yt9pb7S32lvsrfaW+yt9lb7S32lvsrfaW+yt9pb7S32VvtLfaW+0t9lb7S32lvsrfZW+0t9pb7S32U1gkG6ShOM26x9G1kqtR2iTzMv/XmSLzM15ia8xNeYmvMTXmJrzE15ia8wReYmvMETZma8xNeYmmzE02YmvMTXmJrzE02ZmmzM15ma8zNeYmmzM15ma8zNeZmvMzXmCLzM15mTfjVyFS31YfuntDA3ReXmvLkXlyJ8uReXIvLkT5cq8uReXIny5F5ci8uReXIvMTT5ia8uReXIvLkXlyLy5F5ci8uReXIvLkXlyJ8uReXIvLkXlyJ8uVeXKvMEXlyLy5EHKBl0jNnZ/wB9V36Dk7+i3zEvck7Ri7oUH/Mn+0jwYDsWDP1b0MnbnWFGAqWOHVjvl0ZdF0ZdF0ZdGXRl0XRl0ZdGXRl0ZdF0XRdF0ZdGXRl0ZdGXRl0ZdGXRl0ZdGXRl0ZdGXRl0XRlkMdCwzGFjbb26/WftMRgilNSk85PKX+Dx1j8Qz/fIHZoSf0m+Zl7lpv8Apkm+5m/aZPvHNA/Kh6B2aebqwf8AwlL/AIZbJwb25X5X/C0/mRfvqv8AA/pN8zL3Ln5D/tSfwTQPyYehLo2dD/ha/wBbynuZX5X/AAtP5oX76r/BL0m+Zl7lz8l/dsWRV9u9slXdQJAkWlD3YzhJ3aKMeAIb5+SrIJhHj1h65jQBFpT/AMTeqz2RKOleiWG0hbN9iPGLZGyP4w2w2G6e7P4JoH5MPQl9dD6R7Aaw5EK2oqG7ogHDYGxBenMwxPFp+pX+t5T3Mr8r6bCK7dWKSAHjBmJBwsRzijDpOHsjGU3Zo8I3RThMctsv7Gn8yL99V/gf0m+Yl7lz8h/dybNy6alAcm6SxrtAp4tO2SRJDAK5Jido9izGvDrJ7VyEd8mL3Ad0NWdljWHgsl8umYfZhFQiwskQYzW3gRgijbMMkYHsWI14bne1bG24kjwgHuu1q47b4vYeVVzwEeJK45tStvbnKCc8uVEETW3YnaBG6WE4jsWrD1htNCsHIRnadwndeALlgk4QGT/E27EmdgjLQIALGYJ2tC7UmKauTtntUW294FG6xhu0/bP4JoP5UPQl0bOh9IsPJ5eQS8ev2+29LHGo3DOK/lOKSABeWu1ZQ52RuvTpvYg+TyBm7tXHZCF8Mpq1lStYerTr5U8LA697J5N6E67KnbyBzO5SZW0YxRUaeUKSy9S1mSXHu1Weqez2ZTttlb1qUno47JxuTmE3o1/reU9zK/K+k34srJiDI0YXPzIodggo7WsSeYBSf2DfsVu42+Tv1RDxKGMZhr9yLznxwkZ+yEXcKw3euEbuxT1+1tlEwWHEc4gCxGJKXpVPusi/fVf4H9JvmZe5c6dh/dyEXkarGNoFsQnm9WA4VmcWN/Ik6yX3MCTWG63arSfo7P1xv3QOzYx2azdd1k/llCGR2R6VqrAeU5TtFY0hgt8roJzWqvIBF2excA3/AHEhC5X6M0r9WLxcR4WgN0iRw1bIVGL0rFSSpdZmOdY/752ZPlGZqbOr3V6gd34D+7Gs3bK6ye3YF3/xM+tW5umeMS1Z7Is23dEZYXR9sjzYQt06UXmaZW9s/gmgflQ9CXRs6H0qUmDm7op3qx7A4MGrK4HKxqz623zFzj2g5u2CYS5Mcw4KIiVYxhVDFsO7tdyrNjXyXcuPVuVczdiOJM0zPbxTO7tGPV4ZK9cnPgwa352nys385i1mXk2MsPGhLMNUCwK1TIyyQ7Vj0a/1vKe5k4s1Rot6Lfiyufmsrn5kfYX5YPtn/wA6Q3Zdufb7iOzNSqM1Z3Y4+kGZsg/Q7u5idY/fQn1h/wBtWcFP/qqxj6dP5kX76r/A/pN8zL3Ln5L+7aEQ565GkzSi8XqgPW7kJMCzWnOdaFc5ixLYs1nswaUJeSnHtyAGIBtBuxZCchAK1XnYBJoQbpCLewgLASyMEte5Y2TnaCQkIOOcchZG8JSrvxmBB/KO2x6tbjj2I9PfciWOQqOQEWjVG4AQG5KlgZZGA4LFmUO/brkOCPbb8GZOCwAs51z0bhosQn+JMCB4bZRlaoO8VOdQr7nm4ovFwjMO8HtFhCI4tGPtn8MkD8mHoS+uh9LJYx7ew4eupOnRYzGcN5lLfx9h7ELlOYs3caIi5OmSxj3AIMHgEcHx1I1a1eIQ+Pu1LRLNGFfK3DinZyVM9ixRmOUWlF4uKrlcc8xVgY+8+SBbPlqJbYwzBXhasVSDujr5jH9RV6FW8x5Wbfo1/reU9zK/K+k34srQiTIzxYljo3UpXHCDuU0i9Gf2AP2urP8A+F+KOfuM0ICNDtuEsZ1gdZDqO8rUHcj1yEk5DnjKERDpu7GZlbm0zOzelT+ZF++q/wAEvSb5mXuXOnYf9mvWA/4tXDH8LtF4/wDcAW/tx3+2fwTQPyYehL66H/C1/reU9zK/K+mxzM3RuQZSnOb9ZegEnaJGanLdOUvZCxEcOkPTp/Mi/fVf4Jek3zEvcttFgdP2pP4JoH5MPQf6/X/woPreU9zK/K/4Wn91kX76r/A/pN8xL3LEGcLs0JNKLP8AtMz9BTdCboKLP7+UaQD1rsRzgSDTh/gilgEciTxEJlezcn7bo3LXmzf4SiN5F3/vqt8D+k3zMvd6zqSTEhL8N0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0Vuit0VuinMOP4/8AI8md/QnCBYShPh3aD9aTZK834+SuryV1eTuLyd1eSuryd1eTuLyV1eTuryd1eTuryd1eUuLyV1eSuryV1eSuryV1eSuryV1eSuryd1eSuryV1eSurydxeSuryV1eSuryd1eSuryV1eSvuztFqV29JpXoxaEWjH3LeN3dZiepZXEsriWVxLK4llcSyuJZXEsriWVxLK4llcSyuJZXEsriWVxLK4llcSyuHYXEsrh2VxLK4llcSyuJZXEsriWVxLK4llcSyuJZXEsriWVxLKDjjFl/yGOAYtGP75r/AAS9JvmZe84BP+PHCuwFccK44VxwrsBXHCuOFccK44V2ArjhXHCuOFccK44VxwrjhXHCuOFccK44V2ArjhXHCuOFdgK44VxwrjhXHCuOFccK44V2ArjhXHCuOFdgK44VxwrjhXHCuOFccK44VxwrjhXHCuOFccK44VxwrjhXHCuOFccK44VxwrjhXYCuOFccK7AVxwrjhXHCuwFccK44VxwrsBXYCuOFccK44VxwrjhXHCuOFMETfh/tGv8AA/pN+e/8Ka/wP6TfMS/hTX+B/SPF/um0ZNJurfwmLP8A+kYR2xZvTkBvxhtOtp1tOtp1tOtp1tOtp1tOtp1tOnYzMtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOnYzMtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOnYzMtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOnYzMtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOuh1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOnYzMtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOuh1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOnYzMtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOnYzMtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp10Otp1tOtp1tOtp1tOnYzMtp1tOtp1tOuh2b79p1tOtp10Otp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOtp1tOuh1tOtp1tOtp1tOtp1tOuh2b79p1tOtp10Otp1tOtp1tOtp1tOtp1tOngd26PEbQ+/8A/wC0spxj+PJGuSNcka5A1yRrkjXJGuSNcga5I1yRrkjXIGuSNcka5I1yRrkDXJGuSNcka5I1yRrkjXJGuSNcka5I1yRrkjXJGuSNcga5I1yRrkjXIGuSNcka5I1yRrkDXJGuSNcka5A1yRrkjXJGuSNcka5I1yRrkjXJGuSNcka5I1yRrkjXJGuSNcka5I1yRrkDXJGuSNcka5A1yRrkjXJGmsDdcga5I1yRrkDXIGuSNcka5I1yRrkjXJGuSNcga5I1yRrkjXIGuSNcka5I1yRrkjXJGuSNcka5I1yRrkjXJGuSNcka5I01gbrkjXJGuSNcga5I1yRrkjXJGuSNcka5I1yRrkjXJGuSNcka5A1yRrkjXJGuSNcka5I1yRrkjXJGuSNcka5I1yRrkjXJGmsDdcka5I1yRrkDXJGuSNcka5A1yRrkjXJGuSNcka5I1yRrkjXIGuSNcka5A1yRrkjXJGuSNcka5I1yRrkDXJGuSNcka5I01gbrkjXJGuSNcga5I1yRrkjXIGuSNcka5I1yRrkjXIGuSNcka5A1yRrkjXIGmsDdcka5I1yRrkDXJGuSNcga5I1yBrkjXJGmsDdcga5I1yRrkDXJGuSNcka5A1yRrkjXJGuSNcga5A1yRrkjXJGuSNcka5A1yRrkjXJGuSNcga5I1yRrkDXJGuQNcka5I01gbrkDXIGuSNcga5I1yRrkjXJGuSNcka5I1yBrkDXJGuSNcka5I1yRrkjXJGuSNcka5I1yRrkDXJGuSNcka5I1yRrkjXJGmsDdcka5A1yRrkjXIGuSNcka5A1yRLkjXJGuQNcka5I1yRrkjXJGuSNcka5I1yRqJxpnZ/TJLa33DC0fvl/CV2Z1Mbh/5wZ+rM/pfif+FIH6w9JvmZfwpr/A/pN8zL+FNf4Jek3zEv4U1/gl6TfMS95stQ5b0/ct5CnQ7XJZ+rM/st2wUgyMYVkRwDKH045mi196D/3XkqMbkac7l6rjxd0/2twSq5bH3n21/bZy1CgYYrDOzt1b+wPlKOOmKB/Z5miPINRf/a4H6Dl6TfMy922eNWsY0mBZhXhm1TNGxWCWMpNBnlKWrMRWI8VqO3XujxRq8PggrOo8XRL2yZnK1clg7Uq2E+k0fYUgwwkQn2vw4p7VQuUrommG1k6mMKEBrOpsTUNIRKmRq5Gv3KuqcrXYBKDYLOY44q1JFytOrcFVNX1HjD3IVBd7EPlnE8yQFCRCfa/EClsVC7Svi7gDFDWDI521Zid3RAsAthYoVqHJVqlQlYmm87Sr1AUSlKMMJTJ9r8SF9jY67Rvi3gVg0awSFfHZGjkAOYNy8KoEpZ0L4bddjBtahxdArjLQz2NyH/UA5YVwEOTH5AF2t3wX8hWxoe6edoIqj2nnqTFQqiO+PygshVjYGjfrQC1kzNhxLHYjGTx1UhdQYUFEEL9LHXXu42uaXsuAJmrWTtwwFuF3GQI6t6hxlAjwLQzuNyDPAH4I+p8TUI8JUctRyUH405QHF5TfV+JA+xVMhTvi3VCmiEciTbWGIE+xqNytbGxg3clTx0GlZrauxJXYbM7OzOy1T9QxPtIbEyzURTMYVcciGfVmJZ0CzUsVuSPI5qmbM0rI8Xk6eShMgf8AaVf4H9JvmZe7q6y0KwaQp0BPg3otpSy5KhKs9W2ChrgrDo4inRrQC2pMeKlfpzBnbXBxEyx07iqoMeOwXVmLCGnG4HCfSaPs1NY7tyljpAnpkAIgjUJWx+o4ipati5LeNgwMZSrhYUcWKOP1UesLV7QfGkKsQETUKk21CHmahpBcWOo0oxiL8NbutVHd7VPHuCemq1ZgwrzrUNSjhR1c02ehOdc2n7lVhCxuMDixEEFZyMSY65OWlQhfDwnLVEyHu0sVAGNpVwxDAgmwOogNXUotKLxfAP47L38bPVZpSHVojyXTDafftadxdYVCFk2qMVAFdr9ctnmaWJZWkXZsIy1kzNigK1+mCrS2LrFpcs0BwHHbBH+7WldazZmw4lj9R4kNOqMmYzDZuA6GPx4uHSBW9met8HFmKtN1IVsXFp6cJ4/M3cdPO250MWc8NPYmsKgO0bKadFZmCxW1NcLVxsBwxWIq0Kg2exp6DXwXqmq7E4NVoql9m6deIW7lXHaiqTo6vYr0BPGhZwFinAQcfigYqZ3rXpgBqffkDU8HmqrwhTFxa8AxWqfqOI9pf1iBavYrgquqljT1qrEQaGJr4tiwBmBCjqLFRYYxjb/h/tKv8EvSb5mXu2AfaHP2YR+ysVSC+Az4gS1mOUeHaHUtisAgYOrLEHvUK0NTgkfATaGnbYLWKE7auswFjmAsH9KoezVIAwzdE9ltN4Z2Z2r0dOgyTBFqb6hivZHo2tZrWf0h1hOk8bTdZL9UY32fhrWS1PAflaFmw2nMA42mMFDACyQwDt28fAg6lq7pLFzaUoaVt2CcuqZZn6XdWjXZ8PBai6Us3jrqhOJIRnDLka/qKhWB7NTwehkqOTHS25fUhbK1RW72Fk0NPWg2sWJ21XbEHGOFCDIOlOOtHWBviZhWsLImrCrq1+lyrRn0hvab9agWsmZsOJY7G46WOpO4q1auzsH2al6X8nTxQm0nBmZlksYTT1qnchqMLWtPlmHTtkNnEidruVqY3tPY1aKRKADwoWRXKgjwtZapUshqk1iGPNo2Cx07gCiYooY7TQMkEDXrtKqwx27WlcOdt0dNHPC/coFMTC5MpqJMtgKlGsa5T0/bJdxUDnWqfqOJ9p/1oFW7uOrvCvct6TxJG6tpG1Ze3cpFzbtHUWJl/tSv8D+l1ZrEnf2kjKQ5NDC4aOJGdnWZw8MtAJInAO1VetabTV6s7xpG0iHs1+m1nhsc2mCgPItB9MsWudrFGrGnUBXdZHFV8iDsl+zGUA2wGJwtbFvIqyuIbJnqmZeI6Zp8o12gK1WmE1DAHxltnHaxHIylW+yfEs2afKNextbIhcE20vk67PCvisFXxkpmfLYivmAtunpvMbNhMZja2LB2gIg4EHOBBaaLUtMWpfx9S5UcFj7L5QDbQYvC18S0p+3UnY8RZ72lK3CxbOnZpM7OfS8xFkXHVNNNCxGzedmdujn0q7WJGoz0qGdVxIlBiYwlZ8TQ8bSjW9psF1zAsmszjWytNqzVBPWqAr+5VwvYyli+RZTHwydMgJY+tOlSHTJZ0s4izPjamm+3aazfMMRhTFOemLNWcuBjMAKkZ7Vi9QBbBIJvsvkqzO1TF4EGOI9ieTxlXKVu0X7O5aMe02LxFbFClEWUwArxubXfTd21JmvArirBgESz2AfMzqkh9msgsbhbVCz3imwrPlh5NZfB18sGA5vpvMdthvjMVWxYXGLM4Wrlwwm2IpW8dAsTf7SB8D+k3zMv8Q/4OpabOezvvNFoszN/v6t8D+k3zMv4U1/gf0m+Zl/Cmu/Qcnf0fwPJ/wCFIHZoSf0m+Zl/Cmv8D+lN5tZlt3HW463HW463HW463HW463HW463HW463HW463HW463HW463HW463HW463HW466nW463HW463HXU63HW463HW463HW463HW463HW463HW463HW463HW463HW463HW463HW463HW463HW463HXU63HW463HW466nW463HW463HW463HW463HW463HW463HW463HW463HW463HW463HW463HW463HW463HW463HW463HW463HXU63HW466nW463HW6wtx1uOtx1uOup1uOtx1uOtx1usLcdbjrcdbrC3HW463HW463HW463HW463HW463HW463HW463HW463HW463HW463WFuOtx1uOtx1uOtx1uOt1hbjrcdbjrdYW463HW463WFuOtx1uOtx1uOtx1uOtx1uOtx1uOtx11Otx1uOtx1uOtx1uOtx1uOtx1uOtx1usLcdbjrcdbrC3HW463HW6wtx1uOtx1uOtx1uOtx1uOtx1uOtx1uOup1uOtx1uOtx11Otx1uOtx11Otx1uOt1hbjrcdbjrdYW463HW463WFuOtx1uOtx1uOtx1uOtx1uOtx1uOtx1usLcdbjrcdbrC6nW463HW463HW463HW6wtx1uOtx1usLcdbjrcdbrC3HW463HW466nZvu6nW463HXU7Mup1uOtx1usLcdbjrcdbjrqdbjrcdbrC3HW463HW463HW463HW463HW463HW463HW463HW463HXU63HW466nZl1Otx1W+B/Sb5mX8Ka/wP6TfMS/hTX+B/Sb5mX8Ka/wAD+k3zMv4U1/gl6TfMy/hTX+CXpN8xL3buRvXbs6GMfG6hAzlBiMs2VA7FYov+aCcR26w1PYNUDQ7HdGzwhNDIMnXYtP3LFg2TY7PlL+XyFcNqGosOJ7KoWoXqQTQUzVwdO8ztJmdpThCLylmbbwxRz1ceSZaVac+9XFJoTJZNDVA6rOQbS2KBQG3bJHAJ2YjOzszsScBtumIwyR3Qd2izu4DhNF3GpEHDo00xIEeTNIoAMzmjKMmZ4uQbSYalKMGeUmdpMzspmr1/zWdpMztKUYReUsra2Yu0etjCyNjaMyd0EJtCc7J4alDUdZnIvjaUjDrXIWgD2lMIMepIThOLSgpEFDbGSgQc3dozOATsxWdnZnbePfsU5RG26ciDjFpS5ABvGElljmrZnFAHqOzbA1GFZ8Vnhs7i09kjXXtVbaKWIhTI+Fy92eThykQoxR6zhOM4tKDuzfe4jgL17ahYrxntkjHEFmckZRnHdGc4QbrMRBEbdBaksnp16EgRfrFldBOVMzNjREBTEMnfrwk0SSlGDPKWMzcbFq8EieY4vFpyk0Wd5RlGcd0e4ObThHGVrFapY7mBs2rOHsnLpu0Wzi5EOsJeMXKZuBwnCWLuNciu0mHL2TlAcXlMRwlhuHqK0eoGi4JWK8P+DrA2TWJ5TvEJAcdxBkGSO6Cyt1qFMp2xlqF+sGaISA23TEYRYbh+xzgHJoTRSjE3Ug5jJHdB5wi8YyieuxO2ndmZ3fv14j7k4yjOO6LuzM7uOwArP213q45tEjyaLPJ6WaifIXq01fsxpVDnWIvwyFMZpzlAcXlMJAmbcNEnAbdZpiDeexGMILdSQnCbNKLkHGUYyd2Znd2IN4b2exVHCLy9kTV3l23yRJhoWiQwp3Li65zxlGbNKLTg8ng05wHHdMRRlhuhlsv46dSEBkGRusJErh6d1nZ2Z27g+rxQzBKzuP8Af9f4H9JvmJe7oza9K5YkqTMDVN+EQUefm8uEuRoBwlijdo6rZuNi1qYLtWqXRX70A4V70NO1Xp4uEZLS3zeUQL1OhqHMcjK56paqEqUsTWlTx9avKbSlCTRbDYoLknktNnEPK3aYXB53NWgm1DhBY+gSxQPb4Omg2I47TtM9SJ7tGsSnqiNaeVDK5qQIYmr08BQtnqUcfibAWPkcMaFPKmx4rXFsZwwcrUw1Wrchax+oXG+WqV7Y8HjpSDaxq1NTc+LaYwXxEwrZF8Lup4i3kzYrDByYOfkRCfT+cr1w2P1XSWofpF1Yj6XSU2k8JNFsLiAtOeT04YI8rcpgKLzubNULnsEHH0DWKD3moaXAeGN07TPThYu0apKmqI15rV9Co1HlPQo0MZS74cXjoZti38icDacylWdVaqrSfHgtiJdG2EfJNQeWKwBrssdgKtqs1q/Q7mJzL41N0+2jrV/0cqz36VAqGDqRhWuHWd/UOIWqpxG+NnImp8Q7dVp6BTXb+UKtTWHFRiCGYo8XFU5gDcGekI8KFJtQFPkL4cGTG34zpZRiZPLjxTW9L1oiaeNzt+xTxleDQw2mXr9C6ctkI1mpOpChdv3vL43FQx5jSq3OLYzZg5Wphala1C3jlqxmavjFH4IrMv1x1tY+VgekdwMTRwd+rF7FTFtXpEo2cNh8dYv5KBVq8UnpV7MM7b7+LBAWnSyrVrNU2mYbw3709LfTLi099DsrR30n2VKL381lwzy+IDigxv47NZXtYmtMI8NpnjMxtNXiShepTV/FBt2u/dtvjcbfpGxmrnm1OnshpnHzButadOYJb+MPpmcIvm0Dh5u3Zs5KfEwt6obG/iy1TTqWMfM74HG061QFkVSu2obtuzbyVFtPFDeoDIxIQm2fuEx2OmYdTTFF6/W5i6trGhsBPRqNnynvX7df7N3adupq1ivkcKwrmmse1Qsx420S5pqZC4HDgyGPEa/jg+G1AWgLLGhdyvjj36OIqAe1jr2WceAHdhS01RnXadzHYuxUDZqW8dh8aTNZUBYxaLNFs/UqWseSZNN4ylCjWuMGv5+/bnZy2NFg2HfxoZwOIZWzdVr2NMFsFeifEwsEwb7/ACOXJi8dDNsTI5AoG05lqr1c59YwKyXyNlUf0oRYXC1rtCtZuszMzM2oLJ4tUpV5aTxXHeDTFZDpy4K7V/R7rT7/APw9VA6Nq24rnEPqEoMrVwlSvaHbx2paFV7VMrkHWwePskr4+lirYGtZLDGHj81wa9inztTXQPSx9XHDcVf9/wBb4H9JvmZe7UsPp2/ZrWbGcxtcTkfAAMY9vKHwX1/LrWXytBam/IxyOCFqmUUwTLcanhJszMzMy018xlljawbGfzDEjUqgf/pWSkaFGzIGIq4GdFj2sGQBc/enXqmjic7eFY1NkgSoTAC5Xnb0zAA8RmaljHCYtK3G5qtjQL0bV4FnKz3MWcI8SPTtmkzHxj4ad4sKVi7hblk9G4w69LO0h4q/foNahQvZSvRoGqlxcHd4x6zi04Si5JFFE+BiegxcUeqHAZOtCi1U9g0MtnKUK+VLChqDH3C6gylUmNOGvh/plJZKRYUbMg4etgSUYntYMoC6ivTAM0cRqG5yNR5MHjigCWuS5paAB4fL1LOPBEtS5C7quJRrV8XliCKnZqXsWwBaeuhqgnQtZM8MtkaNKsiAGYUxzhI84i0/LM1ORiTVxYbL0zUBwMEkMxqWFgVywPH6mBaPqbLBs0ZgBnv0tWVaTcCtH2Z79Q4hapZpSxkXajSZ+sWZmboyuAhnc7MDz0jjdrstMfk28WXT1wdNjY61HNAnkBVA3Jti8/C2a9m6VMHcbUISW6Fay4BaUMBjPgSY2ULJqLWMHmHNGzh+lfNnBRPcw1y2ejeiIFLOUh4pardohou7ZXHLJHr2MTcmHEWoU9N1jykPTOUrcmWlyGnXsRli7AamXywzq5WgeocL4Pu3r9CpPPklRyFvt0KzVKFYC0t9MuLT30OwtHfSfZjL46Ofy3e1DkAFqPSBncbOOIpMhh0qevE0NPzxpY2S0FGFS3mbzZPMSxkDUAUdX72qU3gDNUS1IkfTzzsZG/klp38zLLGVsWCzcpZIkdNht164m+5lnoSniLbRwFuqfHAhDC2B4q1ex9nP3B3Ygx1UTbBjgtU05WMX0FTzlK1SgV6GQhlh2dmnrYaLnx1vNHHlLNPHVs79Uwqv/JWVgv0sZaR+jhRmjDWVZlka9StnnLetQ0vUB3HzNBi4CPGx+ao2qUCTxWYhf5TiqWA1tRZZjs7OzO2Tg5MdchHS9ytLGCE2KOPFZG7Ss6hvCtAhQq1RxDXAJldKXGGyWOGDHRFieOtNZANME6FnJWIZTKUKdbUr8W1iLj5PM0nx54V6P6UKtOfRa3s1LGYSYy/CWZxrVu+pXefp28dY4Xe0n21gcvRHjRBPij8vUdwzWL2FtWjULjir0c1WhitTvs8cZ77CyeKswrYIWnj1Ghcx74F8m4qdb9V3lkcpVxsIOWMmlFpR/f1f4Jek3zMvdsgBZG4zQwODBN5wZmZujNRqVzFKK7Vq2hxjYt1KNiEGJev18aGBrGCBKzdu5Unsr1K1VySE1OoKwU4vbPA4whO7NsfUAeRg36dG9DaceGxwRzHCEIChEcC4TGHL3SNjqUDRKKdOo9qNmKtYPDWJby1KdWoLoK/jcbd6OanjKOPbcK/TpXobT18Tjqc94FduBohcpcZ1yWaNlnVrEY67LeerTq0R7Q261e1BxEHiMaEcxQEMYRwGNTwWLIXuzjQpBO5g36lO9DYaGFw4BzGMQhgFAQzYTGHJ3S+PojLEo1KMZxeMq+NpUJzlWv4vGXvvNTx9LGQfsq9eBQD3j4WEreUs5aasYXG2idw1akGtBhivVat1tho4egMMwNZp0ygarOMYwjGMUepUMURpXKdKyw5F9oqNSsQxAqFCnCyWwO1jKN7o9mtj6OOboCxWr2BOM1fC42sRiCdmdnZyYDESm83EIYYNAdjDY65NyGq06tAe0N/GY290c1bHUce3Ssr1KjchGBnwOFUMXQrV5hiGqAAYihLAYmZO44hDDBoDt43GW3jMv4eweNp1yGMO5To2nhMiqUalQU2EChUrjcQq1SvTEwgJ8bRfvs9bEY6lN5gdmdnZ54HEznvkMQwQaA1bxdG999iOGx8IQg1mnVswgxy4TGHL3SDEMUIwGGnVquWQMhQoX2bv1MVjsa24KdmdnZx4zHVDzNWyFChfZmPVxtCl8v7C4LFFI5JiEMMGGO/jMbe6OapjKOOi/ZPj6p5iJOUYzjKMoUqVYPZGEFeoGIQEoVOQ1pj169sWwwMHjK094kXB4sxXLMAh1xRGO/jcbd6OaMWhFosh4ugGw9kd+jRvxZj1cVQou711ZsiqAIWY3jns2K3BW8XQuuz2KlCpSi8a5hCsQcZQ4ahX3uMdCtCq9aAQCrigIKlGM4vGXgcRAm9pgEQUhTBXFXFEIiYfGlL3Z8GoKw5xX8bjbzMx6uLoY7rIJwBsDcZalKrQg8AWMPjrc+4arTrUobK7U6grMrTXadG5CMCszMzM37+r/A/pN+e/pzhCbdJ/3Eoxm3STMzN0b+0nGE26SZmb0pkgKLvPymOXlccvKY5eVxy8rjl5XHLyuNXlccvK45eVxybKY9eVxq8pjl5XHLyuNXlccvK41eVxy8rjV5XHLyuOXlccmyuNXlccvK45eVxy8pjl5XHLyuOXlccvK45eVxy8rjl5XHLyuOXlcavK45eVxy8rjl5XGryuOXlccvK45NlMcvK45eVxy8rjl5THLyuOXlKC8rjl5XHLyuOXlccvK45eUxy8rjl5XHLyuOXlMcvK45eVxy8rjl5XHLyuOXlccvK45eVxy8rjl5XHLyuOXlccvK41eVxy8rjl5XHJ8pjHb72yeMj9zeVxy8rjl5XHLyuOXlccvK45eVxq8rjl5XHLyuOTZXGryuOXlccoZGgR2jFnZ/8AQ9f4H9JvmJfsS5ZjUrFNK5es5AzzLwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwri4VxcK4uFcXCuLhXFwrinVsji8p4PLmrHGAn+hgfBL0m+Yl+xNTMzYtpNp6MZ5QW7oy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoy6MujLoyMOBBTjJndnZ2H8Ef8AQ1f4Jek3zMv2Jqf6WtN/VB/tyXwv7B/BH/Q1f4H9JvmJe7+CnepifaTyePXk6C8nj15PHryePXk6C8nQXk8evJ49eToLydBeTx68nj15OgvJ0F5OgvJ49eToLyePXk6C8nj15OgvJ49eToLyePXksevJ49eToLyePXk6C8nQXk6C8nj15PHrydBeTx68nj15PHrydBeToLyePXk8evJ0F5OgvJ0F5PHrydBeToLydBeTx68nQXk8evJ0F5PHrydBeTx68nQXk8evJY9eTx68nQWobVaxjX7Om/qg/eKYQY7ieUx68njl5THrymPXlMevJ45eUx68nj15PHryePXk8evKY9eUx68pj15THrymPXlMevJ45eUx68pj15THrymPXlMevKY9eUx68pj15PHrymPXlMevJ49eTxy8nj15PHrymPXk8cvKY9eUx68pj15PHLymPXlMevJ49eTx68pj15PHrymPXlMevKY9eUx68pj15PHLymPXlMevKY9eUx68pj15THrymPXlMevJ49eUx68pj15PHryeOXk8eg3qZX6D/s5fC/sH+XD/AENX+CXpN8xL3Hfoy3WMsSbDHiceJujeNorxtFeNoLxtFeNorxtFeNorxtFeNoLxtFeNorxtFeNorxtFeNorxtFeNorxtFeNorxtFeNorxtFeNorxtFeNorxtBeNorxtFeNorxtBPjaC8bRXjaK8ZQXjaK8bQXjaK8bQXjaK8bRXjaK8bQXjaK8bRXjaK8bRXjaK8bRXjaK8bRXjaK8bRXjaK8bRXjaK8bRXjaK8bRXjaC8bRXjaC1DRqAoOQWnpxhkxvL3MjelVYYghwwpuxb74zHrxeOXjccvG45eLxy8Xjl4zHp8Zj14zHrxeNT4zHrxuOXjMevG45eMx68bjl4zHrxeOT43HLxeOXjMevG45eNxy8Zj14zHrxmPT4zHLxePXjccnxmPXi8cnxuOT4zHJ8Zj14vHLxuOXjccvF45eLxy8Xjk+Mx68Zj143HLxmPXjccvGY9eNxy8Zj143HLxmPXi8cnxmOXi8cvGY9eNx68Xjl4zHrxmPXjMevF45eLx68bjl4zHrxeOXi8ci4bHFj0QjWcVYhXs/2U/gl7B/lw/0NX+B/Sb5iXuZczix1ibVARrVxCj/AJ7U/wBLVKLyLJ2rE71cRPcrN381cJL9h5cUTULDPjiyPSrkl/Yy+B/YP4I/6Gr/AAS9JvmZe5n/AKVYTfg3+f1P9LWP/MOsf8jV9yh9Uyv7EyP0+0sP9Nq/2U/gl7B/BH/Q1f4H9JvmJe5nvpR034N74rdY05jH7DWA19vd9grADSJAanbrDNEM0UowweZAHDYF3Be5PK0By2SEcJ47xeq9kLGYL2LtWq7MaOUoS+5mfqzOyd+iDbr2N7C9hbIAyHAnsDYBYaTi9TU/0tY/8w6x/wAjV9zH/VMr757IK0WkaE4kjGcS2AgcbFLZAGQ4l9DmVu/2PWd2izu4cxRPYjXF6J7Aa0N5hkgWDTh6B7IKzRcvq5D5GysP9Nq/2U/gl7B/BH/Q1f4Jek3zMvcz30o6b8G97IWeLUMVUhkx1ijYmrOZEErgDk747jVYtYsCqhcpY51n/wCT4SUZ28lKKzjEfKNIVKxG1UGVZ885sKmLDFcOIcjUrcLteBoTyEIXx02WZsGlMFEAMNjwjaL+OPQvhLSPfgC3XrPYM1cBDPDIQlQe43nIk2di1lgVLDBJSyErXceU87B5yjXoZGvehLYbMABYMCYc2ORoCPm7Uw1ZjjhrpChECSL+ohLLwgXK46EyYfHEG7LAkmzW6sllrPFolk2PHPGXqsZo2aHA0ggu3R3bmOeNu4GkFymhnYv95NPOzhtuz54DdyDNfhCm1mx59mbfIRxHFEo55yDzlGvSyAb0JOOzmBBK4Q0soK5KQn9zU/0tY/8AMOsf8jV9yh9Uyvv5aL5LJCoxwB+5UcE89+film/nMSrVwNILlL59mbfMuQFClK2KvZHYrQsNjssHIzLCBMjCF4VNkUkRDnOTMdmjmEOcSQjON7LApziJq2bCUkQGt3AUhOUzagi3/MkDiIFjRln4SlLj0r9e+N5it5gNY3YhTzArJexO9kwUXhCQM5CRohsLN2pgqEHHAWG7QQtPO14EOJByEXpvZsef6tvaraBcB3h+eD1LFhZ+rJisaOfgxIQO3szPW7dq4+GAM7CNUnkL8MfAU5kz8IO8oNfFOnK0GlbHdrxPCeRhG/Ck1LJRsntBazlg1bgqkr90dCu5554vdp0SOTPwg+6FW0K4CJhWs0GuZwCpZgFsrglcyoKNgYS0clO7MjOXOjiWYq9DKVr2+EfdyHyNlYf6ZU/spfC/sH+XD/QwPgf0m+Zl7mf+lHUfhb3tQWYvOvWfJ5Krcp9qFO29jHROtPChxZmWfDFi0itnehD40Eu3Bx7FgIRGfIQiixaWfhF8RN6Z7lCdXrdPfvPgWZ8YzPiZPSuXqcsPHlWrt+Svf9ecozkiGEJ4RnkPrONWT+n2UH9OyWFhCGOA7W4Rnn6jSzc3FjjbaNy2CsKAqMLT5V7EgwjPUVyT6ii3ChNZH78QeT4np4+t7C/qISzbljkaDhcmfM2xsZQ4IHjJZy1CV2sCWUyQLgYdt7bGxc7I8AKEcfCbZYUI5THkjlWY+SxwJkFAgpQlppulaysGOL2L83zs5cmgNSv3pQeD4WseFI4T1p38RGQZ4+1StuWYO1fxdu0UVK/Ttnf3dT/S1j/zDrH/ACNX3KH1TK+8UkQjnOWNyoQHt2TY++NszNx578/ErN/N4lZZmPlcdXnMUJiccsHBpgyNSVa1KthrwJVAeLv0Vime1kr9z2aitdil2ovlKL47hLT1rv0O1LDQY2QyZ52adW24nLlGY+XoVpkCIgpDnlxQo4YgQUr10FYIxYwdryhzyOC9j8ie2Cpk6dyzGJcnTuQvBv1YZatYKMN5ll/ptpYX6XVWHHCWWyk31C++dADxFCI2HHDMwL+UBDBDjyslNXYQfP01qKMXxsnVV+tcLqUmhF5PSyoR37dw1e+HzTGHqdt1SuyYQ2Cw203/AMqdkb4ib0bV+jLCR5Vm7kJEbh6ghNHC92OTvtfN5KWLrx1LFnrVYphQiJhtgZPCle6Ym3aCMsxWHv3LlM7ZIcS5rGQnlJuLHWZQxVq1WqDiEMbhssC0/u5D6faWH+m1f7KfwS9g/gj/AKGr/A/pN8zL3M/9KsJvwb3qNYxsjat2HHB2dnxAD1C3K0xQvYY5oQuiyV8gDyy1GdyuOQY5HJzH2WwtSxVLcYynXN5uBlnKdiRRWKteo9bG9hsIEoKMYFz4phKGyLH1+JTEH2ZPH84DbYX8tXZhmrU7l22O3dzFOySVezWsGyuQrkChVzNgpBfFwIKgAZDANLN1jNbqtarEE9ezkcbBqxaD5ExiGshrmhnLZXzgCnp7BHrSPj5hWILcE0ah0Sud84MyyFY5MnjyQ9jv0Z1i6hpWbduyQAiDnCWHrmGC1TsVnv4d5gRwZG3dqWiZeiWxEJq738lYH2YYGsasA8C4cBgzuuTLUSWYCKHymS2ONVB3Wp7TivZSqzis4upZazYuHe1k6RzMSrXtW8lG6b3NT/S1j/zDrH/I1fcofVMr72baxOk4gUqo61YQmzlMhQhNWyYrFp8WSOXrnNaxsh5jHlsMGxXnk8mUXZhiaD0auyd3GHJmGeGdqFsVRuDD1pVKQ4zRqx7maHInbggVT08wV4HrXMZeLaqsfJ5E4dmXoGP2bFaeSyphdkb0CGxnFsAt5PHjjWNjPIkmY1s9jKUrhXeALeTv17JMhPJ1bUTgstdzEwDUW2xZlkxzLQswhihzDj64yYkBwZDJElmMdK7Xj22ymUiNgvh6JagzEPhgHCW+5bVY883UM2dCU+PmMVZnjXDF8w5+AWAMdUarTCKWcpSsVGkHLCtW6FLp/wDTosBXNXDYYuohTEYFgOOrtTqBCs9UMeuIgMbS7WMiAmDx1kNspD5+uY4azC/9LB1ihFbY0B38KYsRVz5W7bFNXK555mgWJwxOEgp1y5LEM9edEmUsWnMf3cj9OtLD/Tav9lP4ZewfwR/0NX+CXpN8xL3M99KOm/BvU6e9LrtfaOhftWRGve3p6HT+y6e509nT0NT/AEtY/wDMOsf8jV9yh9Uy3qdPU6ezp6fT059dr7RY+/atiPf/ALLI/IWlh/ptX+yl8L+wfwR/0NX+CXpN8xL3M99LOm/Bv8/qf6Wsf+YdY/5Gr7lD6plf2JkPkbKxH0yp/ZS+F/YP8uH+hgP0HL0m+Zl7mXD3seeEaViNqqIrf57U/wBLVKUmK8Y1x9kAh+4D/ozdkcv2HmDRBQMqAnBTAJ/7GXwv7B/lw/0NX+B/Sb5mXuOpBs4s0y1x5rHzb/l5fHLy+OXl8cvL45eXxy8vjl5fHLy+OXl8cvL45eXxy8vjl5fHLy+OXl8cvL45eXxy8vjl5fHLy+OXl8cvL45eXxy8vjl5fHLy+OXl8cvL45eXxy8vjl5fHLy+OXl8cvL45eXxy8vjl5fHLy+OXl8cvL45eXxy8vjl5fHLy+OXl8cvL45eXxy8vjl5fHLy+OXl8cvL45eXxy8vjl5fHLy+OXl8cvL45eXxy8vjV5fHLP5GnZouIWnWaWTF19zJUWuQhIQ8w9d2Ff8AM4xeZxq8zjV5nGrzONXmcavM41eZxq8zjV5nGrzONXmcavM41eZxq8zjV5nGrzONXmcavM41eZxq8zjV5nGrzONXmcavM41eZxq8zjV5nGrzONXmcavM41eZxq8zjV5nGrzONXmcavM41eZxq8zjV5nGrzONXmcavM41eZxq8zjV5nGLzONXmcavM41eZxi8zjV5nGrzONXmcavM41eZxq8zjV5nGrzONXmcavM41eZxq8zjV5nGrzONRM5QgzdsFazfPC1c/sp/DL2D+CP+hq/wS9JvmZe9OuCf3z4VRcKouFUUqNFvw4VRcGouDUXCqLhVFwai4VRcKouFUXDqLhVFwai4VRcKouDUXCqLhVFw6i4VRcKouFUXCqLg1Fwai4VRcKouFUXBqLhVFwqi4NRcKouFUXCqLg1Fwai4VRcKouFUXCqLhVFwqi4dRcKouDUXCqLhVFwai4VRcKouFUXCqLg1Fwqi4NRcGouDUWowiFjHaGm/qg/elCE26T4NNcGmuBTXBprg01waa4NNcCmuDTXBprg01wKa4NNcGmuDTXAprx9JcGmuDTXAprgU1waa4NNcCmuBTXBprg01waa4NNcGmuDTXAprgU1waa4NNcGmuDTXBprg01waa4FNcGmuDTXBprgU1waa4NNcGmuBSXApLg01waa4FNcCmuDTXBprgU1wKa4NNcGmuBTXAprg01waa4FNQrgF8H9nL4X9g/y4f6Gr/A/pN0axN3/Yep/pa039UH+3J/BL2D/Lh/oYHwP6TfMy/Ymp/pa039UH+3J/BL2D/Lh/oat8D+k3zMv2Jqf6WtN/dlB/tyfwy9g/gj/oav8AA/pN8zL9iao+lMtOOzZQX7cI7NCTuzdXQvgj/oav8EvSb5iXu6dzk8layoJn1NmI5e/jqD6vyeNlDzATiOEZQrUGUfB46dt9NZkeYoNbf2VM7M2ormIdZi34nFWryxN7n4ynbl/ksjVa7UKBThZoWHi7Z7LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LLz2WXnssvPZZeeyy89ll57LI2YyR4SGTDY0t21CSZujM3+hgOzQk/pN8zL3dFv0v6oWH/WWpFquzRr4C7A+loFFgcdAyzH/AMxqbGYtsR/8PqfKYtZzKQw2ONclUx+sb9aN8um7Nqzq3JTt5TJ5a9mGwuI1CDP4jE2msVbBqehQnBh46oztGpZWZyuUPlB4XEXvtLp6ELps5qFqOCfI1R47WFilG/Bspfq6eNfv0AarzlNsi2APlnoShlP8XZo1LjN3/s7i0+nMUvs5il9nMWvs5i19nMUvs5il9nMUvs5il9nMUvs5il9nMUvs7il9nMWvs5il9nMUvs5i19ncUvs5il9nMUvs5i19nMWvs5il9nMUvs5il9nMWvs5ilqbE0sdhbVqtjcHjbFCqUmq8TXx2GLbp5QWGx2FDfQdI5wwGM+nauOyYbA7dHFmz92/xMVp60G7aq5K5SfI5guLxNjA5HDW6zm1TUBijYnh4zSdkZXLktL0KuTjlHtYalezhLFcNmgbT9+q2S1bh3xdR79LEaXaAnJe0rRqZKndNcqY0+oblqdBqM8LlqtPK6nqixd/Eip3tLZenSndjh8bh8rjKtxfZ3FL7OYpfZzFL7OYtfZzFL7OYpfZzFL7OYpfZzFL7O4tfZzFL7OYpfZ3FL7OYpfZzFL7OYpfZzFp9OYpfZzFL7OYpfZzFr7OYpfZzFL7OYpPp3Fr7OYpfZzFL7OYpfZzFL7O4pfZzFL7OYpfZzFL7O4pfZzFL7OYpfZzFL7OYtfZzFL7OYpfZzFKOnsVB2dhBECDQF/oav8AA/pN8zL3dD/U9TKGGrZzVuegcWh8NULAs2ZmZmYxYBEQs8HT1Lk7V7NUM3T1HjT0M1e110s4CtZDVshsVAmBhjis63zJBYuca2sc+EutDiDgLbE//wBfLSUmbTVBEx5rOs8qBr2liNXm12dPFA06GlenpnL4cEz4fCZsObwBD5OOlj1RvYwOlM5byda+G9+x9a/p64sD9Gx611bEesHDVdd0mq4jEMw9M3iDgSGnMTWxxcicR9N0L1uxfw2EyuZpZ+eDy2lJtWzuo6pbN+ljGFO5rT6vpj2aGdmhnV/TuwHplq6/qIUcqFKo2uf0uVUps+MrLRcN+EzcG/p4WDYgtVa8lApcJRDqT9QabWY+l3Voj9OVv9ZV/gl6TfMy93D4SGItZMg6WAhSzV/KN7MvjvIY2xTjj6YcXQBSBlMcLK0T0y1sMCrhY4yzDR1mozgqUdLU8PkJ26+Z0qDKzFYhPRz2xG8o2Fi2A8O2Hoti8eClHP6aq5ftnZ9GnNKDZPJ4qjksdwLH2PusN60oYLG18W+PaGj7lRpBq4nFU8LV49b9j5zFRzWONSlDQ1iEO22L0tQxJJGHkKlW7VJVsfY+3WhINXEYSlhKjhEbSMq9o9nFYfTwMUctueX0oHJGhcCDSLwsitZLN4MeUs42zJYbEQwrXmHpjBNk6t+yKjpaILzXr+QxtfJUjUzYXTtjDuaD4LCiwALI2v6UHK4S9jcZpkVG69+1lcDDJ5HG3XthaxWMF8Njo4TGjoj/ANY1/gl6TfMy/d2OxdLDQMKn/rqv8D+k3zMv4U1/gf0m+Zl/Cmv8EvSb5mXuyJKUngNqsf8A7cQa4o1xBriDXEGuINcQa4g1xBriDXEGuINcQa4o1xBriDXEGuINcQa4g1xBriDXFGuINcQa4g1xBriDXEGuINcQa4g1xBriDXFGuINcQa4g1xBriDXEGuINcQa4g1xBriDXFGuINcQa4g1xBriDXDGuINcQaesKP3vxBriDXEGuINcUa4g1xBriDXEGuINcQa4g1xBriDXEGuINcQa4g1xBriDXEGuINcQa4g1xXb4RWHZ9hfQd2izu/Q1luqesJvx4o1xBriDXEGuINcQa4g1xBriDXEGuINcQa4g1xBriDXEGuINcQa4g1xBriDXEGuINcQa4g1wxriDXEGuINcQa4g1xBriDXFGuINcQa4g1xBriDXEGuINcQa4g1xBriDXEGuINcQa4g1xBriDXEGuINcQa4g1xBriDXEEuINcQa4g09Ybfe/EGuINcQa4o1xRriDXEGuINcQa4g1xBriDXGGuINcQa4g1xBrjDUgkE26AyMRvRMZhszN2ik+8vGguNBcaC40FxoLjQXGguNBcaC40FxoLjQXGguNBcaC40FxoLjQXGguNBcaC40FxoLjQXGguNBcaC40FxoLjQXGguNBcaC40Fxmb4R2DAdol/Ytf4Jek3zMvcLLaOToUWHBv2nYj1G7tCW6EX9Cw/WQxftQjMGzB4+hB++YpX/wAJOLTi8Xpz/wCh937Er/A/pN8zL3Lf5Mv2pP4JoH5UPQl82P8Aalj4wejW+En+Gx/5Zv2KDo0Jek3zMvcts0QP+1J/DJA/Jh6Evmx/tSx8YPRrfCT+26t73Vn9LH/lm/Ytf4Jek3zEvcudOxL3bpTDIEYumSVdywh0J1b2Ts9o4R+zq3t6s6I+2EnVGwQlZnlXlesxnKEj2q04tY9l6wQXbgKsXvBiT2dWVM0y99pq6WYAs42f/izv1b3Orezq3+Qn8E0D8mHoS+bH7sC3TFLETNkWduq6t7K9hj91nXVvZ1b29W9vVmVw0w9nYurezqze679Wdo1u+OEmJ1b2W7jVoxaD/fF9tXvDhJidWb29W/xlj4wejW+EnvlnORJEjF2kzO0pxg3WUzDmObRrfeGCkcUH6PKTStBdlZN24dGEWJGZmmUY/igUZPhVr7gyU3fiIVaMxxk8d4DRh7Ljuw4dFNmeEmcLQYbbJWAxfo++O3eh2WchN3vUZRgIzv8AsSv8D+k3zMvcts3Z6e7fk0D1ZLyMExGJQmSNOu9gHUtffXtvXezXhywsiO1Os7xHUYsGmakaYTGrT2TNdsjYAoghsib8qax3yUFjjCEIjTuHgeMQii22MW9guli4Yqx7bLJa0rpSN2xD4Oxt0Ma7ux+sWneMR5XQzBCO3Iv0x8XYVX/lExW63SkeU2ekQch3yTGw4twm2NMFwpR1dyhTGUbSgPusKESf46fwTQfyoehL5sfuhssA9nrC9Cc4wWRebdjZOj1g8lV6WqUokpVRzkd3tknMw68CU2FB5hcrmoSKq1XkBgQqtCNOMYwPVgEbzhWm5QQm7uzM7uIcr8pmLaFMJAQV004RgMb0Ys33UzyMB2nVGO28pHqQNX7kXZp3TlZ2qlASMgXO49qvEYKsAb5KhJ3oE60xTsB/7HaWPsj6ZQEOgysEEARdmx5JRoklIAHts5jR30rcBvb7nNDCJ6nZHIogT7goT/xdj4wejW+EnvGn2xykguFgPGVSe4bxTt3rTxlYFBxSdoSeFRpNXFBhs7xgwrwfZbZuzJ0NoxHF2rQYm8s7EWFIZYK1+RNT+UQrLRHCKixDliSSu/lw9hfy5oLO9XpEJAQjsIIY4wdoAjFzH6+9j/yzfsWv8D+k3zMvcu9Ow/u5JowtU1uiidHCV2x/ysE/RspBZHbC5TV6Hcoy2V6lA4mkqo6cSz7NX5u57C/lzWN+ViqFcJYElMYBC+BWisEE5qvRm4YyRAzpnEZXv+s1cykYcR73xj7ondY6URGIEmTLDbAbZL5FmTfCyrV60iFGeVbFikNpWCBi0BlsVhVhuUL2YNTHIxadVhuUVCwQlVnl/jp/BNA/Jh6Evmh+7QeLWbPWMxrIfFWUvgdY38mSx7xYtiErgx8wcjFpYscHm7xE1Ejip/Kh9l2UnIESsVqYBTk1L5USlHdCUVjJxaDhnfLBzAHHIwZ3DOXBxO1naq1aI3mBw464zzjTlOJyhejKArBQkJZEOcIPbdvKVVL4ZLH/ACRliSjcbjlckxTADDJfcIaaTSH1jSg86E4tjywkKMHsSY1sA4F6eQArfyxlTk/CE3+LP8YPRrfCT3rP/YQYVxwpmYFlmYnUB+6jHiQcojHDfVaKFYiOOwrTclsUvZZi8gyZgFgSLRYM+K8hHnPkzhGCtfkTU/lFX/JH7bv5cVzAJyRKGbxBv4u6PJDOPQlSLs03UZsA5d/vY/8ALN+xa/wP6TfMS9y50YD+7arAO8evj6qhVgIWyIAQBBoxKGHI7yOEJm2yEKAR7ISoV5O7oQRhjth2BQLOcE7NJnZxBgCDQgMIgboi9hQjPBoERgjLDZNxjcXbeFCvCTO0BDC8+2eqCx98o48LweLFCM0Nk1Zq1zSbqGmAP3xlXHYhtJDH14OzpxwnB4SbG1lGLRZmj/jp/BNA/Jh6Evmx+7OhXnJ5P40HXqrAQz2M7t1bohBgGLtAlQJJ9yRQiIPZOOOrxfqpChKDwcY4ig0IowRGjtnGgCKGOAYNCKs0a03Z5cKpGMYtKMZxeMvHVlCERxaMZ0ATlvQgjDHaO1WAd49Q1ABfrEtYfchOTt1UYDCGcB0qwT047w1Qg+CY4EG8ZhriBF2gEIwQ2QLjgGfqwq4gM7DIATliREhEkJQkMcRQaEf8VY+MHo1vhJ6TMzfgnZn/AB9vRk7M/wCLMzfh7/Rvbtb8fY7M/v478o37Fr/BL0m+Zl7lzp2JftSfwyQPyYehL5sf+AjCMG6R/wAnY+MHo1vhJ/hsf+Wb9i1/gf0m+Yl7lvp2JftSf3Qkgfkw9CXzQ/2pZ+Ov6IP+MzQf/CO7Mzu9IjQBOX7FB8EvSb5mXuTi0oyiq8+sdj/tKzNujBizdGZvQsQ6NGcYyaTdW/aXViF3N6FwD9WMONgb/dLuiXdEu6Jd0S7ol3RLuiXdEu6Jd0S7ol3RLuiXdEu6Jd0S7ol3RLuiXdEu6Jd0S7ol3RLuiXdEu6Jd0S7ol3RLuiXdEu6Jd0Scwm/F3lZ/4xZmizM37EB8EvSb5iXukBGf3t/5UVutLdaW60t1pbrS3WlutLdaW60t9pbrXRbrS3WlutLdaTPYaK3Wkz2Yx+7daW6yt1pbrSeVpbrS3WlutLdZW60t1pbrS3WlutLdaW60t1pbrS3WlutLdaW60t1pbrS32lutLdaW60t1pbrS3WlutLdaW60mezFlutLdaTytLdaW60t1pbrK3WlutLdaW60t1pbrS3WlutLdaW60t1pbrS3WlutLdaW+2t1pbrS3WlutLpbnFoPAbD9J6zxd3HusrdaW60t1pbrS3WlutLdaW60t1pbrS3WlutLdaW60t1pb7a3WlutLdaW60t1pbrS3WlutLdaW60t1pbrSeVpbrS3WlutLdZW60t1pbrS3WlutLdaW60t1pbrS3WlutLfbW60t1pbrS321utLdaW60t1pbrS3WlutLdaTPYaK3WlutLdaW60t1pbrS3Wkz2Wit1pbrS3WlutLdaW60t1pbrS3WlvtLdaW+2t1lbrSYBjN1m0Wi3RvRlCEvi4tdcWuuLXXFrri1lxa64tdcWuuLXXFrri11xa64tdcWuuLXXFrri11xa64tdPWrri11xa64tdcWuuLXXFrri11xa6lWrR+9cWuuLXXFrp61dmXFrpq4G/D9jV/gl6TfMP8Awpr/AAP6TfMS/hTX+CXpN8zL+FIPgf0m+Zl7kpNFur8gK5AVyArkBXICuQFcgK74VyArkBXICmOCLdFyArkBXJAuQFcgK5AV3wpjgb8OQFd8K74VyArkBXICu+FcgK5AVyArvhXICuQFcgK5AVyArkBXICuQFcgK5AVyArkBXICu+FcgK5AVyArkAXICuQFd8K5AVyArvhXfCuQFcgK5AV3wrkBXICuQFd8K5AV3wrkBXICuQFcgK5AVyArkBXfCuQFcgK5AV3wrkBXICuQFcgK5AVyArvhXfCuQFd8K74V3wrvhXICu+FcgK5AVyArvhXICuQFcgK5AVyArkBXICuQFcgK74VyArkBXICu+FcgK5AVyArkBXICu+Fd8K74VyArvhXfCu+Fd8K5AV3wrkBXICuQFcgK5AVyArkgXICuQFcgK5AVyArkBXfCuQFcgK5AVyArkBXICuQFcgK5AV3wrvhXICuQFd8K74VyQLvhXICu+FckC5AUxgsuQFckC5AVyQLkBXJAuQFcgK5AVyArkBXICuQFcgK5AVyArkBXICuQFMcDfhyArvhXICuQFcgKY4VyQLvhXfCu+FckC5AFyArkBXJAuSBckC5AVyQLkBXICuQFcgK5AVyArkBXICuQFd8K5AVyArkBXICuQFcgK5AVyArvhXICuSBd8K5AV3wrkgXIAuQFcgK5IFyQLkgXICuSBcgK5AVyArkBXICuQFcgK74VyArvhXICuQFcgK5AVyArkBXICuQFd8K5AVyQLkgXICu+FckC5IFyArkBXJAuSBckC5AVyQLkgXICuQFcgK5IFyArkBXfCuSBd8K5AVyArkgXICuQFcgK5AVyQLvhXICuQH3a/wP6TfMy93bBbYLay2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2stsFtgtsFtZbYLbBbYLay2wW2C2wW1ltgtsFtgtsFtgtsFtgtsFtgtsFtgtsFtgtsFtgtsFtgtsFtgtsFtZbYLbBbYLay2wW2C2wW1ltgtsFtgtrLbBbYLbBbYLbBbYLbBbYLbBbYLbBbYLbBbYLbBbYLbBbYLbBbYLay2wW2C2wW1ltgtsFtgtrLbBbYLbBbWW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW1ltgtsFtgtrLbBbYLbBbWW2C2wW2C2stsFtgtsFtgtsFtgtsFtgtsFtgtsFtgtsFtgtsFtgtsFtgtsFtgtrLbBbYLbBbWW2C2wW2C2stsFtgtsFtZbYLbBbYLbBbYLbBbYLbBbYLbBbYLbBbYLbBbYLbBbYLbBdIrbBbWW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2C2wW2Hu1/gl6TfMS/hTX+CXpN+e/8KQP0HL0m+Zl/Cmv8D+k3zMv4U1/gl6TfMy93E5elki3Ags6zw+OtHrGq65wVorBj+Psv3a2Jpku2sZk6uVpwt1/Vq5mkXKHxUFlM9Vwxqdc3sIUQISKYZIFhGY/2DksjWw9Odk0f6g4F1ic5ispB3p+jgs7UzLWGB7LBwVhuQ2+LQcjYTPVs4OxMP+tq/wAEvS+5jy93Rbs2R1QsBCE9Y59n1Xi8ebAXDz0wch8DjiFWqnbJ5LEYMekZ+KyuVwiuWwUKprVgOf1HkYcvHYDUFfOVCTRtS37l01PB4fUhD33xWVzmfhh5grB+0uZxUxTzeRzNXHYyWRm2c1Q4ubCjmqlvDwyqFn9Q5OD2MVgM7DMVJkmsV/8A0TN+zN5Z8bbxAGzecjh2AOE8/n8W8DZfWpr7YcjV9HW8u9SsO1+wdafp64sKIL4mmz6sxQ8LGrn8bay1PHYryM4Z/U54NcBhc5WzlF7A31HlL1w9bB4TOmydkuNv5bUk6V1sbjQanyFC2CtndQ599OmxzPhsnnrdt52dOZrygsm8qWsb15jVqdLU9ytfDj85r6eTag4m09bzRajxyOmcy+WqXZzPqW/bvFpYPEamK9+GKzOez74S3jhufUOoqUXunpZAGQpBuA/1iD4H9JvmZe7ot2bI6oQwZg2r882JNpzPZZ9mYBXhWCIAndmZ3elmL5s/ksyC/l7487jcuf8AqLPrggoMBjAGI7lavWpZItT+nm3wbo1aoQ4izsPmJ66yPAyINb5OoamfwBrmlA4q5G3q/CAaB6dujqfCzlCrU1ZgAcerpzP18uMzusV//RM37NZ/V9OrUen7OVehboz1Hn8G7PnNTTGbS2UKPAfQ8X+wte/p6ysNJmwdFl/UE4xYNqra2EWthNPVVGxr3ZBoabxOXxlvKGvvg9SYG1bPhsHqXmXiY3IaV6EzWpSFPVqHjGNjWkIvlNMwdaGdmhnVoOAmBl5N/UaMGxdQja6/TM1Tfpja7LRb7cJnHh/T3Y2EKVa8ZhxwJh6qhF89pdnzMW8Tei2gv07W/wBZVvgf0m+Zl7uncTexdrNlsYrDXaepcxkTezN17hMTcFS07j3w+IBUWpsX5fEnrMLDmu6ZHjcpUDrjFhagDTuEPiwWS3mwuodPXLE8LQxWasZOOUzOdwNu5cDlsSYOtsuLi2MrhbFvDwpAb7eODgvi9MtQwZMbCmPXeNHwoacwxsO1qxbVrC6irZ+/lcZv/qCs/hr1+3hDiz+Iy1h6d7F3cfqvN12p37mHCfBzxcdPA1FjGahf/YOq8dZymGsVa1UOv6lcFcdDTdw1+GSzmfxFfMY0lUoG17Shw4YLFXcbUNzoUdX4Oxa4OMwmSfLzzOXyuDyVXLSzOEli9SZk9V8tn8Ncv3MGcC03iruJbKQtaXp5mDX7+JjhM3mb9a3m9SYyOYxZ6awDairQLVymmMPcwtO8G2+Ez+At2zYWrhMtksoDI5zUOJt3cxhbYsgGdijZDDTFKzicOGof/WNf4H9JvmZfu7TuJ8EC2Nv9dV/gl6TfMS/hSD4H9JvmZfwpr/A/pN8zL+FNf4Jek3zMv4U1/gl6TfMy/hTX+CXpN8zL+FIPgl6TfMy92nmLNXLXGOztJmdtP2zzu5ZpnsgrQ6lDfp2G6hWXnMeMuThjyknpoBZ4bH3MnShZlMmSwVqsx/ZqK3de84KWPuQv0BGac4Di8ptepHgSIdKnKbDymYl2lVdnsZu4/cxL1iGADYxQ5Cn3nCi3Klbb3wWQWY7gyIMMXmQGVoWH2glKMWeUoZjHEJ24fwJr/BL0m+Zl7uIphvkz1cuBuTrlNirunH6Xs2qQI5zI3rVvN0BYthZKiEndEMizf0m8sX+lQLTmfoY7HQCazZnqG1UDWZujMyKWARTJLT3asRvXbGmCsGdzGzzUJ3sxVxavYKlGpOdTS89mCeaweODkoEyN7LY8dHI47saqjKb42Ma2MoY0cjixxsTdma5leRjqOVqExmqOjZCpyYY7BX3AShqtixoVHjCnpnI1u1WDBwhHB/4EV/gl6TfMy93S/wBQzK1DRlZjC5U0iR7Vm+SdKw2DyV6pczmQDk4ixtKvBhhGNs59KvLEfpeC0mEEsN1nGMYt0ZaoO4qAwDjpHFDDHuHphwGXomBmHljcvUySvZ2jKrOFTS8YTwDjfA5EFAc8fdy+QhfyOPYGpmZi4dEGxBzg+JfG0pFoZMV3CTv161PJZetUsDq2c02JdwTxGSyEsUKsWxmZacLS7lfFMfx9XkfwIr/BL0m+Zl7o69cDkkFNVq15TcBqVWwJmt16VSp1YKMOBIShNghCPswCMIY7BeyyCueY5zR64DxaJZDHODjkHHUa7vMIQhBHYG7ToXXi5noUmiODHDWNt3q3WpXBs9gFWtVi7AKEFgbxOOhQqP1rzgMkXhOOOxYX3h/gSDo0Jek3zMv4U1/gl6TfMy/hTX+B/Sb5mX8Ka/wS9JvmJfwpr/A/pN8xL+FNf4Jek3Tvdf4U1/gf0jR/CbRk0m6t/CYpP/pGLbYs3plHHo8m7xF3yrvlXfKu+Vd8q5BV3yrvlXfKu+Vd8q75V3yrkFXfKu+Vd8q75V3yrvlXfKu+Vd8q75V3yrvlXfKu+Vd8q75VyCrvlXfKu+Vd8q75V3yrvlXIKu+Vd8q75V3yrvlXfKu+Vcgq75V3yrvlXfKu+Vd8q75V3yrvlXfKu+Vd8q75V3yrvlXfKuQVd8q75V3yrvlXfKu+Vd8q5BV3yrvlXfKu+Vd8q75V3yrkFXfKu+Vd8q75V3yrvlXfKu+Vd8q75V3yrvlXfKu+Vd8q75VyCrvlXfKu+Vd8q75V3yrvlXIKu+Vd8q75V3yrvlXfKu+Vcgq75V3yrvlXfKu+Vd8q75V3yrvlXfKu+Vcgq75V3yrkFXIKuQVd8q75V3yrvlXfKu+Vd8q5BV3yrvlXfKu+Vd8q75V3yrkFXfKu+Vd8q5BV3yrvlXfKu+Vd8q75V3yrvlXfKu+Vcgq5BVyCrvlXfKu+Vd8q75V3yrvlXIKu+Vd8q75V3yrvlXfKu+Vcgq75V3yrvlXIKu+Vd8q75V3yrvlXfKu+Vd8q75V3yrvlXIKu+Vcgq75V3yrvlXfKu+Vd8q75V3yrvlXfKuQVd8q75V3yrkFXfKu+Vd8q75V3yrvlXfKu+Vd8q75V3yrvlXfKu+Vd8q75V3yrkFXfKu+Vd8q75V3yrvlXfKu+Vd8q75V3yrvlXfKu+Vd8q75V3yrvlXfKu+Vd8q75V3yrvlXfKmsFXfKu+Vd8q75V3yrvlXIKu+Vd8q75V3yrvlXfKu+Vd8q75V3yrvlXfKu+Vcgq75V3yrvlXfKu+Vd8q75V3yrvlXfKu+VNYKu+Vd8q75V3Jy/GMIw/D3f/xABHEAACAgAEAgcECAYBAgUCBwABAgADBBESkVKSEBMhMDEyUQUgQVMiM0JgYXFygBQjQFBUcLIGNENiY4Kgc9IVRFWBkKOx/9oACAEBAAk/AP8A4crA/kf6dgSPEZ/sIIAAzJ9AIF0/OM9pXQC6nj+3GDpYoZSPiD3+rrTw8MS1MPoYXa5WXWrTmv6iBMJajvx++2SDdjHGGrHDPaNp/VKwlz+Q+6OxVJ2mAvGufESl7BZMO9QoUE6p/wBwaTold6ZP/OLAgfsI83VQgUNaA8wtGgjwCgxqaquCWK1iuxBHDDqtIzSpZgqgkoFFp6M7LyMxUswVQWYbqbX5IM+rrZthMBUBZcidGBrcVSvSbKtWmezq/PMOlmjxdpQj2vozr/VMBUiFHaYYaP5X09nlauU+y0wdQtwvlQE5NMBUlZcB2B6MIthTzNKFqLsQgHR5TrgQ2KgNKvMJUVI4ZfT55kXCgGVfxF45FmCq0ReqvUfVygWdfW8GZRw2X5HOYWukJTqmHFYoseUJb1vFKBrFBs6uYCpBa+mUiwl9MwyVVU09ZMAgrHHKNGIhyAGZMoF5TxeYZEHGkwFbpVbo1RQhsqDtKOvsTkmCqidVfwf78XVXYhVh6gyk30eqTFXoOFpUHTjSOGrZNSsPSPl1tv0fRVmIwlzlfpu7y9LKHGoBDnphJbD60c/ohJsvt7B6eglC33FBreYc24f7Eq0YlMLYhE/yqf8AmOjjnyDPHVAbb4CErasLPlXf8Jhk/ieOVC2og5oZhUprfzKs8BaWSN2CgM/5rPPicQd3MGS1oB0NosTyNKLfwtSYtnHDbKeqvfyQ5WMNCS9a6O17C5l2Dr4XDy3M03+cHsYAzCpaVo11k/DUIM1a1AR6gmYSuqwrMMlZtc65hku0INOqVBK0wxCgT5wnzo5WogtblwrKtF6IShhyZb0nnuyQQ5J2l/0rKEpbjnnRyrfmJ5/4OBjULAbQPErn2w4VCy/pcT2n9W4bIL/vzDm4awpWYQ0tpJBaYSm0DjUGeQoG0cE4yqRtALhWOXl7Z7X+HyZ7Z3pl/XG1bGD+XzCeCXI2xhzBUES4rcBwQ60eizSTDkq4mpifwDCeBEOa9bPkGcc4RPmVz5V3/Celf/MdP/0nnnFgSv8AK+eTDDX04I26qtYfXKurZDkUmDQPxoAGj5iu1grfpM/9B3mJ6klCVOjXPa//APVPbScsfX1WH0avXSMp85Jwicc+Uk/x2nzhPnT5Dz5Lz56/8p9m2f8Ai1ughAAGZJ+Anlsvdh+RM+QsxZouflnVXoOCXOaC4V6mM8CAf99nSjiVWDQc0tSe0bJVbdY5zawwgv5rX4nMqL0WnW44IUcJxrBZbY5A1AZKojamHnb1Yyk2Ya0lyF+xMSNA4lDFYrubGzsuaeRKtErPU6s6rPVZir3HkCrG1WKql/1GfIacc4RONJwXf8IR1tiQPRiKW+IjA3VvPPoLrCcs+0fiJ5726f8AuavLDfhbh2cMxtmgjIgSp6sKDm7mD+W1WjYZCBxofOq9JbWDxhItiYfXqeydi5ZRGGh/oWcQl2sCnNINVlDa5YanI0uCoO4M874Rtc+cJ8+f4zz5T/8A+T56/wDKeR6hKLCFbNLawSJbiTU/nZgVGmMWFNzpn66TPC3ChYLEep867h5TDSmsEF0SVMmGRw5czwA/34gYehGcwNPLEVB6KMunBVMf0ytUX8Bl04Kkn9MRUX8Bl0Vqw9CJSifkJTWx/FQYoAHgAMhMPVyDoUMPQjOU1q3qFA6MPW/5iVqg/CYZ3urJUN9iDN7rO0+gPiZ5K0Cj3KUcfiJg6lPqBAAOhQw9CM5gadXrpgAHoOipH/UM5QifkOjCVE/piLo4cpTUPxCiIrD0IzlSKfUKB0YerkEoFtyLmEmFqGGhLHgCRM7cTcTkPVjPCqsLEVh+IzmCpB/TAAPQfsUvuBd9coyPGfOf6gvTZxJPadvJKc7eNvN/8vq+zeYizeX2by+zeX2bzEWby+zeX2by6zeYizeX2by+zeYizeYizeX2by+zeX2by+zeX2by+zeYizeYizeYizeX2bzEWby+zeX2by+zeX2bzEWbzEWby+zeX2by+zeYizeX2by+zeX2bzEWby+zeX2by+zeYizeYizeX2bzEWbzEWby+zeX2by+zeX2by+zeX2bzEWby+zeYizeX2by+zeX2by+zeX2bzEWbzEWbzEWby+zeX2by6zeYizeX2by+zeX2bzEWby+zeX2by+zeYizeYizeX2bzEWby+zeX2by+zeX2by+zeX2by+zeYizeX2bzEWby+zeX2by+zeX2by+zeYizeYizeYizeX2by+zeXWbzEWby+zeX2by+zeYizeX2by+zeYizeYizeYizeX2by+zeX2by+zeX2by+zeX2by+zeX2by+zeX2bzEWby+zeYizeX2by+zeX2by+zeYizeYizeX2by+zeX2by+zeX2by+zeYizeX2by+zeX2bzEWby+zeYizeX2by+zeX2by+zeX2by+zeX2by+zeX2by+zeX2bzEWE+mcvs3mIs3l9m8xFm8vs3l9m8xFm8vs3l9m8vs3l9m8vs3l9m8vs3l9m8vs3l9m8vs3l9m8vs3mIs3l9m8xFm8vs3l9m8vs3l9m8vs3l9m8vs3l9m8xFm8vs3l9m8xFm8vs3mIs3l9m8xFm8vs3l9m8vs3l9m8vs3l9m8vs3l9m8vs3l9m8vs3l9m8vs3l9m8xFm8vs3mIs3l9m8vs3mIs3mIs3l9m8vs3l9m8vs3mIs3l9m8vs3mIs3l9m8xFm8vs3l9m8vs3l9m8vs3mIs3mIs3mIs3l9m8vs3l9m8xFm8vs3l9m8ut5piLN5iLN5iLN5iLN5fZvL7N5iLN5iLN5fZvL7N5fZvL7N5fZvL7N5fZvMRZvL7N5iLN5fZvL7N5fZvMRZvL7N5iLN5fZvMRZvL7N5fZvL7N5iLN5fZvL7N5fZvMRZvL7N5iLN5fZvL7N5fZvMRZvMRZvL7N5fZvL7N5fZvL7N5fZvL7N5iLN5iLN5iLN5fZvL7N5fZvL7N5fZvMRZvL7N5iLN5fZvL7N5iLN5iLN5fZvL7N5fZvL7N//mcMFB9THEtUk+Az/s7AD1P9AQB/YWAHqe5YHLx9xgBGA7tgSPEdDAE+AjgfmZYpP5++wJHj0EAf0GJre3hVgZYrlGyYKc8j/qKslVOa5GUkJfaQc2iHrEOYOcszfgUZmWAkeZT5uhgqKMyTLm/VoOUYMrDMEdoMfW/Cgl4ZR4jwMs02Gsv+GkRzqbiEJFaDNiBnLvMgYBRmQDLQyHxloX0HiTLf5nCwyJjBVUZlj4CXN+rQdMt118SDOW2ObcRlV0khWbSCB0EnqW02fqjl34UGcuDEeYHsaHK11LAQ5IgzYw5rYupZ4AZx/P5eIxwxXzCWBEj5MeMZA9FhcrwDMSwOvqJZkT4IBmTLM2VkJUjI+YT4UpCdP2mAlru9l2SSwIkYh24+ly7r4hBnHDgS0Bj4IBmxln0+FhkZc6YnsIKRn11opbUuXRfcMIqZFMvtTyPoIjEuKK9QQeojhwZcF9F8SY+VnC4yPQSETxyEJd38ABLAvoJbps4XGkmEBQMyT4CXEjiCHTLA1eWeYl1n8J1WQGmeV1DD8jCRWgzYgR9RI1ZKM44aWgeixiLTxiMFRRmSZeQOIodMddGnVqz7MvWXM3qyoSI+uvLPUIc0YZiPqak5PCTbaMxLtOfgB2sY2Vp4xCQmYGYEsLWnhGYj9p8FAzMsJ0215rPig6LDYRwDMCOHWXaeoID5iWkWcLDIxgqKMyx8BLzyHTPjb0WhAdzGK2NxifOWf5VUYKoUEk+AlzZcWg6YweoKW1L2zyt4GPndV5hCS9pyQAS4IDLsn4XGmMFUDMky8/qCHTDmGQdHA08r3ZGawfwMsNpw3kc9FoUnwXxJlmVnCwyJjhUUZsx8BHb9RUhZ8bZZkeBRmZZmVsTNSIxFYRBGLWPwiWhSfBR4mWfT4WGRMsCVL4sYzegLKQscBAMyx8Mpef1BDpjBlYZgjwMs1OPEKJcrr9qXac/BQM2Msys4XGRPQxF1qfRlQGIFWbsUmrX1uVoPHLPCwpG01ldWt+yOf1FSFjfWKRWUGcud8SS3aR/p75k+yCRMmuvckmDTruFVg4gegZLfb9OVJ1enLTLGIRXdA0Aa24a3eAAXkrYIM00Sr6pNSmeYYRDEB6ysMxgyTzASi20o2ioKuoLMHiK8TW4atuqIn0RibAHlCdXp0x2evMsA0UZ/xb9I+kF1L+Yh/mU1lD+sTstdGtgBtu+kzQaVvfq7J/jPOCfJWcJiDW9jQAC6ohpW9lVNQIRR4kzA4jVp/lnqYCLnIrmBxHlGo9TKba8PYutQ6kQamrt0JEAKOJ8KUgB7J/kGHOrC169ErGa1syn0YCeYDRDkSpAMwjg9YT1wXMGMCbPPMPdba1pCaU1BVmFxCX12glurInGDABnUnQB/23Qi/TqRm/EkQaaw4YCUW2sH01hU1BQJhMQmJqcFH6oidhZAdx0INTVgkwZmlurrBmS3VHWjCdhxbVq5lKaAuWWU+pGTqIoy6jo4IgL2oGcmfRrxSHWszOIC5IBm0wd1ZS5crWXTBqddDleKYVsPmmhgyzLSasqpg3pITQ+aZqZpbDujfHMZmefCM4h/72hg/wCrzTyYYCpIoPUHRWJkttX01YTzWdWTEUZKIAzi4okUB0cT5YmQUgg/lMNdd+hJUahwGL9WRpigOmKQAidi32AvKE6rTlllPsWqOjtvUfQAmCtQrYpFjJpnxtrn+VVCQMRaFeUJ1enLKdtNZzUTwwljuv6Yf+7Liw/qOYnamDSU23JSNNSouqYPELiEINbdURPonEtWrSterC5ZTyoOlC5F3lE9h26//PCP4nEtqYcI6M3xRXygayJg7ail31rLpnYmIxAV5h06vTpn2bYAzi3QkUK4cThqiDyAkzD3WuH016V1BRMLiExNVgKv1ZEr16bBYa+ISg0+A0WLPEKpWYRqMk0MGSMDWlRNZz4jMFiHscZ2MK/EmUW103VkWBkIGcwzX0GvT2DMpNQxNXkBGg9IGfVCf5higuMQyiV62qfWycUwrUZjTk6QBq1w7aPj2ZT/AM3+nlJysM8GUiVWGoOTVYozlLJg8K2vNxlqboGd1D60mDv/AIkDy6IQlmIVs14VMocdUT1diDMESk1YXDD6GoZFjPKKJ8ozx/g1nyV6KDbhMR2kgZ6TMIzMXGt7EyULADfQ4sWYO0YnhCwKLHOYXhWUWZX4nOtwOKVGw32aB0/UX4hb/wD2w5K1ZQSmzJCerdVzBEpavC4byah5jKjYtQKOBMLYECg2OwnyVnCYNLdY08oqlfWpo0WoJgrGxDeAKRVGKKB8hxzDWpdWNJySYYU0H6rMZMRKWfB4nhGeRlNgorKtY7KQSScgBPAVJ0U2AW3ko4ErL16dNyCYa5r7RpJKkBZ9kdsy1ZHLP1mDYXD0TMGUmnD2gCtJhjZhbnLo6jOYQldWdj2LkAIM7PNK7UspRc9Q6KnenqtDaYhycqQIe0Ur0Yc24TEHVmoz0GYNiSc3d1yAHRwwZfyllJtwmI4RmVMpsysI6yxlyCifWYbQa/x0TB2jEKMtAWJotxHgvoJS7UPToJWZgWIGAPYRnOCU2ZBB1ToCQyykpTUhSkGYc2UXBdDAZ6ZhrOoW5dTkSoWOgXWPwmDse510hDXKw+JrBIUzB2G7Tka9ETR1lmpU4RAepxpr1wZ2UWixJ9be5saVGzCYoZvp+yZTZk5HWOy5ACAtpKicIlJbC3HUGUSmwUVupZipE4BPO6ELMHatyLpKBJVoZxFIBKxPDFpBnfhyHEwVxxXgK9P2oMnLjooe2mysBWAz0zDWdStil3KwEnra4CT/ABVU+vw7CxZhLRieDTF03Yh9WnhE8mORan5hFzfDaHX81n1t9hMw/XYe9f5mQzKzBu9xIzZ0yAEI6+oixZhrhiANPVhImh2QE9HwLT5vuYZ3rvINdgXVMPYKEvH0iPEztvosFizC3fxPgKtB80B1myUs+GvbWGQSiwYetwWZlILGD5U+FYmGazC3nVmozyMwhYlgbHtTIASkWjX/ADR8dEwlll7jIA1xBZdWBqUzBWvcV+r0RtBtDnLh1TDWh6vohwuYImG6vCAdhYZEmYUtg3X6LIJhXQU2B3uK6OjhnwqE8TjIMicUZSLBq+mPjpmDtsvcZBdEyNpqKGYZ1vDv+XFMKSrLn1n+nwD7gGfQAe4AHQBn0gdRQhP4l+jD9a/BFCXOuitOBegA9yAOgA5dI90AdIB6QB3IA90AdCjP3E1O6ZKIuTLWAwPQMx0gZ9AGfvADpHuAZ+6Bn7gyHuKM/X3gB0AZ9wAR0gZ+vQPdAHQB0AZ9AB6BBl74A/8Ai2GI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtK32iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaVvtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaK+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtEfaI+0R9pW+0R9oj7RH2iPtEfaI+0R9oj7RH2iPtAw/MQ934nsE7T6/sn27v4L+yniPdcH7KeI91wfsp4j3XB+yniPdcHuhzYh4Zr5ZYHX1HvsFHqTlCCD4Ef1IJuAJ/DpdSw8QP6l1bLxyOc8iCAaXGY7o52UZh/fsXXw5/7S4j3XB7qKwybzCYKnafU2LHCIJh7ynHHDCOErUZkmYS9xxARwQspfwDapQ+sJo19AJWtSxA9BEcF/LV9uUvS3rZ0A228CSp6C3H0I97jgiGu7heOFRRmSZg77F44/YPMvxlbLdTxQEqIpCkkaTKney3ygRwiCYO4rxywFIrE2+BEoe0mwJrHllmkTCXKvGYQUapiOhGZVIBCym0i52IAgZS65hT4iK9zjgiGq3hfpfL0EwV4TjMcOjDMGP+lfiZg7gvGY4ZGHmEJLHwRZhrKhxPGBUjMETN7PlpKrKP1w5gxHus4UlTU2njjBUUZkmYW64cYh/UD0VuwZtICyix+seeBQGBncoSK0/VKmptPH0I178KRDVZ6P02aRMHdo44wZD8ZxvKnL3eU/DorcPT5iY/6U+JmDuVeIz6dQrNhI+IEpt0aNOn7UrZQSQNUDW28CSs0ueODMIpYgeglb5schX9qVlPgA3+zOI91we7Q1uokaVmAsX9UGT2A6FhIorXUwlKBMsguQyn1GIHljEI5BaVqEAyyn1WJ8yfqPR8odHyXiAuXIQxQLKl1CHN0zrl6pZZ8xwXj67kYaXCmeZ8PAacR1hzLLCHuq8GqaDyvqeOlJAAKPMx13mybMTyYn6Bn/AItupp/4b5rO1MMMkhPUoup5SoTLLTkMp9TiB5Z8WMqQBR4gT6jDJ5YoNZGWieQK4Xp42ni2SCKDbaNbGdl9JDFoe1qwT0WZdVxuAkvSzMEaQpM8gsn1OF8EMQFD4iXtYrNqAIy0wa9L6awYoyI7DlmRLmsVAWzIlyjFPYfPMShuC5pG7a80BiGu5n+sKy1LHqOYNbAGAkhlYgcIjLS6p5XGQnmt8SG+j0/OnDEBssBOqKBYtoGqecoJiE/iX88vT+JrYeXMEzxepSejtowo8srU1kZacuyXs4sOoIR5Jxv0+rSrXhlEuRg48pyBmfVr6nOcE8UrJEvX+LsY5lpehxSZFCJ9Z/DuGMQFzYRB/sziPdcHvAa8iwM+uw8JFNq6SYwK5Z559kOdOGEBNakBowZGGYInkw+RY/pPQfBOj5LxwHRyQI2TuulRAdba7Z/OvNpB1Smmu2xxkEABn2aQzGU1P+YAaEhrHGqsGEhX9BMOn0x51l2vDKCZ56HDieSmhOYieS2lgZ58Q+qfUWAq0YFcs88+yHOnDCfM6PqcQscBAM9XwynkIcDp+1cYM28889S6CIc7ryAFnmWsZ9DnqE8qcUwVKBBnrZRPjbDkmI8rRwEUZkk9kpdUU6c2+1CFPWEpGyVREetXUqNUw9RxNdh84mBpRBK9FBBby6ZTXYUOkhlyMtKWl/IGmpVbIEKM+0zDVnWM9aS3XhyG6fEWThnDPnCDOzQGmFpNwGVgIGqYKldRyACCJpTSCq5ZZA9HZVivBjHAQDMsfDKUvprbTrbwM4zPJL0CZZ5zy2KxHNBm7jyso0xBSVUkOI3lfSsIAIyHLPM1cw9RuQkPqAmBpX0AWVgVNh3KDy+InzT/ALN4j3XB7t7s9vinwHRiXV+AAZGICs9oXirhi/qaAMp8Zj7kr4YCWbzOfExQyMMiDMRZoQ5ivo+S8c1Wo76bFmJe8iDsyyymLegt4hJinNreNkOYVNMxVlGrhlzX38Txc0M9o3rXwwEs3mc+Jng6kGWO+tsyWljIVOYInlrUARQUntC8VcMX9T/Eyx0NJzAXoX9LCY656eCWnPRo0ADSB03NVd6pLDYFGRZpiXocnM6JY99o8C/STVavg6zGWX6fBTLXfrGzOqL5fK0x9z18MQKi+Amddq+DrMbbcnCYoVFGQAlhqv4kmNsvHBFARRkFmKegnxCS58RYDmC8UMjeIntC9K+GAs7eZ28T03vS7ebRLWAZAuoeMYstYyBMtdND6s1naAMpiHoc8EufEMOPpXw8rT2he1XDECVrLGLXkkiL2CYmy1B4IZaxNw8uQAEzSxRkGE9oXunDECovlWE12qMg6y9rBqzzaXvTb6pMVZiCPANPoq1ZTs+AIyljMpctmf8AZvEe64P6FtOtSssLgEn9i/Ee64P2U8R7rg/ZTxHuuD9lPEe64P2U8R7rLyfGMsZIyRkjLGSMkZYyRkjJGSMkZIyRkjJGSMkZIyRkjJGSMkZIyRkjLGSMkZIyRljJGSMkZYyRkjLGSMkZIyRkjJGSMkZIyRkjJGSMkZIyRkjJGSMkZYyRkjJGSMsZIyRkjLGSMkZYyRkjJGSMkZIyRljJGSMkZIyRkjJGSMkZIyRkjLGSMkZIyRljJGSMkZYyRkjLGSMkZIyRkjJGSMsZIyRkjJGSMkZIyRkjJGSMkZYyRkjJGSMsZIyRkjLGSMkZYyRkjJGSMkZIyRljJGSMkZIyRkjJGSMkZIyRkjJGSMkZIyRljJGSMkZYyRkjJGSMkZIyRkjJGSMsZIyRkjJGSMkZIyRkjJGSMkZIyRkjJGSMsZIyRkjLGSMkZIyRkjJGSMkZIyRljJGSMkZIyRkjJGSMkZIyRkjJHSMkZIyRkjJGSMkZIyRkjJGSMkZIyRljJGSMkZIyRkjJGSMkZIyRkjJGSMkZI6RljJGSOkZIyRkjJGSMkZIyxkjJGSMsZIyRkjJGSMkZIyRkjJGSMkZIyRkjJHSMsZJxHuuD9lPEe64P2U8R7rg/ZTxHuuD9lPEe64PdtQH9QhBH4dzYoPoSBGU/kc/6FhqHiP2EeOo91we6bD1OMcLk8xdydURrreZDXUHmJBMsV0YZhlmJAfhEvBfhl5rxjgGqX6sUlCNZ0FzSMLxTF4inEVKSn059b5WmIDMOGXrZXMSEZvBR2tL9Tr5gRkZeKUO5mIyt4WlpGItXNFI7DLNClgscGt0Dg/gY5ZEbSZis3/AS4WVn4iWgPQgLywlEOTu40zGR80NCfsI4j3XB7vsp8YHxj6tMwqYEAg2hodIvuSiYeplCafKMzCTQn064gezrylQMAqvpvQRASUpfmlSBzhKvADh6MJbfnQBornsPEVu4067Jb9husf8AVKWxPEVr6yUmmh0DhDMA9+Fv+ptC6wkcDFVgjT5JgjisCtOkjgMIpxNRzQfVNPHC3rqjfyTU+JfbJJ9dhdeFh03X5b2Smt3esFyQCSWhyw2Ko60JwmDWtdKNog0HFXis6ZhqmBrGrNZ5EoAr/YRxHuuD3f8AMn12GIFv6IQbUIurEw1yYtPPTolJquxhGivhSUWHAYi02VXSlzhFsD33lYM7Ai6R+mLYmJw9CB9Y4ej/ABOj62ygqswVteMqAR06rzPKTU9lQOiYLRhj9Taq6gZgzTRRmb7tGiYIv7OKfWqMyHmBc3paHe8V6ABPt0zxroqwifkk/wC2xmLrxRhAYAMn5pMPbTi6AEI0eaUmqo19VhUPDPkJPr6211zDXJjUGg1aCSWlfV3W4XWV5f2EcR7rg7hFLDwOXb0AEehigD0HQBmfdUavXLoUEehGcGQgzEUAegGXuopI8CRn7ijV65fsJ4j3XB+yniPdcH7KeI91we9Tv2SleaVLvKl3lK80pXmlS7yleaUrzSleaVLvKl3lK80pXmlS7yleaUrzSleaVLvKV5pSvNKV5pUu8pXmlS7yleaUrzSleaUrzSleaUrzSleaVLvKV5pSvNKl3lS7yleaUrzSpd5SvNKV5pSvNKV5pUu8pXmlK80qXeUrzSleaUrzSpd5SvNKV5pSvNKl3lK80pXmlK80pXmlK80pXmlK80qXeUrzSleaUrzSleaVLvKl3lK80pXmlS7yleaUrzSleaUrzSpd5SvNKV5pUu8pyH4Q90/0z4KO0z2dij+OiezMVyT2ZiuSezcVyT2ZiuSezMVyT2ZiuSezcVyT2biuSezcVyT2ZiuSezcVyT2ZiuSezcVyT2biuSezcVyT2biuSezMVyT2biuSezcVyT2ZiuSezcVyT2ZiuSezcVyT2biuSezMVyT2ZiuSezcVyT2biuSezMVyT2biuSezMVyT2biuSez8WB+iP9Ie92mVCUrvKV3lK7ypd5Su8pEqXeVLvKV3lK7ypd5Uu8pXeUiVLvKl3lK7yld5Su8qXeUrvKl3lS7ypd5Uu8pXeVLvKl3lSbyld5Uu8qXeVLvEKffviPdcHufCedvD8B90wB2/SHcjO61tKCfTubz2HxP9j+hiFH0XEGVqMVcfiPc+EOZP9kPYfv1xHuuD7sehnp3HglTMP7L4fQO49zjH9l9fv1xHuuD3PUfdThM9O4+Qf7L6V+5xj+y+v364j3XB7nqPd+PhC20YEe8wJHj0eELbQ5/0HgTl/amJZPhCA4lewzlfZ+WU7Dwn3eEz07j5B7pwqjxJmvL10ywMD6d44UschmfHvfSv3OMd2jbSpT2SodpynlbpGZmX5ZwEH+i9fv1xHuuD3PUe76mIDPIrdkq1lfFicgJXoY+WDMk5Aesww0fn2zJiR2CVAkv9Pt6OIQAkqMzPKVzIlZezYCVaQ3gwgJJOQHqZhgE+ORnlyzmGGn8+2L/+xnYCuZigERR4ZsYnWP8AH0Eq0avAiAEaspTlUftHxlesr5jK9D6wfXMf2rzHxjfTHiIxVx4EHKfSQ+s7PiROxl9zhM9O4+Qe6+ow4z0+pMrTRllpyGUdf4Z+0JmcwZUbbn8EEweitjlrU5xQ0wOuniLZFvyEQo6nJhMObrB5jnkFmG6prPKwOYMQMr56vUATCiugjNSTML1nVnJnY5CUdVdpzHbmDKQAtn8rt88qWoj/AM2YmC11qctbNlnKzVcnmTuvSv3OMd22Q0zhgGU8SenzucgYxzgJsB7G/CPorHiZbmw+BGUOWctyPwAGcbUjeDQ5qwjZKoz7v1+/XEe64Pc9R7rZHOYlmA8QJ4Fc854lznPMLBlPDI9HlFhynH0eomJAGXpGL2N4sZWCyjtYzQBrGQWPpZWzWVBlHiyw5BgCDAtiDfKDLxBE8ytpH5NPtJoM+JyWebXkejx1rnOGeOszj/tQzGcOfZGydTOxx4GHsUbz6I9zhM9O4+Qe67DYAyzEmkg5kgZzGNcoQlotbWAfbleG0t6EzLUgRTPAVgDafMiUkm06y+ecSgaHDAqTPmzwAmGTQrZdY5hTXpbLT8BkZ86cMrw/V6fo55xalAQq2g916V+5xr3fDOH3fssc+gfRzyzngSZ6zw1T1nwfsnihzE8XOZ7v1+/XEe64O5yyRu2eBEA0HyzIqe0rMgF8qCHS6doMUDsyLdGkq57ejLMT4AdABD+IM0DS3lzj6XU5iBFWMVIHYYo/VDmSczMurORI/EQjWCJ4/GAEkfSSAKinPSPjMsw4Jz6NLBvFTMhkfKD/AGsZrAyH8IzZiHJxBkB7nCZ6dx8g91ZovTytFTP5kfXe/maOBcBkwPgRClKZjUyntna3ZlnPEKBMtNr5rGUizzIY4prQ6tKHtMA01vm0+IyipZSWJXV2ERgeL8OwjIRgLan1LEUE5jJZptpzOnV2ES76RGQrXyjuvSv3OMd2pI0ynOIMz4iAADpGanxEL/lBpRfAQZrnmD6Qlm+Gc9YSrAkHL4wZIIMw3YZ4L2Du/X79cR7rg9z1H3NrErAnYR4iebLt9zhM9O4+Qf7L6V+5xju3Mcxs+5GeU+J6KwHyyLd56/friPdcHueo+6nCZ6dx8k/2X0r9zjH9l9fv1xHuuD3fiPun6T07gZis5WfpMYFSMwf7G2SqMyYCDe+ag8I9zxHaP7L4L9+uI91we6uaE7RhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhGEYRhBkg8B3IBBGRjCyr5LGey7dxPZls9mWz2ZdPZlu89mWz2ZbvPZl289mWz2XdvPZlu89l3T2ZbvPZl09mWz2ZbPZls9mWz2ZbPZls9mWz2ZbPZl09mWz2ZbPZls9mXT2ZbPZls9mWz2ZbvPZls9mWz2ZZn+JjBagcxSkGQHu7SppS0paUtKWlLSlpS0paUtKWlLSlpS0paUtKWlLSppS0qaUtKWlLSlpS0paUtKWlLSlpS0paUtBpHxzg+/XEe64PeQRBEErEQRBEErEQRBEEQRBEErErEQRBEErEQRBEEQRBEEQRBEEQRBKxEEQRBEErEQRBEErEQRBEErEQRBKxKxEEQRBKxEEQRBKxEEQRBKxEEQRBEErEQRBEErEQRBEEQRBEEQSsRBEErEQf7S4j3XB+yniPdcH7KeI914r+yjxPeHSY6x1jrHWOsdY6x1jrHWOsZY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jLHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsZY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jLHWOsdY6x1jrHWOsdY6x1jrGWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsZY6x1jrHWOsdY6x1jrHWOsdYyx1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jLHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsZY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6xljrHWOsdY6x1jLHWOsdY6x1jrHWOsZY6x1jrHWOsdY6x1jrHWOsdY6x1jrHWOsdY6x1jLHWOsdY6x1jrHWOsdY6x1jLHWOsdY6x1jrHWOscTtP/APNKZntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntA20z2me0DbTPaZ7TPaBtpntM9pntA20DbTPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaBtpntM9pntA20z2me0z2gbaBtpntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9oG2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0DbTPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7TPaZ7QNtM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntM9pntA20z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z2me0z273zHwnaf2T+HxHd/Bf2U/Akd1wfsp4j3XB+yniPdcH7KeI91we9avX8HuWaNbZL+J6X01r4mNqR1DKfUHvCTf+X0f6u3+cfBAI+hMwsxJ5Zejt7lgDv4D+ityst8g6frzt/tjx1HuuD3fCtC0J1HFZzwdc4QABmSYWc8SiWa0N84RGLv6IJZ5Ss+SnQ4VVGZJ8BDY54gscOISbbfDIR3dgcjoGYEtDKPMISLgUaF+uFYhPWOMxwzN7H2lA/jwoOrTGCqBmSYzseICWBxHCVjxJjWckcMh8COhnFt1J0ZCM5ckxgqqMyTDY/qwEsD9AJCDM5QkgHI5iHJKxmxgbJvKSI5d+FBGys4XGRnkrUscvQTMhiQCRCQk8gq6wn0XLON2Wcxg7G8On5yzA0EtSn2BB1T1sPLPNYgJ6SdNAySedV0dDl34EEf6fo4yPQxsYcAlmZHiD2NGAUDMkxnf1YCXBx8fWOErUZljBafxCywOp8CJbp9B4kwupPFDmOjjPTSP4ogEHTHCIozJMa3klwevI+Eaw1U+eHNU/wBp8R7rg936zEPNH1Xr9qeeh55rnlSNxMRmWM+qts8vw1RsrNKgRA99w1EtAEJIV9I8wM+SnRb1dVhDWtLMPpAlobDYgQ5EkylCMsiSMyZ9S6RBq6xJWgbqgcwISBYqAyhAR6CcAluiuw52GW4Y5LLA2GxAyIB4oCcMLD1kFJzXLQcgZa7K7Z9vQoLCoxF1iMQLSNcoTSBl2ifUYn7H6ujwIynlbN6558RbPFEFaRBZfeNRLQCu6ojMrP8AxMKWnGZ8yf4sQO5cgRQq+g6fjcsubMVKCAspZgzAs5E+wgHR2FhoSadVoLPGGknWk8+WlIge+4ayxli03I3mE89zaJUjWsgLsRnmTLkpK+dR9qPoFxzdpfh5ajU3kK6g8U8gtHWAQVeUDQZYxFuwik4c/VSxD6OviIzMEGQLdHEen5aQE0izOyClgRloMtZkc55HwErUAgZgCIq/kMv9p8R7rg92wpTQNIYDhntLEywvTeuWowfU2w5q4zBjAlHBaeI0PCNSIEhGu1xPkL0JnhnAR5hllajEV9qicR6OCcYnh1Sz/wAvRwCLnh2yV5hwQRmMjFVcSh1KI6Z3eCMJnU54JZrFLZK3R8ppxT6oEAxgVYAgj0MyPUEFukeVwjz6nCIFSDtBDxgXrQIZ57TGAsGEeMNaMYw1l5/izjPTwz5yzB1FjSpMpSseqgDpb8XntHES57R1g1ap+FohzatBXPtnIACAkVWB4wIZBCTbb5VAik0g6HlGoMMxkYAt3mQRwBcdIBGYM/lN61y42VVeUwJZZWMyDLXpesT6zo4j08Cx0zu7FVhmJnU//kl3WrR5X/2rxHuuD3G0sVIVss8jLute1s2OnT0XdTZU3YQuqAWKVAM9rWVU8ExZFyWmyyxl1GydoyyMxz4bV4pMY9uJsAHXMPLH1dUgXV0Lms9sW10cMZrb281ry3q+oPlC559F/wAPq9M7UYds9pO9A8KjL8upHk09F/igHVwArPa1qU8Mdrr28bGmaOnlYT2zb1UB9WY+JPQuYYEEfgZj7K6deo1QZqZ7Ytrp4YTZe/ndun0+j+qDJ7zrMGYIyImPNGrzIJi3xVi+XV0Y6zD6vsiYpzczBmucajLNINHVBpbr0knVp09OK8q5BJZ1WTBtcbMVoFz9cvcxHWtZ5V0AaehtGfg0tFoQEasssxPaDYYt5kWYpsTaPLqiBkYEEGe0rKFb7EvbE4njaDUreInta2qnhlr34k+NrzMEHMET23Z1UzZ387t4mYh8NieJZ7Vstr4IoVFGSjoxXUdTPbmIntO29dBGhhMT9gLo0wlSnlIntmzqZmWbzufExijp5WExz36yCC3+0+I91wf2n2g99QcsK4MgBkB/v/iPdcH7KeI91wfsp4j3XB+yniPdcH7KeI91wxFiLEWIsRYixFiLEWIsRYixF3iLEWIsRYixFiLEWIsRYixViLvEWIsRYqxFiLEWIsRYixFiLvEWIsRYixFiLEWIu8RYixFiLEWIsRYixFiLEWKsRd4ixFiLFWIsRYixFiLEWIsRd4ixFiLEWIsRYixF3iLEWIsRYixFiLEWIsRYixFiLvEWIsRYqxFiLFWIsRYi7xFiLvEWIsVYixFiLEWIu8RYixFiLvEWIsRYixFiLEWIsRYixFiLEWIsRYixFiLEWIsRd4ixFiLEWIsRYiytd4ixFiLEXeIsRYixF3iLEWIsRYixFiLEWIsRYixFirEWIsRYixFiLEWIsRYixFla7xFiLEWIu8RYixFiLvEWIsRYixFiLEWIsRYixFiLFWIsRYixFirEWIsRYqxFiLK13iLEWIsRd4ixFiLEXeIsRYixFiLEWIsRYixFiLEWVrvEWIsRZWu8VYixFiLEWIsRZWu8RYixFiLvEWIsRYi7xFiLEWIsRYqxFiLEWKsRYiytd4ixFiLEWKsRYixF3iLEWIsRYixFiLEWIsRYixFiLEWIsRYixViLEWIsVYiziPdcH7KeI91wfsp4j3XB+yniPdcH7KeI91wfsp4j3XB7ulTX9dfwz2mb24XSDRfUcnSOua+b8JYjfpOcfTneBHAJHYM+09Dq2RyORz6LNYS3TXPaLUpTkQNIMxoxNQ86ETyugPRciegYgQ5gxgAPiTkJeNSjsZSDDmzVgky5Fc+AJAMsHU9Rq0Rxqyzyz7ZarlfEKQcpciE8RAhjBR6k5R1ZfVTnCAJaj5eOk59Dqufhmcs+h1JXxAPaJaiZ8RAhBB8CI41H4Z9sIAHxMIIPgR0XImfEQIQQfAwgAeJMuXUqEhlIMfNjVm0tRSfAEgEx8qjRqKdGg2BhkrGOhzRSQD6x1QficowYeoOfQ6gt4AnLPodSR4gHOXImfhqIEOYMddWWeWfbGAHqTlHUL6k9ktQO3lUsMz0WkV2fWS81G23SSJ7czb8UiacRScn6DkqqSTCf4fFa+pHQ6qPVjlGDD1Bz6LUfLxCkHovQPwlh0WKi+rHKMCD4ERgo9Sco6sPUHPofQbLwD0XdU7Kcm9JiOvYDtslyKfgCwEIAHxM6oLU2SHPocAnwBMIAHiTGBB+Ilg1AdoB7RMd/EHU7B+GOHcdZk0s12FyOi0GuqxQglivl46Tn0XoH4dQz6WCj1JyEsV/0nOW6C+JRGliKT8CQCei0v1V+msGOqj1JyEcMPwOfQU1quahjHQWPWGKgxgq+pOQlisPVTn03IHPgpYA9Dqi+pOUcMPUHMRwC3gCfGWp1nDmM4Zcmj1LDKMCD8RDkJaj5cJB6LUVj4KSAYQB6mdV1dRGhs+hl1pWzIGPiQI6CxlzKAxgo9SchLFceoOfQ4UepOXQ66uHOWKi+pOUYMp8CDnHAJ8AT4w5COpXiz7JdWqt4EsAD03pr4dQzhyZamIMs1NozJMIIPgRHGoeIz7YwUepOUdWHqpzmgmy3S+Z8ojqw/A5y1Ez8AxAhzBjrmBmRnLFfI5HSQf8AQHEe64Pd873sW6PIwBIjt1GoGxAfOZqqBuFdiAkghp8cSpmYswrAz7Vea/m089v81/zbo/yJcKwXAE1X3XDQAonmRADDkSOwzFLdexJLO+mXa8KB1lcc/wAJhDp0ephKI2QsTiE84pASarr7lDuxPFLS6JTlWTwRzWHw+T6eGV6fozFpbiLhqOq3yzEddhmr11fS1aZYRSoBoUkhZisqiuVlanUrS0rgiu7zFLU9bAtoJcMOjz1HrBPk6jO224vcZnbZaSVGZAAhzweKOQThM/xnnBPkpDkxHYZi0uvZsyzvpl2vChdaRyMJhfMnEZqrXTpsUEkMs+sFI0fmxmd19w1kk8UtLolX8sng6KwbwVXVK9LNSr2HMnwGczs1uRVXmQABP+0vfQ9fR9bhnFonyNcX+dbnZzQG+60aiSxjlsNdWXqHCRPH+D6P/She3F5I+ssek5Bb5iw36QZWUW/IVqej6zEuKxMuswRRhBkj16zCXTWVqqzIAAlwGGb62okmWlKETXd+MPVYpfI4McJfaRXr4fUzGobyO2w2y7rf4d8ks4lj/wA9bSER2KqFmLzwtnhV6GWEUIAaVJIWYoCojKytTqDdHxxInoJ8lp9aKWIlmvFP9brsIbVLv4iphkM+GUjTTYNA1Ho8+GuFiwHViyiiebCuQZ58TceWfPtnFdPmt0Owwwu1WoD5yCdM/lPWRmIdFmJIAbhmNRrz42db9qW9b/DuFW3iXoxf8gL9CknQJcAxsC2orEgrPN/FLC1uIbzuWOZMfWaPI0+OKmKUVJYUppL6RMUDVbYEtpD6gehM3prOgykLdZQA7Ri2GpsNdVQJAh0JrAsTxBngyg7z6xyESBrb3GbuWMuFlKkmr1CiEvXrKVV5kAQlcPbYEtqj6HLtA4vVCws1HPMRtTaGUmZuikrWkfPD2VdYqTEinDVKDZ26dZmLSq+oZjTb5ocrrEAWa7b7Bqdyxji6huyoEnMLKc6qD9ATwAyErzeql2UyoC4p5oxOFofQlYJEJrCOBYnEDPBwGEHaF1L+oQ/TrTS0+2zBCeFZnZrcipMyAoEY/wAJiH0OnmE4p8oz/Guha06cqwSQEAPQ+izE2adUYi4L5xxR1suWlxr4hPkT5c+UI5GGVAaASQkxQWrLKytTqVpUNV9+Vkq0qql9OZOZmKrsxF2ZINvlmIFuFtXUg1atBjslJrGscUQohOZGZP8AoDiPdcHujLDXuXqeYlG9ApiFWvOSKeHo/wAtJ/kpO0MuUz/k4lus/QnT/kRFMrUdH1grOmFLL/Gw2GKBT/D5JPo14o663McWO/Dwzz6EYS4I9SBGVuITMVdUQh9QJ8mecpKKRiEGlw8wi6qh9aolAD1eDWRyyO2VyA5qBKfOuYLj6Mt04hrQOrRswRPHIZzwIIni+LUJ+hp8nQghFd1H0CrzJq8KddjzMUmt6y0tFjvwz5CT6wVnTCll/i5tMQLV/D5JDpqxY1K5jh3sHgsGbdUGH5rL0Rqk0OGn1QqyQ9Nivqw+l8vEahHFV1Lnzw6xVZrsceA6BmrqVP5GZ6kxhDfoEGZCdg/THSp6xlYDPqMLWV1zJaradOqWCwkjPRP/AEZ49WvSAQcRMLWD0kijDVzE2zz0O6f+1o4rtrsOWqJ12YzdlPYsGVF9egvLFtc+REOZaU/VOHeuVVCYTqwDp1ZZapSq21sVIsyVpc9mE0Zt6K0o0tX5TZLdSu38+sHNQvR/kiYureWq6dQ4zEQ2aEY6R+qaUYjMkEKRHZ6UtIqZpYEZ3BXV0eFiaTOz+BD6p/8AnqQJ/wCHWJ8+2cV0+a3R9Xbdlq+GoRxbdeQqhImsYYqWlVA2BEwgqAbRqyyBy6LfI2VSMclyldYIvUuUE8wxKy5EGnNwxi5V2HSk/wAmU19aLS1bOPFTMGllz8AB09AzJrMtUulIzWNozuL1M3gQYwssttBOn4AT7KgbCfWVEWS5E4wTkQZWVqBKK/FHFbV2ExtZ60PaeET5zz5Zn/qdHwwkrDYa+vzHwDTD0WeipkSZTo0hXSqXojKgDKewgiUkVI2SWcccILSChPR4mhwNpaOuRSNMOgW2a62MYW23Ovln2K1XYdA/7plNX/unh1OmZV30sRk0OtabOstYT6upvpSwW221kKqz/HunB0LqWh/py9NGnOUGrVSZ9qgy8VmnsYNAQpo+hMOA9Y7DbLiQ5/moDmAJ5ExALSwWF0IGUoqGIrGVgeYQE1DPrQJwCPpBOWcOYIzB+/8AxHuuD3alsHCZhU1dFSrZZ5iJWHCtqUH1EqD6Dms1BCchkJUU646ageHpqCazm34mUqrWedh4t7mDrLSoB2UKzfgJUtkwlYV/MIoCqMgPQTCVl5UAyJo1+glQNwXLX0YdXaVKieglCOwlKpKlslCK3F0EhQQCQM5TlUiaKsxkT0UIz8UqVB6CVLYvoZhqwj+YRQqIMlHoB0YSvVKgHKBC/qBK1slCaX7CIoVFGQAmErLSpQ6JoDeg6ACpGRBlKoz+YiYdHaVqmfQSFzAlWSkaKQejCoz+srVKx8BKlsX8ZhKxW/mWUq1fCYMgAAB+A6KVNlfkY+MqVyhzT3KRWHObkfaPRWBYwyd/iZSjmUop9BKldT8DMKgfigzBmFrJiBVHgBKELcUqVPwEoV2EoRPUjopWzScwDMBVKglTfYErC1qCAvwAMwdeqIFUeAEw4d+I9NQRrPOR4tKVdq/L0UrXWSSQJSqpwiVhE9B0Yes9cc7OzzShFbigzBmCr1RAqjwA6KVciYWsKh1AehlQZa2DLn8CJhULxQqL4ASsJ1jamy+JlK2EShVboGYlCrYwILCUrYZQqHpwdZYxAiDwAlCuwlSrKVZ6jmhPwgzBBBH4GUqtfAJWErHgolQ60LpDytXT0MwqBujCVl4gRR4KJQjsIMgBkOjDotvEJUtkw6IeLoOSIM2MqIwuHXsYjLUeihXPFKVQHxyiK6HxBmFrGqUqKSpXR8MjECIvlUeA6ACD4gzC1looNZXSV+GUQLWoyCiYSsvKgLCukvKEcylVJiB1PwMrCAnPITDIzcUqVF/CVKLmGRb4mVLZkcwIMgPv/wAR7rg7tQ35jP8AqVBHoRBkP6VQw9CM+7cKPUmYynnExtPOJjKOcTGU84mNp5xMbTziY2jnExtHOJjaecTG084mNp5xMbRziYyjnExtPOJjaOcTG0c4mNo5xMbTziY2jnExtHOJjaecTGU84mNo5xMbRziY2nnExtPOJjaOcTG0c4mNp5xMbTziY2nnExtHOJjaecTGU84mMp5xMbRziY2nnExlPOJjaecTG0c4mNp5xMbTziY2nnExtPOJjaOcTG084mNp5xMbRziY2jnExlPOJjaecTG0c4mNo5xMbTziYynnExlPOJjaOcTG084mNp5xMbRziY2jnExtPOJjaecTG084mMo5xMZRziY2nnExtPOJjaecTG0c4mMp5xMbTziY2jnExtPOJjaecTG084mMo5xMXQPycTG084mNp5xMbTziY2jnExlPOJjaecTGUc4mMo5xMZRziYynnExtHOJjaOcTGU84mKqJ9A4/0RxHuuD7ieCCMTwoPATC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chmFu5DMLdyGYW7kMwt3IZhbuQzC3chlFqj1KECOWpcgAcJ/0PxHuuD7iccA8Ggg9we4P6Ee4PcHuD3B7g70e4PcHuD3B7g7xQQQQZ4ien+huI91wfcT5gnA/3c9D0eg/0NxHuuD3sRWp9CwExdPMJi6ecTF08wmLp5hMXTzCYunnExdPOJi6eYTF08wmLp5xMXTziYunmExdPMJi6ecTF084mLp5xMXTzCYunnExdPMJi6ecTF08wmLp5xMXTzCYunnExdPMJi6eYTF08wmLp5xMXTzCYunnExdPOJi6ecTF08wmLp5hMXTziYunmExdPMJi6eYTF084mLp5xMXTzCYunmExdPOJi6ecTF084mLp5hMXTziYunnExdPOJi6eYTF084mLp5hMXTziYunmExdPOJi6eYTF084mLp5hMXTzCYunmExdPOJcj5WL5WBnA/vOqj1JymMp5xMZTziYynnExlPOJjKecTGU84mMp5xMZTziYynnExlPOJjKecTGU84mMp5xMZTziYynnExlPOJjKecTGU84mMp5xMZTziYynnExlPOJjKecTGU84mMp5xMZTziYynnExlPOJjKecTGU84mMp5xMZTziYynnExlPOJjKecTGU84mMp5xMZTziYynnExlPOJjKecTGU84mMp5xMZTziYynnExlPOJjKecTGU84mMp5xMZTziYynnExlPOJjKecTGU84mMp5xMZTziYynnExlPOJjKecTGU84mMp5xMZTziYynnExlPOJjKecS+tz8AGB/pPQ9HoP9DcR7rg92xqsIpy1L5rDMMh/UNUwlPIJhKeQTCU8gmEp5BMJTyCYSnkEwlPIJhKeQTCU8gmEp5BMJTyCYSnkEwlXIJhKeQTCU8gmEp5BMJTyCYSnkEwlPIJhKeQTCU8gmEp5BMJTyCYSnkEwlPIJhKeQTCU8gmEp5BMJTyCYSnkEwlPIJhKeQTCU8gmDp5BMJTyCYSnkEwlPIJhKeQTCU8gmEp5BMJTyCYSnkEwlPIJhKeQTCU8gmEq5BMJTyCYSnkEwlPIJhKeQTCU8gmEp5BMJTyCYSnkEwlPIJhKeQTCU8gmEp5BMJTyCYSnkEwlPIJQiNrAzAhAzU+6uu+06UWOcRb+PlH5CYOnkEwtHIJhKOQTCUcgmFo5BMLRyCYOnkEwdPIJg6eQTC0cgmDp5BMJRyCYOnkEwlHIJg6eQTCUcgmDp5BMLRyCYSnkEwtHIJg6eQTCUcgmEo5BMHTyCYOnkEwdPIJhKOQTCUcgmEo5BMHTyCYWjkEwlPIJhKOQTB08gmFo5BMJRyCYSjkEwtHIJhaOQTC0cgmDp5BMHTyCYSjkEwdPIJhKOQTB08gmEo5BMHTyCYSjkEwdPIJhaOQTCUcgmFo5BMHTyCYSnkEwtHIJg6eQTB08gmDp5BMLRyCYSjkEwlHIJg6eQTC0cgmFo5BMOiH1QaZYbcPZ2V2HxB9D/S+g/0NxHuuD3OGeCqB/f/AJghyKIzg/p7Z9pAfc/8BERR+r7ifBCw/NZ4sgJ/pPQf6G4j3XB7nqn/ACH3A+YJ/j2T5Se5xVfcTgacH9F6Ho9B/obiPdcHueqf8h3FqsyeYDpsC6jkM+lwWQ5MB0WqLG8F6GCqPEmOGX4Ee7iU1SwMPUHvrB1hGYWXKhPqZiq+Ye5ar6fNl02BS5yUH49LhtJyOXe/ME/x7J8pPc4q/fsVFJ8TDmpGYMcKXOlc/iZYqmw5ICfHubl63g77sEcsx9Ae6sCL6mMCp8CO5sCBjkCe++W84P6L0PR6D/Q3Ee64Pc9U/wCQ9/xC9kJIxAIfope+weIQRGrsS4aq3GRhyUCYO9afmFYcwbcwejzJSH5STPtCdrPmzD8FiltJc5CAgGISzLqJ+A6G0vce0+iyhXPqwzMUmmw5Wpn2CISbc8jBmEUmIQAhbKYWy3sBbSPLFP1euYaypVGYLjLOYW28L4somYZfMG8REbUgGWX2iZhraCxyUuJU5DoRrXwWYewAJ9YR2Ho+VFBUzDoP0jIw6hRYVU9ByYjSv5meXEVjm6KLb2XzFB2CKyst2TowyIjZAbkzCXV1HwsKz57Sl2sDlQgGZMQ1eqnxEwd4p4yIwKEZgzC23hTkWUQEMnmVhkRKbL7B4hBK2qtXxRxkfd+YJ/j2T5Se5xV++30a0LvPPQxQz/IE+bGyWYHELVxkQdagGf0Z5WXOIV0RCXYZk/AdByCgkmZ5nE+H/lngREe25vCtBmZRZQ7eAcRshMFelR8H0xwUIzzmDuuRfF1WE/iCMiJU913AglL028DiBntfy1oM2MwttBfsXWOiixxZW4LqOxZg7B2Em2VObK30AAeaVtQATmGmAxBq49MbMGUWPYthQIozJyiPVYnijCYS2lWOSs46T6u889DkRSQ7hJhL3p+aB9GAuoUnITwMrJYrqJ+AiFHpOkkxSS8UsAQMhBp1XIcjMHe9I8bQsPYZTZfaPFUEreq3gcQEB1J1TCW1IACruMtUw1uIK+YoIGSxPGthkR73y3nB/Reh6PQf6G4j3XB7nqn/ACHv5katTgSi1WQgoSuQGU7W0HP8xO13c6jANRsAnke0aoo05ZZTwWzIdAzBoIh8hLoTwmeUK1dc4mhyHnSfbfQn6R0eBBA6HCljkufxM9GnBPkvB4gmAEdVPjkJ7NZl0+YNMG1KOmTQeCDLYT7Niz5U4Oj5UUNYM9IMqqr/ABj6nc6nb1PQCyVnW4WV2pZUwZWKzx6lmH5gQDU5JYwAMzjOeTMkiAFSMjPmmDt60iJrXVno4jPZDFSMiNQiMmbNpB9DMJ11WokOkQJafOMsjMP19Vraj6iUGvEAeDLk3u/ME/x7J8pPc4qveOQUEmV2u9rfZGYAgdKsRxDL6U/yBPmztr8SIoKkZZTM1i1lH5GfWUu1fMZ2C+nS/wCqeAOhOjzXHTKL8ur056J5qSRMi62aRKwTWc1PoZ21eaAEMMtMGlcwNzPZLFAgyIYCYRqa7UlIvrt8w+ImHNWIXy6xEFpRNJQzBmp9X0dYzGfR8ozggzKv2H8yZ5Ht+lFAUDICfVhgQJ49eRPisAzDLPiiw5ACV2OXOSaVzyEV0S8BWDDKfG9Yo06csp5RYwE8FJdJ9t9CfpE8mJqO6zxrtHVn8EnhZlY8+N6iKNOnLKfYseYFr2dyWs1T2c9TVWDNswcxBmMiZ4hDlPZjOD2lw2WqYF6RpIf3uBpwf0Xoej0H+huI91we56p/yHv1FR5aw0USsirXqrb4EGYdrsO7Zrp8RMOyItgC1+J/Mw5W1EMhmAcW+BY+WKe1xk3F0VnqxVlqisXyKPp9DB9LQc/xJiFW1GHJ2Br3nwXt6DpsQ5qZgGsbjWKEFf1dcGdlRmENS6fpE+LfgJWRZ1TDRFKsq9oMrJrWogtPBhMG1qr2I6QdXWRklUT+WUChohdtanITsZk0zCsoQeforPVivLVKyUQ/SPuVFWdslDekUZMMjKzoViFJ8GVph3upzJQrKCqiwZJwgHxM+upOazBPXYewu3lWIVJsMrK6riRPrqjmk9mv13r9mWg3EHt9Jg3ubM5OsQVm0ZBJh3xFTNmhXxAlHUoi5Kp8T7vzBP8AHsnyk9ziq96ss1hCmKOxREzupcMspbNbVZxwyssEszciEdfUcxPZ1i3EZaj5RDnY5LOfxMrPUWWI7+maxSbK3BAEXJ2zZ+ioiihc1J8GMUSonD3DtPwBlJtpt7bEEpfDUK2bk+LT6+k5iez7EtIyLnwEtL2Fe1z6zAPboGlXSfQRvJVwylr8O3lC+KzDGiqk5jiaIbqdORrEwbUVq2pnfoBLGsgCKVcJkQYhVXfNTDlbW2pJ7OsN3F9mHO+5tTysqGvJWVk1qhBaIXYsvYJ4hFB2lZd3+jkIo1BfpRCba2DLKCX61C69FZQm0w5M4NRn2VgJtrcEAQZF0OsH1aIwFa6K85WXIuUkDorK67iQDMOb8M7agB4rKTh8OnmDeLSsmtFbU08HUgzCNfUCdDJE6mgDIVHxJ97gacH9F6Ho9B/obiPdcHueqf8AIf0jppqOaon9s+YJ/j2T5Se5xVf3DxyjppqOaIn9H8p5wf0Xoej0H+huI91we56p/wAh9wPmCf49k+UnucVf3E+W8+WP6L0PR6D/AEN46j3XB7nm05w+ZR/f/mCDMuhQD9XZPsoBsPcIAxCqyn9P3E8XGhR6lp4qg/ovQ9HoP9DcR7rg91DZh3ObVjxUy3QRxTFJMUkxSTEpMSkxKTEpMSkxKTEpMUkxKTEpMSkxKTEpMSkxKTFJMSkxKTEpMUkxKTEpMSkxKTEpMUkxSTFJMSkxKTEpMUkxKTEpMSkxKTEpMSkxKTEpMUkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTFJLA7a1M4T7raLq/I0qap+PLNTMVXMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTFVzEpMSkxKTFVzEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKTEpMSkxKSw2n0QZmJorTtqp/o/Q9HoP9DcR7rg96pGP4gGYevlEw9fKJh6+USis/8AtEw9fKJh6+UTD18omHr5RMPXyiYevlEw9fKJh6+UTD18olFfKJh6+UTD18omHr5RMPXyiYevlEw9fKJh6+USivlEw9fKJh6+UTD18omHr5RMPXyiYevlEw9fKJh6+UTD18omHr5RMPXyiYevlEw9fKJh6+UTD18omHr5RMPXyiYevlEw9fKJh6+USivlEw9fKJh6+UTD18olFfKJh6+UTD18omHr5RMPXyiYevlEw9fKJh6+UTD18omHr5RMPXyiYevlEw9fKJh6+UTD18olar/MWcD+8oImHq5RMNVyCYarkEw1XIJhquQTDVcgmHq5BMNVyCYarkEw1XIJhquQTDVcgmGq5BMNVyCYarkEw1XIJh6eQTDVcgmGq5BMNVyCYarkEw1XIJhquQTDV8omGq5BMNVyCYarkEw1XIJhquQTDVcgmGq5BMNVyCYarkEw1XIJhquQTD1cgmGq5BMNVyCYarkEw1XIJhquQTDVcgmGq5BMPVyCYarkEw1XIJhquQTDVcgmGq5BMNVyCYarkEw1XIJhquQTDVcgmGq5BMNVyCYarkEw1XIJhquQTDVcgmGq5BMPVyCYarkEw1XIJhquQSpF/IAf0noej0H+huI91wfcT5gnA/3c9D0eg/0NxHuuD7ifME4H+7noej0H+huI91wfcT5gnA33c9D0eg/0NxHuuD7icc4G+7noej0H+huI91we7SKxhbtE9jpijhzxz/p58PT81Trjhq3UMrDwIPQms5hUSIEfh6aQEpqD6+ioO1SEhIApuqD5f3P7Qmqu1DMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZizyrMWeVZiiVPiMgIpFVbAu3+h+I91we7/mP0OmuyohBPOKuj6rDDr7p2U4oC+mLqKjJF4mntsYd3GtaAmaiVCu8YfRYBxSwUsgzvv4Zjv4/CWjQ/rXDosrwIZTPaZwlA3tjqLggN9/BPaf8A+IYZCBcjiDXqRCn/AL57f/mFNQpCDQZhwmIqqLOgnt4UJYCUqrSUhL0bSH4xxf2ylXmGPO0w55zMOeczDnnMw55zMMedphzzmYc85mHPOZhzzmYc85mHPOZQeczDnnMw55zMOeczDnnMw55zMOeczDnnMw55zMOeczDnnMw55zMOecyg85mGPO0TQ6SnN3qUsdZg0WVukweuy5K+qTWfPYJja0tI+q6uYTRjMM+i4B5iK8Hh8NaatP1jmVLfSAGqxKnTESlcN9fiCS8y9o4Ww6X0IUZOWUA9ddkycUvSxGT6lFI0n9UTX1OLZE/TCtIpubXiSNX/ALQIUxWCvITrANBQzEaETQpqmJ/iRYilQF0aIuo1Yh0H6YyYPBUEoHILmwzTiMNizoqv8hV5QHF5YGvVMfU5QazTomFKmxOMyg85mHPOZhzzmYc85mHPOZhzzmYc85mHPOZhjztMMedphzzmYc85lB5zMMedphzzmYc85mHPOZhzzmYc85mHPOZhzzmYY87TDnnMw55zMMedphzzmYc85mHPOZQeczDnnMw55zMOeczDnnMw55zMOeczDnnMw55zMOeczDnnMw55zMOeczD7sYgVR8AP9D8R7rg93/MMutQI2YKQ23uPmtBkBDkiKWY/gBL6qv4i0+eX02jD2j6uHNOtqthBWxAwIhBXqp57yHrh84Cif/p0+Nc9oW4N7RqQp9uf9T4nqD59RmIDYXqgmt57ccUohIqs8sCIBml/DPbr1VEZonikA/iMLcam+5PyFhD4rE3AaY5CUX1o5n/U+MKkcU9qHFvZ9b+oT2s+Gu15WdVxS4Xk1a0un1xuly1a20pnP8zo/wAx55/4otPrrcSuicVM+NQn+RbOy2m9w4n17Y1DPmPPktOI/wCsuI91we7iC5xdxs/TL2JxPinTeaesGRcQkpUoGrinktXKWHEIKyn057fxFeE+TLWyekV6JiHoxVfktSe2LcU+gqh4ZadP8P1WuWl1qXIuZY1GIq8liT2xfiqk8KYgWvIZKv2Z/wBR4g4T5cqzp05ZT/qG+vC/LgJBOp3bzO33ItNYf7Qn/UWO0Qvdf8b7TKw9bjIrP+oL68NwTMlvMx8TPa9uD63zqkusxOMsGRusmJfDYxBkLq57StxtlXkDy8ocJcLAvF0XtZ/EXG05jTMXZhcVTi3AtrmPsxuITyF59XYMjPaVt9BAFaP9iXF+ttNk9o2YK9/Oa5i7MZi/mvLihwhJAhy1oVlxsCZnWf8AWXEe64PvcCOsfW+Z/wBd8R7rg/ZTxHuuD9lPEe64Pd+HiY7GM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8d94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z95awgyPdHQkZi35xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvGfeM+8Z94z7xn3jPvC28Z94z7xn3jPvC28cn1E7CPEdyM2PgJYfyEZ+aM/NGfmjPzRn5oz80Z+aM/NGfmjPzRn5oz80Z+aM/NGfmjPzRn5oz80Z+aM/NGfmjPzRn5oz80Z+aM/NGfmjPzRn5oz80Z+aM/NGfmjPzR3B/OfSQ+DfcbiPdcH3Y8V7RPiO48PE/dT7XYe5+B0j+y+Bh8jFdvuLxHuuD7scJnp3HAfupx9z8xv7N85/uLxHuuD3PUfdThM9O44D91OPufmN/ZvnP9xeI91wdyRmxl6xhqh6FB19B9z4AwgEk9ssUBTAChPiOnztnsJ8R0GeK2ZDoIz1Ae8eg/3DhM9O44D7tgAUy9cvcGko2XQeg9J9w+Z8j0H3yM44ZvXpAJJyhjhmz8f7hx9z8xvfP0UIE+MOQjduU9IwzhzBB6Dk0OZy7YwEYHp4BGbMj1jEq3r0cXR4ZQ5iOIez1j/Rz+j7/wA9/uLxHuuD3PUe6CciZTbtFIzU+MsbSCQoBjlkK6hC38zMntnw9ZcxZhn2Hwh1gDUpMsKoD25QnKcJnEY6g6/iYdRLfCfAAdHkQaRPAHUIcmsOWcufXl5iZxxyKkOQAjkozDx+BE7CXEdi/j+AlpWpTkAD4ywlCcmUxtKu2TN6CXNq+BJzBhzIyEvcuR5tUYEr8R/b+Ez07jgPuozZt8BK7BmcgSJ465a/WZZ55w9oJByjEaH7ADG06hmzfhLmDr6nMGdhKGWN+AB6LAmfm9cpcwcfEmeJXodgmeSqDHLVl8xn4iHJ7DkDL36zizna9ZKy5gc/KDlLNSHy+scrVWcsh8THYp9tSY2RIjsSVyJMJJ1mWMEUkAA+Jj66nOWRM1DU2ULHP1OczIDEx2yJ+ioMcvU3rG0krLX1L2nM+M+I/tfH3PzG9+xc28e2HynKeVB4QAEek4YASe0kzwIJ6BAB9GDMkwZHVkengErc5CIVVfAHo4+j0niQcouTfHMQ5qTn6xR2H3/nP9xeI91we56j3fAGOu8yI09HBPATtOoGeOXEYDqUZE5kjp4TPUxASHyiKvR6ZD8zMQ6FhmQpylrOM8iTPKp7YRpyznxeHI6s4czqBM+JnpPOD2dpEDaj6EmeDS0ofzieYDOWaG8RkZ5szmf7fwmenccB91h5467z5onpOMw5HrIPoMuWc1cxg+hpJnp0PoRvMRBnmPo9s9J8QRDkyMc58HGcGaBsmhJH6jAcvjnAwbYxy6AZgw5EtmJ2sxyAHR6GcRhyIhzIbNpxCEHsnidUIBQ5EQ+Q5tOEzhnoP7Xx9z8xve+JzMrEGSuIM1YZGdpI7fwE+KzNWWDIEHLpzzA7RAQueamA6AcyengE9OnihO09DBmQPCDt+IIgIUtmoMzAY5g+/wDOf7i8R7rg9z1HugnT4EGIdzBksGQEH0ssooKzwikfkYABB9JvE9HgYMhBkCcz0rmAc+gZiDNcssjF3MHmOZixewwZjoXPKDM+pi5rAT+ZgBWKd4MgP7fwmenccB91TmfxinmMXtQ5joGQi/S9QYARFJ/MwdhGU8B0LmIs8B0A6/UGDPTBmDFO8GQEUg/gYABB5fAxe31hzZehTkQTFzIJyMHafiYMwYDkYMhAf1QTzqMs54EZGeA/tfH3PzG7odAHuCCDuAOkDufnP9xeI91wfdjhM9O44D/YFAH904+5+Y39m+c/3F4j3XB92PQz07jgP3U4+58Q2e/9mHazswH3F4j3XB7nxE8y/dPxbx/Adz9n7qD6KeH4nufOJ9BvQyxdxLF3EtXeWrvLF3EsXcSxdxLV3lq7yxdxLF3EtXeWrvLF3EtXeWrvLV3li7iWLuJau8tXeWLuJau8tXeWrvLV3li7iWrvLV3li7iWLuJau8tXeWLuJYu8BFfxJ+M8B9xeI91we72N6zQ0RIiREiJESIkRIiRElabxEErTeIkRIiREiJK0ErTeIkRIiRE3iJvESIkRIiREiJK03iJESIkRIiREiJESIkRIiStN4iStN4iREiJESIkRJWm8rSIkRIibxE3iJESIkRIiRElabxEiJESIkRIiREiJESIkRJWm8RIiREiJCF9SJ2sfE90+URIiREiJK03iJESIkRIiREiJESIkRIiStN4iREiJESIkrTeIkRJWm8RN4iREiJvETeIkRIiREiJESVpvESIkRIiREiJESVpvESIkRJWm8RIiREiJESVpvESIkRIibxEiJESIm8RIiREiJESIkrTeIkRIiStN4iStN4iStN4iStN42kQd0oMqWVLKllSyldpUsqWVLKllSypZUsqWVLKllSypZUsqWVLKllSypZUsqWVLKllSypc5UsqWVLKl2lSytfuPxHuuD9lPEe64P2U8R7rg/ZTxHuuD3DkI4lglglglglgjiOJYJYJYI4lgjiWCWCWCWCOI4lgjiOJYJYJYI4lglglgjiWCWCOJYJYJYJYJYI4jiWCWCWCOJYI4lglglglgjiWCWCOI4lglglgjiWCWCWCOJYI4jiWCWCWCWCWCWCOJYJYJYI4lgjiWCWCWCWCOI4lgjiOI4jiWCOJYJYJYI4lglgjiWCWCOJYJYJYI4lgjiWCOJYJYJYJYJYI4jiOJYI4jiOI4lgjiWCWCWCWCWCWCWCWCWCOJYI4lgjiOI4lglglgjiWCOJYI4jiOJYI4jiWCOJYI4lglgjiWCWCWCWCWCWCWCWCOJYI4jiOJYI4lgjiWCOI4lgjiOJYJYI4lgjiOI4lglglgjiWCWCWCWCWCWCWCOJYI4lgjiWCOI4jiWCOJYJYI4jiWCOJYJYI4lgjiWCWCWCWCWCWCWCWCWCWCWCOJYI4lgjiOI4jiOJYI4lglgjiOJYI4lglglglgjiWCWCWCWCWCWCWCWCWCWCWCOJYJYJYI4jiWCOI4lglglglgjiOJYI4lgjj3eI91we6BAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAIBAPd4j3XB+yniPdcH7KfHUe64P2U8R7rg/ZTxHuuD3frMNYUsgtN1XmySXFHPGNPSSESNnW3fEnEVIGforsd8QcgV6XCVr4sxyEYMrDMEeBH3CBFaeYzruSYlXIGbd1TYhofQdfTataAjNmOQhzGWcrdBS+ltf8ArfiPdcHu/wCY0EpQPXQSjzz9T0fbfrr/ANM8oY20xtFVS6mM9gB8J9jXYA7yo1WVHTZUZ7OGKNJystdgEns8YTF6c0HmDiYc4rHX+SiexBVhnORtQhtEOqoAET2AP4Tz5G0ays+hT1Zdhwz2EGwv2XtsALyg0W1PosqPR/i//Z0YZLP4i3LU0w734rEHTRSv2p7DCYVnAL1PrKTDo+GspJttLeSYSuvCCgaLQ/3DqrOdCeKgxRVbS6i1eMGfU6Fbmn/T4OF/GwayIDWwJV6z5lM9ljEihtFl1jhF1TAnC4uoa+JWEwf8XjeHwVJ7JXDi45JajhhKg1d9mmxp7KGGwTIdDM4Nkw9dRpxBrGj7U9lLfjBaRp+wqDinswYV7vqrQc0MwyHBHQXu+IaYKuisVIKSrhtUorpKXlAqT2cMWaTlbazAJMAMJinGdXC8qD1X6tc9gAYL/wCoNcOaWrqX/WXEe64Pd/zHmMShw30y6B57b10fKqQLFC11qFUegEOQE9l24wfVVeiCeyLcGqEVWEnUHnle9IBoCKFA4QOyU1rc9NhYoACSBPMbTqlVZtTyOQNS/lEoa4YcAC7gmF9n6H/XH0WonmHEJg6cfhaR508+mApS6NWwldGOwdZJTjlJqvpOl06P8X/7Oj58vFOLwpzraeyq2o1AG+mHUr4ZCOaf4yfcP40Cee6wIgn2balIPEizC+zfAccroT+KOoCox6b6L7Nb0WTAfwuMQc0+v/iJUlgHhrAMA0nF9H+Y885xk+tGJTRPWqfGoT/Itn2r3Lz68Y1NE458lv8AWfEe64Pd0AYrEF0mkUX+TpCnEWV6VJOkCZdYBnYRPOfLNIt0aSVgw2IrQZVXtLzbfiDqthquwt51Gl/smXqHqGVNFflEsFWNqGll41howdJ89qeaYopdWBpslGF8unryZijrcPqsHE0qw16r5L3MuFmLxTarOivDGq+sAdY8owHNNGWHsBumKK30D6o+R4mHwuGLDrShzZp2J1OgRKThKqtNbr9wtGtuKVYEJWsxgxF9f1VQ8iT6PA3CYmGu4b2mLN19xJfhENWLw1tmsC1u1ZYn8Sa9Fddfggl9fW2fX0v4PDVhsLS/1VR80CdXhrg9nQEHX4o2rpjobBiXSymydXVRQc0oSPpLRKNFaAVWJAmdt5cZHUNM6q7DXvqNFkatRh/qcPXNPVYZmNk8z1kCaetXzEf6y4j3XB97resN1xs/13xHuuD9lPEe64P2U8R7rg/ZTxHuuD9lPEe64P2U8R7rg/ZTxHuuD3XdsKcS9ZPAYcwZYzKlmSgy1a14mMvV/wAuhiGFLEGPm5w5Jae1MQhJIyBmLOJwtzac26bGH8LSbbdM8GQZxgqjxJmMrdwp7AZZrcuZcifmZfmj3ZMUMsVS5yXP4mWg2j7IlyoD6mWq4/CMEX1Jloc+ghAA8SZiEL+mY/YVxHuuD3fB8S0+tq+qbiWfMgD00v1dVUUVGtxrRfAgz7Sgz5LT/FML+fglLiip9buw6DkqqST+AjoDinYZHgj/AFFh0fpjkUhOssAiJTei51lZ9g2vtEFz2udIbwAma02XfV/ANDkTiJhxrRCTae1jMRWbXc6KnPlExCFLn0W1KZmcH9sTEV021MCDRA3VdcOu08McV3cQIDgxi5VQNR8T+wniPdcHu/5Rn/dUfSWDzvPoVW2GypzHFz2uNbJ2hQJ9lQJ8lp8hpWhOtoAB+HR9biXCARCz6QIMqLfoWCdtJTq7DLVuusGlETtJJnFcscUvU5yLz6VNdwHWDylp/kLPtAjeV1LYjnS7r5hMBVe3i9iKMklH8mwfWsM1hBxhtGgUTDa0c5WHxCR6v4n7Bqn1orGr9hPEe64PdpRC5zYqACx6KK62c5uVUAmUo/4MAZRXXnwqAT0IrKwyKsMwRK0CZZaAAFAlSVpwooUbDppR3Q5o7AEj8uimuwDjUMBvEBQjIqRmJhaUJ+KoBKkrXPPSgCjYSiu1h6gGUV5VnNfojJT+EoRyhzUsoOR9Rn0YZX9NYEoSsf8AkUCVK6cLAETDVI3EqgRQynxBGYMwNKNxaR+wriPdcH7KeI91wfsp4j3XB+yniPdcH7KeI91wfsp4j3XxT9lPEe68V/ZR4nvMwfwjGNHMcxo0aNGjRzHMaNGjmNGjRzGjRo0aNHMcxzGjmNGjRo5jmNGjRo0aOY5jRo0cxzHMaOY0aNGjmOY5jmOY5jmNGjRo5jmNGjRzHMaOY5jmOY0cxzHMaOY5jmOY0cxzHMcxzHMcxo0aNHMcxo0aOY5jRzHMcxzGjmOY5jRzHMcxzGjmOY0cxzGjRo0aNHMcxzGjRo0aOY5jmOY0cxzHMaOY0aOY0cxzHMcxzGjRo0aNHMcxzGjRo0aOY5jmOY0cxzHMaOY0aOY0cxzHMcxzGjRzGjmOY5jmOY0cxzGjRo5jmOY0cxzHMcxzHMcxzGjmOY5jmOY0cxzGjmNHMcxzHMcxzGjRo5jmOY5jmOY5jmOY5jmOY0cxo5jmOY0cxzGjmOY5jmOY5jmOY0cxzHMcxo5jmOY5jmOY5jmOY0cxo5jmNGPv/wD/xAAyEQACAQMDAwMDBAEEAwEAAAAAAQIQERIDE0EgITEyM3AiMEAEUGCAI1FhsMBCUpCg/9oACAECAQEIAf8A9A9vy7fYt/RO3Ratuixbqt0qt62+1Yt0WLVt/QC34L/EX9Ebl/uvoX27l+hD6H0P+l7/APjXf/oTKZct0WpatqWralqNdVuq1bUtVVapatq2FW3YXktW1bUtS1Ldq2ralqW6rUtRVtSxalh1sIVLdVqWOKcCLjralh0VLUtS1LUtW1EOlqWo18PKjqqs4OBHIx+Kqrqjk5GcHAh0ZwcU5o6KrOKPqVeBUfQvIzgVWcUQziiF18HAheaMVe4zgQqOqEIVGOnBxRUdOBFh070VGMQvI6ro5pwcHeioxiq/h5U8niiLU8ng4Ec04PJYRb7Hk8Co6M4palqeRdNq2GeaIdOBeacHmqp5PFLDOKItTgVeSw6IZ5PAqeRUVGcCEWHTgQhCLDELyOlhFh04F0cURYZ5ELzTz02OepUY+h/Mq/5O6xb+mXFOKWraiGq2LUsWpYtS3XatqWOK2pYt0Wrali1LFqWLUt0NC8li1LFui1OOm1LdqWLUt8ucFhnFOBU8oQ6IYhncRyOiLOnAhnBxTgVOKPxXgQxdFmM4q6sVxjohroYhiOR3FXivBxR+BHenFGI5H8ucCGcU4FTgXkdWIfkYjk8HmlxiH4EM4pwKnFOK8CGIdODuM4q6s708C71firEPyIXkZ3rxXgXijpcZxRnej+XL0vS9bl6XohsvS5ely9L1uXrfpuXrely9bly9L0uXpcvS5ely9b9Ny/RfouXLl6Xpcv8A9WejpykbZto20baNtG2jbRto20baNtG2jbRto20baNtG2jbRto20baNtG2jbRto20baNtG2jbRto20bZKEo+f6HacbvvObl+xwnbs9SOL/odHtpS/ZZ+1D+hy9p/ssvah8b2+1b+Gr2n9pJydlsTGnHs/t2b+7L2ofGyoyxalqOipxS1O1O1WqWLVsL98XtP7S/x6V1k73J6mcVeGnl3e1CXo04ZyxNuC7S1IYMjpq2Unpq14aenuZE4wS7LTilectNKOUdFQwkSUb/TtQj656ePdfZl7UPjZVYqsdEdi9FXwduhjEKjF++L2n9qffRgyDS8ywelkfTsxvF6UXc02nrXUvUzV9GmauFo3hLShc0fTqUenGK+v6diWOj6dQ0fcRqbebvKUNtxX2Ze1D487dC6O1O1O1L9hdCrf98XtP7WnqY9n/g8mpqZdlCaxxlfRj3WnJRndvu2TmpRghThKKjNvTinjpzUYzVHLT1O8nqQ23FaU1G924qV4OWlPu5yha0fsy9qH9Dl7T/ZZe1D+hy9p/ssvah/Q7T7qUB9v2NK7NXtaP8AQ7wZQn6tuBtwNuBtwNuBtwNuBtwNuBtwNuBtwNuBtwNuBtwNuBtwNuBtwNuBtwNuBtwNuBtwNuBtwNuBtwNuBtwNuBtwMow9P/RrrP8AoLYVLCHW1bUQ1S1bVtVDVLddi3TavAi4n0eB0sWpatq2raq6bUt2+Y0cjEXELyMQ6I7nAqcCGLxTgR3OBHeqGKnlVYjkYjkYjkdeBFjxVDGIbLipcR3OBHenB3qq8CO9F4+Y/CLnlCoheRiHRFzuKnAhnFOBF2d6XHRDF4pxVHhCGI5GI5HXgXSi55F5HRUsIuzvS46Jjoq8UuMXj5jbpely9Ll+i5et6Xrely9L9F6XL1Q6XL0uX6b9V+i5ely9Ll6XpcvVOxcuXpeif9B7/wDKHduhdLp2+zx+TxRfgI7Dq195dVutj+0qv8i34j6bfCjpwcHkQqOjOC3QzwhHJc4ODzRnFEXoqOnk4F0M8ITOS5xRU8HmipyXpwIXkvReBFh0VfBelxHcdeKqiOTvR0QhjGPyOnFEdx9CLjGeDzRDPB5oi9PB5EXORnFeS9L9qIZ4PNEMZ4PNeR0Rc5ORnBxRHmvHwh5LDH4EKn+9WcdL8HAjkscHAjvXjp/3ozg4FXuPwcCOacUVH0f70ZwcCF5qvAujwi4xljgsI7jO9eKKvJ3H0qjH5HTgQjuOnFEWGOneiGMVEWoxCpyM4ODuLyWpxV0XS6IscjoixycjOKqjvTj4Rv1P7F63pfov+Bet6X+3et/v3pel+q9b/fv0Xpf7l/tXpf7Xjpv03rfqv8p3RdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdF0XRdfKzdkNtlmYsxZizFmLMWYsxZizFmLMWYsxZizFmLMWYsxZizFmLMWYsxZizFmLMWYsxZizFmLMWYsxZizFmLMWYsxZizFmLMWYsxZizFmLMWYsxZizFmLMWYsxZizFmLMWYsxZizFmLMWYsxZizFmLMWYsxZizFmLMWYsxZizFmLMWWZCXHyrqekh6v47z8q6npNP1fx3n4XxkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSNSLUTT9XVZswkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRhIwkYSMJGEjCRjL8Xn4W7aSHqTZlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRnI1JScbGn6umEb929V+I5SMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIWpNFlqK6/D5+FdJXmiTu2/wB/1PSfpvcQ+zfRLtpR/gmm7TRNWk/w+fhXR9yP8A1PSfp/dRP1Pon6NP8AgkfUjU9b/D5+FdH3I/YcWvNEm/FGmqYtq9Er+GmvPQtObGmvP3cXa4oyl4259Di4+aKLdWmvP3NT0n6f3UT9T6J+jT61Fy8CTYk3f7OLtf770pJXf2Um/DVvspN+Pux9SNT1v8Pn4V0fcj1wjlJIm1qRkkR0m1d6UHHIUXJ2WyaytGFNC227yWMmj9Oldyesr6ticXB2MPoypopd5uWrNs3FODU1C8XISu7GH14mzbzHSc1dShjY2f8AWcHDytJuKkPRdrrRinI1oJSbovYZpdtOYtWaZrpfTKmlHKaNR7kJUWk7XcIOMJkYubstn/TX8o2X2MLyxWyNNOxs/wDtODgR0m1dz03Hv06npP0/uon6n0T9Gn16X+ODkay+q5peJml6dQjFydlsig8sW42diem4WMPoyou7O3tD7MjpuXcek0rqMXJ2Wz/pZ3sbP+soOHmOm2rktPFXIQch6Xa6NGKcjWXdi0XZMw+rFbJKLi7PZfZj0ZcbPbtTS+iLmay7qRCGbsLRMHli5RxdjD6MiUMUmR0nKLkQjm7GivqkjZJRcXZx0m1dy03FXI6bkm1KGItF2u56bh1R9SNT1v8AD5+FdH3I9ehHtKRp6cozu5Rx1LGu/qSNBu0kaPZTZfua7vGFF7LNX6lGZL6FCBr+4an1xhI1fpjGFIezOlmyHtTNP1ofvms/8jIdtGZoq+oicIuTvPHbsN/4Imh6iHumr65UXsM0bbc720Uak83TRj9EmaWm4t3xtqWNd/XY0m9qaNPtpzYm0z9R6kaz+mBoJYzZhC5rSWaalhq9ycZRte8NSMU5wlGPTqek/T+6ifqfRP0afUldmpptqKU4PaNL0zNL06hp9tObLu5q+YSHHLUiyT3ISNT6YRjTQjeVzbluZGvG0zV7QghScfGn205su07ml9eqm5Qi5M1HHbSE4TgouWnKMe2nKOLhJ6bSvE0/WjV9yRqP/FA0P/Jl+5q94QZrP6YEH/hkaHrJeXSem8IxUoPas9D1Mu7mv6kzU+uMZGr9KjAX1aInhhAgsNxmh6mXdzW9UTVjF2usIxkjTdtKZp95o1Ixcu7xWk49UfUjU9b/AA+fhXR9yPXOSWnGKuzVkpYyHhqpXg9OCaNKeL74ad7mtOMlGxktlo0ZxSalKWWpc1mpT7aDTTi5yyk3TTngzDSfdSlCMcYaUoq8ZRWlCVxyW9c1HebaUltSRGWMkyUdOf1E8EkouS2Yo0Woy7qVp3NXF/UhSWy0QklpzXRqSWMYxTaZqyTakpYavcUtOMJRWlNK6eGnF3NaSlJW1ZJqNtKajdPb0/JJxy7OGnLutSUbRisdOSVpSjHTwXRqek/T+6ifqfRP0afVpWUruUnKTZpTs2nBqOZpySU76c0rp4aadzUnnIjqR2zRkoy76sspuiko6TtdjkpaaE4zgoytpwTNKaV08NNO5mlPJOOnPup4KyilpyihuMIOKgtOSs446V6Q7SRqO82yck4QRpTwZt6d7mrNSaS1ZJqNoyW1JGk0p95eWadslec8pNmlO0u+m4xnKmtJSatoNNNOcspNmjJRfec76lzW1IuKS0Wk3c1ZJuNrw1UrtacYkZLbmhOzuPDU7k8FGy6Y+pGp63+Hz8K6PuR/EzjGLUP2vU9J+n91E/U+ifo0/wBxc4xjaP4UfUjU9b/D5+FdH3I/wDU9J+n91E/U+ifo0/4JH1I1PW/w+fhXSdpomsZNfv8Aqek/Te4h92+h/VpR/gmmryRN3k/w+fha8dRWb0pm1M25m3M2pm1M25m3M25m1M25m3M25m1M25m1M25m1M25m1M2pm1M25m1M2pm1M25m1M25m1M25m3M2pm1M2pm1M2pm1M2pm1M2pm1M25m3M25m1M25m1M2pm1M2pm1M2pm1M2pm1M2pm1M25m1M25m3M1YSUO+n6umE8R6d+8NqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqZtTNqYtKY3GCxj+Hz8L5MykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykajeJp+rrykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRlIykZSMpGUjKRd/i8/Kup6TT9X8d5+VdT0mn6v47z8q6npNP1fx3n5V1PSafq/jvPyq1dHdMzkZyM5GcjORnIzkZyM5GcjORnIzkZyM5GcjORnIzkZyM5GcjORnIzkZyM5GcjORnIzkZyM5GcjORnIzkZyM5GcjORnIzkZyM5GcjORnIzkZyM5GcjORnIzkZyM5GcjORnIzkZyM5GcjORnIzkZyM5GcjORnIzkZyM5GcjORnIzkZyM5GcjORnIzkZyM5GcjORnIzkZyIRu/lZpMwiYRMImETCJhEwiYRMImETCJhEwiYRMImETCJhEwiYRMImETCJhEwiYRMImETCJhEwiYRMImETCJhEwiYRMImETbiYRMImETCJhE24m3EwiYRMImETCJhEwiYRMImETCJhEwiYRMImETCJhEwiYRMImETCJhEwiYRMImETCJhEwiYRMImETCJhEwiYRMImETCJhEwiYR/42ZHb7bO3Quh0X8H7ffXyMi1HXzRnFWMVeBiLjPAheaeDyIuL+AWEXoxU56/FEXHXxRUYzyKjPB5OC/xmqs4EI704qx9FzyKjGIVe/S/310VGMXg7nI6I7joxCLuncY6cUYxCo/I6cCH8Z36r/dvW/Rel6X/frl+i9L9N63L0vS9b9D6b/wDderUfTzTzVdDov6RMZ4FRHI0eKsdLj8jouhHcR3/oyxDPIqI5HVFzzSw/I6Lr7/8AHtf/xAAuEQEBAAAEBQMDAwQDAAAAAAABAAIQETEDICFBUTBwgEBQYUKQkWCBoMBx0OD/2gAIAQIBCT8B/wDEx+IsZYyxljLGWMsZYyxljLGWMsZYyxljLGWMsZYyxljLGWMsZYyxljLGWMsZYyxHwQ2Lb7Htbfsg+fsv5+B3n7L+fgd59Ldm3nQsWrlj65OhYtbtYtWxaTqT/wAzrYus6npfn4HefTNbDpkuXnJlvGWLrl4yX0/z8DvPpdTLa2uufa/m6t3y6OWzl0Y9L8/A7z9l/PwO8/Zfz8Du/wBl7fA/o+biXEuJcS4lxLiXEuJcS4lxLiXEuJcS4lxLiXEuJcS4lxLiXEuJcS4lxLiXEuJcS38/60mf6ZOzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMz/gYkRERERERERERERERERERERERERERERERERERERERER6BEREREREREREREREREREREREREREREREREREREREREREe1fXFMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzzbF0JmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmbf3v7/sUHoHoH0BHIfbDp7xfpydC2yxGvjl7cvabftds3TJ1nTN1meRu+f6cnTPEL4vE9LrYjXxk6ZOhdT6PveM8Rr45/GXQnUyxGuWLTLoTqZOuTYv7TdbEa+MnpdSdeTvli6+OTvyYuvjJ0LqZM6e3CXnPfk85f39biWLXTLxeeTadbbPZvPL4ydLi5YtG2sWiTqfRdrcvHL3u2faS783EsWqTppOpdLFr625eOXtn4zx6WPXXPHYtfbZy37zozbNj6Z75eeXax6X82zYtbbLfLFpdfzb5ebFlvyueLRm2bHqZ7Nj6R0sWl10nRnX6PZnLZsfTLcts93J6k6JOrbNj6ZYtMnRnXW6Ni15drH0tjn2npy7PM6M6ueLRur7h9/6j7/sa9ve7oxERERERERERERERERERERERERERERERERERERERERHPtMRERERERERERERERERERERERERERERERERERERERERERl/PtWzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMz6LMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzM/43DMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzkzMzMzMzMzMzMzMzMzMzMzMzMzMz7skRERERERERERERERERERERERERGREREREREREREREREREREREREREREREREREf92of//EADMRAAIDAAEDAQUIAgICAwAAAAACAQMTFAQREjEgITIzcBAiMDRAQlBgBYAjUUHQJLDg/9oACAEDAQEIAf8A8THPciG8m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m0m8iXI/p/odc8rHaK6oX3z/BW1Q3vil/Nff8A6Gt770/ha/dfbH+h0/mF/hU/MW/UHv8A02fzC/hM6pHeeUncVoeO8fhy0R6/iJ+Yt+oE/wBOn8wv4Uxtf4z4J27FdU1vPa27wnxjexO2ltngnlG1re9arYsge+fLwRbmhoWy+7LxK3tae8zc7NMV13TLeD9TNmiiM3j3fex/l1XefeJ/BT8xb9Np+2Cftj7Z/Gn7YJ/nJ/ML+FXPj1DxNis0fdSbIu8Z+/yG8XW917TbEx0/aa47VwdP8dpTp5P4ulz9u/U/FUekEWu8znHnyV8up+Oo6jvjJVt4R4olm0O34KfmLf8AQ6fzC/hXU+f3l/8AlehTT4e9raW8vNO3UP7ptrlq/GFjssQU1MjvMtVYjy1cLc7RLXVM7J27d47EJdVMwiVW6w7X1TZESqw7JMWQl9XuWpLPLyf8FPzFv+h0/mF/hU/MW/6HT+YX+FT59v8Aodf3VkcWYaO8fwTTCx3miO8s/wDodMQ0dpzsq+Da02sNrDaw2sNrDaw2sNrDaw2sNbDaw1sNrDaw2sNrDaw2sNrDaw1sNrDaw2sNbDaw2sNrDWw2sNrDa3/xnZbP/JEdv/U3ERM+mVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhlYZWGVhm/1WrSXaFiupKlNajao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqjao2qNqiLK290dV06ssvH1U6P50HWT2pn+uLMxMH7CfWfqp0fzoOt+TP9cj1P2E+s/RbRINUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUNUOhdWujt1vyZ9qWiPXVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVDVCLEn9JHqfsJ9Z+iv372ntFNamaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaGaHQ1rF8THXT2pn2bbPH3KtHf3vmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmhmg1KSQzUtCt+ij1P2E+s/RS+e1cla+KRH8/0fzoP8p+VYSe6xPsJ9695/ol6965Kp7pH6KPU/YT6z9FOp+VJHp/P9H86D/J/lXKvgX2Kvm2/wBEt+CSn5a/oo9T9hPrP0U6n5Ukentw6tPaPsZ1X1+yHVu/YmxYnx+yZhY7yrQ0d49ib64FZW9PxfNfLxGsVPXas9ftV1f0+yXVZiJ+xWhvT8To/nQf5P8AKuVfAvsVfNt9tnVfWJ7ktC+suq9u/wCBovl4/jehF6M3jH4LNCx74mJjvH4DNC+v4tvwSU/LX9FHqfsJ9Z+inU/Kkj09q5/BJkrianRpH6iIbxi62H8YGeEXvPJOlnu9n2dT3291beaRJ1bz2hI6dvGjuV2RYvcm3/khPs6hp7rWq9PXEGTV2RKNb4uqjt4LLEWxNfmcrv8AC98I3aa7fPv3nqff92u1bPRuoVWlSOpjv2bqXlU93TWSyxH2T+agvjvdXEzRXMHSzP3l+y9/GuSmJpsXuN1ERPZbbIsesexa17zyf++ln3McqPfBrEJ5NyhWhl8onqY7/drtiwfqIie0V3Q89vZ6P50H+T/KuVfAvsVfNt9u/vbZ4R0zd08Z6j4qzqfirHda47zySbY8POFeGXyKrosmYJtjSE+xp7RMn3vmiz3iJLLoSewvURM9pexUjvPJ/wC4aJXyOT7/ALtdq2R7nvhZ8YS+HntNlsV+4XqO89pOoeVXsdM3uiCepiJmCLfueTckR4eO8cmPfBHUr7+/J9/v+zqP+R4SOlb3Sk2WRXBPU/8AWsSnnFbxYveJt/5PASzzZoGvhX8SyyEXudQ3dFk5H/SPDx3h74We0JfDz4ll0VtETXb59yeojv2Wu5bPat+CSn5cfoo9T9hPrP0U6n5Ukeke11b+9VLbVdIiEfyp8jpVjtMnVLHkknUe9qlPGPHsdJHZ7PsaO/U9iic2dJX/AJGssOm99RTObuk0R5u9n2W+7qEmSWiPW359Zf8AKYX8qdNERVBZHfqUOpnxqntVY6pHaqH28iI79Ux1cR4RJb8go+Uv2T+ag6nvFiePfqW9xTVmv2dS/exVLroeI7eflT5R0sRn3OoiItSS771tazKxK9jpPhY6aI8nk6uZ8kg0smOx06t4NErNlHeJqdH79u1lLtMV2o7ez0fzoP8AJ/lXKvgX2Kvm2+009omSq2IZmmqz/nOo+Ks6n4qi771tazKx49iiO62KK/jU6iRi6lP37Xf7OqfxTsarl4HTP3r7FEeVlky1at273feuRZlVmOxdEV09lrseFiIph9ZkZbK7JeEtR299yP5w6xcrTEOXfLko+VBTETdYdV6pBCx49ij3PZB08ffceI5KnUxGYnwwTPaBLYixnlbY37nVfCp4x49jpferwUzmzpNEeTO8t9y/uMvn52Dtrkp1UfdU8Y8ex0/uRyl2Xv2nR3WS2O91cTd92pu1Lsq+5PObob2rfgkp+Wv6KPU/YT6z9FOp+VJHp7VdctaztKqUoyS6zGnTtPZ4utaGL65dY7a3THidNWyS/keDciGOpraZhkSvxq8Tp1la+09WvZlaKk8EiPsvq0j3RbenulEssfzs6itp8WR2vtXxFRuP4lMStcRLI09QjFieaSor20/dmrVpmWVG5DMdSssnuZJarxKJePuMSjciGLUaba5+2SmufNndkWYmChGhWRl1omYGW2x1ab62bsy63PHjHTIyK3fp0ZZfvfVLxErtb28RIsz+9Flye5qK28mefO6tp7ojvbpPsdH86D/J/lXKvgX2Kvm2+1f5SnZa64VIgvr7xErarNnJejM1fa+uW7Ms22zHiU1+C+96Wm73dRXLJHahPCuO4yM98d/GBUZLp7Mr1PLL5W2tBfWzdmXW5o8YzlqvFle2uPGatZmZZmureSFe2yGm2bUbvDed8qR7oLY7pMFMTFcRNKMtlkzfXovu2tiPEorlImWoWVl+7o26sXrLJ2hPcsF/l4TC1VwqRE31+S91tV3RD/wdMjL5d+qXsyzFS+CRB1NcusTFVfarxnp6WV5luoWWhe3/AIKEmIbvEWUNPZWtd4kdZm5JGXyWYFm2j7pXNrP3n2bfgkp+Wv6KPU/YT6z9FOp+VJHp+ikiqx3ibP4vo/nQf5P8q5V8C+xV823+Qkip3fys/RW/BJT8tf0Uep+wn1n6KdT8qSPT+f6P50H+T/KuVfAvsVfNt/olvwSU/LX9FHqfsJ9Z+il8d65Km8kif5/o/nQf5TvxW7JHZYj2F+5e0f0S9u1clUdkiP0Uep+wn1n6KzD0z3WOork3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rN6zes3rOhtRr4iOt+TPs21+fviL/H3Wb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZvWb1m9ZPUJHoqva3k/6KPU/YT6z9FvBZM0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0M0OiWIujt1vyZ9qYiTNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNDNCEWP0kep+wn1n6qdH86Drfkz/XI9T9hPrP1U6P50HW/Jn+uR6n7CfWfqp0fzoOt+TP9cj1P2E+s/VTo/nQdZ8mf65HrB+wn1n6qVPm8MRKXIcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSg4lBxKDiUHEoOJQcSgXpqVnvHU3RWnb6rJY6fDy7zl3nLvOXecu85d5y7zl3nLvOXecu85d5y7zl3nLvOXecu85d5y7zl3nLvOXecu85d5y7zl3nLvOXecu85d5y7zl3nLvOXecu85d5y7zl3nLvOXecy85d5y7zl3nLvOXecu85d5y7zl3nLvOXecu85d5y7zl3nLvOXecu85d5y7zl3nLvOXecu85d5y7zl3nLvOXecu85d5y7zl3nLvOXecu85d5y7zl3nLvOXecu85d5y7zl3nLvOXecu85d5y7zlXyTMtPef8A7Jef0sf0qf1c/TyP0UfZP9Tj8aPsn6ddv/WKH//EAC8RAQEBAAAFAwMCBQQDAAAAAAABAgMQESExIEFRYXCAMEAiQlBggRIykdBxkOD/2gAIAQMBCT8B/wDiY/FYrFYrFYrFYrFYrFYrFYrFYrFYrFYrFYrFYrFYrFYrFYrFYrFYq/gf5rvf6H208z/0zfT8GLyv7n6fgx/tyi/wp1rHbljtyz1rPRPLPTLHVOlT/wAJ0Y7J0v6X0/Bj3a6N9XlOXx6s/wCfRO36f0/Bjtrl5eXac/d7u0e3Lxy8zl3i/pfT8M/p+Gf0/A72/ovv+B/efDhOE4ThOE4ThOE4ThOE4ThOE4ThOE4ThOE4ThOE4ThOE4ThOE4ThOE4ThPHx/1N1msVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVisVis37rxqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqNRqLHn8M/j8M/j7L1VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV9dVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/a/H2W7YRERERERERERERERERERERERERERERERERERERERERE9Xmu9REREREREREREREREREREREREREREREREREREREREREREXrm/tPj7t+39sfH3b+n9sfH2dv6F7/AKFX9a91X0Xnf2309d53z+je/wDYvx9nf5uU612vLPb5/S90f7fd7889eWejPXlPCdET0R7c/wCblOvPPSfPKd3Zjt8vDPXlOtTpf0/p6/Z7ejHZ39Xzy71OnLPZ4Z68p1qdK8s9OUZ/yndOjHblO7tWeno9uWe3zzj29Ge3zynWu15Z6J1ef3Xx9nc+Hw88vCen/Hrvr4THSXn8ejynR55eyeHw9+Xj0T/Dg8sdYnSs9ZU6a/T+nrnl4vPxy8PMe/P3R7co8Jy4TPSVOsZ6aTqx0/VjtLynt6Pf0T25Y6sdOnPh9WOn7r4+zkR4Z65Z7PMY78529fmMdX/DzGOid+U7csdXafCduefHKdk9ER4Z6xl5jHflHmOH3Xux1Tp1Z/1Rnp+n9PVPKJ3iI8xju8vFeY88p/DET+Gp1lTpHmMd17sdX/Cdcs9JHeM9J6I8xju83lO3oid4nflHvy8z0xOuU6Z556x2nx+6+PtT4n9S+n9R9v6P8fdv6f2x8fdv+b+2Pj7LTrnlVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV9XmJ0VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVXd4+P2nx9l4iIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIieuIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiftfj8M/j8M/j8M/j8M/j7rd5WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWXn7rVtttttttttttttttttttttttttttptptptppppppppppppppppttttttttttttttttttttttttttttttttttttttttttv/u1D/8QALBEBAAICAgEDAgYDAQEBAAAAAQARMWEQIUEgUZFwoTBAUGCAwXGwsaCBwP/aAAgBAgEBPxD/ANAoSiP5AR4T8FRE/ABcSoRqv4EGY8DGBfBOAvgnoByIruJUDhOQRK5PoBwSoF8JUCJcruJAuPAlcB6AOCV9fRnXodwjni4M88mI54WGZhCPBGYORljKIkIzv0XwYZjiEWXwZi1LePPHcX6+BcTgjn0F280+gxHPJmYQzHHKu48hcSoPBmP4AEcQ9Az6DLlxPr7mUcMGUTojBlExwM6hHPJmOIcJDh5Gp0yiLxYyiKcDEgzM8xh6Vzo48zplEX+CgTH5ipT/AAZuLf5i+C/72WmV9DqfpGKREYpgmInNuBMRJSy3FoiQFiji0Dv0ALwiQTESV1cC+bREgLLcWiVAWBHgL4AuW5tESAsUQXEr0BZiJAWKOAuWgLFHFvQZloiQFiji3FpTdcIkqAsUQLloCwIx4tESWiji0wOAWKJnMni3CVKQuVAWKOLRw4Mp5y8GHgFiji0wICwIyrZaAsUcW4twJiJAWKOBbLRvq5aU3UtAtlH0ewYZi8Q7E48mWzLF3DtQyibhKbmCV0VASZMtl2zw5VMSHuF3DtTzmRMmEfcyoEAjmBCK5mBHoCPuOeeIFGZcPeGN+YnQED3mUc88ovc7BKaKgJzLuPQi7gdsVudgeWUXczBdQVFTKcjB77iK2M7vuJaR7GPQj0Iu50hbZ0AjjCUpgRWk8p5MtuYnBddQTJmTMlls8GC6j2JMRUAmWZMVuYHBlPOU94r4LEDM8mWzxnQCPgVsroqBWZ0ZTB77iN2Mz2iMfdDukoHGcc8FiLuU5OM/o9g8JkTDhVw6HqJkTCGUy4uY4TCdMsVPBq+o9kC+TFy+7iZEwqURIFMyYTOGUGypswaY2wxAmEFR4Sy4dlQpmLbxozAJhKrLFTBXB3SdIcxwmEqsvGUczCBkwe+C8Q7pxlGzHoqHcCoUsBHMaYt7xWweou4u5hAyYu4zCCozoxFczAjhwXfDx4O6QVMmZMVPBeIiouE7KmzFTwwODKYPoyKgVcXDEmEFcDmYSqywBgI5nSwQ5i5RL7GJRAsmDvjKOZ4TwgDARzF7gvuV1f0yGh4IpVH6f0Y5/wBnaJl4lfoNNX+dC4lfhV1f1ocZbHu0BZeIkzxRIfMyPB3LxfAmL4FFHF4iRKIFsSoFlwF4okC+ILLx4hbHocCijkFl4lQFl5iCZfgTL8CYo4vETgLlnApWKOBMUcgsvEqOErq+EogLxrup4IlQTFHF4lfVsLE/ziKoh0njM9BZbCGcyeMiOLuCmdoEmIC4lp1M42RVh3HW2dwY9QdW4e4xYqw6i2K+bhBbF3H3USngXBCHjMRbFudACCkywOCAM6MChU7JlEuK8+Myj7jyQLgJgTMel4xFxbjhDPjBMElCLfE7SBgRiVD+rjjFTD5h3TjE9hODpcWTwZhgtnaEmJdYx3A0bgLiWJgMfc6R2KmcO6cYjF4zxSubjMoKYe7nZ4OrEtTxj2OEqdgYFzDE9JBUbudwqNwLepaTtbnxmUyTOKoSMFc54JUcJ2RKZiQHMFcz4nSRIwVPMz+rmFcYVBriqwa4qeGxxkSh5k3l93xbQa4qsOo2hSLcGuKrBrgU4qSuREW2EvIRxbQU4rcEcxE1OCWBrip5JGm4tsGuCXmKcVuL0EFOLYgjitwRFuHKt/Wg6Ytv+0kWDfFc1AicYJcZXFSuK9NPFcUzx/BdmIcMYQzHjxKOO4zxO4TuEOp5mWXPE8Q4cfwXYQ6ZUZmYhmJx44JUcSp45IzxCVGeISo4/wDIb2Ide8qZ/QcRERERERERERERERERERERq4lhXXv/AAPFn3DFKOhg/Q1eR5IPT2uz+By6PlP0Xsv+f4HfZv0X7/6bAWW/BC5eIn5AL/XPs34RgLZ1+PmOUU/iFijH4v3/ANNmDLeGzHpZAuU95VPcpRxnFj3AeWU8MC5Xuld1K90ruolNSiV1cPJlPDAtiB5h0tYP1z7N+Eqv83PKLgCHXzKzWfMoPSeElCmpb6HsqLXdjhmVdiPdQZJW7UxRoI3ssBxCvde6eGJSjBlA6V4CVSw4fwvv/psxYIuI5mc84YePCYHGcv2RtA6tZTuoNMocMynaJTKtGZGdAOGcyZ54ef659m/CFR4zF1mCgWvUTu0NQmbCMMDca/507K+0FVjrqoghLKnhxlgazQjimrMz7NAN8D7RLYq31f4X3/02DQ8KMscywKINcFtlid8KnkRKYIgk6S+74LcMEVMW2CJTLDEVPFidwB+ufZvwuyduSV5n/E6YUcEvjuCzY/BPGLuMV7sKmR3DQxiB4WeWJDI6g03AhECVfrv5iIPRTCSSht8kqesefwvv/wCBz7N+i/f/AMDn2b9F+/8A4HGOZwgUj+hoQHbEA3H+BwqElUroT2R8TQ+JofE0PiaHxND4mh8TQ+JofE0PiaHxND4mh8TQ+JofE0PiaHxND4mh8TQ+JofE0PiaHxND4mh8TQ+JofE0PiaHxND4mh8TQ+JofE90fETIb9yKrb/+F9pq/QC8hf4tNX+bpq4C8SJ6AX8kC801f1nO2ajxeBL4EwIzJ5vESAsFESHzEgLLBO64vETi8xwLYl8XiJyiQLiVBMUcJUC+LRK55xVyzpiU8lAIKeBMUcWiJxeInF4lQFl+cXnuuL8EET6xHzLe0MMUxWMFwu4+5k8HpeD32mLLY92mcyYqUVeJyxh77TzjSLfGRM50FlvoOwM98O4XcfiYjOZwht+jnF3AyY9vAtmU7AwWzshAtiqzCmGNp29pnEtFWHsliPGLycbSLcyfWMeCw84lQ9PAKYe5lxgy0WkxeecyYZ8308C0gOSDh4yJnOyODpc9hIwygph7lxnDCM/Rzit+joLxGiTpApgLM4lMEk8+C6QFxLw9DOzuItMFPGL6IMHDhmT6x2cFJdMr7c9zit8DUp7RtBrjCoNRsYl9VxhUGpX2jaDUp7Rb57GINSntFPOcdvG5H2cHvON93Ft5wqDTH0PSuBpi2zcj7ONyNoNSntG0GpT2i3Ckr4I98isNvEwqDUp7RblEUf4DdDo/2hoAWy14iVyL9IAJ0B1C3iJT6Q7jnivywO454o4C4lfjB0wEv2QVyFfjC74I55aB6ntOoAZlwnR+AJXAx6AtiV+TehwFsSn8gZg79Jb6LXCLQg9hnnMkgd1FbxgRFEENsW3i3wQdDwK3uVcPsi3B1Haipidi4eyL12TBrMt5OMpXdTshFromHHIjni3wTAeCx7lXD7It8H3FVi4Ew4yld1HukaPUey+OfA8Mk85aYBwO4NrK7qLgRsdw6Lh7oLZcDDGwKi3D2ReqTjz4yIwoxF7uJo4zg7mU8CZEwTPhaEG+mDuXA4GjohaN8bXPBnhOx1Ox3w+4lY9IODwLYqKMXAh0mLBXGFT1Hu0Ci4K5gQqLFU4VMTcwIODwqYO54EXAmEMMMxlU0cDzLzqGMo4Q9kXqkjaZlFToj2XLqFv6H4QU9kwccmYZi+XjFoS3gzG76mCZEHcGoVcquBUx7Ruu4CFkyrgOSFufQHu4O7mFDtEDccwzHtMEyIO5dSjcquGcemC+yHQvoD3A3MQdwEeBzxknnLeRheZDBfZMLh3Ap0eFvM6Rx3ATsme3HnKYZIlx04vYcEeidiPbMiYJnwliDu4u2MW88YFQum+KrnQCeEVoqYcZQdzsCQV28LuKuJUF9zoKzBhmMZ8BvpCFCriVHscGYO49gkFd8ndMvu4L7gUMwYK4zlx5EtKqGA3HoQELJkbnnDp4G67jhAv6IW4uXUtIFzAObWX6LS2CktxaW82lrBSKsFIq8BceiuBSKsGpbzaWwUluLS3lVgpFXgLj0VxaLcGpb02not4tgpFXi3FuBEVYKRV4FJb3lvFuBTm2WnFrL5tirxbkUluLcikVebWCkVebcCkVZbxczLZiW4twKc2wUirzhwKRV4tL9FubS2CkVeFWCIq8Cn1PUJrTWmtNaa01prTWmtNaa01JrTWmtNaa01prTWmtNaa01prTWmtNaa01prTWmtNaa01prTWmtNaa01prTWmtNaak1prTWmtNaa01prTWmtNaa01pqTWmtNaa01prTWmpNaa01prTWmtNaak1JrTWmtNaa01prTWmtNaa01prTU+q1gx2a2aWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWaWI5GKIsfVXJCI/biWMx/wDUMfVTJMH7d/tDH0WG7HNubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3NuNLRMHqMAWbc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Nubc25tzbm3Ei1flf7Qx9FaIpZ9oza5u/M3fmbvzN35m78zd+Zu/M3fmbvzN35m78zd+Zuzd+Zu/M3fmbvzN35m78zd+Zu/M3fmbvzN35m78zcm78zd+Zu/M3fmbvzN35m78zd+Zu/M3fmbvzN35m78zd+Zu/M3fmbvzN35m78zdm78zd+Zu/M3fmbvzN35m78zd+Zu/M3fmbvzN35m78zd+Zs/McqxCor0ndKzQHTDdm5Nybk3JuTdm7N2bk3ZuTdm5N2bk3ZuTdm5N2bk3Juzdm7N2bs3Juzcm7N2bs3JuTcm5Nybk3Zuzcm7Nybs3Juzcm7Nybs3Juzdm5N2bs3ZuTdm5N2bk3JjflFU6OT3/Kf2hj6KAdjue/6/kiKmGl7Po6U96/sR/iSp/k/wC0MfRT/sjl/X8nF+5fR9u/sT70/KX+0MfRT/sjl9YYoDza0uuRBTpxwOHocIgFsdoU+kSxxWgj+N4XqXndEPOJXNVYXyEoWGea2lfi5OL9y+j7d9bVC4iKMFUMQdBjP4Pn2342ZR6j8J2hbESJT+Da0v8AG+9Pyl/tDH0U/wCyOX1yEN3SuBJmwstHSjpJUy2PgJJBpTXCrYrUdjwwWD6ELvVcUuDU2g4EFsm1XPYI4Qco5z1Ff7os7e4u9fs3GvgYdAU+CWATTgix4OGHh6ZYDrmobUFOICC944+9iVmZdBxFBSezigeDtgMDtcOAy4uWLSJ0ymkUdQA0cdaQIzL2v+UvgaSpFMEAjeLF4cMAGbCwEsJ5PTk4v3L6Pt31oRMvUMrxw77WU4nsGgjEYzvNw1LmDVeg4CAeWXXpAUGH9tjGBiykpxPYj2RKw7ggKk4I1Qlqp7jHoj3CW6dBljCGTPApSU/M7/viE0wJ8RXZj2MhAFM6UAvMBShPMspNGTmqx/iFVwYiBiPSCVGIx3cGptES3sgKYItKVZnUE9IIIg7lCnuMrlPcJ4+IZOq+QlAO8ol7HD6vvT8nWf2hj6Kf9kc+p/cVROsA3cvHDexCe2VTo+B1Ldr7uIlynDRj2QV3npips2MKraIaXL0x0Xgt47gZ4AKGOP8Afz/uRG1iVHvCsxuEfaoySWuowY5WNueFTqP8uV9/CxdQLu+k78KGDh6NC9EGkIdzpOEX4AVFKwE63mAEe7naJqPFRiLKz7QK1PiJTGCsdTDK878J0YT1OOTi/cvo+3fUgB5YpZQlTaPDX2sdXmABHu468xIQuBfxP859T/t+Neg2p7ztvEJPwkvhZzHW5hUHuJyGNCbuUWILy6GzFS528xdSea4+54jAjknQ84RTa+7nY8pEr1EzxPxZ90wFQItA6zO1Bwzr/mTJvu50FmoaPL0x03gtnaPLnePI3KPs9EzYyL7uAd4RQAAxDRdERJAHfeO6TVR9wvHq+9Pyl/tDH0U/7I5fV/ljQBywMnCEsJncFd1MwHPYMT6Wk9pZjg7TliYp8k7TiFCrKJiFOzm7OlrJFvQe0d9y54ZEFI2lyITSyFe9mMT4iNLnJCxW8w230WWlUUwvbwGby+OA2ezGg9uOTMxiGYIRxE/7TuUirzyxipn3ie/ztI9iPj6qFquiIbNOzpht5aIfuI5unlLcJZIyt3L6cnF+5fR9u+plWgl4ISHohxcjqF6pTqMxxcA+ydwYMQ1XEhFF+2YbDHjgsUtjD5i8eAMbkWxwLi8B9kKNoHEce6ySt2Eyw06ct84Qr7DET7ExFtWMW4GAU9QqPYdwO3LMUoGk9gEIHq6IVL2w0qimIWPdlbOgjCPUuh9iWoOjU8xJV0TEednEjkxO5dHA9RGc5ioq64ZFgigpyOgtwrHtxGIeGH/7wQGXZ6vvT8pf7Qx9FP8Asjl/JGS4mpvJ/TMnF+5fR9u/qBVlxafeT+T+9Pyl/tDH0U/7o5f1/JxfuX0fbv7E+9Pyl/tDH0UB7FQ/X8kpcuBjte76O4PeP7E147ZYvyf9oY+ioEtOH3gsW/Xn+3/3/wB999t9t99/ttttt/8Afbbbbbff6pFRoPTcRLWSD1CfsJttttttttttttttttttttttttttttttttqyARylrn8o/tDH0WBwpuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3ZuxytWYPUKYZuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bs3Zuzdm7N2bsXyvyv8AaGPqpkmD9u/2hj6qZJg/bv8AaGPqpkmD9u/2hj6qZJg/bjP7Qx9FWVK5I/rN0RPZP6lpJJJJJJJJJJJJpJpNJttNJNJN36VJJJJJJJJJJJJJJJJJJJJFCliXePosxwQjwYj2QnUccFTzOjjpgTrjqP6ZlD85JJJJJJJJJJJJJDiQ50z6ZtscbM1PjjHGzzsj9NWkkkkkkkkkkkkkkkkkkkkkAB0fRZl9S/QVDPDiOIZnmM8S/Rcf2QZjCEuMvh4OGMIZ4Ywh54fqTcuXL5GXxcuX+yrl8XxcWXL9DL4vi5f+ktB3Lh5CxfUQUxpKHHIuJTyKrgW/sagCX6CV+GLv8JrkL4a+m+Lw8xUx7Lg9MO0WsEHVywEW4ZmcDBXby9CdgYPPDxmEV8C+JhMIJsSixUcR/XUsIx5zKPTFiDq4N5JVQtY4EIKuIlPC4QbGCW9oKY9YItzCWJ3MGLcyiqYRy7ZlFwJhCrQs1Hp+mWDKYZmXDBnRihitdxw4MzKJKirwFsYGiTBJTPGdqYZlwrRUuC8kFekld9QXDBT+u4HHlxlMCLwlx3lMnjBYKZlwPMHTPPgb8yxMBnYGAwy4yhnQXlgg7h0MMpkTL6ZCktLi3L6qCkW5aKsvlbiryNcDXFoqwUloKcCkUwUluLfr1paXXC3LQalvQKS0zwIluLcWirBSKZfF2xIxVmIqxbgiKsvqv/wJFSv23X1WZ4IR4I8UcVKgSuKOKOfHBAlEI/sIzGHFHFRIEqBwypUIxlSoFyj6hM8Sz0nLx4nU8zpmJ0xOPHBCVDMf2EcEJ1Hio44OGMIZ4Ywhw/UF9BGHHXoGdcdS51F46qdcE6OH9hE6lwnXHXFzqHDHg4eTh/hU/wDv8QnA6OKeDMYUHE6HIK7iU8UzKUwZ4p9FPFP8GVqoPJMCLQqK+njIjCLAo826J4MOhYK+JV0QbGectY2dEpRuYMsZmcsfwZwIvE8YlCCu2MyIxnzlLy6MOxIK+AX2QKGeXDb2Smu2BC/MC2EOf4NikVfw7eBSW82kteLXi3/VT//EAC0RAAIBAwIFBAMBAQADAQAAAAABERAxYSFBIFGh8PEwcHGRQFCAYLCBoMCx/9oACAEDAQE/EP8A2BZpP4DFSfQmk8U/wa6xWSayTSaTSSaSOs8LFSKSTR8U0kkmkkk0kn3+ik+hHCqQMQ/TkfGxVfA6Rwx7+zVUgj0VViGKjovRjhYqsVWLggj+AnwNcaqxVVVV8U8CHwMXoz/Cs/kyT/DMfkwR/wDBMtEEk1lUmkk0lUkmkqifFPFJNJpNJG6p0ms0kkbE6TqOxNJJrJJNJVJ1rNZpKpKrNZpJI3WaSTSUKsjHSVWazSUK9NxkCpJNJESN0kkmk0lUms0YhE0kYn7POiHRkFkIszcZsIVzcY6qrNjYRubjsKiNx3ptRCHVG4xcTruMQuB2EK4x0RvR2EbjgYyUbcO5djHaiHXQQrjHRUcDHYY6IVNzcYyBXo7oZIlRwNjohDHYVWSh02EK9NKOiEOq9nnSxejJpYubjNhG5YkZIhaE8Ni7GNyKiNxk0kRYYqIZIqSItRidHcdqbliaOwizLjRIh3o7CpuOkmxIhjEWLjpYdHRCGMkVNxjsMZIhjsIsSMkVNx8LGSIsMdqWoyTYkViwqXdHRCHTYXtsv17F/wBO2Sf0U/whvRXpNZE5GxOsk0kmkk0mkjpNJo2TSTejJ1pJNZJrNJJpJNJJpJNGJjsJk0kngmiuyeCaTrSSaSL3b3JEO9LMdLMYqMQ0I0Q2bCoxNUejGI3HelmOl3RXqrjEMVZQi7pYVLCHAhGjHYTXAh2EMdhQOBUu6JCuO9FcdzR03GKBwbC93NxiLOm46XYxWoxDFYQxWNGWpCFcYtWMRvS7HSzor13GIYqbmgizqqXEaU0Y9Kq9UMVhjsI0rZ0kVx3or0aEbjEaUXu5FIpFYEqRRiRFIIpBFIEqJDU1ijVYIrFIIq0QJDRFGiKQRSCKQJVirVIIq0RSBoggaIo1SCKL3pX/AFK3wR+Dv/Dr/D3/AIaX4i4n/wCoe0huXyQnqV+hu7u7u7u7u7u7u7u7u7u7u5q1ZaTqcn/B63hxetRu2QuRCIXIhciFyIXIhciEQuRC5ELkQuRC5EIhEIhELkQuRC5ELkQuRC5ELkQuRC5ELkQuRC5ELkQuRC5EIhckJLZZobBlfwcqWeyf6VYC2n8HdI/0vSe2xuCV6LcEPwW/3nSP0nCEi2hxzFyUvUdpNE29XpPbY7qiJExuCXRTwNxskbglkksnSick6jZLGyWSN/vOkfpOb8AO0YGnrNiFlOwmNA25o9JEU/nkc3ENXRJwq5PUuzGvGVC2acRvR9ds0m7ri76OiU9RNyTDN16XSe2zYNMVqm6qV3VqJEjkakloujVF0WRYQiwdv350j9JHxILyCpxKuN6VIcloFq6oEIoM0tDTFTc6mlyUMTTZKl2SE8mExnZ1zHSHkh1jBHBJb+l0ntsa1VEoIaIY0aiRDTo1WGQ2M1ZGhqJQNajUiUIghsapDQ03+86R+k1kyA8w126XGVfBcfLMvdegxpeBA2lo1PS5D8YNhy23jktyFwS7JXzzHScsW0MnjRJtc9vS6T+DnSP9L0n8HOkf8Z4iatLhIZKf6NpZCQ52on/BzwiUyXLiW432ZH2ZH2ZH2ZH2ZH2ZH2ZH2ZH2ZH2ZH2Zn2ZH2Zn2ZH2ZH2ZH2ZH2ZH2ZH2ZH2ZH2Zn2ZH2ZH2ZH2Zn2ZH2ZH2ZH2Zn2ZH2ZH2ODUn5EkbQaFSS0X/AML7P4k/lzSUTwT+LPvXI6SNirNZo2Jkk1msqrE6TxyTwzXcYkNcFxUkmkk0ms0kmrrvSVRvUTn3iZsIZAx2EhoVGaG4yDcYh3puM0NxmlWIZBZ1VxlkJDRsIZsJV3GJjc1YrCGJEDEiBmhuMUU3NHV13GaEDv7x3ZBZjox2EMVGQjQdNxiN6bjIRCpAqMQ6XdWXYxDNhDNhV3HwsgiB2ESMRIyEaUgRoNCo670gQ7+8aVGqQRSCOCBIapGtIIpGtIEhogVYpAlV2FSCKQRRVgfDFGqQRSBKkCQ0QxIaIq1JAkQNSQJDQv4Gj/qGs1E6vhVNVxMVN/xnXejf4DIYqp+s6qs8aF6TquBi/Dmq/AYuFv2UVNzcsMZsKiNxsVIEXY0bCRuO5aipAyKM2FYSLM3GKkIRdjRsQb0ZZC1LOjNjYSpuMdhKjuMlCox0uRSBkIXBFHRmwqJUY7CEIVhVagbIQqNUZAhFy1GIuWoyKXLDINhIsx0nQgSIU0Yi49KMQhalmMZsJUZBsbCNzcgZZURv7IWJEK4x8KFerFArlmM2Ezc3GKBU3HxIuzcbEMUCuWY2bCZvR0ReuBiZdm42OwqO4+C7IEIksyRmgjRivTejJorGguBDohWFRXGNGguBkiFTSjEIdGSXEMe1FYRuO5oOwnTejEIfChjGzYVGSbGwmK9Nxl0KKb+yMLiXoRWKRwQuGK70isVisKkeiqRWPUisUikcUKsUj0oIpCrBFI9GKx6UUjhikVu6RWOGKxxR7pNYRtngzwJ4E8CeBPAngTwJ4E8CeBPAngTwJ4E8CeBPAngTwJ4E8CeBPAngTwJ4E8CeBPAngTwJ4E8CeBPAngTwJ4E8CeBPAngTwJ4E8CeBPAngTwJ4E8CeBPAngTwJ4E8CeBPAngTwJ4E8CeBPAngTwJ4E8CeBPAngTwJ4E8CeBPAngTwJ4E8CeBPAngTwJ4E8CeBPAngRrUv6vdbccYvJJl/5zVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVWUkxIML191Vn4mMgvkS/wDNsJPVMeuo9Z7qdMzrF/nLHzR65+yzchr+/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbYR1i4rWKjw+Bt4fqNttttvD9VtttttvD4G3h8Dbw/UbbbbbeH6rbbbbbeHRsIS/iWPmj1z9lG0k2yUDNHUWQjimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpikGBiJT3XC6KptCtWzxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFMUxTFOTmbFAP8Ox80eufso99ClX7/pmK5Uz4u4EmOWP8IjnUOff4Vj5o9c/ZTqUWvj9/wBM6DouDr/8I6P8PWPmj1z9lOpRa+ON8kNVgZxNXCNLV6IrUHhkJEuZXA2kMoZSyp+m3Vo+gHSUUieU0kp0bSUsfNTResQBu1Zecx6vTOg6Lg6vjIU4hCpqzHyTRNhwkGz0f/xD1m0jbFBkv0pc8IWmSn6MPOJ9bo/w9Y+aPXP2U6lFr44xnTaSmjc0q49iaHqiT8JI1y26JLiejdNM70LR7oc1Fu47SzAoKJSqluj7DUqac3uyPbbWCGK1CWCsaJ6CSMvMLy7obKxFuxMkxVuLXuV0PfqDiszsRhjncbN/muVaDPjs1W5T6U0pd6I2I0NLsSMk009UIDRI020EzxyodpsJCr4nM2RJjJEonKtxW40auhneVcenZG3D0zoOi4Or40Cywey4511GktOdsuYRtQg7IE6UQJIqW6JebIiaJCzdEMrAK7zsmaJbtXMQww0bSItxgdyuibjkExgNlc1kIS1O1HFHc7ifnRJe0FGedNkS3lGqh2g/BWjYSJU5HZicpOkm+1xm8FabUyIWrWUqmUJBJSLJiYGGFd0SKpNoxqobWCO7QluKalhExXGtJkW4szMSYS0S64uj/D9j5o9c/ZTqUdJxf/rR5BOEEclxvOzFUbtmsLrjsiIEIKiltumSrtqhJiyUBVC+bJuaXRMG7hU5OUfpQN2oeiptGBCbUocMqJFzkZk3QXKRohqmjUzfIKqmZ2k7BrCmk3LXdF3BXElqY1oI6OrORZlq3qKEOrJRFG88tZEFpIPaeOYmribF7DuTfnKGXu1vwzpnQdFwdfxEvNkNdthCaE0PXUSADrMlEE02WGvbkfZO3ZXIsxtakAty79sTT3CtVtNDttZWEQZK6QPFolYIlXkhM7hZXKhLTzYlpRWVb3g1ZjTsWxFESiDRdhba1uRCLmsRqmjX8YpjeyHmm3Y0mncGv5BWZA1JaSXltUc7ThGj2KMdezUExG9zQRJ0kBnB2JLLbuPqfYSQaRyCbfbyI/w78XR/h6x80eufsp1KLXxxfBbGk1AUFiKpY5jxQR6IaWMKJ24ki5ej+yAmzWzGRlrDFZMOWMcYb0dZ4eUWmJE+3MWUQlYO0pFh0G4hMCzIaFA0LsQ43FGkKzI18gIXwricllyjmBAvUVbqOf8AmAhskripYx/VDsQMaoQNgPhLI6im3IWF2xcUIbEAsNjGilmKLojpmpJSd2YjIliGozNmKMKtuHpnQdFwdfxF10YZpJH63BA1lxgSRcTE4edWyOjuXJimsRpB6iar0YgOEPYHd7JCjLru6JNzAXOPXY24laZZlq6dg8HOtSORNXzULFRncSBDly1EuQ9SAAm9BqPgClL7ZMt2NpIljCmhuOqJcoRpu8CMolsWwkjNQH/kIRFuBDopY667qbohBsZq0xBVrC8WYdKtilmorGljXFxWiuxTmzRONy7It8W6P8PWPmj1z9lOpRa+PUhcUocXGlchcEL0IX4ULghUheh0zoOi4Ov9RC9SFSF6cL05Q4uKjNLF+H0f4esfNHrn7KdSi18fv+mdB0XB1f8AhHR/h6x80eufso9dCVX7/pmPAS7Rg5cDyXw/wilN3ohr78Kx80eufso0moY1zC65F8bT+KPtrgbfbVH21R9tUfbVH21R9tUfbXA2+2uBt9tcDb7a9Rttttt9tHbVW+2vUbbbbbfbVH21V7JU5ua4YSZFpjtdZb/4Jtttttttttttttttttttttttttttttttt6czYSVQlvw1j5o9c/ZZ3ymKYpimCYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKRcLQ6xcV0RmCYpimKYpimKYpimKYpimKYpimKYJimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpimKYpYF/EsfNHrn7qdMzrF/nLHzR65+6nTM6xf5yx80eufup0zOoX+csfNHrn7qdMxW/lX+cVv5Cw9d7qP5MLlkDvv8q222222222222998Ft77rb33W3vs77O+zvul774Lb33+uttttttttttttttttttttttqVrUPivWoS9mV+9by1HaSO0kdpI7SR2kjtJHaSO0kdpKi7SR2kqLtJHaSO0kdpI7SR2kjtJHaSO0kdpI7SR2kqLtJfk3UUW22002025dpI7SR2kjtJHaSO0kdpI7SR2kjtJHaSou0kdpI7SR2kjtJHaSO0kdpI7SR2kjtJHaSou0kdpKi7SR2kjtJHaSO0kdpI7SR2kjtJHaSO0kdpIakMPjW2/ZZUjhfA/8i6MfG6odUIdV7oR/jo/4n7NRVnjVhSS1VsVU6N/4bU14E/Tb9JVbii9t7kSbCUlmNalkQJm4hisJjdbsQ2RRcaHYSLssMgdiBfvU4ZNEC1QkMg2EqOipceg2QJi1EXIaHciBWFqWYgkKwlJZjuNQL2yd1Ww3HdDsJIUCu6MVhIijEizGTQqHaiS3IQxqDYhCt+9VHRCHchGwrUY0oLKIYyEKDRiESO6orUOpWEO5uh2Fb2yakgRVqaQiPQa4IVGiERRqSFSFSF++hELgikLgaIVYRCpCpCIGpIRFUtCKJCRCI/8AhflVetvwsX+Cfrv3Pfq70f8AhnR+g6IQx0Qv+S7NFwM2EWq+BUf8QoQi46M2Ey9UIZArCo+B0Zp/DKGIsOjNhVZBZjuSKwhjoqNjo4F/GUf8qf8A/9k=`\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SQLAdvisor/utils/suggestedCommandMaps.tsx",
    "content": "export const SuggestedCommandMaps: Record<\n  string,\n  (params: string[]) => string\n> = {\n  execute_command: (params: string[]) => {\n    return `${params.join(',')}`\n  },\n  table_without_where_condition: (params: string[]) => {\n    return `${params[0]} without where condition in the query`\n  },\n  table_where_condition_with_func: (params: string[]) => {\n    return `Where conditions of the ${params[0]}  using funcs, it would cause index invalid`\n  },\n  query_cannot_be_tuned_find_other_help: () => {\n    return `The query can't be tuned. Please ask DBA for help`\n  },\n  Found_index_in_table: () => {\n    return `Foud correct index in the table`\n  }\n}\n\nexport function getSuggestedCommand(suggestion_key: string, params: string[]) {\n  return SuggestedCommandMaps[suggestion_key]\n    ? SuggestedCommandMaps[suggestion_key](params)\n    : suggestion_key\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/components/Icon.tsx",
    "content": "import {\n  CheckCircleTwoTone,\n  InfoCircleTwoTone,\n  LoadingOutlined\n} from '@ant-design/icons'\nimport React from 'react'\n\nexport function LoadingIcon() {\n  return <LoadingOutlined />\n}\n\nexport function SuccessIcon() {\n  return <CheckCircleTwoTone twoToneColor=\"#52c41a\" />\n}\n\nexport function FailIcon() {\n  return <InfoCircleTwoTone twoToneColor=\"#faad14\" />\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/components/LogRow.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.logRow {\n  padding-left: @padding-page; // 48px\n  padding-right: @padding-page; // 48px\n  padding-top: 11px;\n  padding-bottom: 11px;\n  border-bottom: 1px solid rgb(243, 242, 241); // hardcode from Fluentui\n  animation-duration: 0.367s; // hardcode from Fluentui\n  animation-timing-function: cubic-bezier(\n    0.1,\n    0.25,\n    0.75,\n    0.9\n  ); // hardcode from Fluentui\n  animation-fill-mode: both; // hardcode from Fluentui\n  animation-name: fadeIn;\n\n  font-size: 0.9rem;\n  cursor: pointer;\n\n  position: relative;\n\n  pre {\n    overflow: hidden;\n    text-overflow: ellipsis;\n    display: -webkit-box;\n    -webkit-line-clamp: 8;\n    line-clamp: 8;\n    -webkit-box-orient: vertical;\n  }\n\n  &.isExpanded pre {\n    -webkit-line-clamp: initial;\n    line-clamp: initial;\n  }\n}\n\n@keyframes fadeIn {\n  0% {\n    opacity: 0;\n  }\n  to {\n    opacity: 1;\n  }\n}\n\n.logRow {\n  &:hover {\n    border-left: 5px solid @gray-4;\n    padding-left: @padding-page - 5px;\n  }\n\n  &:hover::before {\n    content: attr(data-level);\n    user-select: none;\n    font-size: 0.8rem;\n    position: absolute;\n    left: 0;\n    top: 0;\n    padding: 0 5px;\n  }\n}\n\n.logRow[data-level='DEBUG'],\n.logRow[data-level='INFO'] {\n  &:hover::before {\n    background-color: @gray-4;\n    color: @gray-7;\n  }\n}\n\n.logRow[data-level='WARN'] {\n  border-left: 5px solid @orange-4;\n  padding-left: @padding-page - 5px;\n\n  &:hover::before {\n    background-color: @orange-4;\n    color: #fff;\n  }\n}\n\n.logRow[data-level='ERROR'],\n.logRow[data-level='CRITICAL'] {\n  border-left: 5px solid @red-6;\n  padding-left: @padding-page - 5px;\n\n  &:hover::before {\n    background-color: @red-6;\n    color: #fff;\n  }\n}\n\n.cell {\n  vertical-align: top;\n}\n\n.textCell {\n  display: inline;\n}\n\n.infoCell {\n  display: inline-block;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  background: @gray-3;\n  padding: 0 5px;\n\n  font-size: 0.8rem;\n  margin-right: 5px;\n  border-bottom: 3px solid @gray-4;\n}\n\n.highlight {\n  background: @gold-3;\n  margin: 0;\n  padding: 0;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/components/LogRow.tsx",
    "content": "import cx from 'classnames'\nimport { ModelRequestTargetNode } from '@lib/client'\nimport {\n  ICellStyleProps,\n  IColumn,\n  IDetailsRowProps\n} from 'office-ui-fabric-react/lib/DetailsList'\nimport React, { useCallback, useState } from 'react'\nimport TextHighlighter from 'react-highlight-words'\nimport styles from './LogRow.module.less'\nimport { Pre } from '@lib/components'\nimport { InstanceKind, instanceKindName } from '@lib/utils/instanceTable'\nimport { hsluvToHex } from 'hsluv'\nimport moize from 'moize'\n\nexport interface ComponentWithSortIndex extends ModelRequestTargetNode {\n  sortIndex: number // range from [0, 1), used to determine component color\n}\n\nexport interface ILogItem {\n  key: number\n  time?: string\n  level?: string\n  component?: ComponentWithSortIndex\n  log?: string\n}\n\nexport interface IRowProps extends IDetailsRowProps {\n  item: ILogItem\n\n  patterns: string[]\n}\n\nexport function LogRow(props: IRowProps) {\n  const [expanded, setExpanded] = useState(false)\n  const handleClick = useCallback(() => {\n    setExpanded((v) => !v)\n  }, [])\n\n  return (\n    <div\n      onClick={handleClick}\n      key={props.item.key}\n      className={cx(styles.logRow, { [styles.isExpanded]: expanded })}\n      data-level={props.item.level}\n    >\n      <LogRowCacheable\n        item={props.item}\n        patterns={props.patterns}\n        cellStyleProps={props.cellStyleProps}\n        columns={props.columns}\n      />\n    </div>\n  )\n}\n\ninterface IRowCacheableProps {\n  // A subset of IRowProps for better caching\n  item: ILogItem\n  patterns: string[]\n  cellStyleProps?: ICellStyleProps\n  columns: IColumn[]\n}\n\n// This component is cached globally (instead of per-instance as React.memo) so that\n// it will work in virtualized lists.\n// When the props are unchanged, this function will always return the same vDOM.\nfunction LogRowCacheable_(props: IRowCacheableProps) {\n  return (\n    <Pre>\n      {props.columns.map((column, columnIdx) => {\n        const colProps: IColProps = {\n          column,\n          columnIdx,\n          ...props\n        }\n        switch (column.key) {\n          case 'component':\n            return <ColumnComponent {...colProps} key={column.key} />\n          case 'log':\n            return <ColumnMessage {...colProps} key={column.key} />\n          default:\n            return (\n              <BaseInfoColumn {...colProps} key={column.key}>\n                {props.item[column.key]}\n              </BaseInfoColumn>\n            )\n        }\n      })}\n    </Pre>\n  )\n}\n\nconst LogRowCacheable = moize(LogRowCacheable_, {\n  isShallowEqual: true,\n  maxArgs: 2,\n  maxSize: 1000\n})\n\ninterface IColProps extends IRowCacheableProps {\n  column: IColumn\n  columnIdx: number\n  children?: React.ReactNode\n  htmlAttributes?: React.HTMLAttributes<HTMLDivElement>\n}\n\nfunction BaseInfoColumn({\n  column,\n  columnIdx,\n  children,\n  htmlAttributes,\n  cellStyleProps\n}: IColProps) {\n  let maxWidth\n  if (column.calculatedWidth) {\n    maxWidth =\n      column.calculatedWidth +\n      (cellStyleProps?.cellLeftPadding ?? 0) +\n      (cellStyleProps?.cellRightPadding ?? 0)\n    if (columnIdx === 0) {\n      maxWidth -= 48 // hardcoded @padding-page\n    }\n  }\n  const { style, className, ...restHtmlAttributes } = htmlAttributes ?? {}\n\n  return (\n    <div\n      className={cx(styles.cell, styles.infoCell, className)}\n      style={{ maxWidth, ...style }}\n      data-column-name={column.key}\n      {...restHtmlAttributes}\n    >\n      {children}\n    </div>\n  )\n}\n\nfunction BaseTextColumn({ column, children, htmlAttributes }: IColProps) {\n  const { className, ...restHtmlAttributes } = htmlAttributes ?? {}\n  return (\n    <div\n      className={cx(styles.cell, styles.textCell, className)}\n      data-column-name={column.key}\n      {...restHtmlAttributes}\n    >\n      {children}\n    </div>\n  )\n}\n\nfunction ColumnMessage(props: IColProps) {\n  return (\n    <BaseTextColumn {...props}>\n      <TextHighlighter\n        highlightClassName={styles.highlight}\n        searchWords={props.patterns.map((p) => new RegExp(p, 'gi'))}\n        textToHighlight={props.item.log}\n      />\n    </BaseTextColumn>\n  )\n}\n\nfunction ColumnComponent(props: IColProps) {\n  const { item } = props\n  if (!item.component) {\n    return null\n  }\n  return (\n    <BaseInfoColumn\n      {...props}\n      htmlAttributes={{\n        style: {\n          borderColor: hsluvToHex([item.component.sortIndex * 360, 60, 85])\n        }\n      }}\n    >\n      {item.component.kind\n        ? instanceKindName(item.component.kind as InstanceKind)\n        : '?'}{' '}\n      {item.component.display_name}\n    </BaseInfoColumn>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/components/SearchHeader.tsx",
    "content": "import {\n  LogsearchCreateTaskGroupRequest,\n  ModelRequestTargetNode\n} from '@lib/client'\nimport { Button, Form, Input, Select, Modal } from 'antd'\nimport React, { useState, useCallback, useRef, useContext } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useNavigate } from 'react-router-dom'\nimport { useMount } from 'ahooks'\nimport {\n  TimeRangeSelector,\n  TimeRange,\n  calcTimeRange,\n  InstanceSelect,\n  IInstanceSelectRefProps\n} from '@lib/components'\n\nimport { ValidLogLevels, LogLevelText } from '../utils'\nimport { SearchLogsContext } from '../context'\n\ninterface Props {\n  taskGroupID?: number\n}\n\ninterface IFormProps {\n  timeRange?: TimeRange\n  logLevel?: number\n  instances?: string[]\n  keywords?: string\n}\n\nexport default function SearchHeader({ taskGroupID }: Props) {\n  const ctx = useContext(SearchLogsContext)\n\n  const { t } = useTranslation()\n  const navigate = useNavigate()\n  const [form] = Form.useForm()\n  const [isSubmitting, setSubmitting] = useState(false)\n  const instanceSelect = useRef<IInstanceSelectRefProps>(null)\n\n  useMount(() => {\n    async function fetchData() {\n      if (!taskGroupID) {\n        return\n      }\n      const res = await ctx!.ds.logsTaskgroupsIdGet(String(taskGroupID))\n      const { task_group, tasks } = res.data\n      const { start_time, end_time, min_level, patterns } =\n        task_group?.search_request ?? {}\n      const fieldsValue: IFormProps = {\n        timeRange: {\n          type: 'absolute',\n          value: [start_time! / 1000, end_time! / 1000]\n        },\n        logLevel: min_level || 2,\n        instances: (tasks ?? [])\n          .filter((t) => t.target && t.target!.display_name)\n          .map((t) => t.target!.display_name!),\n        keywords: (patterns ?? []).join(' ')\n      }\n      form.setFieldsValue(fieldsValue)\n    }\n    fetchData()\n  })\n\n  const handleSearch = useCallback(\n    async (fieldsValue: IFormProps) => {\n      if (\n        !fieldsValue.instances ||\n        fieldsValue.instances.length === 0 ||\n        !fieldsValue.logLevel ||\n        !fieldsValue.timeRange\n      ) {\n        Modal.error({\n          content: 'Some required fields are not filled'\n        })\n        return\n      }\n      if (!instanceSelect.current) {\n        Modal.error({\n          content: 'Internal error: Instance select is not ready'\n        })\n        return\n      }\n\n      const targets: ModelRequestTargetNode[] = instanceSelect\n        .current!.getInstanceByKeys(fieldsValue.instances)\n        .map((instance) => {\n          let port\n          switch (instance.instanceKind) {\n            case 'pd':\n            case 'tikv':\n            case 'tiflash':\n            case 'ticdc':\n            case 'tso':\n            case 'scheduling':\n              port = instance.port\n              break\n            case 'tidb':\n            case 'tiproxy':\n              port = instance.status_port\n              break\n          }\n          return {\n            kind: instance.instanceKind,\n            display_name: instance.key,\n            ip: instance.ip,\n            port\n          }\n        })\n        .filter((i) => i.port != null)\n\n      const [startTime, endTime] = calcTimeRange(fieldsValue.timeRange)\n\n      const req: LogsearchCreateTaskGroupRequest = {\n        targets,\n        request: {\n          start_time: startTime * 1000, // unix millionsecond\n          end_time: endTime * 1000, // unix millionsecond\n          min_level: fieldsValue.logLevel,\n          patterns: (fieldsValue.keywords ?? '').split(/\\s+/) // 'foo boo' => ['foo', 'boo']\n        }\n      }\n\n      try {\n        setSubmitting(true)\n        const result = await ctx!.ds.logsTaskgroupPut(req)\n        const id = result?.data?.task_group?.id\n        if (id) {\n          navigate(`/search_logs/detail?id=${id}`)\n        }\n      } finally {\n        setSubmitting(false)\n      }\n    },\n    [navigate, ctx]\n  )\n\n  return (\n    <Form\n      id=\"search_form\"\n      layout=\"inline\"\n      onFinish={handleSearch}\n      form={form}\n      initialValues={{\n        timeRange: null,\n        logLevel: 2,\n        instances: []\n      }}\n    >\n      <Form.Item name=\"timeRange\" rules={[{ required: true }]}>\n        <TimeRangeSelector />\n      </Form.Item>\n      <Form.Item name=\"logLevel\" rules={[{ required: true }]}>\n        <Select style={{ width: 100 }}>\n          {ValidLogLevels.map((val) => (\n            <Select.Option key={val} value={val}>\n              {LogLevelText[val]}\n            </Select.Option>\n          ))}\n        </Select>\n      </Form.Item>\n      <Form.Item name=\"instances\" rules={[{ required: true }]}>\n        <InstanceSelect\n          ref={instanceSelect}\n          defaultSelectAll\n          enableTiFlash\n          style={{ width: 320 }}\n          data-e2e=\"log_search_instances\"\n          dropContainerProps={\n            { 'data-e2e': 'log_search_instances_drop' } as any\n          }\n          getTiDBTopology={ctx!.ds.getTiDBTopology}\n          getStoreTopology={ctx!.ds.getStoreTopology}\n          getPDTopology={ctx!.ds.getPDTopology}\n          getTiCDCTopology={ctx!.ds.getTiCDCTopology}\n          getTiProxyTopology={ctx!.ds.getTiProxyTopology}\n          getTSOTopology={ctx!.ds.getTSOTopology}\n          getSchedulingTopology={ctx!.ds.getSchedulingTopology}\n        />\n      </Form.Item>\n      <Form.Item name=\"keywords\">\n        <Input\n          data-e2e=\"log_search_keywords\"\n          placeholder={t('search_logs.common.keywords_placeholder')}\n          style={{ width: 300 }}\n        />\n      </Form.Item>\n      <Form.Item>\n        <Button\n          data-e2e=\"log_search_submit\"\n          type=\"primary\"\n          htmlType=\"submit\"\n          loading={isSubmitting}\n        >\n          {t('search_logs.common.search')}\n        </Button>\n      </Form.Item>\n    </Form>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/components/SearchProgress.tsx",
    "content": "import { Button, Modal, Tree } from 'antd'\nimport _ from 'lodash'\nimport React, {\n  useEffect,\n  useState,\n  useMemo,\n  useCallback,\n  useContext\n} from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\n\nimport { LogsearchTaskModel } from '@lib/client'\nimport { AnimatedSkeleton } from '@lib/components'\nimport { FailIcon, LoadingIcon, SuccessIcon } from './Icon'\nimport { TaskState } from '../utils'\n\nimport styles from './Styles.module.less'\nimport { instanceKindName, InstanceKinds } from '@lib/utils/instanceTable'\nimport { SearchLogsContext } from '../context'\n\nconst { confirm } = Modal\nconst taskStateIcons = {\n  [TaskState.Running]: LoadingIcon,\n  [TaskState.Finished]: SuccessIcon,\n  [TaskState.Error]: FailIcon\n}\n\nfunction getLeafNodes(tasks: LogsearchTaskModel[]) {\n  return tasks.map((task) => {\n    const title = (\n      <span>\n        {task.target?.display_name ?? ''}{' '}\n        <small>({getValueFormat('bytes')(task.size!, 1)})</small>\n      </span>\n    )\n    return {\n      key: String(task.id),\n      title,\n      icon: taskStateIcons[task.state || TaskState.Error],\n      disableCheckbox: !task.size || task.state !== TaskState.Finished\n    }\n  })\n}\n\nfunction parentNodeIcon(tasks: LogsearchTaskModel[]) {\n  // Running: has at least one task running\n  if (tasks.some((task) => task.state === TaskState.Running)) {\n    return LoadingIcon\n  }\n  // Finished: all tasks are finished\n  if (!tasks.some((task) => task.state !== TaskState.Finished)) {\n    return SuccessIcon\n  }\n  // Failed: no task is running, and has failed task\n  return FailIcon\n}\n\nfunction parentNodeCheckable(tasks: LogsearchTaskModel[]) {\n  // Checkable: at least one task has finished and the log must not be empty\n  return (\n    tasks.some((task) => task.state === TaskState.Finished) &&\n    tasks.reduce((acc, task) => (acc += task.size || 0), 0) > 0\n  )\n}\n\ninterface Props {\n  taskGroupID: number\n  tasks: LogsearchTaskModel[]\n  toggleReload: () => void\n}\n\nexport default function SearchProgress({\n  taskGroupID,\n  tasks,\n  toggleReload\n}: Props) {\n  const ctx = useContext(SearchLogsContext)\n\n  const [checkedKeys, setCheckedKeys] = useState<string[]>([])\n  const [isLoading, setIsLoading] = useState<boolean>(true)\n\n  const { t } = useTranslation()\n\n  useEffect(() => {\n    if (tasks !== undefined && tasks.length > 0) {\n      setIsLoading(false)\n    }\n  }, [tasks])\n\n  const descriptionArray = useMemo(\n    () => [\n      t('search_logs.progress.running'),\n      t('search_logs.progress.success'),\n      t('search_logs.progress.failed')\n    ],\n    [t]\n  )\n\n  const describeProgress = useCallback(\n    (tasks: LogsearchTaskModel[]) => {\n      const arr = [0, 0, 0]\n      tasks.forEach((task) => {\n        const state = task.state\n        if (state !== undefined) {\n          arr[state - 1]++\n        }\n      })\n      const res: string[] = []\n      arr.forEach((count, index) => {\n        if (index < 1 || count <= 0) {\n          return\n        }\n        const str = `${count} ${descriptionArray[index]}`\n        res.push(str)\n      })\n      return (\n        res.join(', ') +\n        ' (' +\n        getValueFormat('bytes')(_.sumBy(tasks, 'size'), 1) +\n        ')'\n      )\n    },\n    [descriptionArray]\n  )\n\n  const treeData = useMemo(() => {\n    const data: any[] = []\n    const tasksByIK = _.groupBy(tasks, (t) => t.target?.kind)\n    InstanceKinds.forEach((ik) => {\n      const tasks = tasksByIK[ik]\n      if (!tasks) {\n        return\n      }\n      const title = (\n        <span>\n          {instanceKindName(ik)} <small>{describeProgress(tasks)}</small>\n        </span>\n      )\n      data.push({\n        title,\n        key: ik,\n        icon: parentNodeIcon(tasks),\n        disableCheckbox: !parentNodeCheckable(tasks),\n        children: getLeafNodes(tasks)\n      })\n    })\n    return data\n  }, [tasks, describeProgress])\n\n  async function handleDownload() {\n    if (taskGroupID < 0) {\n      return\n    }\n    // filter out all parent node\n    const keys = checkedKeys.filter(\n      (key) => !InstanceKinds.some((ik) => ik === key)\n    )\n\n    const res = await ctx!.ds.logsDownloadAcquireTokenGet(keys)\n    const token = res.data\n    if (!token) {\n      return\n    }\n    const url = `${ctx!.cfg.apiPathBase}/logs/download?token=${token}`\n    window.location.href = url\n  }\n\n  async function handleCancel() {\n    if (taskGroupID < 0) {\n      return\n    }\n    confirm({\n      title: t('search_logs.confirm.cancel_tasks'),\n      onOk() {\n        ctx!.ds.logsTaskgroupsIdCancelPost(taskGroupID + '')\n        toggleReload()\n      }\n    })\n  }\n\n  async function handleRetry() {\n    if (taskGroupID < 0) {\n      return\n    }\n    confirm({\n      title: t('search_logs.confirm.retry_tasks'),\n      onOk() {\n        ctx!.ds.logsTaskgroupsIdRetryPost(taskGroupID + '')\n        toggleReload()\n      }\n    })\n  }\n\n  const handleCheck = useCallback((checkedKeys) => {\n    setCheckedKeys(checkedKeys as string[])\n  }, [])\n\n  return (\n    <AnimatedSkeleton showSkeleton={isLoading}>\n      {tasks && (\n        <>\n          <div>{describeProgress(tasks)}</div>\n          <div className={styles.buttons}>\n            <Button\n              type=\"primary\"\n              onClick={handleDownload}\n              disabled={checkedKeys.length < 1}\n            >\n              {t('search_logs.common.download_selected')}\n            </Button>\n            <Button\n              danger\n              onClick={handleCancel}\n              disabled={!tasks.some((task) => task.state === TaskState.Running)}\n            >\n              {t('search_logs.common.cancel')}\n            </Button>\n            <Button\n              onClick={handleRetry}\n              disabled={\n                tasks.some((task) => task.state === TaskState.Running) ||\n                !tasks.some((task) => task.state === TaskState.Error)\n              }\n            >\n              {t('search_logs.common.retry')}\n            </Button>\n          </div>\n          <Tree\n            checkable\n            expandedKeys={[...InstanceKinds]}\n            selectable={false}\n            defaultExpandAll\n            showIcon\n            onCheck={handleCheck}\n            style={{ overflowX: 'hidden' }}\n            treeData={treeData}\n          />\n        </>\n      )}\n    </AnimatedSkeleton>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/components/SearchResult.tsx",
    "content": "import { LogsearchTaskModel } from '@lib/client'\nimport { CardTable, Card } from '@lib/components'\nimport { Alert } from 'antd'\nimport React, {\n  useEffect,\n  useState,\n  useMemo,\n  useCallback,\n  useContext\n} from 'react'\nimport { useTranslation } from 'react-i18next'\nimport dayjs from 'dayjs'\nimport { LogLevelText } from '../utils'\nimport {\n  DetailsListLayoutMode,\n  IColumn,\n  IDetailsRowProps\n} from 'office-ui-fabric-react/lib/DetailsList'\nimport { ComponentWithSortIndex, ILogItem, LogRow } from './LogRow'\nimport { sortBy } from 'lodash'\nimport { SearchLogsContext } from '../context'\nimport { tz } from '@lib/utils'\n\ninterface Props {\n  patterns: string[]\n  taskGroupID: number\n  tasks: LogsearchTaskModel[]\n}\n\nexport default function SearchResult({ patterns, taskGroupID, tasks }: Props) {\n  const ctx = useContext(SearchLogsContext)\n\n  const [logPreviews, setData] = useState<ILogItem[]>([])\n  const { t } = useTranslation()\n  const [loading, setLoading] = useState(true)\n\n  const componentByTaskId = useMemo(() => {\n    const sortedComponents = sortBy(\n      tasks.map((t) => ({\n        ...t.target,\n        sortIndex: 0,\n        taskId: t.id\n      })),\n      (target) => `${target?.kind} ${target?.display_name}`\n    )\n\n    sortedComponents.forEach((c, idx) => {\n      c.sortIndex = idx / sortedComponents.length\n    })\n\n    const byTaskId: Record<number, ComponentWithSortIndex> = {}\n    sortedComponents.forEach((c) => {\n      byTaskId[c.taskId ?? -1] = c\n    })\n    return byTaskId\n  }, [tasks])\n\n  useEffect(() => {\n    async function getLogPreview() {\n      if (!taskGroupID) {\n        return\n      }\n      try {\n        const res = await ctx!.ds.logsTaskgroupsIdPreviewGet(taskGroupID + '')\n        setData(\n          res.data.map((value, index): ILogItem => {\n            return {\n              key: index,\n              time: dayjs(value.time)\n                .utcOffset(tz.getTimeZone())\n                .format('YYYY-MM-DD HH:mm:ss (UTCZ)'),\n              level: LogLevelText[value.level ?? 0],\n              component: componentByTaskId[value.task_id ?? -1],\n              log: value.message\n            }\n          })\n        )\n      } finally {\n        setLoading(false)\n      }\n    }\n    if (tasks.length > 0 && taskGroupID !== tasks[0].task_group_id) {\n      setLoading(true)\n    }\n    getLogPreview()\n  }, [taskGroupID, componentByTaskId, tasks, ctx])\n\n  const renderRow = useCallback(\n    (props?: IDetailsRowProps, defaultRender?) => {\n      if (!props) {\n        return null\n      }\n      return <LogRow patterns={patterns} {...props} />\n    },\n    [patterns]\n  )\n\n  const columns = useMemo<IColumn[]>(\n    () => [\n      {\n        name: t('search_logs.preview.time'),\n        key: 'time',\n        fieldName: 'time',\n        minWidth: 120,\n        maxWidth: 200\n      },\n      {\n        name: t('search_logs.preview.component'),\n        key: 'component',\n        minWidth: 40,\n        maxWidth: 150\n      },\n      {\n        name: t('search_logs.preview.log'),\n        key: 'log',\n        minWidth: 100,\n        maxWidth: 100,\n        isResizable: false\n      }\n    ],\n    [t]\n  )\n\n  return (\n    <div data-e2e=\"log_search_result\">\n      {!loading && (\n        <Card noMarginTop>\n          <Alert message={t('search_logs.page.tip')} type=\"info\" showIcon />\n        </Card>\n      )}\n      <CardTable\n        cardNoMarginTop\n        loading={loading}\n        columns={columns}\n        items={logPreviews || []}\n        onRenderRow={renderRow}\n        extendLastColumn\n        hideLoadingWhenNotEmpty\n        layoutMode={DetailsListLayoutMode.fixedColumns}\n      />\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/components/Styles.module.less",
    "content": "// FIXME: Use <Space>\n.buttons {\n  margin-top: 12px;\n}\n\n.buttons > :global(button) {\n  margin-right: 12px;\n  margin-bottom: 12px;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/components/index.ts",
    "content": "import SearchHeader from './SearchHeader'\nimport SearchProgress from './SearchProgress'\nimport SearchResult from './SearchResult'\n\nexport { SearchHeader, SearchProgress, SearchResult }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport {\n  LogsearchCreateTaskGroupRequest,\n  LogsearchTaskGroupResponse,\n  LogsearchTaskGroupModel,\n  LogsearchPreviewModel,\n  TopologyTiDBInfo,\n  ClusterinfoStoreTopologyResponse,\n  TopologyPDInfo,\n  TopologyTiCDCInfo,\n  TopologyTiProxyInfo,\n  TopologyTSOInfo,\n  TopologySchedulingInfo\n} from '@lib/client'\n\nimport { IContextConfig, ReqConfig } from '@lib/types'\n\nexport interface ISearchLogsDataSource {\n  logsDownloadAcquireTokenGet(\n    id?: Array<string>,\n    options?: ReqConfig\n  ): AxiosPromise<string>\n\n  // logsDownloadGet(token: string, options?: ReqConfig): AxiosPromise<void>\n\n  logsTaskgroupPut(\n    request: LogsearchCreateTaskGroupRequest,\n    options?: ReqConfig\n  ): AxiosPromise<LogsearchTaskGroupResponse>\n\n  logsTaskgroupsGet(\n    options?: ReqConfig\n  ): AxiosPromise<Array<LogsearchTaskGroupModel>>\n\n  logsTaskgroupsIdCancelPost(\n    id: string,\n    options?: ReqConfig\n  ): AxiosPromise<object>\n\n  logsTaskgroupsIdDelete(id: string, options?: ReqConfig): AxiosPromise<object>\n\n  logsTaskgroupsIdGet(\n    id: string,\n    options?: ReqConfig\n  ): AxiosPromise<LogsearchTaskGroupResponse>\n\n  logsTaskgroupsIdPreviewGet(\n    id: string,\n    options?: ReqConfig\n  ): AxiosPromise<Array<LogsearchPreviewModel>>\n\n  logsTaskgroupsIdRetryPost(\n    id: string,\n    options?: ReqConfig\n  ): AxiosPromise<object>\n\n  getTiDBTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTiDBInfo>>\n\n  getStoreTopology(\n    options?: ReqConfig\n  ): AxiosPromise<ClusterinfoStoreTopologyResponse>\n\n  getPDTopology(options?: ReqConfig): AxiosPromise<Array<TopologyPDInfo>>\n\n  getTiCDCTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTiCDCInfo>>\n\n  getTiProxyTopology(\n    options?: ReqConfig\n  ): AxiosPromise<Array<TopologyTiProxyInfo>>\n\n  getTSOTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTSOInfo>>\n\n  getSchedulingTopology(\n    options?: ReqConfig\n  ): AxiosPromise<Array<TopologySchedulingInfo>>\n}\n\nexport interface ISearchLogsContext {\n  ds: ISearchLogsDataSource\n  cfg: IContextConfig\n}\n\nexport const SearchLogsContext = createContext<ISearchLogsContext | null>(null)\n\nexport const SearchLogsProvider = SearchLogsContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { Root, ParamsPageWrapper } from '@lib/components'\nimport { HashRouter as Router, Route, Routes } from 'react-router-dom'\n\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\nimport { addTranslations } from '@lib/utils/i18n'\n\nimport { LogSearch, LogSearchHistory, LogSearchDetail } from './pages'\nimport { SearchLogsContext } from './context'\nimport translations from './translations'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/search_logs\" element={<LogSearch />} />\n      <Route path=\"/search_logs/history\" element={<LogSearchHistory />} />\n      <Route\n        path=\"/search_logs/detail\"\n        element={\n          <ParamsPageWrapper>\n            <LogSearchDetail />\n          </ParamsPageWrapper>\n        }\n      />\n    </Routes>\n  )\n}\n\nexport default function () {\n  const ctx = useContext(SearchLogsContext)\n  if (ctx === null) {\n    throw new Error('SearchLogsContext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/pages/LogSearch.tsx",
    "content": "import { Empty } from 'antd'\nimport React from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { Link } from 'react-router-dom'\n\nimport { Card } from '@lib/components'\nimport { SearchHeader } from '../components'\n\nexport default function LogSearch() {\n  const { t } = useTranslation()\n\n  return (\n    <div>\n      <Card>\n        <SearchHeader />\n      </Card>\n      <Empty description={t('search_logs.page.intro')}>\n        {t('search_logs.page.view')}{' '}\n        <Link to=\"/search_logs/history\">\n          {t('search_logs.page.search_histroy')}\n        </Link>\n      </Empty>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/pages/LogSearchDetail.tsx",
    "content": "import { Button, Drawer } from 'antd'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport React, { useContext, useEffect, useMemo, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { Link } from 'react-router-dom'\nimport { ArrowLeftOutlined } from '@ant-design/icons'\n\nimport { Head } from '@lib/components'\nimport { useClientRequestWithPolling } from '@lib/utils/useClientRequest'\nimport { SearchHeader, SearchProgress, SearchResult } from '../components'\nimport { TaskState } from '../utils'\nimport useQueryParams from '@lib/utils/useQueryParams'\nimport { SearchLogsContext } from '../context'\n\nexport default function LogSearchingDetail() {\n  const ctx = useContext(SearchLogsContext)\n\n  const { t } = useTranslation()\n  const { id } = useQueryParams()\n  const [reloadKey, setReloadKey] = useState(false)\n  const [taskWasUnfinished, setTaskUnfinished] = useState(false)\n  const [sidebarOpen, setSidebarOpen] = useState<boolean | undefined>(undefined)\n\n  function toggleReload() {\n    setReloadKey(!reloadKey)\n  }\n\n  const taskGroupID = id === undefined ? 0 : +id\n\n  function isFinished(data) {\n    if (taskGroupID < 0) {\n      return true\n    }\n    if (!data) {\n      return false\n    }\n    if (data.tasks.some((task) => task.state === TaskState.Running)) {\n      return false\n    }\n    return true\n  }\n\n  const { data } = useClientRequestWithPolling(\n    (reqConfig) => ctx!.ds.logsTaskgroupsIdGet(id, reqConfig),\n    {\n      shouldPoll: (data) => !isFinished(data)\n    }\n  )\n\n  const tasks = useMemo(() => data?.tasks ?? [], [data])\n\n  useEffect(() => {\n    for (const task of data?.tasks ?? []) {\n      if (task.state !== TaskState.Finished) {\n        setTaskUnfinished(true)\n        break\n      }\n    }\n  }, [data])\n\n  return (\n    <>\n      <div\n        style={{\n          display: 'flex',\n          flexDirection: 'column',\n          height: '100vh'\n        }}\n      >\n        <Head\n          title={t('search_logs.nav.detail')}\n          back={\n            <Link to={`/search_logs`}>\n              <ArrowLeftOutlined /> {t('search_logs.nav.search_logs')}\n            </Link>\n          }\n          titleExtra={\n            <Button type=\"primary\" onClick={() => setSidebarOpen(true)}>\n              {t('search_logs.nav.show_sidebar')}\n            </Button>\n          }\n        ></Head>\n        <div style={{ height: '100%', position: 'relative', marginRight: 4 }}>\n          <ScrollablePane>\n            <div style={{ marginLeft: 48, marginRight: 48, marginBottom: 24 }}>\n              <SearchHeader taskGroupID={taskGroupID} />\n            </div>\n            <SearchResult\n              patterns={data?.task_group?.search_request?.patterns || []}\n              taskGroupID={taskGroupID}\n              tasks={tasks}\n            />\n          </ScrollablePane>\n        </div>\n      </div>\n      <Drawer\n        mask={false}\n        visible={sidebarOpen ?? taskWasUnfinished}\n        width={350}\n        title={t('search_logs.common.progress')}\n        onClose={() => setSidebarOpen(false)}\n      >\n        <SearchProgress\n          key={`${reloadKey}`}\n          toggleReload={toggleReload}\n          taskGroupID={taskGroupID}\n          tasks={tasks}\n        />\n      </Drawer>\n    </>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/pages/LogSearchHistory.tsx",
    "content": "import { LogsearchTaskGroupModel } from '@lib/client'\nimport { Head, CardTable, DateTime } from '@lib/components'\nimport { ArrowLeftOutlined, ExclamationCircleOutlined } from '@ant-design/icons'\nimport { Badge, Button, Modal, Space } from 'antd'\nimport React, { useContext, useEffect, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { Link } from 'react-router-dom'\nimport {\n  Selection,\n  SelectionMode\n} from 'office-ui-fabric-react/lib/DetailsList'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport { LogLevelText } from '../utils'\nimport { SearchLogsContext } from '../context'\n\nfunction componentRender({ target_stats: stats, t }) {\n  // FIXME: Extract common util\n  const r: Array<string> = []\n  if (stats?.num_tidb_nodes) {\n    r.push(`${stats.num_tidb_nodes} ${t('distro.tidb')}`)\n  }\n  if (stats?.num_tikv_nodes) {\n    r.push(`${stats.num_tikv_nodes} ${t('distro.tikv')}`)\n  }\n  if (stats?.num_pd_nodes) {\n    r.push(`${stats.num_pd_nodes} ${t('distro.pd')}`)\n  }\n  return <span>{r.join(', ')}</span>\n}\n\nfunction timeRender({ search_request }: LogsearchTaskGroupModel) {\n  return (\n    <span>\n      {search_request?.start_time && (\n        <DateTime.Calendar unixTimestampMs={search_request?.start_time} />\n      )}\n      {' ~ '}\n      {search_request?.end_time && (\n        <DateTime.Calendar unixTimestampMs={search_request?.end_time} />\n      )}\n    </span>\n  )\n}\n\nfunction levelRender({ search_request: request }: LogsearchTaskGroupModel) {\n  return LogLevelText[request?.min_level!]\n}\n\nfunction patternRender({ search_request: request }: LogsearchTaskGroupModel) {\n  return (request?.patterns ?? []).join(' ')\n}\n\nexport default function LogSearchingHistory() {\n  const ctx = useContext(SearchLogsContext)\n\n  const [taskGroups, setTaskGroups] = useState<LogsearchTaskGroupModel[]>([])\n  const [selectedRowKeys, setRowKeys] = useState<string[]>([])\n\n  const { t } = useTranslation()\n\n  useEffect(() => {\n    async function getData() {\n      const res = await ctx!.ds.logsTaskgroupsGet()\n      setTaskGroups(res.data)\n    }\n\n    getData()\n  }, [ctx])\n\n  function stateRender({ state }: LogsearchTaskGroupModel) {\n    switch (state) {\n      case 1:\n        return (\n          <Badge status=\"processing\" text={t('search_logs.history.running')} />\n        )\n      case 2:\n        return (\n          <Badge status=\"success\" text={t('search_logs.history.finished')} />\n        )\n      default:\n        return\n    }\n  }\n\n  function actionRender(taskGroup: LogsearchTaskGroupModel) {\n    if (taskGroup.id === 0) {\n      return\n    }\n    return (\n      <Link to={`/search_logs/detail?id=${taskGroup.id}`}>\n        {t('search_logs.history.detail')}\n      </Link>\n    )\n  }\n\n  async function handleDeleteSelected() {\n    Modal.confirm({\n      title: t('search_logs.history.delete_confirm_title'),\n      icon: <ExclamationCircleOutlined />,\n      content: t('search_logs.history.delete_selected_confirm_content'),\n      okText: t('search_logs.history.delete'),\n      cancelText: t('search_logs.common.cancel'),\n      okButtonProps: { danger: true },\n      onOk: async () => {\n        for (const taskGroupID of selectedRowKeys) {\n          await ctx!.ds.logsTaskgroupsIdDelete(taskGroupID)\n        }\n        const res = await ctx!.ds.logsTaskgroupsGet()\n        setTaskGroups(res.data)\n      }\n    })\n  }\n\n  async function handleDeleteAll() {\n    Modal.confirm({\n      title: t('search_logs.history.delete_confirm_title'),\n      icon: <ExclamationCircleOutlined />,\n      content: t('search_logs.history.delete_all_confirm_content'),\n      okText: t('search_logs.history.delete'),\n      cancelText: t('search_logs.common.cancel'),\n      okButtonProps: { danger: true },\n      onOk: async () => {\n        const allKeys = taskGroups.map((taskGroup) => taskGroup.id)\n        for (const key of allKeys) {\n          if (key === undefined) {\n            continue\n          }\n          await ctx!.ds.logsTaskgroupsIdDelete(String(key))\n        }\n        const res = await ctx!.ds.logsTaskgroupsGet()\n        setTaskGroups(res.data)\n      }\n    })\n  }\n\n  const rowSelection = new Selection({\n    onSelectionChanged: () => {\n      const items = rowSelection.getSelection() as LogsearchTaskGroupModel[]\n      setRowKeys(items.map((item) => item.id!.toString()))\n    }\n  })\n\n  const columns = [\n    {\n      name: t('search_logs.common.time_range'),\n      key: 'time',\n      minWidth: 200,\n      maxWidth: 300,\n      onRender: timeRender\n    },\n    {\n      name: t('search_logs.preview.level'),\n      key: 'level',\n      minWidth: 70,\n      maxWidth: 120,\n      onRender: levelRender\n    },\n    {\n      name: t('search_logs.history.instances'),\n      key: 'target_stats',\n      minWidth: 100,\n      maxWidth: 250,\n      onRender: (p) => componentRender({ ...p, t })\n    },\n    {\n      name: t('search_logs.common.keywords'),\n      key: 'keywords',\n      minWidth: 100,\n      maxWidth: 200,\n      onRender: patternRender\n    },\n    {\n      name: t('search_logs.history.status'),\n      key: 'state',\n      minWidth: 100,\n      maxWidth: 150,\n      onRender: stateRender\n    },\n    {\n      name: t('search_logs.history.action'),\n      key: 'action',\n      minWidth: 100,\n      maxWidth: 200,\n      onRender: actionRender\n    }\n  ]\n\n  return (\n    <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>\n      <Head\n        title={t('search_logs.nav.history')}\n        back={\n          <Link to={`/search_logs`}>\n            <ArrowLeftOutlined /> {t('search_logs.nav.search_logs')}\n          </Link>\n        }\n        titleExtra={\n          <Space>\n            <Button\n              danger\n              onClick={handleDeleteSelected}\n              disabled={selectedRowKeys.length === 0}\n            >\n              {t('search_logs.history.delete_selected')}\n            </Button>\n            <Button\n              danger\n              onClick={handleDeleteAll}\n              disabled={taskGroups?.length === 0}\n            >\n              {t('search_logs.history.delete_all')}\n            </Button>\n          </Space>\n        }\n      />\n      <div style={{ height: '100%', position: 'relative' }}>\n        <ScrollablePane>\n          <CardTable\n            cardNoMarginTop\n            cardNoMarginBottom\n            columns={columns}\n            items={taskGroups || []}\n            selection={rowSelection}\n            selectionMode={SelectionMode.multiple}\n          />\n        </ScrollablePane>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/pages/index.ts",
    "content": "import LogSearch from './LogSearch'\nimport LogSearchHistory from './LogSearchHistory'\nimport LogSearchDetail from './LogSearchDetail'\n\nexport { LogSearch, LogSearchHistory, LogSearchDetail }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/translations/en.yaml",
    "content": "search_logs:\n  nav_title: Search Logs\n  nav:\n    search_logs: Search Logs\n    detail: Search Result\n    history: History\n    show_sidebar: Download or Show Progress\n  page:\n    intro: Preview and download logs by clicking Search\n    tip: The preview shows only the first 500 logs\n    view: View\n    search_histroy: search histroy\n  common:\n    time_range: Time Range\n    start_time: Start Time\n    end_time: End Time\n    log_level: Log Level\n    components: instances\n    keywords: Keywords\n    keywords_placeholder: Keywords, Optional, separated by spaces\n    search: Search\n    progress: Progress\n    download_selected: Download selected\n    cancel: Cancel\n    retry: Retry\n  progress:\n    running: running\n    success: completed\n    failed: failed\n  confirm:\n    cancel_tasks: Are you sure you want to cancel all running log search tasks?\n    retry_tasks: Are you sure you want to retry all failed log search tasks?\n  preview:\n    time: Time\n    level: Level\n    component: Component\n    log: Log\n  history:\n    instances: Instances\n    running: Running\n    finished: Finished\n    delete_selected: Delete selected\n    delete_all: Delete All\n    status: Status\n    action: Action\n    detail: Detail\n    delete: Delete\n    delete_confirm_title: Delete Log Search Histories\n    delete_selected_confirm_content: Are you sure you want to delete selected log search histories?\n    delete_all_confirm_content: Are you sure you want to delete all log search histories?\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/translations/zh.yaml",
    "content": "search_logs:\n  nav_title: 日志搜索\n  nav:\n    search_logs: 日志搜索\n    detail: 搜索结果\n    history: 历史搜索\n    show_sidebar: 下载或显示进度\n  page:\n    intro: 点击搜索预览和下载日志\n    tip: 预览仅显示前 500 项日志\n    view: 查看\n    search_histroy: 搜索历史\n  common:\n    time_range: 时间范围\n    start_time: 起始时间\n    end_time: 结束时间\n    log_level: 日志等级\n    components: 选择实例\n    keywords: 关键字\n    keywords_placeholder: 搜索关键字，可选，以空格分割\n    search: 搜索\n    progress: 搜索进度\n    download_selected: 下载选中日志\n    cancel: 取消\n    retry: 重试\n  progress:\n    running: 正在运行\n    success: 成功\n    failed: 失败\n  confirm:\n    cancel_tasks: 确认要取消正在运行的日志搜索任务么？\n    retry_tasks: 确认要重试所有失败的日志搜索任务么？\n  preview:\n    time: 时间\n    level: 日志等级\n    component: 组件\n    log: 日志\n  history:\n    instances: 实例\n    running: 正在搜索\n    finished: 已完成\n    delete_selected: 删除选中的任务\n    delete_all: 删除全部任务\n    status: 状态\n    action: 操作\n    detail: 查看详情\n    delete: 删除\n    delete_confirm_title: 删除搜索历史\n    delete_selected_confirm_content: 确认要删除选中的搜索历史吗？\n    delete_all_confirm_content: 确认要删除所有的搜索历史吗？\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SearchLogs/utils/index.ts",
    "content": "export enum LogLevel {\n  Unknown = 0,\n  Debug,\n  Info,\n  Warn,\n  Trace,\n  Critical,\n  Error\n}\n\nexport const LogLevelText = {\n  [LogLevel.Unknown]: 'UNKNOWN',\n  [LogLevel.Debug]: 'DEBUG',\n  [LogLevel.Info]: 'INFO',\n  [LogLevel.Warn]: 'WARN',\n  [LogLevel.Trace]: 'TRACE',\n  [LogLevel.Critical]: 'CRITICAL',\n  [LogLevel.Error]: 'ERROR'\n}\n\nexport const ValidLogLevels = [\n  LogLevel.Debug,\n  LogLevel.Info,\n  LogLevel.Warn,\n  // LogLevel.Trace,\n  LogLevel.Critical,\n  LogLevel.Error\n]\n\nexport enum TaskState {\n  Running = 1,\n  Finished,\n  Error\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/components/LimitTimeRange.tsx",
    "content": "import { TimeRange, TimeRangeSelector } from '@lib/components'\nimport dayjs from 'dayjs'\nimport React, { useState } from 'react'\n\ninterface LimitTimeRangeProps {\n  value: TimeRange\n  onChange: (val: TimeRange) => void\n}\n\ntype RangeValue = [dayjs.Dayjs | null, dayjs.Dayjs | null] | null\n\nconst RECENT_SECONDS = [10 * 60, 30 * 60, 60 * 60]\n\nexport const LimitTimeRange: React.FC<LimitTimeRangeProps> = ({\n  value,\n  onChange\n}) => {\n  const [dates, setDates] = useState<RangeValue>(null)\n\n  const disabledDate = (current: dayjs.Dayjs) => {\n    if (!dates) {\n      return false\n    }\n\n    const inOneDay = dates[0] && dates[0].hour() < 23\n    const inOneDayTooLate =\n      dates[0] && inOneDay && current.diff(dates[0], 'day') > 0\n    const tooLate = dates[0] && !inOneDay && current.diff(dates[0], 'day') === 1\n\n    const inOneDay2 = dates[1] && dates[1].hour() > 0\n    const inOneDay2TooEarly =\n      dates[1] && inOneDay2 && dates[1].diff(current, 'day') > 0\n    const tooEarly =\n      dates[1] && !inOneDay2 && dates[1].diff(current, 'day') === 1\n\n    return !!inOneDayTooLate || !!tooLate || !!inOneDay2TooEarly || !!tooEarly\n  }\n\n  const disabledTime = (_, type) => {\n    if (type === 'start' && dates?.[1]) {\n      const h = dates[1].hour()\n      return {\n        disabledHours: () => range(0, 60).filter((r) => r !== h - 1),\n        disabledMinutes: () => [],\n        disabledSeconds: () => []\n      }\n    }\n    if (type === 'end' && dates?.[0]) {\n      const h = dates[0].hour()\n      return {\n        disabledHours: () => range(0, 60).filter((r) => r !== h + 1),\n        disabledMinutes: () => [],\n        disabledSeconds: () => []\n      }\n    }\n    return {\n      disabledHours: () => [],\n      disabledMinutes: () => [],\n      disabledSeconds: () => []\n    }\n  }\n\n  const onOpenChange = (open: boolean) => {\n    if (open) {\n      setDates([null, null])\n    } else {\n      setDates(null)\n    }\n  }\n\n  return (\n    <TimeRangeSelector\n      recent_seconds={RECENT_SECONDS}\n      value={value}\n      onChange={onChange}\n      disabledDate={disabledDate}\n      disabledTime={disabledTime}\n      onCalendarChange={(val) => setDates(val)}\n      onOpenChange={onOpenChange}\n    />\n  )\n}\n\nconst range = (start: number, end: number) => {\n  const result: number[] = []\n  for (let i = start; i < end; i++) {\n    result.push(i)\n  }\n  return result\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/components/SlowQueriesTable.tsx",
    "content": "import { useMemoizedFn } from 'ahooks'\nimport React, { useCallback, useContext } from 'react'\nimport { CardTable, ICardTableProps } from '@lib/components'\nimport { ISlowQueryTableController } from '../utils/useSlowQueryTableController'\nimport openLink from '@lib/utils/openLink'\nimport { useNavigate } from 'react-router-dom'\n\nimport { SlowQueryContext } from '../context'\n\ninterface Props extends Partial<ICardTableProps> {\n  controller: ISlowQueryTableController\n  detailPathPrefix?: string\n}\n\nfunction SlowQueriesTable({\n  controller,\n  detailPathPrefix = '/slow_query/detail',\n  ...restProps\n}: Props) {\n  const ctx = useContext(SlowQueryContext)\n  const navigate = useNavigate()\n  const handleRowClick = useMemoizedFn(\n    (rec, idx, ev: React.MouseEvent<HTMLElement>) => {\n      ctx?.event?.selectSlowQueryItem(rec)\n      controller.saveClickedItemIndex(idx)\n      openLink(\n        `/slow_query/detail?digest=${rec.digest}&connection_id=${rec.connection_id}&timestamp=${rec.timestamp}`,\n        ev,\n        navigate\n      )\n    }\n  )\n\n  const getKey = useCallback((row) => `${row.digest}_${row.timestamp}`, [])\n\n  return (\n    <CardTable\n      {...restProps}\n      loading={controller.isLoading}\n      columns={controller.availableColumnsInTable}\n      items={controller.data ?? []}\n      orderBy={controller.orderOptions.orderBy}\n      desc={controller.orderOptions.desc}\n      onChangeOrder={controller.changeOrder}\n      errors={controller.errors}\n      visibleColumnKeys={controller.queryOptions.visibleColumnKeys}\n      onRowClicked={handleRowClick}\n      clickedRowIndex={controller.getClickedItemIndex()}\n      getKey={getKey}\n      data-e2e=\"detail_tabs_slow_query\"\n    />\n  )\n}\n\nexport default SlowQueriesTable\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/components/index.ts",
    "content": "import SlowQueriesTable from './SlowQueriesTable'\n\nexport { SlowQueriesTable }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport { SlowqueryModel, SlowqueryGetListRequest } from '@lib/client'\n\nimport { IContextConfig, ReqConfig } from '@lib/types'\nimport { PromDataSuccessResponse } from '@lib/utils'\n\nexport interface ISlowQueryDataSource {\n  getDatabaseList(\n    beginTime: number,\n    endTime: number,\n    options?: ReqConfig\n  ): AxiosPromise<Array<string>>\n\n  infoListResourceGroupNames(options?: ReqConfig): AxiosPromise<Array<string>>\n\n  slowQueryAvailableFieldsGet(options?: ReqConfig): AxiosPromise<Array<string>>\n\n  slowQueryListGet(\n    beginTime?: number,\n    db?: Array<string>,\n    desc?: boolean,\n    digest?: string,\n    endTime?: number,\n    fields?: string,\n    limit?: number,\n    orderBy?: string,\n    plans?: Array<string>,\n    resourceGroup?: Array<string>,\n    text?: string,\n    showInternal?: boolean,\n    options?: ReqConfig\n  ): AxiosPromise<Array<SlowqueryModel>>\n\n  slowQueryDetailGet(\n    connectId?: string,\n    digest?: string,\n    timestamp?: number,\n    options?: ReqConfig\n  ): AxiosPromise<SlowqueryModel>\n\n  slowQueryDownloadTokenPost(\n    request: SlowqueryGetListRequest,\n    options?: ReqConfig\n  ): AxiosPromise<string>\n\n  slowQueryAnalyze?(start: number, end: number): AxiosPromise\n\n  slowQueryDownloadDBFile?(begin_time: number, end_time: number): AxiosPromise\n\n  promqlQuery?(\n    query: string,\n    time: number,\n    timeout: string\n  ): AxiosPromise<PromDataSuccessResponse>\n\n  promqlQueryRange?(\n    query: string,\n    start: number,\n    end: number,\n    step: string\n  ): AxiosPromise<PromDataSuccessResponse>\n}\n\nexport interface ISlowQueryEvent {\n  selectSlowQueryItem(item: SlowqueryModel): void\n}\n\nexport interface ISlowQueryConfig extends IContextConfig {\n  enableExport: boolean\n  showDBFilter: boolean\n  showResourceGroupFilter: boolean\n  showDigestFilter: boolean\n  showHelp?: boolean\n\n  // true means the list api will return all fields value of an item, not just the selected fields\n  // in this case, the detail page doesn't need to request detail api any more\n  listApiReturnDetail?: boolean\n\n  // true means start to search instantly after changing any filter options\n  // false means only to start searching after clicking the \"Query\" button\n  instantQuery?: boolean\n\n  // to limit the time range picker range\n  timeRangeSelector?: {\n    recentSeconds: number[]\n    customAbsoluteRangePicker: boolean\n    timeRangeLimit?: number\n  }\n\n  // for clinic\n  orgName?: string\n  clusterName?: string\n  showTopSlowQueryLink?: boolean\n  showDownloadSlowQueryDBFile?: boolean\n\n  // show internal slow queries\n  showInternalFilter?: boolean\n\n  // show RU V2 fields (clinic-only: ru_v2, ru_v2_detail columns,\n  // Basic-tab rows, and the dedicated RU V2 Metrics tab)\n  showRuV2?: boolean\n}\n\nexport interface ISlowQueryContext {\n  ds: ISlowQueryDataSource\n  event?: ISlowQueryEvent\n  cfg: ISlowQueryConfig\n}\n\nexport const SlowQueryContext = createContext<ISlowQueryContext | null>(null)\n\nexport const SlowQueryProvider = SlowQueryContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { Root } from '@lib/components'\nimport { HashRouter as Router, Route, Routes } from 'react-router-dom'\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query'\n\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\nimport { addTranslations } from '@lib/utils/i18n'\n\nimport { List, Detail } from './pages'\nimport { SlowQueryContext } from './context'\nimport translations from './translations'\n\naddTranslations(translations)\n\n// Create a client\nconst queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      refetchOnWindowFocus: false,\n      retry: 1\n      // refetchOnMount: false,\n      // refetchOnReconnect: false,\n    }\n  }\n})\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/slow_query\" element={<List />} />\n      <Route path=\"/slow_query/detail\" element={<Detail />} />\n    </Routes>\n  )\n}\n\nexport default function () {\n  const context = useContext(SlowQueryContext)\n  if (context === null) {\n    throw new Error('SlowQueryContext must not be null')\n  }\n\n  return (\n    <QueryClientProvider client={queryClient}>\n      <Root>\n        <Router>\n          <AppRoutes />\n        </Router>\n      </Root>\n    </QueryClientProvider>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/pages/Detail/DetailTabBasic.tsx",
    "content": "import React from 'react'\nimport { SlowqueryModel } from '@lib/client'\nimport { DateTime } from '@lib/components'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\n\nexport const tabBasicItems = (\n  data: SlowqueryModel,\n  options?: { showRuV2?: boolean }\n) => [\n  {\n    key: 'timestamp',\n    value: <DateTime.Calendar unixTimestampMs={(data.timestamp ?? 0) * 1000} />\n  },\n  { key: 'digest', value: data.digest },\n  { key: 'is_internal', value: data.is_internal },\n  { key: 'is_success', value: data.success },\n  { key: 'is_prepared', value: data.prepared },\n  { key: 'is_plan_from_cache', value: data.plan_from_cache },\n  { key: 'is_plan_from_binding', value: data.plan_from_binding },\n  { key: 'db', value: data.db },\n  { key: 'index_names', value: data.index_names },\n  { key: 'stats', value: data.stats },\n  { key: 'backoff_types', value: data.backoff_types },\n  {\n    key: 'memory_max',\n    value: getValueFormat('bytes')(data.memory_max || 0, 1)\n  },\n  {\n    key: 'mem_arbitration',\n    value: getValueFormat('s')(data.mem_arbitration || 0, 1)\n  },\n  {\n    key: 'disk_max',\n    value: getValueFormat('bytes')(data.disk_max || 0, 1)\n  },\n  { key: 'instance', value: data.instance },\n  { key: 'connection_id', value: data.connection_id },\n  { key: 'user', value: data.user },\n  { key: 'host', value: data.host },\n  { key: 'ru', value: data.ru },\n  ...(options?.showRuV2 ? [{ key: 'ru_v2', value: data.ru_v2 }] : []),\n  { key: 'resource_group', value: data.resource_group }\n]\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/pages/Detail/DetailTabCopr.tsx",
    "content": "import React from 'react'\n\nimport { SlowqueryModel } from '@lib/client'\nimport { ValueWithTooltip } from '@lib/components'\n\nexport const tabCoprItems = (data: SlowqueryModel) => [\n  {\n    key: 'request_count',\n    value: <ValueWithTooltip.Short value={data.request_count} />\n  },\n  {\n    key: 'process_keys',\n    value: <ValueWithTooltip.Short value={data.process_keys} />\n  },\n  {\n    key: 'total_keys',\n    value: <ValueWithTooltip.Short value={data.total_keys} />\n  },\n  {\n    key: 'cop_proc_addr',\n    value: data.cop_proc_addr\n  },\n  {\n    key: 'cop_wait_addr',\n    value: data.cop_wait_addr\n  },\n  {\n    key: 'rocksdb_block_cache_hit_count',\n    value: <ValueWithTooltip.Short value={data.rocksdb_block_cache_hit_count} />\n  },\n  {\n    key: 'rocksdb_block_read_byte',\n    value: <ValueWithTooltip.ScaledBytes value={data.rocksdb_block_read_byte} />\n  },\n  {\n    key: 'rocksdb_block_read_count',\n    value: <ValueWithTooltip.Short value={data.rocksdb_block_read_count} />\n  },\n  {\n    key: 'rocksdb_delete_skipped_count',\n    value: <ValueWithTooltip.Short value={data.rocksdb_delete_skipped_count} />\n  },\n  {\n    key: 'rocksdb_key_skipped_count',\n    value: <ValueWithTooltip.Short value={data.rocksdb_key_skipped_count} />\n  },\n  {\n    key: 'unpacked_bytes_sent_tikv_total',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.unpacked_bytes_sent_tikv_total}\n      />\n    )\n  },\n  {\n    key: 'unpacked_bytes_received_tikv_total',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.unpacked_bytes_received_tikv_total}\n      />\n    )\n  },\n  {\n    key: 'unpacked_bytes_sent_tikv_cross_zone',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.unpacked_bytes_sent_tikv_cross_zone}\n      />\n    )\n  },\n  {\n    key: 'unpacked_bytes_received_tikv_cross_zone',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.unpacked_bytes_received_tikv_cross_zone}\n      />\n    )\n  },\n  {\n    key: 'unpacked_bytes_sent_tiflash_total',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.unpacked_bytes_sent_tiflash_total}\n      />\n    )\n  },\n  {\n    key: 'unpacked_bytes_received_tiflash_total',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.unpacked_bytes_received_tiflash_total}\n      />\n    )\n  },\n  {\n    key: 'unpacked_bytes_sent_tiflash_cross_zone',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.unpacked_bytes_sent_tiflash_cross_zone}\n      />\n    )\n  },\n  {\n    key: 'unpacked_bytes_received_tiflash_cross_zone',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.unpacked_bytes_received_tiflash_cross_zone}\n      />\n    )\n  },\n  {\n    key: 'ia_remote_read_segment_size',\n    value: (\n      <ValueWithTooltip.ScaledBytes value={data.ia_remote_read_segment_size} />\n    )\n  }\n]\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/pages/Detail/DetailTabRuV2.tsx",
    "content": "import React, { ReactNode } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\n\nimport { RequestUnitV2Metrics, SlowqueryModel } from '@lib/client'\nimport { CardTable, Pre } from '@lib/components'\nimport { valueColumns } from '@lib/utils/tableColumns'\n\nconst num = (v: number | undefined, unit: 'short' | 'bytes' = 'short') =>\n  v == null ? '' : getValueFormat(unit)(v, 2)\n\nconst mapToCompactJson = (map: Record<string, number> | undefined): string => {\n  if (!map || Object.keys(map).length === 0) return ''\n  return JSON.stringify(map)\n}\n\n/**\n * Metrics struct rows for the metrics table inside the RU V2 tab.\n */\nfunction buildMetricsItems(data: SlowqueryModel) {\n  const m: RequestUnitV2Metrics = data.ru_v2_metrics ?? {}\n  return [\n    { key: 'ru_v2_metrics.total_ru', value: num(m.total_ru) },\n    { key: 'ru_v2_metrics.tidb_ru', value: num(m.tidb_ru) },\n    { key: 'ru_v2_metrics.tikv_ru', value: num(m.tikv_ru) },\n    { key: 'ru_v2_metrics.tiflash_ru', value: num(m.tiflash_ru) },\n    { key: 'ru_v2_metrics.txn_cnt', value: num(m.txn_cnt) },\n    { key: 'ru_v2_metrics.plan_cnt', value: num(m.plan_cnt) },\n    {\n      key: 'ru_v2_metrics.plan_derive_stats_paths',\n      value: num(m.plan_derive_stats_paths)\n    },\n    {\n      key: 'ru_v2_metrics.session_parser_total',\n      value: num(m.session_parser_total)\n    },\n    { key: 'ru_v2_metrics.executor_l1', value: num(m.executor_l1) },\n    { key: 'ru_v2_metrics.executor_l2', value: num(m.executor_l2) },\n    { key: 'ru_v2_metrics.executor_l3', value: num(m.executor_l3) },\n    {\n      key: 'ru_v2_metrics.executor_l5_insert_rows',\n      value: num(m.executor_l5_insert_rows)\n    },\n    {\n      key: 'ru_v2_metrics.result_chunk_cells',\n      value: num(m.result_chunk_cells)\n    },\n    {\n      key: 'ru_v2_metrics.resource_manager_read_cnt',\n      value: num(m.resource_manager_read_cnt)\n    },\n    {\n      key: 'ru_v2_metrics.resource_manager_write_cnt',\n      value: num(m.resource_manager_write_cnt)\n    },\n    {\n      key: 'ru_v2_metrics.tikv_coprocessor_executor_iterations',\n      value: num(m.tikv_coprocessor_executor_iterations)\n    },\n    {\n      key: 'ru_v2_metrics.tikv_coprocessor_response_bytes',\n      value: num(m.tikv_coprocessor_response_bytes, 'bytes')\n    },\n    {\n      key: 'ru_v2_metrics.tikv_coprocessor_executor_work_total',\n      value: mapToCompactJson(m.tikv_coprocessor_executor_work_total)\n    },\n    {\n      key: 'ru_v2_metrics.tikv_storage_processed_keys_get',\n      value: num(m.tikv_storage_processed_keys_get)\n    },\n    {\n      key: 'ru_v2_metrics.tikv_storage_processed_keys_batch_get',\n      value: num(m.tikv_storage_processed_keys_batch_get)\n    },\n    {\n      key: 'ru_v2_metrics.tikv_kv_engine_cache_miss',\n      value: num(m.tikv_kv_engine_cache_miss)\n    },\n    {\n      key: 'ru_v2_metrics.tikv_raftstore_store_write_trigger_wb_bytes',\n      value: num(m.tikv_raftstore_store_write_trigger_wb_bytes, 'bytes')\n    }\n  ]\n}\n\nfunction Section({ title, children }: { title: string; children: ReactNode }) {\n  return (\n    <div style={{ marginBottom: 20 }}>\n      <div\n        style={{\n          fontSize: 14,\n          fontWeight: 600,\n          color: '#262626',\n          marginBottom: 8\n        }}\n      >\n        {title}\n      </div>\n      {children}\n    </div>\n  )\n}\n\nexport function RuV2TabContent({\n  data,\n  schemaColumns\n}: {\n  data: SlowqueryModel\n  schemaColumns: string[]\n}) {\n  const { t } = useTranslation()\n  const schemaSet = new Set(schemaColumns)\n  const v2 = data.ru_v2\n  const v2Detail = data.ru_v2_detail\n\n  // Schema (available_fields) tells us which RU V2 fields the backend can\n  // populate for this cluster tier. Premium only declares `ru_v2`; Starter /\n  // Essential declare `ru_v2`, `ru_v2_detail`, `ru_v2_metrics`. Hide each\n  // section if its field is not in the schema, regardless of the data value.\n  const showV2 = schemaSet.has('ru_v2')\n  const showDetail = schemaSet.has('ru_v2_detail')\n  const showMetrics = schemaSet.has('ru_v2_metrics')\n  const metricsItems = showMetrics ? buildMetricsItems(data) : []\n\n  // Columns: Name + Value only (drop the Description column).\n  const nameValueColumns = valueColumns('slow_query.fields.').slice(0, 2)\n\n  return (\n    <div style={{ padding: '8px 16px' }}>\n      {showV2 && (\n        <Section title={t('slow_query.fields.ru_v2')}>\n          <span style={{ fontSize: 13 }}>{num(v2)}</span>\n        </Section>\n      )}\n\n      {showDetail && (\n        <Section title={t('slow_query.fields.ru_v2_detail')}>\n          <Pre style={{ margin: 0, fontSize: 12, lineHeight: 1.5 }}>\n            {v2Detail ?? ''}\n          </Pre>\n        </Section>\n      )}\n\n      {showMetrics && (\n        <Section title={t('slow_query.fields.ru_v2_metrics_label')}>\n          <CardTable\n            cardNoMargin\n            columns={nameValueColumns}\n            items={metricsItems}\n            extendLastColumn\n          />\n        </Section>\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/pages/Detail/DetailTabTime.tsx",
    "content": "import React from 'react'\nimport { SlowqueryModel } from '@lib/client'\nimport { Typography } from 'antd'\nimport { TFunction } from 'react-i18next'\n\nexport const tabTimeItems = (data: SlowqueryModel, t: TFunction) => {\n  return [\n    {\n      key: 'query_time2',\n      keyDisplay: (\n        <Typography.Text strong>\n          {t('slow_query.fields.query_time2')}\n        </Typography.Text>\n      ),\n      value: data.query_time! * 10e8,\n      indentLevel: 0\n    },\n    {\n      key: 'parse_time',\n      value: data.parse_time! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'compile_time',\n      value: data.compile_time! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'rewrite_time',\n      value: data.rewrite_time! * 10e8,\n      indentLevel: 2\n    },\n    {\n      key: 'preproc_subqueries_time',\n      value: data.preproc_subqueries_time! * 10e8,\n      indentLevel: 3\n    },\n    {\n      key: 'optimize_time',\n      value: data.optimize_time! * 10e8,\n      indentLevel: 2\n    },\n    {\n      key: 'cop_time',\n      value: data.cop_time! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'wait_time',\n      value: data.wait_time! * 10e8,\n      indentLevel: 2\n    },\n    {\n      key: 'process_time',\n      value: data.process_time! * 10e8,\n      indentLevel: 2\n    },\n    {\n      key: 'local_latch_wait_time',\n      value: data.local_latch_wait_time! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'lock_keys_time',\n      value: data.lock_keys_time! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'resolve_lock_time',\n      value: data.resolve_lock_time! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'wait_ts',\n      value: data.wait_ts! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'get_commit_ts_time',\n      value: data.get_commit_ts_time! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'prewrite_time',\n      value: data.prewrite_time! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'commit_time',\n      value: data.commit_time! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'backoff_time',\n      value: data.backoff_time! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'commit_backoff_time',\n      value: data.commit_backoff_time! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'exec_retry_time',\n      value: data.exec_retry_time! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'write_sql_response_total',\n      value: data.write_sql_response_total! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'wait_prewrite_binlog_time',\n      value: data.wait_prewrite_binlog_time! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'time_queued_by_rc',\n      value: data.time_queued_by_rc! * 10e8,\n      indentLevel: 1\n    },\n    {\n      key: 'ia_remote_read_segment_wait_time',\n      value: data.ia_remote_read_segment_wait_time! * 10e8,\n      indentLevel: 1\n    }\n  ]\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/pages/Detail/DetailTabTxn.tsx",
    "content": "import React from 'react'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\n\nimport { SlowqueryModel } from '@lib/client'\nimport { ValueWithTooltip } from '@lib/components'\n\nexport const tabTxnItems = (data: SlowqueryModel) => [\n  {\n    key: 'txn_start_ts',\n    value: data.txn_start_ts\n  },\n  {\n    key: 'write_keys',\n    value: <ValueWithTooltip.Short value={data.write_keys} />\n  },\n  {\n    key: 'write_size',\n    value: getValueFormat('bytes')(data.write_size || 0, 1)\n  },\n  {\n    key: 'prewrite_region',\n    value: <ValueWithTooltip.Short value={data.prewrite_region} />\n  },\n  {\n    key: 'txn_retry',\n    value: <ValueWithTooltip.Short value={data.txn_retry} />\n  }\n]\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/pages/Detail/DetailTabs.tsx",
    "content": "import React, { useContext, useMemo } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport ReactJson from 'react-json-view'\n\nimport { SlowqueryModel } from '@lib/client'\nimport { valueColumns, timeValueColumns } from '@lib/utils/tableColumns'\nimport { CardTabs, CardTable } from '@lib/components'\n\nimport { tabBasicItems } from './DetailTabBasic'\nimport { tabTimeItems } from './DetailTabTime'\nimport { tabCoprItems } from './DetailTabCopr'\nimport { tabTxnItems } from './DetailTabTxn'\nimport { RuV2TabContent } from './DetailTabRuV2'\nimport { useSchemaColumns } from '../../utils/useSchemaColumns'\nimport { SlowQueryContext } from '../../context'\n\nexport default function DetailTabs({ data }: { data: SlowqueryModel }) {\n  const ctx = useContext(SlowQueryContext)\n  const showRuV2 = !!ctx?.cfg.showRuV2\n\n  const { t } = useTranslation()\n  const { schemaColumns } = useSchemaColumns(\n    ctx!.ds.slowQueryAvailableFieldsGet\n  )\n\n  const tabs = useMemo(() => {\n    const schemaSet = new Set(schemaColumns)\n    // Premium tier only declares `ru_v2` in the schema; Starter / Essential\n    // also declare `ru_v2_detail` and `ru_v2_metrics`. Use the presence of the\n    // metrics struct as the tier signal: when absent it's Premium, so the\n    // ru_v2 row stays inside the Basic tab; when present, RU V2 / Detail /\n    // Metrics get their own dedicated tab.\n    const isEssentialLike = schemaSet.has('ru_v2_metrics')\n    const showRuV2InBasic =\n      showRuV2 && schemaSet.has('ru_v2') && !isEssentialLike\n    const showRuV2Tab = showRuV2 && isEssentialLike\n    const tbs = [\n      {\n        key: 'basic',\n        title: t('slow_query.detail.tabs.basic'),\n        content: () => {\n          const items = tabBasicItems(data, { showRuV2: showRuV2InBasic })\n          const columns = valueColumns('slow_query.fields.')\n          return (\n            <CardTable\n              cardNoMargin\n              columns={columns}\n              items={items}\n              extendLastColumn\n              data-e2e=\"details_list\"\n            />\n          )\n        }\n      },\n      {\n        key: 'time',\n        title: t('slow_query.detail.tabs.time'),\n        content: () => {\n          const items = tabTimeItems(data, t)\n          const columns = timeValueColumns('slow_query.fields.', items)\n          return (\n            <CardTable\n              cardNoMargin\n              columns={columns}\n              items={items}\n              extendLastColumn\n            />\n          )\n        }\n      },\n      {\n        key: 'copr',\n        title: t('slow_query.detail.tabs.copr'),\n        content: () => {\n          const columnsSet = new Set(schemaColumns)\n          const items = tabCoprItems(data).filter((item) =>\n            columnsSet.has(item.key)\n          )\n          const columns = valueColumns('slow_query.fields.')\n          return (\n            <CardTable\n              cardNoMargin\n              columns={columns}\n              items={items}\n              extendLastColumn\n            />\n          )\n        }\n      },\n      {\n        key: 'txn',\n        title: t('slow_query.detail.tabs.txn'),\n        content: () => {\n          const items = tabTxnItems(data)\n          const columns = valueColumns('slow_query.fields.')\n          return (\n            <CardTable\n              cardNoMargin\n              columns={columns}\n              items={items}\n              extendLastColumn\n            />\n          )\n        }\n      }\n    ]\n    if (showRuV2Tab) {\n      tbs.push({\n        key: 'ru_v2',\n        title: t('slow_query.detail.tabs.ru_v2'),\n        content: () => (\n          <RuV2TabContent data={data} schemaColumns={schemaColumns} />\n        )\n      })\n    }\n    if (data.warnings) {\n      tbs.push({\n        key: 'warnings',\n        title: t('slow_query.detail.tabs.warnings'),\n        content: () => {\n          return (\n            <ReactJson\n              src={data.warnings! as any}\n              enableClipboard={false}\n              displayObjectSize={false}\n              displayDataTypes={false}\n              name={false}\n              iconStyle=\"circle\"\n              groupArraysAfterLength={10}\n            />\n          )\n        }\n      })\n    }\n    return tbs\n  }, [schemaColumns, data, t, showRuV2])\n  return <CardTabs animated={false} tabs={tabs} />\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/pages/Detail/index.tsx",
    "content": "import React, { useState, useContext, useMemo } from 'react'\nimport { Space, Modal, Tabs, Typography } from 'antd'\nimport { useTranslation } from 'react-i18next'\nimport { useLocation, useNavigate } from 'react-router-dom'\nimport { ArrowLeftOutlined } from '@ant-design/icons'\nimport { useQuery } from '@tanstack/react-query'\n\nimport formatSql from '@lib/utils/sqlFormatter'\nimport {\n  AnimatedSkeleton,\n  BinaryPlanTable,\n  PlanText,\n  CopyLink,\n  Descriptions,\n  ErrorBar,\n  Expand,\n  Head,\n  HighlightSQL,\n  TextWithInfo\n} from '@lib/components'\nimport {\n  VisualPlanThumbnailView,\n  VisualPlanView\n} from '@lib/components/VisualPlan'\nimport { useVersionedLocalStorageState } from '@lib/utils/useVersionedLocalStorageState'\n\nimport DetailTabs from './DetailTabs'\nimport { SlowQueryContext } from '../../context'\nimport { useSlowQueryDetailUrlState } from '../../utils/detail-url-state'\nimport { telemetry } from '../../utils/telemetry'\n\nconst SLOW_QUERY_DETAIL_EXPAND = 'slow_query.detail_expand'\n\nfunction useSlowQueryDetailData() {\n  const ctx = useContext(SlowQueryContext)\n  const { digest, connectionId, timestamp } = useSlowQueryDetailUrlState()\n\n  const query = useQuery({\n    queryKey: ['slow_query', 'detail', digest, connectionId, timestamp],\n    queryFn: () => {\n      return ctx?.ds\n        .slowQueryDetailGet(connectionId, digest, timestamp, {\n          handleError: 'custom'\n        })\n        .then((res) => res.data)\n    }\n  })\n\n  return query\n}\n\nfunction DetailPage() {\n  const location = useLocation()\n  const navigate = useNavigate()\n  const { t } = useTranslation()\n\n  const historyBack = (location.state ?? ({} as any)).historyBack ?? false\n\n  const { data, isLoading, error } = useSlowQueryDetailData()\n\n  const binaryPlanObj = useMemo(() => {\n    const json = data?.binary_plan_json ?? data?.binary_plan\n    if (json) {\n      return JSON.parse(json)\n    }\n    return undefined\n  }, [data?.binary_plan, data?.binary_plan_json])\n\n  const [detailExpand, setDetailExpand] = useVersionedLocalStorageState(\n    SLOW_QUERY_DETAIL_EXPAND,\n    {\n      defaultValue: {\n        prev_query: false,\n        query: false,\n        plan: false\n      }\n    }\n  )\n\n  const togglePrevQuery = () =>\n    setDetailExpand((prev) => ({ ...prev, prev_query: !prev.prev_query }))\n  const toggleQuery = () =>\n    setDetailExpand((prev) => ({ ...prev, query: !prev.query }))\n\n  const [isVpVisible, setIsVpVisible] = useState(false)\n  const toggleVisualPlan = (action: 'open' | 'close') => {\n    telemetry.toggleVisualPlanModal(action)\n    setIsVpVisible(!isVpVisible)\n  }\n\n  return (\n    <div>\n      <Head\n        title={t('slow_query.detail.head.title')}\n        back={\n          <Typography.Link\n            onClick={() =>\n              historyBack ? navigate(-1) : navigate('/slow_query')\n            }\n          >\n            <ArrowLeftOutlined /> {t('slow_query.detail.head.back')}\n          </Typography.Link>\n        }\n      >\n        <AnimatedSkeleton showSkeleton={isLoading}>\n          {error && <ErrorBar errors={[error]} />}\n          {!!data && (\n            <>\n              <Descriptions>\n                <Descriptions.Item\n                  span={2}\n                  multiline={detailExpand.query}\n                  label={\n                    <Space size=\"middle\">\n                      <TextWithInfo.TransKey transKey=\"slow_query.detail.head.sql\" />\n                      <Expand.Link\n                        expanded={detailExpand.query}\n                        onClick={toggleQuery}\n                      />\n                      {\n                        // avoid page hang when sql is too long\n                        data.query!.length < 10000 && (\n                          <CopyLink\n                            displayVariant=\"formatted_sql\"\n                            data={formatSql(data.query!)}\n                          />\n                        )\n                      }\n                      <CopyLink\n                        displayVariant=\"original_sql\"\n                        data={data.query!}\n                      />\n                      <CopyLink\n                        displayVariant=\"original_sql\"\n                        data={data.query!}\n                      />\n                    </Space>\n                  }\n                >\n                  <Expand\n                    expanded={detailExpand.query}\n                    collapsedContent={\n                      <HighlightSQL sql={data.query!} compact />\n                    }\n                  >\n                    <HighlightSQL sql={data.query!} />\n                  </Expand>\n                </Descriptions.Item>\n                {(() => {\n                  if (!!data.prev_stmt && data.prev_stmt.length !== 0)\n                    return (\n                      <Descriptions.Item\n                        span={2}\n                        multiline={detailExpand.prev_query}\n                        label={\n                          <Space size=\"middle\">\n                            <TextWithInfo.TransKey transKey=\"slow_query.detail.head.previous_sql\" />\n                            <Expand.Link\n                              expanded={detailExpand.prev_query}\n                              onClick={togglePrevQuery}\n                            />\n                            {\n                              // avoid page hang when sql is too long\n                              data.prev_stmt!.length < 10000 && (\n                                <CopyLink\n                                  displayVariant=\"formatted_sql\"\n                                  data={formatSql(data.prev_stmt!)}\n                                />\n                              )\n                            }\n                            <CopyLink\n                              displayVariant=\"original_sql\"\n                              data={data.prev_stmt!}\n                            />\n                          </Space>\n                        }\n                      >\n                        <Expand\n                          expanded={detailExpand.prev_query}\n                          collapsedContent={\n                            <HighlightSQL sql={data.prev_stmt!} compact />\n                          }\n                        >\n                          <HighlightSQL sql={data.prev_stmt!} />\n                        </Expand>\n                      </Descriptions.Item>\n                    )\n                })()}\n              </Descriptions>\n              {(!!data.binary_plan_text || !!data.plan) && (\n                <>\n                  <Space size=\"middle\" style={{ color: '#8c8c8c' }}>\n                    {t('slow_query.detail.plan.title')}\n                  </Space>\n                  <Tabs\n                    defaultActiveKey={\n                      !!data.binary_plan_text\n                        ? 'binary_plan_table'\n                        : 'text_plan'\n                    }\n                    onTabClick={(key) =>\n                      telemetry.clickPlanTabs(key, data.digest!)\n                    }\n                  >\n                    {!!data.binary_plan_text && (\n                      <Tabs.TabPane\n                        tab={t('slow_query.detail.plan.table')}\n                        key=\"binary_plan_table\"\n                      >\n                        <BinaryPlanTable\n                          data={data.binary_plan_text}\n                          downloadFileName={`${data.digest}.txt`}\n                        />\n                        <div style={{ height: 24 }} />\n                      </Tabs.TabPane>\n                    )}\n\n                    <Tabs.TabPane\n                      tab={t('slow_query.detail.plan.text')}\n                      key=\"text_plan\"\n                    >\n                      <PlanText\n                        data={data.binary_plan_text || data.plan || ''}\n                        downloadFileName={`${data.digest}.txt`}\n                      />\n                    </Tabs.TabPane>\n\n                    {binaryPlanObj && !binaryPlanObj.discardedDueToTooLong && (\n                      <Tabs.TabPane\n                        tab={t('slow_query.detail.plan.visual')}\n                        key=\"binary_plan\"\n                      >\n                        <Modal\n                          title={t('slow_query.detail.plan.modal_title')}\n                          centered\n                          visible={isVpVisible}\n                          width={window.innerWidth}\n                          onCancel={() => toggleVisualPlan('close')}\n                          footer={null}\n                          destroyOnClose={true}\n                          bodyStyle={{\n                            background: '#f5f5f5',\n                            height: window.innerHeight - 100\n                          }}\n                        >\n                          <VisualPlanView data={binaryPlanObj} />\n                        </Modal>\n                        <Descriptions>\n                          <Descriptions.Item span={2}>\n                            <div onClick={() => toggleVisualPlan('open')}>\n                              <VisualPlanThumbnailView data={binaryPlanObj} />\n                            </div>\n                          </Descriptions.Item>\n                        </Descriptions>\n                      </Tabs.TabPane>\n                    )}\n                  </Tabs>\n                </>\n              )}\n              <DetailTabs data={data} />\n            </>\n          )}\n        </AnimatedSkeleton>\n      </Head>\n    </div>\n  )\n}\n\nexport default DetailPage\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/pages/List/DownloadDBFileModal.tsx",
    "content": "import React, { useContext, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { Button, Form, Modal, Radio, Select } from 'antd'\nimport { useMemoizedFn } from 'ahooks'\nimport { DatePicker } from '@lib/components'\nimport { SlowQueryContext } from '../../context'\nimport dayjs, { Dayjs } from 'dayjs'\n\nconst hoursRange = [...Array(24).keys()]\n\nfunction DownloadDBFileModal({\n  visible,\n  setVisible\n}: {\n  visible: boolean\n  setVisible: React.Dispatch<React.SetStateAction<boolean>>\n}) {\n  const ctx = useContext(SlowQueryContext)\n  const { t } = useTranslation()\n\n  type RangeType = 'by_day' | 'by_hour'\n\n  const [rangeTypeVal, setRangeTypeVal] = useState<RangeType>('by_day')\n  const [downloading, setDownloading] = useState(false)\n\n  const [dateVal, setDateVal] = useState<dayjs.Dayjs | null>(null)\n  const [hourVal, setHourVal] = useState(0)\n\n  const downloadDBFile = useMemoizedFn(\n    async (dateVal: Dayjs, hourVal: number, rangeTypeVal: RangeType) => {\n      // use last effective query options\n      if (hourVal < 0 || hourVal > 23) {\n        console.log(`Illegegal hour value: ${hourVal}`)\n        hourVal = hourVal % 24\n      }\n      try {\n        setDownloading(true)\n        const offset = rangeTypeVal === 'by_day' ? 86400 : 3600\n        const dateAlignedUnix = dateVal.unix() - (dateVal.unix() % 86400)\n        const begin_time =\n          dateAlignedUnix + (rangeTypeVal === 'by_hour' ? hourVal * 3600 : 0)\n        const res = await ctx!.ds.slowQueryDownloadDBFile!(\n          begin_time,\n          begin_time + offset\n        )\n        const { data, headers } = res\n        const fileName = `${begin_time}.db`\n        const blob = new Blob([data], { type: headers['content-type'] })\n        let dom = document.createElement('a')\n        let url = window.URL.createObjectURL(blob)\n        dom.href = url\n        dom.download = decodeURI(fileName)\n        dom.style.display = 'none'\n        document.body.appendChild(dom)\n        dom.click()\n        dom.parentNode!.removeChild(dom)\n        window.URL.revokeObjectURL(url)\n      } finally {\n        setDownloading(false)\n      }\n    }\n  )\n\n  return (\n    <Modal\n      visible={visible}\n      footer={null}\n      onCancel={(e) => setVisible(false)}\n      title={t('slow_query.download_modal.title')}\n    >\n      <Form>\n        <Form.Item label=\"Timezone\" name=\"timezone\">\n          UTC\n        </Form.Item>\n        <Form.Item label=\"Range Type\" name=\"layout\">\n          <Radio.Group\n            onChange={(e) => setRangeTypeVal(e.target.value)}\n            defaultValue={rangeTypeVal}\n          >\n            <Radio value={'by_day'}>\n              {t('slow_query.download_modal.download_by_day')}\n            </Radio>\n            <Radio value={'by_hour'}>\n              {t('slow_query.download_modal.download_by_hour')}\n            </Radio>\n          </Radio.Group>\n        </Form.Item>\n        <Form.Item label=\"Date\" name=\"date\">\n          <DatePicker\n            onChange={(date) => {\n              date && setDateVal(date)\n            }}\n          />\n        </Form.Item>\n        <Form.Item label=\"Hour\" name=\"hour\">\n          <Select\n            defaultValue={0}\n            disabled={rangeTypeVal === 'by_day'}\n            onChange={(val) => setHourVal(val)}\n            options={hoursRange.map((i) => {\n              return {\n                key: i,\n                value: i,\n                label: `${i}:00~${(i + 1) % 24}:00`\n              }\n            })}\n          />\n        </Form.Item>\n        <Form.Item label=\"Download Range\" name=\"download_range\">\n          {dateVal &&\n            `${dateVal.format('YYYY-MM-DD')} ${\n              rangeTypeVal === 'by_day'\n                ? `00:00 ~ 24:00`\n                : `${hourVal}:00~${hourVal + 1}:00`\n            } (UTC)`}\n        </Form.Item>\n        <Form.Item>\n          <Button\n            disabled={downloading}\n            onClick={() =>\n              dateVal && downloadDBFile(dateVal, hourVal, rangeTypeVal)\n            }\n          >\n            {t(`slow_query.download_modal.download`)}\n          </Button>\n        </Form.Item>\n      </Form>\n    </Modal>\n  )\n}\n\nexport default DownloadDBFileModal\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/pages/List/List.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.list {\n  &_container {\n    display: flex;\n    flex-direction: column;\n    height: 100vh;\n  }\n\n  &_toolbar {\n    @media only screen and (max-width: @screen-md) {\n      flex-direction: column;\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/pages/List/index.tsx",
    "content": "import React, { useContext, useState, useMemo, useCallback } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport {\n  Select,\n  Space,\n  Input,\n  Checkbox,\n  message,\n  Menu,\n  Dropdown,\n  Tooltip,\n  Result,\n  Tag,\n  Button,\n  Form,\n  Switch\n} from 'antd'\nimport { useMemoizedFn } from 'ahooks'\nimport { Link, useNavigate } from 'react-router-dom'\nimport {\n  LoadingOutlined,\n  ExportOutlined,\n  MenuOutlined,\n  QuestionCircleOutlined\n} from '@ant-design/icons'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport { useQuery } from '@tanstack/react-query'\nimport { CSVLink } from 'react-csv'\n\nimport {\n  Card,\n  ColumnsSelector,\n  TimeRangeSelector,\n  Toolbar,\n  MultiSelect,\n  toTimeRangeValue,\n  IColumnKeys,\n  LimitTimeRange,\n  CardTable\n} from '@lib/components'\nimport { useVersionedLocalStorageState } from '@lib/utils/useVersionedLocalStorageState'\nimport { getSelectedFields } from '@lib/utils/tableColumnFactory'\nimport { isDistro } from '@lib/utils/distro'\nimport openLink from '@lib/utils/openLink'\n\nimport {\n  LIMITS,\n  DEF_SLOW_QUERY_COLUMN_KEYS,\n  SLOW_QUERY_VISIBLE_COLUMN_KEYS,\n  SLOW_QUERY_SHOW_FULL_SQL\n} from '../../utils/helpers'\nimport { SlowQueryContext } from '../../context'\nimport { useSlowQueryListUrlState } from '../../utils/list-url-state'\nimport { derivedFields, slowQueryColumns } from '../../utils/tableColumns'\nimport DownloadDBFileModal from './DownloadDBFileModal'\n\nimport styles from './List.module.less'\nimport { telemetry } from '../../utils/telemetry'\n\nconst { Option } = Select\n\nfunction useDbsData() {\n  const ctx = useContext(SlowQueryContext)\n  const { timeRange } = useSlowQueryListUrlState()\n  const timeRangeValue = toTimeRangeValue(timeRange)\n\n  const query = useQuery({\n    queryKey: ['slow_query', 'dbs', timeRange],\n    queryFn: () => {\n      // get database list from s3\n      if (ctx?.cfg.showTopSlowQueryLink) {\n        return ctx?.ds\n          .getDatabaseList(timeRangeValue[0], timeRangeValue[1], {\n            handleError: 'custom'\n          })\n          .then((res) => res.data)\n      }\n\n      // get database list from PD\n      return ctx?.ds\n        .getDatabaseList(0, 0, { handleError: 'custom' })\n        .then((res) => res.data)\n    },\n    enabled: ctx?.cfg.showDBFilter && !!timeRangeValue[0]\n  })\n  return query\n}\n\nfunction useRuGroupsData() {\n  const ctx = useContext(SlowQueryContext)\n\n  const query = useQuery({\n    queryKey: ['slow_query', 'ru_groups'],\n    queryFn: () => {\n      return ctx?.ds\n        .infoListResourceGroupNames({ handleError: 'custom' })\n        .then((res) => res.data)\n    },\n    enabled: ctx?.cfg.showResourceGroupFilter\n  })\n  return query\n}\n\nfunction useAvailableColumnsData() {\n  const ctx = useContext(SlowQueryContext)\n\n  const query = useQuery({\n    queryKey: ['slow_query', 'available_columns'],\n    queryFn: () => {\n      return ctx?.ds\n        .slowQueryAvailableFieldsGet({ handleError: 'custom' })\n        .then((res) => res.data.map((d) => d.toLowerCase()))\n    }\n  })\n  return query\n}\n\nfunction useSlowqueryListData(visibleColumnKeys: IColumnKeys) {\n  const ctx = useContext(SlowQueryContext)\n\n  const { timeRange, dbs, order, digest, limit, ruGroups, term, showInternal } =\n    useSlowQueryListUrlState()\n\n  const timeRangeValue = toTimeRangeValue(timeRange)\n\n  const actualVisibleColumnKeys = useMemo(\n    () => getSelectedFields(visibleColumnKeys, derivedFields).join(','),\n    [visibleColumnKeys]\n  )\n\n  const query = useQuery({\n    queryKey: [\n      'slow_query',\n      'list',\n      timeRange,\n      dbs,\n      order,\n      digest,\n      visibleColumnKeys,\n      limit,\n      ruGroups,\n      term,\n      showInternal\n    ],\n    queryFn: () => {\n      return ctx?.ds\n        .slowQueryListGet(\n          timeRangeValue[0],\n          dbs,\n          order.type === 'desc',\n          digest,\n          timeRangeValue[1],\n          actualVisibleColumnKeys,\n          limit,\n          order.col,\n          [],\n          ruGroups,\n          term,\n          showInternal,\n          { handleError: 'custom' }\n        )\n        .then((res) => res.data)\n    },\n    retry: false\n  })\n  return { query }\n}\n\nfunction List() {\n  const { t } = useTranslation()\n\n  const ctx = useContext(SlowQueryContext)\n\n  const {\n    setQueryParams,\n\n    timeRange,\n    setTimeRange,\n\n    dbs,\n    setDbs,\n\n    ruGroups,\n    setRuGroups,\n\n    limit,\n    setLimit,\n\n    digest,\n    term,\n\n    order,\n    setOrder,\n    resetOrder,\n\n    rowIdx,\n    setRowIdx,\n\n    showInternal,\n    setShowInternal\n  } = useSlowQueryListUrlState()\n\n  const [defDbs, _] = useState(dbs)\n  const { data: dbsData, isFetching: fetchingDbs } = useDbsData()\n  const finalDbs = useMemo(() => {\n    if (dbsData && dbsData.length > 0) {\n      return dbsData\n    }\n    return defDbs\n  }, [defDbs, dbsData])\n\n  const [visibleColumnKeys, setVisibleColumnKeys] =\n    useVersionedLocalStorageState(SLOW_QUERY_VISIBLE_COLUMN_KEYS, {\n      defaultValue: DEF_SLOW_QUERY_COLUMN_KEYS\n    })\n  const [showFullSQL, setShowFullSQL] = useVersionedLocalStorageState(\n    SLOW_QUERY_SHOW_FULL_SQL,\n    { defaultValue: false }\n  )\n\n  const [downloadModalVisible, setDownloadModalVisible] = useState(false)\n\n  const { data: ruGroupsData, isFetching: fetchingRuGroups } = useRuGroupsData()\n  const { data: availableColumnsData, isFetching: fetchingAvailableColumns } =\n    useAvailableColumnsData()\n  const {\n    query: {\n      data: slowQueryData,\n      refetch: refetchSlowQueryData,\n      isFetching: fetchingSlowQueryData,\n      error: slowQueryError\n    }\n  } = useSlowqueryListData(visibleColumnKeys)\n\n  const availableColumnsInTable = useMemo(\n    () =>\n      slowQueryColumns(\n        slowQueryData ?? [],\n        availableColumnsData ?? [],\n        showFullSQL\n      ),\n    [slowQueryData, availableColumnsData, showFullSQL]\n  )\n  const csvHeaders = availableColumnsInTable\n    .filter((c) => visibleColumnKeys[c.key])\n    .map((c) => ({\n      label: c.key,\n      key: c.key\n    }))\n\n  const [downloading, setDownloading] = useState(false)\n\n  function updateVisibleColumnKeys(v: IColumnKeys) {\n    setVisibleColumnKeys(v)\n    if (!v[order.col]) {\n      resetOrder()\n    }\n  }\n\n  function menuItemClick({ key }) {\n    switch (key) {\n      case 'export':\n        const hide = message.loading(\n          t('slow_query.toolbar.exporting') + '...',\n          0\n        )\n        downloadCSV().finally(hide)\n        break\n    }\n  }\n\n  const dropdownMenu = (\n    <Menu onClick={menuItemClick}>\n      <Menu.Item\n        key=\"export\"\n        disabled={downloading}\n        icon={<ExportOutlined />}\n        data-e2e=\"slow_query_export_btn\"\n      >\n        {downloading\n          ? t('slow_query.toolbar.exporting')\n          : t('slow_query.toolbar.export')}\n      </Menu.Item>\n    </Menu>\n  )\n\n  const downloadCSV = useMemoizedFn(async () => {\n    // use last effective query options\n    const timeRangeValue = toTimeRangeValue(timeRange)\n\n    try {\n      setDownloading(true)\n      const res = await ctx!.ds.slowQueryDownloadTokenPost({\n        fields: '*',\n        begin_time: timeRangeValue[0],\n        end_time: timeRangeValue[1],\n        db: dbs,\n        resource_group: ruGroups,\n        text: term,\n        orderBy: order.col,\n        desc: order.type === 'desc',\n        limit: 10000,\n        digest: digest,\n        plans: []\n      })\n      const token = res.data\n      if (token) {\n        window.location.href = `${\n          ctx!.cfg.apiPathBase\n        }/slow_query/download?token=${token}`\n      }\n    } finally {\n      setDownloading(false)\n    }\n  })\n\n  // only for clinic\n  const clusterInfo = useMemo(() => {\n    const infos: string[] = []\n    if (ctx?.cfg.orgName) {\n      infos.push(`Org: ${ctx?.cfg.orgName}`)\n    }\n    if (ctx?.cfg.clusterName) {\n      infos.push(`Cluster: ${ctx?.cfg.clusterName}`)\n    }\n    return infos.join(' | ')\n  }, [ctx?.cfg.orgName, ctx?.cfg.clusterName])\n\n  const getKey = useCallback((row) => `${row.digest}_${row.timestamp}`, [])\n\n  function handleFormSubmit(values: any) {\n    telemetry.clickQueryButton()\n    const { term, digest } = values\n    setQueryParams({ term, digest })\n    setTimeout(() => {\n      // wrapped in setTimeout, to get the updated term and digest values\n      refetchSlowQueryData()\n    })\n  }\n\n  const navigate = useNavigate()\n  const handleRowClick = useMemoizedFn(\n    (rec, idx, ev: React.MouseEvent<HTMLElement>) => {\n      telemetry.clickTableRow()\n      ctx?.event?.selectSlowQueryItem(rec)\n      setRowIdx(idx)\n      openLink(\n        `/slow_query/detail?digest=${rec.digest}&connection_id=${rec.connection_id}&timestamp=${rec.timestamp}`,\n        ev,\n        navigate\n      )\n    }\n  )\n\n  return (\n    <div className={styles.list_container}>\n      <Card noMarginBottom>\n        {clusterInfo && (\n          <div\n            style={{\n              marginBottom: 16,\n              display: 'flex',\n              flexDirection: 'row-reverse',\n              justifyContent: 'space-between'\n            }}\n          >\n            {clusterInfo}\n            {ctx?.cfg.showTopSlowQueryLink && (\n              <span>\n                <span style={{ fontSize: 18, fontWeight: 600 }}>\n                  <Link\n                    to=\"/top_slowquery\"\n                    onClick={() => telemetry.clickTopSlowQueryTab()}\n                  >\n                    Top SlowQueries{' '}\n                  </Link>\n                  <Tag color=\"geekblue\">beta</Tag>\n                  <span>| </span>\n                  <span>Slow Query Logs</span>\n                </span>\n              </span>\n            )}\n          </div>\n        )}\n\n        <Toolbar className={styles.list_toolbar} data-e2e=\"slow_query_toolbar\">\n          <Space>\n            {ctx?.cfg.timeRangeSelector !== undefined ? (\n              <LimitTimeRange\n                value={timeRange}\n                onChange={setTimeRange}\n                recent_seconds={ctx.cfg.timeRangeSelector.recentSeconds}\n                customAbsoluteRangePicker={true}\n                onZoomOutClick={() => {}}\n                timeRangeLimit={ctx?.cfg.timeRangeSelector?.timeRangeLimit}\n              />\n            ) : (\n              <TimeRangeSelector value={timeRange} onChange={setTimeRange} />\n            )}\n\n            {(ctx!.cfg.showDBFilter || defDbs.length > 0) && (\n              <MultiSelect.Plain\n                placeholder={t('slow_query.toolbar.schemas.placeholder')}\n                selectedValueTransKey=\"slow_query.toolbar.schemas.selected\"\n                columnTitle={t('slow_query.toolbar.schemas.columnTitle')}\n                value={dbs}\n                style={{ width: 150 }}\n                onChange={setDbs}\n                items={finalDbs}\n                data-e2e=\"execution_database_name\"\n              />\n            )}\n\n            {ctx!.cfg.showResourceGroupFilter &&\n              (ruGroupsData ?? []).length > 1 && (\n                <MultiSelect.Plain\n                  placeholder={t(\n                    'slow_query.toolbar.resource_groups.placeholder'\n                  )}\n                  selectedValueTransKey=\"slow_query.toolbar.resource_groups.selected\"\n                  columnTitle={t(\n                    'slow_query.toolbar.resource_groups.columnTitle'\n                  )}\n                  value={ruGroups}\n                  style={{ width: 180 }}\n                  onChange={setRuGroups}\n                  items={ruGroupsData}\n                  data-e2e=\"resource_group_name_select\"\n                />\n              )}\n\n            <Select\n              style={{ width: 150 }}\n              value={limit}\n              onChange={setLimit}\n              data-e2e=\"slow_query_limit_select\"\n            >\n              {LIMITS.map((item) => (\n                <Option\n                  value={item}\n                  key={item}\n                  data-e2e=\"slow_query_limit_option\"\n                >\n                  Limit {item}\n                </Option>\n              ))}\n            </Select>\n\n            <Form\n              layout=\"inline\"\n              initialValues={{ term, digest }}\n              onFinish={handleFormSubmit}\n            >\n              {ctx!.cfg.showDigestFilter && (\n                <Form.Item name=\"digest\">\n                  <Input\n                    placeholder={t('slow_query.toolbar.digest.placeholder')}\n                    data-e2e=\"slow_query_digest\"\n                  />\n                </Form.Item>\n              )}\n\n              <Form.Item name=\"term\">\n                <Input\n                  placeholder={t('slow_query.toolbar.keyword.placeholder')}\n                  data-e2e=\"slow_query_search\"\n                />\n              </Form.Item>\n\n              <Form.Item>\n                <Button type=\"primary\" htmlType=\"submit\">\n                  {t('slow_query.toolbar.query')}\n                </Button>\n              </Form.Item>\n            </Form>\n\n            {(fetchingDbs || fetchingRuGroups || fetchingAvailableColumns) && (\n              <LoadingOutlined />\n            )}\n\n            {ctx!.cfg.showDownloadSlowQueryDBFile && (\n              <Button\n                type=\"link\"\n                onClick={() => setDownloadModalVisible(!downloadModalVisible)}\n              >\n                {t('slow_query.toolbar.download_db')}\n              </Button>\n            )}\n          </Space>\n\n          <Space>\n            {ctx!.cfg.showInternalFilter && (\n              <Space>\n                <Switch checked={showInternal} onChange={setShowInternal} />\n                {t('slow_query.toolbar.show_internal')}\n              </Space>\n            )}\n            <Space>\n              {availableColumnsInTable.length > 0 && (\n                <ColumnsSelector\n                  columns={availableColumnsInTable}\n                  visibleColumnKeys={visibleColumnKeys}\n                  defaultVisibleColumnKeys={DEF_SLOW_QUERY_COLUMN_KEYS}\n                  onChange={updateVisibleColumnKeys}\n                  foot={\n                    <Checkbox\n                      checked={showFullSQL}\n                      onChange={(e) => setShowFullSQL(e.target.checked)}\n                      data-e2e=\"slow_query_show_full_sql\"\n                    >\n                      {t('slow_query.toolbar.select_columns.show_full_sql')}\n                    </Checkbox>\n                  }\n                />\n              )}\n\n              {ctx!.cfg.enableExport && (\n                <Dropdown overlay={dropdownMenu} placement=\"bottomRight\">\n                  <div\n                    style={{ cursor: 'pointer' }}\n                    data-e2e=\"slow_query_export_menu\"\n                  >\n                    <MenuOutlined />\n                  </div>\n                </Dropdown>\n              )}\n\n              {!isDistro() && (ctx!.cfg.showHelp ?? true) && (\n                <Tooltip\n                  mouseEnterDelay={0}\n                  mouseLeaveDelay={0}\n                  title={t('slow_query.toolbar.help')}\n                  placement=\"bottom\"\n                >\n                  <QuestionCircleOutlined\n                    onClick={() => {\n                      window.open(t('slow_query.toolbar.help_url'), '_blank')\n                    }}\n                  />\n                </Tooltip>\n              )}\n            </Space>\n          </Space>\n        </Toolbar>\n      </Card>\n      <div style={{ height: 16 }} />\n      {slowQueryData?.length === 0 ? (\n        <Result title={t('slow_query.overview.empty_result')} />\n      ) : (\n        <div style={{ height: '100%', position: 'relative' }}>\n          <ScrollablePane>\n            {(slowQueryData?.length ?? 0) > 0 && (\n              <Card noMarginBottom noMarginTop>\n                <div className=\"ant-form-item-extra\">\n                  {t('slow_query.overview.result_count', {\n                    n: slowQueryData?.length\n                  })}{' '}\n                  <CSVLink\n                    data={slowQueryData}\n                    headers={csvHeaders}\n                    filename=\"slowquery\"\n                  >\n                    Download to CSV\n                  </CSVLink>\n                </div>\n              </Card>\n            )}\n            <CardTable\n              cardNoMarginTop\n              loading={fetchingSlowQueryData}\n              columns={availableColumnsInTable}\n              items={slowQueryData ?? []}\n              orderBy={order.col}\n              desc={order.type === 'desc'}\n              onChangeOrder={(col, desc) =>\n                setOrder({ col, type: desc ? 'desc' : 'asc' })\n              }\n              errors={slowQueryError ? [slowQueryError] : []}\n              visibleColumnKeys={visibleColumnKeys}\n              onRowClicked={handleRowClick}\n              clickedRowIndex={rowIdx}\n              getKey={getKey}\n              data-e2e=\"detail_tabs_slow_query\"\n            />\n          </ScrollablePane>\n        </div>\n      )}\n      {ctx!.cfg.showDownloadSlowQueryDBFile && (\n        <DownloadDBFileModal\n          visible={downloadModalVisible}\n          setVisible={setDownloadModalVisible}\n        />\n      )}\n    </div>\n  )\n}\n\nexport default List\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/pages/index.ts",
    "content": "import List from './List'\nimport Detail from './Detail'\n\nexport { List, Detail }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/translations/en.yaml",
    "content": "slow_query_v2:\n  overview:\n    head:\n      title: Slow Queries\n  detail:\n    head:\n      title: Slow Query Comparison\n\nslow_query:\n  nav_title: Slow Queries\n  fields:\n    instance: '{{distro.tidb}} Instance'\n    instance_tooltip: The {{distro.tidb}} address that handles the query\n    connection_id: Connection ID\n    connection_id_tooltip: Unique connection ID of the query\n    sql: Query\n    query: Query\n    timestamp: Finish Time\n    timestamp_tooltip: The time this query finished execution\n    query_time: Latency\n    query_time_tooltip: Execution time of the query\n    memory_max: Max Memory\n    memory_max_tooltip: Maximum memory usage of the query\n    mem_arbitration: Mem Arbitration\n    mem_arbitration_tooltip: Total wait time of memory arbitration of the query\n    disk_max: Max Disk\n    disk_max_tooltip: Maximum disk usage of the query\n    digest: Query Template ID\n    digest_tooltip: a.k.a. Query digest\n    is_internal: Is Internal?\n    is_internal_tooltip: Whether this is an internal query\n    is_success: Is Success?\n    is_success_tooltip: Whether query is executed successfully\n    is_prepared: Is Prepared?\n    is_prepared_tooltip: Is Generated by the prepare statement\n    is_plan_from_cache: Is Plan from Cache?\n    is_plan_from_binding: Is Plan from Binding?\n    result: Result\n    result_tooltip: Whether query is executed successfully\n    index_names: Index Names\n    index_names_tooltip: The name of the used index\n    stats: Used Statistics\n    backoff_types: Backoff Types\n    user: Execution User\n    user_tooltip: The user that executes the query\n    host: Client Address\n    host_tooltip: The address of the client that sends the query\n    db: Execution Database\n    db_tooltip: The database used to execute the query\n\n    query_time2: Query Time\n    query_time2_tooltip: The elapsed wall time when execution the query\n    parse_time: Parse Time\n    parse_time_tooltip: Time consumed when parsing the query\n    compile_time: Generate Plan Time\n    rewrite_time: Rewrite Plan Time\n    preproc_subqueries_time: Preprocess Sub-Query Time\n    preproc_subqueries_time_tooltip: Time consumed when pre-processing the subquery during the rewrite plan phase\n    optimize_time: Optimize Plan Time\n    wait_ts: Get Start Ts Time\n    wait_ts_tooltip: Time consumed when getting a start timestamp when transaction begins\n    cop_time: Coprocessor Executor Time\n    cop_time_tooltip: 'The elapsed wall time when {{distro.tidb}} Coprocessor executor waiting all Coprocessor requests to finish (note: when there are JOIN in SQL statement, multiple {{distro.tidb}} Coprocessor executors may be running in parallel, which may cause this time not being a wall time)'\n    wait_time: Coprocessor Wait Time\n    wait_time_tooltip: 'The total time of Coprocessor request is prepared and wait to execute in {{distro.tikv}}, which may happen when retrieving a snapshot though Raft concensus protocol (note: {{distro.tikv}} waits requests in parallel so that this is not a wall time)'\n    process_time: Coprocessor Process Time\n    process_time_tooltip: 'The total time of Coprocessor request being executed in {{distro.tikv}} (note: {{distro.tikv}} executes requests in parallel so that this is not a wall time)'\n    backoff_time: Execution Backoff Time\n    backoff_time_tooltip: 'The total backoff waiting time before retry when a query encounters errors (note: there may be multiple backoffs in parallel so that this may not be a wall time)'\n    lock_keys_time: Lock Keys Time\n    lock_keys_time_tooltip: Time consumed when locking keys in pessimistic transaction\n    get_commit_ts_time: Get Commit Ts Time\n    get_commit_ts_time_tooltip: Time consumed when getting a commit timestamp for 2PC commit phase when transaction commits\n    local_latch_wait_time: Local Latch Wait Time\n    local_latch_wait_time_tooltip: Time consumed when {{distro.tidb}} waits for the lock in the current {{distro.tidb}} instance before 2PC commit phase when transaction commits\n    resolve_lock_time: Resolve Lock Time\n    resolve_lock_time_tooltip: Time consumed when {{distro.tidb}} resolves locks from other transactions in 2PC prewrite phase when transaction commits\n    prewrite_time: Prewrite Time\n    prewrite_time_tooltip: Time consumed in 2PC prewrite phase when transaction commits\n    wait_prewrite_binlog_time: Wait Binlog Prewrite Time\n    wait_prewrite_binlog_time_tooltip: Time consumed when waiting Binlog prewrite to finish\n    commit_time: Commit Time\n    commit_time_tooltip: Time consumed in 2PC commit phase when transaction commits\n    commit_backoff_time: Commit Backoff Time\n    commit_backoff_time_tooltip: 'The total backoff waiting time when 2PC commit encounters errors (note: there may be multiple backoffs in parallel so that this may not be a wall time)'\n    write_sql_response_total: Send response Time\n    write_sql_response_total_tooltip: Time consumed when sending response to the SQL client\n    exec_retry_time: Retried execution Time\n    exec_retry_time_tooltip: Wall time consumed when SQL statement is retried and executed again, except for the last exection\n\n    request_count: Request Count\n    process_keys: Process Keys\n    total_keys: Total Keys\n    cop_proc_addr: Copr Address (Process)\n    cop_proc_addr_tooltip: The address of the {{distro.tikv}} that takes most time process the Coprocessor request\n    cop_wait_addr: Copr Address (Wait)\n    cop_wait_addr_tooltip: The address of the {{distro.tikv}} that takes most time wait the Coprocessor request\n\n    txn_start_ts: Start Timestamp\n    txn_start_ts_tooltip: Transaction start timestamp, a.k.a. Transaction ID\n    write_keys: Write Keys\n    write_size: Write Size\n    prewrite_region: Prewrite Regions\n    txn_retry: Transaction Retries\n\n    prev_stmt: Previous Query\n    plan: Execution Plan\n\n    cop_proc_avg: Mean Cop Proc # ?\n    cop_wait_avg: Mean Cop Wait # ?\n\n    rocksdb_delete_skipped_count: RocksDB Skipped Deletions\n    rocksdb_delete_skipped_count_tooltip: Total number of deleted (a.k.a. tombstone) key versions that are skipped during iteration (RocksDB delete_skipped_count)\n    rocksdb_key_skipped_count: RocksDB Skipped Keys\n    rocksdb_key_skipped_count_tooltip: Total number of keys skipped during iteration (RocksDB key_skipped_count)\n    rocksdb_block_cache_hit_count: RocksDB Block Cache Hits\n    rocksdb_block_cache_hit_count_tooltip: Total number of hits from the block cache (RocksDB block_cache_hit_count)\n    rocksdb_block_read_count: RocksDB Block Reads\n    rocksdb_block_read_count_tooltip: Total number of blocks RocksDB read from file (RocksDB block_read_count)\n    rocksdb_block_read_byte: RocksDB Read Size\n    rocksdb_block_read_byte_tooltip: Total number of bytes RocksDB read from file (RocksDB block_read_byte)\n\n    ru: RU\n    ru_tooltip: request units\n    ru_v2: RU V2\n    ru_v2_tooltip: Total RU V2 consumed by this query\n    ru_v2_detail: RU V2 Detail\n    ru_v2_detail_tooltip: Per-component RU V2 breakdown (raw)\n    ru_v2_metrics_label: RU V2 Metrics\n    ru_v2_metrics:\n      total_ru: Total RU\n      tidb_ru: TiDB RU\n      tikv_ru: TiKV RU\n      tiflash_ru: TiFlash RU\n      txn_cnt: Transaction Count\n      plan_cnt: Plan Count\n      plan_derive_stats_paths: Plan Derive Stats Paths\n      session_parser_total: Session Parser Total\n      executor_l1: Executor L1\n      executor_l2: Executor L2\n      executor_l3: Executor L3\n      executor_l5_insert_rows: Executor L5 Insert Rows\n      result_chunk_cells: Result Chunk Cells\n      resource_manager_read_cnt: Resource Manager Read Count\n      resource_manager_write_cnt: Resource Manager Write Count\n      tikv_coprocessor_executor_iterations: TiKV Coprocessor Executor Iterations\n      tikv_coprocessor_response_bytes: TiKV Coprocessor Response Bytes\n      tikv_coprocessor_executor_work_total: TiKV Coprocessor Executor Work Total\n      tikv_storage_processed_keys_get: TiKV Storage Processed Keys (Get)\n      tikv_storage_processed_keys_batch_get: TiKV Storage Processed Keys (Batch Get)\n      tikv_kv_engine_cache_miss: TiKV KV Engine Cache Miss\n      tikv_raftstore_store_write_trigger_wb_bytes: TiKV Raftstore Write Trigger WB Bytes\n    resource_group: Resource Group\n    resource_group_tooltip: The resource group that the query belongs to\n    time_queued_by_rc: The total time queued by RC\n    time_queued_by_rc_tooltip: 'The total wait time spent in the resource queue (note: {{distro.tikv}} executes requests in parallel so that this is not a wall time)'\n\n    # Network fields\n    unpacked_bytes_sent_tikv_total: Bytes Sent to TiKV\n    unpacked_bytes_sent_tikv_total_tooltip: The number of bytes sent to TiKV\n    unpacked_bytes_received_tikv_total: Bytes Received from TiKV\n    unpacked_bytes_received_tikv_total_tooltip: The number of bytes received from TiKV\n    unpacked_bytes_sent_tikv_cross_zone: Cross-Zone Bytes Sent to TiKV\n    unpacked_bytes_sent_tikv_cross_zone_tooltip: The number of bytes sent to TiKV across zones\n    unpacked_bytes_received_tikv_cross_zone: Cross-Zone Bytes Received from TiKV\n    unpacked_bytes_received_tikv_cross_zone_tooltip: The number of bytes received from TiKV across zones\n    unpacked_bytes_sent_tiflash_total: Bytes Sent to TiFlash\n    unpacked_bytes_sent_tiflash_total_tooltip: The number of bytes sent to TiFlash\n    unpacked_bytes_received_tiflash_total: Bytes Received from TiFlash\n    unpacked_bytes_received_tiflash_total_tooltip: The number of bytes received from TiFlash\n    unpacked_bytes_sent_tiflash_cross_zone: Cross-Zone Bytes Sent to TiFlash\n    unpacked_bytes_sent_tiflash_cross_zone_tooltip: The number of bytes sent to TiFlash across zones\n    unpacked_bytes_received_tiflash_cross_zone: Cross-Zone Bytes Received from TiFlash\n    unpacked_bytes_received_tiflash_cross_zone_tooltip: The number of bytes received from TiFlash across zones\n    ia_remote_read_segment_size: IA Remote Read Segment Size\n    ia_remote_read_segment_size_tooltip: Total number of bytes read from IA remote segments\n    ia_remote_read_segment_wait_time: IA Remote Read Segment Wait Time\n    ia_remote_read_segment_wait_time_tooltip: The wait time spent reading IA remote segments\n\n  common:\n    status:\n      success: Success\n      error: Failed\n  overview:\n    empty_result: No matched slow queries\n    result_count: '{{ n }} results.'\n    slow_load_info: On-the-fly update is disabled due to slow data loading. You can initiate query manually by clicking the \"Query\" button.\n  detail:\n    head:\n      title: Slow Query Detail\n      back: List\n      sql: Query\n      previous_sql: Previous Query\n    plan:\n      title: Execution Plan\n      text: Text\n      table: Table\n      visual: Visual\n      modal_title: Visual Plan\n    tabs:\n      basic: Basic\n      time: Time\n      copr: Coprocessor\n      txn: Transaction\n      ru_v2: RU V2\n      warnings: Warnings\n  toolbar:\n    schemas:\n      placeholder: All Databases\n      selected: '{{ n }} Databases'\n      columnTitle: Execution Database Name\n    resource_groups:\n      placeholder: All Resource Groups\n      selected: '{{ n }} Resource Groups'\n      columnTitle: Resource Group Name\n    select_columns:\n      show_full_sql: Show Full Query Text\n    show_internal: Show Internal\n    refresh: Refresh\n    digest:\n      placeholder: Digest\n    keyword:\n      placeholder: Filter keyword\n    query: Query\n    export: Export\n    exporting: Exporting\n    help: Help\n    help_url: https://docs.pingcap.com/tidb/dev/dashboard-slow-query\n    download_db: Download DB\n  download_modal:\n    download: Download\n    title: 'Download Slow Query db File'\n    download_by_day: Download By Day\n    download_by_hour: Download By Hour\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/translations/zh.yaml",
    "content": "slow_query_v2:\n  overview:\n    head:\n      title: 慢查询\n  detail:\n    head:\n      title: 慢查询对比\n\nslow_query:\n  nav_title: 慢查询\n  fields:\n    instance: '{{distro.tidb}} 实例'\n    instance_tooltip: 处理该 SQL 查询的 {{distro.tidb}} 实例地址\n    connection_id: 连接号\n    connection_id_tooltip: SQL 查询客户端连接 ID\n    sql: SQL\n    query: SQL\n    sql_tooltip: SQL\n    timestamp: 结束运行时间\n    timestamp_tooltip: 该 SQL 查询结束运行时的时间\n    query_time: 总执行时间\n    query_time_tooltip: 该 SQL 查询总的执行时间\n    memory_max: 最大内存\n    memory_max_tooltip: 该 SQL 查询执行时占用的最大内存空间\n    mem_arbitration: 等待内存资源耗时\n    mem_arbitration_tooltip: 该 SQL 等待内存资源的总耗时\n    disk_max: 最大磁盘空间\n    disk_max_tooltip: 该 SQL 查询执行时占用的最大磁盘空间\n    digest: SQL 模板 ID\n    digest_tooltip: SQL 模板的唯一标识（SQL 指纹）\n    is_internal: 是否为内部 SQL 查询\n    is_success: 是否执行成功\n    is_success_tooltip: SQL 查询是否执行成功\n    is_prepared: 是否由 prepare 语句生成\n    is_plan_from_cache: 查询计划是否来自缓存\n    is_plan_from_binding: 查询计划是否来自绑定\n    result: 执行结果\n    result_tooltip: SQL 查询是否执行成功\n    index_names: 索引名\n    index_names_tooltip: SQL 查询执行时使用的索引名称\n    stats: 使用的统计信息\n    backoff_types: 重试类型\n    user: 执行用户名\n    user_tooltip: 执行该 SQL 查询的用户名，可能存在多个执行用户，仅显示其中某一个\n    host: 客户端地址\n    host_tooltip: 发送 SQL 查询的客户端地址\n    db: 执行数据库\n    db_tooltip: 执行该 SQL 查询时使用的数据库名称\n\n    query_time2: SQL 执行时间\n    query_time2_tooltip: 执行 SQL 耗费的自然时间\n    parse_time: 解析耗时\n    parse_time_tooltip: 解析该 SQL 查询的耗时\n    compile_time: 生成执行计划耗时\n    compile_time_tooltip: 生成该 SQL 的执行计划的耗时\n    rewrite_time: 重写执行计划耗时\n    rewrite_time_tooltip: 重写执行计划的耗时，例如常量折叠等\n    preproc_subqueries_time: 子查询预处理耗时\n    optimize_time: 优化执行计划耗时\n    optimize_time_tooltip: 优化器寻找执行计划的耗时，包括规则优化和物理优化的耗时\n    wait_ts: 取事务 Start Ts 耗时\n    wait_ts_tooltip: 从 {{distro.pd}} 取事务开始时间戳步骤的耗时\n    cop_time: Coprocessor 执行耗时\n    cop_time_tooltip: '{{distro.tidb}} Coprocessor 算子等待所有任务在 {{distro.tikv}} 上并行执行完毕耗费的自然时间（注：当 SQL 语句中包含 JOIN 时，多个 {{distro.tidb}} Coprocessor 算子可能会并行执行，此时不再等同于自然时间）'\n    wait_time: Coprocessor 累计等待耗时\n    wait_time_tooltip: '{{distro.tikv}} 准备并等待 Coprocessor 任务执行的累计时间，等待过程中包括通过 Raft 一致性协议取快照等（注：{{distro.tikv}} 会并行等待任务，因此该时间不是自然流逝时间）'\n    process_time: Coprocessor 累计执行耗时\n    process_time_tooltip: '{{distro.tikv}} 执行 Coprocessor 任务的累计处理时间（注：{{distro.tikv}} 会并行处理任务，因此该时间不是自然流逝时间）'\n    lock_keys_time: 上锁耗时\n    lock_keys_time_tooltip: 悲观事务中对相关行数据进行上锁的耗时\n    backoff_time: 执行阶段累计 Backoff 耗时\n    backoff_time_tooltip: 在执行失败时，Backoff 机制等待一段时间再重试时的 Backoff 累计耗时（注：可能同时存在多个 Backoff，因此该时间可能不是自然流逝时间）\n    get_commit_ts_time: 取事务 Commit Ts 耗时\n    get_commit_ts_time_tooltip: 从 {{distro.pd}} 取提交时间戳（事务号）步骤的耗时\n    local_latch_wait_time: '{{distro.tidb}} 本地等锁耗时'\n    local_latch_wait_time_tooltip: 事务在 {{distro.tidb}} 本地与其他事务产生了锁冲突并等待的耗时\n    resolve_lock_time: 解锁耗时\n    resolve_lock_time_tooltip: 事务在提交过程中与其他事务产生了锁冲突并处理锁冲突的耗时\n    prewrite_time: Prewrite 阶段耗时\n    prewrite_time_tooltip: 事务两阶段提交中第一阶段（prewrite 阶段）的耗时\n    wait_prewrite_binlog_time: Binlog Prewrite 等待耗时\n    wait_prewrite_binlog_time_tooltip: 等待 Binlog Prewrite 完成的耗时\n    commit_time: Commit 阶段耗时\n    commit_time_tooltip: 事务两阶段提交中第二阶段（commit 阶段）的耗时\n    commit_backoff_time: Commit 阶段累计 Backoff 耗时\n    commit_backoff_time_tooltip: 事务递交失败时，Backoff 机制等待一段时间再重试时的 Backoff 累计耗时（注：可能同时存在多个 Backoff，因此该时间可能不是自然流逝时间）\n    write_sql_response_total: 发送结果耗时\n    write_sql_response_total_tooltip: 发送 SQL 语句执行结果给客户端的耗时\n    exec_retry_time: 前序执行耗时\n    exec_retry_time_tooltip: 由于锁冲突或错误，计划可能会执行失败并重试执行多次，该时间是不包含最后一次执行的前序执行自然时间（注：执行计划中的时间不含该前序时间）\n\n    request_count: Coprocessor 请求数\n    process_keys: 可见版本数\n    total_keys: 遇到版本数\n    total_keys_tooltip: 含已删除或覆盖但未 GC 的版本\n    cop_proc_addr: 最长处理时间实例\n    cop_proc_addr_tooltip: 耗费最长时间处理 Coprocessor 请求的 {{distro.tikv}} 实例地址\n    cop_wait_addr: 最长等待时间实例\n    cop_wait_addr_tooltip: 耗费最长时间等待 Coprocessor 请求的 {{distro.tikv}} 实例地址\n\n    txn_start_ts: 事务号\n    txn_start_ts_tooltip: 事务开始的时间戳，也即是事务号\n    write_keys: 写入 Key 个数\n    write_size: 写入数据量\n    prewrite_region: Prewrite 涉及 Regions 个数\n    txn_retry: 事务重试次数\n\n    prev_stmt: 前一条 SQL 查询\n    plan: 执行计划\n\n    cop_proc_avg: 平均处理 # ?\n    cop_wait_avg: 平均等待 # ?\n\n    rocksdb_delete_skipped_count: RocksDB 已删除 Key 扫描数\n    rocksdb_delete_skipped_count_tooltip: RocksDB 扫数据时遇到的已删除 (tombstone) Key 数量 (delete_skipped_count)\n    rocksdb_key_skipped_count: RocksDB Key 扫描数\n    rocksdb_key_skipped_count_tooltip: RocksDB 扫数据时所有遇到的 Key 数量 (key_skipped_count)\n    rocksdb_block_cache_hit_count: RocksDB 缓存读次数\n    rocksdb_block_cache_hit_count_tooltip: RocksDB 从 Block Cache 缓存中读数据的次数 (block_cache_hit_count)\n    rocksdb_block_read_count: RocksDB 文件系统读次数\n    rocksdb_block_read_count_tooltip: RocksDB 从文件系统中读数据的次数 (block_read_count)\n    rocksdb_block_read_byte: RocksDB 文件系统读数据量\n    rocksdb_block_read_byte_tooltip: RocksDB 从文件系统中读数据的数据量 (block_read_byte)\n\n    ru: RU\n    ru_tooltip: 资源单位(RU)\n    ru_v2: RU V2\n    ru_v2_tooltip: 本次查询消耗的 RU V2 总量\n    ru_v2_detail: RU V2 明细\n    ru_v2_detail_tooltip: 各组件 RU V2 拆分（原始数据）\n    ru_v2_metrics_label: RU V2 Metrics\n    ru_v2_metrics:\n      total_ru: Total RU\n      tidb_ru: TiDB RU\n      tikv_ru: TiKV RU\n      tiflash_ru: TiFlash RU\n      txn_cnt: 事务数\n      plan_cnt: Plan 数\n      plan_derive_stats_paths: Plan Derive Stats Paths\n      session_parser_total: Session Parser Total\n      executor_l1: Executor L1\n      executor_l2: Executor L2\n      executor_l3: Executor L3\n      executor_l5_insert_rows: Executor L5 插入行数\n      result_chunk_cells: Result Chunk Cells\n      resource_manager_read_cnt: Resource Manager 读次数\n      resource_manager_write_cnt: Resource Manager 写次数\n      tikv_coprocessor_executor_iterations: TiKV Coprocessor Executor 迭代数\n      tikv_coprocessor_response_bytes: TiKV Coprocessor 响应字节\n      tikv_coprocessor_executor_work_total: TiKV Coprocessor Executor 工作总量\n      tikv_storage_processed_keys_get: TiKV Storage Processed Keys (Get)\n      tikv_storage_processed_keys_batch_get: TiKV Storage Processed Keys (Batch Get)\n      tikv_kv_engine_cache_miss: TiKV KV Engine Cache Miss\n      tikv_raftstore_store_write_trigger_wb_bytes: TiKV Raftstore Write Trigger WB Bytes\n    resource_group: 资源组\n    resource_group_tooltip: SQL 语句所属的资源组\n    time_queued_by_rc: RC 等待累积耗时\n    time_queued_by_rc_tooltip: SQL 语句在资源组队列中等待的累积时间（注：{{distro.tikv}} 会并行等待任务，因此该时间不是自然流逝时间）\n\n    # Network fields\n    unpacked_bytes_sent_tikv_total: 发送至 TiKV 的字节数\n    unpacked_bytes_sent_tikv_total_tooltip: 发送至 TiKV 的字节数\n    unpacked_bytes_received_tikv_total: 从 TiKV 接收的字节数\n    unpacked_bytes_received_tikv_total_tooltip: 从 TiKV 接收的字节数\n    unpacked_bytes_sent_tikv_cross_zone: 跨可用区发送至 TiKV 的字节数\n    unpacked_bytes_sent_tikv_cross_zone_tooltip: 跨可用区发送至 TiKV 的字节数\n    unpacked_bytes_received_tikv_cross_zone: 跨可用区从 TiKV 接收的字节数\n    unpacked_bytes_received_tikv_cross_zone_tooltip: 跨可用区从 TiKV 接收的字节数\n    unpacked_bytes_sent_tiflash_total: 发送至 TiFlash 的字节数\n    unpacked_bytes_sent_tiflash_total_tooltip: 发送至 TiFlash 的字节数\n    unpacked_bytes_received_tiflash_total: 从 TiFlash 接收的字节数\n    unpacked_bytes_received_tiflash_total_tooltip: 从 TiFlash 接收的字节数\n    unpacked_bytes_sent_tiflash_cross_zone: 跨可用区发送至 TiFlash 的字节数\n    unpacked_bytes_sent_tiflash_cross_zone_tooltip: 跨可用区发送至 TiFlash 的字节数\n    unpacked_bytes_received_tiflash_cross_zone: 跨可用区从 TiFlash 接收的字节数\n    unpacked_bytes_received_tiflash_cross_zone_tooltip: 跨可用区从 TiFlash 接收的字节数\n\n    # IA remote read\n    ia_remote_read_segment_size: IA 远程读取数据量\n    ia_remote_read_segment_size_tooltip: 从 IA 远程存储段读取的数据总量\n    ia_remote_read_segment_wait_time: IA 远程读分段等待时间\n    ia_remote_read_segment_wait_time_tooltip: IA 远程读分段等待时间\n\n  common:\n    status:\n      success: 成功\n      error: 失败\n  overview:\n    empty_result: 没有符合条件的慢查询\n    result_count: '{{ n }} 条结果。'\n    slow_load_info: 数据加载耗时较长，已禁用即时更新。修改查询条件后，您可以手工点击”查询“按钮来发起查询。\n  detail:\n    head:\n      title: 慢查询详情\n      back: 列表\n      sql: SQL 查询\n      previous_sql: 上一条 SQL 查询\n    plan:\n      title: 执行计划\n      text: 文本\n      table: 表格\n      visual: 图形\n      modal_title: 执行计划可视化\n    tabs:\n      basic: 基本信息\n      time: 执行时间\n      copr: Coprocessor 读取\n      txn: 事务\n      ru_v2: RU V2\n      warnings: 警告\n  toolbar:\n    schemas:\n      placeholder: 所有数据库\n      selected: '{{ n }} 数据库'\n      columnTitle: 执行数据库名\n    resource_groups:\n      placeholder: 所有资源组\n      selected: '{{ n }} 资源组'\n      columnTitle: 资源组名\n    select_columns:\n      show_full_sql: 显示完整 SQL 文本\n    show_internal: 显示内部查询\n    refresh: 刷新\n    digest:\n      placeholder: Digest\n    keyword:\n      placeholder: 关键字过滤\n    query: 查询\n    export: 导出\n    exporting: 正在导出\n    help: 帮助\n    help_url: https://docs.pingcap.com/zh/tidb/dev/dashboard-slow-query\n    download_db: 下载 DB\n  download_modal:\n    download: 下载\n    title: '下载慢查询 db 文件'\n    download_by_day: 按天下载\n    download_by_hour: 按小时下载\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/utils/detail-url-state.ts",
    "content": "import useUrlState from '@ahooksjs/use-url-state'\n\ntype DetailUrlState = Partial<\n  Record<'digest' | 'connection_id' | 'timestamp', string>\n>\n\nexport function useSlowQueryDetailUrlState() {\n  const [queryParams, _] = useUrlState<DetailUrlState>()\n\n  // digest\n  const digest = queryParams.digest ?? ''\n  // connect_id\n  const connectionId = queryParams.connection_id ?? ''\n  // timestamp, timestamp is float type number\n  const timestamp = parseFloat(queryParams.timestamp)\n\n  return {\n    digest,\n    connectionId,\n    timestamp\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/utils/helpers.ts",
    "content": "import { IColumnKeys } from '@lib/components'\n\nexport const LIMITS = [100, 200, 500, 1000]\n\nexport const DEF_SLOW_QUERY_COLUMN_KEYS: IColumnKeys = {\n  query: true,\n  timestamp: true,\n  query_time: true,\n  memory_max: true\n}\nexport const SLOW_QUERY_VISIBLE_COLUMN_KEYS = 'slow_query.visible_column_keys'\nexport const SLOW_QUERY_SHOW_FULL_SQL = 'slow_query.show_full_sql'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/utils/list-url-state.ts",
    "content": "import { useCallback, useMemo } from 'react'\nimport useUrlState from '@ahooksjs/use-url-state'\nimport {\n  DEFAULT_TIME_RANGE,\n  TimeRange,\n  toURLTimeRange,\n  urlToTimeRange\n} from '@lib/components/TimeRangeSelector'\n\ntype ListUrlState = Partial<\n  Record<\n    | 'from'\n    | 'to'\n    | 'dbs'\n    | 'digest'\n    | 'ru_groups'\n    | 'term'\n    | 'limit'\n    | 'fields'\n    | 'full_sql'\n    | 'order'\n    | 'row'\n    | 'show_internal',\n    string\n  >\n>\n\ntype OrderOpt = {\n  col: string\n  type: 'asc' | 'desc'\n}\n\nexport function useSlowQueryListUrlState() {\n  const [queryParams, setQueryParams] = useUrlState<ListUrlState>()\n\n  // from & to\n  const timeRange = useMemo(() => {\n    const { from, to } = queryParams\n    if (from && to) {\n      return urlToTimeRange({ from, to })\n    }\n    return DEFAULT_TIME_RANGE\n  }, [queryParams.from, queryParams.to])\n\n  const setTimeRange = useCallback(\n    (newTimeRange: TimeRange) => {\n      setQueryParams({ ...toURLTimeRange(newTimeRange) })\n    },\n    [setQueryParams]\n  )\n\n  // dbs\n  const dbs = useMemo<string[]>(() => {\n    const dbs = queryParams.dbs\n    return dbs ? dbs.split(',') : []\n  }, [queryParams.dbs])\n  const setDbs = useCallback(\n    (v: string[]) => {\n      setQueryParams({ dbs: v.join(',') })\n    },\n    [setQueryParams]\n  )\n\n  // digest\n  const digest = queryParams.digest ?? ''\n  const setDigest = useCallback(\n    (v: string) => {\n      setQueryParams({ digest: v })\n    },\n    [setQueryParams]\n  )\n\n  // ru_groups\n  const ruGroups = useMemo(() => {\n    const ruGroups = queryParams.ru_groups\n    return ruGroups ? ruGroups.split(',') : []\n  }, [queryParams.ru_groups])\n  const setRuGroups = useCallback(\n    (v: string[]) => {\n      setQueryParams({ ru_groups: v.join(',') })\n    },\n    [setQueryParams]\n  )\n\n  // term\n  const term = queryParams.term ?? ''\n  const setTerm = useCallback(\n    (v: string) => {\n      setQueryParams({ term: v })\n    },\n    [setQueryParams]\n  )\n\n  // limit\n  const limit = parseInt(queryParams.limit ?? '100')\n  const setLimit = useCallback(\n    (v: number) => {\n      setQueryParams({ limit: v.toString() })\n    },\n    [setQueryParams]\n  )\n\n  // order\n  const order = useMemo<OrderOpt>(() => {\n    const _order = queryParams.order ?? '-timestamp'\n    let type: 'asc' | 'desc' = 'asc'\n    let col = _order\n    if (col.startsWith('-')) {\n      col = col.slice(1)\n      type = 'desc'\n    }\n    return { col, type }\n  }, [queryParams.order])\n  const setOrder = useCallback(\n    (v: OrderOpt) => {\n      setQueryParams({ order: v.type === 'asc' ? v.col : '-' + v.col })\n    },\n    [setQueryParams]\n  )\n  const resetOrder = useCallback(() => {\n    setQueryParams({ order: undefined })\n  }, [setQueryParams])\n\n  // row\n  const rowIdx = useMemo(() => {\n    const r = parseInt(queryParams.row)\n    if (r >= 0) return r\n    return -1\n  }, [queryParams.row])\n  const setRowIdx = useCallback(\n    (v: number) => {\n      setQueryParams({ row: v + '' })\n    },\n    [setQueryParams]\n  )\n\n  // show internal\n  const showInternal = useMemo(() => {\n    const showInternalStr = String(\n      queryParams.show_internal ?? ''\n    ).toLowerCase()\n    if (showInternalStr === '1' || showInternalStr === 'true') {\n      return true\n    }\n    return false\n  }, [queryParams.show_internal])\n  const setShowInternal = useCallback(\n    (show: boolean) => {\n      setQueryParams({ show_internal: show })\n    },\n    [setQueryParams]\n  )\n\n  return {\n    queryParams,\n    setQueryParams,\n\n    timeRange,\n    setTimeRange,\n\n    dbs,\n    setDbs,\n\n    digest,\n    setDigest,\n\n    ruGroups,\n    setRuGroups,\n\n    term,\n    setTerm,\n\n    limit,\n    setLimit,\n\n    order,\n    setOrder,\n    resetOrder,\n\n    rowIdx,\n    setRowIdx,\n\n    showInternal,\n    setShowInternal\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/utils/tableColumns.tsx",
    "content": "import { Badge } from 'antd'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport React from 'react'\nimport { useTranslation } from 'react-i18next'\n\nimport { SlowqueryModel } from '@lib/client'\nimport { TableColumnFactory } from '@lib/utils/tableColumnFactory'\n\n//////////////////////////////////////////\n\nfunction ResultStatusBadge({ status }: { status: 'success' | 'error' }) {\n  const { t } = useTranslation()\n  return (\n    <Badge status={status} text={t(`slow_query.common.status.${status}`)} />\n  )\n}\n\n//////////////////////////////////////////\nconst TRANS_KEY_PREFIX = 'slow_query.fields'\n\nexport const derivedFields = {\n  cop_proc_avg: [\n    { tooltipPrefix: 'mean', fieldName: 'cop_proc_avg' },\n    { tooltipPrefix: 'max', fieldName: 'cop_proc_max' },\n    { tooltipPrefix: 'p90', fieldName: 'cop_proc_p90' }\n  ],\n  cop_wait_avg: [\n    { tooltipPrefix: 'mean', fieldName: 'cop_wait_avg' },\n    { tooltipPrefix: 'max', fieldName: 'cop_wait_max' },\n    { tooltipPrefix: 'p90', fieldName: 'cop_wait_p90' }\n  ]\n}\n\n//////////////////////////////////////////\n\nexport function slowQueryColumns(\n  rows: SlowqueryModel[],\n  tableSchemaColumns: string[],\n  showFullSQL?: boolean\n): IColumn[] {\n  const tcf = new TableColumnFactory(TRANS_KEY_PREFIX, tableSchemaColumns)\n  return tcf.columns([\n    tcf.sqlText('query', showFullSQL, rows),\n    tcf.textWithTooltip('digest', rows),\n    tcf.textWithTooltip('instance', rows),\n    tcf.textWithTooltip('db', rows),\n    tcf.textWithTooltip('connection_id', rows),\n    tcf.timestamp('timestamp', rows),\n\n    tcf.bar.single('query_time', 's', rows),\n    tcf.bar.single('parse_time', 's', rows),\n    tcf.bar.single('compile_time', 's', rows),\n    tcf.bar.single('process_time', 's', rows),\n    tcf.bar.single('memory_max', 'bytes', rows),\n    tcf.bar.single('mem_arbitration', 's', rows),\n    tcf.bar.single('disk_max', 'bytes', rows),\n\n    tcf.textWithTooltip('txn_start_ts', rows),\n    // success columnn\n    tcf.textWithTooltip('success', rows).patchConfig({\n      name: 'result',\n      minWidth: 50,\n      maxWidth: 100,\n      onRender: (rec) => (\n        <ResultStatusBadge status={rec.success === 1 ? 'success' : 'error'} />\n      )\n    }),\n\n    // basic\n    // is_internal column\n    tcf.textWithTooltip('is_internal', rows).patchConfig({\n      minWidth: 50,\n      maxWidth: 100,\n      onRender: (rec) => (rec.is_internal === 1 ? 'Yes' : 'No')\n    }),\n    tcf.textWithTooltip('index_names', rows),\n    tcf.textWithTooltip('stats', rows),\n    tcf.textWithTooltip('backoff_types', rows),\n    // connection\n    tcf.textWithTooltip('user', rows),\n    tcf.textWithTooltip('host', rows),\n    // time\n    tcf.bar.single('wait_ts', 's', rows),\n    tcf.bar.single('wait_time', 's', rows),\n    tcf.bar.single('backoff_time', 's', rows),\n    tcf.bar.single('get_commit_ts_time', 's', rows),\n    tcf.bar.single('local_latch_wait_time', 's', rows),\n    tcf.bar.single('prewrite_time', 's', rows),\n    tcf.bar.single('commit_time', 's', rows),\n    tcf.bar.single('commit_backoff_time', 's', rows),\n    tcf.bar.single('resolve_lock_time', 's', rows),\n    // cop\n    tcf.bar.multiple({ sources: derivedFields.cop_proc_avg }, 's', rows),\n    tcf.bar.multiple({ sources: derivedFields.cop_wait_avg }, 's', rows),\n    tcf.bar.single('request_count', 'short', rows),\n    tcf.bar.single('process_keys', 'short', rows),\n    tcf.bar.single('total_keys', 'short', rows),\n    tcf.textWithTooltip('cop_proc_addr', rows),\n    tcf.textWithTooltip('cop_wait_addr', rows),\n    // transaction\n    tcf.bar.single('write_keys', 'short', rows),\n    tcf.bar.single('write_size', 'bytes', rows),\n    tcf.bar.single('prewrite_region', 'short', rows),\n    tcf.bar.single('txn_retry', 'short', rows),\n    // rocksdb\n    tcf.bar.single('rocksdb_delete_skipped_count', 'short', rows).patchConfig({\n      minWidth: 220,\n      maxWidth: 250\n    }),\n    tcf.bar.single('rocksdb_key_skipped_count', 'short', rows).patchConfig({\n      minWidth: 220,\n      maxWidth: 250\n    }),\n    tcf.bar.single('rocksdb_block_cache_hit_count', 'short', rows).patchConfig({\n      minWidth: 220,\n      maxWidth: 250\n    }),\n    tcf.bar.single('rocksdb_block_read_count', 'short', rows).patchConfig({\n      minWidth: 220,\n      maxWidth: 250\n    }),\n    tcf.bar.single('rocksdb_block_read_byte', 'bytes', rows).patchConfig({\n      minWidth: 220,\n      maxWidth: 250\n    }),\n    // resource control\n    tcf.bar.single('ru', 'none', rows),\n    tcf.bar.single('ru_v2', 'none', rows),\n    tcf.textWithTooltip('ru_v2_detail', rows),\n    tcf.textWithTooltip('resource_group', rows),\n    tcf.bar.single('time_queued_by_rc', 's', rows),\n\n    // Network fields\n    tcf.bar.single('unpacked_bytes_sent_tikv_total', 'bytes', rows),\n    tcf.bar.single('unpacked_bytes_received_tikv_total', 'bytes', rows),\n    tcf.bar.single('unpacked_bytes_sent_tikv_cross_zone', 'bytes', rows),\n    tcf.bar.single('unpacked_bytes_received_tikv_cross_zone', 'bytes', rows),\n    tcf.bar.single('unpacked_bytes_sent_tiflash_total', 'bytes', rows),\n    tcf.bar.single('unpacked_bytes_received_tiflash_total', 'bytes', rows),\n    tcf.bar.single('unpacked_bytes_sent_tiflash_cross_zone', 'bytes', rows),\n    tcf.bar.single('unpacked_bytes_received_tiflash_cross_zone', 'bytes', rows),\n    tcf.bar.single('ia_remote_read_segment_size', 'bytes', rows),\n    tcf.bar.single('ia_remote_read_segment_wait_time', 's', rows)\n  ])\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/utils/telemetry.ts",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\nimport { mixpanel } from '@lib/utils/telemetry'\n\nexport const telemetry = {\n  clickTopSlowQueryTab() {\n    mixpanel.track('Slowquery: Click TopSlowquery Tab')\n  },\n  clickTableRow() {\n    mixpanel.track('Slowquery: Click Table Row')\n  },\n  clickQueryButton() {\n    mixpanel.track('Slowquery: Click Query Button')\n  },\n\n  clickPlanTabs(tab: string, queryDigest: string) {\n    mixpanel.track('Slowquery: Plan Tab Clicked', { tab, queryDigest })\n  },\n  toggleVisualPlanModal(action: 'open' | 'close') {\n    mixpanel.track('Slowquery: Visual Plan Modal Toggled', { action })\n  },\n  toggleExpandBtnOnNode(nodeName: string) {\n    mixpanel.track('Slowquery: Node Button Toggled', { nodeName })\n  },\n  clickNode(nodeName: string) {\n    mixpanel.track('Slowquery: Node Clicked', { nodeName })\n  },\n  clickTabOnNodeDetail(tab: string) {\n    mixpanel.track('Slowquery: Detail Tab on Node Clicked', { tab })\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/utils/useSchemaColumns.ts",
    "content": "import { useState, useEffect } from 'react'\n\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { ISlowQueryDataSource } from '../context'\n\nexport const useSchemaColumns = (\n  availableFieldsFetcher: ISlowQueryDataSource['slowQueryAvailableFieldsGet']\n) => {\n  const [schemaColumns, setSchemaColumns] = useState<string[]>([])\n  const { data, isLoading } = useClientRequest(availableFieldsFetcher)\n\n  useEffect(() => {\n    if (!data) {\n      return\n    }\n    setSchemaColumns(data.map((d) => d.toLowerCase()))\n  }, [data])\n\n  return {\n    schemaColumns,\n    isLoading\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SlowQuery/utils/useSlowQueryTableController.ts",
    "content": "import { useMemo, useState } from 'react'\nimport { useMemoizedFn, useSessionStorageState } from 'ahooks'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport {\n  TimeRange,\n  IColumnKeys,\n  DEFAULT_TIME_RANGE,\n  toTimeRangeValue\n} from '@lib/components'\nimport useOrderState, { IOrderOptions } from '@lib/utils/useOrderState'\nimport { getSelectedFields } from '@lib/utils/tableColumnFactory'\nimport { CacheMgr } from '@lib/utils/useCache'\nimport useCacheItemIndex from '@lib/utils/useCacheItemIndex'\nimport { derivedFields, slowQueryColumns } from './tableColumns'\nimport { useSchemaColumns } from './useSchemaColumns'\nimport { useChange } from '@lib/utils/useChange'\nimport { ISlowQueryDataSource } from '../context'\nimport { SlowqueryModel } from '@lib/client'\n\nconst SLOW_DATA_LOAD_THRESHOLD = 2000\n\nexport const DEF_SLOW_QUERY_COLUMN_KEYS: IColumnKeys = {\n  query: true,\n  timestamp: true,\n  query_time: true,\n  memory_max: true\n}\n\nconst QUERY_OPTIONS = 'slow_query.query_options'\n\nconst DEF_ORDER_OPTIONS: IOrderOptions = {\n  orderBy: 'timestamp',\n  desc: true\n}\n\ninterface RuntimeCacheEntity {\n  data: SlowqueryModel[]\n  isDataLoadedSlowly: boolean\n}\n\nexport interface ISlowQueryOptions {\n  visibleColumnKeys: IColumnKeys\n  timeRange: TimeRange\n  schemas: string[]\n  groups: string[]\n  searchText: string\n  limit: number\n  showInternal: boolean\n\n  // below is for showing slow queries in the statement detail page\n  digest: string\n  plans: string[]\n}\n\nexport const DEF_SLOW_QUERY_OPTIONS: ISlowQueryOptions = {\n  visibleColumnKeys: DEF_SLOW_QUERY_COLUMN_KEYS,\n  timeRange: DEFAULT_TIME_RANGE,\n  schemas: [],\n  searchText: '',\n  limit: 100,\n  showInternal: false,\n\n  digest: '',\n  plans: [],\n  groups: []\n}\n\nfunction useQueryOptions(\n  initial?: ISlowQueryOptions,\n  persistInSession: boolean = true\n) {\n  const [memoryQueryOptions, setMemoryQueryOptions] = useState(\n    initial || DEF_SLOW_QUERY_OPTIONS\n  )\n  const [sessionQueryOptions, setSessionQueryOptions] = useSessionStorageState(\n    QUERY_OPTIONS,\n    { defaultValue: initial || DEF_SLOW_QUERY_OPTIONS }\n  )\n  const queryOptions = persistInSession\n    ? sessionQueryOptions\n    : memoryQueryOptions\n  const setQueryOptions = useMemoizedFn(\n    (value: React.SetStateAction<ISlowQueryOptions>) => {\n      if (persistInSession) {\n        setSessionQueryOptions(value as any)\n      } else {\n        setMemoryQueryOptions(value)\n      }\n    }\n  )\n  return {\n    queryOptions,\n    setQueryOptions\n  }\n}\n\nexport interface ISlowQueryTableControllerOpts {\n  cacheMgr?: CacheMgr\n  showFullSQL?: boolean\n  fetchSchemas?: boolean\n  initialQueryOptions?: ISlowQueryOptions\n  persistQueryInSession?: boolean\n  filters?: Set<string>\n\n  ds: ISlowQueryDataSource\n}\n\nexport interface ISlowQueryTableController {\n  queryOptions: ISlowQueryOptions\n  setQueryOptions: (value: React.SetStateAction<ISlowQueryOptions>) => void // Updating query options will result in a refresh\n\n  orderOptions: IOrderOptions\n  changeOrder: (orderBy: string, desc: boolean) => void\n  resetOrder: () => void\n\n  isLoading: boolean\n\n  data?: SlowqueryModel[]\n  isDataLoadedSlowly: boolean | null // SLOW_DATA_LOAD_THRESHOLD. NULL = Unknown\n  allSchemas: string[]\n  allGroups: string[]\n  errors: Error[]\n\n  availableColumnsInTable: IColumn[] // returned from backend\n\n  saveClickedItemIndex: (idx: number) => void\n  getClickedItemIndex: () => number\n}\n\nexport default function useSlowQueryTableController({\n  cacheMgr,\n  showFullSQL = false,\n  fetchSchemas = true,\n  initialQueryOptions,\n  persistQueryInSession = true,\n  ds,\n  filters\n}: ISlowQueryTableControllerOpts): ISlowQueryTableController {\n  const { orderOptions, changeOrder } = useOrderState(\n    'slow_query',\n    persistQueryInSession,\n    DEF_ORDER_OPTIONS\n  )\n  function resetOrder() {\n    changeOrder(DEF_ORDER_OPTIONS.orderBy, DEF_ORDER_OPTIONS.desc)\n  }\n\n  const { queryOptions, setQueryOptions } = useQueryOptions(\n    initialQueryOptions,\n    persistQueryInSession\n  )\n\n  const [allSchemas, setAllSchemas] = useState<string[]>([])\n  const [allGroups, setAllGroups] = useState<string[]>([])\n  const [isOptionsLoading, setOptionsLoading] = useState(true)\n  const [data, setData] = useState<SlowqueryModel[] | undefined>(undefined)\n  const [isDataLoading, setDataLoading] = useState(false)\n  const [isDataLoadedSlowly, setDataLoadedSlowly] = useState<boolean | null>(\n    null\n  )\n  const [errors, setErrors] = useState<any[]>([])\n  const { schemaColumns, isLoading: isColumnsLoading } = useSchemaColumns(\n    ds.slowQueryAvailableFieldsGet\n  )\n\n  const filteredData = useMemo(() => {\n    if (!filters) {\n      return data\n    }\n    return data?.filter((d) => filters.has(d.digest!))\n  }, [data, filters])\n\n  // Reload these options when sending a new request.\n  useChange(() => {\n    async function querySchemas() {\n      if (!fetchSchemas) {\n        return\n      }\n      try {\n        // this file will be removed later\n        const res = await ds.getDatabaseList(0, 0, {\n          handleError: 'custom'\n        })\n        setAllSchemas(res?.data || [])\n      } catch (e) {\n        setErrors((prev) => prev.concat(e as Error))\n      }\n    }\n\n    async function queryGroups() {\n      try {\n        const res = await ds.infoListResourceGroupNames({\n          handleError: 'custom'\n        })\n        setAllGroups(res?.data || [])\n      } catch (e) {\n        // setErrors((prev) => prev.concat(e as Error))\n      }\n    }\n\n    async function doRequest() {\n      setOptionsLoading(true)\n      try {\n        await Promise.all([\n          querySchemas(),\n          queryGroups()\n          // Multiple query options can be added later\n        ])\n      } finally {\n        setOptionsLoading(false)\n      }\n    }\n\n    doRequest()\n  }, [queryOptions])\n\n  useChange(() => {\n    async function getSlowQueryList() {\n      // Try cache if options are unchanged.\n      // Note: When clicking \"Query\" manually, cache will be cleared before reach here. So that it\n      // will always send a request without looking up in the cache.\n\n      // The cache key is built over queryOptions, instead of evaluated one.\n      // So that when passing in same relative times options (e.g. Recent 15min)\n      // the cache can be reused.\n      const cacheKey = JSON.stringify({ queryOptions, orderOptions })\n      {\n        const cache = cacheMgr?.get(cacheKey)\n        if (cache) {\n          const cacheCloned = JSON.parse(\n            JSON.stringify(cache)\n          ) as RuntimeCacheEntity\n          setData(cacheCloned.data)\n          setDataLoadedSlowly(cacheCloned.isDataLoadedSlowly)\n          setDataLoading(false)\n          return\n        }\n      }\n\n      // May be caused by visibleColumnKeys is empty (when available columns are not yet loaded)\n      // In this case, we don't send any requests.\n      const actualVisibleColumnKeys = getSelectedFields(\n        queryOptions.visibleColumnKeys,\n        derivedFields\n      ).join(',')\n      if (actualVisibleColumnKeys.length === 0) {\n        return\n      }\n\n      const requestBeginAt = performance.now()\n      setDataLoading(true)\n\n      const timeRange = toTimeRangeValue(queryOptions.timeRange)\n\n      try {\n        const res = await ds.slowQueryListGet(\n          timeRange[0],\n          queryOptions.schemas,\n          orderOptions.desc,\n          queryOptions.digest,\n          timeRange[1],\n          actualVisibleColumnKeys,\n          queryOptions.limit,\n          orderOptions.orderBy,\n          queryOptions.plans,\n          queryOptions.groups,\n          queryOptions.searchText,\n          queryOptions.showInternal,\n          {\n            handleError: 'custom'\n          }\n        )\n        const data = res?.data || []\n        setData(data)\n        setErrors([])\n\n        const elapsed = performance.now() - requestBeginAt\n        const isLoadSlow = elapsed >= SLOW_DATA_LOAD_THRESHOLD\n        setDataLoadedSlowly(isLoadSlow)\n\n        const cacheEntity: RuntimeCacheEntity = {\n          data,\n          isDataLoadedSlowly: isLoadSlow\n        }\n        cacheMgr?.set(cacheKey, cacheEntity)\n      } catch (e) {\n        setData(undefined)\n        setErrors((prev) => prev.concat(e))\n      } finally {\n        setDataLoading(false)\n      }\n    }\n\n    getSlowQueryList()\n  }, [queryOptions, orderOptions])\n\n  const availableColumnsInTable = useMemo(\n    () => slowQueryColumns(data ?? [], schemaColumns, showFullSQL),\n    [data, schemaColumns, showFullSQL]\n  )\n\n  const { saveClickedItemIndex, getClickedItemIndex } =\n    useCacheItemIndex(cacheMgr)\n\n  return {\n    queryOptions,\n    setQueryOptions,\n\n    orderOptions,\n    changeOrder,\n    resetOrder,\n\n    isLoading: isColumnsLoading || isDataLoading || isOptionsLoading,\n\n    data: filteredData,\n    isDataLoadedSlowly,\n    allSchemas,\n    allGroups,\n    errors,\n\n    availableColumnsInTable,\n\n    saveClickedItemIndex,\n    getClickedItemIndex\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/components/StatementsTable.tsx",
    "content": "import { useMemoizedFn } from 'ahooks'\nimport React, { useCallback } from 'react'\nimport { useNavigate } from 'react-router-dom'\nimport { getTheme } from 'office-ui-fabric-react/lib/Styling'\nimport openLink from '@lib/utils/openLink'\nimport { CardTable, ICardTableProps } from '@lib/components'\nimport DetailPage from '../pages/Detail'\nimport { IStatementTableController } from '../utils/useStatementTableController'\nimport {\n  DetailsRow,\n  IDetailsListProps,\n  IDetailsRowStyles\n} from 'office-ui-fabric-react/lib/DetailsList'\n\ninterface Props extends Partial<ICardTableProps> {\n  controller: IStatementTableController\n}\n\nconst theme = getTheme()\n\nexport default function StatementsTable({ controller, ...restPrpos }: Props) {\n  const navigate = useNavigate()\n  const handleRowClick = useMemoizedFn(\n    (rec, idx, ev: React.MouseEvent<HTMLElement>) => {\n      // the evicted record's digest is empty string\n      if (!rec.digest) {\n        return\n      }\n      controller.saveClickedItemIndex(idx)\n      const qs = DetailPage.buildQuery({\n        digest: rec.digest,\n        schema: rec.schema_name,\n        beginTime: controller.data!.timeRange[0],\n        endTime: controller.data!.timeRange[1]\n      })\n      openLink(`/statement/detail?${qs}`, ev, navigate)\n    }\n  )\n\n  const getKey = useCallback((row) => `${row.digest}_${row.schema_name}`, [])\n\n  return (\n    <CardTable\n      {...restPrpos}\n      loading={controller.isLoading}\n      columns={controller.availableColumnsInTable}\n      items={controller.data?.list ?? []}\n      orderBy={controller.orderOptions.orderBy}\n      desc={controller.orderOptions.desc}\n      onChangeOrder={controller.changeOrder}\n      errors={controller.errors}\n      visibleColumnKeys={controller.queryOptions.visibleColumnKeys}\n      onRowClicked={handleRowClick}\n      getKey={getKey}\n      clickedRowIndex={controller.getClickedItemIndex()}\n      onRenderRow={renderRow}\n    />\n  )\n}\n\nconst renderRow: IDetailsListProps['onRenderRow'] = (props) => {\n  if (!props) {\n    return null\n  }\n\n  const customStyles: Partial<IDetailsRowStyles> = {}\n  // the evicted record's digest is empty string\n  if (!props.item.digest) {\n    customStyles.root = {\n      backgroundColor: theme.palette.neutralLighter,\n      cursor: 'not-allowed'\n    }\n  }\n\n  return <DetailsRow {...props} styles={customStyles} />\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/components/index.ts",
    "content": "import StatementsTable from './StatementsTable'\n\nexport { StatementsTable }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport {\n  StatementEditableConfig,\n  StatementGetStatementsRequest,\n  StatementModel,\n  StatementBinding\n} from '@lib/client'\n\nimport { IContextConfig, ReqConfig } from '@lib/types'\nimport { ISlowQueryDataSource } from '@lib/apps/SlowQuery'\n\nexport type StatementTimeRange = {\n  begin_time: number\n  end_time: number\n}\n\nexport interface IStatementDataSource extends ISlowQueryDataSource {\n  statementsAvailableFieldsGet(options?: ReqConfig): AxiosPromise<Array<string>>\n\n  statementsConfigGet(\n    options?: ReqConfig\n  ): AxiosPromise<StatementEditableConfig>\n\n  statementsConfigPost(\n    request: StatementEditableConfig,\n    options?: ReqConfig\n  ): AxiosPromise<string>\n\n  statementsDownloadGet(token: string, options?: ReqConfig): AxiosPromise<void>\n\n  statementsDownloadTokenPost(\n    request: StatementGetStatementsRequest,\n    options?: ReqConfig\n  ): AxiosPromise<string>\n\n  statementsListGet(\n    beginTime?: number,\n    endTime?: number,\n    fields?: string,\n    schemas?: Array<string>,\n    resourceGroups?: Array<string>,\n    stmtTypes?: Array<string>,\n    text?: string,\n    options?: ReqConfig\n  ): AxiosPromise<Array<StatementModel>>\n\n  statementsPlanDetailGet(\n    beginTime?: number,\n    digest?: string,\n    endTime?: number,\n    plans?: Array<string>,\n    schemaName?: string,\n    options?: ReqConfig\n  ): AxiosPromise<StatementModel>\n\n  statementsPlansGet(\n    beginTime?: number,\n    digest?: string,\n    endTime?: number,\n    schemaName?: string,\n    options?: ReqConfig\n  ): AxiosPromise<Array<StatementModel>>\n\n  statementsStmtTypesGet(options?: ReqConfig): AxiosPromise<Array<string>>\n\n  statementsTimeRangesGet(\n    options?: ReqConfig\n  ): AxiosPromise<Array<StatementTimeRange>>\n\n  statementsPlanBindStatusGet?(\n    sqlDigest: string,\n    beginTime: number,\n    endTime: number,\n    options?: ReqConfig\n  ): AxiosPromise<StatementBinding>\n\n  statementsPlanBindCreate?(\n    planDigest: string,\n    options?: ReqConfig\n  ): AxiosPromise<string>\n\n  statementsPlanBindDelete?(\n    sqlDigest: string,\n    options?: ReqConfig\n  ): AxiosPromise<string>\n}\n\nexport interface IStatementConfig extends IContextConfig {\n  enableExport?: boolean\n  showConfig?: boolean // default is true\n  showDBFilter?: boolean // default is true\n  showResourceGroupFilter?: boolean // default is true\n  showHelp?: boolean // default is true\n  persistQueryOptions?: boolean // default is true\n\n  // control whether show statement actual time range\n  // for example:\n  // Due to time window and expiration configurations, currently displaying data in time range: Today at 1:00 PM (UTC+08:00) ~ Today at 3:30 PM (UTC+08:00)\n  // for serverless, the statement window is 1 minutes, instead of 30 mins in OP\n  // so for serverless, this message is unnecessary\n  showActualTimeRange?: boolean\n\n  enablePlanBinding?: boolean\n\n  // true means start to search instantly after changing any filter options\n  // false means only to start searching after clicking the \"Query\" button\n  instantQuery?: boolean\n\n  // to limit the time range picker range\n  timeRangeSelector?: {\n    recentSeconds: number[]\n    customAbsoluteRangePicker: boolean\n    timeRangeLimit?: number\n  }\n\n  // show RU V2 fields (clinic-only: avg_ru_v2 / sum_ru_v2 columns and the\n  // matching Basic-tab rows)\n  showRuV2?: boolean\n}\n\nexport interface IStatementContext {\n  ds: IStatementDataSource\n  cfg: IStatementConfig\n}\n\nexport const StatementContext = createContext<IStatementContext | null>(null)\n\nexport const StatementProvider = StatementContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { HashRouter as Router, Routes, Route } from 'react-router-dom'\n\nimport { Root } from '@lib/components'\nimport useCache, { CacheContext } from '@lib/utils/useCache'\nimport { addTranslations } from '@lib/utils/i18n'\n\nimport { Detail, List } from './pages'\nimport { StatementContext } from './context'\n\nimport translations from './translations'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/statement\" element={<List />} />\n      <Route path=\"/statement/detail\" element={<Detail />} />\n    </Routes>\n  )\n}\n\nexport default function () {\n  const statementCacheMgr = useCache(2)\n\n  const ctx = useContext(StatementContext)\n  if (ctx === null) {\n    throw new Error('StatementContext must not be null')\n  }\n\n  return (\n    <Root>\n      <CacheContext.Provider value={statementCacheMgr}>\n        <Router>\n          <AppRoutes />\n        </Router>\n      </CacheContext.Provider>\n    </Root>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/Detail/PlanBind.module.less",
    "content": ".GreenDot {\n  background-color: #0bc40b;\n  height: 8px;\n  width: 8px;\n  border-radius: 50%;\n  display: inline-block;\n}\n\n.GreyDot {\n  background-color: #c3c3c3;\n  height: 8px;\n  width: 8px;\n  border-radius: 50%;\n  display: inline-block;\n}\n\n.PreBlock {\n  background: #f1f1f1;\n  padding: 10px;\n}\n\n.SmallFont {\n  font-size: 13px;\n  font-weight: normal;\n}\n\n.Center {\n  text-align: center;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/Detail/PlanBind.tsx",
    "content": "import React, { useContext, useState, useMemo, useRef, useEffect } from 'react'\nimport { Space, Button, Modal, Tooltip, Radio, Alert } from 'antd'\nimport { InfoCircleOutlined } from '@ant-design/icons'\n\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { StatementModel } from '@lib/client'\nimport { useTranslation } from 'react-i18next'\nimport { IPageQuery } from '.'\nimport { StatementContext } from '../../context'\nimport { CardTable } from '@lib/components'\nimport styles from './PlanBind.module.less'\nimport { planColumns as genPlanColumns } from '../../utils/tableColumns'\nimport {\n  SelectionMode,\n  CheckboxVisibility\n} from 'office-ui-fabric-react/lib/DetailsList'\nimport { Selection } from 'office-ui-fabric-react/lib/Selection'\n\ninterface PlanBindProps {\n  query: IPageQuery\n  plans: StatementModel[]\n}\n\nconst PlanBind = ({ query, plans }: PlanBindProps) => {\n  const ctx = useContext(StatementContext)\n  const { t } = useTranslation()\n  const { data: planBindingStatus } = useClientRequest((reqConfig) =>\n    ctx!.ds.statementsPlanBindStatusGet!(\n      query.digest!,\n      query.beginTime!,\n      query.endTime!,\n      reqConfig\n    )\n  )\n\n  const [boundPlanDigest, setBoundPlanDigest] = useState<string | null>(null)\n  const [showPlanBindModal, setShowPlanBindModal] = useState(false)\n\n  useEffect(() => {\n    if (planBindingStatus) {\n      setBoundPlanDigest(planBindingStatus.plan_digest!)\n    }\n  }, [planBindingStatus])\n\n  const hasPlanToBind = plans[0].plan_can_be_bound\n\n  return (\n    <Space align=\"center\">\n      {!hasPlanToBind ? (\n        <Tooltip\n          title={t('statement.pages.detail.plan_bind.bound_available_tooltip')}\n          placement=\"leftTop\"\n        >\n          <InfoCircleOutlined /> Unavailable\n        </Tooltip>\n      ) : (\n        <>\n          {boundPlanDigest ? (\n            <Space>\n              <span className={styles.GreenDot} />\n              {t('statement.pages.detail.plan_bind.bound')}\n            </Space>\n          ) : (\n            <Space>\n              <span className={styles.GreyDot} />\n              {t('statement.pages.detail.plan_bind.not_bound')}\n            </Space>\n          )}\n        </>\n      )}\n\n      <Button\n        onClick={() => setShowPlanBindModal(true)}\n        disabled={!hasPlanToBind}\n      >\n        {t('statement.pages.detail.plan_bind.title')}\n      </Button>\n      <PlanBindModal\n        showPlanBindModal={showPlanBindModal}\n        boundPlanDigest={boundPlanDigest}\n        plans={plans}\n        sqlDigest={query.digest!}\n        onHandleModalVisibility={setShowPlanBindModal}\n        onHandleSetBoundPlanDigets={setBoundPlanDigest}\n      />\n    </Space>\n  )\n}\n\ninterface PlanBindModalProps {\n  showPlanBindModal: boolean\n  boundPlanDigest: string | null\n  plans: StatementModel[]\n  sqlDigest: string\n  onHandleModalVisibility: (visibility: boolean) => void\n  onHandleSetBoundPlanDigets: (planDigest: string | null) => void\n}\n\nconst PlanBindModal = ({\n  showPlanBindModal,\n  boundPlanDigest,\n  plans,\n  sqlDigest,\n  onHandleModalVisibility,\n  onHandleSetBoundPlanDigets\n}: PlanBindModalProps) => {\n  const ctx = useContext(StatementContext)\n  const { t } = useTranslation()\n\n  const [selectedPlan, setSelectedPlan] = useState<string | null>(null)\n  const [isLoading, setIsLoading] = useState(false)\n\n  const handlePlanBind = async () => {\n    setIsLoading(true)\n    try {\n      await ctx!.ds.statementsPlanBindCreate!(selectedPlan!)\n      onHandleSetBoundPlanDigets(selectedPlan!)\n    } catch (error) {\n      console.log(error)\n    } finally {\n      setIsLoading(false)\n    }\n  }\n\n  const handleDropPlan = async () => {\n    setIsLoading(true)\n    try {\n      await ctx!.ds.statementsPlanBindDelete!(sqlDigest)\n      setSelectedPlan(null)\n      onHandleSetBoundPlanDigets(null)\n    } catch (error) {\n      console.log(error)\n    } finally {\n      setIsLoading(false)\n    }\n  }\n\n  const handleSelectedPlanChange = (plan: StatementModel[] | []) => {\n    if (plan.length === 0) return setSelectedPlan(null)\n    return setSelectedPlan(plan[0].plan_digest!)\n  }\n\n  return (\n    <Modal\n      visible={showPlanBindModal}\n      title={\n        <Space direction=\"vertical\">\n          <Space size={10}>\n            {t('statement.pages.detail.plan_bind.title')}{' '}\n            {boundPlanDigest ? (\n              <Space className={`${styles.SmallFont}`}>\n                <span className={styles.GreenDot} />\n                {t('statement.pages.detail.plan_bind.bound')}\n              </Space>\n            ) : (\n              <Space className={`${styles.SmallFont}`}>\n                <span className={styles.GreyDot} />\n                {t('statement.pages.detail.plan_bind.not_bound')}\n              </Space>\n            )}\n          </Space>\n          <Alert\n            type=\"warning\"\n            message={t('statement.pages.detail.plan_bind.notice')}\n            className={`${styles.SmallFont}`}\n          />\n        </Space>\n      }\n      onCancel={() => onHandleModalVisibility(false)}\n      width={1000}\n      footer={\n        <div className={styles.Center}>\n          {boundPlanDigest ? (\n            <Space direction=\"vertical\">\n              {t('statement.pages.detail.plan_bind.bound_status_desc')}\n              <Button\n                onClick={handleDropPlan}\n                loading={isLoading}\n                disabled={isLoading}\n              >\n                {t('statement.pages.detail.plan_bind.drop_btn_txt')}\n              </Button>\n            </Space>\n          ) : (\n            <Button\n              onClick={handlePlanBind}\n              loading={isLoading}\n              disabled={isLoading || !selectedPlan}\n            >\n              {t('statement.pages.detail.plan_bind.bind_btn_txt')}\n            </Button>\n          )}\n        </div>\n      }\n      destroyOnClose\n    >\n      <p>{t('statement.pages.detail.plan_bind.bound_sql')}</p>\n      <pre className={`${styles.PreBlock} ${styles.SmallFont}`}>\n        {sqlDigest}\n      </pre>\n      <p>{t('statement.pages.detail.plan_bind.to_plan')}</p>\n      {!isLoading && (\n        <PlanTable\n          plans={plans}\n          boundPlanDigest={boundPlanDigest}\n          onHandleSelectedPlanChange={handleSelectedPlanChange}\n        />\n      )}\n    </Modal>\n  )\n}\n\ninterface PlanTableProps {\n  boundPlanDigest: string | null\n  plans: StatementModel[]\n  onHandleSelectedPlanChange: (plan: StatementModel[] | []) => void\n}\n\nconst PlanTable = ({\n  boundPlanDigest,\n  plans,\n  onHandleSelectedPlanChange\n}: PlanTableProps) => {\n  const planColumns = useMemo(() => genPlanColumns(plans || []), [plans])\n\n  const selection = useRef(\n    new Selection({\n      canSelectItem: (item) => {\n        const digest = (item as StatementModel).plan_digest\n        return !boundPlanDigest || digest === boundPlanDigest\n      },\n      onSelectionChanged: () => {\n        const s = selection.current.getSelection() as StatementModel[]\n        onHandleSelectedPlanChange(s)\n        if (!boundPlanDigest) return\n\n        // if bound plan is selected, keep it selected\n        const selectedPlanIndex = plans.findIndex(\n          (v) => v.plan_digest === boundPlanDigest\n        )\n\n        if (s.length === 0) {\n          selection.current.setIndexSelected(selectedPlanIndex, true, true)\n        }\n      }\n    })\n  )\n\n  useEffect(() => {\n    if (boundPlanDigest && plans.length > 0) {\n      const selectedPlanIndex = plans.findIndex(\n        (v) => v.plan_digest === boundPlanDigest\n      )\n      selection.current.setIndexSelected(selectedPlanIndex, true, true)\n    } else if (!boundPlanDigest) {\n      selection.current.setAllSelected(false)\n    }\n  }, [boundPlanDigest])\n\n  return (\n    <CardTable\n      cardNoMarginTop\n      cardNoMarginBottom\n      columns={planColumns}\n      items={plans}\n      selectionMode={SelectionMode.single}\n      checkboxVisibility={CheckboxVisibility.always}\n      selection={selection.current}\n      selectionPreservedOnEmptyClick\n      onRenderCheckbox={(props) => (\n        <Radio checked={props?.checked} disabled={!!boundPlanDigest} />\n      )}\n    />\n  )\n}\n\nexport default PlanBind\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/Detail/PlanDetail.tsx",
    "content": "import React, { useState, useContext, useMemo } from 'react'\nimport { Space, Tabs, Modal } from 'antd'\nimport { useTranslation } from 'react-i18next'\nimport {\n  AnimatedSkeleton,\n  BinaryPlanTable,\n  PlanText,\n  Card,\n  CopyLink,\n  Descriptions,\n  ErrorBar,\n  Expand,\n  HighlightSQL,\n  TextWithInfo\n} from '@lib/components'\nimport {\n  VisualPlanThumbnailView,\n  VisualPlanView\n} from '@lib/components/VisualPlan'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport formatSql from '@lib/utils/sqlFormatter'\nimport { useVersionedLocalStorageState } from '@lib/utils/useVersionedLocalStorageState'\n\nimport type { IPageQuery } from '.'\nimport DetailTabs from './PlanDetailTabs'\nimport { useSchemaColumns } from '../../utils/useSchemaColumns'\nimport { telemetry } from '../../utils/telemetry'\nimport { StatementContext } from '../../context'\n\nexport interface IQuery extends IPageQuery {\n  plans: string[]\n  allPlans: number\n}\n\nexport interface IPlanDetailProps {\n  query: IQuery\n}\n\nconst STMT_DETAIL_PLAN_EXPAND = 'statement.detail_plan_expand'\n\nfunction PlanDetail({ query }: IPlanDetailProps) {\n  const ctx = useContext(StatementContext)\n\n  const { t } = useTranslation()\n  const {\n    data,\n    isLoading: isDataLoading,\n    error\n  } = useClientRequest((reqConfig) =>\n    ctx!.ds.statementsPlanDetailGet(\n      query.beginTime!,\n      query.digest!,\n      query.endTime!,\n      query.plans,\n      query.schema!,\n      reqConfig\n    )\n  )\n  const { isLoading: isSchemaLoading } = useSchemaColumns(\n    ctx!.ds.statementsAvailableFieldsGet\n  )\n  const isLoading = isDataLoading || isSchemaLoading\n\n  const binaryPlanObj = useMemo(() => {\n    const json = data?.binary_plan_json ?? data?.binary_plan\n    if (json) {\n      return JSON.parse(json)\n    }\n    return undefined\n  }, [data?.binary_plan, data?.binary_plan_json])\n\n  const [isVpVisible, setIsVpVisable] = useState(false)\n  const toggleVisualPlan = (action: 'open' | 'close') => {\n    telemetry.toggleVisualPlanModal(action)\n    setIsVpVisable(!isVpVisible)\n  }\n\n  const [detailExpand, setDetailExpand] = useVersionedLocalStorageState(\n    STMT_DETAIL_PLAN_EXPAND,\n    {\n      defaultValue: {\n        prev_query: false,\n        query: false,\n        plan: false\n      }\n    }\n  )\n\n  const togglePrevQuery = () =>\n    setDetailExpand((prev) => ({ ...prev, prev_query: !prev.prev_query }))\n  const toggleQuery = () =>\n    setDetailExpand((prev) => ({ ...prev, query: !prev.query }))\n\n  let titleKey\n  if (query.allPlans === 1) {\n    titleKey = 'one_for_all'\n  } else if (query.plans.length === query.allPlans) {\n    titleKey = 'all'\n  } else {\n    titleKey = 'some'\n  }\n\n  return (\n    <Card\n      title={t(`statement.pages.detail.desc.plans.title.${titleKey}`, {\n        n: query.plans.length\n      })}\n    >\n      <AnimatedSkeleton showSkeleton={isLoading}>\n        {error && <ErrorBar errors={[error]} />}\n        {data && (\n          <>\n            <Descriptions>\n              <Descriptions.Item\n                span={2}\n                multiline={detailExpand.query}\n                label={\n                  <Space size=\"middle\">\n                    <TextWithInfo.TransKey transKey=\"statement.fields.query_sample_text\" />\n                    <Expand.Link\n                      expanded={detailExpand.query}\n                      onClick={toggleQuery}\n                    />\n                    {\n                      // avoid page hang when sql is too long\n                      data.query_sample_text!.length < 10000 && (\n                        <CopyLink\n                          displayVariant=\"formatted_sql\"\n                          data={formatSql(data.query_sample_text!)}\n                        />\n                      )\n                    }\n                    <CopyLink\n                      displayVariant=\"original_sql\"\n                      data={data.query_sample_text}\n                    />\n                  </Space>\n                }\n              >\n                <Expand\n                  expanded={detailExpand.query}\n                  collapsedContent={\n                    <HighlightSQL sql={data.query_sample_text!} compact />\n                  }\n                >\n                  <HighlightSQL sql={data.query_sample_text!} />\n                </Expand>\n              </Descriptions.Item>\n              {data.prev_sample_text ? (\n                <Descriptions.Item\n                  span={2}\n                  multiline={detailExpand.prev_query}\n                  label={\n                    <Space size=\"middle\">\n                      <TextWithInfo.TransKey transKey=\"statement.fields.prev_sample_text\" />\n                      <Expand.Link\n                        expanded={detailExpand.prev_query}\n                        onClick={togglePrevQuery}\n                      />\n                      {\n                        // avoid page hang when sql is too long\n                        data.prev_sample_text!.length < 10000 && (\n                          <CopyLink\n                            displayVariant=\"formatted_sql\"\n                            data={formatSql(data.prev_sample_text!)}\n                          />\n                        )\n                      }\n                      <CopyLink\n                        displayVariant=\"original_sql\"\n                        data={data.prev_sample_text}\n                      />\n                    </Space>\n                  }\n                >\n                  <Expand\n                    expanded={detailExpand.prev_query}\n                    collapsedContent={\n                      <HighlightSQL sql={data.prev_sample_text!} compact />\n                    }\n                  >\n                    <HighlightSQL sql={data.prev_sample_text!} />\n                  </Expand>\n                </Descriptions.Item>\n              ) : null}\n            </Descriptions>\n\n            {(!!data.binary_plan_text || !!data.plan) && (\n              <>\n                <Space size=\"middle\" style={{ color: '#8c8c8c' }}>\n                  {t('statement.pages.detail.desc.plans.execution.title')}\n                </Space>\n\n                <Tabs\n                  defaultActiveKey={\n                    !!data.binary_plan_text ? 'binary_plan_table' : 'text_plan'\n                  }\n                  onTabClick={(key) =>\n                    telemetry.clickPlanTabs(key, data.digest!)\n                  }\n                >\n                  {!!data.binary_plan_text && (\n                    <Tabs.TabPane\n                      tab={t(\n                        'statement.pages.detail.desc.plans.execution.table'\n                      )}\n                      key=\"binary_plan_table\"\n                    >\n                      <BinaryPlanTable\n                        data={data.binary_plan_text}\n                        downloadFileName={`${data.digest}.txt`}\n                      />\n                      <div style={{ height: 24 }} />\n                    </Tabs.TabPane>\n                  )}\n\n                  <Tabs.TabPane\n                    tab={t('statement.pages.detail.desc.plans.execution.text')}\n                    key=\"text_plan\"\n                  >\n                    <PlanText\n                      data={data.binary_plan_text || data.plan || ''}\n                      downloadFileName={`${data.digest}.txt`}\n                    />\n                  </Tabs.TabPane>\n\n                  {binaryPlanObj && !binaryPlanObj.main.discardedDueToTooLong && (\n                    <Tabs.TabPane\n                      tab={t(\n                        'statement.pages.detail.desc.plans.execution.visual'\n                      )}\n                      key=\"binary_plan\"\n                    >\n                      <Modal\n                        title={t('slow_query.detail.plan.modal_title')}\n                        centered\n                        visible={isVpVisible}\n                        width={window.innerWidth}\n                        onCancel={() => toggleVisualPlan('close')}\n                        footer={null}\n                        destroyOnClose={true}\n                        bodyStyle={{\n                          background: '#f5f5f5',\n                          height: window.innerHeight - 100\n                        }}\n                      >\n                        <VisualPlanView data={binaryPlanObj} />\n                      </Modal>\n                      <Descriptions>\n                        <Descriptions.Item span={2}>\n                          <div onClick={() => toggleVisualPlan('open')}>\n                            <VisualPlanThumbnailView data={binaryPlanObj} />\n                          </div>\n                        </Descriptions.Item>\n                      </Descriptions>\n                    </Tabs.TabPane>\n                  )}\n                </Tabs>\n              </>\n            )}\n\n            <DetailTabs data={data} query={query} />\n          </>\n        )}\n      </AnimatedSkeleton>\n    </Card>\n  )\n}\n\nexport default PlanDetail\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/Detail/PlanDetailTabBasic.tsx",
    "content": "import { Tooltip } from 'antd'\nimport React from 'react'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\n\nimport { StatementModel } from '@lib/client'\nimport { DateTime, Pre, ValueWithTooltip, TextWrap } from '@lib/components'\n\nexport const tabBasicItems = (\n  data: StatementModel,\n  options?: { showRuV2?: boolean }\n) => [\n  {\n    key: 'table_names',\n    value: (\n      <Tooltip title={data.table_names}>\n        <TextWrap>\n          <Pre>{data.table_names}</Pre>\n        </TextWrap>\n      </Tooltip>\n    )\n  },\n  { key: 'index_names', value: data.index_names },\n  {\n    key: 'first_seen',\n    value: data.first_seen && (\n      <DateTime.Calendar unixTimestampMs={data.first_seen * 1000} />\n    )\n  },\n  {\n    key: 'last_seen',\n    value: data.last_seen && (\n      <DateTime.Calendar unixTimestampMs={data.last_seen * 1000} />\n    )\n  },\n  {\n    key: 'exec_count',\n    value: <ValueWithTooltip.Short value={data.exec_count} />\n  },\n  {\n    key: 'plan_cache_hits',\n    value: <ValueWithTooltip.Short value={data.plan_cache_hits} />\n  },\n  {\n    key: 'sum_latency',\n    value: getValueFormat('ns')(data.sum_latency || 0, 1)\n  },\n  { key: 'sample_user', value: data.sample_user },\n  {\n    key: 'sum_errors',\n    value: <ValueWithTooltip.Short value={data.sum_errors} />\n  },\n  {\n    key: 'sum_warnings',\n    value: <ValueWithTooltip.Short value={data.sum_warnings} />\n  },\n  {\n    key: 'avg_mem',\n    value: getValueFormat('bytes')(data.avg_mem || 0, 1)\n  },\n  {\n    key: 'max_mem',\n    value: getValueFormat('bytes')(data.max_mem || 0, 1)\n  },\n  {\n    key: 'avg_mem_arbitration',\n    value: getValueFormat('s')(data.avg_mem_arbitration || 0, 1)\n  },\n  {\n    key: 'max_mem_arbitration',\n    value: getValueFormat('s')(data.max_mem_arbitration || 0, 1)\n  },\n  {\n    key: 'avg_disk',\n    value: getValueFormat('bytes')(data.avg_disk || 0, 1)\n  },\n  {\n    key: 'max_disk',\n    value: getValueFormat('bytes')(data.max_disk || 0, 1)\n  },\n  {\n    key: 'avg_ru',\n    value: getValueFormat('short')(data.avg_ru || 0, 1)\n  },\n  {\n    key: 'max_ru',\n    value: getValueFormat('short')(data.max_ru || 0, 1)\n  },\n  ...(options?.showRuV2\n    ? [\n        {\n          key: 'avg_ru_v2',\n          value: getValueFormat('short')(data.avg_ru_v2 || 0, 1)\n        },\n        {\n          key: 'sum_ru_v2',\n          value: getValueFormat('short')(data.sum_ru_v2 || 0, 1)\n        },\n        {\n          key: 'max_ru_v2',\n          value: getValueFormat('short')(data.max_ru_v2 || 0, 1)\n        },\n        {\n          key: 'sum_ru',\n          value: getValueFormat('short')(data.sum_ru || 0, 1)\n        }\n      ]\n    : []),\n  {\n    key: 'resource_group',\n    value: data.resource_group\n  }\n]\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/Detail/PlanDetailTabCopr.tsx",
    "content": "import React from 'react'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\n\nimport { StatementModel } from '@lib/client'\nimport { ValueWithTooltip } from '@lib/components'\n\nexport const tabCoprItems = (data: StatementModel) => [\n  { key: 'sum_cop_task_num', value: data.sum_cop_task_num },\n  {\n    key: 'avg_processed_keys',\n    value: <ValueWithTooltip.Short value={data.avg_processed_keys} />\n  },\n  {\n    key: 'max_processed_keys',\n    value: <ValueWithTooltip.Short value={data.max_processed_keys} />\n  },\n  {\n    key: 'avg_total_keys',\n    value: <ValueWithTooltip.Short value={data.avg_total_keys} />\n  },\n  {\n    key: 'max_total_keys',\n    value: <ValueWithTooltip.Short value={data.max_total_keys} />\n  },\n  {\n    key: 'avg_rocksdb_block_cache_hit_count',\n    value: (\n      <ValueWithTooltip.Short value={data.avg_rocksdb_block_cache_hit_count} />\n    )\n  },\n  {\n    key: 'max_rocksdb_block_cache_hit_count',\n    value: (\n      <ValueWithTooltip.Short value={data.max_rocksdb_block_cache_hit_count} />\n    )\n  },\n  {\n    key: 'avg_rocksdb_block_read_byte',\n    value: (\n      <ValueWithTooltip.ScaledBytes value={data.avg_rocksdb_block_read_byte} />\n    )\n  },\n  {\n    key: 'max_rocksdb_block_read_byte',\n    value: (\n      <ValueWithTooltip.ScaledBytes value={data.max_rocksdb_block_read_byte} />\n    )\n  },\n  {\n    key: 'avg_rocksdb_block_read_count',\n    value: <ValueWithTooltip.Short value={data.avg_rocksdb_block_read_count} />\n  },\n  {\n    key: 'max_rocksdb_block_read_count',\n    value: <ValueWithTooltip.Short value={data.max_rocksdb_block_read_count} />\n  },\n  {\n    key: 'avg_rocksdb_delete_skipped_count',\n    value: (\n      <ValueWithTooltip.Short value={data.avg_rocksdb_delete_skipped_count} />\n    )\n  },\n  {\n    key: 'max_rocksdb_delete_skipped_count',\n    value: (\n      <ValueWithTooltip.Short value={data.max_rocksdb_delete_skipped_count} />\n    )\n  },\n  {\n    key: 'avg_rocksdb_key_skipped_count',\n    value: <ValueWithTooltip.Short value={data.avg_rocksdb_key_skipped_count} />\n  },\n  {\n    key: 'max_rocksdb_key_skipped_count',\n    value: <ValueWithTooltip.Short value={data.max_rocksdb_key_skipped_count} />\n  },\n  {\n    key: 'sum_unpacked_bytes_sent_tikv_total',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.sum_unpacked_bytes_sent_tikv_total}\n      />\n    )\n  },\n  {\n    key: 'sum_unpacked_bytes_received_tikv_total',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.sum_unpacked_bytes_received_tikv_total}\n      />\n    )\n  },\n  {\n    key: 'sum_unpacked_bytes_sent_tikv_cross_zone',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.sum_unpacked_bytes_sent_tikv_cross_zone}\n      />\n    )\n  },\n  {\n    key: 'sum_unpacked_bytes_received_tikv_cross_zone',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.sum_unpacked_bytes_received_tikv_cross_zone}\n      />\n    )\n  },\n  {\n    key: 'sum_unpacked_bytes_sent_tiflash_total',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.sum_unpacked_bytes_sent_tiflash_total}\n      />\n    )\n  },\n  {\n    key: 'sum_unpacked_bytes_received_tiflash_total',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.sum_unpacked_bytes_received_tiflash_total}\n      />\n    )\n  },\n  {\n    key: 'sum_unpacked_bytes_sent_tiflash_cross_zone',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.sum_unpacked_bytes_sent_tiflash_cross_zone}\n      />\n    )\n  },\n  {\n    key: 'sum_unpacked_bytes_received_tiflash_cross_zone',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.sum_unpacked_bytes_received_tiflash_cross_zone}\n      />\n    )\n  },\n  {\n    key: 'avg_ia_remote_read_segment_size',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.avg_ia_remote_read_segment_size}\n      />\n    )\n  },\n  {\n    key: 'max_ia_remote_read_segment_size',\n    value: (\n      <ValueWithTooltip.ScaledBytes\n        value={data.max_ia_remote_read_segment_size}\n      />\n    )\n  }\n]\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/Detail/PlanDetailTabTime.tsx",
    "content": "import React from 'react'\nimport { StatementModel } from '@lib/client'\nimport { Typography } from 'antd'\nimport { TFunction } from 'react-i18next'\n\nexport const tabTimeItems = (data: StatementModel, t: TFunction) => [\n  {\n    key: 'parse_latency',\n    avg: data.avg_parse_latency,\n    max: data.max_parse_latency\n  },\n  {\n    key: 'compile_latency',\n    avg: data.avg_compile_latency,\n    max: data.max_compile_latency\n  },\n  { key: 'wait_time', avg: data.avg_wait_time, max: data.max_wait_time },\n  {\n    key: 'process_time',\n    avg: data.avg_process_time,\n    max: data.max_process_time\n  },\n  {\n    key: 'backoff_time',\n    avg: data.avg_backoff_time,\n    max: data.max_backoff_time\n  },\n  {\n    key: 'get_commit_ts_time',\n    avg: data.avg_get_commit_ts_time,\n    max: data.max_get_commit_ts_time\n  },\n  {\n    key: 'local_latch_wait_time',\n    avg: data.avg_local_latch_wait_time,\n    max: data.max_local_latch_wait_time\n  },\n  {\n    key: 'resolve_lock_time',\n    avg: data.avg_resolve_lock_time,\n    max: data.max_resolve_lock_time\n  },\n  {\n    key: 'prewrite_time',\n    avg: data.avg_prewrite_time,\n    max: data.max_prewrite_time\n  },\n  {\n    key: 'commit_time',\n    avg: data.avg_commit_time,\n    max: data.max_commit_time\n  },\n  {\n    key: 'commit_backoff_time',\n    avg: data.avg_commit_backoff_time,\n    max: data.max_commit_backoff_time\n  },\n  {\n    key: 'rc_wait_time',\n    avg: data.avg_time_queued_by_rc,\n    max: data.max_time_queued_by_rc\n  },\n  {\n    key: 'query_time2',\n    keyDisplay: (\n      <Typography.Text strong>\n        {t('statement.fields.query_time2')}\n      </Typography.Text>\n    ),\n    avg: data.avg_latency,\n    min: data.min_latency,\n    max: data.max_latency\n  },\n  {\n    key: 'ia_remote_read_segment_wait_time',\n    avg: data.avg_ia_remote_read_segment_wait_time,\n    max: data.max_ia_remote_read_segment_wait_time\n  }\n]\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/Detail/PlanDetailTabTxn.tsx",
    "content": "import React from 'react'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\n\nimport { StatementModel } from '@lib/client'\nimport { ValueWithTooltip } from '@lib/components'\n\nexport const tabTxnItems = (data: StatementModel) => [\n  {\n    key: 'avg_affected_rows',\n    value: <ValueWithTooltip.Short value={data.avg_affected_rows} />\n  },\n  {\n    key: 'sum_backoff_times',\n    value: <ValueWithTooltip.Short value={data.sum_backoff_times} />\n  },\n  {\n    key: 'avg_write_keys',\n    value: <ValueWithTooltip.Short value={data.avg_write_keys} />\n  },\n  {\n    key: 'max_write_keys',\n    value: <ValueWithTooltip.Short value={data.max_write_keys} />\n  },\n  {\n    key: 'avg_write_size',\n    value: getValueFormat('bytes')(data.avg_write_size || 0, 1)\n  },\n  {\n    key: 'max_write_size',\n    value: getValueFormat('bytes')(data.max_write_size || 0, 1)\n  },\n  {\n    key: 'avg_prewrite_regions',\n    value: <ValueWithTooltip.Short value={data.avg_prewrite_regions} />\n  },\n  {\n    key: 'max_prewrite_regions',\n    value: <ValueWithTooltip.Short value={data.max_prewrite_regions} />\n  },\n  {\n    key: 'avg_txn_retry',\n    value: <ValueWithTooltip.Short value={data.avg_txn_retry} />\n  },\n  {\n    key: 'max_txn_retry',\n    value: <ValueWithTooltip.Short value={data.max_txn_retry} />\n  }\n]\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/Detail/PlanDetailTabs.tsx",
    "content": "import React, { useContext } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { CardTable, CardTabs } from '@lib/components'\nimport { StatementModel } from '@lib/client'\nimport { valueColumns, timeValueColumns } from '@lib/utils/tableColumns'\n\nimport { tabBasicItems } from './PlanDetailTabBasic'\nimport { tabTimeItems } from './PlanDetailTabTime'\nimport { tabCoprItems } from './PlanDetailTabCopr'\nimport { tabTxnItems } from './PlanDetailTabTxn'\nimport SlowQueryTab from './SlowQueryTab'\nimport { useSchemaColumns } from '../../utils/useSchemaColumns'\nimport type { IQuery } from './PlanDetail'\nimport { StatementContext } from '../../context'\nimport { telemetry as stmtTelemetry } from '../../utils/telemetry'\n\nexport default function DetailTabs({\n  data,\n  query\n}: {\n  data: StatementModel\n  query: IQuery\n}) {\n  const ctx = useContext(StatementContext)\n\n  const { t } = useTranslation()\n  const { schemaColumns } = useSchemaColumns(\n    ctx!.ds.statementsAvailableFieldsGet\n  )\n  const columnsSet = new Set(schemaColumns)\n\n  const tabs = [\n    {\n      key: 'basic',\n      title: t('statement.pages.detail.tabs.basic'),\n      content: () => {\n        const items = tabBasicItems(data, { showRuV2: !!ctx?.cfg.showRuV2 })\n        const columns = valueColumns('statement.fields.')\n        return (\n          <CardTable\n            cardNoMargin\n            columns={columns}\n            items={items}\n            extendLastColumn\n            data-e2e=\"statement_pages_detail_tabs_basic\"\n          />\n        )\n      }\n    },\n    {\n      key: 'time',\n      title: t('statement.pages.detail.tabs.time'),\n      content: () => {\n        const items = tabTimeItems(data, t)\n        const columns = timeValueColumns('statement.fields.', items)\n        return (\n          <CardTable\n            cardNoMargin\n            columns={columns}\n            items={items}\n            extendLastColumn\n            data-e2e=\"statement_pages_detail_tabs_time\"\n          />\n        )\n      }\n    },\n    {\n      key: 'copr',\n      title: t('statement.pages.detail.tabs.copr'),\n      content: () => {\n        const items = tabCoprItems(data).filter((item) =>\n          columnsSet.has(item.key)\n        )\n        const columns = valueColumns('statement.fields.')\n        return (\n          <CardTable\n            cardNoMargin\n            columns={columns}\n            items={items}\n            extendLastColumn\n            data-e2e=\"statement_pages_detail_tabs_copr\"\n          />\n        )\n      }\n    },\n    {\n      key: 'txn',\n      title: t('statement.pages.detail.tabs.txn'),\n      content: () => {\n        const items = tabTxnItems(data)\n        const columns = valueColumns('statement.fields.')\n        return (\n          <CardTable\n            cardNoMargin\n            columns={columns}\n            items={items}\n            extendLastColumn\n            data-e2e=\"statement_pages_detail_tabs_txn\"\n          />\n        )\n      }\n    },\n    {\n      key: 'slow_query',\n      title: t('statement.pages.detail.tabs.slow_query'),\n      content: () => <SlowQueryTab query={query} />\n    }\n  ]\n  return (\n    <CardTabs\n      animated={false}\n      tabs={tabs}\n      onChange={(tab) => {\n        stmtTelemetry.switchDetailTab(tab)\n      }}\n    />\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/Detail/SlowQueryTab.tsx",
    "content": "import React, { useContext } from 'react'\nimport { fromTimeRangeValue } from '@lib/components'\nimport useSlowQueryTableController, {\n  DEF_SLOW_QUERY_OPTIONS\n} from '@lib/apps/SlowQuery/utils/useSlowQueryTableController'\nimport SlowQueriesTable from '@lib/apps/SlowQuery/components/SlowQueriesTable'\n\nimport { IQuery } from './PlanDetail'\nimport { StatementContext } from '../../context'\n\nexport interface ISlowQueryTabProps {\n  query: IQuery\n}\n\nexport default function SlowQueryTab({ query }: ISlowQueryTabProps) {\n  const ctx = useContext(StatementContext)\n\n  const controller = useSlowQueryTableController({\n    initialQueryOptions: {\n      ...DEF_SLOW_QUERY_OPTIONS,\n      timeRange: fromTimeRangeValue([query.beginTime!, query.endTime!]),\n      limit: 100,\n      digest: query.digest!,\n      plans: query.plans\n    },\n    persistQueryInSession: false,\n    fetchSchemas: false,\n\n    ds: ctx!.ds\n  })\n\n  return <SlowQueriesTable cardNoMargin controller={controller} />\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/Detail/index.tsx",
    "content": "import { Alert, Space, Typography } from 'antd'\nimport { SelectionMode } from 'office-ui-fabric-react/lib/DetailsList'\nimport { Selection } from 'office-ui-fabric-react/lib/Selection'\nimport React, { useContext, useEffect, useMemo, useRef, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useLocation, useNavigate } from 'react-router-dom'\nimport { ArrowLeftOutlined } from '@ant-design/icons'\nimport { useIsFeatureSupport } from '@lib/utils/store'\n\nimport { StatementModel } from '@lib/client'\nimport {\n  AnimatedSkeleton,\n  CardTable,\n  DateTime,\n  Descriptions,\n  ErrorBar,\n  Expand,\n  Head,\n  HighlightSQL,\n  TextWithInfo\n} from '@lib/components'\nimport CopyLink from '@lib/components/CopyLink'\nimport formatSql from '@lib/utils/sqlFormatter'\nimport { buildQueryFn, parseQueryFn } from '@lib/utils/query'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { useVersionedLocalStorageState } from '@lib/utils/useVersionedLocalStorageState'\n\nimport { planColumns as genPlanColumns } from '../../utils/tableColumns'\nimport PlanDetail from './PlanDetail'\nimport PlanBind from './PlanBind'\nimport { StatementContext } from '../../context'\n\nexport interface IPageQuery {\n  digest?: string\n  schema?: string\n  beginTime?: number\n  endTime?: number\n}\n\nconst STMT_DETAIL_EXPAND = 'statement.detail_expand'\n\n// sort plans by plan_count first,\n// if plan_count is the same, sort plans by ava_latency\nconst compareFn = (a: StatementModel, b: StatementModel) => {\n  if (a.exec_count! === b.exec_count!) {\n    return b.avg_latency! - a.avg_latency!\n  }\n\n  return b.exec_count! - a.exec_count!\n}\n\nfunction DetailPage() {\n  const ctx = useContext(StatementContext)\n\n  const location = useLocation()\n  const navigate = useNavigate()\n\n  const query = DetailPage.parseQuery(location.search)\n  const historyBack = (location.state ?? ({} as any)).historyBack ?? false\n\n  const {\n    data: plans,\n    isLoading,\n    error\n  } = useClientRequest((reqConfig) =>\n    ctx!.ds.statementsPlansGet(\n      query.beginTime!,\n      query.digest!,\n      query.endTime!,\n      query.schema!,\n      reqConfig\n    )\n  )\n\n  const { t } = useTranslation()\n  const planColumns = useMemo(() => genPlanColumns(plans || []), [plans])\n\n  const [selectedPlans, setSelectedPlans] = useState<string[]>([])\n  const selection = useRef(\n    new Selection({\n      onSelectionChanged: () => {\n        const s = selection.current.getSelection() as StatementModel[]\n        setSelectedPlans(s.map((v) => v.plan_digest || ''))\n      }\n    })\n  )\n  const [sqlExpanded, setSqlExpanded] = useVersionedLocalStorageState(\n    STMT_DETAIL_EXPAND,\n    { defaultValue: false }\n  )\n  const toggleSqlExpanded = () => setSqlExpanded((prev) => !prev)\n\n  useEffect(() => {\n    if (plans && plans.length > 0) {\n      selection.current.setAllSelected(true)\n      plans.sort(compareFn)\n    }\n  }, [plans])\n\n  const supportPlanBinding = useIsFeatureSupport('plan_binding')\n\n  return (\n    <div>\n      <Head\n        title={t('statement.pages.detail.head.title')}\n        back={\n          <Typography.Link\n            onClick={() =>\n              historyBack ? navigate(-1) : navigate('/statement')\n            }\n          >\n            <ArrowLeftOutlined /> {t('statement.pages.detail.head.back')}\n          </Typography.Link>\n        }\n        titleExtra={\n          ctx?.cfg.enablePlanBinding &&\n          supportPlanBinding &&\n          plans &&\n          plans.length > 0 ? (\n            <PlanBind query={query} plans={plans!} />\n          ) : null\n        }\n      >\n        <AnimatedSkeleton showSkeleton={isLoading}>\n          {error && <ErrorBar errors={[error]} />}\n          {plans && plans.length > 0 && (\n            <>\n              <Descriptions>\n                <Descriptions.Item\n                  span={2}\n                  multiline={sqlExpanded}\n                  label={\n                    <Space size=\"middle\">\n                      <TextWithInfo.TransKey transKey=\"statement.fields.digest_text\" />\n                      <Expand.Link\n                        expanded={sqlExpanded}\n                        onClick={toggleSqlExpanded}\n                      />\n                      {\n                        // avoid page hang when sql is too long\n                        plans[0].digest_text!.length < 10000 && (\n                          <CopyLink\n                            displayVariant=\"formatted_sql\"\n                            data={formatSql(plans[0].digest_text!)}\n                          />\n                        )\n                      }\n                      <CopyLink\n                        displayVariant=\"original_sql\"\n                        data={plans[0].digest_text!}\n                      />\n                    </Space>\n                  }\n                >\n                  <Expand\n                    expanded={sqlExpanded}\n                    collapsedContent={\n                      <HighlightSQL sql={plans[0].digest_text!} compact />\n                    }\n                  >\n                    <HighlightSQL sql={plans[0].digest_text!} />\n                  </Expand>\n                </Descriptions.Item>\n                <Descriptions.Item\n                  label={\n                    <Space size=\"middle\">\n                      <TextWithInfo.TransKey transKey=\"statement.fields.digest\" />\n                      <CopyLink data={plans[0].digest!} />\n                    </Space>\n                  }\n                >\n                  <div style={{ whiteSpace: 'pre-wrap', paddingRight: '8px' }}>\n                    {plans[0].digest}\n                  </div>\n                </Descriptions.Item>\n                <Descriptions.Item\n                  label={\n                    <TextWithInfo.TransKey transKey=\"statement.pages.detail.desc.time_range\" />\n                  }\n                >\n                  <DateTime.Calendar\n                    unixTimestampMs={\n                      Number(plans[0].summary_begin_time!) * 1000\n                    }\n                  />\n                  {' ~ '}\n                  <DateTime.Calendar\n                    unixTimestampMs={Number(plans[0].summary_end_time!) * 1000}\n                  />\n                </Descriptions.Item>\n                <Descriptions.Item\n                  label={\n                    <TextWithInfo.TransKey transKey=\"statement.fields.plan_count\" />\n                  }\n                >\n                  {plans.length}\n                </Descriptions.Item>\n                <Descriptions.Item\n                  label={\n                    <Space size=\"middle\">\n                      <TextWithInfo.TransKey transKey=\"statement.fields.schema_name\" />\n                      <CopyLink data={query.schema!} />\n                    </Space>\n                  }\n                >\n                  {query.schema!}\n                </Descriptions.Item>\n              </Descriptions>\n              <div\n                style={{\n                  display: plans && plans.length > 1 ? 'block' : 'none'\n                }}\n                data-e2e=\"statement_multiple_execution_plans\"\n              >\n                <Alert\n                  message={t(`statement.pages.detail.desc.plans.note`)}\n                  type=\"info\"\n                  showIcon\n                />\n                <CardTable\n                  cardNoMargin\n                  columns={planColumns}\n                  items={plans}\n                  orderBy=\"exec_count\"\n                  selectionMode={SelectionMode.multiple}\n                  selection={selection.current}\n                  selectionPreservedOnEmptyClick\n                />\n              </div>\n            </>\n          )}\n        </AnimatedSkeleton>\n      </Head>\n\n      {selectedPlans.length > 0 && plans && plans.length > 0 && (\n        <PlanDetail\n          query={{\n            ...query,\n            plans: selectedPlans,\n            allPlans: plans.length\n          }}\n          key={JSON.stringify(selectedPlans)}\n        />\n      )}\n    </div>\n  )\n}\n\nDetailPage.buildQuery = buildQueryFn<IPageQuery>()\nDetailPage.parseQuery = parseQueryFn<IPageQuery>()\n\nexport default DetailPage\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/List/List.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.list {\n  &_container {\n    display: flex;\n    flex-direction: column;\n    height: 100vh;\n  }\n\n  &_toolbar {\n    @media only screen and (max-width: @screen-md) {\n      flex-direction: column;\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/List/StatementSettingForm.tsx",
    "content": "import React, { useState, useCallback } from 'react'\nimport {\n  Form,\n  Skeleton,\n  Switch,\n  Input,\n  Slider,\n  Space,\n  Button,\n  Modal\n} from 'antd'\nimport { ExclamationCircleOutlined } from '@ant-design/icons'\nimport { useTranslation } from 'react-i18next'\nimport { StatementEditableConfig } from '@lib/client'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { DrawerFooter, ErrorBar } from '@lib/components'\nimport { useIsWriteable } from '@lib/utils/store'\nimport { ReqConfig } from '@lib/types'\n\nimport { AxiosPromise } from 'axios'\n\ninterface Props {\n  onClose?: () => void\n  onConfigUpdated?: () => any\n\n  getStatementConfig: (\n    reqConfig: ReqConfig\n  ) => AxiosPromise<StatementEditableConfig>\n  updateStatementConfig: (\n    request: StatementEditableConfig,\n    options?: ReqConfig\n  ) => AxiosPromise<string>\n}\n\nconst convertArrToObj = (arr: number[]) =>\n  arr.reduce((acc, cur) => {\n    acc[cur] = cur\n    return acc\n  }, {})\n\nfunction StatementSettingForm({\n  onClose,\n  onConfigUpdated,\n  getStatementConfig,\n  updateStatementConfig\n}: Props) {\n  const [submitting, setSubmitting] = useState(false)\n  const { t } = useTranslation()\n  const isWriteable = useIsWriteable()\n\n  const {\n    data: initialConfig,\n    isLoading: loading,\n    error\n  } = useClientRequest((reqConfig) => getStatementConfig(reqConfig))\n\n  const handleSubmit = useCallback(\n    (values) => {\n      async function updateConfig(values) {\n        const newConfig: StatementEditableConfig = {\n          enable: values.enable,\n          max_size: values.max_size,\n          refresh_interval: values.refresh_interval * 60,\n          history_size: values.history_size,\n          internal_query: values.internal_query\n        }\n        try {\n          setSubmitting(true)\n          await updateStatementConfig(newConfig)\n          onClose?.()\n          onConfigUpdated?.()\n        } finally {\n          setSubmitting(false)\n        }\n      }\n\n      if (!values.enable && (initialConfig?.enable ?? true)) {\n        // warning\n        Modal.confirm({\n          title: t('statement.settings.close_statement'),\n          icon: <ExclamationCircleOutlined />,\n          content: t('statement.settings.close_statement_warning'),\n          okText: t('statement.settings.actions.close'),\n          cancelText: t('statement.settings.actions.cancel'),\n          okButtonProps: { danger: true },\n          onOk: () => updateConfig(values)\n        })\n      } else {\n        updateConfig(values)\n      }\n    },\n    [t, onClose, onConfigUpdated, initialConfig, updateStatementConfig]\n  )\n\n  return (\n    <>\n      {error && <ErrorBar errors={[error]} />}\n      {loading && <Skeleton active={true} paragraph={{ rows: 5 }} />}\n      {!loading && initialConfig && (\n        <Form\n          layout=\"vertical\"\n          initialValues={{\n            ...initialConfig,\n            refresh_interval: Math.floor(\n              (initialConfig.refresh_interval ?? 0) / 60\n            )\n          }}\n          onFinish={handleSubmit}\n        >\n          <Form.Item\n            valuePropName=\"checked\"\n            label={t('statement.settings.switch')}\n            extra={t('statement.settings.switch_tooltip')}\n          >\n            <Form.Item noStyle name=\"enable\" valuePropName=\"checked\">\n              <Switch\n                disabled={!isWriteable}\n                data-e2e=\"statemen_enbale_switcher\"\n              />\n            </Form.Item>\n          </Form.Item>\n          <Form.Item\n            noStyle\n            shouldUpdate={(prev, cur) => prev.enable !== cur.enable}\n          >\n            {({ getFieldValue }) =>\n              getFieldValue('enable') && (\n                <>\n                  <Form.Item\n                    label={t('statement.settings.max_size')}\n                    extra={t('statement.settings.max_size_tooltip')}\n                    data-e2e=\"statement_setting_max_size\"\n                  >\n                    <Input.Group>\n                      <Form.Item noStyle name=\"max_size\">\n                        <Slider\n                          disabled={!isWriteable}\n                          min={200}\n                          max={5000}\n                          step={100}\n                          marks={convertArrToObj([200, 1000, 2000, 5000])}\n                        />\n                      </Form.Item>\n                    </Input.Group>\n                  </Form.Item>\n                  <Form.Item\n                    label={t('statement.settings.refresh_interval')}\n                    extra={t('statement.settings.refresh_interval_tooltip')}\n                    data-e2e=\"statement_setting_refresh_interval\"\n                  >\n                    <Input.Group>\n                      <Form.Item noStyle name=\"refresh_interval\">\n                        <Slider\n                          disabled={!isWriteable}\n                          min={1}\n                          max={60}\n                          step={null}\n                          marks={convertArrToObj([1, 5, 15, 30, 60])}\n                        />\n                      </Form.Item>\n                    </Input.Group>\n                  </Form.Item>\n                  <Form.Item\n                    label={t('statement.settings.history_size')}\n                    extra={t('statement.settings.history_size_tooltip')}\n                    data-e2e=\"statement_setting_history_size\"\n                  >\n                    <Input.Group>\n                      <Form.Item noStyle name=\"history_size\">\n                        <Slider\n                          disabled={!isWriteable}\n                          min={1}\n                          max={255}\n                          marks={convertArrToObj([1, 255])}\n                        />\n                      </Form.Item>\n                    </Input.Group>\n                  </Form.Item>\n                  <Form.Item\n                    label={t('statement.settings.keep_duration')}\n                    extra={t('statement.settings.keep_duration_tooltip')}\n                    shouldUpdate={(prev, cur) =>\n                      prev.refresh_interval !== cur.refresh_interval ||\n                      prev.history_size !== cur.history_size\n                    }\n                    data-e2e=\"statement_setting_keep_duration\"\n                  >\n                    {({ getFieldValue }) => {\n                      const refreshInterval =\n                        getFieldValue('refresh_interval') || 0\n                      const historySize = getFieldValue('history_size') || 0\n                      const totalMins = refreshInterval * historySize\n                      const day = Math.floor(totalMins / (24 * 60))\n                      const hour = Math.floor((totalMins - day * 24 * 60) / 60)\n                      const min = totalMins - day * 24 * 60 - hour * 60\n                      return `${day} day ${hour} hour ${min} min`\n                    }}\n                  </Form.Item>\n                  <Form.Item\n                    label={t('statement.settings.internal_query')}\n                    extra={t('statement.settings.internal_query_tooltip')}\n                    name=\"internal_query\"\n                    valuePropName=\"checked\"\n                    data-e2e=\"statement_setting_internal_query\"\n                  >\n                    <Switch disabled={!isWriteable} />\n                  </Form.Item>\n                </>\n              )\n            }\n          </Form.Item>\n          <DrawerFooter>\n            <Space>\n              <Button\n                type=\"primary\"\n                htmlType=\"submit\"\n                loading={submitting}\n                disabled={!isWriteable}\n                data-e2e=\"submit_btn\"\n              >\n                {t('statement.settings.actions.save')}\n              </Button>\n              <Button onClick={onClose}>\n                {t('statement.settings.actions.cancel')}\n              </Button>\n            </Space>\n          </DrawerFooter>\n        </Form>\n      )}\n    </>\n  )\n}\n\nexport default StatementSettingForm\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/List/index.tsx",
    "content": "import React, { useState, useContext, useMemo } from 'react'\nimport {\n  Space,\n  Tooltip,\n  Drawer,\n  Button,\n  Checkbox,\n  Result,\n  Input,\n  Dropdown,\n  Menu,\n  Alert,\n  message\n} from 'antd'\nimport {\n  LoadingOutlined,\n  SettingOutlined,\n  ExportOutlined,\n  MenuOutlined,\n  QuestionCircleOutlined\n} from '@ant-design/icons'\nimport { useURLTimeRange } from '@lib/hooks/useURLTimeRange'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport { useTranslation } from 'react-i18next'\nimport { CacheContext } from '@lib/utils/useCache'\nimport {\n  Card,\n  ColumnsSelector,\n  Toolbar,\n  MultiSelect,\n  TimeRangeSelector,\n  DateTime,\n  toTimeRangeValue,\n  IColumnKeys,\n  LimitTimeRange\n} from '@lib/components'\nimport { useVersionedLocalStorageState } from '@lib/utils/useVersionedLocalStorageState'\nimport { StatementsTable } from '../../components'\nimport StatementSettingForm from './StatementSettingForm'\nimport useStatementTableController, {\n  DEF_STMT_COLUMN_KEYS,\n  DEF_STMT_QUERY_OPTIONS\n} from '../../utils/useStatementTableController'\nimport styles from './List.module.less'\nimport { useDebounceFn, useMemoizedFn } from 'ahooks'\nimport { useDeepCompareChange } from '@lib/utils/useChange'\nimport { StatementModel } from '@lib/client'\nimport { isDistro } from '@lib/utils/distro'\nimport { StatementContext } from '../../context'\nimport { telemetry as stmtTelmetry } from '../../utils/telemetry'\n\nconst STMT_VISIBLE_COLUMN_KEYS = 'statement.visible_column_keys'\nconst STMT_SHOW_FULL_SQL = 'statement.show_full_sql'\n\nfunction getDataTimeRange(\n  list?: StatementModel[]\n): [number, number] | undefined {\n  if (!list || list?.length === 0) {\n    return\n  }\n  let min = list[0].summary_begin_time ?? 0\n  let max = list[0].summary_end_time ?? 0\n  for (const item of list) {\n    if ((item.summary_begin_time ?? 0) < min) {\n      min = item.summary_begin_time ?? 0\n    }\n    if ((item.summary_end_time ?? 0) > max) {\n      max = item.summary_end_time ?? 0\n    }\n  }\n  if (min === 0 || max === 0) {\n    return\n  }\n  return [min, max]\n}\n\nexport default function StatementsOverview() {\n  const { t } = useTranslation()\n\n  const ctx = useContext(StatementContext)\n\n  const cacheMgr = useContext(CacheContext)\n\n  const [showSettings, setShowSettings] = useState(false)\n  const [visibleColumnKeys, setVisibleColumnKeys] =\n    useVersionedLocalStorageState(\n      STMT_VISIBLE_COLUMN_KEYS,\n      {\n        defaultValue: DEF_STMT_COLUMN_KEYS\n      },\n      ctx?.cfg.persistQueryOptions\n    )\n  const [showFullSQL, setShowFullSQL] = useVersionedLocalStorageState(\n    STMT_SHOW_FULL_SQL,\n    { defaultValue: false }\n  )\n  const [downloading, setDownloading] = useState(false)\n\n  const { timeRange, setTimeRange } = useURLTimeRange()\n\n  const controller = useStatementTableController({\n    cacheMgr,\n    showFullSQL,\n    fetchSchemas: ctx?.cfg.showDBFilter,\n    fetchGroups: ctx?.cfg.showResourceGroupFilter,\n    fetchConfig: ctx?.cfg.showConfig,\n    persistQueryInSession: ctx?.cfg.persistQueryOptions,\n    initialQueryOptions: {\n      ...DEF_STMT_QUERY_OPTIONS,\n      visibleColumnKeys,\n      timeRange\n    },\n    ds: ctx!.ds\n  })\n  function updateVisibleColumnKeys(v: IColumnKeys) {\n    setVisibleColumnKeys(v)\n    stmtTelmetry.changeVisibleColumns(v)\n\n    if (!v[controller.orderOptions.orderBy]) {\n      controller.resetOrder()\n    }\n  }\n\n  function menuItemClick({ key }) {\n    switch (key) {\n      case 'export':\n        const hide = message.loading(\n          t('statement.pages.overview.toolbar.exporting') + '...',\n          0\n        )\n        downloadCSV().finally(hide)\n        stmtTelmetry.export()\n        break\n    }\n  }\n\n  const dropdownMenu = (\n    <Menu onClick={menuItemClick}>\n      <Menu.Item\n        key=\"export\"\n        disabled={downloading}\n        icon={<ExportOutlined />}\n        data-e2e=\"statement_export_btn\"\n      >\n        {downloading\n          ? t('statement.pages.overview.toolbar.exporting')\n          : t('statement.pages.overview.toolbar.export')}\n      </Menu.Item>\n    </Menu>\n  )\n\n  const [filterSchema, setFilterSchema] = useState<string[]>(\n    controller.queryOptions.schemas\n  )\n  const [filterGroup, setFilterGroup] = useState<string[]>(\n    controller.queryOptions.groups\n  )\n  const [filterStmtType, setFilterStmtType] = useState<string[]>(\n    controller.queryOptions.stmtTypes\n  )\n  const [filterText, setFilterText] = useState<string>(\n    controller.queryOptions.searchText\n  )\n\n  const sendQueryNow = useMemoizedFn(() => {\n    cacheMgr?.clear()\n    controller.setQueryOptions({\n      timeRange,\n      schemas: filterSchema,\n      groups: filterGroup,\n      stmtTypes: filterStmtType,\n      searchText: filterText,\n      visibleColumnKeys\n    })\n    stmtTelmetry.search()\n  })\n\n  const sendQueryDebounced = useDebounceFn(sendQueryNow, {\n    wait: 300\n  }).run\n\n  useDeepCompareChange(() => {\n    if (\n      ctx?.cfg.instantQuery === false ||\n      controller.isDataLoadedSlowly || // if data was loaded slowly\n      controller.isDataLoadedSlowly === null // or a request is not yet finished (which means slow network)..\n    ) {\n      // do not send requests on-the-fly.\n      return\n    }\n    sendQueryDebounced()\n  }, [\n    timeRange,\n    filterSchema,\n    filterGroup,\n    filterStmtType,\n    filterText,\n    visibleColumnKeys\n  ])\n\n  const downloadCSV = useMemoizedFn(async () => {\n    // use last effective query options\n    const timeRangeValue = toTimeRangeValue(controller.queryOptions.timeRange)\n    try {\n      setDownloading(true)\n      const res = await ctx!.ds.statementsDownloadTokenPost({\n        begin_time: timeRangeValue[0],\n        end_time: timeRangeValue[1],\n        fields: '*',\n        schemas: controller.queryOptions.schemas,\n        resource_groups: controller.queryOptions.groups,\n        stmt_types: controller.queryOptions.stmtTypes,\n        text: controller.queryOptions.searchText\n      })\n      const token = res.data\n      if (token) {\n        window.location.href = `${\n          ctx!.cfg.apiPathBase\n        }/statements/download?token=${token}`\n      }\n    } finally {\n      setDownloading(false)\n    }\n  })\n\n  const dataTimeRange = useMemo(() => {\n    return getDataTimeRange(controller.data?.list)\n  }, [controller.data])\n\n  return (\n    <div className={styles.list_container}>\n      <Card noMarginBottom>\n        <Toolbar className={styles.list_toolbar} data-e2e=\"statement_toolbar\">\n          <Space>\n            {ctx?.cfg.timeRangeSelector !== undefined ? (\n              <LimitTimeRange\n                value={timeRange}\n                onChange={setTimeRange}\n                recent_seconds={ctx.cfg.timeRangeSelector.recentSeconds}\n                customAbsoluteRangePicker={true}\n                onZoomOutClick={() => {}}\n                timeRangeLimit={ctx?.cfg.timeRangeSelector?.timeRangeLimit}\n              />\n            ) : (\n              <TimeRangeSelector\n                value={timeRange}\n                onChange={(t) => {\n                  setTimeRange(t)\n                  stmtTelmetry.changeTimeRange(t)\n                }}\n                data-e2e=\"statement_time_range_selector\"\n              />\n            )}\n            {(ctx?.cfg.showDBFilter ?? true) && (\n              <MultiSelect.Plain\n                placeholder={t(\n                  'statement.pages.overview.toolbar.schemas.placeholder'\n                )}\n                selectedValueTransKey=\"statement.pages.overview.toolbar.schemas.selected\"\n                columnTitle={t(\n                  'statement.pages.overview.toolbar.schemas.columnTitle'\n                )}\n                value={filterSchema}\n                style={{ width: 150 }}\n                onChange={(d) => {\n                  setFilterSchema(d)\n                  stmtTelmetry.changeDatabases()\n                }}\n                items={controller.allSchemas}\n                data-e2e=\"execution_database_name\"\n              />\n            )}\n\n            {(ctx?.cfg.showResourceGroupFilter ?? true) &&\n              controller.allGroups?.length > 1 && (\n                <MultiSelect.Plain\n                  placeholder={t(\n                    'statement.pages.overview.toolbar.resource_groups.placeholder'\n                  )}\n                  selectedValueTransKey=\"statement.pages.overview.toolbar.resource_groups.selected\"\n                  columnTitle={t(\n                    'statement.pages.overview.toolbar.resource_groups.columnTitle'\n                  )}\n                  value={filterGroup}\n                  style={{ width: 150 }}\n                  onChange={(d) => {\n                    setFilterGroup(d)\n                  }}\n                  items={controller.allGroups}\n                  data-e2e=\"resource_group_name_select\"\n                />\n              )}\n            <MultiSelect.Plain\n              placeholder={t(\n                'statement.pages.overview.toolbar.statement_types.placeholder'\n              )}\n              selectedValueTransKey=\"statement.pages.overview.toolbar.statement_types.selected\"\n              columnTitle={t(\n                'statement.pages.overview.toolbar.statement_types.columnTitle'\n              )}\n              value={filterStmtType}\n              style={{ width: 150 }}\n              onChange={(v) => {\n                setFilterStmtType(v)\n                stmtTelmetry.changeStmtTypes()\n              }}\n              items={controller.allStmtTypes}\n              data-e2e=\"statement_types\"\n            />\n            <Input.Search\n              value={filterText}\n              onChange={(e) => {\n                setFilterText(e.target.value)\n                stmtTelmetry.changeSearchText()\n              }}\n              onSearch={sendQueryNow}\n              placeholder={t(\n                'statement.pages.overview.toolbar.keyword.placeholder'\n              )}\n              data-e2e=\"sql_statements_search\"\n              enterButton={t('statement.pages.overview.toolbar.query')}\n            />\n            {controller.isLoading && (\n              <LoadingOutlined data-e2e=\"statement_refresh\" />\n            )}\n          </Space>\n          <Space>\n            {controller.availableColumnsInTable.length > 0 && (\n              <ColumnsSelector\n                columns={controller.availableColumnsInTable}\n                visibleColumnKeys={visibleColumnKeys}\n                defaultVisibleColumnKeys={DEF_STMT_COLUMN_KEYS}\n                onChange={updateVisibleColumnKeys}\n                foot={\n                  <Checkbox\n                    checked={showFullSQL}\n                    onChange={(e) => {\n                      setShowFullSQL(e.target.checked)\n                      stmtTelmetry.toggleShowFullSQL(e.target.checked)\n                    }}\n                    data-e2e=\"statement_show_full_sql\"\n                  >\n                    {t(\n                      'statement.pages.overview.toolbar.select_columns.show_full_sql'\n                    )}\n                  </Checkbox>\n                }\n              />\n            )}\n            {(ctx?.cfg.showConfig ?? true) && (\n              <Tooltip\n                mouseEnterDelay={0}\n                mouseLeaveDelay={0}\n                title={t('statement.settings.title')}\n                placement=\"bottom\"\n              >\n                <SettingOutlined\n                  onClick={() => {\n                    setShowSettings(true)\n                    stmtTelmetry.openSetting()\n                  }}\n                  data-e2e=\"statement_setting\"\n                />\n              </Tooltip>\n            )}\n            {(ctx?.cfg.enableExport ?? true) && (\n              <Dropdown overlay={dropdownMenu} placement=\"bottomRight\">\n                <div\n                  style={{ cursor: 'pointer' }}\n                  data-e2e=\"statement_export_menu\"\n                >\n                  <MenuOutlined />\n                </div>\n              </Dropdown>\n            )}\n            {!isDistro() && (ctx!.cfg.showHelp ?? true) && (\n              <Tooltip\n                mouseEnterDelay={0}\n                mouseLeaveDelay={0}\n                title={t('statement.settings.help')}\n                placement=\"bottom\"\n              >\n                <QuestionCircleOutlined\n                  onClick={() => {\n                    window.open(t('statement.settings.help_url'), '_blank')\n                    stmtTelmetry.openHelp()\n                  }}\n                />\n              </Tooltip>\n            )}\n          </Space>\n        </Toolbar>\n      </Card>\n\n      <div style={{ height: 16 }} />\n\n      {controller.isEnabled ? (\n        <div\n          style={{ height: '100%', position: 'relative' }}\n          data-e2e=\"statements_table\"\n        >\n          <ScrollablePane>\n            {controller.isDataLoadedSlowly && (ctx?.cfg.instantQuery ?? true) && (\n              <Card noMarginBottom noMarginTop>\n                <Alert\n                  message={t('statement.pages.overview.slow_load_info')}\n                  type=\"info\"\n                  showIcon\n                />\n              </Card>\n            )}\n            <Card noMarginBottom noMarginTop>\n              <p className=\"ant-form-item-extra\">\n                {dataTimeRange && (ctx?.cfg.showActualTimeRange ?? true) && (\n                  <div>\n                    {t('statement.pages.overview.actual_range')}\n                    <DateTime.Calendar\n                      unixTimestampMs={dataTimeRange[0] * 1000}\n                    />\n                    {' ~ '}\n                    <DateTime.Calendar\n                      unixTimestampMs={dataTimeRange[1] * 1000}\n                    />\n                  </div>\n                )}\n                {(controller.data?.list.length ?? 0) > 0 && (\n                  <div>\n                    {t('statement.pages.overview.result_count', {\n                      n: controller.data?.list.length\n                    })}\n                  </div>\n                )}\n              </p>\n            </Card>\n            <StatementsTable cardNoMarginTop controller={controller} />\n          </ScrollablePane>\n        </div>\n      ) : (\n        <Result\n          title={t('statement.settings.disabled_result.title')}\n          subTitle={t('statement.settings.disabled_result.sub_title')}\n          extra={\n            <Space>\n              <Button type=\"primary\" onClick={() => setShowSettings(true)}>\n                {t('statement.settings.open_setting')}\n              </Button>\n              {!isDistro() && (\n                <Button\n                  onClick={() => {\n                    window.open(t('statement.settings.help_url'), '_blank')\n                    stmtTelmetry.openHelp()\n                  }}\n                >\n                  {t('statement.settings.help')}\n                </Button>\n              )}\n            </Space>\n          }\n        />\n      )}\n\n      <Drawer\n        title={t('statement.settings.title')}\n        width={300}\n        closable={true}\n        visible={showSettings}\n        onClose={() => setShowSettings(false)}\n        destroyOnClose={true}\n      >\n        <StatementSettingForm\n          onClose={() => setShowSettings(false)}\n          onConfigUpdated={sendQueryNow}\n          getStatementConfig={ctx!.ds.statementsConfigGet}\n          updateStatementConfig={ctx!.ds.statementsConfigPost}\n        />\n      </Drawer>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/pages/index.ts",
    "content": "import List from './List'\nimport Detail from './Detail'\n\nexport { List, Detail }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/translations/en.yaml",
    "content": "statement:\n  nav_title: SQL Statements\n  pages:\n    detail:\n      head:\n        back: List\n        title: Statement Information\n      plan_bind:\n        title: Plan Binding\n        bound: Bound\n        not_bound: Not Bound\n        bound_available_tooltip: 'Plan Binding only supported bindable SQL statements including SELECT, DELETE, UPDATE, and INSERT / REPLACE with SELECT subqueries.'\n        notice: 'Notice: This feature does not work for queries with subqueries, queries that access TiFlash, or queries that join 3 or more tables.'\n        bound_sql: 'Bind this SQL'\n        to_plan: 'to a special plan'\n        bound_status_desc: The plan has bound to this SQL\n        drop_btn_txt: Drop\n        bind_btn_txt: Bind\n      desc:\n        time_range: Time Range\n        plans:\n          note: There are multiple execution plans for this kind of SQL statement. You can choose to view one or multiple of them.\n          title:\n            one_for_all: Execution Detail\n            all: Execution Detail of All Plans\n            some: 'Execution Detail of Selected {{n}} Plans'\n          execution:\n            title: Execution Plan\n            text: Text\n            table: Table\n            visual: Visual\n            modal_title: Visual Plan\n      tabs:\n        basic: Basic\n        time: Time\n        copr: Coprocessor Read\n        txn: Transaction\n        slow_query: Slow Query\n    overview:\n      toolbar:\n        schemas:\n          placeholder: All Databases\n          selected: '{{ n }} Databases'\n          columnTitle: Execution Database Name\n        resource_groups:\n          placeholder: All Resource Groups\n          selected: '{{ n }} Resource Groups'\n          columnTitle: Resource Group Name\n        statement_types:\n          placeholder: All Kinds\n          selected_one: '{{ count }} Kind'\n          selected_other: '{{ count }} Kinds'\n          columnTitle: Statement Kind\n        select_columns:\n          show_full_sql: Show Full Query Text\n        query: Query\n        keyword:\n          placeholder: Filter keyword\n        time_range_selector:\n          name: Select Time Range\n          recent: Recent\n          usual_time_ranges: Common\n          custom_time_ranges: Custom\n        export: Export\n        exporting: Exporting\n      result_count: '{{ n }} results.'\n      actual_range: 'Due to time window and expiration configurations, currently displaying data in time range: '\n      slow_load_info: On-the-fly update is disabled due to slow data loading. You can initiate query manually by clicking the \"Query\" button.\n  settings:\n    title: Settings\n    disabled_result:\n      title: Feature Not Enabled\n      sub_title: |\n        Statement feature is not enabled so that statement history cannot be viewed.\n        You can modify settings to enable the feature and wait for new data being collected.\n    open_setting: Open Settings\n    close_statement: Disable Statement Feature\n    close_statement_warning: Are you sure want to disable this feature? Current statement history will be cleared.\n    switch: Enable Feature\n    switch_tooltip: Whether Statement feature is enabled. When enabled, there will be a small SQL statement execution overhead.\n    max_size: 'Max # Statement'\n    max_size_tooltip: Max number of statement to collect. After exceeding, old statement information will be dropped. You may enlarge this setting when memory is sufficient and you discovered that data displayed in UI is incomplete.\n    refresh_interval: Window Size (min)\n    refresh_interval_tooltip: By reducing this setting you can select time range more precisely.\n    history_size: '# Windows'\n    history_size_tooltip: By enlarging this setting more statement history will be preserved, with larger memory cost.\n    keep_duration: SQL Statement History Size\n    keep_duration_tooltip: Window Size × Number of Windows\n    internal_query: Collect Internal Queries\n    internal_query_tooltip: After enabled, {{distro.tidb}} internal queries will be collected as well.\n    actions:\n      save: Save\n      close: Disable\n      cancel: Cancel\n    help: Help\n    help_url: https://docs.pingcap.com/tidb/dev/dashboard-statement-list\n  fields:\n    table_names: Table Names\n    related_schemas: Database\n    related_schemas_tooltip: Related databases of the statement\n    plan_digest: Plan ID\n    plan_digest_tooltip: Different execution plans have different plan ID\n    digest_text: Statement Template\n    digest_text_tooltip: Similar queries have same statement template even for different query parameters\n    sum_latency: Total Latency\n    sum_latency_tooltip: Total execution time for this kind of statement\n    exec_count: '# Exec'\n    exec_count_tooltip: Total execution count for this kind of statement\n    plan_count: '# Plans'\n    plan_count_tooltip: Number of distinct execution plans of this statement in current time range\n    plan_cache_hits: '# Plan Cache Hits'\n    plan_cache_hits_tooltip: Number of times the execution plan cache is hit\n    avg_latency: Mean Latency\n    avg_latency_tooltip: Execution time of single query\n    avg_mem: Mean Memory\n    avg_mem_tooltip: Memory usage of single query\n    max_mem: Max Memory\n    max_mem_tooltip: Maximum memory usage of single query\n    max_mem_arbitration: Max Mem Arbitration\n    max_mem_arbitration_tooltip: Maximum wait time of memory arbitration of single query\n    avg_mem_arbitration: Mean Mem Arbitration\n    avg_mem_arbitration_tooltip: Average wait time of memory arbitration of single query\n    avg_disk: Mean Disk\n    avg_disk_tooltip: Disk usage of single query\n    max_disk: Max Disk\n    max_disk_tooltip: Maximum disk usage of single query\n    index_names: Index Name\n    index_names_tooltip: The name of the used index\n    first_seen: First Seen\n    last_seen: Last Seen\n    sample_user: Execution User\n    sample_user_tooltip: The user that executes the query (sampled)\n    sum_errors: Total Errors\n    sum_warnings: Total Warnings\n    errors_warnings: Errors / Warnings\n    errors_warnings_tooltip: Total Errors and Total Warnings\n    parse_latency: Parse Time\n    parse_latency_tooltip: Time consumed when parsing the query\n    compile_latency: Compile\n    compile_latency_tooltip: Time consumed when optimizing the query\n    wait_time: Coprocessor Wait Time\n    process_time: Coprocessor Execution Time\n    total_process_time: Total Execution Time\n    total_wait_time: Total Wait Time\n    backoff_time: Backoff Retry Time\n    backoff_time_tooltip: The waiting time before retry when a query encounters errors that require a retry\n    get_commit_ts_time: Get Commit Ts Time\n    local_latch_wait_time: Local Latch Wait Time\n    resolve_lock_time: Resolve Lock Time\n    prewrite_time: Prewrite Time\n    commit_time: Commit Time\n    commit_backoff_time: Commit Backoff Retry Time\n    latency: Query\n    query_time2: Query Time\n    query_time2_tooltip: The execution time of a query (due to the parallel execution, it may be significantly smaller than the above time)\n    sum_cop_task_num: Total Coprocessor Tasks\n    avg_processed_keys: Mean Visible Versions Per Query\n    max_processed_keys: Max Visible Versions Per Query\n    avg_total_keys: Mean Meet Versions Per Query\n    avg_total_keys_tooltip: Meet versions contains overwritten or deleted versions\n    max_total_keys: Max Meet Versions Per Query\n    avg_affected_rows: Mean Affected Rows\n    sum_backoff_times: Total Backoff Count\n    avg_write_keys: Mean Written Keys\n    max_write_keys: Max Written Keys\n    avg_write_size: Mean Written Data Size\n    max_write_size: Max Written Data Size\n    avg_prewrite_regions: Mean Prewrite Regions\n    max_prewrite_regions: Max Prewrite Regions\n    avg_txn_retry: Mean Transaction Retries\n    max_txn_retry: Max Transaction Retries\n    digest: Query Template ID\n    digest_tooltip: a.k.a. Query digest\n    schema_name: Execution Database\n    schema_name_tooltip: The database used to execute the query\n    query_sample_text: Query Sample\n    prev_sample_text: Previous Query Sample\n    plan: Execution Plan\n\n    avg_rocksdb_delete_skipped_count: Mean RocksDB Skipped Deletions\n    avg_rocksdb_delete_skipped_count_tooltip: Total number of deleted (a.k.a. tombstone) key versions that are skipped during iteration (RocksDB delete_skipped_count)\n    max_rocksdb_delete_skipped_count: Max RocksDB Skipped Deletions\n    avg_rocksdb_key_skipped_count: Mean RocksDB Skipped Keys\n    avg_rocksdb_key_skipped_count_tooltip: Total number of keys skipped during iteration (RocksDB key_skipped_count)\n    max_rocksdb_key_skipped_count: Max RocksDB Skipped Keys\n    avg_rocksdb_block_cache_hit_count: Mean RocksDB Block Cache Hits\n    avg_rocksdb_block_cache_hit_count_tooltip: Total number of hits from the block cache (RocksDB block_cache_hit_count)\n    max_rocksdb_block_cache_hit_count: Max RocksDB Block Cache Hits\n    avg_rocksdb_block_read_count: Mean RocksDB Block Reads\n    avg_rocksdb_block_read_count_tooltip: Total number of blocks RocksDB read from file (RocksDB block_read_count)\n    max_rocksdb_block_read_count: Max RocksDB Block Reads\n    avg_rocksdb_block_read_byte: Mean RocksDB FS Read Size\n    avg_rocksdb_block_read_byte_tooltip: Total number of bytes RocksDB read from file (RocksDB block_read_byte)\n    max_rocksdb_block_read_byte: Max RocksDB FS Read Size\n\n    resource_group: Resource Group\n    resource_group_tooltip: The resource group that the query belongs to\n    avg_ru: Mean RU\n    avg_ru_tooltip: The average number of request units (RU) consumed by the statement\n    max_ru: Max RU\n    max_ru_tooltip: The maximum number of request units (RU) consumed by the statement\n    sum_ru: Total RU\n    sum_ru_tooltip: The total number of request units (RU) consumed by the statement\n    avg_ru_v2: Mean RU V2\n    avg_ru_v2_tooltip: The average number of RU V2 consumed by the statement\n    sum_ru_v2: Total RU V2\n    sum_ru_v2_tooltip: The total number of RU V2 consumed by the statement\n    max_ru_v2: Max RU V2\n    max_ru_v2_tooltip: The maximum number of RU V2 consumed by the statement\n    avg_time_queued_by_rc: Mean RC Wait Time in Queue\n    avg_time_queued_by_rc_tooltip: The average time that the query waits in the resource control's queue (not a wall time)\n    max_time_queued_by_rc: Max RC Wait Time in Queue\n    max_time_queued_by_rc_tooltip: The maximum time that the query waits in the resource control's queue (not a wall time)\n    rc_wait_time_tooltip: 'The total wait time spent in the resource queue (note: {{distro.tikv}} executes requests in parallel so that this is not a wall time)'\n\n    sum_unpacked_bytes_sent_tikv_total: Total Bytes Sent to TiKV\n    sum_unpacked_bytes_sent_tikv_total_tooltip: The total number of bytes sent to TiKV\n    sum_unpacked_bytes_received_tikv_total: Total Bytes Received from TiKV\n    sum_unpacked_bytes_received_tikv_total_tooltip: The total number of bytes received from TiKV\n    sum_unpacked_bytes_sent_tikv_cross_zone: Total Cross-Zone Bytes Sent to TiKV\n    sum_unpacked_bytes_sent_tikv_cross_zone_tooltip: The total number of bytes sent to TiKV across zones\n    sum_unpacked_bytes_received_tikv_cross_zone: Total Cross-Zone Bytes Received from TiKV\n    sum_unpacked_bytes_received_tikv_cross_zone_tooltip: The total number of bytes received from TiKV across zones\n    sum_unpacked_bytes_sent_tiflash_total: Total Bytes Sent to TiFlash\n    sum_unpacked_bytes_sent_tiflash_total_tooltip: The total number of bytes sent to TiFlash\n    sum_unpacked_bytes_received_tiflash_total: Total Bytes Received from TiFlash\n    sum_unpacked_bytes_received_tiflash_total_tooltip: The total number of bytes received from TiFlash\n    sum_unpacked_bytes_sent_tiflash_cross_zone: Total Cross-Zone Bytes Sent to TiFlash\n    sum_unpacked_bytes_sent_tiflash_cross_zone_tooltip: The total number of bytes sent to TiFlash across zones\n    sum_unpacked_bytes_received_tiflash_cross_zone: Total Cross-Zone Bytes Received from TiFlash\n    sum_unpacked_bytes_received_tiflash_cross_zone_tooltip: The total number of bytes received from TiFlash across zones\n    avg_ia_read_segment_count: Mean IA Read Segments\n    avg_ia_read_segment_count_tooltip: The average number of IA read segments per execution\n    avg_ia_remote_read_segment_size: Mean IA Remote Read Segment Size\n    avg_ia_remote_read_segment_size_tooltip: The average of bytes read from IA remote segments\n    avg_ia_remote_read_segment_wait_time: Mean IA Remote Read Segment Wait Time\n    avg_ia_remote_read_segment_wait_time_tooltip: The average wait time spent reading IA remote segments\n    max_ia_read_segment_count: Max IA Read Segments\n    max_ia_read_segment_count_tooltip: Maximum IA read segment count\n    max_ia_remote_read_segment_size: Max IA Remote Read Segment Size\n    max_ia_remote_read_segment_size_tooltip: Maximum number of bytes read from IA remote segments\n    max_ia_remote_read_segment_wait_time: Max IA Remote Read Segment Wait Time\n    max_ia_remote_read_segment_wait_time_tooltip: Maximum wait time spent reading IA remote segments\n    ia_remote_read_segment_wait_time: IA Remote Read Segment Wait Time\n    ia_remote_read_segment_wait_time_tooltip: The wait time spent reading IA remote segments\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/translations/zh.yaml",
    "content": "statement:\n  nav_title: SQL 语句分析\n  pages:\n    detail:\n      head:\n        back: 返回列表\n        title: SQL 语句信息\n      plan_bind:\n        title: 执行计划绑定\n        bound: 已绑定\n        not_bound: 未绑定\n        bound_available_tooltip: 计划绑定仅支持可绑定的 SQL 语句，包括 SELECT、DELETE、UPDATE 和 INSERT / REPLACE with SELECT 子查询。\n        notice: '注意: 此功能不适用于带有子查询的查询、访问 TiFlash 的查询或连接 3 个或更多表的查询。'\n        bound_sql: '绑定这条 SQL'\n        to_plan: '到执行计划'\n        bound_status_desc: 该 SQL 已绑定执行计划\n        drop_btn_txt: 解绑\n        bind_btn_txt: 绑定\n      desc:\n        time_range: 时间范围\n        plans:\n          note: 该 SQL 模板在选定的时间范围内有多个执行计划，您可以选择查看其中一个或多个执行计划。\n          title:\n            one_for_all: 执行详情\n            all: 所有执行计划的执行详情\n            some: '{{n}} 个执行计划的执行详情'\n          execution:\n            title: 执行计划\n            text: 文本\n            table: 表格\n            visual: 图形\n            modal_title: 执行计划可视化\n      tabs:\n        basic: 基本信息\n        time: 执行时间\n        copr: Coprocessor 读取\n        txn: 事务\n        slow_query: 慢查询\n    overview:\n      toolbar:\n        schemas:\n          placeholder: 所有数据库\n          selected: '{{ n }} 数据库'\n          columnTitle: 执行数据库名\n        resource_groups:\n          placeholder: 所有资源组\n          selected: '{{ n }} 资源组'\n          columnTitle: 资源组名\n        statement_types:\n          placeholder: 所有类型\n          selected: '{{ count }} 类型'\n          columnTitle: SQL 语句类型\n        select_columns:\n          show_full_sql: 显示完整 SQL 文本\n        query: 查询\n        keyword:\n          placeholder: 关键字过滤\n        time_range_selector:\n          name: 选择时间段\n          recent: 最近\n          usual_time_ranges: 常用时间范围\n          custom_time_ranges: 自定义时间范围\n        export: 导出\n        exporting: 正在导出\n      result_count: '{{ n }} 条结果。'\n      actual_range: 基于设置的时间窗及过期时间，当前显示数据的时间范围：\n      slow_load_info: 数据加载耗时较长，已禁用即时更新。修改查询条件后，您可以手工点击\"查询\"按钮来发起查询。\n  settings:\n    title: 设置\n    disabled_result:\n      title: 该功能未启用\n      sub_title: |\n        SQL 语句分析功能未启用，因此无法查看历史记录。\n        您可以修改设置打开该功能后等待新数据收集。\n    open_setting: 打开设置\n    close_statement: 关闭 SQL 语句分析功能\n    close_statement_warning: 确认要关闭该功能吗？关闭后现有历史记录也将被清空！\n    switch: 启用功能\n    switch_tooltip: 是否启用 SQL 语句分析功能，关闭后将不能使用 SQL 语句分析功能，但能提升少量 {{distro.tidb}} 性能。\n    max_size: 最大收集 SQL 语句个数\n    max_size_tooltip: 收集的 SQL 语句个数上限，当实际执行的 SQL 语句种类超过设定个数后最早执行的 SQL 语句信息将被丢弃。若您发现界面上呈现的 SQL 语句信息不完整，建议在内存允许的情况下调大本参数。\n    refresh_interval: 时间窗大小 (min)\n    refresh_interval_tooltip: 缩小时间窗大小可以使得选择的时间范围更精细。\n    history_size: 时间窗个数\n    history_size_tooltip: 扩大时间窗个数可以保留更长时间的执行历史，但也会引入更大的内存开销。\n    keep_duration: SQL 语句历史保留时长\n    keep_duration_tooltip: 时间窗大小 × 时间窗个数\n    internal_query: 收集内部查询\n    internal_query_tooltip: 开启后 {{distro.tidb}} 内部执行的 SQL 语句信息也将被收集。\n    actions:\n      save: 保存\n      close: 确认\n      cancel: 取消\n    help: 帮助\n    help_url: https://docs.pingcap.com/zh/tidb/dev/dashboard-statement-list\n  fields:\n    related_schemas: 数据库\n    related_schemas_tooltip: SQL 语句涉及的数据库\n    plan_digest: 执行计划 ID\n    plan_digest_tooltip: 不同的执行计划有不同的 ID\n    digest_text: SQL 模板\n    digest_text_tooltip: 相似的 SQL 查询即使查询参数不一样也具有相同的 SQL 模板\n    sum_latency: 累计耗时\n    sum_latency_tooltip: 该类 SQL 语句在时间段内的累计执行时间\n    exec_count: 执行次数\n    exec_count_tooltip: 该类 SQL 语句在时间段内被执行的总次数\n    plan_count: 计划数\n    plan_count_tooltip: 该类 SQL 语句在时间段内的不同执行计划数量\n    plan_cache_hits: 计划缓存命中次数\n    plan_cache_hits_tooltip: 该类 SQL 语句在时间段内的计划缓存命中次数\n    avg_latency: 平均耗时\n    avg_latency_tooltip: 单条 SQL 查询的执行时间\n    avg_mem: 平均内存\n    avg_mem_tooltip: 单条 SQL 查询的消耗内存大小\n    max_mem: 最大内存\n    max_mem_tooltip: 最大单条 SQL 查询消耗内存大小\n    max_mem_arbitration: 最大等待内存资源的耗时\n    max_mem_arbitration_tooltip: 最大单条 SQL 查询等待内存资源的耗时\n    avg_mem_arbitration: 平均等待内存资源的耗时\n    avg_mem_arbitration_tooltip: 单条 SQL 查询平均等待内存资源的耗时\n    avg_disk: 平均磁盘空间\n    avg_disk_tooltip: 单条 SQL 查询占用的磁盘空间大小\n    max_disk: 最大磁盘空间\n    max_disk_tooltip: 最大单条 SQL 查询占用的磁盘空间大小\n    table_names: 表名\n    index_names: 索引名\n    index_names_tooltip: SQL 执行时使用的索引名称\n    first_seen: 首次出现时间\n    last_seen: 最后出现时间\n    sample_user: 执行用户名\n    sample_user_tooltip: 执行该类 SQL 的用户名，可能存在多个执行用户，仅显示其中某一个\n    sum_errors: 累计 Error 个数\n    sum_warnings: 累计 Warning 个数\n    errors_warnings: 错误 / 警告\n    errors_warnings_tooltip: 累计错误和警告个数\n    parse_latency: 解析耗时\n    parse_latency_tooltip: 解析 SQL 查询的耗时\n    compile_latency: 优化耗时\n    compile_latency_tooltip: 编译并优化 SQL 查询的耗时\n    wait_time: Coprocessor 等待耗时\n    wait_time_tooltip: SQL 查询在 {{distro.tikv}} Coprocessor 上被等待执行的耗时，单个 SQL 查询所有 Coprocessor 任务累计后计算\n    process_time: Coprocessor 执行耗时\n    process_time_tooltip: SQL 查询在 {{distro.tikv}} Coprocessor 上的执行耗时，单个 SQL 查询所有 Coprocessor 任务累计后计算\n    total_process_time: 所有执行耗时\n    total_wait_time: 所有等待耗时\n    backoff_time: 重试等待耗时\n    backoff_time_tooltip: 单个 SQL 查询所有重试累计后计算\n    get_commit_ts_time: 取 Commit Ts 耗时\n    get_commit_ts_time_tooltip: 从 {{distro.pd}} 取递交时间戳（事务号）步骤的耗时\n    local_latch_wait_time: Local Latch Wait 耗时\n    local_latch_wait_time_tooltip: 事务在 {{distro.tidb}} 本地与其他事务产生了锁冲突并等待的耗时\n    resolve_lock_time: Resolve Lock 耗时\n    resolve_lock_time_tooltip: 事务在 {{distro.tikv}} 与其他事务产生了锁冲突并处理锁冲突的耗时\n    prewrite_time: Prewrite 阶段耗时\n    commit_time: Commit 阶段耗时\n    commit_backoff_time: Commit 重试等待耗时\n    latency: 执行耗时\n    query_time2: SQL 执行时间\n    query_time2_tooltip: 由于存在并行执行，因此 SQL 执行时间可能远小于上述各项时间\n    sum_cop_task_num: 累计 Coprocessor 请求数\n    sum_cop_task_num_tooltip: 时间段内该类 SQL 语句累计发送的 Coprocessor 请求数\n    avg_processed_keys: 单 SQL 查询平均可见版本数\n    max_processed_keys: 单 SQL 查询最大可见版本数\n    avg_total_keys: 单 SQL 查询平均遇到版本数\n    avg_total_keys_tooltip: 含已删除或覆盖但未 GC 的版本\n    max_total_keys: 单 SQL 查询最大遇到版本数\n    avg_affected_rows: 平均影响行数\n    sum_backoff_times: 累计重试次数\n    sum_backoff_times_tooltip: 这类 SQL 语句遇到需要重试的错误后的总重试次数\n    avg_write_keys: 平均写入 Key 个数\n    max_write_keys: 最大写入 Key 个数\n    avg_write_size: 平均写入数据量\n    max_write_size: 最大写入数据量\n    avg_prewrite_regions: Prewrite 平均涉及 Region 个数\n    max_prewrite_regions: Prewrite 最大涉及 Region 个数\n    avg_txn_retry: 事务平均重试次数\n    max_txn_retry: 事务最大重试次数\n    digest: SQL 模板 ID\n    digest_tooltip: SQL 模板的唯一标识（SQL 指纹）\n    schema_name: 执行数据库\n    schema_name_tooltip: 执行该 SQL 查询时使用的数据库名称\n    query_sample_text: SQL 查询样例\n    prev_sample_text: 前一条 SQL 查询样例\n    prev_sample_text_tooltip: 一般来说你可能只需要看 COMMIT 语句的前一条 SQL 查询\n    plan: 执行计划\n\n    avg_rocksdb_delete_skipped_count: RocksDB 已删除 Key 平均扫描数\n    avg_rocksdb_delete_skipped_count_tooltip: RocksDB 扫数据时遇到的已删除 (tombstone) Key 数量 (delete_skipped_count)\n    max_rocksdb_delete_skipped_count: RocksDB 已删除 Key 最大扫描数\n    avg_rocksdb_key_skipped_count: RocksDB Key 平均扫描数\n    avg_rocksdb_key_skipped_count_tooltip: RocksDB 扫数据时所有遇到的 Key 数量 (key_skipped_count)\n    max_rocksdb_key_skipped_count: RocksDB Key 最大扫描数\n    avg_rocksdb_block_cache_hit_count: RocksDB 缓存平均读次数\n    avg_rocksdb_block_cache_hit_count_tooltip: RocksDB 从 Block Cache 缓存中读数据的次数 (block_cache_hit_count)\n    max_rocksdb_block_cache_hit_count: RocksDB 缓存最大读次数\n    avg_rocksdb_block_read_count: RocksDB 文件系统平均读次数\n    avg_rocksdb_block_read_count_tooltip: RocksDB 从文件系统中读数据的次数 (block_read_count)\n    max_rocksdb_block_read_count: RocksDB 文件系统最大读次数\n    avg_rocksdb_block_read_byte: RocksDB 文件系统平均读数据量\n    avg_rocksdb_block_read_byte_tooltip: RocksDB 从文件系统中读数据的数据量 (block_read_byte)\n    max_rocksdb_block_read_byte: RocksDB 文件系统最大读数据量\n\n    resource_group: 资源组\n    resource_group_tooltip: SQL 语句所属的资源组\n    avg_ru: 平均 RU\n    avg_ru_tooltip: Statement 语句的平均 RU\n    max_ru: 最大 RU\n    max_ru_tooltip: 该 Statement 执行中使用过的最大 RU\n    sum_ru: 累积 RU\n    sum_ru_tooltip: 该 Statement 的 RU 累积值\n    avg_ru_v2: 平均 RU V2\n    avg_ru_v2_tooltip: Statement 语句的平均 RU V2\n    sum_ru_v2: 累积 RU V2\n    sum_ru_v2_tooltip: 该 Statement 的 RU V2 累积值\n    max_ru_v2: 最大 RU V2\n    max_ru_v2_tooltip: 该 Statement 执行中使用过的最大 RU V2\n    avg_time_queued_by_rc: RC 平均等待耗时\n    avg_time_queued_by_rc_tooltip: SQL 语句在资源组控制队列中平均等待的时间 (Resource Control)（注：{{distro.tikv}} 会并行处理任务，因此该时间不是自然流逝时间）\n    max_time_queued_by_rc: RC 最大等待耗时\n    max_time_queued_by_rc_tooltip: SQL 语句在资源组控制队列中最大等待的时间 (Resource Control)（注：{{distro.tikv}} 会并行处理任务，因此该时间不是自然流逝时间）\n    rc_wait_time: RC 资源控制等待累积耗时\n    rc_wait_time_tooltip: SQL 语句在资源组队列中等待的累积时间（注：{{distro.tikv}} 会并行等待任务，因此该时间不是自然流逝时间）\n\n    sum_unpacked_bytes_sent_tikv_total: 发给 TiKV 的总字节数\n    sum_unpacked_bytes_sent_tikv_total_tooltip: SQL 语句发送给 TiKV 的总字节数\n    sum_unpacked_bytes_received_tikv_total: 从 TiKV 接收的总字节数\n    sum_unpacked_bytes_received_tikv_total_tooltip: 从 TiKV 接收的总字节数\n    sum_unpacked_bytes_sent_tikv_cross_zone: 跨可用区发给 TiKV 的总字节数\n    sum_unpacked_bytes_sent_tikv_cross_zone_tooltip: 跨可用区发送给 TiKV 的总字节数\n    sum_unpacked_bytes_received_tikv_cross_zone: 跨可用区从 TiKV 接收的总字节数\n    sum_unpacked_bytes_received_tikv_cross_zone_tooltip: 跨可用区从 TiKV 接收的总字节数\n    sum_unpacked_bytes_sent_tiflash_total: 发给 TiFlash 的总字节数\n    sum_unpacked_bytes_sent_tiflash_total_tooltip: SQL 语句发送给 TiFlash 的总字节数\n    sum_unpacked_bytes_received_tiflash_total: 从 TiFlash 接收的总字节数\n    sum_unpacked_bytes_received_tiflash_total_tooltip: 从 TiFlash 接收的总字节数\n    sum_unpacked_bytes_sent_tiflash_cross_zone: 跨可用区发给 TiFlash 的总字节数\n    sum_unpacked_bytes_sent_tiflash_cross_zone_tooltip: 跨可用区发送给 TiFlash 的总字节数\n    sum_unpacked_bytes_received_tiflash_cross_zone: 跨可用区从 TiFlash 接收的总字节数\n    sum_unpacked_bytes_received_tiflash_cross_zone_tooltip: 跨可用区从 TiFlash 接收的总字节数\n    avg_ia_read_segment_count: 平均 IA 读分段数\n    avg_ia_read_segment_count_tooltip: 按执行次数加权的平均 IA 读分段数\n    avg_ia_remote_read_segment_size: 平均 IA 远程读分段数据量\n    avg_ia_remote_read_segment_size_tooltip: 按执行次数加权的平均 IA 远程读分段数据量\n    avg_ia_remote_read_segment_wait_time: 平均 IA 远程读分段等待时间\n    avg_ia_remote_read_segment_wait_time_tooltip: 按执行次数加权的平均 IA 远程读分段等待时间\n    max_ia_read_segment_count: 最大 IA 读分段数\n    max_ia_read_segment_count_tooltip: 单次执行中 IA 读分段数的最大值\n    max_ia_remote_read_segment_size: 最大 IA 远程读分段数据量\n    max_ia_remote_read_segment_size_tooltip: 单次执行中 IA 远程读分段数据量的最大值\n    max_ia_remote_read_segment_wait_time: 最大 IA 远程读分段等待时间\n    max_ia_remote_read_segment_wait_time_tooltip: 单次执行中 IA 远程读分段等待时间的最大值\n    ia_remote_read_segment_wait_time: IA 远程读分段等待时间\n    ia_remote_read_segment_wait_time_tooltip: IA 远程读分段数据量的最大值\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/utils/tableColumns.tsx",
    "content": "import { Tooltip } from 'antd'\nimport { max } from 'lodash'\nimport {\n  ColumnActionsMode,\n  IColumn\n} from 'office-ui-fabric-react/lib/DetailsList'\nimport React from 'react'\nimport { orange, red } from '@ant-design/colors'\n\nimport { StatementModel } from '@lib/client'\nimport { Bar, Pre } from '@lib/components'\nimport {\n  formatVal,\n  genDerivedBarSources,\n  TableColumnFactory,\n  Column\n} from '@lib/utils/tableColumnFactory'\n\n///////////////////////////////////////\n// statements order list in local by fieldName of IColumn\n// slow query order list in backend by key of IColumn\nconst TRANS_KEY_PREFIX = 'statement.fields'\n\nexport const derivedFields = {\n  avg_latency: genDerivedBarSources(\n    'avg_latency',\n    'max_latency',\n    'min_latency'\n  ),\n  parse_latency: genDerivedBarSources('avg_parse_latency', 'max_parse_latency'),\n  compile_latency: genDerivedBarSources(\n    'avg_compile_latency',\n    'max_compile_latency'\n  ),\n  process_time: genDerivedBarSources(\n    'avg_cop_process_time',\n    'max_cop_process_time'\n  ),\n  wait_time: genDerivedBarSources('avg_cop_wait_time', 'max_cop_wait_time'),\n  total_process_time: genDerivedBarSources(\n    'avg_process_time',\n    'max_process_time'\n  ),\n  total_wait_time: genDerivedBarSources('avg_wait_time', 'max_wait_time'),\n  backoff_time: genDerivedBarSources('avg_backoff_time', 'max_backoff_time'),\n  avg_write_keys: genDerivedBarSources('avg_write_keys', 'max_write_keys'),\n  avg_processed_keys: genDerivedBarSources(\n    'avg_processed_keys',\n    'max_processed_keys'\n  ),\n  avg_total_keys: genDerivedBarSources('avg_total_keys', 'max_total_keys'),\n  prewrite_time: genDerivedBarSources('avg_prewrite_time', 'max_prewrite_time'),\n  commit_time: genDerivedBarSources('avg_commit_time', 'max_commit_time'),\n  get_commit_ts_time: genDerivedBarSources(\n    'avg_get_commit_ts_time',\n    'max_get_commit_ts_time'\n  ),\n  commit_backoff_time: genDerivedBarSources(\n    'avg_commit_backoff_time',\n    'max_commit_backoff_time'\n  ),\n  resolve_lock_time: genDerivedBarSources(\n    'avg_resolve_lock_time',\n    'max_resolve_lock_time'\n  ),\n  local_latch_wait_time: genDerivedBarSources(\n    'avg_local_latch_wait_time',\n    'max_local_latch_wait_time'\n  ),\n  avg_write_size: genDerivedBarSources('avg_write_size', 'max_write_size'),\n  avg_prewrite_regions: genDerivedBarSources(\n    'avg_prewrite_regions',\n    'max_prewrite_regions'\n  ),\n  avg_txn_retry: genDerivedBarSources('avg_txn_retry', 'max_txn_retry'),\n  avg_mem: genDerivedBarSources('avg_mem', 'max_mem'),\n  avg_disk: genDerivedBarSources('avg_disk', 'max_disk'),\n  avg_mem_arbitration: genDerivedBarSources(\n    'avg_mem_arbitration',\n    'max_mem_arbitration'\n  ),\n  sum_errors: ['sum_errors', 'sum_warnings'],\n  related_schemas: ['table_names'],\n  avg_rocksdb_delete_skipped_count: genDerivedBarSources(\n    'avg_rocksdb_delete_skipped_count',\n    'max_rocksdb_delete_skipped_count'\n  ),\n  avg_rocksdb_key_skipped_count: genDerivedBarSources(\n    'avg_rocksdb_key_skipped_count',\n    'max_rocksdb_key_skipped_count'\n  ),\n  avg_rocksdb_block_cache_hit_count: genDerivedBarSources(\n    'avg_rocksdb_block_cache_hit_count',\n    'max_rocksdb_block_cache_hit_count'\n  ),\n  avg_rocksdb_block_read_count: genDerivedBarSources(\n    'avg_rocksdb_block_read_count',\n    'max_rocksdb_block_read_count'\n  ),\n  avg_rocksdb_block_read_byte: genDerivedBarSources(\n    'avg_rocksdb_block_read_byte',\n    'max_rocksdb_block_read_byte'\n  ),\n  avg_ru: genDerivedBarSources('avg_ru', 'max_ru'),\n  avg_ru_v2: genDerivedBarSources('avg_ru_v2', 'max_ru_v2'),\n  avg_time_queued_by_rc: genDerivedBarSources(\n    'avg_time_queued_by_rc',\n    'max_time_queued_by_rc'\n  ),\n  avg_ia_read_segment_count: genDerivedBarSources(\n    'avg_ia_read_segment_count',\n    'max_ia_read_segment_count'\n  ),\n  avg_ia_remote_read_segment_size: genDerivedBarSources(\n    'avg_ia_remote_read_segment_size',\n    'max_ia_remote_read_segment_size'\n  ),\n  avg_ia_remote_read_segment_wait_time: genDerivedBarSources(\n    'avg_ia_remote_read_segment_wait_time',\n    'max_ia_remote_read_segment_wait_time'\n  )\n}\n\n//////////////////////////////////////////\n\nfunction avgMinMaxLatencyColumn(\n  tcf: TableColumnFactory,\n  rows?: { max_latency?: number; min_latency?: number; avg_latency?: number }[]\n): Column {\n  return tcf.bar.multiple({ sources: derivedFields.avg_latency }, 'ns', rows)\n}\n\nfunction errorsWarningsColumn(\n  tcf: TableColumnFactory,\n  rows?: { sum_errors?: number; sum_warnings?: number }[]\n): Column {\n  const capacity = rows\n    ? max(rows.map((v) => v.sum_errors! + v.sum_warnings!)) ?? 0\n    : 0\n  const key = 'sum_errors'\n  return tcf.control({\n    name: 'errors_warnings',\n    key,\n    fieldName: key,\n    minWidth: 140,\n    maxWidth: 200,\n    columnActionsMode: ColumnActionsMode.clickable,\n    onRender: (rec) => {\n      const errorsFmtVal = formatVal(rec.sum_errors, 'short')\n      const warningsFmtVal = formatVal(rec.sum_warnings, 'short')\n      const tooltipContent = `\nErrors:   ${errorsFmtVal}\nWarnings: ${warningsFmtVal}`\n      return (\n        <Tooltip title={<Pre>{tooltipContent.trim()}</Pre>}>\n          <Bar\n            textWidth={70}\n            value={[rec.sum_errors, rec.sum_warnings]}\n            colors={[red[4], orange[4]]}\n            capacity={capacity}\n          >\n            {`${errorsFmtVal} / ${warningsFmtVal}`}\n          </Bar>\n        </Tooltip>\n      )\n    }\n  })\n}\n\n////////////////////////////////////////////////\n// util methods\n\nfunction avgMaxColumn<T>(\n  tcf: TableColumnFactory,\n  displayTransKey: string,\n  unit: string,\n  rows?: T[]\n): Column {\n  return tcf.bar.multiple(\n    {\n      displayTransKey,\n      sources: derivedFields[displayTransKey]\n    },\n    unit,\n    rows\n  )\n}\n\n////////////////////////////////////////////////\n\nexport function statementColumns(\n  rows: StatementModel[],\n  tableSchemaColumns: string[],\n  showFullSQL?: boolean\n): IColumn[] {\n  const tcf = new TableColumnFactory(TRANS_KEY_PREFIX, tableSchemaColumns)\n  return tcf.columns([\n    evictedRenderColumn(\n      tcf.sqlText('digest_text', showFullSQL, rows).getConfig()\n    ),\n    evictedRenderColumn(tcf.textWithTooltip('digest', rows).getConfig()),\n    tcf.bar.single('sum_latency', 'ns', rows),\n    avgMinMaxLatencyColumn(tcf, rows),\n    tcf.bar.single('exec_count', 'short', rows),\n    tcf.textWithTooltip('plan_count', rows).patchConfig({\n      minWidth: 100,\n      maxWidth: 300,\n      columnActionsMode: ColumnActionsMode.clickable\n    }),\n    tcf.bar.single('plan_cache_hits', 'short', rows),\n    avgMaxColumn(tcf, 'avg_mem', 'bytes', rows),\n    avgMaxColumn(tcf, 'avg_mem_arbitration', 's', rows),\n    avgMaxColumn(tcf, 'avg_disk', 'bytes', rows),\n    errorsWarningsColumn(tcf, rows),\n    avgMaxColumn(tcf, 'parse_latency', 'ns', rows),\n    avgMaxColumn(tcf, 'compile_latency', 'ns', rows),\n    tcf.bar.single('sum_cop_task_num', 'short', rows),\n    avgMaxColumn(tcf, 'process_time', 'ns', rows),\n    avgMaxColumn(tcf, 'wait_time', 'ns', rows),\n    avgMaxColumn(tcf, 'total_process_time', 'ns', rows),\n    avgMaxColumn(tcf, 'total_wait_time', 'ns', rows),\n    avgMaxColumn(tcf, 'backoff_time', 'ns', rows),\n    avgMaxColumn(tcf, 'avg_write_keys', 'short', rows),\n    avgMaxColumn(tcf, 'avg_processed_keys', 'short', rows),\n    avgMaxColumn(tcf, 'avg_total_keys', 'short', rows),\n    avgMaxColumn(tcf, 'prewrite_time', 'ns', rows),\n    avgMaxColumn(tcf, 'commit_time', 'ns', rows),\n    avgMaxColumn(tcf, 'get_commit_ts_time', 'ns', rows),\n    avgMaxColumn(tcf, 'commit_backoff_time', 'ns', rows),\n    avgMaxColumn(tcf, 'resolve_lock_time', 'ns', rows),\n    avgMaxColumn(tcf, 'local_latch_wait_time', 'ns', rows),\n    avgMaxColumn(tcf, 'avg_write_size', 'bytes', rows),\n    avgMaxColumn(tcf, 'avg_prewrite_regions', 'short', rows),\n    avgMaxColumn(tcf, 'avg_txn_retry', 'short', rows),\n\n    tcf.bar.single('sum_backoff_times', 'short', rows),\n    tcf.bar.single('avg_affected_rows', 'short', rows),\n\n    tcf.timestamp('first_seen', rows),\n    tcf.timestamp('last_seen', rows),\n    tcf.textWithTooltip('sample_user', rows),\n\n    tcf.sqlText('query_sample_text', showFullSQL, rows),\n    tcf.sqlText('prev_sample_text', showFullSQL, rows),\n\n    tcf.textWithTooltip('schema_name', rows),\n    tcf.textWithTooltip('table_names', rows),\n    tcf.textWithTooltip('index_names', rows),\n\n    tcf.textWithTooltip('plan_digest', rows),\n\n    tcf.textWithTooltip('related_schemas', rows).patchConfig({\n      minWidth: 160,\n      maxWidth: 240\n    }),\n\n    // rocksdb\n    avgMaxColumn(\n      tcf,\n      'avg_rocksdb_delete_skipped_count',\n      'short',\n      rows\n    ).patchConfig({\n      minWidth: 220,\n      maxWidth: 250\n    }),\n    avgMaxColumn(\n      tcf,\n      'avg_rocksdb_key_skipped_count',\n      'short',\n      rows\n    ).patchConfig({\n      minWidth: 220,\n      maxWidth: 250\n    }),\n    avgMaxColumn(\n      tcf,\n      'avg_rocksdb_block_cache_hit_count',\n      'short',\n      rows\n    ).patchConfig({\n      minWidth: 220,\n      maxWidth: 250\n    }),\n    avgMaxColumn(\n      tcf,\n      'avg_rocksdb_block_read_count',\n      'short',\n      rows\n    ).patchConfig({\n      minWidth: 220,\n      maxWidth: 250\n    }),\n    avgMaxColumn(tcf, 'avg_rocksdb_block_read_byte', 'bytes', rows).patchConfig(\n      {\n        minWidth: 220,\n        maxWidth: 250\n      }\n    ),\n    //resource control\n    tcf.textWithTooltip('resource_group', rows),\n    avgMaxColumn(tcf, 'avg_ru', 'none', rows),\n    tcf.textWithTooltip('sum_ru', rows).patchConfig({\n      minWidth: 100,\n      maxWidth: 300,\n      columnActionsMode: ColumnActionsMode.clickable\n    }),\n    avgMaxColumn(tcf, 'avg_ru_v2', 'none', rows),\n    tcf.textWithTooltip('sum_ru_v2', rows).patchConfig({\n      minWidth: 100,\n      maxWidth: 300,\n      columnActionsMode: ColumnActionsMode.clickable\n    }),\n    avgMaxColumn(tcf, 'avg_time_queued_by_rc', 'ns', rows),\n\n    // Network fields\n    tcf.bar.single('sum_unpacked_bytes_sent_tikv_total', 'bytes', rows),\n    tcf.bar.single('sum_unpacked_bytes_received_tikv_total', 'bytes', rows),\n    tcf.bar.single('sum_unpacked_bytes_sent_tikv_cross_zone', 'bytes', rows),\n    tcf.bar.single(\n      'sum_unpacked_bytes_received_tikv_cross_zone',\n      'bytes',\n      rows\n    ),\n    tcf.bar.single('sum_unpacked_bytes_sent_tiflash_total', 'bytes', rows),\n    tcf.bar.single('sum_unpacked_bytes_received_tiflash_total', 'bytes', rows),\n    tcf.bar.single('sum_unpacked_bytes_sent_tiflash_cross_zone', 'bytes', rows),\n    tcf.bar.single(\n      'sum_unpacked_bytes_received_tiflash_cross_zone',\n      'bytes',\n      rows\n    ),\n    avgMaxColumn(tcf, 'avg_ia_read_segment_count', 'short', rows),\n    avgMaxColumn(tcf, 'avg_ia_remote_read_segment_size', 'bytes', rows),\n    avgMaxColumn(tcf, 'avg_ia_remote_read_segment_wait_time', 'ns', rows)\n  ])\n}\n\nexport function planColumns(rows: StatementModel[]): IColumn[] {\n  const tcf = new TableColumnFactory(TRANS_KEY_PREFIX)\n\n  return tcf.columns([\n    tcf.textWithTooltip('plan_digest').patchConfig({\n      minWidth: 100,\n      maxWidth: 300\n    }),\n    tcf.bar.single('sum_latency', 'ns', rows),\n    avgMinMaxLatencyColumn(tcf, rows),\n    tcf.bar.single('exec_count', 'short', rows),\n    avgMaxColumn(tcf, 'avg_mem', 'bytes', rows)\n  ])\n}\n\nexport function evictedRenderColumn(defaultRenderColumn: IColumn): IColumn {\n  return {\n    ...defaultRenderColumn,\n    onRender: (...props) => {\n      const rec = props[0]\n      // the evicted record's digest is empty string\n      return rec.digest ? (\n        defaultRenderColumn.onRender!(...props)\n      ) : (\n        <Tooltip title=\"All of other dropped SQL statements\">\n          <i>Others</i>\n        </Tooltip>\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/utils/telemetry.ts",
    "content": "import { IColumnKeys, TimeRange } from '@lib/components'\nimport { mixpanel } from '@lib/utils/telemetry'\n\nexport const telemetry = {\n  // list\n  changeTimeRange(t: TimeRange) {\n    mixpanel.track('Statement: Change Time Range Filter', { t })\n  },\n  changeDatabases() {\n    mixpanel.track('Statement: Change Databases Filter')\n  },\n  changeStmtTypes() {\n    mixpanel.track('Statement: Change Stmt Types Filter')\n  },\n  changeSearchText() {\n    mixpanel.track('Statement: Change Search Text')\n  },\n  search() {\n    mixpanel.track('Statement: Search')\n  },\n  changeVisibleColumns(columns: IColumnKeys) {\n    mixpanel.track('Statement: Change Visible Columns', { columns })\n  },\n  toggleShowFullSQL(showFull: boolean) {\n    mixpanel.track('Statement: Toggle Show Full SQL', { showFull })\n  },\n  openSetting() {\n    mixpanel.track('Statement: Open Setting')\n  },\n  export() {\n    mixpanel.track('Statement: Export')\n  },\n  openHelp() {\n    mixpanel.track('Statement: Open Help')\n  },\n\n  // detail\n  switchDetailTab(tab: string) {\n    mixpanel.track('Statement: Switch Detail Tab', { tab })\n  },\n\n  clickPlanTabs(tab: string, queryDigest: string) {\n    mixpanel.track('Statement: Plan Tab Clicked', { tab, queryDigest })\n  },\n  toggleVisualPlanModal(action: 'open' | 'close') {\n    mixpanel.track('Statement: Visual Plan Modal Toggled', { action })\n  },\n  toggleExpandBtnOnNode(nodeName: string) {\n    mixpanel.track('Statement: Node Button Toggled', { nodeName })\n  },\n  clickNode(nodeName: string) {\n    mixpanel.track('Statement: Node Clicked', { nodeName })\n  },\n  clickTabOnNodeDetail(tab: string) {\n    mixpanel.track('Statement: Detail Tab on Node Clicked', { tab })\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/utils/useSchemaColumns.ts",
    "content": "import { useMemo } from 'react'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { AxiosPromise } from 'axios'\nimport { ReqConfig } from '@lib/types'\n\nexport function useSchemaColumns(\n  getAvaiableFields: (options?: ReqConfig) => AxiosPromise<Array<string>>\n) {\n  const { data, isLoading } = useClientRequest((options) => {\n    return getAvaiableFields(options)\n  })\n\n  const schemaColumns = useMemo(() => {\n    if (!data) {\n      return []\n    }\n    return data.map((d) => d.toLowerCase())\n  }, [data])\n\n  return {\n    schemaColumns,\n    isLoading\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/Statement/utils/useStatementTableController.ts",
    "content": "import React, { useEffect, useMemo, useState } from 'react'\nimport { useMemoizedFn, useSessionStorageState } from 'ahooks'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport { StatementModel } from '@lib/client'\nimport {\n  DEFAULT_TIME_RANGE,\n  IColumnKeys,\n  TimeRange,\n  toTimeRangeValue\n} from '@lib/components'\nimport { getSelectedFields } from '@lib/utils/tableColumnFactory'\nimport { CacheMgr } from '@lib/utils/useCache'\nimport useOrderState, { IOrderOptions } from '@lib/utils/useOrderState'\nimport useCacheItemIndex from '@lib/utils/useCacheItemIndex'\nimport { derivedFields, statementColumns } from './tableColumns'\nimport { useSchemaColumns } from './useSchemaColumns'\nimport { useChange } from '@lib/utils/useChange'\nimport { IStatementDataSource } from '../context'\n\nconst SLOW_DATA_LOAD_THRESHOLD = 2000\n\nexport const DEF_STMT_COLUMN_KEYS: IColumnKeys = {\n  digest_text: true,\n  sum_latency: true,\n  avg_latency: true,\n  exec_count: true,\n  plan_count: true\n}\n\nconst QUERY_OPTIONS = 'statement.query_options'\n\nconst DEF_ORDER_OPTIONS: IOrderOptions = {\n  orderBy: 'sum_latency',\n  desc: true\n}\n\ninterface RuntimeCacheEntity {\n  data: IStatementList\n  isDataLoadedSlowly: boolean\n}\n\nexport interface IStatementQueryOptions {\n  visibleColumnKeys: IColumnKeys\n  timeRange: TimeRange\n  schemas: string[]\n  groups: string[]\n  stmtTypes: string[]\n  searchText: string\n}\n\nexport interface IStatementList {\n  list: StatementModel[]\n  timeRange: [number, number] // Useful for sending detail requests\n}\n\nexport const DEF_STMT_QUERY_OPTIONS: IStatementQueryOptions = {\n  visibleColumnKeys: DEF_STMT_COLUMN_KEYS,\n  timeRange: DEFAULT_TIME_RANGE,\n  schemas: [],\n  groups: [],\n  stmtTypes: [],\n  searchText: ''\n}\n\nfunction useQueryOptions(\n  initial?: IStatementQueryOptions,\n  persistInSession: boolean = true\n) {\n  const [memoryQueryOptions, setMemoryQueryOptions] = useState(\n    initial || DEF_STMT_QUERY_OPTIONS\n  )\n  const [sessionQueryOptions, setSessionQueryOptions] = useSessionStorageState(\n    QUERY_OPTIONS,\n    { defaultValue: initial || DEF_STMT_QUERY_OPTIONS }\n  )\n  const queryOptions = persistInSession\n    ? sessionQueryOptions\n    : memoryQueryOptions\n  const setQueryOptions = useMemoizedFn(\n    (value: React.SetStateAction<IStatementQueryOptions>) => {\n      if (persistInSession) {\n        // as any is a workaround for https://github.com/alibaba/hooks/issues/1582\n        setSessionQueryOptions(value as any)\n      } else {\n        setMemoryQueryOptions(value)\n      }\n    }\n  )\n  return {\n    queryOptions,\n    setQueryOptions\n  }\n}\n\nexport interface IStatementTableControllerOpts {\n  cacheMgr?: CacheMgr\n  showFullSQL?: boolean\n  fetchSchemas?: boolean\n  fetchGroups?: boolean\n  fetchConfig?: boolean\n  initialQueryOptions?: IStatementQueryOptions\n  persistQueryInSession?: boolean\n\n  ds: IStatementDataSource\n}\n\nexport interface IStatementTableController {\n  queryOptions: IStatementQueryOptions\n  setQueryOptions: (value: React.SetStateAction<IStatementQueryOptions>) => void // Updating query options will result in a refresh\n\n  orderOptions: IOrderOptions\n  changeOrder: (orderBy: string, desc: boolean) => void\n  resetOrder: () => void\n\n  isEnabled: boolean // returned from backend\n  isLoading: boolean\n\n  data?: IStatementList\n  isDataLoadedSlowly: boolean | null // SLOW_DATA_LOAD_THRESHOLD. NULL = Unknown\n  allSchemas: string[]\n  allGroups: string[]\n  allStmtTypes: string[]\n  errors: Error[]\n\n  availableColumnsInTable: IColumn[] // returned from backend\n\n  saveClickedItemIndex: (idx: number) => void\n  getClickedItemIndex: () => number\n}\n\nexport default function useStatementTableController({\n  cacheMgr,\n  showFullSQL = false,\n  fetchSchemas = true,\n  fetchGroups = true,\n  fetchConfig = true,\n  initialQueryOptions,\n  persistQueryInSession = true,\n  ds\n}: IStatementTableControllerOpts): IStatementTableController {\n  const { orderOptions, changeOrder } = useOrderState(\n    'statement',\n    persistQueryInSession,\n    DEF_ORDER_OPTIONS\n  )\n  function resetOrder() {\n    changeOrder(DEF_ORDER_OPTIONS.orderBy, DEF_ORDER_OPTIONS.desc)\n  }\n\n  const { queryOptions, setQueryOptions } = useQueryOptions(\n    initialQueryOptions,\n    persistQueryInSession\n  )\n\n  const [isEnabled, setEnabled] = useState(true)\n  const [allSchemas, setAllSchemas] = useState<string[]>([])\n  const [allGroups, setAllGroups] = useState<string[]>([])\n  const [allStmtTypes, setAllStmtTypes] = useState<string[]>([])\n  const [isOptionsLoading, setOptionsLoading] = useState(true)\n  const [data, setData] = useState<IStatementList | undefined>(undefined)\n  const [isDataLoading, setDataLoading] = useState(true)\n  const [isDataLoadedSlowly, setDataLoadedSlowly] = useState<boolean | null>(\n    null\n  )\n  const [errors, setErrors] = useState<any[]>([])\n  const { schemaColumns, isLoading: isColumnsLoading } = useSchemaColumns(\n    ds.statementsAvailableFieldsGet\n  )\n\n  // By PR https://github.com/pingcap/tidb-dashboard/pull/1234 (feat: improve statement)\n  // which brings in v2022.05.16.1 and PD >=5.4.2, >=6.1.0\n  // The statement API logic changes a bit\n  // related code: https://github.com/pingcap/tidb-dashboard/pull/1234/files#diff-4bebd6011f602ac611ee19697803dc09877df197bf0176d1f27f84133b15e68bR54\n  // The new UI can't work with the old tidb-dashboard backend API well\n  // So we try to make the new UI compatible with the old tidb-dashboard backend\n  // By enlarging the selected time range with window size\n  const [windowSize, setWindowSize] = useState(0)\n  // assume the backend is old at first\n  // then update it after the first request\n  const [oldBackend, setOldBackend] = useState(true)\n\n  // check old or new backend\n  // the new backend removed the `/statements/time_ranges` API\n  // so if get 404, then it's the new backend\n  // else the old backend\n  useEffect(() => {\n    async function queryTimeRanges() {\n      try {\n        await ds.statementsTimeRangesGet({ handleError: 'custom' })\n      } catch (e) {\n        if ((e as any).response?.status === 404) {\n          setOldBackend(false)\n        }\n      }\n    }\n    queryTimeRanges()\n  }, [ds])\n\n  // Reload these options when sending a new request.\n  useChange(() => {\n    async function queryStatementStatus() {\n      if (!fetchConfig) {\n        return\n      }\n      try {\n        const res = await ds.statementsConfigGet({ handleError: 'custom' })\n        setEnabled(res?.data.enable!)\n        setWindowSize(res?.data?.refresh_interval ?? 0)\n      } catch (e) {\n        setErrors((prev) => prev.concat(e))\n      }\n    }\n\n    async function querySchemas() {\n      if (!fetchSchemas) {\n        return\n      }\n      try {\n        const res = await ds.getDatabaseList(0, 0, { handleError: 'custom' })\n        setAllSchemas(res?.data || [])\n      } catch (e) {\n        setErrors((prev) => prev.concat(e))\n      }\n    }\n\n    async function queryGroups() {\n      if (!fetchGroups) {\n        return\n      }\n      try {\n        const res = await ds.infoListResourceGroupNames({\n          handleError: 'custom'\n        })\n        setAllGroups(res?.data || [])\n      } catch (e) {\n        setErrors((prev) => prev.concat(e as Error))\n      }\n    }\n\n    async function queryStmtTypes() {\n      try {\n        const res = await ds.statementsStmtTypesGet({ handleError: 'custom' })\n        const stmtTypes = (res?.data || []).sort()\n        setAllStmtTypes(stmtTypes)\n      } catch (e) {\n        setErrors((prev) => prev.concat(e))\n      }\n    }\n\n    async function doRequest() {\n      setOptionsLoading(true)\n      try {\n        await Promise.all([\n          queryStatementStatus(),\n          querySchemas(),\n          queryGroups(),\n          queryStmtTypes()\n        ])\n      } finally {\n        setOptionsLoading(false)\n      }\n    }\n\n    doRequest()\n  }, [queryOptions])\n\n  useChange(() => {\n    async function queryStatementList() {\n      // Try cache if options are unchanged.\n      // Note: When clicking \"Query\" manually, cache will be cleared before reach here. So that it\n      // will always send a request without looking up in the cache.\n\n      // The cache key is built over queryOptions, instead of evaluated one.\n      // So that when passing in same relative times options (e.g. Recent 15min)\n      // the cache can be reused.\n      const cacheKey = JSON.stringify(queryOptions)\n      {\n        const cache = cacheMgr?.get(cacheKey)\n        if (cache) {\n          const cacheCloned = JSON.parse(\n            JSON.stringify(cache)\n          ) as RuntimeCacheEntity\n          setData(cacheCloned.data)\n          setDataLoadedSlowly(cacheCloned.isDataLoadedSlowly)\n          setDataLoading(false)\n          return\n        }\n      }\n\n      // May be caused by visibleColumnKeys is empty (when available columns are not yet loaded)\n      // In this case, we don't send any requests.\n      const actualVisibleColumnKeys = getSelectedFields(\n        queryOptions.visibleColumnKeys,\n        derivedFields\n      ).join(',')\n      if (actualVisibleColumnKeys.length === 0) {\n        return\n      }\n\n      const requestBeginAt = performance.now()\n      setDataLoading(true)\n\n      const timeRange = toTimeRangeValue(queryOptions.timeRange)\n      // enlarge the time range automatically for old tidb-dashboard backend\n      if (oldBackend) {\n        timeRange[0] -= windowSize\n        timeRange[1] += windowSize\n      }\n\n      try {\n        console.log('queryOptions', queryOptions)\n        const res = await ds.statementsListGet(\n          timeRange[0],\n          timeRange[1],\n          actualVisibleColumnKeys,\n          queryOptions.schemas,\n          queryOptions.groups,\n          queryOptions.stmtTypes,\n          queryOptions.searchText,\n          { handleError: 'custom' }\n        )\n        const data = {\n          list: res?.data || [],\n          timeRange\n        }\n        setData(data)\n        setErrors([])\n\n        const elapsed = performance.now() - requestBeginAt\n        const isLoadSlow = elapsed >= SLOW_DATA_LOAD_THRESHOLD\n        setDataLoadedSlowly(isLoadSlow)\n\n        const cacheEntity: RuntimeCacheEntity = {\n          data,\n          isDataLoadedSlowly: isLoadSlow\n        }\n        cacheMgr?.set(cacheKey, cacheEntity)\n      } catch (e) {\n        setData(undefined)\n        setErrors((prev) => prev.concat(e))\n      } finally {\n        setDataLoading(false)\n      }\n    }\n\n    queryStatementList()\n  }, [queryOptions, windowSize, oldBackend])\n\n  const availableColumnsInTable = useMemo(\n    () => statementColumns(data?.list ?? [], schemaColumns, showFullSQL),\n    [data, schemaColumns, showFullSQL]\n  )\n\n  const { saveClickedItemIndex, getClickedItemIndex } =\n    useCacheItemIndex(cacheMgr)\n\n  return {\n    queryOptions,\n    setQueryOptions,\n\n    orderOptions,\n    changeOrder,\n    resetOrder,\n\n    isEnabled,\n    isLoading: isColumnsLoading || isDataLoading || isOptionsLoading,\n\n    data,\n    isDataLoadedSlowly,\n    allSchemas,\n    allGroups,\n    allStmtTypes,\n    errors,\n\n    availableColumnsInTable,\n\n    saveClickedItemIndex,\n    getClickedItemIndex\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SystemReport/components/ReportHistory.tsx",
    "content": "import { Badge } from 'antd'\nimport dayjs from 'dayjs'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport React, { useContext, useMemo } from 'react'\nimport { useTranslation, TFunction } from 'react-i18next'\nimport { useNavigate } from 'react-router-dom'\nimport { useMemoizedFn } from 'ahooks'\n\nimport { DiagnoseReport } from '@lib/client'\nimport { CardTable, DateTime } from '@lib/components'\nimport openLink from '@lib/utils/openLink'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { SystemReportContext } from '../context'\n\nconst tableColumns = (t: TFunction): IColumn[] => [\n  {\n    name: t('system_report.list_table.id'),\n    key: 'id',\n    fieldName: 'id',\n    minWidth: 200,\n    maxWidth: 350\n  },\n  {\n    name: t('system_report.list_table.report_create_time'),\n    key: 'created_at',\n    minWidth: 100,\n    maxWidth: 200,\n    onRender: (rec: DiagnoseReport) => (\n      <DateTime.Calendar\n        unixTimestampMs={dayjs(rec.created_at).unix() * 1000}\n      />\n    )\n  },\n  {\n    name: t('system_report.list_table.status'),\n    key: 'progress',\n    minWidth: 100,\n    maxWidth: 150,\n    onRender: (rec: DiagnoseReport) => {\n      if (rec.progress! < 100) {\n        return (\n          <Badge\n            status=\"processing\"\n            text={t('system_report.list_table.status_running')}\n          />\n        )\n      } else {\n        return (\n          <Badge\n            status=\"success\"\n            text={t('system_report.list_table.status_finish')}\n          />\n        )\n      }\n    }\n  },\n  {\n    name: t('system_report.list_table.range'),\n    key: 'start_time',\n    minWidth: 200,\n    maxWidth: 350,\n    onRender: (rec: DiagnoseReport) => {\n      return (\n        <span>\n          <DateTime.Calendar\n            unixTimestampMs={dayjs(rec.start_time).unix() * 1000}\n          />{' '}\n          ~{' '}\n          <DateTime.Calendar\n            unixTimestampMs={dayjs(rec.end_time).unix() * 1000}\n          />\n        </span>\n      )\n    }\n  },\n  {\n    name: t('system_report.list_table.compare_range'),\n    key: 'compare_start_time',\n    minWidth: 200,\n    maxWidth: 350,\n    onRender: (rec: DiagnoseReport) =>\n      rec.compare_start_time && (\n        <span>\n          <DateTime.Calendar\n            unixTimestampMs={dayjs(rec.compare_start_time).unix() * 1000}\n          />{' '}\n          ~{' '}\n          <DateTime.Calendar\n            unixTimestampMs={dayjs(rec.compare_end_time).unix() * 1000}\n          />\n        </span>\n      )\n  }\n]\n\nexport default function ReportHistory() {\n  const ctx = useContext(SystemReportContext)\n\n  const navigate = useNavigate()\n  const { t } = useTranslation()\n  const { data, isLoading, error } = useClientRequest(\n    ctx!.ds.diagnoseReportsGet\n  )\n  const columns = useMemo(() => tableColumns(t), [t])\n\n  const handleRowClick = useMemoizedFn(\n    (rec, _idx, ev: React.MouseEvent<HTMLElement>) => {\n      openLink(`/system_report/detail?id=${rec.id}`, ev, navigate)\n    }\n  )\n\n  return (\n    <CardTable\n      cardNoMarginTop\n      loading={isLoading}\n      items={data || []}\n      columns={columns}\n      errors={[error]}\n      onRowClicked={handleRowClick}\n    />\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SystemReport/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport {\n  DiagnoseReport,\n  DiagnoseGenerateReportRequest,\n  DiagnoseGenerateMetricsRelationRequest\n} from '@lib/client'\n\nimport { IContextConfig, ReqConfig } from '@lib/types'\n\nexport interface ISystemReportDataSource {\n  diagnoseReportsGet(options?: ReqConfig): AxiosPromise<Array<DiagnoseReport>>\n\n  diagnoseReportsPost(\n    request: DiagnoseGenerateReportRequest,\n    options?: ReqConfig\n  ): AxiosPromise<number>\n\n  diagnoseGenerateMetricsRelationship(\n    request: DiagnoseGenerateMetricsRelationRequest,\n    options?: ReqConfig\n  ): AxiosPromise<string>\n\n  diagnoseReportsIdStatusGet(\n    id: string,\n    options?: ReqConfig\n  ): AxiosPromise<DiagnoseReport>\n}\n\nexport interface ISystemReportConfig extends IContextConfig {\n  publicPathBase: string\n\n  fullReportLink(reportId: string): string\n}\n\nexport interface ISystemReportContext {\n  ds: ISystemReportDataSource\n  cfg: ISystemReportConfig\n}\n\nexport const SystemReportContext = createContext<ISystemReportContext | null>(\n  null\n)\n\nexport const SystemReportProvider = SystemReportContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SystemReport/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { HashRouter as Router, Route, Routes } from 'react-router-dom'\n\nimport { ParamsPageWrapper, Root } from '@lib/components'\nimport { ReportGenerator, ReportStatus } from './pages'\n\nimport translations from './translations'\nimport { addTranslations } from '@lib/utils/i18n'\nimport { SystemReportContext } from './context'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/system_report\" element={<ReportGenerator />} />\n      <Route\n        path=\"/system_report/detail\"\n        element={\n          <ParamsPageWrapper>\n            <ReportStatus />\n          </ParamsPageWrapper>\n        }\n      />\n    </Routes>\n  )\n}\n\nconst App = () => {\n  const ctx = useContext(SystemReportContext)\n  if (ctx === null) {\n    throw new Error('SystemReportÇontext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport default App\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SystemReport/pages/ReportGenerator.tsx",
    "content": "import { Button, Form, Input, InputNumber, Select, Switch, Modal } from 'antd'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport React, { useState, useCallback, useContext } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useNavigate } from 'react-router-dom'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\n\nimport { Card, Pre, DatePicker } from '@lib/components'\n\nimport ReportHistory from '../components/ReportHistory'\nimport { ISystemReportDataSource, SystemReportContext } from '../context'\nimport { useIsWriteable } from '@lib/utils'\n\nconst useFinishHandler = (\n  navigate,\n  genReport: ISystemReportDataSource['diagnoseReportsPost']\n) => {\n  return async (fieldsValue) => {\n    const start_time = fieldsValue['rangeBegin'].unix()\n    let range_duration = fieldsValue['rangeDuration']\n    if (fieldsValue['rangeDuration'] === 0) {\n      range_duration = fieldsValue['rangeDurationCustom']\n    }\n    const is_compare = fieldsValue['isCompare']\n    const compare_range_begin = fieldsValue['compareRangeBegin']\n\n    const end_time = start_time + range_duration * 60\n    const compare_start_time = is_compare ? compare_range_begin.unix() : 0\n    const compare_end_time = is_compare\n      ? compare_start_time + range_duration * 60\n      : 0\n\n    const res = await genReport({\n      start_time,\n      end_time,\n      compare_start_time,\n      compare_end_time\n    })\n    navigate(`/system_report/detail?id=${res.data}`)\n  }\n}\n\nconst DURATIONS = [5, 10, 30, 60, 24 * 60]\n\nexport default function ReportGenerator() {\n  const ctx = useContext(SystemReportContext)\n\n  const { t } = useTranslation()\n  const navigate = useNavigate()\n  const handleFinish = useFinishHandler(navigate, ctx!.ds.diagnoseReportsPost)\n\n  const isWriteable = useIsWriteable()\n\n  const [form] = Form.useForm()\n  const [isGenerateRelationPosting, setGenerateRelationPosting] =\n    useState(false)\n\n  const handleMetricsRelation = useCallback(async () => {\n    try {\n      await form.validateFields()\n    } catch (e) {\n      return\n    }\n\n    const fieldsValue = form.getFieldsValue()\n\n    const start_time = fieldsValue['rangeBegin'].unix()\n    let range_duration = fieldsValue['rangeDuration']\n    if (fieldsValue['rangeDuration'] === 0) {\n      range_duration = fieldsValue['rangeDurationCustom']\n    }\n    const end_time = start_time + range_duration * 60\n\n    try {\n      setGenerateRelationPosting(true)\n\n      const resp = await ctx!.ds.diagnoseGenerateMetricsRelationship({\n        start_time,\n        end_time,\n        type: 'sum'\n      })\n      Modal.success({\n        title: t('system_report.metrics_relation.success.title'),\n        okText: t('system_report.metrics_relation.success.button'),\n        okButtonProps: {\n          target: '_blank',\n          href:\n            `${ctx!.cfg.apiPathBase}/diagnose/metrics_relation/view?token=` +\n            encodeURIComponent(resp.data)\n        }\n      })\n    } catch (e) {\n      const err = e as any\n      Modal.error({\n        title: 'Error',\n        content: <Pre>{err?.response?.data?.message ?? err.message}</Pre>\n      })\n    }\n\n    setGenerateRelationPosting(false)\n  }, [t, form, ctx])\n\n  return (\n    <div style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}>\n      <Card title={t('system_report.generate.title')}>\n        <Form\n          style={{ rowGap: 16 }}\n          form={form}\n          layout=\"inline\"\n          onFinish={handleFinish}\n          initialValues={{ rangeDuration: 10, rangeDurationCustom: 10 }}\n        >\n          <Form.Item\n            name=\"rangeBegin\"\n            rules={[{ required: true }]}\n            label={t('system_report.generate.range_begin')}\n          >\n            <DatePicker showTime />\n          </Form.Item>\n          <Form.Item\n            label={t('system_report.generate.range_duration')}\n            required\n          >\n            <Input.Group compact>\n              <Form.Item\n                name=\"rangeDuration\"\n                rules={[{ required: true }]}\n                noStyle\n              >\n                <Select style={{ width: 120 }}>\n                  {DURATIONS.map((val) => (\n                    <Select.Option key={val} value={val}>\n                      {getValueFormat('m')(val, 0)}\n                    </Select.Option>\n                  ))}\n                  <Select.Option value={0}>\n                    {t('system_report.time_duration.custom')}\n                  </Select.Option>\n                </Select>\n              </Form.Item>\n              <Form.Item\n                noStyle\n                shouldUpdate={(prev, cur) =>\n                  prev.rangeDuration !== cur.rangeDuration\n                }\n              >\n                {({ getFieldValue }) => {\n                  return (\n                    getFieldValue('rangeDuration') === 0 && (\n                      <Form.Item\n                        noStyle\n                        name=\"rangeDurationCustom\"\n                        rules={[{ required: true }]}\n                      >\n                        <InputNumber\n                          min={1}\n                          max={30 * 24 * 60}\n                          formatter={(value) => `${value} min`}\n                          parser={(value) =>\n                            parseInt(value?.replace(/[^\\d]/g, '') || '')\n                          }\n                          style={{ width: 120 }}\n                        />\n                      </Form.Item>\n                    )\n                  )\n                }}\n              </Form.Item>\n            </Input.Group>\n          </Form.Item>\n          <Form.Item\n            name=\"isCompare\"\n            valuePropName=\"checked\"\n            label={t('system_report.generate.is_compare')}\n          >\n            <Switch />\n          </Form.Item>\n          <Form.Item\n            noStyle\n            shouldUpdate={(prev, cur) => prev.isCompare !== cur.isCompare}\n          >\n            {({ getFieldValue }) => {\n              return (\n                getFieldValue('isCompare') && (\n                  <Form.Item\n                    name=\"compareRangeBegin\"\n                    rules={[{ required: true }]}\n                    label={t('system_report.generate.compare_range_begin')}\n                  >\n                    <DatePicker showTime />\n                  </Form.Item>\n                )\n              )\n            }}\n          </Form.Item>\n          <Form.Item>\n            <Button type=\"primary\" htmlType=\"submit\" disabled={!isWriteable}>\n              {t('system_report.generate.submit')}\n            </Button>\n          </Form.Item>\n          <Form.Item>\n            <Button\n              onClick={handleMetricsRelation}\n              loading={isGenerateRelationPosting}\n              disabled={!isWriteable}\n            >\n              {t('system_report.generate.metrics_relation')}\n            </Button>\n          </Form.Item>\n        </Form>\n      </Card>\n\n      <div style={{ height: '100%', position: 'relative' }}>\n        <ScrollablePane>\n          <ReportHistory />\n        </ScrollablePane>\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SystemReport/pages/ReportStatus.tsx",
    "content": "import { Button, Descriptions, Progress } from 'antd'\nimport React, { useContext } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { Link } from 'react-router-dom'\nimport { ArrowLeftOutlined } from '@ant-design/icons'\n\nimport { AnimatedSkeleton, DateTime, ErrorBar, Head } from '@lib/components'\nimport { useClientRequestWithPolling } from '@lib/utils/useClientRequest'\nimport useQueryParams from '@lib/utils/useQueryParams'\nimport { SystemReportContext } from '../context'\n\nfunction ReportStatus() {\n  const ctx = useContext(SystemReportContext)\n\n  const { id } = useQueryParams()\n  const { t } = useTranslation()\n\n  const {\n    data: report,\n    isLoading,\n    error\n  } = useClientRequestWithPolling(\n    (reqConfig) => ctx!.ds.diagnoseReportsIdStatusGet(id, reqConfig),\n    {\n      shouldPoll: (data) => data?.progress! < 100\n    }\n  )\n\n  return (\n    <Head\n      title={t('system_report.status.head.title')}\n      back={\n        <Link to={`/system_report`}>\n          <ArrowLeftOutlined /> {t('system_report.status.head.back')}\n        </Link>\n      }\n      titleExtra={\n        report && (\n          <Button type=\"primary\" disabled={report?.progress! < 100}>\n            {/* Not using client basePath intentionally so that it can be handled by dev server */}\n            <a\n              // href={`${ctx!.cfg.publicPathBase}/api/diagnose/reports/${\n              //   report.id\n              // }/detail`}\n              href={ctx!.cfg.fullReportLink(report.id!)}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n            >\n              {t('system_report.status.head.view')}\n            </a>\n          </Button>\n        )\n      }\n    >\n      <AnimatedSkeleton showSkeleton={isLoading && !report}>\n        {error && <ErrorBar errors={[error]} />}\n        {report && (\n          <Descriptions column={1} bordered size=\"small\">\n            <Descriptions.Item label={t('system_report.status.range_begin')}>\n              <DateTime.Calendar\n                unixTimestampMs={new Date(report.start_time!).valueOf()}\n              />\n            </Descriptions.Item>\n            <Descriptions.Item label={t('system_report.status.range_end')}>\n              <DateTime.Calendar\n                unixTimestampMs={new Date(report.end_time!).valueOf()}\n              />\n            </Descriptions.Item>\n            {report.compare_start_time && (\n              <Descriptions.Item\n                label={t('system_report.status.baseline_begin')}\n              >\n                <DateTime.Calendar\n                  unixTimestampMs={new Date(\n                    report.compare_start_time\n                  ).valueOf()}\n                />\n              </Descriptions.Item>\n            )}\n            <Descriptions.Item label={t('system_report.status.progress')}>\n              <Progress style={{ width: 200 }} percent={report.progress || 0} />\n            </Descriptions.Item>\n          </Descriptions>\n        )}\n      </AnimatedSkeleton>\n    </Head>\n  )\n}\n\nexport default ReportStatus\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SystemReport/pages/index.ts",
    "content": "import ReportGenerator from './ReportGenerator'\nimport ReportStatus from './ReportStatus'\n\nexport { ReportGenerator, ReportStatus }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SystemReport/translations/en.yaml",
    "content": "system_report:\n  nav_title: Cluster Diagnostics\n  generate:\n    title: New Diagnostic Report\n    range_begin: Range Start Time\n    range_duration: Range Duration\n    is_compare: Compare by Baseline\n    compare_range_begin: Baseline Range Start Time\n    submit: Start\n    metrics_relation: Generate Metrics Relation\n  list_table:\n    id: Report ID\n    report_create_time: Diagnose At\n    status: Status\n    status_running: Running\n    status_finish: Finish\n    range: Range\n    compare_range: Baseline Range\n  status:\n    head:\n      title: Diagnostic Status\n      back: New Diagnostic Report\n      view: View Full Report\n    range_begin: Range Start Time\n    range_end: Range End Time\n    baseline_begin: Baseline Range Start Time\n    progress: Progress\n  time_duration:\n    custom: Custom\n  metrics_relation:\n    success:\n      title: Generate metrics relation graph successfully\n      button: View Graph\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SystemReport/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/SystemReport/translations/zh.yaml",
    "content": "system_report:\n  nav_title: 集群诊断\n  generate:\n    title: 生成诊断报告\n    range_begin: 区间起始时间\n    range_duration: 区间长度\n    is_compare: 与基线区间对比\n    compare_range_begin: 基线区间起始时间\n    submit: 开始\n    metrics_relation: 生成监控关系图\n  list_table:\n    id: 报告 ID\n    report_create_time: 诊断时间\n    status: 状态\n    status_running: 诊断中\n    status_finish: 完成\n    range: 诊断区间\n    compare_range: 对比区间\n  status:\n    head:\n      title: 诊断状态\n      back: 生成诊断报告\n      view: 查看完整诊断报告\n    range_begin: 区间起始时间\n    range_end: 区间结束时间\n    baseline_begin: 基线区间起始时间\n    progress: 生成进度\n  time_duration:\n    custom: 自定义\n  metrics_relation:\n    success:\n      title: 监控耗时关系图生成成功\n      button: 查看关系图\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/components/Filter/InstanceSelect.tsx",
    "content": "import React, { useMemo } from 'react'\nimport { Select } from 'antd'\n\nimport { TopsqlInstanceItem } from '@lib/client'\n\nimport commonStyles from './common.module.less'\n\ninterface InstanceGroup {\n  name: string\n  instances: TopsqlInstanceItem[]\n}\n\nexport interface InstanceSelectProps {\n  value: TopsqlInstanceItem | null\n  onChange: (instance: TopsqlInstanceItem) => void\n  instances: TopsqlInstanceItem[]\n  disabled?: boolean\n  onDropdownVisibleChange?: (visible: boolean) => void\n}\n\nconst splitter = ' - '\n\nconst combineSelectValue = (item: TopsqlInstanceItem | null) => {\n  if (!item) {\n    return ''\n  }\n  return `${item.instance_type}${splitter}${item.instance}`\n}\n\nconst splitSelectValue = (v: string): TopsqlInstanceItem => {\n  const [instance_type, instance] = v.split(splitter)\n  return { instance, instance_type }\n}\n\nexport function InstanceSelect({\n  value,\n  onChange,\n  instances,\n  disabled = false,\n  ...otherProps\n}: InstanceSelectProps) {\n  const instanceGroups: InstanceGroup[] = useMemo(() => {\n    if (!instances) {\n      return []\n    }\n\n    // Depend on the ordered instances\n    return instances.reduce((prev, instance) => {\n      const lastGroup = prev[prev.length - 1]\n      if (!lastGroup || lastGroup.name !== instance.instance_type) {\n        prev.push({ name: instance.instance_type!, instances: [instance] })\n        return prev\n      }\n\n      lastGroup.instances.push(instance)\n      return prev\n    }, [] as InstanceGroup[])\n  }, [instances])\n\n  return (\n    <Select\n      style={{ minWidth: 200 }}\n      placeholder=\"Select Instance\"\n      value={combineSelectValue(value)}\n      onChange={(value) => {\n        const instance = splitSelectValue(value)\n        onChange(instance)\n      }}\n      disabled={disabled}\n      data-e2e=\"instance-selector\"\n      {...otherProps}\n    >\n      {instanceGroups.map((instanceGroup) => (\n        <Select.OptGroup label={instanceGroup.name} key={instanceGroup.name}>\n          {instanceGroup.instances.map((item) => (\n            <Select.Option\n              className={commonStyles.select_option}\n              value={combineSelectValue(item)}\n              key={item.instance}\n            >\n              <span className={commonStyles.hide}>{instanceGroup.name} - </span>\n              {item.instance}\n            </Select.Option>\n          ))}\n        </Select.OptGroup>\n      ))}\n    </Select>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/components/Filter/common.module.less",
    "content": "// A hack displays content in selection area, but not in options\n.select_option {\n  .hide {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/components/Filter/index.ts",
    "content": "export * from './InstanceSelect'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport {\n  RestErrorResponse,\n  TopsqlEditableConfig,\n  TopsqlInstanceResponse,\n  TopsqlSummaryResponse\n} from '@lib/client'\n\nimport { ReqConfig } from '@lib/types'\n\nexport interface TopsqlTikvNetworkIoCollectionConfig {\n  /**\n   * Whether enable TiKV network IO collection (resource-metering.enable-network-io-collection)\n   */\n  enable: boolean\n  /**\n   * Whether values are not identical across TiKV nodes\n   */\n  is_multi_value?: boolean\n}\n\nexport interface TopsqlTikvNetworkIoCollectionUpdateResponse {\n  warnings: RestErrorResponse[]\n}\n\nexport interface ITopSQLDataSource {\n  topsqlConfigGet(options?: ReqConfig): AxiosPromise<TopsqlEditableConfig>\n\n  topsqlConfigPost(\n    request: TopsqlEditableConfig,\n    options?: ReqConfig\n  ): AxiosPromise<string>\n\n  topsqlTikvNetworkIoCollectionGet(\n    options?: ReqConfig\n  ): AxiosPromise<TopsqlTikvNetworkIoCollectionConfig>\n\n  topsqlTikvNetworkIoCollectionPost(\n    request: TopsqlTikvNetworkIoCollectionConfig,\n    options?: ReqConfig\n  ): AxiosPromise<TopsqlTikvNetworkIoCollectionUpdateResponse>\n\n  topsqlInstancesGet(\n    end?: string,\n    start?: string,\n    dataSource?: string,\n    options?: ReqConfig\n  ): AxiosPromise<TopsqlInstanceResponse>\n\n  topsqlSummaryGet(\n    end?: string,\n    groupBy?: string,\n    instance?: string,\n    instanceType?: string,\n    orderBy?: string,\n    start?: string,\n    top?: string,\n    window?: string,\n    dataSource?: string,\n    options?: ReqConfig\n  ): AxiosPromise<TopsqlSummaryResponse>\n}\n\nexport interface ITopSQLConfig {\n  checkNgm: boolean\n  showSetting: boolean\n\n  // to limit the time range picker range\n  timeRangeSelector?: {\n    recentSeconds: number[]\n    customAbsoluteRangePicker: boolean\n    timeRangeLimit?: number\n  }\n  autoRefresh?: boolean\n\n  // for clinic\n  orgName?: string\n  clusterName?: string\n  userName?: string\n\n  showSearchInStatements?: boolean\n  showLimit?: boolean\n  showGroupBy?: boolean\n  showGroupByRegion?: boolean\n  showOrderBy?: boolean\n  minWindowInterval?: number\n  dataSource?: string\n}\n\nexport interface ITopSQLContext {\n  ds: ITopSQLDataSource\n  cfg: ITopSQLConfig\n}\n\nexport const TopSQLContext = createContext<ITopSQLContext | null>(null)\n\nexport const TopSQLProvider = TopSQLContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { HashRouter as Router, Routes, Route } from 'react-router-dom'\n\nimport { Root, NgmNotStartedGuard } from '@lib/components'\nimport { addTranslations } from '@lib/utils/i18n'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\n\nimport { TopSQLList } from './pages/List/List'\nimport { TopSQLContext } from './context'\nimport translations from './translations'\n\naddTranslations(translations)\n\nfunction AppRoutes() {\n  const ctx = useContext(TopSQLContext)\n\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route\n        path=\"/topsql\"\n        element={\n          ctx?.cfg.checkNgm ? (\n            <NgmNotStartedGuard>\n              <TopSQLList />\n            </NgmNotStartedGuard>\n          ) : (\n            <TopSQLList />\n          )\n        }\n      />\n    </Routes>\n  )\n}\n\nexport default function () {\n  const ctx = useContext(TopSQLContext)\n  if (ctx === null) {\n    throw new Error('TopSQLContext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/List.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.container {\n  display: flex;\n  height: 100%;\n  flex-direction: column;\n  .chart_container {\n    margin: 24px 24px 24px 48px;\n    height: 200px;\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/List.tsx",
    "content": "import { BrushEndListener, BrushEvent } from '@elastic/charts'\nimport React, {\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n  useMemo\n} from 'react'\nimport {\n  Select,\n  Space,\n  Button,\n  Spin,\n  Alert,\n  Tooltip,\n  Drawer,\n  Result\n} from 'antd'\nimport {\n  LoadingOutlined,\n  QuestionCircleOutlined,\n  SettingOutlined\n} from '@ant-design/icons'\nimport { useTranslation } from 'react-i18next'\nimport { useMount, useSessionStorage } from 'react-use'\nimport { useMemoizedFn } from 'ahooks'\nimport { sortBy } from 'lodash'\nimport formatSql from '@lib/utils/sqlFormatter'\nimport {\n  TopsqlInstanceItem,\n  TopsqlSummaryByItem,\n  TopsqlSummaryItem,\n  TopsqlSummaryResponse\n} from '@lib/client'\nimport {\n  Card,\n  toTimeRangeValue as _toTimeRangeValue,\n  Toolbar,\n  AutoRefreshButton,\n  TimeRange,\n  fromTimeRangeValue,\n  TimeRangeValue,\n  LimitTimeRange\n} from '@lib/components'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\n\nimport { telemetry } from '../../utils/telemetry'\nimport { InstanceSelect } from '../../components/Filter'\nimport styles from './List.module.less'\nimport { ListTable } from './ListTable'\nimport { ListChart } from './ListChart'\nimport { SettingsForm } from './SettingsForm'\nimport { onLegendItemOver, onLegendItemOut } from './legendAction'\nimport { InstanceType } from './ListDetail/ListDetailTable'\nimport { isDistro } from '@lib/utils/distro'\nimport { TopSQLContext } from '../../context'\nimport { useURLTimeRange } from '@lib/hooks/useURLTimeRange'\nimport { useQueryParams } from '@lib/hooks/useQueryParams'\n\nconst { Option } = Select\nconst CHART_BAR_WIDTH = 8\nconst RECENT_RANGE_OFFSET = -60\nconst LIMITS = [5, 20, 100]\n\nexport enum AggLevel {\n  Query = 'query',\n  Table = 'table',\n  Schema = 'db',\n  Region = 'region'\n}\n\nexport enum OrderBy {\n  CpuTime = 'cpu',\n  NetworkBytes = 'network',\n  LogicalIoBytes = 'logical_io'\n}\n\nconst formatLabel = (item: AggLevel): string => {\n  if (item === AggLevel.Schema) return 'DB' // Special case for 'db'\n  return item.charAt(0).toUpperCase() + item.slice(1) // Capitalize first letter\n}\n\nconst formatOrderByLabel = (item: OrderBy): string => {\n  const labels: Record<OrderBy, string> = {\n    [OrderBy.CpuTime]: 'CPU',\n    [OrderBy.NetworkBytes]: 'Network',\n    [OrderBy.LogicalIoBytes]: 'Logical IO'\n  }\n  return labels[item] || item\n}\n\nconst GROUP = [AggLevel.Query, AggLevel.Table, AggLevel.Schema, AggLevel.Region]\n\nconst toTimeRangeValue: typeof _toTimeRangeValue = (v) => {\n  return _toTimeRangeValue(v, v?.type === 'recent' ? RECENT_RANGE_OFFSET : 0)\n}\n\nexport function TopSQLList() {\n  const ctx = useContext(TopSQLContext)\n  const { t } = useTranslation()\n  const canOpenSettings = ctx?.cfg.showSetting !== false\n  const { topSQLConfig, isConfigLoading, updateConfig, haveHistoryData } =\n    useTopSQLConfig()\n  const [showSettings, setShowSettings] = useState(false)\n  const [instance, setInstance] = useSessionStorage<TopsqlInstanceItem | null>(\n    'topsql.instance',\n    null\n  )\n  const { queryParams, setQueryParams } = useQueryParams<{\n    instance: string\n  }>({\n    instance: ''\n  })\n  const { timeRange, setTimeRange } = useURLTimeRange()\n  const [limit, setLimit] = useState(5)\n  const [groupBy, setGroupBy] = useState(AggLevel.Query)\n  const [orderBy, setOrderBy] = useState(OrderBy.CpuTime)\n  const [timeWindowSize, setTimeWindowSize] = useState(0)\n  const containerRef = useRef<HTMLDivElement>(null)\n  const computeTimeWindowSize = useMemoizedFn(\n    ([min, max]: TimeRangeValue): number => {\n      const screenWidth = containerRef.current?.offsetWidth || 0\n      const windowSize = Math.ceil(\n        (CHART_BAR_WIDTH * (max - min)) / screenWidth\n      )\n      const finalWindowSize =\n        ctx?.cfg.minWindowInterval !== undefined\n          ? Math.max(windowSize, ctx.cfg.minWindowInterval)\n          : windowSize\n      setTimeWindowSize(finalWindowSize)\n      return finalWindowSize\n    }\n  )\n  const {\n    topSQLData,\n    isLoading: isDataLoading,\n    updateTopSQLData\n  } = useTopSQLData(\n    instance,\n    timeRange,\n    limit,\n    groupBy,\n    orderBy,\n    computeTimeWindowSize\n  )\n  const isLoading = isConfigLoading || isDataLoading\n  const {\n    instances,\n    isLoading: isInstancesLoading,\n    fetchInstances\n  } = useInstances(timeRange)\n  const {\n    data: tikvNetworkIoCollection,\n    isLoading: isTikvNetworkIoCollectionLoading,\n    sendRequest: refreshTikvNetworkIoCollection\n  } = useClientRequest(ctx!.ds.topsqlTikvNetworkIoCollectionGet, {\n    immediate: false\n  })\n\n  const handleBrushEnd: BrushEndListener = useCallback(\n    (v: BrushEvent) => {\n      if (!v.x) {\n        return\n      }\n\n      let value: [number, number]\n      const tr = v.x.map((d) => d / 1000)\n      const delta = tr[1] - tr[0]\n      if (delta < 60) {\n        const offset = Math.floor(delta / 2)\n        value = [\n          Math.ceil(tr[0] + offset - 30),\n          Math.floor(tr[1] - offset + 30)\n        ]\n      } else {\n        value = [Math.ceil(tr[0]), Math.floor(tr[1])]\n      }\n\n      setTimeRange(fromTimeRangeValue(value))\n      telemetry.dndZoomIn(value)\n    },\n    [setTimeRange]\n  )\n\n  const fetchInstancesAndSelectInstance = useMemoizedFn(async () => {\n    const instances = await fetchInstances(timeRange)\n    const instanceFromURL = queryParams.instance\n\n    if (instanceFromURL) {\n      const instance = instances.find(\n        (instance) => instance.instance === instanceFromURL\n      )\n      if (instance) {\n        setInstance(instance)\n        return\n      }\n    }\n\n    // Select the first instance if there not instance selected\n    if (!!instance) {\n      return\n    }\n    setInstance(instances[0])\n  })\n\n  useMount(() => {\n    fetchInstancesAndSelectInstance()\n  })\n\n  const chartRef = useRef<any>(null)\n\n  // only for clinic\n  const clusterInfo = useMemo(() => {\n    const infos: string[] = []\n    if (ctx?.cfg.orgName) {\n      infos.push(`Org: ${ctx?.cfg.orgName}`)\n    }\n    if (ctx?.cfg.clusterName) {\n      infos.push(`Cluster: ${ctx?.cfg.clusterName}`)\n    }\n    return infos.join(' | ')\n  }, [ctx?.cfg.orgName, ctx?.cfg.clusterName])\n\n  const shouldCheckNetworkIoCollection =\n    canOpenSettings &&\n    instance?.instance_type === 'tikv' &&\n    (orderBy === OrderBy.NetworkBytes ||\n      orderBy === OrderBy.LogicalIoBytes ||\n      groupBy === AggLevel.Region)\n  const shouldShowNetworkIoTip =\n    shouldCheckNetworkIoCollection &&\n    !isTikvNetworkIoCollectionLoading &&\n    (tikvNetworkIoCollection?.enable === false ||\n      tikvNetworkIoCollection?.is_multi_value === true)\n  const networkIoTipBody =\n    tikvNetworkIoCollection?.is_multi_value === true\n      ? t('topsql.tikv_network_io_collection_tip.body_partial')\n      : t('topsql.tikv_network_io_collection_tip.body')\n\n  useEffect(() => {\n    if (shouldCheckNetworkIoCollection) {\n      refreshTikvNetworkIoCollection()\n    }\n  }, [shouldCheckNetworkIoCollection, refreshTikvNetworkIoCollection])\n\n  return (\n    <>\n      <div className={styles.container} ref={containerRef}>\n        {/* Show \"not enabled\" Alert when there are historical data */}\n        {!isConfigLoading && !topSQLConfig?.enable && haveHistoryData && (\n          <Card noMarginBottom>\n            <Alert\n              data-e2e=\"topsql_not_enabled_alert\"\n              message={t(`topsql.alert_header.title`)}\n              description={\n                canOpenSettings ? (\n                  <>\n                    {t(`topsql.alert_header.body`)}\n                    {` `}\n                    <a\n                      onClick={() => {\n                        setShowSettings(true)\n                        telemetry.clickSettings('bannerTips')\n                      }}\n                    >\n                      {t('topsql.alert_header.settings')}\n                    </a>\n                  </>\n                ) : (\n                  t(`topsql.alert_header.body`)\n                )\n              }\n              type=\"info\"\n              showIcon\n            />\n          </Card>\n        )}\n\n        <Card noMarginBottom>\n          {clusterInfo && (\n            <div style={{ marginBottom: 8, textAlign: 'right' }}>\n              {clusterInfo}\n            </div>\n          )}\n\n          <Toolbar>\n            <Space>\n              <InstanceSelect\n                value={instance}\n                onChange={(inst) => {\n                  setInstance(inst)\n                  if (!!inst?.instance) {\n                    setQueryParams({ instance: inst.instance })\n                  }\n                  if (inst) {\n                    telemetry.finishSelectInstance(inst?.instance_type!)\n                  }\n                  // only group by sql when instance is not tikv\n                  if (inst?.instance_type !== 'tikv') {\n                    setGroupBy(AggLevel.Query)\n                  }\n                  // Reset orderBy if current selection is not supported by new instance type\n                  if (\n                    inst?.instance_type !== 'tikv' &&\n                    orderBy === OrderBy.LogicalIoBytes\n                  ) {\n                    setOrderBy(OrderBy.CpuTime)\n                  }\n                }}\n                instances={instances}\n                disabled={isLoading || isInstancesLoading}\n                onDropdownVisibleChange={(open) =>\n                  open && telemetry.openSelectInstance()\n                }\n              />\n              <LimitTimeRange\n                value={timeRange}\n                recent_seconds={ctx?.cfg.timeRangeSelector?.recentSeconds}\n                customAbsoluteRangePicker={\n                  ctx?.cfg.timeRangeSelector?.customAbsoluteRangePicker\n                }\n                timeRangeLimit={ctx?.cfg.timeRangeSelector?.timeRangeLimit}\n                onChange={(v) => {\n                  setTimeRange(v)\n                  telemetry.selectTimeRange(v)\n                }}\n                onZoomOutClick={(start, end) =>\n                  telemetry.clickZoomOut([start, end])\n                }\n                disabled={isLoading}\n              />\n              {ctx?.cfg.showLimit && (\n                <Select\n                  style={{ width: 150 }}\n                  value={limit}\n                  onChange={setLimit}\n                  data-e2e=\"limit_select\"\n                >\n                  {LIMITS.map((item) => (\n                    <Option value={item} key={item} data-e2e=\"limit_option\">\n                      Limit {item}\n                    </Option>\n                  ))}\n                </Select>\n              )}\n              {ctx?.cfg.showGroupBy && instance?.instance_type === 'tikv' && (\n                <Select\n                  style={{ width: 150 }}\n                  value={groupBy}\n                  onChange={setGroupBy}\n                  data-e2e=\"group_select\"\n                >\n                  {GROUP.filter((item) => {\n                    // Only show Region option when showGroupByRegion is true\n                    // (instance?.instance_type === 'tikv' is already checked in outer condition)\n                    if (item === AggLevel.Region) {\n                      return ctx?.cfg.showGroupByRegion === true\n                    }\n                    return true\n                  }).map((item) => (\n                    <Option value={item} key={item} data-e2e=\"group_option\">\n                      By {formatLabel(item)}\n                    </Option>\n                  ))}\n                </Select>\n              )}\n              {ctx?.cfg.showOrderBy && instance && (\n                <Select\n                  style={{ width: 150 }}\n                  value={orderBy}\n                  onChange={setOrderBy}\n                  data-e2e=\"order_by_select\"\n                >\n                  <Option\n                    value={OrderBy.CpuTime}\n                    key={OrderBy.CpuTime}\n                    data-e2e=\"order_by_option_cpu_time\"\n                  >\n                    Order By {formatOrderByLabel(OrderBy.CpuTime)}\n                  </Option>\n                  <Option\n                    value={OrderBy.NetworkBytes}\n                    key={OrderBy.NetworkBytes}\n                    data-e2e=\"order_by_option_network_bytes\"\n                  >\n                    Order By {formatOrderByLabel(OrderBy.NetworkBytes)}\n                  </Option>\n                  {instance.instance_type === 'tikv' && (\n                    <Option\n                      value={OrderBy.LogicalIoBytes}\n                      key={OrderBy.LogicalIoBytes}\n                      data-e2e=\"order_by_option_logical_io_bytes\"\n                    >\n                      Order By {formatOrderByLabel(OrderBy.LogicalIoBytes)}\n                    </Option>\n                  )}\n                </Select>\n              )}\n\n              <AutoRefreshButton\n                defaultValue={ctx?.cfg.autoRefresh === false ? 0 : undefined}\n                disabled={isLoading}\n                onRefresh={async () => {\n                  await fetchInstancesAndSelectInstance()\n                  updateTopSQLData(instance, timeRange, limit)\n                }}\n              />\n              {isLoading && (\n                <Spin\n                  indicator={<LoadingOutlined style={{ fontSize: 24 }} spin />}\n                />\n              )}\n            </Space>\n\n            <Space>\n              {canOpenSettings && (\n                <Tooltip\n                  mouseEnterDelay={0}\n                  mouseLeaveDelay={0}\n                  title={t('topsql.settings.title')}\n                  placement=\"bottom\"\n                >\n                  <SettingOutlined\n                    data-e2e=\"topsql_settings\"\n                    onClick={() => {\n                      setShowSettings(true)\n                      telemetry.clickSettings('settingIcon')\n                    }}\n                  />\n                </Tooltip>\n              )}\n              {!isDistro() && (\n                <Tooltip\n                  mouseEnterDelay={0}\n                  mouseLeaveDelay={0}\n                  title={t('topsql.settings.help')}\n                  placement=\"bottom\"\n                >\n                  <QuestionCircleOutlined\n                    onClick={() => {\n                      window.open(t('topsql.settings.help_url'), '_blank')\n                    }}\n                  />\n                </Tooltip>\n              )}\n            </Space>\n          </Toolbar>\n        </Card>\n\n        {shouldShowNetworkIoTip && (\n          <Card noMarginBottom>\n            <Alert\n              data-e2e=\"topsql_tikv_network_io_collection_alert\"\n              message={t('topsql.tikv_network_io_collection_tip.title')}\n              description={\n                <>\n                  {networkIoTipBody}\n                  {` `}\n                  <a\n                    onClick={() => {\n                      setShowSettings(true)\n                      telemetry.clickSettings('settingIcon')\n                    }}\n                  >\n                    {t('topsql.tikv_network_io_collection_tip.action')}\n                  </a>\n                </>\n              }\n              type=\"warning\"\n              showIcon\n            />\n          </Card>\n        )}\n\n        {/* Show \"not enabled\" Result when there are no historical data */}\n        {!isConfigLoading && !topSQLConfig?.enable && !haveHistoryData ? (\n          <Result\n            title={t('topsql.settings.disabled_result.title')}\n            subTitle={t('topsql.settings.disabled_result.sub_title')}\n            extra={\n              canOpenSettings || !isDistro() ? (\n                <Space>\n                  {canOpenSettings && (\n                    <Button\n                      type=\"primary\"\n                      onClick={() => {\n                        setShowSettings(true)\n                        telemetry.clickSettings('firstTimeTips')\n                      }}\n                    >\n                      {t('topsql.settings.open_settings')}\n                    </Button>\n                  )}\n                  {!isDistro() && (\n                    <Button\n                      onClick={() => {\n                        window.open(t('topsql.settings.help_url'), '_blank')\n                      }}\n                    >\n                      {t('topsql.settings.help')}\n                    </Button>\n                  )}\n                </Space>\n              ) : undefined\n            }\n          />\n        ) : (\n          <>\n            <div\n              className={styles.chart_container}\n              data-e2e=\"topsql_list_chart\"\n            >\n              <ListChart\n                onBrushEnd={handleBrushEnd}\n                data={topSQLData}\n                groupBy={groupBy}\n                orderBy={orderBy}\n                timeRangeTimestamp={toTimeRangeValue(timeRange)}\n                timeWindowSize={timeWindowSize}\n                ref={chartRef}\n              />\n            </div>\n            {Boolean(topSQLData?.length) && (\n              <ListTable\n                onRowOver={(key: string) =>\n                  onLegendItemOver(chartRef.current, key)\n                }\n                onRowLeave={() => onLegendItemOut(chartRef.current)}\n                topN={limit}\n                instanceType={instance?.instance_type as InstanceType}\n                data={topSQLData}\n                groupBy={groupBy}\n                orderBy={orderBy}\n                timeRange={timeRange}\n              />\n            )}\n            {Boolean(!topSQLData?.length && timeRange.type === 'recent') && (\n              <Card noMarginBottom noMarginTop>\n                <p className=\"ant-form-item-extra\">\n                  {t('topsql.table.description_no_recent_data')}\n                </p>\n              </Card>\n            )}\n          </>\n        )}\n      </div>\n\n      {canOpenSettings && (\n        <Drawer\n          title={t('statement.settings.title')}\n          width={300}\n          closable={true}\n          visible={showSettings}\n          onClose={() => setShowSettings(false)}\n          destroyOnClose={true}\n        >\n          <SettingsForm\n            onClose={() => setShowSettings(false)}\n            onConfigUpdated={() => {\n              updateConfig()\n              refreshTikvNetworkIoCollection()\n            }}\n          />\n        </Drawer>\n      )}\n    </>\n  )\n}\n\nconst useTopSQLData = (\n  instance: TopsqlInstanceItem | null,\n  timeRange: TimeRange,\n  limit: number,\n  groupBy: string,\n  orderBy: OrderBy,\n  computeTimeWindowSize: (ts: TimeRangeValue) => number\n) => {\n  const ctx = useContext(TopSQLContext)\n\n  const [topSQLData, setTopSQLData] = useState<any[]>([])\n  const [isLoading, setIsLoading] = useState(false)\n  const updateTopSQLData = useMemoizedFn(\n    async (\n      _instance: TopsqlInstanceItem | null,\n      _timeRange: TimeRange,\n      _limit: number | 5\n    ) => {\n      if (!_instance) {\n        return\n      }\n\n      let dataResp: TopsqlSummaryResponse\n      const ts = toTimeRangeValue(_timeRange)\n      const timeWindowSize = computeTimeWindowSize(ts)\n\n      const [start, end] = ts\n      try {\n        setIsLoading(true)\n        const resp = await ctx!.ds.topsqlSummaryGet(\n          String(end),\n          ctx?.cfg.showGroupBy === true\n            ? _instance.instance_type === 'tidb'\n              ? AggLevel.Query\n              : groupBy\n            : undefined,\n          _instance.instance,\n          _instance.instance_type,\n          ctx?.cfg.showOrderBy === true ? orderBy : undefined,\n          String(start),\n          String(limit),\n          `${timeWindowSize}s`,\n          ctx?.cfg.dataSource\n        )\n        dataResp = resp.data\n      } finally {\n        setIsLoading(false)\n      }\n\n      if (groupBy === AggLevel.Query || instance?.instance_type === 'tidb') {\n        // Sort data by digest\n        let data: TopsqlSummaryItem[] = dataResp.data ?? []\n        // If this digest occurs continuously on the timeline, we can easily see the sequential overhead\n        data.sort((a, b) => a.sql_digest?.localeCompare(b.sql_digest!) || 0)\n\n        data.forEach((d) => {\n          d.sql_text = formatSql(d.sql_text)\n          d.plans?.forEach((item) => {\n            // Filter data based on orderBy dimension\n            let filterFn: (index: number) => boolean\n            switch (orderBy) {\n              case OrderBy.NetworkBytes:\n                filterFn = (index: number) => !!item.network_bytes?.[index]\n                break\n              case OrderBy.LogicalIoBytes:\n                filterFn = (index: number) => !!item.logical_io_bytes?.[index]\n                break\n              case OrderBy.CpuTime:\n              default:\n                filterFn = (index: number) => !!item.cpu_time_ms?.[index]\n                break\n            }\n            item.timestamp_sec = item.timestamp_sec?.filter((_, index) =>\n              filterFn(index)\n            )\n            // Filter the corresponding data arrays\n            if (item.cpu_time_ms) {\n              item.cpu_time_ms = item.cpu_time_ms.filter((_, index) =>\n                filterFn(index)\n              )\n            }\n            if (item.network_bytes) {\n              item.network_bytes = item.network_bytes.filter((_, index) =>\n                filterFn(index)\n              )\n            }\n            if (item.logical_io_bytes) {\n              item.logical_io_bytes = item.logical_io_bytes.filter((_, index) =>\n                filterFn(index)\n              )\n            }\n\n            item.timestamp_sec = item.timestamp_sec?.map((t) => t * 1000)\n          })\n        })\n        setTopSQLData(data)\n      }\n\n      if (\n        groupBy === AggLevel.Table ||\n        groupBy === AggLevel.Schema ||\n        groupBy === AggLevel.Region\n      ) {\n        let data: TopsqlSummaryByItem[] = dataResp.data_by ?? []\n        // Sort data by table\n        // If this table occurs continuously on the timeline, we can easily see the sequential overhead\n        // data.sort((a, b) => a.table_?.localeCompare(b.table!) || 0)\n        data.forEach((d) => {\n          // Filter data based on orderBy dimension\n          // Note: TopsqlSummaryByItem may have network_bytes and logical_io_bytes arrays\n          // but they're not in the type definition, so we use type assertion\n          const byItem = d as any\n          let filterFn: (index: number) => boolean\n          switch (orderBy) {\n            case OrderBy.NetworkBytes:\n              filterFn = (index: number) => !!byItem.network_bytes?.[index]\n              break\n            case OrderBy.LogicalIoBytes:\n              filterFn = (index: number) => !!byItem.logical_io_bytes?.[index]\n              break\n            case OrderBy.CpuTime:\n            default:\n              filterFn = (index: number) => !!d.cpu_time_ms?.[index]\n              break\n          }\n          d.timestamp_sec = d.timestamp_sec?.filter((_, index) =>\n            filterFn(index)\n          )\n          // Filter the corresponding data arrays\n          if (d.cpu_time_ms) {\n            d.cpu_time_ms = d.cpu_time_ms.filter((_, index) => filterFn(index))\n          }\n          if (byItem.network_bytes) {\n            byItem.network_bytes = byItem.network_bytes.filter((_, index) =>\n              filterFn(index)\n            )\n          }\n          if (byItem.logical_io_bytes) {\n            byItem.logical_io_bytes = byItem.logical_io_bytes.filter(\n              (_, index) => filterFn(index)\n            )\n          }\n\n          d.timestamp_sec = d.timestamp_sec?.map((t) => t * 1000)\n        })\n        setTopSQLData(data)\n      }\n    }\n  )\n\n  useEffect(() => {\n    updateTopSQLData(instance, timeRange, limit)\n  }, [instance, timeRange, limit, groupBy, orderBy, updateTopSQLData])\n\n  return { topSQLData, isLoading, updateTopSQLData }\n}\n\nconst useTopSQLConfig = () => {\n  const ctx = useContext(TopSQLContext)\n  // Use the instance interface to query if historical data is available\n  const {\n    data: topSQLConfig,\n    isLoading: isConfigLoading,\n    sendRequest: updateConfig\n  } = useClientRequest(ctx!.ds.topsqlConfigGet)\n  const [haveHistoryData, setHaveHistoryData] = useState(true)\n  const [loadingHistory, setLoadingHistory] = useState(true)\n\n  useEffect(() => {\n    if (!topSQLConfig) {\n      return\n    }\n\n    if (!!topSQLConfig.enable) {\n      setLoadingHistory(false)\n      return\n    }\n\n    ;(async function () {\n      const now = Date.now() / 1000\n      const sevenDaysAgo = now - 7 * 24 * 60 * 60\n\n      setLoadingHistory(true)\n      try {\n        const res = await ctx!.ds.topsqlInstancesGet(\n          String(now),\n          String(sevenDaysAgo),\n          ctx?.cfg.dataSource\n        )\n        const data = res.data.data\n        if (!!data?.length) {\n          setHaveHistoryData(true)\n        } else {\n          setHaveHistoryData(false)\n        }\n      } finally {\n        setLoadingHistory(false)\n      }\n    })()\n  }, [topSQLConfig, ctx])\n\n  return {\n    topSQLConfig,\n    isConfigLoading: isConfigLoading || loadingHistory,\n    updateConfig,\n    haveHistoryData\n  }\n}\n\nconst useInstances = (timeRange: TimeRange) => {\n  const ctx = useContext(TopSQLContext)\n\n  const [instances, setInstances] = useState<TopsqlInstanceItem[]>([])\n  const [isLoading, setLoading] = useState(false)\n\n  const fetchInstances = useCallback(\n    async (_timeRange: TimeRange | null) => {\n      if (!_timeRange) {\n        return []\n      }\n\n      const [start, end] = toTimeRangeValue(_timeRange)\n      const resp = await ctx!.ds.topsqlInstancesGet(\n        String(end),\n        String(start),\n        ctx?.cfg.dataSource\n      )\n      // Deduplicate by instance and instance_type combination\n      const instanceMap = new Map<string, TopsqlInstanceItem>()\n      ;(resp.data.data || []).forEach((item) => {\n        const key = `${item.instance}_${item.instance_type}`\n        if (item.instance && item.instance_type && !instanceMap.has(key)) {\n          instanceMap.set(key, item)\n        }\n      })\n      const data = sortBy(Array.from(instanceMap.values()), [\n        'instance_type',\n        'instance'\n      ])\n\n      setInstances(data)\n      return data\n    },\n    [ctx]\n  )\n\n  useEffect(() => {\n    setLoading(true)\n    fetchInstances(timeRange).finally(() => {\n      setLoading(false)\n    })\n  }, [timeRange, fetchInstances])\n\n  return {\n    instances,\n    fetchInstances,\n    isLoading\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/ListChart.tsx",
    "content": "import {\n  Axis,\n  BarSeries,\n  Chart,\n  Position,\n  ScaleType,\n  Settings,\n  BrushEndListener\n} from '@elastic/charts'\nimport { orderBy as lodashOrderBy, toPairs } from 'lodash'\nimport React, { useMemo, useState, forwardRef } from 'react'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\nimport { TopsqlSummaryItem, TopsqlSummaryByItem } from '@lib/client'\nimport { useTranslation } from 'react-i18next'\nimport { useChange } from '@lib/utils/useChange'\nimport { DEFAULT_CHART_SETTINGS, timeTickFormatter } from '@lib/utils/charts'\nimport { AggLevel, OrderBy } from './List'\n\nexport interface ListChartProps {\n  data: any[]\n  timeWindowSize: number\n  groupBy: string\n  orderBy: OrderBy\n  timeRangeTimestamp: [number, number]\n  onBrushEnd: BrushEndListener\n}\n\nconst isQueryAggLevel = (groupBy: string) => {\n  // default is query\n  return !(\n    groupBy === AggLevel.Table ||\n    groupBy === AggLevel.Schema ||\n    groupBy === AggLevel.Region\n  )\n}\n\nconst getAxisTitle = (orderBy: OrderBy, t: any): string => {\n  switch (orderBy) {\n    case OrderBy.NetworkBytes:\n      return t('topsql.chart.network_bytes') || 'Network Bytes'\n    case OrderBy.LogicalIoBytes:\n      return t('topsql.chart.logical_io_bytes') || 'Logical IO Bytes'\n    case OrderBy.CpuTime:\n    default:\n      return t('topsql.chart.cpu_time')\n  }\n}\n\nconst getAxisTickFormatter = (orderBy: OrderBy) => {\n  switch (orderBy) {\n    case OrderBy.NetworkBytes:\n      return (v: number, decimals: number) =>\n        getValueFormat('bytes')(v, decimals)\n    case OrderBy.LogicalIoBytes:\n      return (v: number, decimals: number) =>\n        getValueFormat('bytes')(v, decimals)\n    case OrderBy.CpuTime:\n    default:\n      return (v: number, decimals: number) => getValueFormat('ms')(v, decimals)\n  }\n}\n\nexport const ListChart = forwardRef<Chart, ListChartProps>(\n  (\n    { onBrushEnd, data, groupBy, orderBy, timeWindowSize, timeRangeTimestamp },\n    ref\n  ) => {\n    const { t } = useTranslation()\n    // And we need update all the data at the same time and let the chart refresh only once for a better experience.\n    const [bundle, setBundle] = useState({\n      data,\n      groupBy,\n      orderBy,\n      timeWindowSize,\n      timeRangeTimestamp\n    })\n    const { chartData } = useChartData(\n      bundle.data,\n      bundle.groupBy,\n      bundle.orderBy\n    )\n    const { digestMap } = useDigestMap(bundle.data, bundle.groupBy)\n\n    useChange(() => {\n      setBundle({ data, groupBy, orderBy, timeWindowSize, timeRangeTimestamp })\n    }, [data, groupBy, orderBy])\n\n    return (\n      <Chart ref={ref}>\n        <Settings\n          {...DEFAULT_CHART_SETTINGS}\n          showLegend={false}\n          onBrushEnd={onBrushEnd}\n          xDomain={{\n            // Why do we want this? Because some data point may be missing. ech cannot know an\n            // accurate interval.\n            minInterval: bundle.timeWindowSize * 1000,\n            min: bundle.timeRangeTimestamp[0] * 1000,\n            max: bundle.timeRangeTimestamp[1] * 1000\n          }}\n        />\n        <Axis\n          id=\"bottom\"\n          position={Position.Bottom}\n          showOverlappingTicks\n          tickFormat={timeTickFormatter(bundle.timeRangeTimestamp)}\n        />\n        <Axis\n          id=\"left\"\n          title={getAxisTitle(bundle.orderBy, t)}\n          position={Position.Left}\n          showOverlappingTicks\n          tickFormat={(v) => getAxisTickFormatter(bundle.orderBy)(v, 1)}\n          ticks={5}\n        />\n        {Object.keys(chartData).map((originText) => {\n          const sql = digestMap?.[originText] || ''\n          let text = sql\n          if (!originText) {\n            text = t('topsql.table.others')\n            // is unknown text\n          } else if (!sql) {\n            if (isQueryAggLevel(bundle.groupBy)) {\n              // cannot find the sql text, but we agg by sql\n              text = `(SQL ${originText.slice(0, 8)})`\n            } else {\n              text = originText\n            }\n          } else {\n            // text too long, show a part of it\n            text = sql.length > 50 ? `${sql.slice(0, 50)}...` : sql\n          }\n\n          return (\n            <BarSeries\n              key={originText}\n              id={originText}\n              xScaleType={ScaleType.Time}\n              yScaleType={ScaleType.Linear}\n              xAccessor={0}\n              yAccessors={[1]}\n              stackAccessors={[0]}\n              data={chartData[originText]}\n              name={text}\n            />\n          )\n        })}\n        {Object.keys(chartData).length === 0 && (\n          // When there is no data, supply an empty one to preserve the axis.\n          <BarSeries\n            id=\"_placeholder\"\n            hideInLegend\n            xScaleType={ScaleType.Time}\n            yScaleType={ScaleType.Linear}\n            xAccessor={0}\n            yAccessors={[1]}\n            data={[\n              [bundle.timeRangeTimestamp[0] * 1000, null],\n              [bundle.timeRangeTimestamp[1] * 1000, null]\n            ]}\n          />\n        )}\n      </Chart>\n    )\n  }\n)\n\nfunction useDigestMap(seriesDataO: any[] = [], groupBy: string) {\n  const digestMap = useMemo(() => {\n    if (!seriesDataO) {\n      return {}\n    }\n    if (!isQueryAggLevel(groupBy)) {\n      return {}\n    }\n    let seriesData = seriesDataO as TopsqlSummaryItem[]\n    if (!seriesData) {\n      return {}\n    }\n    return seriesData.reduce((prev, { sql_digest, sql_text }) => {\n      prev[sql_digest!] = sql_text\n      return prev\n    }, {} as { [digest: string]: string | undefined })\n  }, [seriesDataO, groupBy])\n  return { digestMap }\n}\n\nfunction useChartData(seriesDataO: any[], groupBy: string, orderBy: OrderBy) {\n  let chartData: Record<string, Array<[number, number]>> = {}\n  chartData = useMemo(() => {\n    if (isQueryAggLevel(groupBy)) {\n      if (!seriesDataO) {\n        return {}\n      }\n      let seriesData = seriesDataO as TopsqlSummaryItem[]\n      // Group by SQL digest + timestamp and sum their values\n      const valuesByDigestAndTs: Record<string, Record<number, number>> = {}\n      const sumValueByDigest: Record<string, number> = {}\n\n      // Get the value getter function based on orderBy\n      const getValue = (values: any, i: number): number => {\n        switch (orderBy) {\n          case OrderBy.NetworkBytes:\n            return values.network_bytes?.[i] || 0\n          case OrderBy.LogicalIoBytes:\n            return values.logical_io_bytes?.[i] || 0\n          case OrderBy.CpuTime:\n          default:\n            return values.cpu_time_ms?.[i] || 0\n        }\n      }\n\n      seriesData.forEach((series) => {\n        const seriesDigest = series.sql_digest!\n\n        if (!valuesByDigestAndTs[seriesDigest]) {\n          valuesByDigestAndTs[seriesDigest] = {}\n        }\n        const map = valuesByDigestAndTs[seriesDigest]\n        let sum = 0\n        series.plans?.forEach((values) => {\n          values.timestamp_sec?.forEach((t, i) => {\n            const value = getValue(values, i)\n            if (!map[t]) {\n              map[t] = value\n            } else {\n              map[t] += value\n            }\n            sum += value\n          })\n        })\n\n        if (!sumValueByDigest[seriesDigest]) {\n          sumValueByDigest[seriesDigest] = 0\n        }\n        sumValueByDigest[seriesDigest] += sum\n      })\n\n      // Order by digest\n      const orderedDigests = lodashOrderBy(\n        toPairs(sumValueByDigest),\n        ['1'],\n        ['desc']\n      )\n        .filter((v) => v[1] > 0)\n        .map((v) => v[0])\n\n      const datumByDigest: Record<string, Array<[number, number]>> = {}\n      for (const digest of orderedDigests) {\n        const datum: Array<[number, number]> = []\n\n        const valuesByTs = valuesByDigestAndTs[digest]\n        for (const ts in valuesByTs) {\n          const value = valuesByTs[ts]\n          datum.push([Number(ts), value])\n        }\n\n        datumByDigest[digest] = datum\n      }\n\n      return datumByDigest\n    } else {\n      if (!seriesDataO) {\n        return {}\n      }\n      let seriesData = seriesDataO as TopsqlSummaryByItem[]\n      const datumBy: Record<string, Array<[number, number]>> = {}\n\n      // Get the value getter function based on orderBy\n      const getValue = (series: any, i: number): number => {\n        switch (orderBy) {\n          case OrderBy.NetworkBytes:\n            return series.network_bytes?.[i] || 0\n          case OrderBy.LogicalIoBytes:\n            return series.logical_io_bytes?.[i] || 0\n          case OrderBy.CpuTime:\n          default:\n            return series.cpu_time_ms?.[i] || 0\n        }\n      }\n\n      seriesData.forEach((series) => {\n        const key = series.text!\n        if (!datumBy[key]) {\n          datumBy[key] = []\n        }\n        series.timestamp_sec?.forEach((t, i) => {\n          const value = getValue(series, i)\n          if (value > 0) {\n            datumBy[key].push([t, value])\n          }\n        })\n      })\n      return datumBy\n    }\n  }, [seriesDataO, groupBy, orderBy])\n\n  return {\n    chartData\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/ListDetail/ListDetail.tsx",
    "content": "import React from 'react'\nimport { useTranslation } from 'react-i18next'\n\nimport { Head } from '@lib/components'\n\nimport { InstanceType, ListDetailTable } from './ListDetailTable'\nimport type { SQLRecord } from '../ListTable'\nimport { OrderBy } from '../List'\n\ninterface ListDetailProps {\n  record: SQLRecord\n  capacity: number\n  instanceType: InstanceType\n  orderBy: OrderBy\n}\n\nexport function ListDetail({\n  record,\n  capacity,\n  instanceType,\n  orderBy\n}: ListDetailProps) {\n  const { t } = useTranslation()\n\n  return (\n    <>\n      <Head title={t('topsql.detail.title')} />\n      <ListDetailTable\n        instanceType={instanceType}\n        record={record}\n        capacity={capacity}\n        orderBy={orderBy}\n      />\n    </>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/ListDetail/ListDetailContent.tsx",
    "content": "import React, { useState } from 'react'\nimport { Space } from 'antd'\n\nimport formatSql from '@lib/utils/sqlFormatter'\nimport {\n  Descriptions,\n  Expand,\n  Pre,\n  HighlightSQL,\n  TextWithInfo,\n  Card,\n  CopyLink\n} from '@lib/components'\nimport type { PlanRecord } from './ListDetailTable'\nimport type { SQLRecord } from '../ListTable'\nimport {\n  isNoPlanRecord,\n  isOverallRecord\n} from '@lib/apps/TopSQL/utils/specialRecord'\n\ninterface ListDetailContentProps {\n  sqlRecord: SQLRecord\n  planRecord?: PlanRecord\n}\n\nexport function ListDetailContent({\n  sqlRecord,\n  planRecord\n}: ListDetailContentProps) {\n  const [sqlExpanded, setSqlExpanded] = useState(false)\n  const toggleSqlExpanded = () => setSqlExpanded((prev) => !prev)\n  const [planExpanded, setPlanExpanded] = useState(false)\n  const togglePlanExpanded = () => setPlanExpanded((prev) => !prev)\n\n  return (\n    <Card data-e2e=\"topsql_listdetail_content\">\n      <Descriptions>\n        <Descriptions.Item\n          span={2}\n          multiline={sqlExpanded}\n          label={\n            <Space size=\"middle\">\n              <TextWithInfo.TransKey transKey=\"topsql.detail_content.fields.sql_text\" />\n              <Expand.Link expanded={sqlExpanded} onClick={toggleSqlExpanded} />\n              {\n                // avoid page hang when sql is too long\n                sqlRecord.sql_text!.length < 10000 && (\n                  <CopyLink\n                    displayVariant=\"formatted_sql\"\n                    data={formatSql(sqlRecord.sql_text!)}\n                  />\n                )\n              }\n              <CopyLink\n                displayVariant=\"original_sql\"\n                data={sqlRecord.sql_text}\n                data-e2e=\"sql_text\"\n              />\n            </Space>\n          }\n        >\n          <Expand\n            expanded={sqlExpanded}\n            collapsedContent={\n              <HighlightSQL sql={sqlRecord.sql_text!} compact />\n            }\n          >\n            <HighlightSQL sql={sqlRecord.sql_text!} />\n          </Expand>\n        </Descriptions.Item>\n        <Descriptions.Item\n          label={\n            <Space size=\"middle\">\n              <TextWithInfo.TransKey transKey=\"topsql.detail_content.fields.sql_digest\" />\n              <CopyLink data={sqlRecord.sql_digest} data-e2e=\"sql_digest\" />\n            </Space>\n          }\n        >\n          {sqlRecord.sql_digest}\n        </Descriptions.Item>\n        {!!planRecord?.plan_digest &&\n        !isOverallRecord(planRecord) &&\n        !isNoPlanRecord(planRecord) ? (\n          <Descriptions.Item\n            label={\n              <Space size=\"middle\">\n                <TextWithInfo.TransKey transKey=\"topsql.detail_content.fields.plan_digest\" />\n                <CopyLink\n                  data={planRecord.plan_digest}\n                  data-e2e=\"plan_digest\"\n                />\n              </Space>\n            }\n          >\n            {planRecord.plan_digest}\n          </Descriptions.Item>\n        ) : null}\n        {!!planRecord?.plan_text ? (\n          <Descriptions.Item\n            span={2}\n            multiline={planExpanded}\n            label={\n              <Space size=\"middle\">\n                <TextWithInfo.TransKey transKey=\"topsql.detail_content.fields.plan\" />\n                <Expand.Link\n                  expanded={planExpanded}\n                  onClick={togglePlanExpanded}\n                />\n                <CopyLink data={planRecord.plan_text} data-e2e=\"plan_text\" />\n              </Space>\n            }\n          >\n            <Expand expanded={planExpanded}>\n              <Pre noWrap>{planRecord.plan_text}</Pre>\n            </Expand>\n          </Descriptions.Item>\n        ) : null}\n      </Descriptions>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/ListDetail/ListDetailTable.tsx",
    "content": "import React, { useCallback, useMemo } from 'react'\nimport { SelectionMode, IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport { Tooltip } from 'antd'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\nimport { useTranslation } from 'react-i18next'\nimport { QuestionCircleOutlined } from '@ant-design/icons'\nimport { CSVLink } from 'react-csv'\n\nimport { Bar, TextWrap, CardTable, Card } from '@lib/components'\nimport { TopsqlSummaryPlanItem } from '@lib/client'\n\nimport type { SQLRecord } from '../ListTable'\nimport { ListDetailContent } from './ListDetailContent'\nimport { useRecordSelection } from '../../../utils/useRecordSelection'\nimport {\n  convertNoPlanRecord,\n  createOverallRecord,\n  isNoPlanRecord,\n  isOverallRecord\n} from '@lib/apps/TopSQL/utils/specialRecord'\nimport { telemetry } from '@lib/apps/TopSQL/utils/telemetry'\nimport { OrderBy } from '../List'\n\nexport type InstanceType = 'tidb' | 'tikv'\n\ninterface ListDetailTableProps {\n  record: SQLRecord\n  capacity: number\n  instanceType: InstanceType\n  orderBy: OrderBy\n}\n\nconst UNKNOWN_LABEL = 'Unknown'\n\nconst formatZero = (v: number) => {\n  if (v.toFixed(1) === '0.0') {\n    return 0\n  }\n  return v\n}\nconst shortFormat = (v: number = 0) => {\n  return v && v < 0.1 ? '<0.1' : getValueFormat('short')(formatZero(v), 1)\n}\nconst msFormat = (v: number = 0) => {\n  return getValueFormat('ms')(formatZero(v), 1)\n}\n\nexport function ListDetailTable({\n  record: sqlRecord,\n  capacity,\n  instanceType,\n  orderBy\n}: ListDetailTableProps) {\n  const {\n    records: planRecords,\n    isMultiPlans,\n    detailCapacity\n  } = usePlanRecord(sqlRecord, orderBy)\n  const { t } = useTranslation()\n\n  // Use detailCapacity if available, otherwise fall back to capacity from parent\n  const effectiveCapacity = detailCapacity > 0 ? detailCapacity : capacity\n\n  // Get column title and value based on orderBy\n  const getColumnTitle = () => {\n    switch (orderBy) {\n      case OrderBy.NetworkBytes:\n        return t('topsql.detail.fields.network_bytes') || 'Network Bytes'\n      case OrderBy.LogicalIoBytes:\n        return t('topsql.detail.fields.logical_io_bytes') || 'Logical IO Bytes'\n      case OrderBy.CpuTime:\n      default:\n        return t('topsql.detail.fields.cpu_time')\n    }\n  }\n\n  const getColumnValue = (rec: PlanRecord): number => {\n    switch (orderBy) {\n      case OrderBy.NetworkBytes:\n        return rec.networkBytes || 0\n      case OrderBy.LogicalIoBytes:\n        return rec.logicalIoBytes || 0\n      case OrderBy.CpuTime:\n      default:\n        return rec.cpuTime || 0\n    }\n  }\n\n  const getValueFormatter = () => {\n    switch (orderBy) {\n      case OrderBy.NetworkBytes:\n      case OrderBy.LogicalIoBytes:\n        return (v: number) => getValueFormat('bytes')(v, 2)\n      case OrderBy.CpuTime:\n      default:\n        return (v: number) => getValueFormat('ms')(v, 2)\n    }\n  }\n\n  const formatter = getValueFormatter()\n\n  const tableColumns = useMemo(\n    () =>\n      [\n        {\n          name: getColumnTitle(),\n          key:\n            orderBy === OrderBy.NetworkBytes\n              ? 'networkBytes'\n              : orderBy === OrderBy.LogicalIoBytes\n              ? 'logicalIoBytes'\n              : 'cpuTime',\n          minWidth: 150,\n          maxWidth: 250,\n          onRender: (rec: PlanRecord) => {\n            const value = getColumnValue(rec)\n            return (\n              <Bar textWidth={80} value={value} capacity={effectiveCapacity}>\n                {formatter(value)}\n              </Bar>\n            )\n          }\n        },\n        {\n          name: t('topsql.detail.fields.plan'),\n          key: 'plan_digest',\n          minWidth: 150,\n          maxWidth: 150,\n          onRender: (rec: PlanRecord) => {\n            return isOverallRecord(rec) ? (\n              <Tooltip\n                title={t('topsql.detail.overall_tooltip')}\n                placement=\"right\"\n              >\n                <span\n                  style={{\n                    verticalAlign: 'middle',\n                    fontStyle: 'italic',\n                    color: '#aaa'\n                  }}\n                >\n                  {t('topsql.detail.overall')} <QuestionCircleOutlined />\n                </span>\n              </Tooltip>\n            ) : isNoPlanRecord(rec) ? (\n              <Tooltip\n                title={t('topsql.detail.no_plan_tooltip')}\n                placement=\"right\"\n              >\n                <span\n                  style={{\n                    verticalAlign: 'middle',\n                    fontStyle: 'italic',\n                    color: '#aaa'\n                  }}\n                >\n                  {t('topsql.detail.no_plan')} <QuestionCircleOutlined />\n                </span>\n              </Tooltip>\n            ) : (\n              <pre style={{ margin: 0 }}>\n                {rec.plan_digest?.slice(0, 8) || UNKNOWN_LABEL}\n              </pre>\n            )\n          }\n        },\n        {\n          name: t('topsql.detail.fields.exec_count_per_sec'),\n          key: 'exec_count_per_sec',\n          minWidth: 50,\n          maxWidth: 150,\n          onRender: (rec: PlanRecord) => (\n            <TextWrap>{shortFormat(rec.exec_count_per_sec)}</TextWrap>\n          )\n        },\n        instanceType === 'tikv' && {\n          name: t('topsql.detail.fields.scan_records_per_sec'),\n          key: 'scan_records_per_sec',\n          minWidth: 50,\n          maxWidth: 150,\n          onRender: (rec: PlanRecord) => (\n            <TextWrap>{shortFormat(rec.scan_records_per_sec)}</TextWrap>\n          )\n        },\n        instanceType === 'tikv' && {\n          name: t('topsql.detail.fields.scan_indexes_per_sec'),\n          key: 'scan_indexes_per_sec',\n          minWidth: 50,\n          maxWidth: 150,\n          onRender: (rec: PlanRecord) => (\n            <TextWrap>{shortFormat(rec.scan_indexes_per_sec)}</TextWrap>\n          )\n        },\n        instanceType === 'tidb' && {\n          name: t('topsql.detail.fields.duration_per_exec_ms'),\n          key: 'duration_per_exec_ms',\n          minWidth: 50,\n          maxWidth: 150,\n          onRender: (rec: PlanRecord) => (\n            <TextWrap>{msFormat(rec.duration_per_exec_ms)}</TextWrap>\n          )\n        }\n      ].filter((c) => !!c) as IColumn[],\n    [effectiveCapacity, instanceType, orderBy, t]\n  )\n\n  const csvHeaders = tableColumns.map((c) => ({ label: c.name, key: c.key }))\n\n  const getKey = useCallback((r: PlanRecord) => r?.plan_digest!, [])\n\n  const { selectedRecord, selection } = useRecordSelection<PlanRecord>({\n    storageKey: 'topsql.list_detail_table_selected_key',\n    selections: planRecords,\n    options: {\n      getKey,\n      canSelectItem: (r) => !isNoPlanRecord(r) && !isOverallRecord(r)\n    }\n  })\n\n  const planRecord = useMemo(() => {\n    if (isMultiPlans) {\n      return selectedRecord\n    }\n\n    return planRecords[0]\n  }, [planRecords, isMultiPlans, selectedRecord])\n\n  return (\n    <>\n      <Card noMarginBottom noMarginTop>\n        <CSVLink\n          data={planRecords || []}\n          headers={csvHeaders}\n          filename=\"topsql-plan\"\n        >\n          Download to CSV\n        </CSVLink>\n      </Card>\n      <CardTable\n        listProps={\n          {\n            'data-e2e': 'topsql_listdetail_table'\n          } as any\n        }\n        cardNoMarginTop\n        getKey={getKey}\n        items={planRecords}\n        columns={tableColumns}\n        selectionMode={SelectionMode.single}\n        selectionPreservedOnEmptyClick\n        onRowClicked={(item) => {\n          const index = planRecords\n            .filter((r) => !isOverallRecord(r) && !isNoPlanRecord(r))\n            .findIndex((r) => r.plan_digest === item.plan_digest)\n          if (index > -1) {\n            telemetry.clickPlan(index)\n          }\n        }}\n        selection={selection}\n      />\n      {!sqlRecord.is_other && (\n        <ListDetailContent sqlRecord={sqlRecord} planRecord={planRecord} />\n      )}\n    </>\n  )\n}\n\nexport type PlanRecord = {\n  cpuTime: number\n  networkBytes?: number\n  logicalIoBytes?: number\n} & TopsqlSummaryPlanItem\n\nconst usePlanRecord = (\n  record: SQLRecord,\n  orderBy: OrderBy\n): { isMultiPlans: boolean; records: PlanRecord[]; detailCapacity: number } => {\n  return useMemo(() => {\n    if (!record?.plans?.length) {\n      return { isMultiPlans: false, records: [], detailCapacity: 0 }\n    }\n\n    const isMultiPlans = record.plans.length > 1\n    const plans = [...record.plans]\n\n    let detailCapacity = 0\n\n    const records: PlanRecord[] = plans\n      .map((p) => {\n        const cpuTime = p.cpu_time_ms?.reduce((pt, t) => pt + t, 0) || 0\n        const networkBytes = p.network_bytes?.reduce((pt, t) => pt + t, 0) || 0\n        const logicalIoBytes =\n          p.logical_io_bytes?.reduce((pt, t) => pt + t, 0) || 0\n\n        // Calculate capacity based on the selected orderBy dimension\n        let value = 0\n        switch (orderBy) {\n          case OrderBy.NetworkBytes:\n            value = networkBytes\n            break\n          case OrderBy.LogicalIoBytes:\n            value = logicalIoBytes\n            break\n          case OrderBy.CpuTime:\n          default:\n            value = cpuTime\n            break\n        }\n\n        if (detailCapacity < value) {\n          detailCapacity = value\n        }\n\n        return {\n          ...p,\n          cpuTime,\n          networkBytes,\n          logicalIoBytes\n        }\n      })\n      .sort((a, b) => {\n        // Sort based on the selected orderBy dimension\n        let aValue = 0\n        let bValue = 0\n        switch (orderBy) {\n          case OrderBy.NetworkBytes:\n            aValue = a.networkBytes || 0\n            bValue = b.networkBytes || 0\n            break\n          case OrderBy.LogicalIoBytes:\n            aValue = a.logicalIoBytes || 0\n            bValue = b.logicalIoBytes || 0\n            break\n          case OrderBy.CpuTime:\n          default:\n            aValue = a.cpuTime\n            bValue = b.cpuTime\n            break\n        }\n        return bValue - aValue\n      })\n      .map(convertNoPlanRecord)\n\n    // add overall record to the first\n    if (isMultiPlans) {\n      const overallRecord = createOverallRecord(record, orderBy)\n      records.unshift(overallRecord)\n      // Update capacity if overall record has larger value\n      const overallValue = getOverallValue(overallRecord, orderBy)\n      if (detailCapacity < overallValue) {\n        detailCapacity = overallValue\n      }\n    }\n\n    return { isMultiPlans, records, detailCapacity }\n  }, [record, orderBy])\n}\n\nconst getOverallValue = (rec: PlanRecord, orderBy: OrderBy): number => {\n  switch (orderBy) {\n    case OrderBy.NetworkBytes:\n      return rec.networkBytes || 0\n    case OrderBy.LogicalIoBytes:\n      return rec.logicalIoBytes || 0\n    case OrderBy.CpuTime:\n    default:\n      return rec.cpuTime || 0\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/ListDetail/index.ts",
    "content": "export * from './ListDetail'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/ListTable.tsx",
    "content": "import React, { useContext, useMemo } from 'react'\nimport { Tooltip, Typography } from 'antd'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\nimport { useTranslation } from 'react-i18next'\nimport {\n  SelectionMode,\n  DetailsRow\n} from 'office-ui-fabric-react/lib/DetailsList'\nimport { QuestionCircleOutlined } from '@ant-design/icons'\nimport { CSVLink } from 'react-csv'\n\nimport { TopsqlSummaryItem, TopsqlSummaryByItem } from '@lib/client'\nimport {\n  Card,\n  CardTable,\n  Bar,\n  TextWrap,\n  HighlightSQL,\n  AppearAnimate,\n  TimeRange,\n  toTimeRangeValue\n} from '@lib/components'\n\nimport { useRecordSelection } from '../../utils/useRecordSelection'\nimport { ListDetail } from './ListDetail'\nimport {\n  isOthersRecord,\n  isSummaryByRecord,\n  isUnknownSQLRecord\n} from '../../utils/specialRecord'\nimport { InstanceType } from './ListDetail/ListDetailTable'\nimport { useMemoizedFn } from 'ahooks'\nimport { telemetry } from '../../utils/telemetry'\nimport openLink from '@lib/utils/openLink'\nimport { useNavigate } from 'react-router-dom'\nimport { TopSQLContext } from '../../context'\nimport { AggLevel, OrderBy } from './List'\n\ninterface ListTableProps {\n  data: any[]\n  groupBy: string\n  orderBy: OrderBy\n  topN: number\n  instanceType: InstanceType\n  timeRange: TimeRange\n  onRowOver: (key: string) => void\n  onRowLeave: () => void\n}\n\nconst emptyFn = () => {}\n\nexport type SQLRecord = TopsqlSummaryItem &\n  TopsqlSummaryByItem & {\n    cpuTime: number\n    networkBytes?: number\n    logicalIoBytes?: number\n  }\n\nfunction isConvertNumber(value: string): boolean {\n  let res = !isNaN(Number(value))\n  return res\n}\n\nexport function ListTable({\n  data,\n  groupBy,\n  orderBy,\n  topN,\n  instanceType,\n  timeRange,\n  onRowLeave,\n  onRowOver\n}: ListTableProps) {\n  const { t } = useTranslation()\n  const { data: tableRecords, capacity } = useTableData(data, orderBy)\n  const navigate = useNavigate()\n  const ctx = useContext(TopSQLContext)\n\n  const tableColumns = useMemo(() => {\n    function goDetail(ev: React.MouseEvent<HTMLElement>, record: SQLRecord) {\n      const sv = sessionStorage.getItem('statement.query_options')\n      if (sv) {\n        const queryOptions = JSON.parse(sv)\n        queryOptions.searchText = record.sql_digest\n        sessionStorage.setItem(\n          'statement.query_options',\n          JSON.stringify(queryOptions)\n        )\n      }\n\n      const tv = toTimeRangeValue(timeRange)\n      openLink(`/statement?from=${tv[0]}&to=${tv[1]}`, ev, navigate)\n    }\n    // Get column title and value based on orderBy\n    const getColumnTitle = () => {\n      switch (orderBy) {\n        case OrderBy.NetworkBytes:\n          return t('topsql.table.fields.network_bytes') || 'Network Bytes'\n        case OrderBy.LogicalIoBytes:\n          return t('topsql.table.fields.logical_io_bytes') || 'Logical IO Bytes'\n        case OrderBy.CpuTime:\n        default:\n          return t('topsql.table.fields.cpu_time')\n      }\n    }\n\n    const getColumnValue = (rec: SQLRecord): number => {\n      switch (orderBy) {\n        case OrderBy.NetworkBytes:\n          return rec.networkBytes || 0\n        case OrderBy.LogicalIoBytes:\n          return rec.logicalIoBytes || 0\n        case OrderBy.CpuTime:\n        default:\n          return rec.cpuTime || 0\n      }\n    }\n\n    const getValueFormatter = () => {\n      switch (orderBy) {\n        case OrderBy.NetworkBytes:\n        case OrderBy.LogicalIoBytes:\n          return (v: number) => getValueFormat('bytes')(v, 2)\n        case OrderBy.CpuTime:\n        default:\n          return (v: number) => getValueFormat('ms')(v, 2)\n      }\n    }\n\n    const formatter = getValueFormatter()\n    let cols = [\n      {\n        name: getColumnTitle(),\n        key:\n          orderBy === OrderBy.NetworkBytes\n            ? 'networkBytes'\n            : orderBy === OrderBy.LogicalIoBytes\n            ? 'logicalIoBytes'\n            : 'cpuTime',\n        minWidth: 150,\n        maxWidth: 250,\n        onRender: (rec: SQLRecord) => {\n          const value = getColumnValue(rec)\n          return (\n            <Bar textWidth={80} value={value} capacity={capacity}>\n              {formatter(value)}\n            </Bar>\n          )\n        }\n      },\n      {\n        name:\n          groupBy === AggLevel.Table\n            ? t('topsql.table.fields.table')\n            : groupBy === AggLevel.Schema\n            ? t('topsql.table.fields.db')\n            : groupBy === AggLevel.Region\n            ? 'RegionID'\n            : t('topsql.table.fields.sql'),\n        key:\n          groupBy === AggLevel.Table ||\n          groupBy === AggLevel.Schema ||\n          groupBy === AggLevel.Region\n            ? 'text'\n            : 'sql_text',\n        minWidth: 250,\n        maxWidth: 550,\n        onRender: (rec: SQLRecord) => {\n          let text = rec.text\n            ? rec.text\n            : isUnknownSQLRecord(rec)\n            ? `(SQL ${rec.sql_digest?.slice(0, 8)})`\n            : rec.sql_text!\n\n          // parser the table name if the text is like \"tableId-tableName\"\n          text =\n            text.includes('-') && text.split('-').length > 1\n              ? isConvertNumber(text.split('-')[0])\n                ? text.split('-')[1] + ' ( tid =' + text.split('-')[0] + ' )'\n                : text\n              : text\n          return isOthersRecord(rec) ? (\n            <Tooltip\n              title={t('topsql.table.others_tooltip', { topN })}\n              placement=\"right\"\n            >\n              <span\n                style={{\n                  verticalAlign: 'middle',\n                  fontStyle: 'italic',\n                  color: '#aaa'\n                }}\n                data-e2e=\"topsql_listtable_row_others\"\n              >\n                {t('topsql.table.others')} <QuestionCircleOutlined />\n              </span>\n            </Tooltip>\n          ) : (\n            <Tooltip\n              title={<HighlightSQL sql={text} theme=\"dark\" maxLen={1000} />}\n              placement=\"right\"\n            >\n              <TextWrap>\n                <HighlightSQL sql={text} compact maxLen={1000} />\n              </TextWrap>\n            </Tooltip>\n          )\n        }\n      },\n      {\n        name: '',\n        key: 'actions',\n        minWidth: 200,\n        // maxWidth: 200,\n        onRender: (rec) => {\n          if (!isOthersRecord(rec) && !isSummaryByRecord(rec)) {\n            return (\n              <Typography.Link onClick={(ev) => goDetail(ev, rec)}>\n                {t('topsql.table.actions.search_in_statements')}\n              </Typography.Link>\n            )\n          }\n          return null\n        }\n      }\n    ]\n    if (ctx?.cfg.showSearchInStatements === false) {\n      cols = cols.filter((c) => c.key !== 'actions')\n    }\n    return cols\n  }, [\n    capacity,\n    t,\n    topN,\n    groupBy,\n    orderBy,\n    navigate,\n    timeRange,\n    ctx?.cfg.showSearchInStatements\n  ])\n\n  const csvHeaders = tableColumns\n    .slice(0, 2)\n    .map((c) => ({ label: c.name, key: c.key }))\n\n  const getKey = useMemoizedFn((r: SQLRecord) => r?.sql_digest ?? r?.text ?? '')\n\n  const { selectedRecord, selection } = useRecordSelection<SQLRecord>({\n    storageKey: 'topsql.list_table_selected_key',\n    selections: tableRecords,\n    options: {\n      getKey,\n      canSelectItem: (r) => !isSummaryByRecord(r)\n    }\n  })\n  const onRenderRow = useMemoizedFn((props: any) => (\n    <div\n      onMouseEnter={() => onRowOver(props.item?.sql_digest ?? props.item?.text)}\n      onMouseLeave={onRowLeave}\n      onClick={() =>\n        telemetry.clickStatement(props.itemIndex, props.itemIndex === topN)\n      }\n    >\n      <DetailsRow {...props} />\n    </div>\n  ))\n\n  return tableRecords.length ? (\n    <>\n      <Card noMarginBottom noMarginTop>\n        <div className=\"ant-form-item-extra\">\n          {t('topsql.table.description', { topN })}{' '}\n          <CSVLink\n            data={tableRecords || []}\n            headers={csvHeaders}\n            filename=\"topsql\"\n          >\n            Download to CSV\n          </CSVLink>\n        </div>\n      </Card>\n      <CardTable\n        listProps={\n          {\n            'data-e2e': 'topsql_list_table'\n          } as any\n        }\n        cardNoMarginTop\n        cardNoMarginBottom\n        getKey={getKey}\n        items={tableRecords || []}\n        columns={tableColumns}\n        selection={selection}\n        selectionMode={SelectionMode.single}\n        selectionPreservedOnEmptyClick\n        onRowClicked={emptyFn}\n        onRenderRow={onRenderRow}\n      />\n      <AppearAnimate motionName=\"contentAnimation\">\n        {selectedRecord &&\n          groupBy !== AggLevel.Table &&\n          groupBy !== AggLevel.Schema &&\n          groupBy !== AggLevel.Region && (\n            <ListDetail\n              instanceType={instanceType}\n              record={selectedRecord}\n              capacity={capacity}\n              orderBy={orderBy}\n            />\n          )}\n      </AppearAnimate>\n    </>\n  ) : null\n}\n\nfunction useTableData(records: any[], orderBy: OrderBy) {\n  const tableData: { data: SQLRecord[]; capacity: number } = useMemo(() => {\n    if (!records) {\n      return { data: [], capacity: 0 }\n    }\n    const sum = (arr?: Array<number>): number =>\n      (arr ?? []).reduce((acc, v) => acc + (v || 0), 0)\n\n    let capacity = 0\n    const d = records\n      .map((r) => {\n        let cpuTime = 0\n        let networkBytes = 0\n        let logicalIoBytes = 0\n\n        r.plans?.forEach((plan: any) => {\n          plan.timestamp_sec?.forEach((t: number, i: number) => {\n            cpuTime += plan.cpu_time_ms?.[i] || 0\n            // network_bytes and logical_io_bytes might be arrays similar to cpu_time_ms\n            networkBytes += plan.network_bytes?.[i] || 0\n            logicalIoBytes += plan.logical_io_bytes?.[i] || 0\n          })\n        })\n\n        // For SummaryByItem (groupBy table / schema / region)\n        // Note: backend may omit unrelated fields depending on orderBy, so avoid using\n        // cpu_time_ms_sum as the \"is summary-by\" guard.\n        if ((r.text?.length ?? 0) > 0) {\n          cpuTime = r.cpu_time_ms_sum ?? sum(r.cpu_time_ms)\n          networkBytes = r.network_bytes_sum ?? sum(r.network_bytes)\n          logicalIoBytes = r.logical_io_bytes_sum ?? sum(r.logical_io_bytes)\n        }\n\n        // Calculate capacity based on the selected orderBy dimension\n        let sortValue = 0\n        switch (orderBy) {\n          case OrderBy.CpuTime:\n            sortValue = cpuTime\n            break\n          case OrderBy.NetworkBytes:\n            sortValue = networkBytes\n            break\n          case OrderBy.LogicalIoBytes:\n            sortValue = logicalIoBytes\n            break\n        }\n\n        if (capacity < sortValue) {\n          capacity = sortValue\n        }\n\n        return {\n          ...r,\n          cpuTime,\n          networkBytes,\n          logicalIoBytes,\n          plans: r.plans || []\n        }\n      })\n      .filter((r) => {\n        // Filter based on the selected orderBy dimension\n        switch (orderBy) {\n          case OrderBy.CpuTime:\n            return !!r.cpuTime\n          case OrderBy.NetworkBytes:\n            return !!r.networkBytes\n          case OrderBy.LogicalIoBytes:\n            return !!r.logicalIoBytes\n          default:\n            return !!r.cpuTime\n        }\n      })\n      .sort((a, b) => {\n        // Sort based on the selected orderBy dimension\n        let aValue = 0\n        let bValue = 0\n        switch (orderBy) {\n          case OrderBy.CpuTime:\n            aValue = a.cpuTime\n            bValue = b.cpuTime\n            break\n          case OrderBy.NetworkBytes:\n            aValue = a.networkBytes || 0\n            bValue = b.networkBytes || 0\n            break\n          case OrderBy.LogicalIoBytes:\n            aValue = a.logicalIoBytes || 0\n            bValue = b.logicalIoBytes || 0\n            break\n        }\n        return bValue - aValue\n      })\n      .sort((a, b) => (b.is_other ? -1 : 0))\n    return { data: d, capacity }\n  }, [records, orderBy])\n  return tableData\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/SettingsForm.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.partialSwitch {\n  background: linear-gradient(\n    90deg,\n    @blue-6 0%,\n    @blue-6 50%,\n    @gray-5 50%,\n    @gray-5 100%\n  ) !important;\n  border-color: transparent !important;\n\n  &:hover:not(:global(.ant-switch-disabled)) {\n    background: linear-gradient(\n      90deg,\n      @blue-5 0%,\n      @blue-5 50%,\n      @gray-4 50%,\n      @gray-4 100%\n    ) !important;\n    border-color: transparent !important;\n  }\n\n  :global(.ant-switch-handle) {\n    left: calc(50% - 9px) !important;\n  }\n\n  :global(.ant-switch-inner) {\n    margin: 0;\n    padding: 0;\n    text-align: center;\n  }\n}\n\n.switchWithStatus {\n  display: inline-flex;\n  align-items: center;\n  gap: 8px;\n}\n\n.switchStatus {\n  color: @text-color-secondary;\n}\n\n.partialResultContent {\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n}\n\n.successInfoContent {\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n}\n\n.successInfoEmphasis {\n  font-weight: 600;\n}\n\n.partialResultWarnings {\n  margin: 0;\n  padding: 8px;\n  max-height: 120px;\n  overflow: auto;\n  white-space: pre-wrap;\n  word-break: break-word;\n  background: @background-color-light;\n  border: 1px solid @border-color-base;\n  border-radius: @border-radius-base;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/SettingsForm.tsx",
    "content": "import React, {\n  useState,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo\n} from 'react'\nimport { Form, Skeleton, Switch, Space, Button, Modal } from 'antd'\nimport { ExclamationCircleOutlined } from '@ant-design/icons'\nimport { useTranslation } from 'react-i18next'\nimport { TopsqlEditableConfig } from '@lib/client'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { DrawerFooter, ErrorBar } from '@lib/components'\nimport { useIsWriteable } from '@lib/utils/store'\nimport { telemetry } from '../../utils/telemetry'\nimport { TopSQLContext } from '../../context'\nimport styles from './SettingsForm.module.less'\n\ninterface Props {\n  onClose: () => void\n  onConfigUpdated: () => any\n}\n\ninterface FormValues {\n  enable: boolean\n  tikv_network_io_collection: boolean\n}\n\nexport function SettingsForm({ onClose, onConfigUpdated }: Props) {\n  const ctx = useContext(TopSQLContext)\n\n  const [form] = Form.useForm<FormValues>()\n  const [submitting, setSubmitting] = useState(false)\n  const { t } = useTranslation()\n  const isWriteable = useIsWriteable()\n\n  const {\n    data: initialConfig,\n    isLoading: loading,\n    error\n  } = useClientRequest(ctx!.ds.topsqlConfigGet)\n\n  const {\n    data: initialTikvNetworkIoCollection,\n    isLoading: loadingTikvNetworkIoCollection,\n    error: errorTikvNetworkIoCollection\n  } = useClientRequest(ctx!.ds.topsqlTikvNetworkIoCollectionGet)\n\n  const handleSubmit = useCallback(\n    (values: FormValues) => {\n      async function updateConfig(values: FormValues) {\n        const newConfig: TopsqlEditableConfig = {\n          enable: values.enable\n        }\n        try {\n          setSubmitting(true)\n          const shouldCheckTikvCollectionResult =\n            values.tikv_network_io_collection &&\n            form.isFieldTouched('tikv_network_io_collection')\n          const [, tikvCollectionUpdateResponse] = await Promise.all([\n            ctx!.ds.topsqlConfigPost(newConfig),\n            ctx!.ds.topsqlTikvNetworkIoCollectionPost({\n              enable: values.tikv_network_io_collection\n            })\n          ])\n          const tikvCollectionWarningMessages = (\n            tikvCollectionUpdateResponse.data.warnings ?? []\n          )\n            .map((w) => w.message || w.full_text || '')\n            .filter((msg) => !!msg)\n          let tikvCollectionAfterSave:\n            | { enable: boolean; is_multi_value?: boolean }\n            | undefined\n          if (shouldCheckTikvCollectionResult) {\n            try {\n              const resp = await ctx!.ds.topsqlTikvNetworkIoCollectionGet()\n              tikvCollectionAfterSave = resp.data\n            } catch {\n              // Ignore this best-effort check so save flow remains non-blocking.\n            }\n          }\n\n          telemetry.saveSettings({\n            ...newConfig,\n            tikv_network_io_collection: values.tikv_network_io_collection\n          })\n          onClose()\n          onConfigUpdated()\n\n          if (values.enable && !initialConfig?.enable) {\n            Modal.success({\n              title: t('topsql.settings.enable_info.title'),\n              content: t('topsql.settings.enable_info.content')\n            })\n          }\n\n          if (shouldCheckTikvCollectionResult) {\n            const isPartialAfterSave =\n              tikvCollectionAfterSave?.is_multi_value === true\n            const isAllEnabledAfterSave =\n              tikvCollectionAfterSave?.enable === true\n            if (\n              !isPartialAfterSave &&\n              isAllEnabledAfterSave &&\n              tikvCollectionWarningMessages.length === 0\n            ) {\n              Modal.success({\n                title: t(\n                  'topsql.settings.tikv_network_io_collection_info.title'\n                ),\n                content: (\n                  <div className={styles.successInfoContent}>\n                    <div>\n                      {t(\n                        'topsql.settings.tikv_network_io_collection_info.content_prefix'\n                      )}\n                    </div>\n                    <strong className={styles.successInfoEmphasis}>\n                      {t(\n                        'topsql.settings.tikv_network_io_collection_info.content_emphasis'\n                      )}\n                    </strong>\n                  </div>\n                )\n              })\n            } else {\n              Modal.warning({\n                title: t(\n                  'topsql.settings.tikv_network_io_collection_partial_info.title'\n                ),\n                content: (\n                  <div className={styles.partialResultContent}>\n                    <div>\n                      {t(\n                        'topsql.settings.tikv_network_io_collection_partial_info.content'\n                      )}\n                    </div>\n                    {tikvCollectionWarningMessages.length > 0 && (\n                      <pre className={styles.partialResultWarnings}>\n                        {tikvCollectionWarningMessages.join('\\n')}\n                      </pre>\n                    )}\n                    <a\n                      onClick={() =>\n                        window.open(t('topsql.settings.help_url'), '_blank')\n                      }\n                    >\n                      {t(\n                        'topsql.settings.tikv_network_io_collection_partial_info.action'\n                      )}\n                    </a>\n                  </div>\n                )\n              })\n            }\n          }\n        } finally {\n          setSubmitting(false)\n        }\n      }\n\n      if (!values.enable && (initialConfig?.enable ?? true)) {\n        // warning\n        Modal.confirm({\n          title: t('topsql.settings.disable_feature'),\n          icon: <ExclamationCircleOutlined />,\n          content: t('topsql.settings.disable_warning'),\n          okText: t('topsql.settings.actions.close'),\n          cancelText: t('topsql.settings.actions.cancel'),\n          okButtonProps: { danger: true },\n          onOk: () => updateConfig(values)\n        })\n      } else {\n        updateConfig(values)\n      }\n    },\n    [t, onClose, onConfigUpdated, initialConfig, ctx, form]\n  )\n\n  const combinedLoading = loading || loadingTikvNetworkIoCollection\n  const combinedError = [error, errorTikvNetworkIoCollection].filter((e) => !!e)\n  const topsqlEnabled = Form.useWatch('enable', form)\n  const tikvNetworkIoCollectionEnabled = Form.useWatch(\n    'tikv_network_io_collection',\n    form\n  )\n  const tikvStatusText = useMemo(() => {\n    if (topsqlEnabled === false) {\n      return t('topsql.settings.tikv_network_io_collection_disabled_by_topsql')\n    }\n    if (!initialTikvNetworkIoCollection) {\n      return ''\n    }\n    if (initialTikvNetworkIoCollection.is_multi_value) {\n      return t('topsql.settings.tikv_network_io_collection_status.partial')\n    }\n    return initialTikvNetworkIoCollection.enable\n      ? t('topsql.settings.tikv_network_io_collection_status.on')\n      : t('topsql.settings.tikv_network_io_collection_status.off')\n  }, [topsqlEnabled, initialTikvNetworkIoCollection, t])\n  const showTikvNetworkIoCollectionPartialState = useMemo(() => {\n    if (topsqlEnabled === false) {\n      return false\n    }\n    if (!initialTikvNetworkIoCollection?.is_multi_value) {\n      return false\n    }\n    if (form.isFieldTouched('tikv_network_io_collection')) {\n      return false\n    }\n    return (\n      tikvNetworkIoCollectionEnabled === initialTikvNetworkIoCollection.enable\n    )\n  }, [\n    topsqlEnabled,\n    initialTikvNetworkIoCollection,\n    tikvNetworkIoCollectionEnabled,\n    form\n  ])\n  const tikvNetworkIoCollectionTooltip = useMemo(() => {\n    return showTikvNetworkIoCollectionPartialState\n      ? t('topsql.settings.tikv_network_io_collection_tooltip_partial')\n      : t('topsql.settings.tikv_network_io_collection_tooltip')\n  }, [showTikvNetworkIoCollectionPartialState, t])\n\n  useEffect(() => {\n    if (topsqlEnabled === false) {\n      form.setFieldsValue({ tikv_network_io_collection: false })\n    }\n  }, [topsqlEnabled, form])\n\n  return (\n    <>\n      {combinedError.length > 0 && <ErrorBar errors={combinedError} />}\n      {combinedLoading && <Skeleton active={true} paragraph={{ rows: 6 }} />}\n      {!combinedLoading && initialConfig && (\n        <Form\n          layout=\"vertical\"\n          form={form}\n          initialValues={{\n            ...initialConfig,\n            tikv_network_io_collection:\n              initialTikvNetworkIoCollection?.enable ?? false\n          }}\n          onFinish={handleSubmit}\n        >\n          <Form.Item\n            valuePropName=\"checked\"\n            label={t('topsql.settings.enable')}\n            extra={t('topsql.settings.enable_tooltip')}\n          >\n            <Form.Item noStyle name=\"enable\" valuePropName=\"checked\">\n              <Switch\n                data-e2e=\"topsql_settings_enable\"\n                disabled={!isWriteable}\n              />\n            </Form.Item>\n          </Form.Item>\n\n          <Form.Item\n            valuePropName=\"checked\"\n            label={t('topsql.settings.tikv_network_io_collection')}\n            extra={tikvNetworkIoCollectionTooltip}\n          >\n            <div className={styles.switchWithStatus}>\n              <Form.Item\n                noStyle\n                name=\"tikv_network_io_collection\"\n                valuePropName=\"checked\"\n              >\n                <Switch\n                  data-e2e=\"topsql_settings_tikv_network_io_collection\"\n                  disabled={!isWriteable || topsqlEnabled === false}\n                  className={\n                    showTikvNetworkIoCollectionPartialState\n                      ? styles.partialSwitch\n                      : undefined\n                  }\n                  checkedChildren={\n                    showTikvNetworkIoCollectionPartialState ? '-' : undefined\n                  }\n                  unCheckedChildren={\n                    showTikvNetworkIoCollectionPartialState ? '-' : undefined\n                  }\n                />\n              </Form.Item>\n              {tikvStatusText && (\n                <span className={styles.switchStatus}>{tikvStatusText}</span>\n              )}\n            </div>\n          </Form.Item>\n          <DrawerFooter>\n            <Space>\n              <Button\n                type=\"primary\"\n                htmlType=\"submit\"\n                loading={submitting}\n                disabled={!isWriteable}\n                data-e2e=\"topsql_settings_save\"\n              >\n                {t('topsql.settings.actions.save')}\n              </Button>\n              <Button onClick={onClose}>\n                {t('topsql.settings.actions.cancel')}\n              </Button>\n            </Space>\n          </DrawerFooter>\n        </Form>\n      )}\n    </>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/pages/List/legendAction.ts",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\nimport { LegendPath } from '@elastic/charts'\n\n// HACK: Use elastic-charts internal api for the legend hover\n// sync with https://github.com/elastic/elastic-charts/blob/master/packages/charts/src/state/actions/legend.ts\n\nexport const ON_LEGEND_ITEM_OVER = 'ON_LEGEND_ITEM_OVER'\n\nexport const ON_LEGEND_ITEM_OUT = 'ON_LEGEND_ITEM_OUT'\n\ninterface LegendItemOverAction {\n  type: typeof ON_LEGEND_ITEM_OVER\n  legendPath: LegendPath\n}\n\ninterface LegendItemOutAction {\n  type: typeof ON_LEGEND_ITEM_OUT\n}\n\nfunction onLegendItemOverAction(legendPath: LegendPath): LegendItemOverAction {\n  return { type: ON_LEGEND_ITEM_OVER, legendPath }\n}\n\nfunction onLegendItemOutAction(): LegendItemOutAction {\n  return { type: ON_LEGEND_ITEM_OUT }\n}\n\nexport const onLegendItemOver = (chart: any, key: string) => {\n  const legendItems = chart.chartStore\n    .getState()\n    .internalChartState.getLegendItems(chart.chartStore.getState())\n  if (!legendItems?.length) {\n    return\n  }\n  const item = legendItems.find((it) => it.seriesIdentifiers[0].specId === key)\n  if (!item) {\n    return\n  }\n\n  chart.chartStore.dispatch(onLegendItemOverAction(item.path))\n}\n\nexport const onLegendItemOut = (chart) => {\n  chart.chartStore.dispatch(onLegendItemOutAction())\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/translations/en.yaml",
    "content": "topsql:\n  nav_title: Top SQL\n  alert_header:\n    title: Feature Not Enabled\n    body: Top SQL feature is not enabled. The history data are available. You can modify settings to enable the feature and wait for new data being collected.\n    settings: Settings\n  settings:\n    title: Settings\n    open_settings: Open Settings\n    disable_feature: Disable Top SQL Feature\n    disable_warning: Are you sure want to disable this feature?\n    enable: Enable Feature\n    enable_tooltip: Whether Top SQL feature is enabled. When enabled, there will be small overhead.\n    tikv_network_io_collection: Enable TiKV Network IO collection (multi-dimensional)\n    tikv_network_io_collection_tooltip: When enabled, TiKV network traffic, logical IO, and other dimensions can be collected, but this increases storage and query overhead. Newly added TiKV nodes will not enable collection automatically. To auto-enable for new nodes, update the TiKV default config via TiUP (see docs).\n    tikv_network_io_collection_tooltip_partial: Some TiKV nodes do not have collection enabled. You can click the switch to enable all, or update the TiKV default config via TiUP (see docs).\n    tikv_network_io_collection_disabled_by_topsql: (Disabled when TopSQL is off)\n    tikv_network_io_collection_status:\n      on: '(Current: all enabled)'\n      off: '(Current: all disabled)'\n      partial: '(Current: partially enabled)'\n    disabled_result:\n      title: Feature Not Enabled\n      sub_title: Top SQL feature is not enabled. You can modify settings to enable the feature and wait for new data being collected.\n    actions:\n      save: Save\n      close: Disable\n      cancel: Cancel\n    enable_info:\n      title: Success\n      content: Top SQL is enabled now and is collecting data. You need to wait for about 1 minute to view this data.\n    tikv_network_io_collection_info:\n      title: Success\n      content: TiKV Network IO collection has been enabled and is being applied to all current TiKV nodes. You may need to wait for about 1 minute to see data for related dimensions. Note that this is not applied automatically to newly scaled-out TiKV nodes. To make it effective for new nodes, update the TiKV default config via tiup and enable it there as well.\n      content_prefix: TiKV Network IO collection has been enabled and is being applied to all current TiKV nodes. You may need to wait for about 1 minute to see data for related dimensions.\n      content_emphasis: Note that this is not applied automatically to newly scaled-out TiKV nodes. To make it effective for new nodes, update the TiKV default config via tiup and enable it there as well.\n    tikv_network_io_collection_partial_info:\n      title: TiKV Network IO collection is not fully enabled\n      content: Some TiKV nodes still have collection disabled, so new data may be incomplete. You can retry later, or update the TiKV default config via TiUP (see docs).\n      action: View docs\n    help: Help\n    help_url: https://docs.pingcap.com/tidb/dev/top-sql\n  tikv_network_io_collection_tip:\n    title: TiKV Network IO collection is disabled\n    body: In the current state, historical data will be shown if available. New data requires enabling TiKV network/logical IO collection.\n    body_partial: The selected dimension depends on TiKV network/logical IO collection. Some TiKV nodes are not enabled. Historical data may still be shown, but new data may be incomplete.\n    action: Go to settings\n  refresh: Refresh\n  chart:\n    cpu_time: CPU Time\n    network_bytes: Network Bytes\n    logical_io_bytes: Logical IO Bytes\n  table:\n    description: The following table shows which top {{topN}} queries are contributing the most to load in the current time range. Click one to see details.\n    description_no_recent_data: There is no data currently. You need to wait for about 1 minute for new data being collected.\n    others: Others\n    others_tooltip: All of other non Top {{topN}} Items\n    fields:\n      cpu_time: Total CPU Time\n      network_bytes: Total Network Bytes\n      logical_io_bytes: Total Logical IO Bytes\n      sql: SQL Statement\n      table: Table Name\n      db: Database Name\n    actions:\n      search_in_statements: Search in SQL Statements\n  detail:\n    title: SQL Statement Details by Plan\n    overall: Overall\n    overall_tooltip: The execution details of all plans of this statement\n    no_plan: Plan Not Available\n    no_plan_tooltip: This statement is not a query or the statement plan was being generated\n    fields:\n      cpu_time: Total CPU Time\n      network_bytes: Total Network Bytes\n      logical_io_bytes: Total Logical IO Bytes\n      plan: Plan\n      exec_count_per_sec: Call/sec\n      scan_records_per_sec: Scan Rows/sec\n      scan_indexes_per_sec: Scan Indexes/sec\n      duration_per_exec_ms: Latency/call\n  detail_content:\n    fields:\n      sql_text: Statement Template\n      sql_text_tooltip: Similar queries have same statement template even for different query parameters\n      sql_digest: Query Template ID\n      sql_digest_tooltip: a.k.a. Query digest\n      plan_digest: Plan Template ID\n      plan_digest_tooltip: a.k.a. Plan digest\n      plan: Execution Plan\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/translations/zh.yaml",
    "content": "topsql:\n  nav_title: Top SQL\n  alert_header:\n    title: 该功能未启用\n    body: Top SQL 功能未启用，可查看历史数据。您可以修改设置打开该功能后等待新数据收集。\n    settings: 设置\n  settings:\n    title: 设置\n    open_settings: 打开设置\n    disable_feature: 关闭 Top SQL 功能\n    disable_warning: 确认要关闭该功能吗？\n    enable: 启用功能\n    enable_tooltip: 是否启用 Top SQL 功能，关闭后将只能看到历史数据，但能提升少量 {{distro.tidb}} 性能。\n    tikv_network_io_collection: 开启 TiKV 网络 IO 采集（多维度）\n    tikv_network_io_collection_tooltip: 开启后可采集 TiKV 网络流量，逻辑 IO 等多维度数据，不过会增加一定的存储和查询开销。全新 TiKV 节点不会自动开启采集，若需自动开启请通过 TiUP 修改 TiKV 默认配置（请参考文档）。\n    tikv_network_io_collection_tooltip_partial: 存在未开启采集的 TiKV 节点，可点击按钮全部开启，或者通过 TiUP 修改 TiKV 默认配置（请参考文档）\n    tikv_network_io_collection_disabled_by_topsql: （TopSQL 未开启时该开关强制关闭）\n    tikv_network_io_collection_status:\n      on: （当前状态：全开）\n      off: （当前状态：全关）\n      partial: （当前状态：部分开）\n    disabled_result:\n      title: 该功能未启用\n      sub_title: Top SQL 功能未启用。您可以修改设置打开该功能后等待新数据收集。\n    actions:\n      save: 保存\n      close: 确认\n      cancel: 取消\n    enable_info:\n      title: 成功\n      content: Top SQL 功能现在已启用，正在收集数据。您需要等待大约 1 分钟时间以便看到该数据。\n    tikv_network_io_collection_info:\n      title: 成功\n      content: 已开启 TiKV 网络 IO 采集，正在下发到所有当前 TiKV 节点。您可能需要等待约 1 分钟以便看到对应维度的数据。注意，对新扩容的 TiKV 节点不会自动生效，如需生效请通过 tiup 修改 TiKV 默认配置并同步开启。\n      content_prefix: 已开启 TiKV 网络 IO 采集，正在下发到所有当前 TiKV 节点。您可能需要等待约 1 分钟以便看到对应维度的数据。\n      content_emphasis: 注意，对新扩容的 TiKV 节点不会自动生效，如需生效请通过 tiup 修改 TiKV 默认配置并同步开启。\n    tikv_network_io_collection_partial_info:\n      title: TiKV 网络 IO 采集未完全开启\n      content: 检测到仍有 TiKV 节点未开启采集，新数据可能不完整。您可以稍后重试，或者通过 TiUP 修改 TiKV 默认配置（请参考文档）。\n      action: 查看文档\n    help: 帮助\n    help_url: https://docs.pingcap.com/zh/tidb/dev/top-sql\n  tikv_network_io_collection_tip:\n    title: TiKV 网络 IO 采集未开启\n    body: 当前状态，历史数据若有则会展示，新数据需要开启 TiKV 网络/逻辑 IO 采集，\n    body_partial: 当前选择的维度依赖 TiKV 的网络/逻辑 IO 采集，检测到部分 TiKV 节点未开启。历史数据仍可展示，但新数据可能不完整。\n    action: 去设置\n  refresh: 刷新\n  chart:\n    cpu_time: CPU 耗时\n    network_bytes: 网络字节数\n    logical_io_bytes: 逻辑 IO 字节数\n  table:\n    description: 以下表格展示了当前时间范围内消耗负载最多的 {{topN}} 类 SQL 查询，点击后可进一步显示详情。\n    description_no_recent_data: 当前暂无数据，您需要等待约 1 分钟完成新数据采集。\n    others: 其他\n    others_tooltip: 所有其他非 Top {{topN}} 的条目\n    fields:\n      cpu_time: 累计 CPU 耗时\n      network_bytes: 累计网络字节数\n      logical_io_bytes: 累计逻辑 IO 字节数\n      sql: SQL 语句\n      table: 表名\n      db: 数据库名\n    actions:\n      search_in_statements: 在 SQL 语句分析中搜索\n  detail:\n    title: SQL 详情\n    overall: 总计\n    overall_tooltip: 该语句在所有执行计划上的总计详情\n    no_plan: 无执行计划\n    no_plan_tooltip: 该语句不是查询语句，或当时执行计划正在生成中\n    fields:\n      cpu_time: 累计 CPU 耗时\n      network_bytes: 累计网络字节数\n      logical_io_bytes: 累计逻辑 IO 字节数\n      plan: 执行计划\n      exec_count_per_sec: Call/sec\n      scan_records_per_sec: Scan Rows/sec\n      scan_indexes_per_sec: Scan Indexes/sec\n      duration_per_exec_ms: Latency/call\n  detail_content:\n    fields:\n      sql_text: SQL 模板\n      sql_text_tooltip: 相似的 SQL 查询即使查询参数不一样也具有相同的 SQL 模板\n      sql_digest: SQL 模板 ID\n      sql_digest_tooltip: SQL 模板的唯一标识（SQL 指纹）\n      plan_digest: Plan 模板 ID\n      plan_digest_tooltip: Plan 模板的唯一标识（Plan 指纹）\n      plan: 执行计划\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/utils/specialRecord.ts",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\nimport type { PlanRecord } from '../pages/List/ListDetail/ListDetailTable'\nimport type { SQLRecord } from '../pages/List/ListTable'\nimport { OrderBy } from '../pages/List/List'\n\nconst OVERALL_IDENTIFIER = '__OVERALL_IDENTIFIER__'\n\nexport const isOverallRecord = (r: PlanRecord) => {\n  return r.plan_digest === OVERALL_IDENTIFIER\n}\n\nexport const createOverallRecord = (\n  record: SQLRecord,\n  orderBy: OrderBy\n): PlanRecord => {\n  return {\n    plan_digest: OVERALL_IDENTIFIER,\n    cpuTime: record.cpuTime || 0,\n    networkBytes: record.networkBytes || 0,\n    logicalIoBytes: record.logicalIoBytes || 0,\n    exec_count_per_sec: record.exec_count_per_sec,\n    scan_records_per_sec: record.scan_records_per_sec,\n    scan_indexes_per_sec: record.scan_indexes_per_sec,\n    duration_per_exec_ms: record.duration_per_exec_ms\n  }\n}\n\nexport const isOthersRecord = (r: SQLRecord) => {\n  return r.is_other || r.text === 'other'\n}\n\nexport const isSummaryByRecord = (r: SQLRecord) => {\n  return (r.text?.length ?? 0) > 0\n}\n\nconst NO_PLAN_IDENTIFIER = '__NO_PLAN_IDENTIFIER__'\n\nexport const convertNoPlanRecord = (r: PlanRecord) => {\n  const _r = { ...r }\n  if (isNoPlanRecord(_r)) {\n    _r.plan_digest = NO_PLAN_IDENTIFIER\n  }\n  return _r\n}\n\nexport const isNoPlanRecord = (r: PlanRecord) => {\n  return !r.plan_digest || r.plan_digest === NO_PLAN_IDENTIFIER\n}\n\nexport const isUnknownSQLRecord = (r: SQLRecord) => {\n  return !r.sql_text && !r.is_other\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/utils/telemetry.ts",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\nimport { mixpanel } from '@lib/utils/telemetry'\nimport { TimeRange } from '@lib/components'\n\nexport const telemetry = {\n  openSelectInstance() {\n    mixpanel.track('TopSQL: Open Select Instance')\n  },\n  finishSelectInstance(type: string) {\n    mixpanel.track('TopSQL: Finish Select Instance', { type })\n  },\n  openTimeRangePicker() {\n    mixpanel.track('TopSQL: Open Time Range Picker')\n  },\n  selectTimeRange(v: TimeRange) {\n    mixpanel.track('TopSQL: Select Time Range', v)\n  },\n  clickZoomOut(timestamps: [number, number]) {\n    mixpanel.track('TopSQL: Click Zoom Out Button', { timestamps })\n  },\n  dndZoomIn(timestamps: [number, number]) {\n    mixpanel.track('TopSQL: Drag & Drop Zoom In', { timestamps })\n  },\n  clickRefresh() {\n    mixpanel.track('TopSQL: Click Refresh')\n  },\n  clickAutoRefresh() {\n    mixpanel.track('TopSQL: Click Auto Refresh Dropdown')\n  },\n  selectAutoRefreshOption(seconds: number) {\n    mixpanel.track('TopSQL: Select Auto Refresh Option', { seconds })\n  },\n  clickSettings(type: 'firstTimeTips' | 'settingIcon' | 'bannerTips') {\n    mixpanel.track('TopSQL: Click Settings', { type })\n  },\n  saveSettings(settings: Record<string, any>) {\n    mixpanel.track('TopSQL: Save Settings', { settings })\n  },\n  clickStatement(index: number, isOther: boolean) {\n    mixpanel.track('TopSQL: Click Statement', {\n      rank: index + 1,\n      isOther\n    })\n  },\n  clickPlan(index: number) {\n    mixpanel.track('TopSQL: Click Plan', { rank: index + 1 })\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSQL/utils/useRecordSelection.ts",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\nimport { useMemo, useEffect } from 'react'\nimport {\n  Selection,\n  SelectionMode,\n  IObjectWithKey\n} from 'office-ui-fabric-react/lib/Selection'\nimport { useGetSet, useSessionStorage } from 'react-use'\nimport { useMemoizedFn } from 'ahooks'\n\ninterface Props<T> {\n  storageKey: string\n  selections: T[]\n  options?: {\n    getKey: (s: T) => string\n    canSelectItem?: (s: T) => boolean\n  }\n}\n\nexport function useRecordSelection<T>({\n  storageKey,\n  selections,\n  options\n}: Props<T>) {\n  const memoOption = useMemo(\n    () => ({\n      getKey: options?.getKey || (() => 'key'),\n      canSelectItem: options?.canSelectItem || (() => true)\n    }),\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    []\n  )\n\n  const [selectedRecordKey, _setSelectedRecordKey] = useSessionStorage<\n    string | null\n  >(storageKey, null)\n  const [getInternalKey, setInternalKey] = useGetSet(selectedRecordKey)\n  const setSelectedRecordKey = useMemoizedFn((k: string) => {\n    _setSelectedRecordKey(k)\n    setInternalKey(k)\n  })\n  const selectedRecord = useMemo(\n    () => selections.find((r) => memoOption.getKey(r) === selectedRecordKey),\n    [selections, selectedRecordKey, memoOption]\n  )\n  const selection = useMemo(() => {\n    const s = new Selection({\n      selectionMode: SelectionMode.single,\n      getKey: memoOption.getKey as\n        | ((\n            item: IObjectWithKey,\n            index?: number | undefined\n          ) => string | number)\n        | undefined,\n      canSelectItem: memoOption.canSelectItem as\n        | ((item: IObjectWithKey, index?: number | undefined) => boolean)\n        | undefined,\n      onSelectionChanged: () => {\n        const r = s.getSelection()[0] as T\n        if (!r) {\n          // A hack to fix the selection zone bug, SelectionZone.tsx L330\n          // It will clear the selection state when click disabled item.\n          // So we need to reselect the correct target.\n          const internalKey = getInternalKey()\n          const isSelectedItemInSelections = s\n            .getItems()\n            .find((r) => memoOption.getKey(r as T) === internalKey)\n          if (!!internalKey && isSelectedItemInSelections) {\n            s.selectToKey(internalKey)\n          }\n          return\n        }\n\n        const rk = memoOption.getKey(r)\n        if (rk !== getInternalKey()) {\n          setSelectedRecordKey(rk)\n        }\n      }\n    })\n    return s\n  }, [memoOption, getInternalKey, setSelectedRecordKey])\n\n  useEffect(() => {\n    // Selected record will be cleared by selection itself when update items\n    // So we need manual keep the selected state in the selection\n    const isRecordInSelections =\n      !!selectedRecordKey &&\n      selection\n        .getItems()\n        .find((tr) => memoOption.getKey(tr as T) === selectedRecordKey)\n    if (!selection.getSelection().length && isRecordInSelections) {\n      selection.selectToKey(selectedRecordKey!)\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [selections])\n\n  return {\n    selectedRecord,\n    selectedRecordKey,\n    selection\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSlowQuery/context.ts",
    "content": "import { createContext, useContext } from 'react'\n\ninterface ISlowQuery {}\n\ninterface ITimeWindow {\n  begin_time: number\n  end_time: number\n}\n\nexport interface ITopSlowQueryConfig {\n  // for clinic\n  orgName?: string\n  clusterName?: string\n  userName?: string\n}\n\nexport type TopSlowQueryCtxValue = {\n  // api\n  api: {\n    getAvailableTimeWindows(params: {\n      from: number\n      to: number\n      duration: number\n    }): Promise<ITimeWindow[]>\n\n    getMetrics: (params: {\n      start: number\n      end: number\n    }) => Promise<[number, number][]>\n\n    getDatabaseList(params: { start: number; end: number }): Promise<string[]>\n\n    getTopSlowQueries(params: {\n      start: number\n      end: number\n      order: string\n      dbs: string[]\n      internal: string\n      stmtKinds: string[]\n    }): Promise<ISlowQuery[]>\n  }\n\n  cfg: ITopSlowQueryConfig\n}\n\nexport const TopSlowQueryContext = createContext<TopSlowQueryCtxValue | null>(\n  null\n)\n\nexport const useTopSlowQueryContext = () => {\n  const context = useContext(TopSlowQueryContext)\n\n  if (!context) {\n    throw new Error('TopSlowQueryContext must be used within a provider')\n  }\n\n  return context\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSlowQuery/index.tsx",
    "content": "import React from 'react'\nimport { HashRouter as Router, Routes, Route } from 'react-router-dom'\n\nimport { Root } from '@lib/components'\nimport { addTranslations } from '@lib/utils/i18n'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\n\nimport translations from './translations'\nimport { TopSlowQueryList } from './pages/List'\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query'\n\naddTranslations(translations)\n\n// Create a client\nconst queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      refetchOnWindowFocus: false,\n      retry: 1\n      // refetchOnMount: false,\n      // refetchOnReconnect: false,\n    }\n  }\n})\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/top_slowquery\" element={<TopSlowQueryList />} />\n    </Routes>\n  )\n}\n\nexport default function () {\n  return (\n    // Provide the client to your App\n    <QueryClientProvider client={queryClient}>\n      <Root>\n        <Router>\n          <AppRoutes />\n        </Router>\n      </Root>\n    </QueryClientProvider>\n  )\n}\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSlowQuery/pages/CountChart.tsx",
    "content": "import React, { useMemo } from 'react'\nimport {\n  Axis,\n  Chart,\n  Position,\n  ScaleType,\n  Settings,\n  timeFormatter,\n  BrushEvent,\n  BarSeries\n} from '@elastic/charts'\nimport { DEFAULT_CHART_SETTINGS } from '@lib/utils/charts'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\nimport { TimeRangeValue } from 'metrics-chart'\n\nexport function CountChart({\n  data,\n  timeRange,\n  onSelectTimeRange\n}: {\n  data: [number, number][]\n  timeRange: TimeRangeValue\n  onSelectTimeRange?: (timeRange: TimeRangeValue) => void\n}) {\n  const convertedData = useMemo(() => {\n    return (data || []).map(([time, count]) => [time * 1000, count])\n  }, [data])\n\n  function onBrushEnd(e: BrushEvent) {\n    if (!e.x) {\n      return\n    }\n\n    let value: [number, number]\n    const tr = e.x.map((d) => d / 1000)\n    const delta = tr[1] - tr[0]\n    if (delta < 60) {\n      const offset = Math.floor(delta / 2)\n      value = [Math.ceil(tr[0] + offset - 30), Math.floor(tr[1] - offset + 30)]\n    } else {\n      value = [Math.ceil(tr[0]), Math.floor(tr[1])]\n    }\n    onSelectTimeRange?.(value)\n  }\n\n  return (\n    <Chart>\n      <Settings\n        {...DEFAULT_CHART_SETTINGS}\n        showLegend={false}\n        onBrushEnd={onSelectTimeRange ? onBrushEnd : undefined}\n        xDomain={{\n          min: timeRange[0] * 1000,\n          max: timeRange[1] * 1000\n        }}\n      />\n      <Axis\n        id=\"bottom\"\n        position={Position.Bottom}\n        showOverlappingTicks\n        tickFormat={timeFormatter('MM-DD HH:mm')}\n      />\n      <Axis\n        id=\"left\"\n        title=\"Count\"\n        position={Position.Left}\n        showOverlappingTicks\n        tickFormat={(v) => getValueFormat('short')(v, 0, 1)}\n        ticks={5}\n      />\n      <BarSeries\n        id=\"count\"\n        xScaleType={ScaleType.Time}\n        yScaleType={ScaleType.Linear}\n        xAccessor={0}\n        yAccessors={[1]}\n        data={convertedData}\n      />\n    </Chart>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSlowQuery/pages/List.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.container {\n  display: flex;\n  height: 100%;\n  flex-direction: column;\n  .chart_container {\n    margin: 24px 24px 24px 48px;\n    height: 200px;\n  }\n}\n\n.sorted_column_header {\n  background-color: #f3f2f1;\n}\n\n.slow_hint_container {\n  position: relative;\n}\n\n.slow_hint {\n  position: absolute;\n  left: 0;\n  right: 0;\n  top: 0;\n  bottom: 0;\n\n  display: flex;\n  align-items: center;\n  justify-content: center;\n\n  span {\n    color: #888;\n    background-color: white;\n    padding: 8px;\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSlowQuery/pages/List.tsx",
    "content": "import React, { useRef, useMemo } from 'react'\nimport { Space, Select, Typography, Button, Tag, Skeleton } from 'antd'\nimport { CaretLeftOutlined, CaretRightOutlined } from '@ant-design/icons'\n\nimport {\n  Card,\n  MultiSelect,\n  TimeRangeValue,\n  toTimeRangeValue\n} from '@lib/components'\n\nimport styles from './List.module.less'\nimport { useTopSlowQueryContext } from '../context'\nimport { Link } from 'react-router-dom'\nimport { useTopSlowQueryUrlState } from '../uilts/url-state'\nimport {\n  DEFAULT_TIME_RANGE,\n  DURATIONS,\n  ORDER_BY,\n  STMT_KINDS\n} from '../uilts/helpers'\nimport { useQuery } from '@tanstack/react-query'\nimport dayjs from 'dayjs'\nimport { TopSlowQueryListTable } from './ListTable'\nimport { CountChart } from './CountChart'\nimport { telemetry } from '../uilts/telemetry'\n\nexport function TopSlowQueryList() {\n  const containerRef = useRef<HTMLDivElement>(null)\n\n  return (\n    <div className={styles.container} ref={containerRef}>\n      <Card noMarginBottom>\n        <ClusterInfoHeader />\n      </Card>\n\n      <Card noMarginTop>\n        <TimeWindowSelect />\n        <SlowQueryCountChart />\n\n        <Typography.Title level={5}>Top 10</Typography.Title>\n        <TopSlowQueryFilters />\n        <TopSlowQueryListTable />\n      </Card>\n    </div>\n  )\n}\n\nfunction ClusterInfoHeader() {\n  const ctx = useTopSlowQueryContext()\n  // only for clinic\n  const clusterInfo = useMemo(() => {\n    const infos: string[] = []\n    if (ctx?.cfg.orgName) {\n      infos.push(`Org: ${ctx?.cfg.orgName}`)\n    }\n    if (ctx?.cfg.clusterName) {\n      infos.push(`Cluster: ${ctx?.cfg.clusterName}`)\n    }\n    return infos.join(' | ')\n  }, [ctx?.cfg.orgName, ctx?.cfg.clusterName])\n\n  if (!clusterInfo) return null\n\n  return (\n    <div\n      style={{\n        marginBottom: 16,\n        display: 'flex',\n        flexDirection: 'row-reverse',\n        justifyContent: 'space-between'\n      }}\n    >\n      {clusterInfo}\n      <span>\n        <span style={{ fontSize: 18, fontWeight: 600 }}>\n          <span>Top SlowQueries </span>\n          <Tag color=\"geekblue\">beta</Tag>\n          <span>| </span>\n          <Link to=\"/slow_query\" onClick={() => telemetry.clickSlowQueryTab()}>\n            Slow Query Logs\n          </Link>\n        </span>\n      </span>\n    </div>\n  )\n}\n\nfunction useTimeWindows() {\n  const ctx = useTopSlowQueryContext()\n  const { duration, tw, setTw, timeRange } = useTopSlowQueryUrlState()\n\n  const query = useQuery({\n    queryKey: [\n      'top_slowquery_time_windows',\n      duration,\n      timeRange,\n      ctx.cfg.orgName,\n      ctx.cfg.clusterName\n    ],\n    queryFn: () => {\n      const timeVal = toTimeRangeValue(timeRange)\n      return ctx.api.getAvailableTimeWindows({\n        from: timeVal[0],\n        to: timeVal[1],\n        duration\n      })\n    },\n    onSuccess(data) {\n      if (data.length === 0) {\n        return\n      }\n      if (data.some((d) => d.begin_time === tw[0] && d.end_time === tw[1])) {\n        return\n      }\n      setTw(`${data[0].begin_time}-${data[0].end_time}`)\n    }\n  })\n  return query\n}\n\nconst timezone = dayjs().format('UTCZ')\nfunction TimeWindowSelect() {\n  const { duration, setQueryParams, tw, setTw } = useTopSlowQueryUrlState()\n  const { data: availableTimeWindows } = useTimeWindows()\n\n  function newerTw() {\n    if (!availableTimeWindows) {\n      return\n    }\n    const idx = availableTimeWindows.findIndex(\n      (item) => item.begin_time === tw[0] && item.end_time === tw[1]\n    )\n    if (idx === -1 || idx === 0) {\n      return\n    }\n    const item = availableTimeWindows[idx - 1]\n    setTw(`${item.begin_time}-${item.end_time}`)\n  }\n\n  function olderTw() {\n    if (!availableTimeWindows) {\n      return\n    }\n    const idx = availableTimeWindows.findIndex(\n      (item) => item.begin_time === tw[0] && item.end_time === tw[1]\n    )\n    if (idx === -1 || idx === availableTimeWindows.length - 1) {\n      return\n    }\n    const item = availableTimeWindows[idx + 1]\n    setTw(`${item.begin_time}-${item.end_time}`)\n  }\n\n  return (\n    <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\n      {/*\n        <div>\n          <span>Time Range: </span>\n          <TimeRangeSelector\n            value={timeRange}\n            onChange={setTimeRange}\n            recent_seconds={TIME_RANGE_RECENT_SECONDS}\n          />\n        </div>\n      */}\n\n      <div>\n        <span>Duration: </span>\n        <Select\n          style={{ width: 128 }}\n          value={duration}\n          onChange={(v) => {\n            setQueryParams({\n              duration: v,\n              from: DEFAULT_TIME_RANGE.value,\n              to: 'now'\n            })\n            telemetry.changeDuration(v)\n          }}\n        >\n          {DURATIONS.map((item) => (\n            <Select.Option value={item.value} key={item.label}>\n              {item.label}\n            </Select.Option>\n          ))}\n        </Select>\n      </div>\n\n      <div>\n        <span>Time Range: </span>\n        <Select\n          style={{ width: 240 }}\n          value={tw[0] === 0 ? '' : `${tw[0]}-${tw[1]}`}\n          onChange={(v) => {\n            setTw(v)\n            telemetry.changeTimeRange()\n          }}\n        >\n          {(availableTimeWindows ?? []).map((item) => {\n            const bd = dayjs.unix(item.begin_time)\n            const ed = dayjs.unix(item.end_time)\n            let ts = ''\n            if (bd.date() === ed.date()) {\n              ts = `${bd.format('MM-DD HH:mm')}~${ed.format('HH:mm')}`\n            } else {\n              ts = `${bd.format('MM-DD HH:mm')}~${ed.format('MM-DD HH:mm')}`\n            }\n\n            return (\n              <Select.Option\n                value={`${item.begin_time}-${item.end_time}`}\n                key={`${item.begin_time}-${item.end_time}`}\n              >\n                {ts}\n              </Select.Option>\n            )\n          })}\n        </Select>\n        <span>\n          {' '}\n          <Button icon={<CaretLeftOutlined />} onClick={olderTw} />{' '}\n          <Button icon={<CaretRightOutlined />} onClick={newerTw} />\n        </span>\n      </div>\n      <span style={{ marginLeft: 'auto' }}>Time Zone: {timezone}</span>\n    </div>\n  )\n}\n\nfunction useChartData() {\n  const ctx = useTopSlowQueryContext()\n  const { tw } = useTopSlowQueryUrlState()\n\n  const query = useQuery({\n    queryKey: [\n      'top_slowquery_chart_data',\n      ctx.cfg.orgName,\n      ctx.cfg.clusterName,\n      tw\n    ],\n    queryFn: () => {\n      return ctx.api.getMetrics({\n        start: tw[0],\n        end: tw[1]\n      })\n    },\n    enabled: !!tw[0]\n  })\n  return query\n}\n\nfunction SlowQueryCountChart() {\n  const { data: chartData, isLoading } = useChartData()\n  const { tw, setQueryParams } = useTopSlowQueryUrlState()\n\n  function onSelectTimeRange(timeRange: TimeRangeValue) {\n    const delta = timeRange[1] - timeRange[0]\n    let duration = 60 * 60\n    if (delta < 60 * 60) {\n      duration = 60 * 60\n    } else if (delta < 3 * 60 * 60) {\n      duration = 3 * 60 * 60\n    } else if (delta < 6 * 60 * 60) {\n      duration = 6 * 60 * 60\n    } else if (delta < 12 * 60 * 60) {\n      duration = 12 * 60 * 60\n    } else if (delta < 24 * 60 * 60) {\n      duration = 24 * 60 * 60\n    } else if (delta < 7 * 24 * 60 * 60) {\n      duration = 7 * 24 * 60 * 60\n    }\n    setQueryParams({ duration, from: timeRange[0], to: timeRange[0] })\n  }\n\n  return (\n    <div style={{ marginTop: 16, marginBottom: 24 }}>\n      <Typography.Title level={5}>Slow Query Count</Typography.Title>\n      <div style={{ height: 200 }}>\n        <Skeleton paragraph={{ rows: 4 }} active loading={isLoading}>\n          <CountChart\n            data={chartData ?? []}\n            timeRange={tw as TimeRangeValue}\n            onSelectTimeRange={onSelectTimeRange}\n          />\n        </Skeleton>\n      </div>\n    </div>\n  )\n}\n\nfunction useDatabaseList() {\n  const ctx = useTopSlowQueryContext()\n  const { tw } = useTopSlowQueryUrlState()\n\n  const query = useQuery({\n    queryKey: [\n      'top_slowquery_database_list',\n      ctx.cfg.orgName,\n      ctx.cfg.clusterName,\n      tw\n    ],\n    queryFn: () => {\n      return ctx.api.getDatabaseList({ start: tw[0], end: tw[1] })\n    },\n    enabled: !!tw[0]\n  })\n  return query\n}\n\nfunction TopSlowQueryFilters() {\n  const { tw, dbs, setDbs, order, setOrder, stmtKinds, setStmtKinds } =\n    useTopSlowQueryUrlState()\n  const { data: databaseList } = useDatabaseList()\n\n  return (\n    <Space style={{ marginBottom: 8 }}>\n      <div>\n        <span>Databases: </span>\n        {/* this component has a weird bug, sometimes it can't select item after changing the time window, use `key` can fix it */}\n        <MultiSelect.Plain\n          key={`${tw[0]}_${tw[1]}`}\n          placeholder=\"All Databases\"\n          columnTitle=\"Databases\"\n          value={dbs}\n          style={{ width: 150 }}\n          onChange={(v) => {\n            telemetry.changeDatabases()\n            setDbs(v)\n          }}\n          items={databaseList || []}\n        />\n      </div>\n\n      <div>\n        <span>Statement Kinds: </span>\n        <MultiSelect.Plain\n          placeholder=\"All Kinds\"\n          columnTitle=\"Statement Kind\"\n          value={stmtKinds}\n          style={{ width: 150 }}\n          onChange={(v) => {\n            telemetry.changeStmtKinds()\n            setStmtKinds(v)\n          }}\n          items={STMT_KINDS}\n        />\n      </div>\n\n      {/* <div>\n        <span>Internal: </span>\n        <Select style={{ width: 80 }} value={internal} onChange={setInternal}>\n          <Select.Option value=\"no\">No</Select.Option>\n          <Select.Option value=\"yes\">Yes</Select.Option>\n        </Select>\n      </div> */}\n\n      <div>\n        <span>Order by: </span>\n        <Select\n          style={{ width: 180 }}\n          value={order}\n          onChange={(v) => {\n            telemetry.changeOrder()\n            setOrder(v)\n          }}\n        >\n          {ORDER_BY.map((item) => (\n            <Select.Option value={item.value} key={item.value}>\n              {item.label}\n            </Select.Option>\n          ))}\n        </Select>\n      </div>\n    </Space>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSlowQuery/pages/ListTable.tsx",
    "content": "import React, { useEffect, useMemo, useState } from 'react'\nimport { useTopSlowQueryContext } from '../context'\nimport { useTopSlowQueryUrlState } from '../uilts/url-state'\nimport { useQuery } from '@tanstack/react-query'\nimport { CSVLink } from 'react-csv'\n\nimport { CardTable, HighlightSQL, TextWrap } from '@lib/components'\nimport {\n  ColumnActionsMode,\n  IColumn\n} from 'office-ui-fabric-react/lib/DetailsList'\nimport { Tooltip } from 'antd'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\nimport { useMemoizedFn } from 'ahooks'\nimport { useNavigate } from 'react-router-dom'\nimport openLink from '@lib/utils/openLink'\n\nimport styles from './List.module.less'\nimport { telemetry } from '../uilts/telemetry'\n\nfunction useTopSlowQueryData() {\n  const ctx = useTopSlowQueryContext()\n  const { tw, order, dbs, internal, stmtKinds } = useTopSlowQueryUrlState()\n\n  const query = useQuery({\n    queryKey: [\n      'top_slowquery_list',\n      ctx.cfg.orgName,\n      ctx.cfg.clusterName,\n      tw,\n      order,\n      dbs,\n      internal,\n      stmtKinds\n    ],\n    queryFn: () => {\n      return ctx.api.getTopSlowQueries({\n        start: tw[0],\n        end: tw[1],\n        order,\n        dbs,\n        internal,\n        stmtKinds\n      })\n    },\n    enabled: !!tw[0]\n  })\n  return query\n}\n\nexport function TopSlowQueryListTable() {\n  const { tw, dbs, order, setOrder } = useTopSlowQueryUrlState()\n  const { isLoading, isFetching, data: slowQueries } = useTopSlowQueryData()\n  const navigate = useNavigate()\n\n  const [loadSlow, setLoadSlow] = useState(false)\n  useEffect(() => {\n    if (!isFetching) {\n      setLoadSlow(false)\n      return\n    }\n    let timerId = window.setTimeout(() => {\n      setLoadSlow(true)\n    }, 10 * 1000)\n\n    return () => {\n      window.clearTimeout(timerId)\n    }\n  }, [isFetching])\n\n  const handleRowClick = useMemoizedFn(\n    (rec, _idx, ev: React.MouseEvent<HTMLElement>) => {\n      telemetry.clickTableRow()\n      openLink(\n        `/slow_query?from=${tw[0]}&to=${tw[1]}&digest=${\n          rec.sql_digest\n        }&dbs=${dbs.join(',')}`,\n        ev,\n        navigate\n      )\n    }\n  )\n\n  const columns: IColumn[] = useMemo(() => {\n    return [\n      {\n        name: 'Query',\n        key: 'sql_text',\n        minWidth: 100,\n        maxWidth: 500,\n        onRender: (row: any) => {\n          return (\n            <Tooltip\n              title={<HighlightSQL sql={row.sql_text} theme=\"dark\" />}\n              placement=\"right\"\n            >\n              <TextWrap>\n                <HighlightSQL sql={row.sql_text} compact />\n              </TextWrap>\n            </Tooltip>\n          )\n        }\n      },\n      {\n        name: 'SQL Digest',\n        key: 'sql_digest',\n        minWidth: 100,\n        maxWidth: 150,\n        onRender: (row: any) => {\n          return (\n            <Tooltip title={row.sql_digest}>\n              <TextWrap>{row.sql_digest}</TextWrap>\n            </Tooltip>\n          )\n        }\n      },\n      {\n        name: 'Total Latency',\n        headerClassName:\n          order === 'sum_latency' ? styles.sorted_column_header : '',\n        key: 'sum_latency',\n        fieldName: 'sum_latency',\n        minWidth: 100,\n        maxWidth: 150,\n        columnActionsMode: ColumnActionsMode.clickable,\n        onRender: (row: any) => {\n          return <span>{getValueFormat('s')(row.sum_latency, 1)}</span>\n        }\n      },\n      {\n        name: 'Max Latency',\n        headerClassName:\n          order === 'max_latency' ? styles.sorted_column_header : '',\n        key: 'max_latency',\n        fieldName: 'max_latency', // fieldName is used to sort\n        minWidth: 100,\n        maxWidth: 150,\n        columnActionsMode: ColumnActionsMode.clickable,\n        onRender: (row: any) => {\n          return <span>{getValueFormat('s')(row.max_latency, 1)}</span>\n        }\n      },\n      {\n        name: 'Avg Latency',\n        headerClassName:\n          order === 'avg_latency' ? styles.sorted_column_header : '',\n        key: 'avg_latency',\n        fieldName: 'avg_latency',\n        minWidth: 100,\n        maxWidth: 150,\n        columnActionsMode: ColumnActionsMode.clickable,\n        onRender: (row: any) => {\n          return <span>{getValueFormat('s')(row.avg_latency, 1)}</span>\n        }\n      },\n      {\n        name: 'Total Memory',\n        headerClassName:\n          order === 'sum_memory' ? styles.sorted_column_header : '',\n        key: 'sum_memory',\n        fieldName: 'sum_memory',\n        minWidth: 100,\n        maxWidth: 150,\n        columnActionsMode: ColumnActionsMode.clickable,\n        onRender: (row: any) => {\n          return <span>{getValueFormat('bytes')(row.sum_memory, 1)}</span>\n        }\n      },\n      {\n        name: 'Max Memory',\n        headerClassName:\n          order === 'max_memory' ? styles.sorted_column_header : '',\n        key: 'max_memory',\n        fieldName: 'max_memory',\n        minWidth: 100,\n        maxWidth: 150,\n        columnActionsMode: ColumnActionsMode.clickable,\n        onRender: (row: any) => {\n          return <span>{getValueFormat('bytes')(row.max_memory, 1)}</span>\n        }\n      },\n      {\n        name: 'Avg Memory',\n        headerClassName:\n          order === 'avg_memory' ? styles.sorted_column_header : '',\n        key: 'avg_memory',\n        fieldName: 'avg_memory',\n        minWidth: 100,\n        maxWidth: 150,\n        columnActionsMode: ColumnActionsMode.clickable,\n        onRender: (row: any) => {\n          return <span>{getValueFormat('bytes')(row.avg_memory, 1)}</span>\n        }\n      },\n      {\n        name: 'Total Count',\n        headerClassName: order === 'count' ? styles.sorted_column_header : '',\n        key: 'count',\n        fieldName: 'count',\n        minWidth: 100,\n        maxWidth: 150,\n        columnActionsMode: ColumnActionsMode.clickable,\n        onRender: (row: any) => {\n          return <span>{getValueFormat('short')(row.count, 0, 1)}</span>\n        }\n      },\n      {\n        name: 'Total Disk',\n        key: 'sum_disk',\n        fieldName: 'sum_disk',\n        minWidth: 100,\n        maxWidth: 120,\n        onRender: (row: any) => {\n          return <span>{getValueFormat('bytes')(row.sum_disk, 1)}</span>\n        }\n      }\n      // {\n      //   name: 'Database',\n      //   key: 'database',\n      //   minWidth: 100,\n      //   maxWidth: 150,\n      //   onRender: (row: any) => {\n      //     return (\n      //       <Tooltip title={row.schema_name}>\n      //         <TextWrap>{row.schema_name}</TextWrap>\n      //       </Tooltip>\n      //     )\n      //   }\n      // },\n      // {\n      //   name: 'Table',\n      //   key: 'table',\n      //   minWidth: 100,\n      //   maxWidth: 150,\n      //   onRender: (row: any) => {\n      //     return (\n      //       <Tooltip title={row.table_names}>\n      //         <TextWrap>{row.table_names}</TextWrap>\n      //       </Tooltip>\n      //     )\n      //   }\n      // }\n    ]\n  }, [order])\n  const csvHeaders = columns.map((c) => ({ label: c.name, key: c.key }))\n\n  return (\n    <div className={styles.slow_hint_container}>\n      {slowQueries && (\n        <CSVLink\n          data={slowQueries}\n          headers={csvHeaders}\n          filename=\"top-slowquery\"\n        >\n          Download to CSV\n        </CSVLink>\n      )}\n\n      <CardTable\n        cardNoMargin\n        loading={isLoading}\n        columns={columns}\n        items={slowQueries ?? []}\n        onRowClicked={handleRowClick}\n        orderBy={order}\n        onChangeOrder={setOrder}\n      />\n      {loadSlow && (\n        <div className={styles.slow_hint}>\n          <span>We are working to prepare the data, please be patient.</span>\n        </div>\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSlowQuery/translations/en.yaml",
    "content": "top_slowquery:\n  nav_title: Top SlowQuery\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSlowQuery/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSlowQuery/translations/zh.yaml",
    "content": "top_slowquery:\n  nav_title: Top SlowQuery\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSlowQuery/uilts/helpers.ts",
    "content": "import { TimeRange } from '@lib/components/TimeRangeSelector'\n\nexport const TIME_RANGE_RECENT_SECONDS = [\n  60 * 60,\n  3 * 60 * 60,\n  6 * 60 * 60,\n  12 * 60 * 60,\n  24 * 60 * 60,\n  7 * 24 * 60 * 60,\n  30 * 24 * 60 * 60\n]\n\nexport const DEFAULT_TIME_RANGE: TimeRange = {\n  type: 'recent',\n  value: TIME_RANGE_RECENT_SECONDS[6]\n}\n\nexport const DURATIONS = [\n  { label: '1 hour', value: 60 * 60 },\n  { label: '3 hours', value: 3 * 60 * 60 },\n  { label: '6 hours', value: 6 * 60 * 60 },\n  { label: '12 hours', value: 12 * 60 * 60 },\n  { label: '1 day', value: 24 * 60 * 60 },\n  { label: '3 days', value: 3 * 24 * 60 * 60 }\n]\n\nexport const STMT_KINDS = [\n  'AlterTable',\n  'AnalyzeTable',\n  'Begin',\n  'Change',\n  'Insert',\n  'Update',\n  'Commit',\n  'Delete',\n  'Select',\n  'Show',\n  'Set',\n  'Others'\n]\n\nexport const ORDER_BY = [\n  { label: 'Total Latency', value: 'sum_latency' },\n  { label: 'Max Latency', value: 'max_latency' },\n  { label: 'Avg Latency', value: 'avg_latency' },\n  { label: 'Total Memory', value: 'sum_memory' },\n  { label: 'Max Memory', value: 'max_memory' },\n  { label: 'Avg Memory', value: 'avg_memory' },\n  { label: 'Total Count', value: 'count' }\n]\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSlowQuery/uilts/telemetry.ts",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\nimport { mixpanel } from '@lib/utils/telemetry'\n\nexport const telemetry = {\n  clickSlowQueryTab() {\n    mixpanel.track('TopSlowquery: Click Slowquery Tab')\n  },\n\n  changeDuration(duration: number) {\n    mixpanel.track('TopSlowquery: Change Duration', { duration })\n  },\n  changeTimeRange() {\n    mixpanel.track('TopSlowquery: Change Time Range')\n  },\n\n  changeDatabases() {\n    mixpanel.track('TopSlowquery: Change Databases')\n  },\n  changeStmtKinds() {\n    mixpanel.track('TopSlowquery: Change Statement Kinds')\n  },\n  changeOrder() {\n    mixpanel.track('TopSlowquery: Change Order')\n  },\n\n  clickTableRow() {\n    mixpanel.track('TopSlowquery: Click Table Row')\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/TopSlowQuery/uilts/url-state.ts",
    "content": "import useUrlState from '@ahooksjs/use-url-state'\nimport {\n  TimeRange,\n  toURLTimeRange,\n  urlToTimeRange\n} from '@lib/components/TimeRangeSelector'\nimport { useCallback, useMemo } from 'react'\nimport { DEFAULT_TIME_RANGE, DURATIONS, ORDER_BY } from './helpers'\n\n// tw: time window (start-end)\ntype UrlState = Partial<\n  Record<\n    | 'from'\n    | 'to'\n    | 'duration'\n    | 'tw'\n    | 'order'\n    | 'dbs'\n    | 'internal'\n    | 'stmt_kinds',\n    string\n  >\n>\n\nexport function useTopSlowQueryUrlState() {\n  const [queryParams, setQueryParams] = useUrlState<UrlState>()\n\n  const timeRange = useMemo(() => {\n    const { from, to } = queryParams\n    if (from && to) {\n      return urlToTimeRange({ from, to })\n    }\n    return DEFAULT_TIME_RANGE\n  }, [queryParams.from, queryParams.to])\n\n  const setTimeRange = useCallback(\n    (newTimeRange: TimeRange) => {\n      setQueryParams({ ...toURLTimeRange(newTimeRange) })\n    },\n    [setQueryParams]\n  )\n\n  const duration: number = useMemo(() => {\n    const v = parseInt(queryParams.duration)\n    if (isNaN(v)) {\n      return DURATIONS[0].value\n    }\n    if (DURATIONS.some((s) => s.value === v)) {\n      return v\n    }\n    return DURATIONS[0].value\n  }, [queryParams.duration])\n  const setDuration = useCallback(\n    (v: number) => {\n      setQueryParams({ duration: v + '' })\n    },\n    [setQueryParams]\n  )\n\n  const tw = useMemo(() => {\n    const arr = queryParams.tw?.split('-')\n    if (arr && arr.length === 2) {\n      const s = parseInt(arr[0])\n      const e = parseInt(arr[1])\n      if (!isNaN(s) && !isNaN(e)) {\n        return [s, e]\n      }\n    }\n    return [0, 0]\n  }, [queryParams.tw])\n  const setTw = useCallback(\n    (v: string) => {\n      // v format: \"from-to\"\n      setQueryParams({ tw: v })\n    },\n    [setQueryParams]\n  )\n\n  const order = queryParams.order || ORDER_BY[0].value\n  const setOrder = useCallback(\n    (v: string) => setQueryParams({ order: v }),\n    [setQueryParams]\n  )\n\n  // dbs\n  const dbs = useMemo(() => {\n    const dbs = queryParams.dbs\n    return dbs ? dbs.split(',') : []\n  }, [queryParams.dbs])\n  const setDbs = useCallback(\n    (v: string[]) => {\n      setQueryParams({ dbs: v.join(',') })\n    },\n    [setQueryParams]\n  )\n\n  const stmtKinds = useMemo(() => {\n    const stmtTypes = queryParams.stmt_kinds\n    return stmtTypes ? stmtTypes.split(',') : []\n  }, [queryParams.stmt_kinds])\n  const setStmtKinds = useCallback(\n    (v: string[]) => {\n      setQueryParams({ stmt_kinds: v.join(',') })\n    },\n    [setQueryParams]\n  )\n\n  const internal = queryParams.internal || 'no'\n  const setInternal = useCallback(\n    (v: string) => setQueryParams({ internal: v }),\n    [setQueryParams]\n  )\n\n  return {\n    timeRange,\n    setTimeRange,\n\n    duration,\n    setDuration,\n\n    tw,\n    setTw,\n\n    order,\n    setOrder,\n\n    dbs,\n    setDbs,\n\n    stmtKinds,\n    setStmtKinds,\n\n    internal,\n    setInternal,\n\n    queryParams,\n    setQueryParams\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/UserProfile/components/Form.Language.tsx",
    "content": "import { Form, Select } from 'antd'\nimport React, { useCallback } from 'react'\nimport { DEFAULT_FORM_ITEM_STYLE } from '../utils/helper'\nimport { ALL_LANGUAGES } from '@lib/utils/i18n'\nimport _ from 'lodash'\nimport { useTranslation } from 'react-i18next'\n\nexport function LanguageForm() {\n  const { t, i18n } = useTranslation()\n\n  const handleLanguageChange = useCallback(\n    (langKey) => {\n      i18n.changeLanguage(langKey)\n    },\n    [i18n]\n  )\n\n  return (\n    <Form layout=\"vertical\" initialValues={{ language: i18n.language }}>\n      <Form.Item name=\"language\" label={t('user_profile.i18n.language')}>\n        <Select onChange={handleLanguageChange} style={DEFAULT_FORM_ITEM_STYLE}>\n          {_.map(ALL_LANGUAGES, (name, key) => {\n            return (\n              <Select.Option key={key} value={key}>\n                {name}\n              </Select.Option>\n            )\n          })}\n        </Select>\n      </Form.Item>\n    </Form>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/UserProfile/components/Form.PrometheusAddr.tsx",
    "content": "import { AnimatedSkeleton, Blink, ErrorBar } from '@lib/components'\nimport { useIsWriteable } from '@lib/utils/store'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { Button, Form, Input, Radio, Space, Typography } from 'antd'\nimport React, { useContext } from 'react'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { DEFAULT_FORM_ITEM_STYLE } from '../utils/helper'\nimport { UserProfileContext } from '../context'\n\nexport function PrometheusAddressForm() {\n  const ctx = useContext(UserProfileContext)\n\n  const { t } = useTranslation()\n  const isWriteable = useIsWriteable()\n  const [isChanged, setIsChanged] = useState(false)\n  const [isPosting, setIsPosting] = useState(false)\n  const handleValuesChange = useCallback(() => setIsChanged(true), [])\n  const { error, isLoading, data } = useClientRequest(\n    ctx!.ds.metricsGetPromAddress\n  )\n  const isInitialLoad = useRef(true)\n  const initialForm = useRef<any>(null) // Used for \"Cancel\" behaviour\n  const [form] = Form.useForm()\n\n  useEffect(() => {\n    if (data && isInitialLoad.current) {\n      isInitialLoad.current = false\n      form.setFieldsValue({\n        sourceType:\n          (data.customized_addr?.length ?? 0) > 0 ? 'custom' : 'deployment',\n        customAddr: data.customized_addr\n      })\n      initialForm.current = { ...form.getFieldsValue() }\n    }\n  }, [data, form])\n\n  const handleFinish = useCallback(\n    async (values) => {\n      let address = ''\n      if (values.sourceType === 'custom') {\n        address = values.customAddr || ''\n      }\n      try {\n        setIsPosting(true)\n        const resp = await ctx!.ds.metricsSetCustomPromAddress({ address })\n        const customAddr = resp?.data?.normalized_address ?? ''\n        form.setFieldsValue({ customAddr })\n        initialForm.current = { ...form.getFieldsValue() }\n        setIsChanged(false)\n      } finally {\n        setIsPosting(false)\n      }\n    },\n    [form, ctx]\n  )\n\n  const handleCancel = useCallback(() => {\n    form.setFieldsValue({ ...initialForm.current })\n    setIsChanged(false)\n  }, [form])\n\n  return (\n    <Blink activeId=\"profile.prometheus\">\n      <Form\n        layout=\"vertical\"\n        onValuesChange={handleValuesChange}\n        form={form}\n        onFinish={handleFinish}\n      >\n        <AnimatedSkeleton loading={isLoading}>\n          <Form.Item\n            name=\"sourceType\"\n            label={t('user_profile.service_endpoints.prometheus.title')}\n          >\n            <Radio.Group disabled={isLoading || error || !data || !isWriteable}>\n              <Space direction=\"vertical\">\n                {error && <ErrorBar errors={[error]} />}\n                <Radio value=\"deployment\">\n                  <Space>\n                    <span>\n                      {t(\n                        'user_profile.service_endpoints.prometheus.form.deployed'\n                      )}\n                    </span>\n                    <span>\n                      {(data?.deployed_addr?.length ?? 0) > 0 &&\n                        `(${data!.deployed_addr})`}\n                      {data && data.deployed_addr?.length === 0 && (\n                        <Typography.Text type=\"secondary\">\n                          (\n                          {t(\n                            'user_profile.service_endpoints.prometheus.form.not_deployed'\n                          )}\n                          )\n                        </Typography.Text>\n                      )}\n                    </span>\n                  </Space>\n                </Radio>\n                <Radio value=\"custom\">\n                  {t('user_profile.service_endpoints.prometheus.form.custom')}\n                </Radio>\n              </Space>\n            </Radio.Group>\n          </Form.Item>\n        </AnimatedSkeleton>\n        <Form.Item noStyle shouldUpdate>\n          {(f) =>\n            f.getFieldValue('sourceType') === 'custom' && (\n              <Form.Item\n                name=\"customAddr\"\n                label={t(\n                  'user_profile.service_endpoints.prometheus.custom_form.address'\n                )}\n                rules={[{ required: true }]}\n              >\n                <Input\n                  style={DEFAULT_FORM_ITEM_STYLE}\n                  placeholder=\"http://IP:PORT\"\n                  disabled={!isWriteable}\n                />\n              </Form.Item>\n            )\n          }\n        </Form.Item>\n        {isChanged && (\n          <Form.Item>\n            <Space>\n              <Button type=\"primary\" htmlType=\"submit\" loading={isPosting}>\n                {t('user_profile.service_endpoints.prometheus.form.update')}\n              </Button>\n              <Button onClick={handleCancel}>\n                {t('user_profile.service_endpoints.prometheus.form.cancel')}\n              </Button>\n            </Space>\n          </Form.Item>\n        )}\n      </Form>\n    </Blink>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/UserProfile/components/Form.SSO.tsx",
    "content": "import { CheckCircleFilled } from '@ant-design/icons'\nimport { SsoSSOImpersonationModel } from '@lib/client'\nimport { AnimatedSkeleton, ErrorBar } from '@lib/components'\nimport { useIsFeatureSupport, useIsWriteable } from '@lib/utils/store'\nimport { useChange } from '@lib/utils/useChange'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport {\n  Alert,\n  Button,\n  Checkbox,\n  Form,\n  Input,\n  Modal,\n  Space,\n  Switch,\n  Typography\n} from 'antd'\nimport React, { useContext } from 'react'\nimport { useCallback, useRef, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { DEFAULT_FORM_ITEM_STYLE } from '../utils/helper'\nimport { UserProfileContext } from '../context'\n\ninterface IUserAuthInputProps {\n  value?: SsoSSOImpersonationModel\n  onChange?: (value: SsoSSOImpersonationModel) => void\n}\n\nfunction isImpersonationNotFailed(imp?: SsoSSOImpersonationModel) {\n  return Boolean(\n    imp &&\n      imp.last_impersonate_status !== 'auth_fail' &&\n      imp.last_impersonate_status !== 'insufficient_privileges'\n  )\n}\n\nfunction UserAuthInput({ value, onChange }: IUserAuthInputProps) {\n  const ctx = useContext(UserProfileContext)\n\n  const { t } = useTranslation()\n  const [modalVisible, setModalVisible] = useState(false)\n  const [isPosting, setIsPosting] = useState(false)\n  const isWriteable = useIsWriteable()\n  const handleClose = useCallback(() => {\n    setModalVisible(false)\n  }, [])\n\n  const handleAuthnClick = useCallback(() => {\n    setModalVisible(true)\n  }, [])\n\n  const supportNonRootLogin = useIsFeatureSupport('nonRootLogin')\n\n  const handleFinish = useCallback(\n    async (data) => {\n      setIsPosting(true)\n      try {\n        const resp = await ctx!.ds.userSSOCreateImpersonation({\n          sql_user: data.user,\n          password: data.password\n        })\n        setModalVisible(false)\n        onChange?.(resp.data)\n      } finally {\n        setIsPosting(false)\n      }\n    },\n    [onChange, ctx]\n  )\n\n  return (\n    <>\n      {Boolean(!value) && (\n        <Space>\n          <Button onClick={handleAuthnClick} disabled={!isWriteable}>\n            {t('user_profile.sso.form.user.authn_button')}\n          </Button>\n        </Space>\n      )}\n      {Boolean(value) && (\n        <Space>\n          <span>{value!.sql_user}</span>\n\n          {isImpersonationNotFailed(value) && (\n            <Typography.Text type=\"success\">\n              <CheckCircleFilled />{' '}\n              {t('user_profile.sso.form.user.authn_status.ok')}\n            </Typography.Text>\n          )}\n          {value?.last_impersonate_status === 'auth_fail' && (\n            <Typography.Text type=\"danger\">\n              <CheckCircleFilled />{' '}\n              {t('user_profile.sso.form.user.authn_status.auth_failed')}\n            </Typography.Text>\n          )}\n          {value?.last_impersonate_status === 'insufficient_privileges' && (\n            <Typography.Text type=\"danger\">\n              <CheckCircleFilled />{' '}\n              {t(\n                'user_profile.sso.form.user.authn_status.insufficient_privileges'\n              )}\n            </Typography.Text>\n          )}\n\n          <Button onClick={handleAuthnClick} disabled={!isWriteable}>\n            {t('user_profile.sso.form.user.modify_authn_button')}\n          </Button>\n        </Space>\n      )}\n      <Modal\n        title={t('user_profile.sso.form.user.authn_dialog.title')}\n        visible={modalVisible}\n        destroyOnClose\n        onCancel={handleClose}\n        width={600}\n        footer={null}\n      >\n        <Form\n          layout=\"vertical\"\n          onFinish={handleFinish}\n          initialValues={{ user: value?.sql_user || 'root', password: '' }}\n        >\n          <Form.Item\n            name=\"user\"\n            label={t('user_profile.sso.form.user.authn_dialog.user')}\n          >\n            <Input\n              style={DEFAULT_FORM_ITEM_STYLE}\n              disabled={!supportNonRootLogin}\n            />\n          </Form.Item>\n          <Form.Item\n            name=\"password\"\n            label={t('user_profile.sso.form.user.authn_dialog.password')}\n          >\n            <Input style={DEFAULT_FORM_ITEM_STYLE} type=\"password\" />\n          </Form.Item>\n          <Form.Item>\n            <Alert\n              message={t('user_profile.sso.form.user.authn_dialog.info')}\n              type=\"info\"\n              showIcon\n            />\n          </Form.Item>\n          <Form.Item>\n            <Space>\n              <Button type=\"primary\" htmlType=\"submit\" loading={isPosting}>\n                {t('user_profile.sso.form.user.authn_dialog.submit')}\n              </Button>\n              <Button onClick={handleClose}>\n                {t('user_profile.sso.form.user.authn_dialog.close')}\n              </Button>\n            </Space>\n          </Form.Item>\n        </Form>\n      </Modal>\n    </>\n  )\n}\n\nconst UserAuthInputMemo = React.memo(UserAuthInput)\n\nexport function SSOForm() {\n  const ctx = useContext(UserProfileContext)\n\n  const { t } = useTranslation()\n  const [isChanged, setIsChanged] = useState(false)\n  const [isPosting, setIsPosting] = useState(false)\n  const handleValuesChange = useCallback(() => setIsChanged(true), [])\n  const [form] = Form.useForm()\n  const {\n    error,\n    isLoading,\n    data: config,\n    sendRequest\n  } = useClientRequest(ctx!.ds.userSSOGetConfig)\n  const {\n    error: impError,\n    isLoading: impIsLoading,\n    data: impData,\n    sendRequest: impSendRequest\n  } = useClientRequest(ctx!.ds.userSSOListImpersonations)\n  const initialForm = useRef<any>(null) // Used for \"Cancel\" behaviour\n  const isWriteable = useIsWriteable()\n\n  useChange(() => {\n    if (config) {\n      form.setFieldsValue(config)\n      initialForm.current = { ...config }\n    }\n  }, [config])\n\n  useChange(() => {\n    if (impData) {\n      let rootImp: SsoSSOImpersonationModel | undefined = impData[0]\n      const update = { user_authenticated: rootImp }\n      form.setFieldsValue(update)\n      initialForm.current = {\n        ...initialForm.current,\n        ...update\n      }\n    }\n  }, [impData])\n\n  // TODO: Extract common logic\n  const handleCancel = useCallback(() => {\n    form.setFieldsValue({ ...initialForm.current })\n    setIsChanged(false)\n  }, [form])\n\n  const handleFinish = useCallback(\n    async (data) => {\n      setIsPosting(true)\n      try {\n        await ctx!.ds.userSSOSetConfig({ config: data })\n        sendRequest()\n        setIsChanged(false)\n      } finally {\n        setIsPosting(false)\n      }\n    },\n    [sendRequest, ctx]\n  )\n\n  const handleAuthStateChange = useCallback(() => {\n    impSendRequest()\n  }, [impSendRequest])\n\n  return (\n    <Form\n      layout=\"vertical\"\n      onValuesChange={handleValuesChange}\n      form={form}\n      onFinish={handleFinish}\n    >\n      <AnimatedSkeleton loading={isLoading || impIsLoading}>\n        {(error || impError) && <ErrorBar errors={[error || impError]} />}\n        <Form.Item\n          name=\"enabled\"\n          label={t('user_profile.sso.switch.label')}\n          extra={t('user_profile.sso.switch.extra')}\n          valuePropName=\"checked\"\n        >\n          <Switch disabled={!isWriteable} />\n        </Form.Item>\n        <Form.Item noStyle shouldUpdate>\n          {(f) =>\n            f.getFieldValue('enabled') && (\n              <>\n                <Form.Item\n                  name=\"client_id\"\n                  label={t('user_profile.sso.form.client_id')}\n                  rules={[{ required: true }]}\n                >\n                  <Input disabled={!isWriteable} style={{ width: 320 }} />\n                </Form.Item>\n                {\n                  // to compatible with old version\n                  config?.client_secret !== undefined && (\n                    <Form.Item\n                      name=\"client_secret\"\n                      label={t('user_profile.sso.form.client_secret')}\n                      rules={[{ required: false }]}\n                      tooltip={t('user_profile.sso.form.client_secret_tooltip')}\n                    >\n                      <Input\n                        disabled={!isWriteable}\n                        style={{ width: 320 }}\n                        placeholder=\"********\"\n                      />\n                    </Form.Item>\n                  )\n                }\n                <Form.Item\n                  name=\"scopes\"\n                  label={t('user_profile.sso.form.scopes')}\n                  rules={[{ required: false }]}\n                >\n                  <Input\n                    disabled={!isWriteable}\n                    style={{ width: 320 }}\n                    placeholder=\"openid profile email\"\n                  />\n                </Form.Item>\n                <Form.Item\n                  name=\"discovery_url\"\n                  label={t('user_profile.sso.form.discovery_url')}\n                  rules={[{ required: true }]}\n                >\n                  <Input\n                    disabled={!isWriteable}\n                    style={{ width: 320 }}\n                    placeholder=\"https://example.com\"\n                  />\n                </Form.Item>\n                <Form.Item\n                  label={t('user_profile.sso.form.user.label')}\n                  extra={t('user_profile.sso.form.user.extra')}\n                  name=\"user_authenticated\"\n                  rules={[\n                    {\n                      validator(_, value) {\n                        if (!value) {\n                          return Promise.reject(\n                            new Error(t('user_profile.sso.form.user.must_auth'))\n                          )\n                        }\n                        return Promise.resolve()\n                      }\n                    }\n                  ]}\n                >\n                  <UserAuthInputMemo onChange={handleAuthStateChange} />\n                </Form.Item>\n                <Form.Item\n                  name=\"is_read_only\"\n                  label={t('user_profile.sso.form.is_read_only')}\n                  valuePropName=\"checked\"\n                >\n                  <Checkbox disabled={!isWriteable} />\n                </Form.Item>\n              </>\n            )\n          }\n        </Form.Item>\n        {isChanged && (\n          <Form.Item>\n            <Space>\n              <Button type=\"primary\" htmlType=\"submit\" loading={isPosting}>\n                {t('user_profile.sso.form.update')}\n              </Button>\n              <Button onClick={handleCancel}>\n                {t('user_profile.sso.form.cancel')}\n              </Button>\n            </Space>\n          </Form.Item>\n        )}\n      </AnimatedSkeleton>\n    </Form>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/UserProfile/components/Form.Session.tsx",
    "content": "import { CopyToClipboard } from 'react-copy-to-clipboard'\nimport {\n  CheckOutlined,\n  CopyOutlined,\n  LogoutOutlined,\n  QuestionCircleOutlined,\n  RollbackOutlined,\n  ShareAltOutlined\n} from '@ant-design/icons'\nimport {\n  Alert,\n  Button,\n  DatePicker,\n  Divider,\n  Form,\n  Input,\n  message,\n  Modal,\n  Select,\n  Space,\n  Tooltip\n} from 'antd'\nimport React, { useContext } from 'react'\nimport { useCallback, useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { Pre } from '@lib/components'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\nimport ReactMarkdown from 'react-markdown'\nimport Checkbox from 'antd/lib/checkbox/Checkbox'\nimport { store } from '@lib/utils/store'\nimport { UserProfileContext } from '../context'\nimport dayjs from 'dayjs'\n\nconst SHARE_SESSION_EXPIRY_HOURS = [\n  0.25,\n  0.5,\n  1,\n  2,\n  3,\n  6,\n  12,\n  24,\n  24 * 3,\n  24 * 7,\n  24 * 30,\n  24 * 365\n]\n\nfunction RevokeSessionButton() {\n  const whoAmI = store.useState((s) => s.whoAmI)\n  const { t } = useTranslation()\n  const ctx = useContext(UserProfileContext)\n\n  function showRevokeConfirm() {\n    Modal.confirm({\n      title: t('user_profile.revoke_modal.title'),\n      content: t('user_profile.revoke_modal.content'),\n      okText: t('user_profile.revoke_modal.ok'),\n      cancelText: t('user_profile.revoke_modal.cancel'),\n      onOk() {\n        ctx?.ds.userRevokeSession().then(() => {\n          message.success(t('user_profile.revoke_modal.success_message'))\n        })\n      }\n    })\n  }\n\n  let button = (\n    <Button\n      onClick={showRevokeConfirm}\n      disabled={!whoAmI || !whoAmI.is_shareable}\n    >\n      <RollbackOutlined /> {t('user_profile.session.revoke')}\n      {Boolean(whoAmI && !whoAmI.is_shareable) && <QuestionCircleOutlined />}\n    </Button>\n  )\n\n  if (whoAmI && !whoAmI.is_shareable) {\n    button = (\n      <Tooltip title={t('user_profile.session.revoke_unavailable_tooltip')}>\n        {button}\n      </Tooltip>\n    )\n  }\n\n  return <>{button}</>\n}\n\nfunction ShareSessionButton() {\n  const ctx = useContext(UserProfileContext)\n\n  const { t } = useTranslation()\n  const [visible, setVisible] = useState(false)\n  const [isPosting, setIsPosting] = useState(false)\n  const [code, setCode] = useState<string | undefined>(undefined)\n  const [isCopied, setIsCopied] = useState(false)\n  const whoAmI = store.useState((s) => s.whoAmI)\n\n  const handleOpen = useCallback(() => {\n    setVisible(true)\n  }, [])\n\n  const handleClose = useCallback(() => {\n    setVisible(false)\n    setCode(undefined)\n    setIsPosting(false)\n    setIsCopied(false)\n  }, [])\n\n  const handleFinish = useCallback(\n    async (values) => {\n      const expire = values['expire']\n      const expireCustom = values['expireCustom']\n\n      let expireInSec = 0\n      if (expire === 0) {\n        // expireCustom has value because it is required\n        expireInSec =\n          dayjs.unix(expireCustom.unix()).endOf('day').unix() - dayjs().unix()\n      } else if (expire === -1) {\n        expireInSec = 100 * 365 * 24 * 60 * 60 // 100 years\n      } else {\n        expireInSec = expire * 60 * 60\n      }\n\n      try {\n        setIsPosting(true)\n        const r = await ctx!.ds.userShareSession({\n          expire_in_sec: expireInSec,\n          revoke_write_priv: !!values.read_only\n        })\n        setCode(r.data.code)\n      } finally {\n        setIsPosting(false)\n      }\n    },\n    [ctx]\n  )\n\n  const handleCopy = useCallback(() => {\n    setIsCopied(true)\n  }, [])\n\n  let button = (\n    <Button onClick={handleOpen} disabled={!whoAmI || !whoAmI.is_shareable}>\n      <ShareAltOutlined /> {t('user_profile.session.share')}\n      {Boolean(whoAmI && !whoAmI.is_shareable) && <QuestionCircleOutlined />}\n    </Button>\n  )\n\n  if (whoAmI && !whoAmI.is_shareable) {\n    button = (\n      <Tooltip title={t('user_profile.session.share_unavailable_tooltip')}>\n        {button}\n      </Tooltip>\n    )\n  }\n\n  return (\n    <>\n      {button}\n      <Modal\n        closable={false}\n        destroyOnClose\n        footer={\n          <Space>\n            <CopyToClipboard text={code ?? ''} onCopy={handleCopy}>\n              <Button type={isCopied ? 'default' : 'primary'}>\n                {isCopied && (\n                  <span>\n                    <CheckOutlined />{' '}\n                    {t('user_profile.share_session.success_dialog.copied')}\n                  </span>\n                )}\n                {!isCopied && (\n                  <span>\n                    <CopyOutlined />{' '}\n                    {t('user_profile.share_session.success_dialog.copy')}\n                  </span>\n                )}\n              </Button>\n            </CopyToClipboard>\n            <Button onClick={handleClose}>\n              {t('user_profile.share_session.close')}\n            </Button>\n          </Space>\n        }\n        visible={!!code}\n      >\n        <Alert\n          message={t('user_profile.share_session.success_dialog.title')}\n          description={<Pre>{code}</Pre>}\n          type=\"success\"\n          showIcon\n        />\n      </Modal>\n      <Modal\n        title={t('user_profile.session.share')}\n        visible={visible}\n        destroyOnClose\n        footer={null}\n        onCancel={handleClose}\n        width={600}\n      >\n        <ReactMarkdown>{t('user_profile.share_session.text')}</ReactMarkdown>\n        <Divider />\n        <Form\n          layout=\"vertical\"\n          initialValues={{ expire: 3, read_only: true }}\n          onFinish={handleFinish}\n        >\n          <Form.Item\n            label={t('user_profile.share_session.form.expire')}\n            required\n          >\n            <Input.Group compact>\n              <Form.Item name=\"expire\" rules={[{ required: true }]} noStyle>\n                <Select style={{ width: 120 }}>\n                  {SHARE_SESSION_EXPIRY_HOURS.map((val) => (\n                    <Select.Option key={val} value={val}>\n                      {getValueFormat('m')(val * 60, 0)}\n                    </Select.Option>\n                  ))}\n                  <Select.Option value={0}>\n                    {t('user_profile.share_session.form.custom_expiration')}\n                  </Select.Option>\n                  <Select.Option value={-1}>\n                    {t('user_profile.share_session.form.no_expiration')}\n                  </Select.Option>\n                </Select>\n              </Form.Item>\n              <Form.Item\n                noStyle\n                shouldUpdate={(prev, cur) => prev.expire !== cur.expire}\n              >\n                {({ getFieldValue }) => {\n                  return (\n                    getFieldValue('expire') === 0 && (\n                      <Form.Item\n                        noStyle\n                        name=\"expireCustom\"\n                        rules={[{ required: true }]}\n                      >\n                        <DatePicker\n                          disabledDate={(date) =>\n                            dayjs()\n                              .endOf('day')\n                              .isAfter(dayjs.unix(date.unix()))\n                          }\n                        />\n                      </Form.Item>\n                    )\n                  )\n                }}\n              </Form.Item>\n            </Input.Group>\n          </Form.Item>\n\n          <Form.Item\n            name=\"read_only\"\n            label={t('user_profile.share_session.form.read_only')}\n            valuePropName=\"checked\"\n          >\n            <Checkbox />\n          </Form.Item>\n          <Form.Item>\n            <Space>\n              <Button type=\"primary\" htmlType=\"submit\" loading={isPosting}>\n                {t('user_profile.share_session.form.submit')}\n              </Button>\n              <Button onClick={handleClose}>\n                {t('user_profile.share_session.close')}\n              </Button>\n            </Space>\n          </Form.Item>\n        </Form>\n      </Modal>\n    </>\n  )\n}\n\nexport function SessionForm() {\n  const ctx = useContext(UserProfileContext)\n  const { t } = useTranslation()\n\n  const handleLogout = useCallback(async () => {\n    let signOutURL: string | undefined = undefined\n    try {\n      const resp = await ctx!.ds.userGetSignOutInfo(\n        `${window.location.protocol}//${window.location.host}${window.location.pathname}`\n      )\n      signOutURL = resp.data.end_session_url\n    } catch (e) {\n      console.error(e)\n    }\n\n    ctx!.event.logOut()\n    if (signOutURL) {\n      window.location.href = signOutURL\n    } else {\n      window.location.reload()\n    }\n  }, [ctx])\n\n  return (\n    <Space>\n      <ShareSessionButton />\n      {/* only available for v8.4.0+, v6.5.11+ */}\n      <RevokeSessionButton />\n      <Button danger onClick={handleLogout}>\n        <LogoutOutlined /> {t('user_profile.session.sign_out')}\n      </Button>\n    </Space>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/UserProfile/components/Form.Version.tsx",
    "content": "import { CopyLink, Descriptions, TextWithInfo } from '@lib/components'\nimport { store } from '@lib/utils/store'\nimport { Space } from 'antd'\nimport React from 'react'\n\nexport function VersionForm() {\n  const info = store.useState((s) => s.appInfo)\n\n  return (\n    <>\n      {Boolean(info) && (\n        <Descriptions>\n          <Descriptions.Item\n            span={2}\n            label={\n              <Space size=\"middle\">\n                <TextWithInfo.TransKey transKey=\"user_profile.version.internal_version\" />\n                <CopyLink data={info!.version?.internal_version} />\n              </Space>\n            }\n          >\n            {info!.version?.internal_version}\n          </Descriptions.Item>\n          <Descriptions.Item\n            span={2}\n            label={\n              <Space size=\"middle\">\n                <TextWithInfo.TransKey transKey=\"user_profile.version.build_git_hash\" />\n                <CopyLink data={info!.version?.build_git_hash} />\n              </Space>\n            }\n          >\n            {info!.version?.build_git_hash}\n          </Descriptions.Item>\n          <Descriptions.Item\n            span={2}\n            label={\n              <TextWithInfo.TransKey transKey=\"user_profile.version.build_time\" />\n            }\n          >\n            {info!.version?.build_time}\n          </Descriptions.Item>\n          <Descriptions.Item\n            span={2}\n            label={\n              <TextWithInfo.TransKey transKey=\"user_profile.version.standalone\" />\n            }\n          >\n            {info!.version?.standalone}\n          </Descriptions.Item>\n          <Descriptions.Item\n            span={2}\n            label={\n              <Space size=\"middle\">\n                <TextWithInfo.TransKey transKey=\"user_profile.version.pd_version\" />\n                <CopyLink data={info!.version?.pd_version} />\n              </Space>\n            }\n          >\n            {info!.version?.pd_version}\n          </Descriptions.Item>\n        </Descriptions>\n      )}\n    </>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/UserProfile/components/index.ts",
    "content": "export * from './Form.SSO'\nexport * from './Form.Session'\nexport * from './Form.PrometheusAddr'\nexport * from './Form.Version'\nexport * from './Form.Language'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/UserProfile/context/index.ts",
    "content": "import { createContext } from 'react'\n\nimport { AxiosPromise } from 'axios'\n\nimport {\n  UserSignOutInfo,\n  SsoCreateImpersonationRequest,\n  SsoSSOImpersonationModel,\n  ConfigSSOCoreConfig,\n  SsoSetConfigRequest,\n  CodeShareRequest,\n  CodeShareResponse,\n  MetricsGetPromAddressConfigResponse,\n  MetricsPutCustomPromAddressRequest,\n  MetricsPutCustomPromAddressResponse\n} from '@lib/client'\n\nimport { ReqConfig } from '@lib/types'\n\nexport interface IUserProfileDataSource {\n  userGetSignOutInfo(\n    redirectUrl?: string,\n    options?: ReqConfig\n  ): AxiosPromise<UserSignOutInfo>\n\n  userSSOCreateImpersonation(\n    request: SsoCreateImpersonationRequest,\n    options?: ReqConfig\n  ): AxiosPromise<SsoSSOImpersonationModel>\n\n  userSSOGetConfig(options?: ReqConfig): AxiosPromise<ConfigSSOCoreConfig>\n\n  userSSOListImpersonations(\n    options?: ReqConfig\n  ): AxiosPromise<Array<SsoSSOImpersonationModel>>\n\n  userSSOSetConfig(\n    request: SsoSetConfigRequest,\n    options?: ReqConfig\n  ): AxiosPromise<ConfigSSOCoreConfig>\n\n  userShareSession(\n    request: CodeShareRequest,\n    options?: ReqConfig\n  ): AxiosPromise<CodeShareResponse>\n\n  userRevokeSession(options?: ReqConfig): AxiosPromise<void>\n\n  metricsGetPromAddress(\n    options?: ReqConfig\n  ): AxiosPromise<MetricsGetPromAddressConfigResponse>\n\n  metricsSetCustomPromAddress(\n    request: MetricsPutCustomPromAddressRequest,\n    options?: ReqConfig\n  ): AxiosPromise<MetricsPutCustomPromAddressResponse>\n}\n\nexport interface IUserProfileEvent {\n  logOut(): void\n}\n\nexport interface IUserProfileContext {\n  ds: IUserProfileDataSource\n  event: IUserProfileEvent\n}\n\nexport const UserProfileContext = createContext<IUserProfileContext | null>(\n  null\n)\n\nexport const UserProfileProvider = UserProfileContext.Provider\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/UserProfile/index.tsx",
    "content": "import React, { useContext } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { HashRouter as Router, Route, Routes } from 'react-router-dom'\nimport { Card, Root } from '@lib/components'\nimport {\n  SSOForm,\n  SessionForm,\n  PrometheusAddressForm,\n  VersionForm,\n  LanguageForm\n} from './components'\n\nimport { addTranslations } from '@lib/utils/i18n'\nimport translations from './translations'\nimport { UserProfileContext } from './context'\nimport { useLocationChange } from '@lib/hooks/useLocationChange'\n\naddTranslations(translations)\n\nfunction UserProfile() {\n  const { t } = useTranslation()\n\n  return (\n    <>\n      <Card title={t('user_profile.session.title')}>\n        <SessionForm />\n      </Card>\n      <Card title={t('user_profile.sso.title')}>\n        <SSOForm />\n      </Card>\n      <Card title={t('user_profile.service_endpoints.title')}>\n        <PrometheusAddressForm />\n      </Card>\n      <Card title={t('user_profile.i18n.title')}>\n        <LanguageForm />\n      </Card>\n      <Card title={t('user_profile.version.title')}>\n        <VersionForm />\n      </Card>\n    </>\n  )\n}\n\nfunction AppRoutes() {\n  useLocationChange()\n\n  return (\n    <Routes>\n      <Route path=\"/user_profile\" element={<UserProfile />} />\n    </Routes>\n  )\n}\n\nfunction App() {\n  const ctx = useContext(UserProfileContext)\n  if (ctx === null) {\n    throw new Error('UserProfileContext must not be null')\n  }\n\n  return (\n    <Root>\n      <Router>\n        <AppRoutes />\n      </Router>\n    </Root>\n  )\n}\n\nexport default App\n\nexport * from './context'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/UserProfile/translations/en.yaml",
    "content": "user_profile:\n  sso:\n    title: Single Sign-On (SSO)\n    switch:\n      label: Enable to use SSO when sign into {{distro.tidb}} Dashboard\n      extra: OIDC based SSO is supported\n    form:\n      client_id: OIDC Client ID\n      client_secret: OIDC Client Secret\n      client_secret_tooltip: Optional, it is needed when using \"Client Secret Post\" authentication method, and it is only can be seen when setting.\n      scopes: Additional OIDC Scopes (space-separated)\n      discovery_url: OIDC Discovery URL\n      is_read_only: Sign in as read-only privilege\n      user:\n        label: Impersonate SQL User\n        extra: The SSO signed-in user will be using {{distro.tidb}} Dashboard on behalf of this SQL user and shares its permissions.\n        must_auth: You must authorize to continue\n        authn_button: Authorize Impersonation\n        modify_authn_button: Modify Authorization\n        authn_dialog:\n          title: Authorize Impersonation\n          user: SQL User to Impersonate\n          password: SQL User Password\n          info: The password of the SQL user will be stored encrypted. The impersonation will fail after SQL user changes the password.\n          submit: Authorize and Save\n          close: Cancel\n        authn_status:\n          ok: Authorized\n          auth_failed: 'Cannot impersonate: SQL user password is changed.'\n          insufficient_privileges: 'Cannot impersonate: Has no sufficient privileges to accsss {{distro.tidb}} dashboard.'\n      update: Update\n      cancel: Cancel\n  service_endpoints:\n    title: Service Endpoints\n    prometheus:\n      title: Prometheus Data Source\n      form:\n        deployed: Use deployed address\n        not_deployed: Prometheus is not deployed\n        custom: Use customized address\n        update: Update\n        cancel: Cancel\n      custom_form:\n        address: Customize Prometheus Address\n  i18n:\n    title: Language & Localization\n    language: Language\n  session:\n    title: Session\n    sign_out: Sign Out\n    share: Share Current Session\n    share_unavailable_tooltip: Current session is not allowed to be shared\n    revoke: Revoke Authorization Codes\n    revoke_unavailable_tooltip: You have no permission to revoke the authorization codes\n  share_session:\n    text: >\n      You can invite others to access this {{distro.tidb}} Dashboard by sharing your\n      current session via an **Authorization Code**:\n\n      - The Authorization Code can be used multiple times.\n\n      - The shared session has the same privilege as your current session.\n\n      - The shared session will be invalidated after the expiry time you specified.\n\n      - The shared session can be revoked in advance by administrator.\n    form:\n      expire: Expire in\n      no_expiration: No expiration\n      custom_expiration: Custom\n      read_only: Share as read-only privilege\n      submit: Generate Authorization Code\n    close: Close\n    success_dialog:\n      title: Authorization Code Generated\n      copy: Copy\n      copied: Copied\n  revoke_modal:\n    title: Are you sure you want to revoke all authorization codes?\n    content: After revoking, all authorization codes that are authorized before can't be used to login again, and this action can't undo.\n    ok: Revoke\n    cancel: Cancel\n    success_message: Revoke authorization codes successfully!\n  version:\n    title: Version Information\n    internal_version: '{{distro.tidb}} Dashboard Internal Version'\n    build_git_hash: '{{distro.tidb}} Dashboard Build Git Hash'\n    build_time: '{{distro.tidb}} Dashboard Build Time'\n    standalone: '{{distro.tidb}} Dashboard Run in Standalone Mode'\n    pd_version: '{{distro.pd}} Version'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/UserProfile/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/UserProfile/translations/zh.yaml",
    "content": "user_profile:\n  sso:\n    title: 单点登录 (SSO)\n    switch:\n      label: 允许使用 SSO 登录到 {{distro.tidb}} Dashboard\n      extra: 支持基于 OIDC 的 SSO 登录\n    form:\n      client_id: OIDC Client ID\n      client_secret: OIDC Client Secret\n      client_secret_tooltip: 可选，使用 \"Client Secret Post\" 认证方法时需要，且仅在设置时可见。\n      scopes: 附加 OIDC Scope（空格分隔）\n      discovery_url: OIDC Discovery URL\n      is_read_only: 以只读权限登录\n      user:\n        label: 实际登录 SQL 用户\n        extra: SSO 登录成功后将被视为使用该 SQL 用户登录使用 {{distro.tidb}} Dashboard，并具有该用户对应的操作权限。\n        must_auth: 必须完成授权后才能继续\n        authn_button: 授权登录\n        modify_authn_button: 修改授权\n        authn_dialog:\n          title: SSO 登录授权\n          user: 实际被登录的 SQL 用户\n          password: SQL 用户的登录密码\n          info: 登录密码将被加密存储；在 SQL 用户修改密码后 SSO 登录将失败（可重新进行登录授权）。\n          submit: 授权并保存\n          close: 取消\n        authn_status:\n          ok: 已授权\n          auth_failed: 授权失败：SQL 用户密码已变更\n          insufficient_privileges: 授权失败：缺少访问 {{distro.tidb}} Dashboard 所需的权限\n      update: 更新\n      cancel: 取消\n  service_endpoints:\n    title: 服务端点\n    prometheus:\n      title: Prometheus 数据源\n      form:\n        deployed: 使用已部署的组件地址\n        not_deployed: 未部署 Prometheus 组件\n        custom: 使用自定义地址\n        update: 更新\n        cancel: 取消\n      custom_form:\n        address: 自定义 Prometheus 数据源地址\n  i18n:\n    title: 语言和本地化\n    language: 语言\n  session:\n    title: 会话\n    sign_out: 登出\n    share: 分享当前会话\n    share_unavailable_tooltip: 当前会话被禁止分享\n    revoke: 撤消授权码\n    revoke_unavailable_tooltip: 你没有权限撤消授权码\n  share_session:\n    text: >\n      您可以生成一个**授权码**来将您当前的会话分享给其他人，邀请他们使用该 {{distro.tidb}} Dashboard：\n\n      - 授权码可以被重复使用。\n\n      - 分享的会话和您当前会话具有相同权限。\n\n      - 分享的会话将在您指定的有效时间后过期。\n\n      - 分享的会话可以被管理员提前撤消。\n    form:\n      expire: 有效时间\n      no_expiration: 无过期\n      custom_expiration: 自定义\n      read_only: 以只读权限分享\n      submit: 生成授权码\n    close: 关闭\n    success_dialog:\n      title: 授权码已生成\n      copy: 复制\n      copied: 已复制\n  revoke_modal:\n    title: 你确定你要撤消所有的授权码吗？\n    content: 撤消之后，所有之前授权的授权码都不能再用于登录，而且这个操作不能回滚。\n    ok: 撤消\n    cancel: 取消\n    success_message: 撤消授权码成功！\n  version:\n    title: 版本信息\n    internal_version: '{{distro.tidb}} Dashboard 内部版本号'\n    build_git_hash: '{{distro.tidb}} Dashboard 编译 Git Hash'\n    build_time: '{{distro.tidb}} Dashboard 编译时间'\n    standalone: '{{distro.tidb}} Dashboard 运行于独立模式'\n    pd_version: '{{distro.pd}} 版本号'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/UserProfile/utils/helper.ts",
    "content": "export const DEFAULT_FORM_ITEM_STYLE = { width: 200 }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/apps/index.ts",
    "content": "export { default as OverviewApp } from './Overview'\nexport * from './Overview'\n\nexport { default as MonitoringApp } from './Monitoring'\nexport * from './Monitoring'\n\nexport { default as ClusterInfoApp } from './ClusterInfo'\nexport * from './ClusterInfo'\n\nexport { default as TopSQLApp } from './TopSQL'\nexport * from './TopSQL'\n\nexport { default as TopSlowQueryApp } from './TopSlowQuery'\nexport * from './TopSlowQuery'\n\nexport { default as SQLAdvisorAPP } from './SQLAdvisor'\nexport * from './SQLAdvisor'\n\nexport { default as StatementApp } from './Statement'\nexport * from './Statement'\n\nexport { default as SlowQueryApp } from './SlowQuery'\nexport * from './SlowQuery'\n\nexport { default as KeyVizApp } from './KeyViz'\nexport * from './KeyViz'\n\nexport { default as SystemReportApp } from './SystemReport'\nexport * from './SystemReport'\n\nexport { default as SearchLogsApp } from './SearchLogs'\nexport * from './SearchLogs'\n\nexport { default as InstanceProfilingApp } from './InstanceProfiling'\nexport * from './InstanceProfiling'\n\nexport { default as ConProfilingApp } from './ContinuousProfiling'\nexport * from './ContinuousProfiling'\n\nexport { default as DebugAPIApp } from './DebugAPI'\nexport * from './DebugAPI'\n\nexport { default as QueryEditorApp } from './QueryEditor'\nexport * from './QueryEditor'\n\nexport { default as ConfigurationApp } from './Configuration'\nexport * from './Configuration'\n\nexport { default as UserProfileApp } from './UserProfile'\nexport * from './UserProfile'\n\nexport { default as DiagnoseApp } from './Diagnose'\nexport * from './Diagnose'\n\nexport { default as OptimizerTraceApp } from './OptimizerTrace'\nexport * from './OptimizerTrace'\n\nexport { default as DeadlockApp } from './Deadlock'\nexport * from './Deadlock'\n\nexport { default as ResourceManagerApp } from './ResourceManager'\nexport * from './ResourceManager'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/client/clinic-extensions.d.ts",
    "content": "/**\n * Type augmentation for fields returned by clinic's NGM proxy that aren't\n * part of the dashboard's own swagger spec.\n *\n * This file is NOT generated. Declarations here merge on top of the\n * interfaces generated into `./models.ts`, so any future re-generation of\n * the swagger client does NOT overwrite these fields.\n *\n * Background: clinic backend (PR pingcap-inc/clinic#1415) returns additional\n * RU V2 metrics on slow-query / sql-statement responses. The dashboard's own\n * Go API server does not yet model these fields, so the auto-generated\n * TypeScript model files don't declare them. This file fills the gap purely\n * on the TS side for the clinic-cloud deliverable.\n *\n * Visibility of these fields in the UI is independently gated by\n * `ISlowQueryConfig.showRuV2` / `IStatementConfig.showRuV2`, so other\n * deliverables (standalone TiDB dashboard, clinic-op) are unaffected.\n */\nimport '@lib/client'\n\ndeclare module '@lib/client' {\n  interface RequestUnitV2Metrics {\n    total_ru?: number\n    tidb_ru?: number\n    tikv_ru?: number\n    tiflash_ru?: number\n    txn_cnt?: number\n    plan_cnt?: number\n    plan_derive_stats_paths?: number\n    session_parser_total?: number\n    executor_l1?: number\n    executor_l2?: number\n    executor_l3?: number\n    executor_l5_insert_rows?: number\n    result_chunk_cells?: number\n    resource_manager_read_cnt?: number\n    resource_manager_write_cnt?: number\n    tikv_coprocessor_executor_iterations?: number\n    tikv_coprocessor_response_bytes?: number\n    tikv_coprocessor_executor_work_total?: Record<string, number>\n    tikv_storage_processed_keys_get?: number\n    tikv_storage_processed_keys_batch_get?: number\n    tikv_kv_engine_cache_miss?: number\n    tikv_raftstore_store_write_trigger_wb_bytes?: number\n  }\n\n  interface SlowqueryModel {\n    ru_v2?: number\n    ru_v2_detail?: string\n    ru_v2_metrics?: RequestUnitV2Metrics\n  }\n\n  interface StatementModel {\n    avg_ru_v2?: number\n    sum_ru_v2?: number\n    max_ru_v2?: number\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/client/index.ts",
    "content": "export * from './models'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/client/models.ts",
    "content": "/* tslint:disable */\n/* eslint-disable */\n/* This file is auto generated by tidb-dashboard-client/swagger/gen_api.sh */\n\n\n\n/**\n * \n * @export\n * @interface ClusterinfoClusterStatisticsPartial\n */\nexport interface ClusterinfoClusterStatisticsPartial {\n    /**\n     * \n     * @type {number}\n     * @memberof ClusterinfoClusterStatisticsPartial\n     */\n    'number_of_hosts'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ClusterinfoClusterStatisticsPartial\n     */\n    'number_of_instances'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ClusterinfoClusterStatisticsPartial\n     */\n    'total_logical_cores'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ClusterinfoClusterStatisticsPartial\n     */\n    'total_memory_capacity_bytes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ClusterinfoClusterStatisticsPartial\n     */\n    'total_physical_cores'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ClusterinfoClusterStatistics\n */\nexport interface ClusterinfoClusterStatistics {\n    /**\n     * \n     * @type {number}\n     * @memberof ClusterinfoClusterStatistics\n     */\n    'probe_failure_hosts'?: number;\n    /**\n     * \n     * @type {{ [key: string]: ClusterinfoClusterStatisticsPartial; }}\n     * @memberof ClusterinfoClusterStatistics\n     */\n    'stats_by_instance_kind'?: { [key: string]: ClusterinfoClusterStatisticsPartial; };\n    /**\n     * \n     * @type {ClusterinfoClusterStatisticsPartial}\n     * @memberof ClusterinfoClusterStatistics\n     */\n    'total_stats'?: ClusterinfoClusterStatisticsPartial;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof ClusterinfoClusterStatistics\n     */\n    'versions'?: Array<string>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ClusterinfoGetHostsInfoResponse\n */\nexport interface ClusterinfoGetHostsInfoResponse {\n    /**\n     * \n     * @type {Array<HostinfoInfo>}\n     * @memberof ClusterinfoGetHostsInfoResponse\n     */\n    'hosts'?: Array<HostinfoInfo>;\n    /**\n     * \n     * @type {RestErrorResponse}\n     * @memberof ClusterinfoGetHostsInfoResponse\n     */\n    'warning'?: RestErrorResponse;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ClusterinfoStoreTopologyResponse\n */\nexport interface ClusterinfoStoreTopologyResponse {\n    /**\n     * \n     * @type {Array<TopologyStoreInfo>}\n     * @memberof ClusterinfoStoreTopologyResponse\n     */\n    'tiflash'?: Array<TopologyStoreInfo>;\n    /**\n     * \n     * @type {Array<TopologyStoreInfo>}\n     * @memberof ClusterinfoStoreTopologyResponse\n     */\n    'tikv'?: Array<TopologyStoreInfo>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface CodeShareRequest\n */\nexport interface CodeShareRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof CodeShareRequest\n     */\n    'expire_in_sec'?: number;\n    /**\n     * \n     * @type {boolean}\n     * @memberof CodeShareRequest\n     */\n    'revoke_write_priv'?: boolean;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface CodeShareResponse\n */\nexport interface CodeShareResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof CodeShareResponse\n     */\n    'code'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConfigKeyVisualConfig\n */\nexport interface ConfigKeyVisualConfig {\n    /**\n     * \n     * @type {boolean}\n     * @memberof ConfigKeyVisualConfig\n     */\n    'auto_collection_disabled'?: boolean;\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigKeyVisualConfig\n     */\n    'policy'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigKeyVisualConfig\n     */\n    'policy_kv_separator'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConfigProfilingConfig\n */\nexport interface ConfigProfilingConfig {\n    /**\n     * \n     * @type {number}\n     * @memberof ConfigProfilingConfig\n     */\n    'auto_collection_duration_secs'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConfigProfilingConfig\n     */\n    'auto_collection_interval_secs'?: number;\n    /**\n     * \n     * @type {Array<ModelRequestTargetNode>}\n     * @memberof ConfigProfilingConfig\n     */\n    'auto_collection_targets'?: Array<ModelRequestTargetNode>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConfigSSOCoreConfig\n */\nexport interface ConfigSSOCoreConfig {\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigSSOCoreConfig\n     */\n    'client_id'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigSSOCoreConfig\n     */\n    'client_secret'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigSSOCoreConfig\n     */\n    'discovery_url'?: string;\n    /**\n     * \n     * @type {boolean}\n     * @memberof ConfigSSOCoreConfig\n     */\n    'enabled'?: boolean;\n    /**\n     * \n     * @type {boolean}\n     * @memberof ConfigSSOCoreConfig\n     */\n    'is_read_only'?: boolean;\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigSSOCoreConfig\n     */\n    'scopes'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConfigurationAllConfigItems\n */\nexport interface ConfigurationAllConfigItems {\n    /**\n     * \n     * @type {Array<RestErrorResponse>}\n     * @memberof ConfigurationAllConfigItems\n     */\n    'errors'?: Array<RestErrorResponse>;\n    /**\n     * \n     * @type {{ [key: string]: Array<ConfigurationItem>; }}\n     * @memberof ConfigurationAllConfigItems\n     */\n    'items'?: { [key: string]: Array<ConfigurationItem>; };\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConfigurationEditRequest\n */\nexport interface ConfigurationEditRequest {\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigurationEditRequest\n     */\n    'id'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigurationEditRequest\n     */\n    'kind'?: string;\n    /**\n     * \n     * @type {object}\n     * @memberof ConfigurationEditRequest\n     */\n    'new_value'?: object;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConfigurationEditResponse\n */\nexport interface ConfigurationEditResponse {\n    /**\n     * \n     * @type {Array<RestErrorResponse>}\n     * @memberof ConfigurationEditResponse\n     */\n    'warnings'?: Array<RestErrorResponse>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConfigurationItem\n */\nexport interface ConfigurationItem {\n    /**\n     * \n     * @type {string}\n     * @memberof ConfigurationItem\n     */\n    'id'?: string;\n    /**\n     * \n     * @type {boolean}\n     * @memberof ConfigurationItem\n     */\n    'is_editable'?: boolean;\n    /**\n     * TODO: Support per-instance config\n     * @type {boolean}\n     * @memberof ConfigurationItem\n     */\n    'is_multi_value'?: boolean;\n    /**\n     * When multi value present, this contains one of the value\n     * @type {object}\n     * @memberof ConfigurationItem\n     */\n    'value'?: object;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConprofComponentNum\n */\nexport interface ConprofComponentNum {\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponentNum\n     */\n    'pd'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponentNum\n     */\n    'ticdc'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponentNum\n     */\n    'tidb'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponentNum\n     */\n    'tiflash'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponentNum\n     */\n    'tikv'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConprofComponent\n */\nexport interface ConprofComponent {\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofComponent\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofComponent\n     */\n    'name'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponent\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofComponent\n     */\n    'status_port'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConprofContinuousProfilingConfig\n */\nexport interface ConprofContinuousProfilingConfig {\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofContinuousProfilingConfig\n     */\n    'data_retention_seconds'?: number;\n    /**\n     * \n     * @type {boolean}\n     * @memberof ConprofContinuousProfilingConfig\n     */\n    'enable'?: boolean;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofContinuousProfilingConfig\n     */\n    'interval_seconds'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofContinuousProfilingConfig\n     */\n    'profile_seconds'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofContinuousProfilingConfig\n     */\n    'timeout_seconds'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConprofEstimateSizeRes\n */\nexport interface ConprofEstimateSizeRes {\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofEstimateSizeRes\n     */\n    'instance_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofEstimateSizeRes\n     */\n    'profile_size'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConprofGroupProfileDetail\n */\nexport interface ConprofGroupProfileDetail {\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofGroupProfileDetail\n     */\n    'profile_duration_secs'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofGroupProfileDetail\n     */\n    'state'?: string;\n    /**\n     * \n     * @type {Array<ConprofProfileDetail>}\n     * @memberof ConprofGroupProfileDetail\n     */\n    'target_profiles'?: Array<ConprofProfileDetail>;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofGroupProfileDetail\n     */\n    'ts'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConprofGroupProfiles\n */\nexport interface ConprofGroupProfiles {\n    /**\n     * \n     * @type {ConprofComponentNum}\n     * @memberof ConprofGroupProfiles\n     */\n    'component_num'?: ConprofComponentNum;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofGroupProfiles\n     */\n    'profile_duration_secs'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofGroupProfiles\n     */\n    'state'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof ConprofGroupProfiles\n     */\n    'ts'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConprofNgMonitoringConfig\n */\nexport interface ConprofNgMonitoringConfig {\n    /**\n     * \n     * @type {ConprofContinuousProfilingConfig}\n     * @memberof ConprofNgMonitoringConfig\n     */\n    'continuous_profiling'?: ConprofContinuousProfilingConfig;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConprofProfileDetail\n */\nexport interface ConprofProfileDetail {\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofProfileDetail\n     */\n    'error'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofProfileDetail\n     */\n    'profile_type'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofProfileDetail\n     */\n    'state'?: string;\n    /**\n     * \n     * @type {ConprofTarget}\n     * @memberof ConprofProfileDetail\n     */\n    'target'?: ConprofTarget;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ConprofTarget\n */\nexport interface ConprofTarget {\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofTarget\n     */\n    'address'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ConprofTarget\n     */\n    'component'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface DeadlockModel\n */\nexport interface DeadlockModel {\n    /**\n     * \n     * @type {string}\n     * @memberof DeadlockModel\n     */\n    'current_sql'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof DeadlockModel\n     */\n    'id'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof DeadlockModel\n     */\n    'instance'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DeadlockModel\n     */\n    'key'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DeadlockModel\n     */\n    'key_info'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DeadlockModel\n     */\n    'occur_time'?: string;\n    /**\n     * \n     * @type {boolean}\n     * @memberof DeadlockModel\n     */\n    'retryable'?: boolean;\n    /**\n     * \n     * @type {number}\n     * @memberof DeadlockModel\n     */\n    'trx_holding_lock'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof DeadlockModel\n     */\n    'try_lock_trx_id'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface DecoratorLabelKey\n */\nexport interface DecoratorLabelKey {\n    /**\n     * \n     * @type {string}\n     * @memberof DecoratorLabelKey\n     */\n    'key': string;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof DecoratorLabelKey\n     */\n    'labels': Array<string>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface DiagnoseGenDiagnosisReportRequest\n */\nexport interface DiagnoseGenDiagnosisReportRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenDiagnosisReportRequest\n     */\n    'end_time'?: number;\n    /**\n     * values: config, error, performance\n     * @type {string}\n     * @memberof DiagnoseGenDiagnosisReportRequest\n     */\n    'kind'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenDiagnosisReportRequest\n     */\n    'start_time'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface DiagnoseGenerateMetricsRelationRequest\n */\nexport interface DiagnoseGenerateMetricsRelationRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenerateMetricsRelationRequest\n     */\n    'end_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenerateMetricsRelationRequest\n     */\n    'start_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseGenerateMetricsRelationRequest\n     */\n    'type'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface DiagnoseGenerateReportRequest\n */\nexport interface DiagnoseGenerateReportRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenerateReportRequest\n     */\n    'compare_end_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenerateReportRequest\n     */\n    'compare_start_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenerateReportRequest\n     */\n    'end_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof DiagnoseGenerateReportRequest\n     */\n    'start_time'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface DiagnoseReport\n */\nexport interface DiagnoseReport {\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'compare_end_time'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'compare_start_time'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'content'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'created_at'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'end_time'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'id'?: string;\n    /**\n     * 0~100\n     * @type {number}\n     * @memberof DiagnoseReport\n     */\n    'progress'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseReport\n     */\n    'start_time'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface DiagnoseTableDef\n */\nexport interface DiagnoseTableDef {\n    /**\n     * The category of the table, such as [TiDB]\n     * @type {Array<string>}\n     * @memberof DiagnoseTableDef\n     */\n    'category'?: Array<string>;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof DiagnoseTableDef\n     */\n    'column'?: Array<string>;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseTableDef\n     */\n    'comment'?: string;\n    /**\n     * \n     * @type {Array<DiagnoseTableRowDef>}\n     * @memberof DiagnoseTableDef\n     */\n    'rows'?: Array<DiagnoseTableRowDef>;\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseTableDef\n     */\n    'title'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface DiagnoseTableRowDef\n */\nexport interface DiagnoseTableRowDef {\n    /**\n     * \n     * @type {string}\n     * @memberof DiagnoseTableRowDef\n     */\n    'comment'?: string;\n    /**\n     * SubValues need fold default.\n     * @type {Array<Array<string>>}\n     * @memberof DiagnoseTableRowDef\n     */\n    'sub_values'?: Array<Array<string>>;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof DiagnoseTableRowDef\n     */\n    'values'?: Array<string>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface EndpointAPIDefinition\n */\nexport interface EndpointAPIDefinition {\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointAPIDefinition\n     */\n    'component'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointAPIDefinition\n     */\n    'id'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointAPIDefinition\n     */\n    'method'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointAPIDefinition\n     */\n    'path'?: string;\n    /**\n     * e.g. /stats/dump/{db}/{table} -> db, table\n     * @type {Array<EndpointAPIParamDefinition>}\n     * @memberof EndpointAPIDefinition\n     */\n    'path_params'?: Array<EndpointAPIParamDefinition>;\n    /**\n     * e.g. /debug/pprof?seconds=1 -> seconds\n     * @type {Array<EndpointAPIParamDefinition>}\n     * @memberof EndpointAPIDefinition\n     */\n    'query_params'?: Array<EndpointAPIParamDefinition>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface EndpointAPIParamDefinition\n */\nexport interface EndpointAPIParamDefinition {\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointAPIParamDefinition\n     */\n    'name'?: string;\n    /**\n     * \n     * @type {boolean}\n     * @memberof EndpointAPIParamDefinition\n     */\n    'required'?: boolean;\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointAPIParamDefinition\n     */\n    'ui_kind'?: string;\n    /**\n     * varies by different ui kinds\n     * @type {object}\n     * @memberof EndpointAPIParamDefinition\n     */\n    'ui_props'?: object;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface EndpointRequestPayload\n */\nexport interface EndpointRequestPayload {\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointRequestPayload\n     */\n    'api_id'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof EndpointRequestPayload\n     */\n    'host'?: string;\n    /**\n     * \n     * @type {{ [key: string]: string; }}\n     * @memberof EndpointRequestPayload\n     */\n    'param_values'?: { [key: string]: string; };\n    /**\n     * \n     * @type {number}\n     * @memberof EndpointRequestPayload\n     */\n    'port'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface HostinfoCPUInfo\n */\nexport interface HostinfoCPUInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof HostinfoCPUInfo\n     */\n    'arch'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoCPUInfo\n     */\n    'logical_cores'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoCPUInfo\n     */\n    'physical_cores'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface HostinfoCPUUsageInfo\n */\nexport interface HostinfoCPUUsageInfo {\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoCPUUsageInfo\n     */\n    'idle'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoCPUUsageInfo\n     */\n    'system'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface HostinfoInfo\n */\nexport interface HostinfoInfo {\n    /**\n     * \n     * @type {HostinfoCPUInfo}\n     * @memberof HostinfoInfo\n     */\n    'cpu_info'?: HostinfoCPUInfo;\n    /**\n     * \n     * @type {HostinfoCPUUsageInfo}\n     * @memberof HostinfoInfo\n     */\n    'cpu_usage'?: HostinfoCPUUsageInfo;\n    /**\n     * \n     * @type {string}\n     * @memberof HostinfoInfo\n     */\n    'host'?: string;\n    /**\n     * Instances in the current host. The key is instance address\n     * @type {{ [key: string]: HostinfoInstanceInfo; }}\n     * @memberof HostinfoInfo\n     */\n    'instances'?: { [key: string]: HostinfoInstanceInfo; };\n    /**\n     * \n     * @type {HostinfoMemoryUsageInfo}\n     * @memberof HostinfoInfo\n     */\n    'memory_usage'?: HostinfoMemoryUsageInfo;\n    /**\n     * Containing unused partitions. The key is path in lower case. Note: deviceName is not used as the key, since TiDB and TiKV may return different deviceName for the same device.\n     * @type {{ [key: string]: HostinfoPartitionInfo; }}\n     * @memberof HostinfoInfo\n     */\n    'partitions'?: { [key: string]: HostinfoPartitionInfo; };\n}\n\n\n\n\n/**\n * \n * @export\n * @interface HostinfoInstanceInfo\n */\nexport interface HostinfoInstanceInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof HostinfoInstanceInfo\n     */\n    'partition_path_lower'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof HostinfoInstanceInfo\n     */\n    'type'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface HostinfoMemoryUsageInfo\n */\nexport interface HostinfoMemoryUsageInfo {\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoMemoryUsageInfo\n     */\n    'total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoMemoryUsageInfo\n     */\n    'used'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface HostinfoPartitionInfo\n */\nexport interface HostinfoPartitionInfo {\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoPartitionInfo\n     */\n    'free'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof HostinfoPartitionInfo\n     */\n    'fstype'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof HostinfoPartitionInfo\n     */\n    'path'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof HostinfoPartitionInfo\n     */\n    'total'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface InfoInfoResponse\n */\nexport interface InfoInfoResponse {\n    /**\n     * \n     * @type {boolean}\n     * @memberof InfoInfoResponse\n     */\n    'enable_experimental'?: boolean;\n    /**\n     * \n     * @type {boolean}\n     * @memberof InfoInfoResponse\n     */\n    'enable_telemetry'?: boolean;\n    /**\n     * \n     * @type {string}\n     * @memberof InfoInfoResponse\n     */\n    'ngm_state'?: string;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof InfoInfoResponse\n     */\n    'supported_features'?: Array<string>;\n    /**\n     * \n     * @type {VersionInfo}\n     * @memberof InfoInfoResponse\n     */\n    'version'?: VersionInfo;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface InfoTableSchema\n */\nexport interface InfoTableSchema {\n    /**\n     * \n     * @type {string}\n     * @memberof InfoTableSchema\n     */\n    'table_id'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof InfoTableSchema\n     */\n    'table_name'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface InfoWhoAmIResponse\n */\nexport interface InfoWhoAmIResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof InfoWhoAmIResponse\n     */\n    'display_name'?: string;\n    /**\n     * \n     * @type {boolean}\n     * @memberof InfoWhoAmIResponse\n     */\n    'is_shareable'?: boolean;\n    /**\n     * \n     * @type {boolean}\n     * @memberof InfoWhoAmIResponse\n     */\n    'is_writeable'?: boolean;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface LogsearchCreateTaskGroupRequest\n */\nexport interface LogsearchCreateTaskGroupRequest {\n    /**\n     * \n     * @type {LogsearchSearchLogRequest}\n     * @memberof LogsearchCreateTaskGroupRequest\n     */\n    'request': LogsearchSearchLogRequest;\n    /**\n     * \n     * @type {Array<ModelRequestTargetNode>}\n     * @memberof LogsearchCreateTaskGroupRequest\n     */\n    'targets': Array<ModelRequestTargetNode>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface LogsearchPreviewModel\n */\nexport interface LogsearchPreviewModel {\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchPreviewModel\n     */\n    'id'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchPreviewModel\n     */\n    'level'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof LogsearchPreviewModel\n     */\n    'message'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchPreviewModel\n     */\n    'task_group_id'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchPreviewModel\n     */\n    'task_id'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchPreviewModel\n     */\n    'time'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface LogsearchSearchLogRequest\n */\nexport interface LogsearchSearchLogRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchSearchLogRequest\n     */\n    'end_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchSearchLogRequest\n     */\n    'min_level'?: number;\n    /**\n     * We use a string array to represent multiple CNF pattern sceniaor like: SELECT * FROM t WHERE c LIKE \\'%s%\\' and c REGEXP \\'.*a.*\\' because Golang and Rust don\\'t support perl-like (?=re1)(?=re2)\n     * @type {Array<string>}\n     * @memberof LogsearchSearchLogRequest\n     */\n    'patterns'?: Array<string>;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchSearchLogRequest\n     */\n    'start_time'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface LogsearchTaskGroupModel\n */\nexport interface LogsearchTaskGroupModel {\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchTaskGroupModel\n     */\n    'id'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof LogsearchTaskGroupModel\n     */\n    'log_store_dir'?: string;\n    /**\n     * \n     * @type {LogsearchSearchLogRequest}\n     * @memberof LogsearchTaskGroupModel\n     */\n    'search_request'?: LogsearchSearchLogRequest;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchTaskGroupModel\n     */\n    'state'?: number;\n    /**\n     * \n     * @type {ModelRequestTargetStatistics}\n     * @memberof LogsearchTaskGroupModel\n     */\n    'target_stats'?: ModelRequestTargetStatistics;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface LogsearchTaskGroupResponse\n */\nexport interface LogsearchTaskGroupResponse {\n    /**\n     * \n     * @type {LogsearchTaskGroupModel}\n     * @memberof LogsearchTaskGroupResponse\n     */\n    'task_group'?: LogsearchTaskGroupModel;\n    /**\n     * \n     * @type {Array<LogsearchTaskModel>}\n     * @memberof LogsearchTaskGroupResponse\n     */\n    'tasks'?: Array<LogsearchTaskModel>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface LogsearchTaskModel\n */\nexport interface LogsearchTaskModel {\n    /**\n     * \n     * @type {string}\n     * @memberof LogsearchTaskModel\n     */\n    'error'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchTaskModel\n     */\n    'id'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof LogsearchTaskModel\n     */\n    'log_store_path'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchTaskModel\n     */\n    'size'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof LogsearchTaskModel\n     */\n    'slow_log_store_path'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchTaskModel\n     */\n    'state'?: number;\n    /**\n     * \n     * @type {ModelRequestTargetNode}\n     * @memberof LogsearchTaskModel\n     */\n    'target'?: ModelRequestTargetNode;\n    /**\n     * \n     * @type {number}\n     * @memberof LogsearchTaskModel\n     */\n    'task_group_id'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface MatrixMatrix\n */\nexport interface MatrixMatrix {\n    /**\n     * \n     * @type {{ [key: string]: Array<Array<number>>; }}\n     * @memberof MatrixMatrix\n     */\n    'data': { [key: string]: Array<Array<number>>; };\n    /**\n     * \n     * @type {Array<DecoratorLabelKey>}\n     * @memberof MatrixMatrix\n     */\n    'keyAxis': Array<DecoratorLabelKey>;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof MatrixMatrix\n     */\n    'timeAxis': Array<number>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface MetricsGetPromAddressConfigResponse\n */\nexport interface MetricsGetPromAddressConfigResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof MetricsGetPromAddressConfigResponse\n     */\n    'customized_addr'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof MetricsGetPromAddressConfigResponse\n     */\n    'deployed_addr'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface MetricsPutCustomPromAddressRequest\n */\nexport interface MetricsPutCustomPromAddressRequest {\n    /**\n     * \n     * @type {string}\n     * @memberof MetricsPutCustomPromAddressRequest\n     */\n    'address'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface MetricsPutCustomPromAddressResponse\n */\nexport interface MetricsPutCustomPromAddressResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof MetricsPutCustomPromAddressResponse\n     */\n    'normalized_address'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface MetricsQueryResponse\n */\nexport interface MetricsQueryResponse {\n    /**\n     * \n     * @type {object}\n     * @memberof MetricsQueryResponse\n     */\n    'data'?: object;\n    /**\n     * \n     * @type {string}\n     * @memberof MetricsQueryResponse\n     */\n    'status'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ModelRequestTargetNode\n */\nexport interface ModelRequestTargetNode {\n    /**\n     * \n     * @type {string}\n     * @memberof ModelRequestTargetNode\n     */\n    'display_name'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ModelRequestTargetNode\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ModelRequestTargetNode\n     */\n    'kind'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetNode\n     */\n    'port'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ModelRequestTargetStatistics\n */\nexport interface ModelRequestTargetStatistics {\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_pd_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_scheduling_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_ticdc_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_tidb_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_tiflash_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_tikv_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_tiproxy_nodes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ModelRequestTargetStatistics\n     */\n    'num_tso_nodes'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ProfilingGroupDetailResponse\n */\nexport interface ProfilingGroupDetailResponse {\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingGroupDetailResponse\n     */\n    'server_time'?: number;\n    /**\n     * \n     * @type {ProfilingTaskGroupModel}\n     * @memberof ProfilingGroupDetailResponse\n     */\n    'task_group_status'?: ProfilingTaskGroupModel;\n    /**\n     * \n     * @type {Array<ProfilingTaskModel>}\n     * @memberof ProfilingGroupDetailResponse\n     */\n    'tasks_status'?: Array<ProfilingTaskModel>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ProfilingStartRequest\n */\nexport interface ProfilingStartRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingStartRequest\n     */\n    'duration_secs'?: number;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof ProfilingStartRequest\n     */\n    'requsted_profiling_types'?: Array<string>;\n    /**\n     * \n     * @type {Array<ModelRequestTargetNode>}\n     * @memberof ProfilingStartRequest\n     */\n    'targets'?: Array<ModelRequestTargetNode>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ProfilingTaskGroupModel\n */\nexport interface ProfilingTaskGroupModel {\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskGroupModel\n     */\n    'id'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskGroupModel\n     */\n    'profile_duration_secs'?: number;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof ProfilingTaskGroupModel\n     */\n    'requsted_profiling_types'?: Array<string>;\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskGroupModel\n     */\n    'started_at'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskGroupModel\n     */\n    'state'?: number;\n    /**\n     * \n     * @type {ModelRequestTargetStatistics}\n     * @memberof ProfilingTaskGroupModel\n     */\n    'target_stats'?: ModelRequestTargetStatistics;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ProfilingTaskModel\n */\nexport interface ProfilingTaskModel {\n    /**\n     * \n     * @type {string}\n     * @memberof ProfilingTaskModel\n     */\n    'error'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskModel\n     */\n    'id'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof ProfilingTaskModel\n     */\n    'profiling_type'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ProfilingTaskModel\n     */\n    'raw_data_type'?: string;\n    /**\n     * The start running time, reset when retry. Used to estimate approximate profiling progress.\n     * @type {number}\n     * @memberof ProfilingTaskModel\n     */\n    'started_at'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskModel\n     */\n    'state'?: number;\n    /**\n     * \n     * @type {ModelRequestTargetNode}\n     * @memberof ProfilingTaskModel\n     */\n    'target'?: ModelRequestTargetNode;\n    /**\n     * \n     * @type {number}\n     * @memberof ProfilingTaskModel\n     */\n    'task_group_id'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface QueryeditorRunRequest\n */\nexport interface QueryeditorRunRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof QueryeditorRunRequest\n     */\n    'max_rows'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof QueryeditorRunRequest\n     */\n    'statements'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface QueryeditorRunResponse\n */\nexport interface QueryeditorRunResponse {\n    /**\n     * \n     * @type {number}\n     * @memberof QueryeditorRunResponse\n     */\n    'actual_rows'?: number;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof QueryeditorRunResponse\n     */\n    'column_names'?: Array<string>;\n    /**\n     * \n     * @type {string}\n     * @memberof QueryeditorRunResponse\n     */\n    'error_msg'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof QueryeditorRunResponse\n     */\n    'execution_ms'?: number;\n    /**\n     * \n     * @type {Array<Array<object>>}\n     * @memberof QueryeditorRunResponse\n     */\n    'rows'?: Array<Array<object>>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ResourcemanagerCalibrateResponse\n */\nexport interface ResourcemanagerCalibrateResponse {\n    /**\n     * \n     * @type {number}\n     * @memberof ResourcemanagerCalibrateResponse\n     */\n    'estimated_capacity'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ResourcemanagerGetConfigResponse\n */\nexport interface ResourcemanagerGetConfigResponse {\n    /**\n     * \n     * @type {boolean}\n     * @memberof ResourcemanagerGetConfigResponse\n     */\n    'enable'?: boolean;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface ResourcemanagerResourceInfoRowDef\n */\nexport interface ResourcemanagerResourceInfoRowDef {\n    /**\n     * \n     * @type {string}\n     * @memberof ResourcemanagerResourceInfoRowDef\n     */\n    'burstable'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ResourcemanagerResourceInfoRowDef\n     */\n    'name'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ResourcemanagerResourceInfoRowDef\n     */\n    'priority'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof ResourcemanagerResourceInfoRowDef\n     */\n    'ru_per_sec'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface RestErrorResponse\n */\nexport interface RestErrorResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof RestErrorResponse\n     */\n    'code'?: string;\n    /**\n     * \n     * @type {boolean}\n     * @memberof RestErrorResponse\n     */\n    'error'?: boolean;\n    /**\n     * \n     * @type {string}\n     * @memberof RestErrorResponse\n     */\n    'full_text'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof RestErrorResponse\n     */\n    'message'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface SlowqueryGetListRequest\n */\nexport interface SlowqueryGetListRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryGetListRequest\n     */\n    'begin_time'?: number;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof SlowqueryGetListRequest\n     */\n    'db'?: Array<string>;\n    /**\n     * \n     * @type {boolean}\n     * @memberof SlowqueryGetListRequest\n     */\n    'desc'?: boolean;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryGetListRequest\n     */\n    'digest'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryGetListRequest\n     */\n    'end_time'?: number;\n    /**\n     * example: \\\"Query,Digest\\\"\n     * @type {string}\n     * @memberof SlowqueryGetListRequest\n     */\n    'fields'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryGetListRequest\n     */\n    'limit'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryGetListRequest\n     */\n    'orderBy'?: string;\n    /**\n     * for showing slow queries in the statement detail page\n     * @type {Array<string>}\n     * @memberof SlowqueryGetListRequest\n     */\n    'plans'?: Array<string>;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof SlowqueryGetListRequest\n     */\n    'resource_group'?: Array<string>;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryGetListRequest\n     */\n    'text'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface SlowqueryModel\n */\nexport interface SlowqueryModel {\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'backoff_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'backoff_types'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'binary_plan'?: string;\n    /**\n     * Computed fields\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'binary_plan_json'?: string;\n    /**\n     * binary plan plain text\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'binary_plan_text'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'commit_backoff_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'commit_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'compile_time'?: number;\n    /**\n     * TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'connection_id'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'cop_proc_addr'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_proc_avg'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_proc_max'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_proc_p90'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'cop_wait_addr'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_wait_avg'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_wait_max'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'cop_wait_p90'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'db'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'digest'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'disk_max'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'exec_retry_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'get_commit_ts_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'host'?: string;\n    /**\n     * IA remote read\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'ia_remote_read_segment_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'ia_remote_read_segment_wait_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'index_names'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'instance'?: string;\n    /**\n     * Basic\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'is_internal'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'local_latch_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'lock_keys_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'mem_arbitration'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'memory_max'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'optimize_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'parse_time'?: number;\n    /**\n     * deprecated, replaced by BinaryPlanText\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'plan'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'plan_from_binding'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'plan_from_cache'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'prepared'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'preproc_subqueries_time'?: number;\n    /**\n     * Detail\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'prev_stmt'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'prewrite_region'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'prewrite_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'process_keys'?: number;\n    /**\n     * Time\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'process_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'query'?: string;\n    /**\n     * latency\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'query_time'?: number;\n    /**\n     * Coprocessor\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'request_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'resolve_lock_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'resource_group'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'rewrite_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'rocksdb_block_cache_hit_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'rocksdb_block_read_byte'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'rocksdb_block_read_count'?: number;\n    /**\n     * RocksDB\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'rocksdb_delete_skipped_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'rocksdb_key_skipped_count'?: number;\n    /**\n     * Resource Control\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'ru'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'stats'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'success'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'time_queued_by_rc'?: number;\n    /**\n     * finish time\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'total_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'txn_retry'?: number;\n    /**\n     * TODO: Switch back to uint64 when modern browser as well as Swagger handles BigInt well.\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'txn_start_ts'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_received_tiflash_cross_zone'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_received_tiflash_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_received_tikv_cross_zone'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_received_tikv_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_sent_tiflash_cross_zone'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_sent_tiflash_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_sent_tikv_cross_zone'?: number;\n    /**\n     * Network fields\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'unpacked_bytes_sent_tikv_total'?: number;\n    /**\n     * Connection\n     * @type {string}\n     * @memberof SlowqueryModel\n     */\n    'user'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'wait_prewrite_binlog_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'wait_ts'?: number;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof SlowqueryModel\n     */\n    'warnings'?: Array<number>;\n    /**\n     * Transaction\n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'write_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'write_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof SlowqueryModel\n     */\n    'write_sql_response_total'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface SsoCreateImpersonationRequest\n */\nexport interface SsoCreateImpersonationRequest {\n    /**\n     * \n     * @type {string}\n     * @memberof SsoCreateImpersonationRequest\n     */\n    'password'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof SsoCreateImpersonationRequest\n     */\n    'sql_user'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface SsoSetConfigRequest\n */\nexport interface SsoSetConfigRequest {\n    /**\n     * \n     * @type {ConfigSSOCoreConfig}\n     * @memberof SsoSetConfigRequest\n     */\n    'config'?: ConfigSSOCoreConfig;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface SsoSSOImpersonationModel\n */\nexport interface SsoSSOImpersonationModel {\n    /**\n     * \n     * @type {string}\n     * @memberof SsoSSOImpersonationModel\n     */\n    'last_impersonate_status'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof SsoSSOImpersonationModel\n     */\n    'sql_user'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface StatementBinding\n */\nexport interface StatementBinding {\n    /**\n     * \n     * @type {string}\n     * @memberof StatementBinding\n     */\n    'plan_digest'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementBinding\n     */\n    'source'?: StatementBindingSourceEnum;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementBinding\n     */\n    'status'?: StatementBindingStatusEnum;\n}\n\nexport const StatementBindingSourceEnum = {\n    manual: 'manual',\n    history: 'history',\n    capture: 'capture',\n    evolve: 'evolve'\n} as const;\n\nexport type StatementBindingSourceEnum = typeof StatementBindingSourceEnum[keyof typeof StatementBindingSourceEnum];\nexport const StatementBindingStatusEnum = {\n    enabled: 'enabled',\n    using: 'using',\n    disabled: 'disabled',\n    deleted: 'deleted',\n    invalid: 'invalid',\n    rejected: 'rejected',\n    pending_verify: 'pending verify'\n} as const;\n\nexport type StatementBindingStatusEnum = typeof StatementBindingStatusEnum[keyof typeof StatementBindingStatusEnum];\n\n\n\n\n\n/**\n * \n * @export\n * @interface StatementEditableConfig\n */\nexport interface StatementEditableConfig {\n    /**\n     * \n     * @type {boolean}\n     * @memberof StatementEditableConfig\n     */\n    'enable'?: boolean;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementEditableConfig\n     */\n    'history_size'?: number;\n    /**\n     * \n     * @type {boolean}\n     * @memberof StatementEditableConfig\n     */\n    'internal_query'?: boolean;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementEditableConfig\n     */\n    'max_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementEditableConfig\n     */\n    'refresh_interval'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface StatementGetStatementsRequest\n */\nexport interface StatementGetStatementsRequest {\n    /**\n     * \n     * @type {number}\n     * @memberof StatementGetStatementsRequest\n     */\n    'begin_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementGetStatementsRequest\n     */\n    'end_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementGetStatementsRequest\n     */\n    'fields'?: string;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof StatementGetStatementsRequest\n     */\n    'resource_groups'?: Array<string>;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof StatementGetStatementsRequest\n     */\n    'schemas'?: Array<string>;\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof StatementGetStatementsRequest\n     */\n    'stmt_types'?: Array<string>;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementGetStatementsRequest\n     */\n    'text'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface StatementModel\n */\nexport interface StatementModel {\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_affected_rows'?: number;\n    /**\n     * avg total back off time per sql\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_backoff_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_commit_backoff_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_commit_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_compile_latency'?: number;\n    /**\n     * avg process time per copr task\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_cop_process_time'?: number;\n    /**\n     * avg wait time per copr task\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_cop_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_disk'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_get_commit_ts_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_ia_read_segment_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_ia_remote_read_segment_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_ia_remote_read_segment_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_latency'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_local_latch_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_mem'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_mem_arbitration'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_parse_latency'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_prewrite_regions'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_prewrite_time'?: number;\n    /**\n     * avg total process time per sql\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_process_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_processed_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_resolve_lock_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_rocksdb_block_cache_hit_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_rocksdb_block_read_byte'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_rocksdb_block_read_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_rocksdb_delete_skipped_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_rocksdb_key_skipped_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_ru'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_time_queued_by_rc'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_total_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_txn_retry'?: number;\n    /**\n     * avg total wait time per sql\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_write_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'avg_write_size'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'binary_plan'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'binary_plan_json'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'binary_plan_text'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'digest'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'digest_text'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'exec_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'first_seen'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'index_names'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'last_seen'?: number;\n    /**\n     * max back off time per sql\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_backoff_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_commit_backoff_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_commit_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_compile_latency'?: number;\n    /**\n     * max process time per copr task\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_cop_process_time'?: number;\n    /**\n     * max wait time per copr task\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_cop_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_disk'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_get_commit_ts_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_ia_read_segment_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_ia_remote_read_segment_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_ia_remote_read_segment_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_latency'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_local_latch_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_mem'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_mem_arbitration'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_parse_latency'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_prewrite_regions'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_prewrite_time'?: number;\n    /**\n     * max process time per sql\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_process_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_processed_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_resolve_lock_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_rocksdb_block_cache_hit_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_rocksdb_block_read_byte'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_rocksdb_block_read_count'?: number;\n    /**\n     * RocksDB\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_rocksdb_delete_skipped_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_rocksdb_key_skipped_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_ru'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_time_queued_by_rc'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_total_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_txn_retry'?: number;\n    /**\n     * max wait time per sql\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_write_keys'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'max_write_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'min_latency'?: number;\n    /**\n     * deprecated, replaced by BinaryPlanText\n     * @type {string}\n     * @memberof StatementModel\n     */\n    'plan'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'plan_cache_hits'?: number;\n    /**\n     * \n     * @type {boolean}\n     * @memberof StatementModel\n     */\n    'plan_can_be_bound'?: boolean;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'plan_count'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'plan_digest'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'plan_hint'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'prev_sample_text'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'query_sample_text'?: string;\n    /**\n     * Computed fields\n     * @type {string}\n     * @memberof StatementModel\n     */\n    'related_schemas'?: string;\n    /**\n     * Resource Control\n     * @type {string}\n     * @memberof StatementModel\n     */\n    'resource_group'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'sample_user'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'schema_name'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'stmt_type'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_backoff_times'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_cop_task_num'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_errors'?: number;\n    /**\n     * IA remote read segment metrics\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_ia_read_segment_count'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_ia_remote_read_segment_size'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_ia_remote_read_segment_wait_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_latency'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_ru'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_received_tiflash_cross_zone'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_received_tiflash_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_received_tikv_cross_zone'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_received_tikv_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_sent_tiflash_cross_zone'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_sent_tiflash_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_sent_tikv_cross_zone'?: number;\n    /**\n     * Network Fields\n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_unpacked_bytes_sent_tikv_total'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'sum_warnings'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'summary_begin_time'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof StatementModel\n     */\n    'summary_end_time'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof StatementModel\n     */\n    'table_names'?: string;\n}\n\n\n/**\n *\n * @export\n * @interface StatementTimeRange\n */\nexport interface StatementTimeRange {\n  /**\n   *\n   * @type {number}\n   * @memberof StatementTimeRange\n   */\n  begin_time?: number\n  /**\n   *\n   * @type {number}\n   * @memberof StatementTimeRange\n   */\n  end_time?: number\n}\n\n\n\n/**\n * \n * @export\n * @interface TopologyAlertManagerInfo\n */\nexport interface TopologyAlertManagerInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyAlertManagerInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyAlertManagerInfo\n     */\n    'port'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopologyGrafanaInfo\n */\nexport interface TopologyGrafanaInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyGrafanaInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyGrafanaInfo\n     */\n    'port'?: number;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopologyPDInfo\n */\nexport interface TopologyPDInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyPDInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyPDInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyPDInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyPDInfo\n     */\n    'port'?: number;\n    /**\n     * Ts = 0 means unknown\n     * @type {number}\n     * @memberof TopologyPDInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyPDInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyPDInfo\n     */\n    'version'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopologySchedulingInfo\n */\nexport interface TopologySchedulingInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologySchedulingInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologySchedulingInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologySchedulingInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologySchedulingInfo\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologySchedulingInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologySchedulingInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologySchedulingInfo\n     */\n    'version'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopologyStoreInfo\n */\nexport interface TopologyStoreInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyStoreInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyStoreInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyStoreInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {{ [key: string]: string; }}\n     * @memberof TopologyStoreInfo\n     */\n    'labels'?: { [key: string]: string; };\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyStoreInfo\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyStoreInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyStoreInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyStoreInfo\n     */\n    'status_port'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyStoreInfo\n     */\n    'version'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopologyStoreLabels\n */\nexport interface TopologyStoreLabels {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyStoreLabels\n     */\n    'address'?: string;\n    /**\n     * \n     * @type {{ [key: string]: string; }}\n     * @memberof TopologyStoreLabels\n     */\n    'labels'?: { [key: string]: string; };\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopologyStoreLocation\n */\nexport interface TopologyStoreLocation {\n    /**\n     * \n     * @type {Array<string>}\n     * @memberof TopologyStoreLocation\n     */\n    'location_labels'?: Array<string>;\n    /**\n     * \n     * @type {Array<TopologyStoreLabels>}\n     * @memberof TopologyStoreLocation\n     */\n    'stores'?: Array<TopologyStoreLabels>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopologyTiCDCInfo\n */\nexport interface TopologyTiCDCInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiCDCInfo\n     */\n    'cluster_name'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiCDCInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiCDCInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiCDCInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiCDCInfo\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiCDCInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiCDCInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiCDCInfo\n     */\n    'status_port'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiCDCInfo\n     */\n    'version'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopologyTiDBInfo\n */\nexport interface TopologyTiDBInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiDBInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiDBInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiDBInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiDBInfo\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiDBInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiDBInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiDBInfo\n     */\n    'status_port'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiDBInfo\n     */\n    'version'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopologyTiProxyInfo\n */\nexport interface TopologyTiProxyInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiProxyInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiProxyInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiProxyInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiProxyInfo\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiProxyInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiProxyInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTiProxyInfo\n     */\n    'status_port'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTiProxyInfo\n     */\n    'version'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopologyTSOInfo\n */\nexport interface TopologyTSOInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTSOInfo\n     */\n    'deploy_path'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTSOInfo\n     */\n    'git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTSOInfo\n     */\n    'ip'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTSOInfo\n     */\n    'port'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTSOInfo\n     */\n    'start_timestamp'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopologyTSOInfo\n     */\n    'status'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopologyTSOInfo\n     */\n    'version'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopsqlEditableConfig\n */\nexport interface TopsqlEditableConfig {\n    /**\n     * \n     * @type {boolean}\n     * @memberof TopsqlEditableConfig\n     */\n    'enable'?: boolean;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopsqlInstanceItem\n */\nexport interface TopsqlInstanceItem {\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlInstanceItem\n     */\n    'instance'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlInstanceItem\n     */\n    'instance_type'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopsqlInstanceResponse\n */\nexport interface TopsqlInstanceResponse {\n    /**\n     * \n     * @type {Array<TopsqlInstanceItem>}\n     * @memberof TopsqlInstanceResponse\n     */\n    'data'?: Array<TopsqlInstanceItem>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopsqlSummaryByItem\n */\nexport interface TopsqlSummaryByItem {\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryByItem\n     */\n    'cpu_time_ms'?: Array<number>;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryByItem\n     */\n    'cpu_time_ms_sum'?: number;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryByItem\n     */\n    'logical_io_bytes'?: Array<number>;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryByItem\n     */\n    'logical_io_bytes_sum'?: number;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryByItem\n     */\n    'network_bytes'?: Array<number>;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryByItem\n     */\n    'network_bytes_sum'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlSummaryByItem\n     */\n    'text'?: string;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryByItem\n     */\n    'timestamp_sec'?: Array<number>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopsqlSummaryItem\n */\nexport interface TopsqlSummaryItem {\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'cpu_time_ms'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'duration_per_exec_ms'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'exec_count_per_sec'?: number;\n    /**\n     * \n     * @type {boolean}\n     * @memberof TopsqlSummaryItem\n     */\n    'is_other'?: boolean;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'logical_io_bytes'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'network_bytes'?: number;\n    /**\n     * \n     * @type {Array<TopsqlSummaryPlanItem>}\n     * @memberof TopsqlSummaryItem\n     */\n    'plans'?: Array<TopsqlSummaryPlanItem>;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'scan_indexes_per_sec'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryItem\n     */\n    'scan_records_per_sec'?: number;\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlSummaryItem\n     */\n    'sql_digest'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlSummaryItem\n     */\n    'sql_text'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopsqlSummaryPlanItem\n */\nexport interface TopsqlSummaryPlanItem {\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'cpu_time_ms'?: Array<number>;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'duration_per_exec_ms'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'exec_count_per_sec'?: number;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'logical_io_bytes'?: Array<number>;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'network_bytes'?: Array<number>;\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'plan_digest'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'plan_text'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'scan_indexes_per_sec'?: number;\n    /**\n     * \n     * @type {number}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'scan_records_per_sec'?: number;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof TopsqlSummaryPlanItem\n     */\n    'timestamp_sec'?: Array<number>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopsqlSummaryResponse\n */\nexport interface TopsqlSummaryResponse {\n    /**\n     * \n     * @type {Array<TopsqlSummaryItem>}\n     * @memberof TopsqlSummaryResponse\n     */\n    'data'?: Array<TopsqlSummaryItem>;\n    /**\n     * \n     * @type {Array<TopsqlSummaryByItem>}\n     * @memberof TopsqlSummaryResponse\n     */\n    'data_by'?: Array<TopsqlSummaryByItem>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopsqlTikvNetworkIoCollectionConfig\n */\nexport interface TopsqlTikvNetworkIoCollectionConfig {\n    /**\n     * \n     * @type {boolean}\n     * @memberof TopsqlTikvNetworkIoCollectionConfig\n     */\n    'enable'?: boolean;\n    /**\n     * \n     * @type {boolean}\n     * @memberof TopsqlTikvNetworkIoCollectionConfig\n     */\n    'is_multi_value'?: boolean;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface TopsqlUpdateTikvNetworkIoCollectionResponse\n */\nexport interface TopsqlUpdateTikvNetworkIoCollectionResponse {\n    /**\n     * \n     * @type {Array<RestErrorResponse>}\n     * @memberof TopsqlUpdateTikvNetworkIoCollectionResponse\n     */\n    'warnings'?: Array<RestErrorResponse>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface UserAuthenticateForm\n */\nexport interface UserAuthenticateForm {\n    /**\n     * FIXME: Use strong type\n     * @type {string}\n     * @memberof UserAuthenticateForm\n     */\n    'extra'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof UserAuthenticateForm\n     */\n    'password'?: string;\n    /**\n     * \n     * @type {number}\n     * @memberof UserAuthenticateForm\n     */\n    'type'?: number;\n    /**\n     * Does not present for AuthTypeSharingCode\n     * @type {string}\n     * @memberof UserAuthenticateForm\n     */\n    'username'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface UserGetLoginInfoResponse\n */\nexport interface UserGetLoginInfoResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof UserGetLoginInfoResponse\n     */\n    'sql_auth_public_key'?: string;\n    /**\n     * \n     * @type {Array<number>}\n     * @memberof UserGetLoginInfoResponse\n     */\n    'supported_auth_types'?: Array<number>;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface UserSignOutInfo\n */\nexport interface UserSignOutInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof UserSignOutInfo\n     */\n    'end_session_url'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface UserTokenResponse\n */\nexport interface UserTokenResponse {\n    /**\n     * \n     * @type {string}\n     * @memberof UserTokenResponse\n     */\n    'expire'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof UserTokenResponse\n     */\n    'token'?: string;\n}\n\n\n\n\n/**\n * \n * @export\n * @interface VersionInfo\n */\nexport interface VersionInfo {\n    /**\n     * \n     * @type {string}\n     * @memberof VersionInfo\n     */\n    'build_git_hash'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof VersionInfo\n     */\n    'build_time'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof VersionInfo\n     */\n    'internal_version'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof VersionInfo\n     */\n    'pd_version'?: string;\n    /**\n     * \n     * @type {string}\n     * @memberof VersionInfo\n     */\n    'standalone'?: string;\n}\n\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/AnimatedSkeleton/index.module.less",
    "content": "@import 'antd/es/style/mixins/motion.less';\n\n.container :global {\n  .skeletonAnimationFirstTime {\n    animation: 0.5s linear 0.5s antFadeIn;\n    animation-fill-mode: both;\n    animation-iteration-count: 1;\n  }\n  .skeletonAnimationNotFirstTime {\n    animation: 0.5s linear 0 antFadeIn;\n    animation-fill-mode: both;\n    animation-iteration-count: 1;\n  }\n\n  .contentAnimation {\n    animation: 0.2s linear 0s antFadeIn;\n    animation-fill-mode: both;\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/AnimatedSkeleton/index.tsx",
    "content": "import React, { useEffect, useState } from 'react'\nimport cx from 'classnames'\nimport { Skeleton } from 'antd'\nimport { SkeletonProps } from 'antd/lib/skeleton'\nimport { AppearAnimate } from '..'\n\nimport styles from './index.module.less'\n\nexport interface IAnimatedSkeletonProps extends SkeletonProps {\n  showSkeleton?: boolean\n  children?: React.ReactNode\n  style?: React.CSSProperties\n}\n\nfunction AnimatedSkeleton({\n  showSkeleton,\n  children,\n  style,\n  ...restProps\n}: IAnimatedSkeletonProps) {\n  const [skeletonAppears, setSkeletonAppears] = useState(0)\n\n  useEffect(() => {\n    if (showSkeleton) {\n      setSkeletonAppears((v) => v + 1)\n    }\n  }, [showSkeleton])\n\n  return (\n    <div className={cx(styles.container)} style={style}>\n      {showSkeleton && (\n        <div\n          className={cx({\n            skeletonAnimationFirstTime: skeletonAppears === 1,\n            skeletonAnimationNotFirstTime: skeletonAppears > 1\n          })}\n        >\n          <Skeleton\n            active\n            title={false}\n            paragraph={{ rows: 3 }}\n            {...restProps}\n          />\n        </div>\n      )}\n      {!showSkeleton && (\n        <AppearAnimate motionName=\"contentAnimation\">{children}</AppearAnimate>\n      )}\n    </div>\n  )\n}\n\nexport default React.memo(AnimatedSkeleton)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/AppearAnimate/index.tsx",
    "content": "import cx from 'classnames'\nimport React, { useState, useCallback, useRef } from 'react'\nimport { useEventListener } from 'ahooks'\n\nexport interface IAppearAnimateProps\n  extends React.HTMLAttributes<HTMLDivElement> {\n  motionName: string\n}\n\n// A component similar to CSSMotion but is simpler, and avoids some edge case bugs.\n// It simply removes the animation class after animation completes.\nfunction AppearAnimate({\n  className,\n  motionName,\n  children\n}: IAppearAnimateProps) {\n  const [isFirst, setIsFirst] = useState(true)\n\n  const handleAnimationEnd = useCallback(() => {\n    setIsFirst(false)\n  }, [])\n\n  const ref = useRef(null)\n  useEventListener('animationend', handleAnimationEnd, { target: ref })\n\n  return (\n    <div ref={ref} className={cx(className, { [motionName]: isFirst })}>\n      {children}\n    </div>\n  )\n}\n\nexport default React.memo(AppearAnimate)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/AutoRefreshButton/AutoRefreshButton.tsx",
    "content": "import React, { useEffect, useRef, useState } from 'react'\nimport { DownOutlined, SyncOutlined } from '@ant-design/icons'\nimport { Dropdown, Menu } from 'antd'\nimport { useSpring, animated } from 'react-spring'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\nimport { useTranslation } from 'react-i18next'\nimport { addTranslationResource } from '@lib/utils/i18n'\nimport styles from './index.module.less'\nimport { useChange } from '@lib/utils/useChange'\nimport { useControllableValue, useMemoizedFn } from 'ahooks'\n\nexport const DEFAULT_AUTO_REFRESH_OPTIONS = [\n  30,\n  60,\n  5 * 60,\n  15 * 60,\n  30 * 60,\n  1 * 60 * 60,\n  2 * 60 * 60\n]\n\nexport interface IAutoRefreshButtonProps {\n  options?: number[]\n  // set to 0 will stop the auto refresh\n  defaultValue?: number\n  value?: number\n  onChange?: (number) => void\n  onRefresh?: () => void\n  // set to false will pause the auto refresh\n  disabled?: boolean\n}\n\nconst translations = {\n  en: {\n    refresh: 'Refresh',\n    auto_refresh: {\n      title: 'Auto Refresh',\n      off: 'Off'\n    }\n  },\n  zh: {\n    refresh: '刷新',\n    auto_refresh: {\n      title: '自动刷新',\n      off: '关闭'\n    }\n  }\n}\n\nfor (const key in translations) {\n  addTranslationResource(key, {\n    component: {\n      autoRefreshButton: translations[key]\n    }\n  })\n}\n\nexport function AutoRefreshButton({\n  options = DEFAULT_AUTO_REFRESH_OPTIONS,\n  onRefresh,\n  disabled = false,\n  ...props\n}: IAutoRefreshButtonProps) {\n  const { t } = useTranslation()\n  const [interval, setInterval] = useControllableValue<number>(props, {\n    defaultValue: 60\n  })\n  const [remaining, setRemaining] = useState<number>(0)\n\n  const autoRefreshMenu = (\n    <Menu\n      onClick={({ key }) => setInterval(parseInt(key as string))}\n      selectedKeys={[String(interval || 0)]}\n    >\n      <Menu.ItemGroup\n        title={t('component.autoRefreshButton.auto_refresh.title')}\n      >\n        <Menu.Item key=\"0\">\n          {t('component.autoRefreshButton.auto_refresh.off')}\n        </Menu.Item>\n        <Menu.Divider />\n        {options.map((sec) => {\n          return (\n            <Menu.Item key={String(sec)} data-e2e={`auto_refresh_time_${sec}`}>\n              {getValueFormat('s')(sec, 0)}\n            </Menu.Item>\n          )\n        })}\n      </Menu.ItemGroup>\n    </Menu>\n  )\n\n  const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n\n  const resetTimer = useMemoizedFn(() => {\n    clearTimeout(timer.current!)\n    timer.current = undefined\n    setRemaining(interval)\n  })\n\n  useChange(() => {\n    clearTimeout(timer.current!)\n    timer.current = undefined\n    if (\n      // If remaining seconds is less than the new interval, keep the current remaining seconds.\n      // Otherwise, set remaining seconds to new interval.\n      remaining > interval ||\n      remaining === 0\n    ) {\n      setRemaining(interval)\n    }\n  }, [interval])\n\n  const handleRefresh = useMemoizedFn(async () => {\n    if (disabled) {\n      return\n    }\n    resetTimer()\n    onRefresh?.()\n  })\n\n  useEffect(() => {\n    // stop or pause auto refresh need to clear timer\n    if (!interval || disabled) {\n      if (!!timer.current) {\n        clearTimeout(timer.current)\n        timer.current = undefined\n      }\n      return\n    }\n\n    timer.current = setTimeout(() => {\n      if (remaining === 0) {\n        setRemaining(interval)\n        handleRefresh()\n      } else {\n        setRemaining((r) => r - 1)\n      }\n    }, 1000)\n    return () => clearTimeout(timer.current!)\n  }, [interval, disabled, remaining, /* unchange */ handleRefresh])\n\n  return (\n    <Dropdown.Button\n      data-e2e=\"auto-refresh-button\"\n      className={styles.auto_refresh_btn}\n      disabled={disabled}\n      onClick={handleRefresh}\n      overlay={autoRefreshMenu}\n      trigger={['click']}\n      icon={\n        <>\n          {Boolean(interval) && (\n            <span className={styles.auto_refresh_secs}>\n              {getValueFormat('s')(interval, 0)}\n            </span>\n          )}\n          <DownOutlined />\n        </>\n      }\n    >\n      {Boolean(interval) ? (\n        <RefreshProgress value={1 - remaining / interval} />\n      ) : (\n        <SyncOutlined />\n      )}\n      {t('component.autoRefreshButton.refresh')}\n    </Dropdown.Button>\n  )\n}\n\nfunction RefreshProgress(props) {\n  const { value } = props\n  const r = 50\n  const totalLength = 2 * Math.PI * r\n  const [springProps, setSpringProps] = useSpring(() => ({\n    value: 0\n  }))\n\n  useEffect(() => {\n    setSpringProps({\n      value\n    })\n  }, [setSpringProps, value])\n\n  return (\n    <svg\n      viewBox=\"0 0 120 120\"\n      width=\"1em\"\n      height=\"1em\"\n      className=\"anticon\"\n      style={{\n        transform: 'rotate(-90deg)'\n      }}\n    >\n      <circle\n        cx=\"60\"\n        cy=\"60\"\n        r={r}\n        fill=\"none\"\n        stroke=\"#eee\"\n        strokeWidth=\"20\"\n      />\n      <animated.circle\n        cx=\"60\"\n        cy=\"60\"\n        r={r}\n        fill=\"none\"\n        stroke={springProps.value.interpolate({\n          range: [0, 1],\n          output: ['#989CAB', '#4571FF']\n        })}\n        strokeWidth=\"20\"\n        strokeDasharray={totalLength}\n        strokeDashoffset={springProps.value.interpolate({\n          range: [0, 1],\n          output: [totalLength, 0]\n        })}\n      />\n    </svg>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/AutoRefreshButton/index.module.less",
    "content": ".auto_refresh_btn {\n  :global {\n    .ant-dropdown-trigger {\n      width: auto;\n      padding: 0 10px;\n\n      display: flex;\n      align-items: center;\n    }\n  }\n\n  .auto_refresh_secs {\n    font-size: 14px;\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/AutoRefreshButton/index.ts",
    "content": "import { AutoRefreshButton } from './AutoRefreshButton'\n\nexport default AutoRefreshButton\nexport * from './AutoRefreshButton'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Bar/Bar.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n@bar-height: 8px;\n@bar-color: lighten(@primary-color, 10%);\n@bar-stack-color: @gray-3;\n@error-bar-height: 6px;\n@error-bar-color: @gold-5;\n@error-line-width: 2px;\n\n.container {\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n  min-height: 1em;\n  height: unit(@line-height-base, em);\n}\n\n.text {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  padding-right: 5px;\n}\n\n.bar_container {\n  flex-grow: 1;\n  position: relative;\n  height: @bar-height;\n  background: @bar-stack-color;\n}\n\n.bar {\n  position: absolute;\n  top: 0;\n  height: 100%;\n  background-color: @bar-color;\n}\n\n.error_bar {\n  position: absolute;\n  top: 50%;\n  height: @error-line-width;\n  margin-top: -(@error-line-width / 2); // https://github.com/less/less.js/issues/3608#issuecomment-811086683\n  background-color: @error-bar-color;\n\n  &::before {\n    content: '';\n    position: absolute;\n    height: @error-bar-height;\n    width: @error-line-width;\n    margin-top: -(@error-bar-height / 2);\n    background-color: @error-bar-color;\n    top: 50%;\n  }\n\n  &.min_bar {\n    &::before {\n      left: 0;\n    }\n  }\n\n  &.max_bar {\n    &::before {\n      right: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Bar/Bar.tsx",
    "content": "import React, { useMemo } from 'react'\nimport cx from 'classnames'\nimport clamp from 'lodash/clamp'\nimport sum from 'lodash/sum'\n\nimport styles from './Bar.module.less'\n\nexport interface IBarProps {\n  value: number[] | number\n  colors?: (string | null)[]\n  capacity: number\n  min?: number\n  max?: number\n  className?: string\n  children?: React.ReactNode\n  textWidth?: number | string\n}\n\nfunction Bar({\n  value,\n  colors,\n  capacity,\n  min,\n  max,\n  className,\n  children,\n  textWidth,\n  ...rest\n}: IBarProps) {\n  const clampedValues = useMemo(() => {\n    if (value instanceof Array) {\n      const r: [number, number][] = []\n      let sum = 0\n      value.forEach((value) => {\n        let v: number\n        if (sum + value <= capacity) {\n          v = value\n        } else if (sum < capacity) {\n          v = capacity - sum\n        } else {\n          v = 0\n        }\n        r.push([sum, v])\n        sum += v\n      })\n      return r\n    } else {\n      return [[0, clamp(value, 0, capacity)]]\n    }\n  }, [value, capacity])\n\n  const valuesSum = useMemo(\n    () => sum(clampedValues.map(([_s, v]) => v)),\n    [clampedValues]\n  )\n\n  if (min != null) {\n    min = clamp(min, 0, valuesSum)\n    if ((valuesSum - min) / capacity < 0.01) {\n      min = undefined\n    }\n  }\n  if (max != null) {\n    max = clamp(max, valuesSum, capacity)\n    if ((max - valuesSum) / capacity < 0.01) {\n      max = undefined\n    }\n  }\n\n  return (\n    <div className={cx(styles.container, className)} {...rest}>\n      {children && (\n        <div className={styles.text} style={{ width: textWidth }}>\n          {children}\n        </div>\n      )}\n      <div className={styles.bar_container}>\n        {clampedValues.map(([offset, value], idx) => (\n          <div\n            className={cx(styles.bar)}\n            style={{\n              width: `${(value / capacity) * 100}%`,\n              left: `${(offset / capacity) * 100}%`,\n              backgroundColor: colors?.[idx] || undefined\n            }}\n            key={idx}\n          />\n        ))}\n        {min != null && (\n          <div\n            className={cx(styles.error_bar, styles.min_bar)}\n            style={{\n              left: `${(min / capacity) * 100}%`,\n              width: `${((valuesSum - min) / capacity) * 100}%`\n            }}\n          />\n        )}\n        {max != null && (\n          <div\n            className={cx(styles.error_bar, styles.max_bar)}\n            style={{\n              left: `${(valuesSum / capacity) * 100}%`,\n              width: `${((max - valuesSum) / capacity) * 100}%`\n            }}\n          />\n        )}\n      </div>\n    </div>\n  )\n}\n\nexport default Bar\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Bar/index.tsx",
    "content": "import Bar from './Bar'\nexport * from './Bar'\nexport default Bar\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/BaseSelect/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n@import 'antd/es/style/mixins/index';\n@import 'antd/es/input/style/mixin';\n@import 'antd/es/select/style/index.less'; // Exist index.less and index.js both, we need to declare it explictly else it will find index.js\n\n.baseSelect {\n  .reset-component;\n  position: relative;\n  display: inline-block;\n}\n\n.baseSelectInner {\n  position: relative;\n  background-color: @select-background;\n  border: @border-width-base @border-style-base @select-border-color;\n  border-radius: @border-radius-base;\n  transition: all 0.3s @ease-in-out;\n  display: flex;\n  width: 100%;\n  height: @input-height-base;\n  padding: 0 @input-padding-horizontal-base;\n  cursor: pointer;\n  color: @text-color;\n\n  &.focused {\n    .active();\n  }\n\n  &.disabled {\n    color: @disabled-color;\n    background: @input-disabled-bg;\n    cursor: not-allowed;\n\n    .baseSelectInput {\n      cursor: not-allowed;\n    }\n  }\n\n  &:not(.disabled):hover {\n    .hover();\n  }\n}\n\n:global(.ant-form-item-has-error) {\n  .baseSelectInner {\n    border-color: @error-color !important;\n    &.focused {\n      .active(@error-color);\n    }\n  }\n}\n\n.baseSelectInput {\n  opacity: 0;\n  position: absolute;\n  top: 0;\n  left: 0;\n  background: transparent;\n  border: none;\n  outline: none;\n  cursor: pointer;\n  width: 100%;\n  height: @select-height-without-border;\n}\n\n.baseSelectValueDisplay {\n  position: relative;\n  display: block;\n  padding-right: @selection-item-padding;\n  font-weight: normal;\n  font-size: @select-dropdown-font-size;\n  line-height: @select-height-without-border;\n  transition: all 0.3s;\n  pointer-events: none;\n  width: 100%;\n\n  &.isPlaceholder {\n    opacity: 0.4;\n  }\n}\n\n.baseSelectArrow {\n  position: absolute;\n  top: 53%; // The same as Ant-design's select\n  right: @input-padding-horizontal-base;\n  width: @font-size-sm;\n  height: @font-size-sm;\n  margin-top: -(@font-size-sm / 2);\n  color: @disabled-color;\n  font-size: @font-size-sm;\n  line-height: 1;\n  text-align: center;\n  pointer-events: none;\n}\n\n.baseSelectOverlay {\n  background-color: @select-dropdown-bg;\n  border-radius: @border-radius-base;\n  outline: none;\n  box-shadow: @box-shadow-base;\n  box-sizing: border-box;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/BaseSelect/index.stories.tsx",
    "content": "import React from 'react'\nimport { Button } from 'antd'\n\nimport BaseSelect from '.'\n\nexport default {\n  title: 'Select/Base Select'\n}\n\nexport const shortContent = () => (\n  <BaseSelect\n    dropdownRender={() => <div>Content</div>}\n    valueRender={() => <span>Short</span>}\n  />\n)\n\nexport const longContent = () => (\n  <BaseSelect\n    style={{ width: 120 }}\n    dropdownRender={() => <div>Content</div>}\n    valueRender={() => <span>Very Lonnnnnnnnng Value</span>}\n  />\n)\n\nexport const disabled = () => (\n  <BaseSelect\n    disabled\n    dropdownRender={() => <div>Content</div>}\n    valueRender={() => <span>Disabled</span>}\n  />\n)\n\nexport const antdButton = () => <Button>Antd Button</Button>\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/BaseSelect/index.tsx",
    "content": "import React, { useState, useCallback, useRef, useMemo } from 'react'\nimport cx from 'classnames'\nimport { useEventListener } from 'ahooks'\nimport { DownOutlined } from '@ant-design/icons'\nimport Trigger from 'rc-trigger'\nimport KeyCode from 'rc-util/lib/KeyCode'\nimport _ from 'lodash'\n\nimport { TextWrap } from '..'\n\nimport styles from './index.module.less'\n\nexport interface IBaseSelectProps<T>\n  extends Omit<\n    React.HTMLAttributes<HTMLDivElement>,\n    'onChange' | 'placeholder'\n  > {\n  dropdownRender: () => React.ReactElement\n  value?: T\n  valueRender: (value?: T) => React.ReactNode\n  placeholder?: React.ReactNode\n  overlayClassName?: string\n  disabled?: boolean\n  tabIndex?: number\n  autoFocus?: boolean\n  onOpen?: () => void\n  onOpened?: () => void\n  onClose?: () => void\n  onClosed?: () => void\n}\n\nconst builtinPlacements = {\n  bottomLeft: {\n    ignoreShake: true,\n    points: ['tl', 'bl'],\n    offset: [0, 4],\n    overflow: {\n      adjustX: 0,\n      adjustY: 0\n    }\n  }\n}\n\nfunction BaseSelect<T>({\n  dropdownRender,\n  value,\n  valueRender,\n  placeholder,\n  disabled,\n  tabIndex,\n  autoFocus,\n  className,\n  overlayClassName,\n  onFocus,\n  onBlur,\n  onKeyDown,\n  onOpen,\n  onOpened,\n  onClose,\n  onClosed,\n  ...restProps\n}: IBaseSelectProps<T>) {\n  const [dropdownVisible, setDropdownVisible] = useState(false)\n  const toggleDropdownVisible = useCallback(() => {\n    if (disabled) {\n      return\n    }\n    setDropdownVisible((v) => !v)\n  }, [disabled])\n\n  const [isFocused, setFocused] = useState(false)\n\n  const handleDebouncedContainerFocus = useCallback(\n    (ev: React.FocusEvent<HTMLDivElement>) => {\n      setFocused(true)\n      onFocus && onFocus(ev)\n    },\n    [onFocus]\n  )\n\n  const handleDebouncedContainerBlur = useCallback(\n    (ev: React.FocusEvent<HTMLDivElement>) => {\n      setDropdownVisible(false)\n      setFocused(false)\n      onBlur && onBlur(ev)\n    },\n    [onBlur]\n  )\n\n  const debouncedFocusOrBlur = useMemo(() => {\n    return _.debounce(\n      (isFocus: boolean, ev: React.FocusEvent<HTMLDivElement>) => {\n        if (isFocus) {\n          handleDebouncedContainerFocus(ev)\n        } else {\n          handleDebouncedContainerBlur(ev)\n        }\n      },\n      50\n    )\n  }, [handleDebouncedContainerFocus, handleDebouncedContainerBlur])\n\n  const handleContainerFocus = useCallback(\n    (ev) => {\n      debouncedFocusOrBlur(true, ev)\n    },\n    [debouncedFocusOrBlur]\n  )\n\n  const handleContainerBlur = useCallback(\n    (ev) => {\n      debouncedFocusOrBlur(false, ev)\n    },\n    [debouncedFocusOrBlur]\n  )\n\n  const handleContainerKeyDown = useCallback(\n    (ev: React.KeyboardEvent<HTMLDivElement>) => {\n      if (ev.which === KeyCode.ENTER) {\n        toggleDropdownVisible()\n      } else if (ev.which === KeyCode.ESC) {\n        setDropdownVisible(false)\n      }\n      onKeyDown && onKeyDown(ev)\n    },\n    [toggleDropdownVisible, onKeyDown]\n  )\n\n  const handleSelectorMouseDown = useCallback(() => {\n    toggleDropdownVisible()\n  }, [toggleDropdownVisible])\n\n  const handleOverlayMouseDown = useCallback(\n    (ev: React.MouseEvent<HTMLDivElement>) => {\n      // Prevent dropdown container blur event\n      ev.preventDefault()\n    },\n    []\n  )\n\n  const handlePopupVisibleChange = useCallback(\n    (visible: boolean) => {\n      if (visible) {\n        onOpen?.()\n      } else {\n        onClose?.()\n      }\n    },\n    [onOpen, onClose]\n  )\n\n  const handleAfterPopupVisibleChange = useCallback(\n    (visible: boolean) => {\n      if (visible) {\n        onOpened?.()\n      } else {\n        onClosed?.()\n      }\n    },\n    [onOpened, onClosed]\n  )\n\n  const dropdownOverlayRef = useRef<HTMLDivElement>(null)\n  const containerRef = useRef<HTMLDivElement>(null)\n\n  const overlay = useMemo(() => {\n    return (\n      <div\n        ref={dropdownOverlayRef}\n        onMouseDown={handleOverlayMouseDown}\n        className={cx(styles.baseSelectOverlay, overlayClassName)}\n      >\n        {dropdownRender()}\n      </div>\n    )\n  }, [dropdownRender, overlayClassName, handleOverlayMouseDown])\n\n  useEventListener('mousedown', (ev: MouseEvent) => {\n    // Close the dropdown if click outside\n    if (!dropdownVisible) {\n      return\n    }\n    const hitElements = [dropdownOverlayRef.current, containerRef.current]\n    if (\n      hitElements.every(\n        (e) =>\n          !e ||\n          !ev.target ||\n          (!e.contains(ev.target as HTMLElement) && e !== ev.target)\n      )\n    ) {\n      setDropdownVisible(false)\n    }\n  })\n\n  // Close dropdown when disabled change\n  React.useEffect(() => {\n    setDropdownVisible((v) => {\n      if (v && !disabled) {\n        return false\n      }\n      return v\n    })\n  }, [disabled])\n\n  const renderedValue = valueRender(value)\n  const displayAsPlaceholder = renderedValue == null\n\n  return (\n    <div\n      className={cx(styles.baseSelect, className)}\n      onFocus={handleContainerFocus}\n      onBlur={handleContainerBlur}\n      onKeyDown={handleContainerKeyDown}\n      ref={containerRef}\n      {...restProps}\n    >\n      <Trigger\n        prefixCls=\"ant-dropdown\"\n        builtinPlacements={builtinPlacements}\n        showAction={[]}\n        hideAction={[]}\n        popupPlacement=\"bottomLeft\"\n        popupTransitionName=\"ant-slide-up\"\n        popup={overlay}\n        popupVisible={dropdownVisible}\n        onPopupVisibleChange={handlePopupVisibleChange}\n        afterPopupVisibleChange={handleAfterPopupVisibleChange}\n      >\n        <div onMouseDown={handleSelectorMouseDown}>\n          <div\n            className={cx(styles.baseSelectInner, {\n              [styles.focused]: isFocused,\n              [styles.disabled]: disabled\n            })}\n          >\n            <input\n              autoComplete=\"off\"\n              className={styles.baseSelectInput}\n              disabled={disabled}\n              tabIndex={tabIndex}\n              autoFocus={autoFocus}\n              readOnly\n            />\n            <div\n              className={cx(styles.baseSelectValueDisplay, {\n                [styles.isPlaceholder]: displayAsPlaceholder\n              })}\n            >\n              <TextWrap data-e2e=\"base_select_input_text\">\n                {displayAsPlaceholder ? placeholder : renderedValue}\n              </TextWrap>\n            </div>\n          </div>\n          <div className={styles.baseSelectArrow}>\n            <DownOutlined />\n          </div>\n        </div>\n      </Trigger>\n    </div>\n  )\n}\n\nexport default React.memo(BaseSelect)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/BinaryPlanTable/BinaryPlanColsSelector.tsx",
    "content": "import React from 'react'\nimport { Checkbox, Popover, Space } from 'antd'\nimport { DownOutlined } from '@ant-design/icons'\nimport { useTranslation } from 'react-i18next'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport { addTranslationResource } from '@lib/utils/i18n'\n\nconst translations = {\n  en: {\n    trigger_text: 'Columns'\n  },\n  zh: {\n    trigger_text: '选择列'\n  }\n}\n\nfor (const key in translations) {\n  addTranslationResource(key, {\n    component: {\n      binaryPlanColsSelector: translations[key]\n    }\n  })\n}\n\nexport interface IColumnKeys {\n  [key: string]: boolean\n}\n\nexport interface IBinaryPlanColsSelectorProps {\n  columns: IColumn[]\n  visibleColumnKeys: IColumnKeys\n  onChange?: (visibleKeys: IColumnKeys) => void\n}\n\nexport function BinaryPlanColsSelector({\n  columns,\n  visibleColumnKeys,\n  onChange\n}: IBinaryPlanColsSelectorProps) {\n  const { t } = useTranslation()\n\n  function handleCheckChange(e, column: IColumn) {\n    const checked = e.target.checked\n    const newVisibleKeys = {\n      ...visibleColumnKeys,\n      [column.key]: checked\n    }\n    onChange && onChange(newVisibleKeys)\n  }\n\n  const content = (\n    <div>\n      <Space\n        direction=\"vertical\"\n        style={{\n          maxHeight: 400,\n          overflow: 'auto',\n          paddingTop: 8,\n          paddingBottom: 8\n        }}\n        data-e2e=\"columns_selector_popover_content\"\n      >\n        {columns.map((column) => (\n          <Checkbox\n            data-e2e={`binary_plan_columns_selector_field_${column.key}`}\n            key={column.key}\n            checked={visibleColumnKeys[column.key]}\n            onChange={(e) => handleCheckChange(e, column)}\n          >\n            {column['extra']}\n          </Checkbox>\n        ))}\n      </Space>\n    </div>\n  )\n\n  return (\n    <Popover content={content} placement=\"bottomLeft\">\n      <span\n        data-e2e=\"binary_plan_cols_selector_popover\"\n        style={{ cursor: 'pointer' }}\n      >\n        {t('component.binaryPlanColsSelector.trigger_text')} <DownOutlined />\n      </span>\n    </Popover>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/BinaryPlanTable/index.module.less",
    "content": ".colHeader {\n  span {\n    opacity: 0;\n  }\n  &:hover {\n    span {\n      opacity: 1;\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/BinaryPlanTable/index.tsx",
    "content": "import { IColumn } from 'office-ui-fabric-react'\nimport React, { useMemo } from 'react'\nimport CardTable from '../CardTable'\nimport { Tooltip } from 'antd'\nimport { CopyLink, TxtDownloadLink } from '@lib/components'\nimport { BinaryPlanColsSelector } from './BinaryPlanColsSelector'\nimport { EyeOutlined } from '@ant-design/icons'\n\nimport styles from './index.module.less'\n\nconst COLUM_KEYS = [\n  'id',\n  'estRows',\n  'estCost',\n  'actRows',\n  'task',\n  'accessObject',\n  'executionInfo',\n  'operatorInfo',\n  'memory',\n  'disk'\n] as const\ntype COLUM_KEYS_UNION = typeof COLUM_KEYS[number]\ntype BinaryPlanItem = Record<COLUM_KEYS_UNION, string>\ntype BinaryPlanFiledPosition = Record<\n  COLUM_KEYS_UNION,\n  {\n    start: number\n    len: number\n  }\n>\n\ntype BinaryPlanTableProps = {\n  data: string\n  downloadFileName: string\n}\n\n// see binary_plan_text example from sample-data/detail-res.json\nfunction convertBinaryPlanTextToArray(\n  binaryPlanText: string\n): BinaryPlanItem[] {\n  const result: BinaryPlanItem[] = []\n  let positions: BinaryPlanFiledPosition | null = null\n\n  // we can't simply split by '\\n', because operator info column may contain '\\n'\n  // for example, execution plan for \"select `tidb_decode_binary_plan` ( ? ) as `binary_plan_text`;\"\n  // const lines = binaryPlanText.split('\\n')\n\n  const headerEndPos = binaryPlanText.indexOf('\\n', 1)\n  const headerLine = binaryPlanText.slice(1, headerEndPos)\n  if (!headerLine.startsWith('| id')) {\n    console.error('invalid binary plan text format')\n    return result\n  }\n  const headerLineLen = headerLine.length\n\n  const headers = headerLine.split('|')\n  // 0: \"\"\n  // 1: \" id                      \"\n  // 2: \" estRows  \"\n  // 3: \" estCost    \"\n  // 4: \" actRows \"\n  // 5: \" task \"\n  // 6: \" access object      \"\n  // 7: \" execution info     \"\n  // 8: \" operator info                                   \"\n  // 9: \" memory   \"\n  // 10: \" disk     \"\n  // 11: \"\"\n  if (headers.length !== 12) {\n    console.error('invalid binary plan text format')\n    return result\n  }\n  positions = {\n    id: {\n      start: 0,\n      len: headers[1].length\n    },\n    estRows: {\n      start: 0,\n      len: headers[2].length\n    },\n    estCost: {\n      start: 0,\n      len: headers[3].length\n    },\n    actRows: {\n      start: 0,\n      len: headers[4].length\n    },\n    task: {\n      start: 0,\n      len: headers[5].length\n    },\n    accessObject: {\n      start: 0,\n      len: headers[6].length\n    },\n    executionInfo: {\n      start: 0,\n      len: headers[7].length\n    },\n    operatorInfo: {\n      start: 0,\n      len: headers[8].length\n    },\n    memory: {\n      start: 0,\n      len: headers[9].length\n    },\n    disk: {\n      start: 0,\n      len: headers[10].length\n    }\n  }\n  positions.id.start = 1\n  positions.estRows.start = positions.id.start + positions.id.len + 1\n  positions.estCost.start = positions.estRows.start + positions.estRows.len + 1\n  positions.actRows.start = positions.estCost.start + positions.estCost.len + 1\n  positions.task.start = positions.actRows.start + positions.actRows.len + 1\n  positions.accessObject.start = positions.task.start + positions.task.len + 1\n  positions.executionInfo.start =\n    positions.accessObject.start + positions.accessObject.len + 1\n  positions.operatorInfo.start =\n    positions.executionInfo.start + positions.executionInfo.len + 1\n  positions.memory.start =\n    positions.operatorInfo.start + positions.operatorInfo.len + 1\n  positions.disk.start = positions.memory.start + positions.memory.len + 1\n\n  let lineIdx = 1\n  while (true) {\n    const lineStart = 1 + (headerLineLen + 1) * lineIdx\n    const lineEnd = 1 + (headerLineLen + 1) * (lineIdx + 1)\n    if (lineEnd > binaryPlanText.length) {\n      break\n    }\n    lineIdx++\n\n    const line = binaryPlanText.slice(lineStart, lineEnd)\n    const item: BinaryPlanItem = {\n      id: line\n        .slice(positions.id.start + 1, positions.id.start + positions.id.len)\n        .trimEnd(), // start+1 for removing the leading white space\n      estRows: line\n        .slice(\n          positions.estRows.start,\n          positions.estRows.start + positions.estRows.len\n        )\n        .trim(),\n      estCost: line\n        .slice(\n          positions.estCost.start,\n          positions.estCost.start + positions.estCost.len\n        )\n        .trim(),\n      actRows: line\n        .slice(\n          positions.actRows.start,\n          positions.actRows.start + positions.actRows.len\n        )\n        .trim(),\n      task: line\n        .slice(positions.task.start, positions.task.start + positions.task.len)\n        .trim(),\n      accessObject: line\n        .slice(\n          positions.accessObject.start,\n          positions.accessObject.start + positions.accessObject.len\n        )\n        .trim(),\n      executionInfo: line\n        .slice(\n          positions.executionInfo.start,\n          positions.executionInfo.start + positions.executionInfo.len\n        )\n        .trim(),\n      operatorInfo: line\n        .slice(\n          positions.operatorInfo.start,\n          positions.operatorInfo.start + positions.operatorInfo.len\n        )\n        .trim(),\n      memory: line\n        .slice(\n          positions.memory.start,\n          positions.memory.start + positions.memory.len\n        )\n        .trim(),\n      disk: line\n        .slice(positions.disk.start, positions.disk.start + positions.disk.len)\n        .trim()\n    }\n    result.push(item)\n  }\n\n  return result\n}\n\nexport const BinaryPlanTable: React.FC<BinaryPlanTableProps> = ({\n  data,\n  downloadFileName\n}) => {\n  const arr = useMemo(() => convertBinaryPlanTextToArray(data), [data])\n\n  function hideColumn(columnKey: COLUM_KEYS_UNION) {\n    setVisibleColumnKeys((prev) => {\n      return {\n        ...prev,\n        [columnKey]: false\n      }\n    })\n  }\n\n  const columns: IColumn[] = useMemo(() => {\n    return [\n      {\n        name: (\n          <div className={styles.colHeader}>\n            id <EyeOutlined onClick={() => hideColumn('id')} />\n          </div>\n        ) as any,\n        extra: 'id',\n        key: 'id',\n        minWidth: 200,\n        maxWidth: 600,\n        onRender: (row: BinaryPlanItem) => {\n          return (\n            <Tooltip title={row.id}>\n              <span style={{ whiteSpace: 'pre', fontFamily: 'monospace' }}>\n                {row.id}\n              </span>\n            </Tooltip>\n          )\n        }\n      },\n      {\n        name: (\n          <div className={styles.colHeader}>\n            estRows <EyeOutlined onClick={() => hideColumn('estRows')} />\n          </div>\n        ) as any,\n        extra: 'estRows',\n        key: 'estRows',\n        minWidth: 100,\n        maxWidth: 120,\n        onRender: (row: BinaryPlanItem) => {\n          return row.estRows\n        }\n      },\n      {\n        name: (\n          <div className={styles.colHeader}>\n            estCost <EyeOutlined onClick={() => hideColumn('estCost')} />\n          </div>\n        ) as any,\n        extra: 'estCost',\n        key: 'estCost',\n        minWidth: 100,\n        maxWidth: 120,\n        onRender: (row: BinaryPlanItem) => {\n          return row.estCost\n        }\n      },\n      {\n        name: (\n          <div className={styles.colHeader}>\n            actRows <EyeOutlined onClick={() => hideColumn('actRows')} />\n          </div>\n        ) as any,\n        extra: 'actRows',\n        key: 'actRows',\n        minWidth: 100,\n        maxWidth: 120,\n        onRender: (row: BinaryPlanItem) => {\n          return row.actRows\n        }\n      },\n      {\n        name: (\n          <div className={styles.colHeader}>\n            task <EyeOutlined onClick={() => hideColumn('task')} />\n          </div>\n        ) as any,\n        extra: 'task',\n        key: 'task',\n        minWidth: 60,\n        maxWidth: 100,\n        onRender: (row: BinaryPlanItem) => {\n          return row.task\n        }\n      },\n      {\n        name: (\n          <div className={styles.colHeader}>\n            access object{' '}\n            <EyeOutlined onClick={() => hideColumn('accessObject')} />\n          </div>\n        ) as any,\n        extra: 'access object',\n        key: 'accessObject',\n        minWidth: 120,\n        maxWidth: 140,\n        onRender: (row: BinaryPlanItem) => {\n          return <Tooltip title={row.accessObject}>{row.accessObject}</Tooltip>\n        }\n      },\n      {\n        name: (\n          <div className={styles.colHeader}>\n            execution info{' '}\n            <EyeOutlined onClick={() => hideColumn('executionInfo')} />\n          </div>\n        ) as any,\n        extra: 'execution info',\n        key: 'executionInfo',\n        minWidth: 120,\n        maxWidth: 300,\n        onRender: (row: BinaryPlanItem) => {\n          return (\n            <Tooltip title={row.executionInfo}>{row.executionInfo}</Tooltip>\n          )\n        }\n      },\n      {\n        name: (\n          <div className={styles.colHeader}>\n            operator info{' '}\n            <EyeOutlined onClick={() => hideColumn('operatorInfo')} />\n          </div>\n        ) as any,\n        extra: 'operator info',\n        key: 'operatorInfo',\n        minWidth: 120,\n        maxWidth: 300,\n        onRender: (row: BinaryPlanItem) => {\n          // truncate the string if it's too long\n          // operation info may be super super long\n          const truncateLength = 100\n          let truncatedStr = row.operatorInfo ?? ''\n          if (truncatedStr.length > truncateLength) {\n            truncatedStr = row.operatorInfo.slice(0, truncateLength) + '...'\n          }\n          const truncateTooltipLen = 2000\n          let truncatedTooltipStr = row.operatorInfo ?? ''\n          if (truncatedTooltipStr.length > truncateTooltipLen) {\n            truncatedTooltipStr =\n              row.operatorInfo.slice(0, truncateTooltipLen) +\n              '...(too long to show, copy or download to analyze)'\n          }\n          return <Tooltip title={truncatedTooltipStr}>{truncatedStr}</Tooltip>\n        }\n      },\n      {\n        name: (\n          <div className={styles.colHeader}>\n            memory <EyeOutlined onClick={() => hideColumn('memory')} />\n          </div>\n        ) as any,\n        extra: 'memory',\n        key: 'memory',\n        minWidth: 80,\n        maxWidth: 100,\n        onRender: (row: BinaryPlanItem) => {\n          return row.memory\n        }\n      },\n      {\n        name: (\n          <div className={styles.colHeader}>\n            disk <EyeOutlined onClick={() => hideColumn('disk')} />\n          </div>\n        ) as any,\n        extra: 'disk',\n        key: 'disk',\n        minWidth: 60,\n        maxWidth: 100,\n        onRender: (row: BinaryPlanItem) => {\n          return row.disk\n        }\n      }\n    ]\n  }, [])\n\n  const [visibleColumnKeys, setVisibleColumnKeys] = React.useState(() => {\n    return COLUM_KEYS.reduce((acc, cur) => {\n      acc[cur] = true\n      return acc\n    }, {})\n  })\n\n  const filteredColumns = useMemo(() => {\n    return columns.filter((c) => visibleColumnKeys[c.key as COLUM_KEYS_UNION])\n  }, [columns, visibleColumnKeys])\n\n  if (arr.length > 0) {\n    return (\n      <>\n        <div style={{ display: 'flex', gap: 16 }}>\n          <CopyLink data={data} />\n          <TxtDownloadLink data={data} fileName={downloadFileName} />\n          <div style={{ marginLeft: 'auto' }}>\n            <BinaryPlanColsSelector\n              columns={columns}\n              visibleColumnKeys={visibleColumnKeys}\n              onChange={setVisibleColumnKeys}\n            />\n          </div>\n        </div>\n        <CardTable cardNoMargin columns={filteredColumns} items={arr} />\n      </>\n    )\n  }\n  return (\n    <div>\n      Parse plan text failed, original content:\n      <div>{data}</div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/BinaryPlanTable/sample-data/binary-plan.json",
    "content": "{\n  \"discardedDueToTooLong\": false,\n  \"main\": {\n    \"accessObjects\": [],\n    \"actRows\": 12,\n    \"children\": [\n      {\n        \"accessObjects\": [],\n        \"actRows\": 12,\n        \"children\": [\n          {\n            \"accessObjects\": [],\n            \"actRows\": 12,\n            \"children\": [\n              {\n                \"accessObjects\": [],\n                \"actRows\": 69,\n                \"children\": [\n                  {\n                    \"accessObjects\": [\n                      {\n                        \"scanObject\": {\n                          \"database\": \"INFORMATION_SCHEMA\",\n                          \"table\": \"CLUSTER_LOAD\"\n                        }\n                      }\n                    ],\n                    \"actRows\": 1193,\n                    \"copExecInfo\": {},\n                    \"diagnosis\": [],\n                    \"diskBytes\": \"N/A\",\n                    \"duration\": \"1.51s\",\n                    \"estRows\": 10000,\n                    \"labels\": [],\n                    \"memoryBytes\": \"N/A\",\n                    \"name\": \"MemTableScan_12\",\n                    \"rootBasicExecInfo\": {\n                      \"loops\": \"3\",\n                      \"time\": \"1.51s\"\n                    },\n                    \"rootGroupExecInfo\": [],\n                    \"storeType\": \"tidb\",\n                    \"taskType\": \"root\",\n                    \"__node_attrs\": {\n                      \"id\": \"5\",\n                      \"collapsed\": false,\n                      \"collapsiable\": false,\n                      \"isNodeDetailVisible\": false,\n                      \"nodeFlexSize\": {\n                        \"width\": 280,\n                        \"height\": 230\n                      }\n                    }\n                  }\n                ],\n                \"copExecInfo\": {},\n                \"cost\": 499000,\n                \"diagnosis\": [\"high_est_error\"],\n                \"diskBytes\": \"N/A\",\n                \"duration\": \"1.51s\",\n                \"estRows\": 8000,\n                \"labels\": [],\n                \"memoryBytes\": \"138400\",\n                \"name\": \"Selection_11\",\n                \"operatorInfo\": \"in(Column#3, \\\"memory\\\", \\\"cpu\\\")\",\n                \"rootBasicExecInfo\": {\n                  \"loops\": \"2\",\n                  \"time\": \"1.51s\"\n                },\n                \"rootGroupExecInfo\": [],\n                \"storeType\": \"tidb\",\n                \"taskType\": \"root\",\n                \"__node_attrs\": {\n                  \"id\": \"4\",\n                  \"collapsed\": false,\n                  \"collapsiable\": true,\n                  \"isNodeDetailVisible\": false,\n                  \"nodeFlexSize\": {\n                    \"width\": 280,\n                    \"height\": 200\n                  }\n                }\n              }\n            ],\n            \"copExecInfo\": {},\n            \"cost\": 1772970.6,\n            \"diagnosis\": [],\n            \"diskBytes\": \"N/A\",\n            \"duration\": \"1.51s\",\n            \"estRows\": 8000,\n            \"labels\": [],\n            \"memoryBytes\": \"48580\",\n            \"name\": \"HashAgg_10\",\n            \"operatorInfo\": \"group by:Column#1, Column#2, Column#3, Column#4, funcs:json_objectagg(Column#5, Column#6)->Column#7, funcs:firstrow(Column#1)->Column#1, funcs:firstrow(Column#2)->Column#2, funcs:firstrow(Column#3)->Column#3, funcs:firstrow(Column#4)->Column#4\",\n            \"rootBasicExecInfo\": {\n              \"loops\": \"6\",\n              \"time\": \"1.51s\"\n            },\n            \"rootGroupExecInfo\": [\n              {\n                \"final_worker\": {\n                  \"concurrency\": \"5\",\n                  \"max\": \"1.513316486s\",\n                  \"p95\": \"1.513316486s\",\n                  \"task_num\": \"5\",\n                  \"tot_exec\": \"223.043µs\",\n                  \"tot_time\": \"7.566371614s\",\n                  \"tot_wait\": \"7.566142195s\",\n                  \"wall_time\": \"1.5133523s\"\n                },\n                \"partial_worker\": {\n                  \"concurrency\": \"5\",\n                  \"max\": \"1.513153823s\",\n                  \"p95\": \"1.513153823s\",\n                  \"task_num\": \"1\",\n                  \"tot_exec\": \"149.691µs\",\n                  \"tot_time\": \"7.56536502s\",\n                  \"tot_wait\": \"7.565194284s\",\n                  \"wall_time\": \"1.513240352s\"\n                }\n              }\n            ],\n            \"storeType\": \"tidb\",\n            \"taskType\": \"root\",\n            \"__node_attrs\": {\n              \"id\": \"3\",\n              \"collapsed\": false,\n              \"collapsiable\": true,\n              \"isNodeDetailVisible\": false,\n              \"nodeFlexSize\": {\n                \"width\": 280,\n                \"height\": 200\n              }\n            }\n          }\n        ],\n        \"copExecInfo\": {},\n        \"cost\": 1856802.6,\n        \"diagnosis\": [],\n        \"diskBytes\": \"N/A\",\n        \"duration\": \"1.51s\",\n        \"estRows\": 8000,\n        \"labels\": [],\n        \"memoryBytes\": \"34596\",\n        \"name\": \"Projection_9\",\n        \"operatorInfo\": \"Column#1, Column#2, Column#3, Column#4, Column#7, field(lower(Column#1), tiflash, tikv, pd, tidb)->Column#8\",\n        \"rootBasicExecInfo\": {\n          \"loops\": \"6\",\n          \"time\": \"1.51s\"\n        },\n        \"rootGroupExecInfo\": [\n          {\n            \"Concurrency\": \"5\"\n          }\n        ],\n        \"storeType\": \"tidb\",\n        \"taskType\": \"root\",\n        \"__node_attrs\": {\n          \"id\": \"2\",\n          \"collapsed\": false,\n          \"collapsiable\": true,\n          \"isNodeDetailVisible\": false,\n          \"nodeFlexSize\": {\n            \"width\": 280,\n            \"height\": 200\n          }\n        }\n      }\n    ],\n    \"copExecInfo\": {},\n    \"cost\": 7403943.686437106,\n    \"diagnosis\": [],\n    \"diskBytes\": \"N/A\",\n    \"duration\": \"1.51s\",\n    \"estRows\": 8000,\n    \"labels\": [],\n    \"memoryBytes\": \"18792\",\n    \"name\": \"Sort_7\",\n    \"operatorInfo\": \"Column#8:desc, Column#2, Column#3, Column#4\",\n    \"rootBasicExecInfo\": {\n      \"loops\": \"2\",\n      \"time\": \"1.51s\"\n    },\n    \"rootGroupExecInfo\": [],\n    \"storeType\": \"tidb\",\n    \"taskType\": \"root\"\n  },\n  \"withRuntimeStats\": true\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/BinaryPlanTable/sample-data/detail-res.json",
    "content": "{\n  \"digest\": \"877ddf60c6084ae30353cc484f375e5159c231f0d9363213d1d1cc2ffbd272ce\",\n  \"query\": \"SELECT  *,  FIELD(LOWER(A.TYPE), 'tiflash', 'tikv', 'pd', 'tidb') AS _ORDER FROM (  SELECT   TYPE, INSTANCE, DEVICE_TYPE, DEVICE_NAME, JSON_OBJECTAGG(NAME, VALUE) AS JSON_VALUE  FROM   INFORMATION_SCHEMA.CLUSTER_LOAD  WHERE   DEVICE_TYPE IN (?,?)  GROUP BY TYPE, INSTANCE, DEVICE_TYPE, DEVICE_NAME ) AS A ORDER BY  _ORDER DESC, INSTANCE, DEVICE_TYPE, DEVICE_NAME [arguments: (\\\"memory\\\", \\\"cpu\\\")];\",\n  \"instance\": \"127.0.0.1:10080\",\n  \"db\": \"\",\n  \"connection_id\": \"5414958427354957035\",\n  \"success\": 1,\n  \"timestamp\": 1690272708.823209,\n  \"query_time\": 1.51515176,\n  \"parse_time\": 0,\n  \"compile_time\": 0.00103503,\n  \"rewrite_time\": 0.000332248,\n  \"preproc_subqueries_time\": 0,\n  \"optimize_time\": 0.000354698,\n  \"wait_ts\": 0,\n  \"cop_time\": 0,\n  \"lock_keys_time\": 0,\n  \"write_sql_response_total\": 0.000068678,\n  \"exec_retry_time\": 0,\n  \"memory_max\": 220680,\n  \"disk_max\": 0,\n  \"txn_start_ts\": \"0\",\n  \"prev_stmt\": \"\",\n  \"plan\": \"\\tid                     \\ttask\\testRows\\toperator info                                                                                                                                                                                                                                      \\tactRows\\texecution info                                                                                                                                                                                                                                                                                                                                                      \\tmemory  \\tdisk\\n\\tSort_7                 \\troot\\t8000   \\tColumn#8:desc, Column#2, Column#3, Column#4                                                                                                                                                                                                        \\t12     \\ttime:1.51s, loops:2                                                                                                                                                                                                                                                                                                                                                 \\t18.4 KB \\t0 Bytes\\n\\t└─Projection_9         \\troot\\t8000   \\tColumn#1, Column#2, Column#3, Column#4, Column#7, field(lower(Column#1), tiflash, tikv, pd, tidb)-\\u003eColumn#8                                                                                                                                        \\t12     \\ttime:1.51s, loops:6, Concurrency:5                                                                                                                                                                                                                                                                                                                                  \\t33.8 KB \\tN/A\\n\\t  └─HashAgg_10         \\troot\\t8000   \\tgroup by:Column#1, Column#2, Column#3, Column#4, funcs:json_objectagg(Column#5, Column#6)-\\u003eColumn#7, funcs:firstrow(Column#1)-\\u003eColumn#1, funcs:firstrow(Column#2)-\\u003eColumn#2, funcs:firstrow(Column#3)-\\u003eColumn#3, funcs:firstrow(Column#4)-\\u003eColumn#4\\t12     \\ttime:1.51s, loops:6, partial_worker:{wall_time:1.513240352s, concurrency:5, task_num:1, tot_wait:7.565194284s, tot_exec:149.691µs, tot_time:7.56536502s, max:1.513153823s, p95:1.513153823s}, final_worker:{wall_time:1.5133523s, concurrency:5, task_num:5, tot_wait:7.566142195s, tot_exec:223.043µs, tot_time:7.566371614s, max:1.513316486s, p95:1.513316486s}\\t47.4 KB \\tN/A\\n\\t    └─Selection_11     \\troot\\t8000   \\tin(Column#3, \\\"memory\\\", \\\"cpu\\\")                                                                                                                                                                                                                      \\t69     \\ttime:1.51s, loops:2                                                                                                                                                                                                                                                                                                                                                 \\t135.2 KB\\tN/A\\n\\t      └─MemTableScan_12\\troot\\t10000  \\ttable:CLUSTER_LOAD                                                                                                                                                                                                                                 \\t1193   \\ttime:1.51s, loops:3                                                                                                                                                                                                                                                                                                                                                 \\tN/A     \\tN/A\",\n  \"binary_plan\": \"{\\\"discardedDueToTooLong\\\":false,\\\"main\\\":{\\\"accessObjects\\\":[],\\\"actRows\\\":12,\\\"children\\\":[{\\\"accessObjects\\\":[],\\\"actRows\\\":12,\\\"children\\\":[{\\\"accessObjects\\\":[],\\\"actRows\\\":12,\\\"children\\\":[{\\\"accessObjects\\\":[],\\\"actRows\\\":69,\\\"children\\\":[{\\\"accessObjects\\\":[{\\\"scanObject\\\":{\\\"database\\\":\\\"INFORMATION_SCHEMA\\\",\\\"table\\\":\\\"CLUSTER_LOAD\\\"}}],\\\"actRows\\\":1193,\\\"copExecInfo\\\":{},\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":10000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"N/A\\\",\\\"name\\\":\\\"MemTableScan_12\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"3\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":499000,\\\"diagnosis\\\":[\\\"high_est_error\\\"],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":8000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"138400\\\",\\\"name\\\":\\\"Selection_11\\\",\\\"operatorInfo\\\":\\\"in(Column#3, \\\\\\\"memory\\\\\\\", \\\\\\\"cpu\\\\\\\")\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"2\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":1772970.6,\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":8000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"48580\\\",\\\"name\\\":\\\"HashAgg_10\\\",\\\"operatorInfo\\\":\\\"group by:Column#1, Column#2, Column#3, Column#4, funcs:json_objectagg(Column#5, Column#6)-\\\\u003eColumn#7, funcs:firstrow(Column#1)-\\\\u003eColumn#1, funcs:firstrow(Column#2)-\\\\u003eColumn#2, funcs:firstrow(Column#3)-\\\\u003eColumn#3, funcs:firstrow(Column#4)-\\\\u003eColumn#4\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"6\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[{\\\"final_worker\\\":{\\\"concurrency\\\":\\\"5\\\",\\\"max\\\":\\\"1.513316486s\\\",\\\"p95\\\":\\\"1.513316486s\\\",\\\"task_num\\\":\\\"5\\\",\\\"tot_exec\\\":\\\"223.043µs\\\",\\\"tot_time\\\":\\\"7.566371614s\\\",\\\"tot_wait\\\":\\\"7.566142195s\\\",\\\"wall_time\\\":\\\"1.5133523s\\\"},\\\"partial_worker\\\":{\\\"concurrency\\\":\\\"5\\\",\\\"max\\\":\\\"1.513153823s\\\",\\\"p95\\\":\\\"1.513153823s\\\",\\\"task_num\\\":\\\"1\\\",\\\"tot_exec\\\":\\\"149.691µs\\\",\\\"tot_time\\\":\\\"7.56536502s\\\",\\\"tot_wait\\\":\\\"7.565194284s\\\",\\\"wall_time\\\":\\\"1.513240352s\\\"}}],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":1856802.6,\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":8000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"34596\\\",\\\"name\\\":\\\"Projection_9\\\",\\\"operatorInfo\\\":\\\"Column#1, Column#2, Column#3, Column#4, Column#7, field(lower(Column#1), tiflash, tikv, pd, tidb)-\\\\u003eColumn#8\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"6\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[{\\\"Concurrency\\\":\\\"5\\\"}],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":7403943.686437106,\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":8000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"18792\\\",\\\"name\\\":\\\"Sort_7\\\",\\\"operatorInfo\\\":\\\"Column#8:desc, Column#2, Column#3, Column#4\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"2\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"},\\\"withRuntimeStats\\\":true}\",\n  \"binary_plan_text\": \"\\n| id                      | estRows  | estCost    | actRows | task | access object      | execution info                                                                                                                                                                                                                                                                                                                                                     | operator info                                                                                                                                                                                                                                       | memory   | disk     |\\n| Sort_7                  | 8000.00  | 7403943.69 | 12      | root |                    | time:1.51s, loops:2                                                                                                                                                                                                                                                                                                                                                | Column#8:desc, Column#2, Column#3, Column#4                                                                                                                                                                                                         | 18.4 KB  | 0 Bytes  |\\n| └─Projection_9          | 8000.00  | 1856802.60 | 12      | root |                    | time:1.51s, loops:6, Concurrency:5                                                                                                                                                                                                                                                                                                                                 | Column#1, Column#2, Column#3, Column#4, Column#7, field(lower(Column#1), tiflash, tikv, pd, tidb)-\\u003eColumn#8                                                                                                                                         | 33.8 KB  | N/A      |\\n|   └─HashAgg_10          | 8000.00  | 1772970.60 | 12      | root |                    | time:1.51s, loops:6, partial_worker:{wall_time:1.513240352s, concurrency:5, task_num:1, tot_wait:7.565194284s, tot_exec:149.691µs, tot_time:7.56536502s, max:1.513153823s, p95:1.513153823s}, final_worker:{wall_time:1.5133523s, concurrency:5, task_num:5, tot_wait:7.566142195s, tot_exec:223.043µs, tot_time:7.566371614s, max:1.513316486s, p95:1.513316486s} | group by:Column#1, Column#2, Column#3, Column#4, funcs:json_objectagg(Column#5, Column#6)-\\u003eColumn#7, funcs:firstrow(Column#1)-\\u003eColumn#1, funcs:firstrow(Column#2)-\\u003eColumn#2, funcs:firstrow(Column#3)-\\u003eColumn#3, funcs:firstrow(Column#4)-\\u003eColumn#4 | 47.4 KB  | N/A      |\\n|     └─Selection_11      | 8000.00  | 499000.00  | 69      | root |                    | time:1.51s, loops:2                                                                                                                                                                                                                                                                                                                                                | in(Column#3, \\\"memory\\\", \\\"cpu\\\")                                                                                                                                                                                                                       | 135.2 KB | N/A      |\\n|       └─MemTableScan_12 | 10000.00 | 0.00       | 1193    | root | table:CLUSTER_LOAD | time:1.51s, loops:3                                                                                                                                                                                                                                                                                                                                                |                                                                                                                                                                                                                                                     | N/A      | N/A      |\\n\",\n  \"warnings\": [\n    {\n      \"Level\": \"Warning\",\n      \"Message\": \"skip prepared plan-cache: PhysicalMemTable plan is un-cacheable\"\n    }\n  ],\n  \"is_internal\": 0,\n  \"index_names\": \"\",\n  \"stats\": \"\",\n  \"backoff_types\": \"\",\n  \"prepared\": 1,\n  \"plan_from_cache\": 0,\n  \"plan_from_binding\": 0,\n  \"user\": \"root\",\n  \"host\": \"127.0.0.1\",\n  \"process_time\": 0,\n  \"wait_time\": 0,\n  \"backoff_time\": 0,\n  \"get_commit_ts_time\": 0,\n  \"local_latch_wait_time\": 0,\n  \"resolve_lock_time\": 0,\n  \"prewrite_time\": 0,\n  \"wait_prewrite_binlog_time\": 0,\n  \"commit_time\": 0,\n  \"commit_backoff_time\": 0,\n  \"cop_proc_avg\": 0,\n  \"cop_proc_p90\": 0,\n  \"cop_proc_max\": 0,\n  \"cop_wait_avg\": 0,\n  \"cop_wait_p90\": 0,\n  \"cop_wait_max\": 0,\n  \"write_keys\": 0,\n  \"write_size\": 0,\n  \"prewrite_region\": 0,\n  \"txn_retry\": 0,\n  \"request_count\": 0,\n  \"process_keys\": 0,\n  \"total_keys\": 0,\n  \"cop_proc_addr\": \"\",\n  \"cop_wait_addr\": \"\",\n  \"rocksdb_delete_skipped_count\": 0,\n  \"rocksdb_key_skipped_count\": 0,\n  \"rocksdb_block_cache_hit_count\": 0,\n  \"rocksdb_block_read_count\": 0,\n  \"rocksdb_block_read_byte\": 0\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Blink/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.blinkActive {\n  animation: blink 0.7s 2 ease-in-out;\n}\n\n@keyframes blink {\n  0% {\n    background-color: transparent;\n  }\n  50% {\n    background-color: rgba(@gold-5, 0.4);\n  }\n  100% {\n    background-color: transparent;\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Blink/index.tsx",
    "content": "import useQueryParams from '@lib/utils/useQueryParams'\nimport React from 'react'\nimport cx from 'classnames'\n\nimport styles from './index.module.less'\n\nexport interface IBlinkProps extends React.HTMLAttributes<HTMLDivElement> {\n  activeId: string\n}\n\nexport default function Blink({\n  activeId,\n  children,\n  className,\n  ...restProps\n}: IBlinkProps) {\n  const { blink } = useQueryParams()\n\n  return (\n    <div\n      className={cx(className, {\n        [styles.blinkActive]: blink === activeId\n      })}\n      {...restProps}\n    >\n      {children}\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Card/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.cardContainer {\n  // &:before,\n  // &:after {\n  //   // Handle margin collapse\n  //   content: ' ';\n  //   display: table;\n  // }\n}\n\n.cardInner {\n  margin: @padding-page;\n\n  &.noMargin {\n    margin: 0;\n  }\n\n  &.noMarginTop {\n    margin-top: 0;\n  }\n\n  &.noMarginBottom {\n    margin-bottom: 0;\n  }\n\n  &.noMarginLeft {\n    margin-left: 0;\n  }\n\n  &.noMarginRight {\n    margin-right: 0;\n  }\n}\n\n.cardTitleSection {\n  margin: @padding-lg 0;\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n}\n\n.cardTitle {\n  color: @heading-color;\n  font-size: @heading-4-size;\n  line-height: 32px;\n  margin-right: @padding-md;\n}\n\n.cardSubTitle {\n  margin-left: @padding-md;\n  margin-right: @padding-md;\n}\n\n.cardTitleSpacer {\n  flex-grow: 1;\n}\n\n.hasTitle > .cardContent {\n  margin-top: @padding-lg;\n}\n\n.cardContainer.flexGrow {\n  display: flex;\n  flex-grow: 1;\n  flex-direction: column;\n\n  .cardInner,\n  .cardContent {\n    display: flex;\n    flex-grow: 1;\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Card/index.tsx",
    "content": "import React, { ReactNode } from 'react'\nimport cx from 'classnames'\nimport styles from './index.module.less'\n\nexport interface ICardProps\n  extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {\n  title?: ReactNode\n  subTitle?: ReactNode\n  extra?: ReactNode\n  noMargin?: boolean\n  noMarginTop?: boolean\n  noMarginBottom?: boolean\n  noMarginLeft?: boolean\n  noMarginRight?: boolean\n  flexGrow?: boolean\n}\n\nexport default function Card({\n  title,\n  subTitle,\n  extra,\n  className,\n  noMargin,\n  noMarginTop,\n  noMarginBottom,\n  noMarginLeft,\n  noMarginRight,\n  flexGrow,\n  children,\n  ...rest\n}: ICardProps) {\n  return (\n    <div\n      className={cx(styles.cardContainer, className, {\n        [styles.flexGrow]: flexGrow\n      })}\n      {...rest}\n    >\n      <div\n        className={cx(styles.cardInner, {\n          [styles.noMargin]: noMargin,\n          [styles.noMarginTop]: noMarginTop,\n          [styles.noMarginBottom]: noMarginBottom,\n          [styles.noMarginLeft]: noMarginLeft,\n          [styles.noMarginRight]: noMarginRight,\n          [styles.hasTitle]: title || subTitle || extra\n        })}\n      >\n        {(title || subTitle || extra) && (\n          <div className={styles.cardTitleSection}>\n            {title && <div className={styles.cardTitle}>{title}</div>}\n            {subTitle && <div className={styles.cardSubTitle}>{subTitle}</div>}\n            <div className={styles.cardTitleSpacer} />\n            {extra && <div className={styles.cardTitleExtra}>{extra}</div>}\n          </div>\n        )}\n        {children && <div className={styles.cardContent}>{children}</div>}\n      </div>\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/CardTable/GroupHeader.tsx",
    "content": "// FIXME: This is mostly a clone from https://github.com/microsoft/fluentui/blob/master/packages/office-ui-fabric-react/src/components/GroupedList/GroupHeader.base.tsx, but replaced with Ant'd Checkbox\n// Drop it after https://github.com/microsoft/fluentui/issues/13144 is resolved\n\nimport React from 'react'\nimport {\n  classNamesFunction,\n  styled\n} from 'office-ui-fabric-react/lib/Utilities'\nimport {\n  IGroupHeaderStyleProps,\n  IGroupHeaderStyles,\n  IGroupHeaderProps,\n  GroupSpacer\n} from 'office-ui-fabric-react/lib/GroupedList'\nimport {\n  FocusZone,\n  FocusZoneDirection\n} from 'office-ui-fabric-react/lib/FocusZone'\nimport { getStyles } from 'office-ui-fabric-react/lib/components/GroupedList/GroupHeader.styles'\n\nimport { Icon } from 'office-ui-fabric-react/lib/Icon'\nimport { Checkbox } from 'antd'\nimport { useMemoizedFn } from 'ahooks'\n\nconst getClassNames = classNamesFunction<\n  IGroupHeaderStyleProps,\n  IGroupHeaderStyles\n>()\n\nfunction BaseAntCheckboxGroupHeader(props: IGroupHeaderProps) {\n  const _classNames = getClassNames(props.styles, {\n    theme: props.theme!,\n    className: props.className,\n    selected: props.selected,\n    isCollapsed: props.group?.isCollapsed,\n    compact: props.compact\n  })\n\n  const _onHeaderClick = useMemoizedFn(() => {\n    if (props.onToggleSelectGroup) {\n      props.onToggleSelectGroup(props.group!)\n    }\n  })\n\n  const _onToggleSelectGroupClick = useMemoizedFn(\n    (ev: React.MouseEvent<HTMLElement>) => {\n      if (props.onToggleSelectGroup) {\n        props.onToggleSelectGroup(props.group!)\n      }\n      ev.preventDefault()\n      ev.stopPropagation()\n    }\n  )\n\n  const _onToggleCollapse = useMemoizedFn(\n    (ev: React.MouseEvent<HTMLElement>) => {\n      if (props.onToggleCollapse) {\n        props.onToggleCollapse(props.group!)\n      }\n      ev.stopPropagation()\n      ev.preventDefault()\n    }\n  )\n\n  return (\n    <div\n      className={_classNames.root}\n      style={props.viewport ? { minWidth: props.viewport.width } : {}}\n      onClick={_onHeaderClick}\n    >\n      <FocusZone\n        className={_classNames.groupHeaderContainer}\n        direction={FocusZoneDirection.horizontal}\n      >\n        <button\n          type=\"button\"\n          className={_classNames.check}\n          onClick={_onToggleSelectGroupClick}\n          {...props.selectAllButtonProps}\n        >\n          <Checkbox checked={props.selected} />\n        </button>\n\n        <GroupSpacer\n          indentWidth={props.indentWidth}\n          count={props.groupLevel!}\n        />\n        <button\n          type=\"button\"\n          className={_classNames.expand}\n          onClick={_onToggleCollapse}\n          {...props.expandButtonProps}\n        >\n          <Icon\n            className={_classNames.expandIsCollapsed}\n            iconName={'ChevronRightMed'}\n          />\n        </button>\n        <div className={_classNames.title}>\n          <span>{props.group?.name}</span>\n        </div>\n      </FocusZone>\n    </div>\n  )\n}\n\nexport const AntCheckboxGroupHeader: React.FunctionComponent<IGroupHeaderProps> =\n  styled<IGroupHeaderProps, IGroupHeaderStyleProps, IGroupHeaderStyles>(\n    BaseAntCheckboxGroupHeader,\n    getStyles,\n    undefined,\n    {\n      scope: 'GroupHeader'\n    }\n  )\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/CardTable/index.module.less",
    "content": ".cardTable {\n  :global {\n    .ms-DetailsRow {\n      .ms-DetailsRow-fields {\n        font-size: 0.9rem;\n      }\n      > :first-child.ms-DetailsRow-fields {\n        > :first-child {\n          padding-left: @padding-page;\n        }\n      }\n      > .ms-DetailsRow-cellCheck + .ms-DetailsRow-fields {\n        > :first-child {\n          padding-left: 0;\n        }\n      }\n    }\n  }\n}\n\n.cardTable.contentExtended {\n  :global {\n    .ms-DetailsRow {\n      > :first-child.ms-DetailsRow-fields {\n        > :last-child {\n          padding-right: @padding-page;\n        }\n      }\n      > .ms-DetailsRow-cellCheck + .ms-DetailsRow-fields {\n        > :first-child {\n          padding-left: 0;\n        }\n      }\n    }\n  }\n}\n\n.tableHeader {\n  :global {\n    .ms-DetailsHeader {\n      padding-top: 0;\n\n      > :first-child .ms-DetailsHeader-cellTitle {\n        padding-left: @padding-page;\n      }\n      > :first-child.ms-DetailsHeader-cellIsCheck\n        + .ms-DetailsHeader-cell\n        .ms-DetailsHeader-cellTitle {\n        padding-left: 0;\n      }\n\n      // FIXME: For sticky headers, when there is `.contentExtended`, we\n      // need to add padding right.\n\n      // > :nth-last-child(2) .ms-DetailsHeader-cellTitle {\n      //   padding-right: @padding-page;\n      // }\n\n      // &.is-resizingColumn > :nth-last-child(3) .ms-DetailsHeader-cellTitle {\n      //   // FIXME: This is highly magical\n      //   padding-right: @padding-page;\n      // }\n    }\n  }\n}\n\n.clickableTableRow {\n  cursor: pointer;\n}\n\n.highlightRow {\n  border: 1px solid;\n}\n\n.cardTableContent {\n  margin-left: -@padding-page;\n  margin-right: -@padding-page;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/CardTable/index.tsx",
    "content": "import { IRenderFunction } from '@uifabric/utilities'\nimport { useMemoizedFn } from 'ahooks'\nimport { Checkbox } from 'antd'\nimport cx from 'classnames'\nimport {\n  ColumnActionsMode,\n  ConstrainMode,\n  DetailsList,\n  DetailsListLayoutMode,\n  IColumn,\n  IDetailsList,\n  IDetailsListProps,\n  IDetailsRowProps,\n  SelectionMode\n} from 'office-ui-fabric-react/lib/DetailsList'\nimport { ScrollToMode } from 'office-ui-fabric-react/lib/List'\nimport { Sticky, StickyPositionType } from 'office-ui-fabric-react/lib/Sticky'\nimport React, { useCallback, useLayoutEffect, useMemo, useRef } from 'react'\n\nimport AnimatedSkeleton from '../AnimatedSkeleton'\nimport Card from '../Card'\nimport ErrorBar from '../ErrorBar'\n\nimport styles from './index.module.less'\n\nexport { AntCheckboxGroupHeader } from './GroupHeader'\n\nDetailsList['whyDidYouRender'] = {\n  customName: 'DetailsList'\n}\n\nfunction renderStickyHeader(props, defaultRender) {\n  if (!props) {\n    return null\n  }\n  return (\n    <Sticky stickyPosition={StickyPositionType.Header} isScrollSynced>\n      <div className={styles.tableHeader}>{defaultRender!(props)}</div>\n    </Sticky>\n  )\n}\n\nfunction renderCheckbox(props) {\n  return <Checkbox checked={props?.checked} />\n}\n\nexport function ImprovedDetailsList(props: IDetailsListProps) {\n  return (\n    <DetailsList\n      onRenderDetailsHeader={renderStickyHeader}\n      onRenderCheckbox={renderCheckbox}\n      {...props}\n    />\n  )\n}\n\nImprovedDetailsList.whyDidYouRender = true\n\nexport const MemoDetailsList = React.memo(ImprovedDetailsList)\n\nfunction copyAndSort<T>(\n  items: T[],\n  columnKey: string,\n  isSortedDescending?: boolean\n): T[] {\n  const key = columnKey as keyof T\n  return items\n    .slice(0)\n    .sort((a: T, b: T) =>\n      (isSortedDescending ? a[key] < b[key] : a[key] > b[key]) ? 1 : -1\n    )\n}\n\nexport interface ICardTableProps extends IDetailsListProps {\n  title?: React.ReactNode\n  subTitle?: React.ReactNode\n  className?: string\n  style?: object\n  loading?: boolean\n  hideLoadingWhenNotEmpty?: boolean // Whether loading animation should not show when data is not empty\n  errors?: any[]\n\n  cardExtra?: React.ReactNode\n  cardNoMargin?: boolean\n  cardNoMarginTop?: boolean\n  cardNoMarginBottom?: boolean\n  extendLastColumn?: boolean\n\n  // The keys of visible columns. If null, all columns will be shown.\n  visibleColumnKeys?: { [key: string]: boolean }\n  visibleItemsCount?: number\n\n  // Handle sort\n  orderBy?: string\n  desc?: boolean\n  onChangeOrder?: (orderBy: string, desc: boolean) => void\n\n  // Event triggered when a row is clicked.\n  onRowClicked?: (\n    item: any,\n    itemIndex: number,\n    ev: React.MouseEvent<HTMLElement>\n  ) => void\n  clickedRowIndex?: number\n}\n\nfunction useRenderClickableRow(\n  onRowClicked,\n  clickedRowIdx,\n  customRender?: IRenderFunction<IDetailsRowProps> | undefined\n) {\n  return useCallback(\n    (props, defaultRender) => {\n      if (!props) {\n        return null\n      }\n      return (\n        <div\n          className={cx(styles.clickableTableRow, {\n            [styles.highlightRow]: clickedRowIdx === props.itemIndex\n          })}\n          onClick={(ev) => onRowClicked?.(props.item, props.itemIndex, ev)}\n        >\n          {customRender ? customRender(props) : defaultRender!(props)}\n        </div>\n      )\n    },\n    [onRowClicked, clickedRowIdx, customRender]\n  )\n}\n\nfunction dummyColumn(): IColumn {\n  return {\n    name: '',\n    key: 'dummy',\n    minWidth: 28,\n    maxWidth: 28,\n    onRender: (_rec) => null\n  }\n}\n\nexport default function CardTable(props: ICardTableProps) {\n  const {\n    title,\n    subTitle,\n    className,\n    style,\n    loading = false,\n    hideLoadingWhenNotEmpty,\n    errors = [],\n    cardExtra,\n    cardNoMargin,\n    cardNoMarginTop,\n    cardNoMarginBottom,\n    extendLastColumn,\n    visibleColumnKeys,\n    visibleItemsCount,\n    orderBy,\n    desc = true,\n    onChangeOrder,\n    onRowClicked,\n    clickedRowIndex,\n    columns,\n    items,\n    onRenderRow,\n    selectionMode = SelectionMode.none,\n    ...restProps\n  } = props\n  const renderClickableRow = useRenderClickableRow(\n    onRowClicked,\n    clickedRowIndex || -1,\n    onRenderRow\n  )\n\n  const activeIdx = useRef<number>(-1)\n  const handleActiveItemChange = useCallback((_, index?: number) => {\n    activeIdx.current = index ?? -1\n  }, [])\n\n  const onColumnClick = useMemoizedFn(\n    (_ev: React.MouseEvent<HTMLElement>, column: IColumn) => {\n      if (!onChangeOrder) {\n        return\n      }\n      if (column.key === orderBy) {\n        onChangeOrder(orderBy, !desc)\n      } else {\n        onChangeOrder(column.key, true)\n      }\n    }\n  )\n\n  const finalColumns = useMemo(() => {\n    let newColumns: IColumn[] = columns || []\n    if (visibleColumnKeys != null) {\n      newColumns = newColumns.filter((c) => visibleColumnKeys[c.key])\n    }\n    newColumns = newColumns.map((c) => ({\n      ...c,\n      isResizable: c.isResizable ?? true,\n      isSorted: c.key === orderBy,\n      isSortedDescending: desc,\n      onColumnClick,\n      columnActionsMode: c.columnActionsMode || ColumnActionsMode.disabled\n    }))\n    if (!extendLastColumn) {\n      newColumns.push(dummyColumn())\n    }\n    return newColumns\n  }, [\n    onColumnClick,\n    columns,\n    visibleColumnKeys,\n    orderBy,\n    desc,\n    extendLastColumn\n  ])\n\n  const finalItems = useMemo(() => {\n    let newItems = items || []\n    const curColumn = finalColumns.find((col) => col.key === orderBy)\n    if (curColumn) {\n      newItems = copyAndSort(\n        newItems,\n        curColumn.fieldName!,\n        curColumn.isSortedDescending\n      )\n    }\n    if (visibleItemsCount != null) {\n      newItems = newItems.slice(0, visibleItemsCount)\n    }\n    return newItems\n  }, [visibleItemsCount, items, orderBy, finalColumns])\n\n  const tableRef = useRef<IDetailsList>(null)\n\n  useLayoutEffect(() => {\n    if (activeIdx.current === -1 && (clickedRowIndex ?? -1) >= 0) {\n      setTimeout(() => {\n        tableRef.current?.focusIndex(\n          clickedRowIndex!,\n          true,\n          undefined,\n          ScrollToMode.center\n        )\n      }, 50)\n    }\n  }, [clickedRowIndex, finalItems])\n\n  return (\n    <Card\n      title={title}\n      subTitle={subTitle}\n      style={style}\n      className={cx(styles.cardTable, className, {\n        [styles.contentExtended]: extendLastColumn\n      })}\n      noMargin={cardNoMargin}\n      noMarginTop={cardNoMarginTop}\n      noMarginBottom={cardNoMarginBottom}\n      extra={cardExtra}\n      {...restProps}\n    >\n      <ErrorBar errors={errors} />\n      <AnimatedSkeleton\n        showSkeleton={\n          (!hideLoadingWhenNotEmpty && loading) ||\n          (items.length === 0 && loading)\n        }\n      >\n        <div className={styles.cardTableContent}>\n          <MemoDetailsList\n            constrainMode={ConstrainMode.unconstrained}\n            layoutMode={DetailsListLayoutMode.justified}\n            onRenderRow={onRowClicked ? renderClickableRow : onRenderRow}\n            columns={finalColumns}\n            items={finalItems}\n            componentRef={tableRef}\n            selectionMode={selectionMode}\n            onActiveItemChanged={handleActiveItemChange}\n            {...restProps}\n          />\n        </div>\n      </AnimatedSkeleton>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/CardTabs/index.module.less",
    "content": ".tabs {\n  margin-left: -@padding-page;\n  margin-right: -@padding-page;\n\n  .card_tab_navs {\n    padding-left: @padding-page;\n    padding-right: @padding-page;\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/CardTabs/index.tsx",
    "content": "import React, { useState } from 'react'\nimport { Tabs } from 'antd'\nimport cx from 'classnames'\nimport styles from './index.module.less'\nimport { TabsProps } from 'antd/es/tabs'\n\ntype Tab = {\n  key: string\n  title: string\n  content: () => React.ReactNode\n}\n\nexport interface ICardTabsProps extends TabsProps {\n  className?: string\n  tabs: Tab[]\n}\n\nfunction renderCardTabBar(props, DefaultTabBar) {\n  return <DefaultTabBar {...props} className={styles.card_tab_navs} />\n}\n\nfunction CardTabs({\n  className,\n  tabs,\n  defaultActiveKey,\n  onChange,\n  renderTabBar,\n  ...restProps\n}: ICardTabsProps) {\n  const [tabKey, setTabKey] = useState(defaultActiveKey || tabs[0].key)\n  const c = cx(styles.tabs, className)\n  const selectedTab = tabs.find((tab) => tab.key === tabKey)\n\n  function changeTab(tabKey) {\n    setTabKey(tabKey)\n    onChange && onChange(tabKey)\n  }\n\n  return (\n    <>\n      <Tabs\n        className={c}\n        defaultActiveKey={tabKey}\n        onChange={changeTab}\n        renderTabBar={renderTabBar || renderCardTabBar}\n        {...restProps}\n        data-e2e=\"tabs\"\n      >\n        {tabs.map((tab) => (\n          <Tabs.TabPane tab={tab.title} key={tab.key} />\n        ))}\n      </Tabs>\n      {selectedTab?.content()}\n    </>\n  )\n}\n\nexport default CardTabs\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/ColumnsSelector/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.title_container {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  height: 32px;\n}\n\n.foot_container {\n  border-top: 1px solid @border-color-split;\n  margin: 0 -@popover-padding-horizontal;\n  padding: 8px @popover-padding-horizontal 0;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/ColumnsSelector/index.tsx",
    "content": "import React, { ReactNode, useMemo, useState, useEffect } from 'react'\nimport { Checkbox, Popover, Space, Button } from 'antd'\nimport { DownOutlined } from '@ant-design/icons'\nimport { useTranslation } from 'react-i18next'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport { addTranslationResource } from '@lib/utils/i18n'\n\nimport styles from './index.module.less'\n\nconst translations = {\n  en: {\n    trigger_text: 'Columns',\n    select: 'Select',\n    reset: 'Reset'\n  },\n  zh: {\n    trigger_text: '选择列',\n    select: '选择',\n    reset: '重置'\n  }\n}\n\nfor (const key in translations) {\n  addTranslationResource(key, {\n    component: {\n      columnsSelector: translations[key]\n    }\n  })\n}\n\nexport interface IColumnKeys {\n  [key: string]: boolean\n}\n\nexport interface IColumnsSelectorProps {\n  columns: IColumn[]\n  visibleColumnKeys?: IColumnKeys\n  defaultVisibleColumnKeys?: IColumnKeys\n  onChange?: (visibleKeys: IColumnKeys) => void\n  foot?: ReactNode\n}\n\nexport default function ColumnsSelector({\n  columns,\n  visibleColumnKeys,\n  defaultVisibleColumnKeys,\n  onChange,\n  foot\n}: IColumnsSelectorProps) {\n  const { t } = useTranslation()\n  const [indeterminate, setIndeterminate] = useState(true)\n  const [checkedAll, setCheckedAll] = useState(false)\n\n  const filteredColumns = useMemo(\n    () => columns.filter((c) => c.key !== 'dummy'),\n    [columns]\n  )\n\n  const visibleKeys = useMemo(() => {\n    if (visibleColumnKeys) {\n      return visibleColumnKeys\n    }\n    return columns.reduce((acc, cur) => {\n      acc[cur.key] = true\n      return acc\n    }, {})\n  }, [visibleColumnKeys, columns])\n\n  useEffect(() => {\n    function updateCheckAllStatus(columnKeys) {\n      const checkedKeysCount = Object.keys(columnKeys).filter(\n        (k) => columnKeys[k] && k !== 'dummy'\n      ).length\n      setIndeterminate(\n        checkedKeysCount > 0 && checkedKeysCount < filteredColumns.length\n      )\n      setCheckedAll(checkedKeysCount === filteredColumns.length)\n    }\n\n    updateCheckAllStatus(visibleKeys)\n  }, [visibleKeys, filteredColumns])\n\n  function handleCheckAllChange(e) {\n    const checked = e.target.checked\n    const newVisibleKeys = columns.reduce((acc, cur) => {\n      acc[cur.key] = checked\n      return acc\n    }, {})\n    onChange && onChange(newVisibleKeys)\n  }\n\n  function handleCheckChange(e, column: IColumn) {\n    const checked = e.target.checked\n    const newVisibleKeys = {\n      ...visibleKeys,\n      [column.key]: checked\n    }\n    onChange && onChange(newVisibleKeys)\n  }\n\n  const title = (\n    <div className={styles.title_container}>\n      <Checkbox\n        indeterminate={indeterminate}\n        checked={checkedAll}\n        onChange={handleCheckAllChange}\n        data-e2e=\"column_selector_title\"\n      >\n        {t('component.columnsSelector.select')}\n      </Checkbox>\n      {defaultVisibleColumnKeys && (\n        <Button\n          type=\"link\"\n          onClick={() => onChange && onChange(defaultVisibleColumnKeys)}\n          data-e2e=\"column_selector_reset\"\n        >\n          {t('component.columnsSelector.reset')}\n        </Button>\n      )}\n    </div>\n  )\n\n  const content = (\n    <div style={{ marginTop: -12 }}>\n      <Space\n        direction=\"vertical\"\n        style={{\n          maxHeight: 400,\n          overflow: 'auto',\n          paddingTop: 8,\n          paddingBottom: 8\n        }}\n        data-e2e=\"columns_selector_popover_content\"\n      >\n        {filteredColumns.map((column) => (\n          <Checkbox\n            data-e2e={`columns_selector_field_${column.key}`}\n            key={column.key}\n            checked={visibleKeys[column.key]}\n            onChange={(e) => handleCheckChange(e, column)}\n          >\n            {column.name}\n          </Checkbox>\n        ))}\n      </Space>\n      {foot && <div className={styles.foot_container}>{foot}</div>}\n    </div>\n  )\n\n  return (\n    <Popover content={content} title={title} placement=\"bottomLeft\">\n      <span data-e2e=\"columns_selector_popover\" style={{ cursor: 'pointer' }}>\n        {t('component.columnsSelector.trigger_text')} <DownOutlined />\n      </span>\n    </Popover>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/CopyLink/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.copiedText {\n  color: @success-color;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/CopyLink/index.tsx",
    "content": "import React, { useState } from 'react'\nimport { CopyToClipboard } from 'react-copy-to-clipboard'\nimport { useTranslation } from 'react-i18next'\nimport { useTimeoutFn } from 'react-use'\nimport { CheckOutlined, CopyOutlined } from '@ant-design/icons'\nimport { addTranslationResource } from '@lib/utils/i18n'\n\nimport styles from './index.module.less'\n\ntype DisplayVariant = 'default' | 'original_sql' | 'formatted_sql'\nconst transKeys: { [K in DisplayVariant]: string } = {\n  default: 'copy',\n  original_sql: 'copyOriginal',\n  formatted_sql: 'copyFormatted'\n}\n\nexport interface ICopyLinkProps\n  extends React.DetailedHTMLProps<\n    React.HTMLAttributes<HTMLSpanElement>,\n    HTMLSpanElement\n  > {\n  data?: string\n  displayVariant?: DisplayVariant\n}\n\nconst translations = {\n  en: {\n    copy: 'Copy',\n    copyOriginal: 'Copy Original',\n    copyFormatted: 'Copy Formatted',\n    success: 'Copied'\n  },\n  zh: {\n    copy: '复制',\n    copyOriginal: '复制原始 SQL',\n    copyFormatted: '复制格式化 SQL',\n    success: '已复制'\n  }\n}\n\nfor (const key in translations) {\n  addTranslationResource(key, {\n    component: {\n      copyLink: translations[key]\n    }\n  })\n}\n\nfunction CopyLink({\n  data,\n  displayVariant = 'default',\n  ...otherProps\n}: ICopyLinkProps) {\n  const { t } = useTranslation()\n  const [showCopied, setShowCopied] = useState(false)\n\n  const reset = useTimeoutFn(() => {\n    setShowCopied(false)\n  }, 1500)[2]\n\n  const handleCopy = () => {\n    setShowCopied(true)\n    reset()\n  }\n\n  return (\n    <span {...otherProps}>\n      {!showCopied && (\n        <CopyToClipboard text={data ?? ''} onCopy={handleCopy}>\n          <a data-e2e={`copy_${displayVariant}_to_clipboard`}>\n            {t(`component.copyLink.${transKeys[displayVariant]}`)}{' '}\n            <CopyOutlined />\n          </a>\n        </CopyToClipboard>\n      )}\n      {showCopied && (\n        <span className={styles.copiedText} data-e2e=\"copied_success\">\n          <CheckOutlined /> {t('component.copyLink.success')}\n        </span>\n      )}\n    </span>\n  )\n}\n\nexport default React.memo(CopyLink)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/DatePicker/index.tsx",
    "content": "import { Dayjs } from 'dayjs'\nimport dayjsGenerateConfig from 'rc-picker/es/generate/dayjs'\nimport generatePicker from 'antd/es/date-picker/generatePicker'\nimport 'antd/es/date-picker/style/index'\n\nconst DatePicker = generatePicker<Dayjs>(dayjsGenerateConfig)\n\nexport default DatePicker\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/DateTime/Calendar.tsx",
    "content": "import React from 'react'\nimport { Tooltip } from 'antd'\nimport dayjs from 'dayjs'\nimport { useTranslation } from 'react-i18next'\nimport { addTranslationResource } from '@lib/utils/i18n'\nimport i18next from 'i18next'\nimport { format as longFormat } from './Long'\nimport { IDateTimeProps } from '.'\n\nimport calendar from './calendarPlugin'\nimport weekOfYear from 'dayjs/plugin/weekOfYear'\nimport localizedFormat from 'dayjs/plugin/localizedFormat'\nimport tz from '@lib/utils/timezone'\n\ndayjs.extend(calendar)\ndayjs.extend(weekOfYear)\ndayjs.extend(localizedFormat)\n\nconst translations = {\n  en: {\n    sameDay: '[Today at] h:mm A (UTCZ)',\n    sameWeek: 'dddd h:mm A (UTCZ)',\n    nextDay: '[Tomorrow] h:mm A (UTCZ)',\n    nextWeek: '[Next] dddd h:mm A (UTCZ)',\n    lastDay: '[Yesterday] h:mm A (UTCZ)',\n    lastWeek: '[Last] dddd h:mm A (UTCZ)',\n    sameElse: 'lll (UTCZ)'\n  },\n  zh: {\n    sameDay: '[今天] HH:mm (UTCZ)',\n    sameWeek: 'dddd HH:mm (UTCZ)',\n    nextDay: '[明天] HH:mm (UTCZ)',\n    nextWeek: '[下]dddd HH:mm (UTCZ)',\n    lastDay: '[昨天] HH:mm (UTCZ)',\n    lastWeek: '[上]dddd HH:mm (UTCZ)',\n    sameElse: 'lll (UTCZ)'\n  }\n}\n\nfor (const key in translations) {\n  addTranslationResource(key, {\n    component: {\n      dateTime: {\n        calendar: translations[key]\n      }\n    }\n  })\n}\n\nfunction Calendar({ unixTimestampMs, ...rest }: IDateTimeProps) {\n  useTranslation() // Re-render when language changes\n  return (\n    <Tooltip title={longFormat(unixTimestampMs)} {...rest}>\n      <span>{format(unixTimestampMs)}</span>\n    </Tooltip>\n  )\n}\n\nexport function format(unixTimestampMs: number) {\n  return dayjs(unixTimestampMs)\n    .utcOffset(tz.getTimeZone())\n    .calendar(undefined, {\n      sameDay: i18next.t('component.dateTime.calendar.sameDay'),\n      sameWeek: i18next.t('component.dateTime.calendar.sameWeek'),\n      nextDay: i18next.t('component.dateTime.calendar.nextDay'),\n      nextWeek: i18next.t('component.dateTime.calendar.nextWeek'),\n      lastDay: i18next.t('component.dateTime.calendar.lastDay'),\n      lastWeek: i18next.t('component.dateTime.calendar.lastWeek'),\n      sameElse: i18next.t('component.dateTime.calendar.sameElse')\n    })\n}\n\nexport default React.memo(Calendar)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/DateTime/Long.tsx",
    "content": "import React from 'react'\nimport { Tooltip } from 'antd'\nimport dayjs from 'dayjs'\nimport { useTranslation } from 'react-i18next'\nimport localizedFormat from 'dayjs/plugin/localizedFormat'\n\nimport tz from '@lib/utils/timezone'\nimport { IDateTimeProps } from '.'\n\ndayjs.extend(localizedFormat)\n\nfunction Long({ unixTimestampMs, ...rest }: IDateTimeProps) {\n  useTranslation() // Re-render when language changes\n  return (\n    <Tooltip title={format(unixTimestampMs)} {...rest}>\n      <span>{format(unixTimestampMs)}</span>\n    </Tooltip>\n  )\n}\n\nexport function format(unixTimestampMs: number) {\n  return dayjs(unixTimestampMs)\n    .utcOffset(tz.getTimeZone())\n    .format('ll LTS (UTCZ)')\n}\n\nexport default React.memo(Long)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/DateTime/calendarPlugin.ts",
    "content": "// Copyright 2024 PingCAP, Inc.\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// Inspired by:\n// https://github.com/iamkun/dayjs/issues/1226#issuecomment-768796249\n// https://github.com/iamkun/dayjs/blob/dev/src/plugin/calendar/index.js\n\ndeclare module 'dayjs' {\n  interface Dayjs {\n    calendar(refTime?: any, formats?: object): string\n  }\n}\n\nexport default (o, c, d) => {\n  const LT = 'h:mm A'\n  const L = 'MM/DD/YYYY'\n  const calendarFormat = {\n    lastDay: `[Yesterday at] ${LT}`,\n    sameDay: `[Today at] ${LT}`,\n    nextDay: `[Tomorrow at] ${LT}`,\n    sameWeek: `dddd [at] ${LT}`,\n    nextWeek: `[Next] dddd [at] ${LT}`,\n    lastWeek: `[Last] dddd [at] ${LT}`,\n    sameElse: L\n  }\n  const proto = c.prototype\n  proto.calendar = function (refTime, formats) {\n    const format = formats || this.$locale().calendar || calendarFormat\n    const refDayStart = d(refTime || undefined).startOf('d')\n\n    let retVal = ''\n    const dayDiff = this.diff(refDayStart, 'd', true)\n\n    if (dayDiff < -14 || dayDiff > 14) {\n      retVal = 'sameElse'\n    } else if (dayDiff < 0 && dayDiff >= -1) {\n      retVal = 'lastDay'\n    } else if (dayDiff >= 0 && dayDiff < 1) {\n      retVal = 'sameDay'\n    } else if (dayDiff >= 1 && dayDiff < 2) {\n      retVal = 'nextDay'\n    } else if (dayDiff < -1) {\n      // -14 ~ -1\n      if (this.startOf('week').unix() === refDayStart.startOf('week').unix()) {\n        retVal = 'sameWeek'\n      } else {\n        const pass1Week = this.add(1, 'week')\n        if (\n          pass1Week.startOf('week').unix() ===\n          refDayStart.startOf('week').unix()\n        ) {\n          retVal = 'lastWeek'\n        }\n      }\n    } else if (dayDiff >= 2) {\n      // 2 ~ 14\n      if (this.startOf('week').unix() === refDayStart.startOf('week').unix()) {\n        retVal = 'sameWeek'\n      } else {\n        const back1Week = this.subtract(1, 'week')\n        if (\n          back1Week.startOf('week').unix() ===\n          refDayStart.startOf('week').unix()\n        ) {\n          retVal = 'nextWeek'\n        }\n      }\n    }\n    if (retVal === '') {\n      retVal = 'sameElse'\n    }\n\n    /* eslint-enable no-nested-ternary */\n    const currentFormat = format[retVal] || calendarFormat[retVal]\n    if (typeof currentFormat === 'function') {\n      return currentFormat.call(this, d())\n    }\n    return this.format(currentFormat)\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/DateTime/index.tsx",
    "content": "import Calendar from './Calendar'\nimport Long from './Long'\n\nexport interface IDateTimeProps {\n  unixTimestampMs: number\n}\n\nexport default { Calendar, Long }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Descriptions/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.descriptions {\n  :global {\n    .ant-descriptions-row {\n      line-height: 1;\n    }\n\n    .ant-descriptions-row > th {\n      padding: 0;\n      padding-bottom: @padding-xs;\n    }\n\n    .ant-descriptions-row > td {\n      padding: 0;\n      padding-bottom: @padding-md;\n    }\n\n    .ant-descriptions-item-label {\n      color: @text-color-secondary;\n    }\n\n    .ant-descriptions-item-content {\n      color: @text-color;\n    }\n  }\n}\n\n.item {\n  // nothing for now\n}\n\n.itemSingleline {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  > span {\n    display: inline;\n  }\n  pre {\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n}\n\n.itemMultiline {\n  overflow-wrap: break-word;\n  white-space: normal;\n  text-overflow: inherit;\n  overflow: auto;\n\n  :global {\n    .ant-descriptions-item-container {\n      overflow: auto;\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Descriptions/index.tsx",
    "content": "import React from 'react'\nimport { Descriptions as AntDescriptions } from 'antd'\nimport type { DescriptionsItemProps } from 'antd/es/descriptions/Item'\nimport cx from 'classnames'\n\nimport styles from './index.module.less'\n\nexport interface IDescriptionsProps {\n  className?: string\n  children?:\n    | (React.ReactElement<IDescriptionsItemProps> | null | undefined)[]\n    | React.ReactElement<IDescriptionsItemProps>\n  column?: number\n  onClick?: () => void\n}\n\nexport interface IDescriptionsItemProps extends DescriptionsItemProps {\n  className?: string\n  children: React.ReactNode\n  multiline?: boolean\n  onClick?: () => void\n}\n\n// FIXME: This logic duplicates to <TextWrap>\nfunction mapItem(item: React.ReactElement<IDescriptionsItemProps>) {\n  const { props } = item\n  const { multiline, className, children, ...restProps } = props\n  const c = cx(className, styles.item, {\n    [styles.itemMultiline]: multiline,\n    [styles.itemSingleline]: !multiline\n  })\n  return (\n    <AntDescriptions.Item className={c} key={item.key || ''} {...restProps}>\n      {children}\n    </AntDescriptions.Item>\n  )\n}\n\nfunction Descriptions({\n  className,\n  children,\n  column,\n  ...restProps\n}: IDescriptionsProps) {\n  const c = cx(className, styles.descriptions)\n  let realChildren\n  if (children) {\n    if (Array.isArray(children)) {\n      realChildren = children.filter((v) => v != null).map((v) => mapItem(v!))\n    } else {\n      realChildren = mapItem(children)\n    }\n  }\n  return (\n    <AntDescriptions\n      layout=\"vertical\"\n      colon={false}\n      className={c}\n      column={column ?? 2}\n      {...restProps}\n    >\n      {realChildren}\n    </AntDescriptions>\n  )\n}\n\nDescriptions.Item = AntDescriptions.Item as React.FC<IDescriptionsItemProps>\n\nexport default Descriptions\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/DrawerFooter/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.container {\n  position: sticky;\n  bottom: -@drawer-body-padding;\n  margin: -@drawer-body-padding;\n  background: @drawer-bg;\n  padding: @drawer-body-padding;\n\n  transition: box-shadow 0.1s linear;\n\n  &.withShadow {\n    box-shadow: 0 -5px 10px rgba(#000, 0.1);\n  }\n}\n\n.mark {\n  height: 1px;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/DrawerFooter/index.tsx",
    "content": "import React from 'react'\nimport { useInView } from 'react-intersection-observer'\nimport cx from 'classnames'\n\nimport styles from './index.module.less'\n\n// A sticky footer for Antd Drawer component.\nfunction DrawerFooter({\n  children,\n  className,\n  ...rest\n}: React.HTMLAttributes<HTMLDivElement>) {\n  const { ref, inView } = useInView({\n    initialInView: true, // prevent shadow being displayed at the beginning\n    rootMargin: '-24px' // equals to @drawer-body-padding\n  })\n  const displayShadow = !inView\n\n  return (\n    <>\n      <div\n        className={cx(className, styles.container, {\n          [styles.withShadow]: displayShadow\n        })}\n        {...rest}\n      >\n        {children}\n      </div>\n      <div ref={ref} className={styles.mark}></div>\n    </>\n  )\n}\n\nexport default React.memo(DrawerFooter)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/ErrorBar/index.tsx",
    "content": "import { Alert } from 'antd'\nimport _ from 'lodash'\nimport React, { useMemo } from 'react'\n\nexport interface IErrorBarProps {\n  errors: any[]\n}\n\nexport default function ErrorBar({ errors }: IErrorBarProps) {\n  // show at most 3 kinds of errors\n  const errorMsgs = useMemo(\n    () =>\n      _.uniq(_.map(errors, (err) => err?.message || ''))\n        .filter((msg) => msg !== '')\n        .slice(0, 3),\n    [errors]\n  )\n\n  if (errorMsgs.length === 0) {\n    return null\n  } else if (errorMsgs.length === 1) {\n    return (\n      <Alert\n        message={errorMsgs[0]}\n        showIcon\n        type=\"error\"\n        data-e2e=\"alert_error_bar\"\n      />\n    )\n  } else {\n    return (\n      <Alert\n        message=\"Errors\"\n        showIcon\n        type=\"error\"\n        description={\n          <ul>\n            {errorMsgs.map((msg, idx) => (\n              <li key={idx}>{msg}</li>\n            ))}\n          </ul>\n        }\n        data-e2e=\"alert_error_bar\"\n      />\n    )\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Expand/index.tsx",
    "content": "import React from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { addTranslationResource } from '@lib/utils/i18n'\n\nexport interface IExpandProps {\n  expanded?: boolean\n  collapsedContent?: React.ReactNode\n  children: React.ReactNode\n}\n\nfunction Expand({ collapsedContent, children, expanded }: IExpandProps) {\n  // FIXME: Animations\n  return (\n    <div data-e2e=\"statement_query_detail_page_query\">\n      {expanded ? children : collapsedContent ?? children}\n    </div>\n  )\n}\n\nconst translations = {\n  en: {\n    expandText: 'Expand',\n    collapseText: 'Collapse'\n  },\n  zh: {\n    expandText: '展开',\n    collapseText: '收起'\n  }\n}\n\nfor (const key in translations) {\n  addTranslationResource(key, {\n    component: {\n      expandLink: translations[key]\n    }\n  })\n}\n\nexport interface IExpandLinkProps\n  extends React.AnchorHTMLAttributes<HTMLAnchorElement> {\n  expanded?: boolean\n}\n\nfunction Link({ expanded, ...restProps }: IExpandLinkProps) {\n  const { t } = useTranslation()\n  return (\n    <a {...restProps} data-e2e={`${expanded ? 'collapseText' : 'expandText'}`}>\n      {expanded\n        ? t('component.expandLink.collapseText')\n        : t('component.expandLink.expandText')}\n    </a>\n  )\n}\n\nExpand.Link = Link\n\nexport default Expand\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Head/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.headContainer {\n  // &:before,\n  // &:after {\n  //   // Handle margin collapse\n  //   content: ' ';\n  //   display: table;\n  // }\n}\n\n.headInner {\n  margin-top: @padding-page;\n}\n\n.headTitleSection {\n  margin: @padding-lg @padding-page;\n  display: flex;\n  flex-direction: row;\n  align-items: center;\n}\n\n.headBack {\n  margin-right: @padding-lg;\n  flex-shrink: 0;\n}\n\n.headTitle {\n  color: @heading-color;\n  font-size: @heading-4-size;\n  line-height: 32px;\n  flex-grow: 1;\n  margin-right: @padding-lg;\n}\n\n.headContent {\n  margin: @padding-lg @padding-page;\n}\n\n.headFooter {\n  margin-top: @padding-lg;\n  border-bottom: 1px solid @border-color-base;\n  padding: 0 @padding-page;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Head/index.tsx",
    "content": "import React, { ReactNode } from 'react'\nimport cx from 'classnames'\nimport styles from './index.module.less'\n\nexport interface IHeadProps {\n  title: string\n  titleExtra?: ReactNode\n  back?: ReactNode\n  footer?: ReactNode\n  className?: string\n  children?: ReactNode\n}\n\nfunction Head({\n  title,\n  titleExtra,\n  back,\n  footer,\n  className,\n  children,\n  ...rest\n}: IHeadProps) {\n  return (\n    <div className={cx(styles.headContainer, className)} {...rest}>\n      <div className={styles.headInner}>\n        {(title || titleExtra || back) && (\n          <div className={cx(styles.headTitleSection)}>\n            {back && <div className={styles.headBack}>{back}</div>}\n            {title && <div className={styles.headTitle}>{title}</div>}\n            {titleExtra && <div>{titleExtra}</div>}\n          </div>\n        )}\n        {children && <div className={styles.headContent}>{children}</div>}\n        {footer && <div className={styles.headFooter}>{footer}</div>}\n      </div>\n    </div>\n  )\n}\n\nexport default Head\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/HighlightSQL/index.tsx",
    "content": "import React, { useMemo } from 'react'\n\n// This usage will generate tons of files about languages highlight when esbuild splitting enable\n// See https://github.com/react-syntax-highlighter/react-syntax-highlighter/blob/master/src/async-languages/hljs.js to understand why\n// import { Light as SyntaxHighlighter } from 'react-syntax-highlighter'\nimport SyntaxHighlighter from 'react-syntax-highlighter/dist/esm/light'\nimport sql from 'react-syntax-highlighter/dist/esm/languages/hljs/sql'\nimport lightTheme from 'react-syntax-highlighter/dist/esm/styles/hljs/atom-one-light'\nimport darkTheme from 'react-syntax-highlighter/dist/esm/styles/hljs/atom-one-dark'\nimport Pre from '../Pre'\nimport formatSql from '@lib/utils/sqlFormatter'\nimport moize from 'moize'\n\nSyntaxHighlighter.registerLanguage('sql', sql)\n\ninterface Props {\n  sql: string\n  compact?: boolean\n  theme?: 'dark' | 'light'\n  format?: boolean\n  maxLen?: number\n}\n\nfunction simpleSqlMinify(str) {\n  return str\n    .replace(/\\s{1,}/g, ' ')\n    .replace(/\\{\\s{1,}/g, '{')\n    .replace(/\\}\\s{1,}/g, '}')\n    .replace(/;\\s{1,}/g, ';')\n    .replace(/\\/\\*\\s{1,}/g, '/*')\n    .replace(/\\*\\/\\s{1,}/g, '*/')\n}\n\nfunction HighlightSQL({\n  sql,\n  compact,\n  theme = 'light',\n  format = true,\n  maxLen = 5000\n}: Props) {\n  const formattedSql = useMemo(() => {\n    const truncatedSql =\n      sql.length > maxLen\n        ? `${sql.slice(0, maxLen)}...(remain: ${sql.length - maxLen})`\n        : sql\n    let f = format ? formatSql(truncatedSql) : truncatedSql\n    if (compact) {\n      f = simpleSqlMinify(f)\n    }\n    return f\n  }, [sql, compact, format, maxLen])\n\n  return (\n    <SyntaxHighlighter\n      language=\"sql\"\n      style={theme === 'light' ? lightTheme : darkTheme}\n      customStyle={{\n        background: 'none',\n        padding: 0,\n        overflowX: 'hidden'\n      }}\n      PreTag={Pre}\n      data-e2e={`syntax_highlighter_${compact ? 'compact' : 'original'}`}\n    >\n      {formattedSql}\n    </SyntaxHighlighter>\n  )\n}\n\nexport default moize(HighlightSQL, {\n  isShallowEqual: true,\n  maxArgs: 5,\n  maxSize: 1000\n})\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/InstanceSelect/DropOverlay.tsx",
    "content": "import React, { useState, useMemo } from 'react'\nimport { AntCheckboxGroupHeader } from '../'\nimport { IColumn, ISelection } from 'office-ui-fabric-react/lib/DetailsList'\nimport {\n  IInstanceTableItem,\n  filterInstanceTable\n} from '@lib/utils/instanceTable'\nimport { useTranslation } from 'react-i18next'\nimport TableWithFilter, { ITableWithFilterRefProps } from './TableWithFilter'\n\nconst groupProps = {\n  onRenderHeader: (props) => <AntCheckboxGroupHeader {...props} />\n}\n\nexport interface IDropOverlayProps {\n  selection: ISelection\n  columns: IColumn[]\n  items: IInstanceTableItem[]\n  filterTableRef?: React.Ref<ITableWithFilterRefProps>\n  containerProps?: React.HTMLAttributes<HTMLDivElement>\n}\n\nfunction DropOverlay({\n  selection,\n  columns,\n  items,\n  filterTableRef,\n  containerProps\n}: IDropOverlayProps) {\n  const { t } = useTranslation()\n  const [keyword, setKeyword] = useState('')\n\n  const [finalItems, finalGroups] = useMemo(() => {\n    return filterInstanceTable(items, keyword)\n  }, [items, keyword])\n\n  const { style: containerStyle, ...restContainerProps } = containerProps ?? {}\n  const finalContainerProps = useMemo(() => {\n    const style: React.CSSProperties = {\n      fontSize: '0.9rem',\n      ...containerStyle\n    }\n    return {\n      style,\n      ...restContainerProps\n    } as React.HTMLAttributes<HTMLDivElement> & Record<string, string>\n  }, [containerStyle, restContainerProps])\n\n  return (\n    <TableWithFilter\n      selection={selection}\n      filterPlaceholder={t('component.instanceSelect.filterPlaceholder')}\n      filter={keyword}\n      onFilterChange={setKeyword}\n      tableMaxHeight={300}\n      tableWidth={400}\n      columns={columns}\n      items={finalItems}\n      groups={finalGroups}\n      groupProps={groupProps}\n      containerProps={finalContainerProps}\n      ref={filterTableRef}\n    />\n  )\n}\n\nexport default React.memo(DropOverlay)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/InstanceSelect/TableWithFilter.module.less",
    "content": ".tableWithFilterContainer {\n  :global {\n    .ms-DetailsHeader {\n      padding-top: 0;\n    }\n\n    .ant-input-affix-wrapper {\n      border: 0;\n      box-shadow: none;\n      outline: 0;\n    }\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/InstanceSelect/TableWithFilter.tsx",
    "content": "import React, { useMemo, useCallback, useRef } from 'react'\nimport cx from 'classnames'\nimport { ScrollablePane } from 'office-ui-fabric-react/lib/ScrollablePane'\nimport { MarqueeSelection } from 'office-ui-fabric-react/lib/MarqueeSelection'\nimport { SelectionMode } from 'office-ui-fabric-react/lib/Selection'\nimport { useSize } from 'ahooks'\nimport {\n  DetailsListLayoutMode,\n  ISelection,\n  IDetailsListProps\n} from 'office-ui-fabric-react/lib/DetailsList'\nimport { Input, InputRef } from 'antd'\nimport { MemoDetailsList } from '../'\n\nimport styles from './TableWithFilter.module.less'\n\nexport interface ITableWithFilterProps extends IDetailsListProps {\n  selection: ISelection\n  filterPlaceholder?: string\n  filter?: string\n  onFilterChange?: (value: string) => void\n  tableMaxHeight?: number\n  tableWidth?: number\n  containerProps?: React.HTMLAttributes<HTMLDivElement>\n}\n\nexport interface ITableWithFilterRefProps {\n  focusFilterInput: () => void\n}\n\nfunction TableWithFilter(\n  {\n    selection,\n    filterPlaceholder,\n    filter,\n    onFilterChange,\n    tableMaxHeight,\n    tableWidth,\n    containerProps,\n    ...restProps\n  }: ITableWithFilterProps,\n  ref: React.Ref<ITableWithFilterRefProps>\n) {\n  const handleInputChange = useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      onFilterChange?.(e.target.value)\n    },\n    [onFilterChange]\n  )\n\n  const inputRef = useRef<InputRef>(null)\n\n  React.useImperativeHandle(ref, () => ({\n    focusFilterInput() {\n      inputRef.current?.focus()\n    }\n  }))\n\n  // FIXME: We should put Input inside ScrollablePane after https://github.com/microsoft/fluentui/issues/13557 is resolved\n\n  const containerRef = useRef(null)\n  const containerSize = useSize(containerRef)\n\n  const paneStyle = useMemo(\n    () =>\n      ({\n        position: 'relative',\n        height: containerSize?.height,\n        maxHeight: tableMaxHeight ?? 400,\n        width: tableWidth ?? 400\n      } as React.CSSProperties),\n    [containerSize?.height, tableMaxHeight, tableWidth]\n  )\n\n  const {\n    className: containerClassName,\n    style: containerStyle,\n    ...containerRestProps\n  } = containerProps ?? {}\n\n  return (\n    <div\n      className={cx(styles.tableWithFilterContainer, containerClassName)}\n      style={containerStyle}\n      {...containerRestProps}\n    >\n      <Input\n        placeholder={filterPlaceholder}\n        allowClear\n        onChange={handleInputChange}\n        value={filter}\n        ref={inputRef}\n      />\n      <ScrollablePane style={paneStyle}>\n        <div ref={containerRef}>\n          <MarqueeSelection selection={selection} isDraggingConstrainedToRoot>\n            <MemoDetailsList\n              selectionMode={SelectionMode.multiple}\n              selection={selection}\n              selectionPreservedOnEmptyClick\n              layoutMode={DetailsListLayoutMode.justified}\n              setKey=\"set\"\n              compact\n              {...restProps}\n            />\n          </MarqueeSelection>\n        </div>\n      </ScrollablePane>\n    </div>\n  )\n}\n\nexport default React.memo(React.forwardRef(TableWithFilter))\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/InstanceSelect/ValueDisplay.tsx",
    "content": "import React, { useMemo } from 'react'\nimport {\n  IInstanceTableItem,\n  InstanceKind,\n  instanceKindName\n} from '@lib/utils/instanceTable'\nimport { useTranslation } from 'react-i18next'\n\ninterface InstanceStat {\n  all: number\n  selected: number\n}\n\nfunction newInstanceStat(): InstanceStat {\n  return {\n    all: 0,\n    selected: 0\n  }\n}\n\nexport interface IValueDisplayProps {\n  items: IInstanceTableItem[]\n  selectedKeys: string[]\n}\n\nexport default function ValueDisplay({\n  items,\n  selectedKeys\n}: IValueDisplayProps) {\n  const { t } = useTranslation()\n\n  const text = useMemo(() => {\n    const selectedKeysMap = {}\n    selectedKeys.forEach((key) => (selectedKeysMap[key] = true))\n    const instanceStats: { [key in InstanceKind]: InstanceStat } = {\n      pd: newInstanceStat(),\n      tidb: newInstanceStat(),\n      tikv: newInstanceStat(),\n      tiflash: newInstanceStat(),\n      ticdc: newInstanceStat(),\n      tiproxy: newInstanceStat(),\n      tso: newInstanceStat(),\n      scheduling: newInstanceStat()\n    }\n    items.forEach((item) => {\n      instanceStats[item.instanceKind].all++\n      if (selectedKeysMap[item.key]) {\n        instanceStats[item.instanceKind].selected++\n      }\n    })\n\n    let hasUnselected = false\n    const p: string[] = []\n    for (const ik in instanceStats) {\n      const stats = instanceStats[ik] as InstanceStat\n      if (stats.selected !== stats.all) {\n        hasUnselected = true\n      }\n      if (stats.selected > 0) {\n        if (stats.all === stats.selected) {\n          p.push(\n            t('component.instanceSelect.selected.partial.all', {\n              component: instanceKindName(ik as InstanceKind)\n            })\n          )\n        } else {\n          p.push(\n            t('component.instanceSelect.selected.partial.n', {\n              n: stats.selected,\n              component: instanceKindName(ik as InstanceKind)\n            })\n          )\n        }\n      }\n    }\n\n    if (!hasUnselected) {\n      return t('component.instanceSelect.selected.all')\n    }\n\n    return p.join(', ')\n  }, [t, items, selectedKeys])\n\n  return <>{text}</>\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/InstanceSelect/index.tsx",
    "content": "import React, { useCallback, useRef, useMemo } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useShallowCompareEffect } from 'react-use'\nimport { Tooltip } from 'antd'\nimport {\n  IBaseSelectProps,\n  BaseSelect,\n  InstanceStatusBadge,\n  TextWrap\n} from '../'\nimport { useClientRequest } from '@lib/utils/useClientRequest'\nimport { addTranslationResource } from '@lib/utils/i18n'\nimport { useMemoizedFn, useControllableValue } from 'ahooks'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport {\n  buildInstanceTable,\n  IInstanceTableItem\n} from '@lib/utils/instanceTable'\nimport SelectionWithFilter from '@lib/utils/selectionWithFilter'\n\nimport DropOverlay from './DropOverlay'\nimport ValueDisplay from './ValueDisplay'\nimport { ITableWithFilterRefProps } from './TableWithFilter'\nimport { useChange } from '@lib/utils/useChange'\n\nimport {\n  TopologyTiDBInfo,\n  ClusterinfoStoreTopologyResponse,\n  TopologyPDInfo,\n  TopologyTiCDCInfo,\n  TopologyTiProxyInfo,\n  TopologyTSOInfo,\n  TopologySchedulingInfo\n} from '@lib/client'\n\nimport { ReqConfig } from '@lib/types'\n\nimport { AxiosPromise } from 'axios'\n\nexport interface IInstanceSelectProps\n  extends Omit<IBaseSelectProps<string[]>, 'dropdownRender' | 'valueRender'> {\n  onChange?: (value: string[]) => void\n  enableTiFlash?: boolean\n  defaultSelectAll?: boolean\n  dropContainerProps?: React.HTMLAttributes<HTMLDivElement>\n\n  getTiDBTopology(options?: ReqConfig): AxiosPromise<Array<TopologyTiDBInfo>>\n  getStoreTopology(\n    options?: ReqConfig\n  ): AxiosPromise<ClusterinfoStoreTopologyResponse>\n  getPDTopology(options?: ReqConfig): AxiosPromise<Array<TopologyPDInfo>>\n  getTiCDCTopology?: (\n    options?: ReqConfig\n  ) => AxiosPromise<Array<TopologyTiCDCInfo>>\n  getTiProxyTopology?: (\n    options?: ReqConfig\n  ) => AxiosPromise<Array<TopologyTiProxyInfo>>\n  getTSOTopology?: (options?: ReqConfig) => AxiosPromise<Array<TopologyTSOInfo>>\n  getSchedulingTopology?: (\n    options?: ReqConfig\n  ) => AxiosPromise<Array<TopologySchedulingInfo>>\n}\n\nexport interface IInstanceSelectRefProps {\n  getInstanceByKeys: (keys: string[]) => IInstanceTableItem[]\n  getInstanceByKey: (key: string) => IInstanceTableItem\n}\n\nconst translations = {\n  en: {\n    placeholder: 'Select Instances',\n    filterPlaceholder: 'Filter instance',\n    selected: {\n      all: 'All Instances',\n      partial: {\n        n: '{{n}} {{component}}',\n        all: 'All {{component}}'\n      }\n    },\n    columns: {\n      key: 'Instance',\n      status: 'Status'\n    }\n  },\n  zh: {\n    placeholder: '选择实例',\n    filterPlaceholder: '过滤实例',\n    selected: {\n      all: '所有实例',\n      partial: {\n        n: '{{n}} {{component}}',\n        all: '所有 {{component}}'\n      }\n    },\n    columns: {\n      key: '实例',\n      status: '状态'\n    }\n  }\n}\n\nfor (const key in translations) {\n  addTranslationResource(key, {\n    component: {\n      instanceSelect: translations[key]\n    }\n  })\n}\n\nfunction InstanceSelect(\n  props: IInstanceSelectProps,\n  ref: React.Ref<IInstanceSelectRefProps>\n) {\n  const [internalVal, setInternalVal] = useControllableValue<string[]>(props)\n  const setInternalValPersist = useMemoizedFn(setInternalVal)\n  const {\n    enableTiFlash,\n    defaultSelectAll,\n    dropContainerProps,\n    value, // only to exclude from restProps\n    onChange, // only to exclude from restProps\n    getTiDBTopology,\n    getPDTopology,\n    getStoreTopology,\n    getTiCDCTopology,\n    getTiProxyTopology,\n    getTSOTopology,\n    getSchedulingTopology,\n    ...restProps\n  } = props\n\n  const { t } = useTranslation()\n\n  const { data: dataTiDB, isLoading: loadingTiDB } =\n    useClientRequest(getTiDBTopology)\n  const { data: dataStores, isLoading: loadingStores } =\n    useClientRequest(getStoreTopology)\n  const { data: dataPD, isLoading: loadingPD } = useClientRequest(getPDTopology)\n  const { data: dataTiCDC, isLoading: loadingTiCDC } =\n    useClientRequest(getTiCDCTopology)\n  const { data: dataTiProxy, isLoading: loadingTiProxy } =\n    useClientRequest(getTiProxyTopology)\n  const { data: dataTSO, isLoading: loadingTSO } =\n    useClientRequest(getTSOTopology)\n  const { data: dataScheduling, isLoading: loadingScheduling } =\n    useClientRequest(getSchedulingTopology)\n\n  const columns: IColumn[] = useMemo(\n    () => [\n      {\n        name: t('component.instanceSelect.columns.key'),\n        key: 'key',\n        minWidth: 150,\n        maxWidth: 150,\n        onRender: (node: IInstanceTableItem) => {\n          return (\n            <TextWrap>\n              <Tooltip title={node.key}>\n                <span>{node.key}</span>\n              </Tooltip>\n            </TextWrap>\n          )\n        }\n      },\n      {\n        name: t('component.instanceSelect.columns.status'),\n        key: 'status',\n        minWidth: 100,\n        maxWidth: 100,\n        onRender: (node: IInstanceTableItem) => {\n          return (\n            <TextWrap>\n              <InstanceStatusBadge status={node.status} />\n            </TextWrap>\n          )\n        }\n      }\n    ],\n    [t]\n  )\n\n  const [tableItems] = useMemo(() => {\n    if (\n      loadingTiDB ||\n      loadingStores ||\n      loadingPD ||\n      loadingTiCDC ||\n      loadingTiProxy ||\n      loadingTSO ||\n      loadingScheduling\n    ) {\n      return [[], []]\n    }\n    return buildInstanceTable({\n      dataPD,\n      dataTiDB,\n      dataTiKV: dataStores?.tikv,\n      dataTiFlash: dataStores?.tiflash,\n      dataTiCDC,\n      dataTiProxy,\n      dataTSO,\n      dataScheduling,\n      includeTiFlash: enableTiFlash\n    })\n  }, [\n    enableTiFlash,\n    dataTiDB,\n    dataStores,\n    dataPD,\n    dataTiCDC,\n    dataTiProxy,\n    dataTSO,\n    dataScheduling,\n    loadingTiDB,\n    loadingStores,\n    loadingPD,\n    loadingTiCDC,\n    loadingTiProxy,\n    loadingTSO,\n    loadingScheduling\n  ])\n\n  const selection = useRef(\n    new SelectionWithFilter({\n      onSelectionChanged: () => {\n        const s = selection.current.getAllSelection() as IInstanceTableItem[]\n        const keys = s.map((v) => v.key)\n        setInternalValPersist([...keys])\n      }\n    })\n  )\n\n  useShallowCompareEffect(() => {\n    selection.current?.resetAllSelection(internalVal ?? [])\n  }, [internalVal])\n\n  const dataHasLoaded = useRef(false)\n\n  useChange(() => {\n    // When data is loaded for the first time, we need to:\n    // - Select all if `defaultSelectAll` is set and value is not given.\n    // - Update selection according to value\n    if (dataHasLoaded.current) {\n      return\n    }\n    if (tableItems.length === 0) {\n      return\n    }\n    const sel = selection.current\n    sel.setChangeEvents(false)\n    sel.setAllItems(tableItems)\n    if (internalVal && internalVal.length > 0) {\n      sel.resetAllSelection(internalVal)\n    } else if (defaultSelectAll) {\n      sel.setAllSelectionSelected(true)\n    }\n    sel.setChangeEvents(true)\n    dataHasLoaded.current = true\n  }, [tableItems])\n\n  const getInstanceByKeys = useMemoizedFn((keys: string[]) => {\n    const keyToItemMap = {}\n    for (const item of tableItems) {\n      keyToItemMap[item.key] = item\n    }\n    return keys.map((key) => keyToItemMap[key])\n  })\n\n  const getInstanceByKey = useMemoizedFn((key: string) => {\n    return getInstanceByKeys([key])[0]\n  })\n\n  React.useImperativeHandle(ref, () => ({\n    getInstanceByKey,\n    getInstanceByKeys\n  }))\n\n  const renderValue = useCallback(\n    (selectedKeys) => {\n      if (\n        tableItems.length === 0 ||\n        !selectedKeys ||\n        selectedKeys.length === 0\n      ) {\n        return null\n      }\n      return <ValueDisplay items={tableItems} selectedKeys={selectedKeys} />\n    },\n    [tableItems]\n  )\n\n  const filterTableRef = useRef<ITableWithFilterRefProps>(null)\n\n  const renderDropdown = useCallback(\n    () => (\n      <DropOverlay\n        columns={columns}\n        items={tableItems}\n        selection={selection.current}\n        filterTableRef={filterTableRef}\n        containerProps={dropContainerProps}\n      />\n    ),\n    [columns, tableItems, dropContainerProps]\n  )\n\n  const handleOpened = useCallback(() => {\n    filterTableRef.current?.focusFilterInput()\n  }, [])\n\n  return (\n    <BaseSelect\n      dropdownRender={renderDropdown}\n      value={internalVal}\n      valueRender={renderValue}\n      disabled={loadingTiDB || loadingStores || loadingPD}\n      placeholder={t('component.instanceSelect.placeholder')}\n      onOpened={handleOpened}\n      {...restProps}\n    />\n  )\n}\n\nexport default React.forwardRef(InstanceSelect)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/InstanceStatusBadge/index.tsx",
    "content": "import React from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { InstanceStatus } from '@lib/utils/instanceTable'\nimport { Badge } from 'antd'\nimport { addTranslationResource } from '@lib/utils/i18n'\n\nconst translations = {\n  en: {\n    status: {\n      up: 'Up',\n      down: 'Down',\n      tombstone: 'Tombstone',\n      offline: 'Leaving',\n      unknown: 'Unknown',\n      unreachable: 'Unreachable'\n    }\n  },\n  zh: {\n    status: {\n      up: '在线',\n      down: '离线',\n      tombstone: '已缩容下线',\n      offline: '下线中',\n      unknown: '未知',\n      unreachable: '无法访问'\n    }\n  }\n}\n\nfor (const key in translations) {\n  addTranslationResource(key, {\n    component: {\n      instanceStatusBadge: translations[key]\n    }\n  })\n}\n\nexport interface IInstanceStatusBadgeProps {\n  status?: number\n}\n\nfunction InstanceStatusBadge({ status }: IInstanceStatusBadgeProps) {\n  const { t } = useTranslation()\n  switch (status) {\n    case InstanceStatus.Down:\n      return (\n        <Badge\n          status=\"error\"\n          text={t('component.instanceStatusBadge.status.down')}\n        />\n      )\n    case InstanceStatus.Unreachable:\n      return (\n        <Badge\n          status=\"error\"\n          text={t('component.instanceStatusBadge.status.unreachable')}\n        />\n      )\n    case InstanceStatus.Up:\n      return (\n        <Badge\n          status=\"success\"\n          text={t('component.instanceStatusBadge.status.up')}\n        />\n      )\n    case InstanceStatus.Tombstone:\n      return (\n        <Badge\n          status=\"default\"\n          text={t('component.instanceStatusBadge.status.tombstone')}\n        />\n      )\n    case InstanceStatus.Offline:\n      return (\n        <Badge\n          status=\"processing\"\n          text={t('component.instanceStatusBadge.status.offline')}\n        />\n      )\n    default:\n      return (\n        <Badge\n          status=\"error\"\n          text={t('component.instanceStatusBadge.status.unknown')}\n        />\n      )\n  }\n}\n\nexport default React.memo(InstanceStatusBadge)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/LanguageDropdown/index.tsx",
    "content": "import { Dropdown, Menu } from 'antd'\nimport _ from 'lodash'\nimport React, { ReactNode } from 'react'\nimport { useTranslation } from 'react-i18next'\n\nimport { ALL_LANGUAGES } from '@lib/utils/i18n'\n\nfunction LanguageDropdown({ children }: { children: ReactNode }) {\n  const { i18n } = useTranslation()\n\n  function handleClick(e) {\n    i18n.changeLanguage(e.key)\n  }\n\n  const menu = (\n    <Menu onClick={handleClick} selectedKeys={[i18n.language]}>\n      {_.map(ALL_LANGUAGES, (name, key) => {\n        return <Menu.Item key={key}>{name}</Menu.Item>\n      })}\n    </Menu>\n  )\n\n  return (\n    <Dropdown overlay={menu} placement=\"bottomRight\">\n      {children}\n    </Dropdown>\n  )\n}\n\nexport default LanguageDropdown\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/LimitTimeRange/index.tsx",
    "content": "import { TimeRange, TimeRangeSelector } from '@lib/components'\nimport dayjs from 'dayjs'\nimport React, { useMemo } from 'react'\n\ninterface LimitTimeRangeProps {\n  value: TimeRange\n  recent_seconds?: number[]\n  customAbsoluteRangePicker?: boolean\n  onChange: (val: TimeRange) => void\n  onZoomOutClick: (start: number, end: number) => void\n  disabled?: boolean\n  // time range limit in seconds\n  timeRangeLimit?: number\n}\n\n// array of 24 numbers, start from 0\nconst hoursRange = [...Array(24).keys()]\nconst minutesRange = [...Array(60).keys()]\n\n// These presets are aligned with Grafana\nconst DEFAULT_RECENT_SECONDS = [\n  5 * 60,\n  15 * 60,\n  30 * 60,\n  60 * 60,\n  3 * 60 * 60,\n  6 * 60 * 60,\n  12 * 60 * 60,\n  24 * 60 * 60,\n  2 * 24 * 60 * 60,\n  7 * 24 * 60 * 60,\n  30 * 24 * 60 * 60,\n  90 * 24 * 60 * 60\n]\n\nexport const LimitTimeRange: React.FC<LimitTimeRangeProps> = ({\n  value,\n  recent_seconds = DEFAULT_RECENT_SECONDS,\n  customAbsoluteRangePicker,\n  onChange,\n  onZoomOutClick,\n  disabled,\n  timeRangeLimit\n}) => {\n  // get the selectable time range value from rencent_seconds\n  const selectableHours = useMemo(() => {\n    return recent_seconds![recent_seconds!.length - 1] / 3600\n  }, [recent_seconds])\n\n  // Use timeRangeLimit if provided, otherwise use selectableHours\n  const timeRangeHours = useMemo(() => {\n    if (timeRangeLimit !== undefined) {\n      return timeRangeLimit / 3600 // Convert seconds to hours\n    }\n    return selectableHours\n  }, [timeRangeLimit, selectableHours])\n\n  const disabledDate = (current) => {\n    const today = dayjs()\n    const todayStartWithHour = today.startOf('hour')\n    const todayStartWithDay = today.startOf('day')\n    const todayEndWithDay = today.endOf('day')\n\n    const curDate = dayjs(current)\n\n    // Can not select days before the earliest allowed date\n    const tooEarly =\n      todayStartWithHour.subtract(timeRangeHours, 'hour') >\n        curDate.startOf('hour') &&\n      todayStartWithDay.subtract(timeRangeHours / 24, 'day') >\n        curDate.startOf('day')\n\n    // Can not select days after today\n    const tooLate =\n      todayStartWithHour < curDate.startOf('hour') &&\n      todayEndWithDay < curDate.endOf('day')\n\n    return current && (tooEarly || tooLate)\n  }\n\n  // control avaliable time on Minute level\n  const disabledTime = (current, type) => {\n    // current hour\n    const today = dayjs()\n    const hour = today.hour()\n    const minute = today.minute()\n\n    // Only apply time restrictions for today\n    // For historical dates, allow all times\n    if (current && current.isSame(today, 'day')) {\n      const curHour = dayjs(current).hour()\n      return {\n        disabledHours: () => hoursRange.slice(hour + 1),\n        disabledMinutes: () =>\n          // if current hour, disable minutes after current minute\n          curHour === hour ? minutesRange.slice(minute + 1) : []\n      }\n    }\n\n    // For the earliest selectable date, restrict times before current time\n    const earliestDate = today.subtract(timeRangeHours / 24, 'day')\n    if (current && current.isSame(earliestDate, 'day')) {\n      const curHour = dayjs(current).hour()\n      return {\n        disabledHours: () => hoursRange.slice(0, hour),\n        disabledMinutes: () =>\n          // if current hour, disable minutes before current minute\n          curHour === hour ? minutesRange.slice(0, minute) : []\n      }\n    }\n\n    // For all other historical dates, allow all times\n    return { disabledHours: () => [] }\n  }\n\n  return (\n    <>\n      {customAbsoluteRangePicker ? (\n        <TimeRangeSelector\n          value={value}\n          onChange={onChange}\n          recent_seconds={recent_seconds}\n          disabledDate={disabledDate}\n          disabledTime={disabledTime}\n          customAbsoluteRangePicker={true}\n          disabled={disabled}\n          selectableHours={selectableHours}\n        />\n      ) : (\n        <TimeRangeSelector.WithZoomOut\n          value={value}\n          onChange={onChange}\n          recent_seconds={recent_seconds}\n          onZoomOutClick={onZoomOutClick}\n          disabled={disabled}\n        />\n      )}\n    </>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/MultiSelect/DropOverlay.tsx",
    "content": "import React, { useState, useMemo } from 'react'\nimport { IColumn, ISelection } from 'office-ui-fabric-react/lib/DetailsList'\nimport { useTranslation } from 'react-i18next'\nimport TableWithFilter, {\n  ITableWithFilterRefProps\n} from '../InstanceSelect/TableWithFilter'\nimport { IItem } from '.'\n\nconst containerProps: React.HTMLAttributes<HTMLDivElement> = {\n  style: { fontSize: '0.9rem' }\n}\n\nexport interface IDropOverlayProps<T> {\n  selection: ISelection\n  columns: IColumn[]\n  items: T[]\n  filterFn?: (keyword: string, item: T) => boolean\n  filterTableRef?: React.Ref<ITableWithFilterRefProps>\n}\n\nfunction DropOverlay<T extends IItem>({\n  selection,\n  columns,\n  items,\n  filterFn,\n  filterTableRef\n}: IDropOverlayProps<T>) {\n  const { t } = useTranslation()\n  const [keyword, setKeyword] = useState('')\n\n  const filteredItems = useMemo(() => {\n    if (keyword.length === 0) {\n      return items\n    }\n    const kw = keyword.toLowerCase()\n    const filter =\n      filterFn == null\n        ? (it: T) =>\n            it.key.toLowerCase().indexOf(kw) > -1 ||\n            (it.label ?? '').toLowerCase().indexOf(kw) > -1\n        : (it: T) => filterFn(keyword, it)\n    return items.filter(filter)\n  }, [items, keyword, filterFn])\n\n  return (\n    <TableWithFilter\n      selection={selection}\n      filterPlaceholder={t('component.multiSelect.filterPlaceholder')}\n      filter={keyword}\n      onFilterChange={setKeyword}\n      tableMaxHeight={300}\n      tableWidth={250}\n      columns={columns}\n      items={filteredItems}\n      containerProps={containerProps}\n      ref={filterTableRef}\n    />\n  )\n}\n\nconst typedMemo: <T>(c: T) => T = React.memo\n\nexport default typedMemo(DropOverlay)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/MultiSelect/Plain.tsx",
    "content": "import MultiSelect, { IMultiSelectProps, IItem } from '.'\nimport { useMemo } from 'react'\nimport React from 'react'\n\nexport interface IPlainMultiSelectProps\n  extends Omit<IMultiSelectProps<IItem>, 'items' | 'filterFn'> {\n  items?: string[]\n}\n\nexport default function PlainMultiSelect({\n  items,\n  ...restProps\n}: IPlainMultiSelectProps) {\n  const objectItems = useMemo(\n    () => items?.map((v) => ({ key: v })) ?? [],\n    [items]\n  )\n  return <MultiSelect items={objectItems} {...restProps} />\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/MultiSelect/index.stories.tsx",
    "content": "import React, { useState } from 'react'\nimport { Pre } from '@lib/components'\n\nimport MultiSelect from '.'\n\nexport default {\n  title: 'Select/Multi Select'\n}\n\nfunction genItems() {\n  const items: any[] = []\n  const items2: any[] = []\n  for (let i = 0; i < 100; i++) {\n    items.push({ key: String(i), label: `Item ${i}` })\n    items2.push({\n      key: String(i),\n      label: `Long Long Long Long Long Long Item ${i}`\n    })\n  }\n  return [items, items2]\n}\nconst [items, items2] = genItems()\n\nconst MultiSelectRegion = () => {\n  const [value, setValue] = useState<string[]>([])\n\n  return (\n    <>\n      <MultiSelect onChange={setValue} items={items2} />\n      <div style={{ marginTop: 12 }}>\n        <Pre>Value = {JSON.stringify(value)}</Pre>\n      </div>\n    </>\n  )\n}\n\nexport const uncontrolled = () => (\n  <MultiSelect placeholder=\"Uncontrolled Value\" items={items} />\n)\n\nexport const controlled = () => <MultiSelectRegion />\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/MultiSelect/index.tsx",
    "content": "import { IBaseSelectProps, BaseSelect, TextWrap } from '..'\nimport { ITableWithFilterRefProps } from '../InstanceSelect/TableWithFilter'\nimport React, { useMemo, useRef, useCallback } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useMemoizedFn, useControllableValue } from 'ahooks'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport SelectionWithFilter from '@lib/utils/selectionWithFilter'\nimport { useShallowCompareEffect } from 'react-use'\nimport { addTranslationResource } from '@lib/utils/i18n'\nimport { Tooltip } from 'antd'\n\nimport DropOverlay from './DropOverlay'\nimport PlainMultiSelect from './Plain'\nimport { useChange } from '@lib/utils/useChange'\n\nconst translations = {\n  en: {\n    filterPlaceholder: 'Filter',\n    selected: '{{n}} selected',\n    columnTitle: 'Items'\n  },\n  zh: {\n    filterPlaceholder: '过滤',\n    selected: '已选择 {{n}} 项',\n    columnTitle: '选择项'\n  }\n}\n\nfor (const key in translations) {\n  addTranslationResource(key, {\n    component: {\n      multiSelect: translations[key]\n    }\n  })\n}\n\nexport interface IItem {\n  key: string\n  label?: string\n}\n\nexport interface IMultiSelectProps<T>\n  extends Omit<IBaseSelectProps<string[]>, 'dropdownRender' | 'valueRender'> {\n  items?: T[]\n  filterFn?: (keyword: string, item: T) => boolean\n  onChange?: (value: string[]) => void\n  selectedValueTransKey?: string\n  columnTitle?: string\n}\n\nfunction MultiSelect<T extends IItem>(props: IMultiSelectProps<T>) {\n  const [internalVal, setInternalVal] = useControllableValue<string[]>(props)\n  const setInternalValPersist = useMemoizedFn(setInternalVal)\n  const {\n    items,\n    filterFn,\n    selectedValueTransKey,\n    columnTitle,\n    placeholder,\n    value, // only to exclude from restProps\n    onChange, // only to exclude from restProps\n    ...restProps\n  } = props\n\n  const { t } = useTranslation()\n\n  const columns: IColumn[] = useMemo(\n    () => [\n      {\n        name: columnTitle ?? t('component.multiSelect.columnTitle'),\n        key: 'name',\n        minWidth: 180,\n        onRender: (node: T) => {\n          let label\n          if ('label' in node) {\n            label = node.label\n          } else {\n            label = node.key\n          }\n          return (\n            <TextWrap data-e2e=\"multi_select_options\">\n              <Tooltip title={label}>\n                <span>{label}</span>\n              </Tooltip>\n            </TextWrap>\n          )\n        }\n      }\n    ],\n    [t, columnTitle]\n  )\n\n  const selection = useRef(\n    new SelectionWithFilter({\n      onSelectionChanged: () => {\n        if (process.env.NODE_ENV === 'development') {\n          console.groupCollapsed(\n            'MultiSelect onSelectionChanged',\n            Math.random()\n          )\n          console.trace()\n          console.groupEnd()\n        }\n        const s = selection.current.getAllSelection() as T[]\n        const keys = s.map((v) => v.key)\n        setInternalValPersist(keys)\n      }\n    })\n  )\n\n  useShallowCompareEffect(() => {\n    selection.current?.resetAllSelection(internalVal ?? [])\n  }, [internalVal])\n\n  useChange(() => {\n    selection.current?.setAllItems(items ?? [])\n    // We may receive value first and then receive items. In this case, we need to re-assign\n    // the selection according to value after receiving new items, so that values in newly appeared\n    // items can be selected.\n    selection.current?.resetAllSelection(internalVal ?? [])\n  }, [items])\n\n  const filterTableRef = useRef<ITableWithFilterRefProps>(null)\n\n  const renderDropdown = useCallback(\n    () => (\n      <DropOverlay<T>\n        columns={columns}\n        items={items ?? []}\n        selection={selection.current}\n        filterFn={filterFn}\n        filterTableRef={filterTableRef}\n      />\n    ),\n    [columns, items, filterFn]\n  )\n\n  const handleOpened = useCallback(() => {\n    filterTableRef.current?.focusFilterInput()\n  }, [])\n\n  const renderValue = useCallback(() => {\n    if (placeholder && (!internalVal || internalVal.length === 0)) {\n      return null\n    }\n    return t(selectedValueTransKey ?? 'component.multiSelect.selected', {\n      n: internalVal?.length ?? 0,\n      count: internalVal?.length ?? 0\n    })\n  }, [t, internalVal, selectedValueTransKey, placeholder])\n\n  return (\n    <BaseSelect\n      dropdownRender={renderDropdown}\n      value={internalVal}\n      valueRender={renderValue}\n      placeholder={placeholder}\n      onOpened={handleOpened}\n      {...restProps}\n    />\n  )\n}\n\nMultiSelect.Plain = PlainMultiSelect\n\nexport default MultiSelect\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Ngm/NgmNotStarted.tsx",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\nimport React, { ReactNode } from 'react'\nimport { Button, Result, Space } from 'antd'\nimport { useTranslation } from 'react-i18next'\n\nimport { Card } from '@lib/components'\nimport { addTranslationResource } from '@lib/utils/i18n'\nimport { isDistro } from '@lib/utils/distro'\nimport { useNgmState, NgmState } from '@lib/utils/store'\n\nconst translations = {\n  en: {\n    title: 'Feature Not Enabled',\n    subTitle:\n      'A required component `NgMonitoring` is not started in this cluster. This feature is not available.',\n    help_text: 'Help',\n    help_url:\n      'https://docs.pingcap.com/tidb/dev/dashboard-faq#a-required-component-ngmonitoring-is-not-started-error-is-shown'\n  },\n  zh: {\n    title: '该功能未启用',\n    subTitle: '集群中未启动必要组件 `NgMonitoring`，本功能不可用。',\n    help_text: '帮助',\n    help_url:\n      'https://docs.pingcap.com/zh/tidb/dev/dashboard-faq#界面提示-集群中未启动必要组件-ngmonitoring'\n  }\n}\n\nfor (const key in translations) {\n  addTranslationResource(key, {\n    component: {\n      ngmNotStarted: translations[key]\n    }\n  })\n}\n\nfunction NgmNotStarted() {\n  const { t } = useTranslation()\n  return (\n    <Card data-e2e=\"ngm_not_started\">\n      <Result\n        title={t('component.ngmNotStarted.title')}\n        subTitle={t('component.ngmNotStarted.subTitle')}\n        extra={\n          <Space>\n            {!isDistro() && (\n              <Button\n                onClick={() => {\n                  window.open(t('component.ngmNotStarted.help_url'), '_blank')\n                }}\n              >\n                {t('component.ngmNotStarted.help_text')}\n              </Button>\n            )}\n          </Space>\n        }\n      />\n    </Card>\n  )\n}\n\nexport function NgmNotStartedGuard({ children }: { children: ReactNode }) {\n  const ngmState = useNgmState()\n  if (React.isValidElement(children)) {\n    return ngmState === NgmState.Started ? children : <NgmNotStarted />\n  }\n  return null\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Ngm/index.ts",
    "content": "export * from './NgmNotStarted'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/ParamsPageWrapper/index.tsx",
    "content": "import React, { ReactNode } from 'react'\nimport { useLocation } from 'react-router-dom'\n\nexport default function ParamsPageWrapper({\n  children\n}: {\n  children: ReactNode\n}) {\n  const { search } = useLocation()\n  if (React.isValidElement(children)) {\n    return React.cloneElement(children, { key: search })\n  }\n  return null\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/PlanText/index.tsx",
    "content": "import React, { useMemo } from 'react'\nimport { CopyLink, TxtDownloadLink, Pre } from '@lib/components'\n\ntype BinaryPlanTextProps = {\n  data: string\n  downloadFileName: string\n}\n\n// mysql> select tidb_decode_binary_plan(\"AgQgAQ==\");\n// +-------------------------------------+\n// | tidb_decode_binary_plan(\"AgQgAQ==\") |\n// +-------------------------------------+\n// | (plan discarded because too long)   |\n// +-------------------------------------+\n// 1 row in set (0.00 sec)\n\nconst DISCARDED_TOO_LONG = 'plan discarded because too long'\n\nconst MAX_SHOW_LEN = 500 * 1024 // 500KB\n\nexport const PlanText: React.FC<BinaryPlanTextProps> = ({\n  data,\n  downloadFileName\n}) => {\n  const discardedDueToTooLong = useMemo(() => {\n    return data\n      .slice(0, DISCARDED_TOO_LONG.length + 10)\n      .includes(DISCARDED_TOO_LONG)\n  }, [data])\n\n  const truncatedStr = useMemo(() => {\n    let str = data\n    if (str.length > MAX_SHOW_LEN) {\n      str =\n        str.slice(0, MAX_SHOW_LEN) +\n        '\\n...(too long to show, copy or download to analyze)'\n    }\n    // binary_plan_text field starts with '\\n' which will show an extra empty line\n    // plan field starts with `\\t`\n    if (str.startsWith('\\n')) {\n      // remove the first empty line\n      str = str.slice(1)\n    }\n    return str\n  }, [data])\n\n  if (discardedDueToTooLong) {\n    return <div>{data}</div>\n  }\n  return (\n    <>\n      <div style={{ display: 'flex', gap: 16 }}>\n        <CopyLink data={data} />\n        <TxtDownloadLink data={data} fileName={downloadFileName} />\n      </div>\n      <Pre noWrap style={{ paddingBlock: 8 }}>\n        {truncatedStr}\n      </Pre>\n    </>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Pre/index.module.less",
    "content": "@font-face {\n  font-family: 'JetBrains Mono NL';\n  src: local('JetBrains Mono NL'),\n    data-uri('./JetBrainsMonoNL-Regular.woff') format('woff'),\n    local('JetBrains Mono');\n  font-weight: normal;\n  font-style: normal;\n}\n\n.font-mixin() {\n  -webkit-font-smoothing: antialiased;\n  font-family: 'JetBrains Mono NL', Menlo, Monaco, Consolas, 'Lucida Console',\n    'Courier New', monospace;\n  font-feature-settings: 'liga' 0;\n  font-variant-ligatures: none;\n}\n\n.pre {\n  white-space: pre-wrap;\n  word-wrap: break-word;\n  margin: 0;\n\n  .font-mixin();\n  pre,\n  code {\n    .font-mixin();\n  }\n}\n\n.preNoWrap {\n  white-space: pre;\n  word-wrap: normal;\n  overflow-x: auto;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Pre/index.tsx",
    "content": "import React from 'react'\nimport cx from 'classnames'\nimport styles from './index.module.less'\n\nexport interface IPreProps extends React.HTMLAttributes<HTMLPreElement> {\n  noWrap?: boolean\n}\n\nexport default function Pre({\n  noWrap,\n  className,\n  children,\n  ...rest\n}: IPreProps) {\n  return (\n    <pre\n      className={cx(styles.pre, className, { [styles.preNoWrap]: noWrap })}\n      {...rest}\n    >\n      {children}\n    </pre>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Root/index.tsx",
    "content": "import React from 'react'\nimport {\n  ArrowUpOutlined,\n  ArrowDownOutlined,\n  DownOutlined,\n  RightOutlined\n} from '@ant-design/icons'\nimport { createTheme, registerIcons } from 'office-ui-fabric-react/lib/Styling'\nimport { Customizations } from 'office-ui-fabric-react/lib/Utilities'\n\nimport { ConfigProvider } from 'antd'\nimport i18next from 'i18next'\nimport enUS from 'antd/es/locale/en_US'\nimport zhCN from 'antd/es/locale/zh_CN'\n\nregisterIcons({\n  icons: {\n    SortUp: <ArrowUpOutlined />,\n    SortDown: <ArrowDownOutlined />,\n    chevronrightmed: <RightOutlined />,\n    tag: <DownOutlined />\n  }\n})\n\nconst theme = createTheme({\n  defaultFontStyle: { fontFamily: 'inherit', fontSize: '1em' }\n})\n\nCustomizations.applySettings({ theme })\n\nexport default function Root({ children }) {\n  return (\n    <ConfigProvider locale={i18next.language === 'en' ? enUS : zhCN}>\n      {children}\n    </ConfigProvider>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/TextWithInfo/index.tsx",
    "content": "import React from 'react'\nimport { Tooltip, Typography } from 'antd'\nimport type { TooltipPlacement } from 'antd/es/tooltip'\nimport { InfoCircleOutlined, WarningOutlined } from '@ant-design/icons'\nimport { useTranslation } from 'react-i18next'\n\nexport interface ITextWithInfoProps {\n  tooltip?: React.ReactNode\n  placement?: TooltipPlacement\n  children: React.ReactNode\n  type?: 'warning' | 'danger'\n}\n\nfunction TextWithInfo({\n  tooltip,\n  placement,\n  children,\n  type\n}: ITextWithInfoProps) {\n  let textWithIcon\n  if (tooltip) {\n    const Icon = type ? WarningOutlined : InfoCircleOutlined\n    textWithIcon = (\n      <span>\n        {children} <Icon />\n      </span>\n    )\n  } else {\n    textWithIcon = children\n  }\n\n  let textWithColor\n  if (type) {\n    textWithColor = (\n      <Typography.Text type={type}>{textWithIcon}</Typography.Text>\n    )\n  } else {\n    textWithColor = textWithIcon\n  }\n\n  if (!tooltip) {\n    return textWithColor\n  }\n\n  return (\n    <Tooltip title={tooltip} placement={placement}>\n      {textWithColor}\n    </Tooltip>\n  )\n}\n\nexport interface ITransKeyTextWithInfo {\n  transKey: string\n  placement?: TooltipPlacement\n  type?: 'warning' | 'danger'\n}\n\nfunction TransKey({ transKey, placement, type }: ITransKeyTextWithInfo) {\n  const { t } = useTranslation()\n  const text = t(transKey)\n  const tooltip = t(`${transKey}_tooltip`, {\n    defaultValue: '',\n    fallbackLng: '_'\n  })\n  return (\n    <TextWithInfo tooltip={tooltip} placement={placement} type={type}>\n      {text}\n    </TextWithInfo>\n  )\n}\n\nTextWithInfo.TransKey = TransKey\n\nexport default TextWithInfo\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/TextWrap/index.module.less",
    "content": ".singleLine {\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  > span {\n    display: inline;\n  }\n  pre {\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n  }\n}\n\n.multiLine {\n  overflow-wrap: break-word;\n  white-space: normal;\n  text-overflow: inherit;\n  overflow: auto;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/TextWrap/index.tsx",
    "content": "import React from 'react'\nimport cx from 'classnames'\n\nimport styles from './index.module.less'\n\nexport interface ITextWrapProps extends React.HTMLAttributes<HTMLDivElement> {\n  // When multiline enabled, text will be wrapped. When multiline disabled,\n  // overflow texts will be truncated with ellipsis.\n  multiline?: boolean\n}\n\nexport default function TextWrap({\n  multiline,\n  className,\n  children,\n  ...rest\n}: ITextWrapProps) {\n  const c = cx(className, {\n    [styles.multiLine]: multiline,\n    [styles.singleLine]: !multiline\n  })\n  return (\n    <div className={c} {...rest}>\n      {children}\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/TimePicker/index.tsx",
    "content": "import { Dayjs } from 'dayjs'\nimport * as React from 'react'\nimport DatePicker from '../DatePicker'\nimport { PickerTimeProps } from 'antd/es/date-picker/generatePicker'\n\nexport interface TimePickerProps\n  extends Omit<PickerTimeProps<Dayjs>, 'picker'> {}\n\nconst TimePicker = React.forwardRef<any, TimePickerProps>((props, ref) => {\n  return <DatePicker {...props} picker=\"time\" mode={undefined} ref={ref} />\n})\n\nTimePicker.displayName = 'TimePicker'\n\nexport default TimePicker\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/TimeRangeSelector/WithZoomOut.tsx",
    "content": "import { ZoomOutOutlined } from '@ant-design/icons'\nimport { useMemoizedFn } from 'ahooks'\nimport { Button } from 'antd'\nimport React from 'react'\nimport TimeRangeSelector, {\n  fromTimeRangeValue,\n  toTimeRangeValue,\n  ITimeRangeSelectorProps\n} from '.'\n\nexport interface ITimeRangeSelectorWithZoomOutProps\n  extends ITimeRangeSelectorProps {\n  zoomOutRate?: number\n  minRange?: number\n  onZoomOutClick?: (start: number, end: number) => void\n}\n\nexport function WithZoomOut({\n  zoomOutRate = 0.5,\n  minRange = 5 * 60,\n  onZoomOutClick,\n  ...rest\n}: ITimeRangeSelectorWithZoomOutProps) {\n  const handleZoomOut = useMemoizedFn(() => {\n    if (!rest.onChange) {\n      return\n    }\n    const [start, end] = toTimeRangeValue(rest.value)\n    let expand = (end - start) * zoomOutRate\n    if (expand < minRange) {\n      expand = minRange\n    }\n\n    let computedStart = start - expand\n    let computedEnd = end + expand\n    const newRange = fromTimeRangeValue([computedStart, computedEnd])\n    onZoomOutClick!(computedStart, computedEnd)\n    rest.onChange?.(newRange)\n  })\n\n  return (\n    <Button.Group>\n      <Button\n        icon={<ZoomOutOutlined />}\n        onClick={handleZoomOut}\n        disabled={rest.disabled}\n      />\n      <TimeRangeSelector {...rest} />\n    </Button.Group>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/TimeRangeSelector/hook.tsx",
    "content": "import { useCallback, useMemo } from 'react'\nimport {\n  toTimeRangeValue,\n  fromTimeRangeValue,\n  TimeRange,\n  TimeRangeValue\n} from '.'\n\nexport function useTimeRangeValue(\n  timeRange: TimeRange,\n  setTimeRange: (TimeRange) => void\n): [TimeRangeValue, (r: TimeRangeValue) => void] {\n  const value = useMemo(() => toTimeRangeValue(timeRange), [timeRange])\n  const setValue = useCallback(\n    (r: TimeRangeValue) => {\n      setTimeRange(fromTimeRangeValue(r))\n    },\n    [setTimeRange]\n  )\n  return [value, setValue]\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/TimeRangeSelector/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.dropdown_content_container {\n  background-color: @select-dropdown-bg;\n  padding: @padding-md;\n  border-radius: @border-radius-base;\n  outline: none;\n  box-shadow: @box-shadow-base;\n  box-sizing: border-box;\n\n  .usual_time_ranges {\n    span {\n      color: @gray-6;\n    }\n\n    margin-bottom: 16px;\n  }\n\n  .custom_time_ranges {\n    & > span {\n      color: @gray-6;\n    }\n  }\n\n  .time_range_items {\n    max-width: 360px;\n    margin-top: 8px;\n    display: flex;\n    flex-wrap: wrap;\n  }\n\n  .time_range_item {\n    width: 120px;\n    padding-bottom: 4px;\n    cursor: pointer;\n  }\n\n  .time_range_item_active {\n    color: @primary-color;\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/TimeRangeSelector/index.tsx",
    "content": "import React, { useState, useMemo, useEffect } from 'react'\nimport { Dropdown, Button, TimePicker } from 'antd'\nimport { ClockCircleOutlined, DownOutlined } from '@ant-design/icons'\nimport {\n  getValueFormat,\n  toFixedScaled,\n  toFixed,\n  DecimalCount\n} from '@baurine/grafana-value-formats'\nimport cx from 'classnames'\nimport dayjs, { Dayjs } from 'dayjs'\nimport { useTranslation } from 'react-i18next'\nimport { RangePickerProps } from 'antd/es/date-picker/generatePicker'\n\nimport styles from './index.module.less'\nimport { useChange } from '@lib/utils/useChange'\nimport { useMemoizedFn } from 'ahooks'\nimport { WithZoomOut } from './WithZoomOut'\nimport { tz } from '@lib/utils'\nimport { PickerComponentClass } from 'antd/lib/date-picker/generatePicker/interface'\nimport DatePicker from '@lib/components/DatePicker'\n// import TimePicker from '@lib/components/TimePicker'\n\nconst { RangePicker: RangePickerAntd } = DatePicker\nconst RangePicker: PickerComponentClass<\n  RangePickerProps<Dayjs>,\n  unknown\n> = RangePickerAntd as any\n\n// These presets are aligned with Grafana\nconst DEFAULT_RECENT_SECONDS = [\n  5 * 60,\n  15 * 60,\n  30 * 60,\n  60 * 60,\n  3 * 60 * 60,\n  6 * 60 * 60,\n  12 * 60 * 60,\n  24 * 60 * 60,\n  2 * 24 * 60 * 60,\n  7 * 24 * 60 * 60,\n  30 * 24 * 60 * 60,\n  90 * 24 * 60 * 60\n]\n\nexport const DEFAULT_TIME_RANGE: TimeRange = {\n  type: 'recent',\n  value: 30 * 60\n}\n\nexport interface RelativeTimeRange {\n  type: 'recent'\n  value: number // unit: seconds\n}\n\nexport interface AbsoluteTimeRange {\n  type: 'absolute'\n  value: TimeRangeValue // unit: seconds\n}\n\nexport type TimeRangeValue = [minSecond: number, maxSecond: number]\n\nexport type TimeRange = RelativeTimeRange | AbsoluteTimeRange\n\nexport function toTimeRangeValue(\n  timeRange?: TimeRange,\n  offset = 0\n): TimeRangeValue {\n  let t2 = timeRange ?? DEFAULT_TIME_RANGE\n  if (t2.type === 'absolute') {\n    return t2.value.map((t) => t + offset) as TimeRangeValue\n  } else {\n    const now = dayjs().unix()\n    return [now - t2.value + offset, now + 1 + offset]\n  }\n}\n\nexport function fromTimeRangeValue(v: TimeRangeValue): AbsoluteTimeRange {\n  return {\n    type: 'absolute',\n    value: [...v]\n  }\n}\n\n//////////////////////\n\nexport type URLTimeRange = { from: string; to: string }\n\nexport const toURLTimeRange = (timeRange: TimeRange): URLTimeRange => {\n  if (timeRange.type === 'recent') {\n    return { from: `${timeRange.value}`, to: 'now' }\n  }\n\n  const timeRangeValue = toTimeRangeValue(timeRange)\n  return { from: `${timeRangeValue[0]}`, to: `${timeRangeValue[1]}` }\n}\n\nexport const urlToTimeRange = (urlTimeRange: URLTimeRange): TimeRange => {\n  if (urlTimeRange.to === 'now') {\n    return { type: 'recent', value: Number(urlTimeRange.from) }\n  }\n  return {\n    type: 'absolute',\n    value: [Number(urlTimeRange.from), Number(urlTimeRange.to)]\n  }\n}\n\nexport const urlToTimeRangeValue = (\n  urlTimeRange: URLTimeRange\n): TimeRangeValue => {\n  return toTimeRangeValue(urlToTimeRange(urlTimeRange))\n}\n\n//////////////////////\n\n/**\n * @deprecated\n */\n// TODO: Compatibility alias. To be removed.\nexport function calcTimeRange(timeRange?: TimeRange): TimeRangeValue {\n  return toTimeRangeValue(timeRange)\n}\n\n/**\n * @deprecated\n */\n// TODO: JSON.stringify() is enough. To be removed.\nexport function stringifyTimeRange(timeRange?: TimeRange): string {\n  let t2 = timeRange ?? DEFAULT_TIME_RANGE\n  if (t2.type === 'absolute') {\n    return `${t2.type}_${t2.value[0]}_${t2.value[1]}`\n  } else {\n    return `${t2.type}_${t2.value}`\n  }\n}\n\nexport interface ITimeRangeSelectorProps {\n  value?: TimeRange\n  onChange?: (val: TimeRange) => void\n  disabled?: boolean\n  recent_seconds?: number[]\n  disabledDate?: RangePickerProps<dayjs.Dayjs>['disabledDate']\n  disabledTime?: RangePickerProps<dayjs.Dayjs>['disabledTime']\n  onCalendarChange?: RangePickerProps<dayjs.Dayjs>['onCalendarChange']\n  onOpenChange?: RangePickerProps<dayjs.Dayjs>['onOpenChange']\n  customAbsoluteRangePicker?: boolean\n  selectableHours?: number\n}\n\nconst trySubstract = (value1, value2) => {\n  if (\n    value1 !== null &&\n    value1 !== undefined &&\n    value2 !== null &&\n    value2 !== undefined\n  ) {\n    return value1 - value2\n  }\n  return undefined\n}\n\nconst customValueFormat = (\n  size: number,\n  decimals?: DecimalCount,\n  scaledDecimals?: DecimalCount\n) => {\n  if (size === null) {\n    return ''\n  }\n  // Less than 1 µs, divide in ns\n  if (Math.abs(size) < 0.000001) {\n    return toFixedScaled(\n      size * 1e9,\n      decimals,\n      trySubstract(scaledDecimals, decimals),\n      -9,\n      ' ns'\n    )\n  }\n  // Less than 1 ms, divide in µs\n  if (Math.abs(size) < 0.001) {\n    return toFixedScaled(\n      size * 1e6,\n      decimals,\n      trySubstract(scaledDecimals, decimals),\n      -6,\n      ' µs'\n    )\n  }\n  // Less than 1 second, divide in ms\n  if (Math.abs(size) < 1) {\n    return toFixedScaled(\n      size * 1e3,\n      decimals,\n      trySubstract(scaledDecimals, decimals),\n      -3,\n      ' ms'\n    )\n  }\n\n  if (Math.abs(size) < 60) {\n    return toFixed(size, decimals) + ' s'\n  } else if (Math.abs(size) < 3600) {\n    // Less than 1 hour, divide in minutes\n    return toFixedScaled(size / 60, decimals, scaledDecimals, 1, ' min')\n  } else if (Math.abs(size) < 86400) {\n    // Less than one day, divide in hours\n    return toFixedScaled(size / 3600, decimals, scaledDecimals, 4, ' hour')\n  } else {\n    // Less than one week, divide in days\n    return toFixedScaled(size / 86400, decimals, scaledDecimals, 5, ' day')\n  }\n}\n\nfunction TimeRangeSelector({\n  value,\n  onChange,\n  disabled = false,\n  recent_seconds = DEFAULT_RECENT_SECONDS,\n  disabledDate,\n  disabledTime,\n  onCalendarChange,\n  onOpenChange,\n  customAbsoluteRangePicker = false,\n  selectableHours\n}: ITimeRangeSelectorProps) {\n  const { t } = useTranslation()\n  const [dropdownVisible, setDropdownVisible] = useState(false)\n  const [rangeError, setRangeError] = useState<string | null>(null)\n\n  useChange(() => {\n    if (!value) {\n      onChange?.(DEFAULT_TIME_RANGE)\n    }\n  }, [value])\n\n  const rangePickerValue = useMemo(() => {\n    if (value?.type !== 'absolute') {\n      return null\n    }\n    return value.value.map((sec) => dayjs(sec * 1000)) as [Dayjs, Dayjs]\n  }, [value])\n\n  // Combined state for From and To date/time pickers\n  const [fromDateTime, setFromDateTime] = useState<Dayjs | null>(null)\n  const [toDateTime, setToDateTime] = useState<Dayjs | null>(null)\n\n  // Initialize from/to values when value changes\n  useEffect(() => {\n    if (value?.type === 'absolute' && rangePickerValue) {\n      const [from, to] = rangePickerValue\n      setFromDateTime(from)\n      setToDateTime(to)\n    } else {\n      setFromDateTime(null)\n      setToDateTime(null)\n    }\n  }, [value, rangePickerValue])\n\n  // Trigger onCalendarChange whenever fromDateTime or toDateTime changes\n  useEffect(() => {\n    if (onCalendarChange) {\n      const rangeValue: [Dayjs | null, Dayjs | null] | null =\n        fromDateTime !== null || toDateTime !== null\n          ? [fromDateTime, toDateTime]\n          : null\n      onCalendarChange(\n        rangeValue,\n        [\n          fromDateTime?.format('YYYY-MM-DD HH:mm:ss') || '',\n          toDateTime?.format('YYYY-MM-DD HH:mm:ss') || ''\n        ],\n        {} as any\n      )\n    }\n  }, [fromDateTime, toDateTime, onCalendarChange])\n\n  // Validate time range: check from > to, selectableHours, disabledDate, and disabledTime\n  useEffect(() => {\n    if (fromDateTime && toDateTime) {\n      // First check: from cannot be greater than to\n      if (fromDateTime.isAfter(toDateTime)) {\n        setRangeError('From time cannot be greater than To time.')\n        return\n      }\n\n      // Second check: time range cannot exceed selectableHours\n      if (selectableHours) {\n        const diffInHours = toDateTime.diff(fromDateTime, 'hour')\n        if (diffInHours > selectableHours) {\n          setRangeError(\n            `Time range cannot exceed ${selectableHours} hours. Current range: ${diffInHours.toFixed(\n              2\n            )} hours.`\n          )\n          return\n        }\n      }\n\n      // Third check: validate disabledDate for fromDateTime\n      if (disabledDate && disabledDate(fromDateTime)) {\n        setRangeError('Selected start date is not allowed.')\n        return\n      }\n\n      // Fourth check: validate disabledDate for toDateTime\n      if (disabledDate && disabledDate(toDateTime)) {\n        setRangeError('Selected end date is not allowed.')\n        return\n      }\n\n      // Fifth check: validate disabledTime for fromDateTime\n      if (disabledTime) {\n        const disabledTimeResult = disabledTime(fromDateTime, 'start')\n        if (disabledTimeResult) {\n          const fromHour = fromDateTime.hour()\n          const fromMinute = fromDateTime.minute()\n          const fromSecond = fromDateTime.second()\n\n          const disabledHours = disabledTimeResult.disabledHours?.() || []\n          const disabledMinutes =\n            disabledTimeResult.disabledMinutes?.(fromHour) || []\n          const disabledSeconds =\n            disabledTimeResult.disabledSeconds?.(fromHour, fromMinute) || []\n\n          if (\n            disabledHours.includes(fromHour) ||\n            disabledMinutes.includes(fromMinute) ||\n            disabledSeconds.includes(fromSecond)\n          ) {\n            setRangeError('Selected start time is not allowed.')\n            return\n          }\n        }\n      }\n\n      // Sixth check: validate disabledTime for toDateTime\n      if (disabledTime) {\n        const disabledTimeResult = disabledTime(toDateTime, 'end')\n        if (disabledTimeResult) {\n          const toHour = toDateTime.hour()\n          const toMinute = toDateTime.minute()\n          const toSecond = toDateTime.second()\n\n          const disabledHours = disabledTimeResult.disabledHours?.() || []\n          const disabledMinutes =\n            disabledTimeResult.disabledMinutes?.(toHour) || []\n          const disabledSeconds =\n            disabledTimeResult.disabledSeconds?.(toHour, toMinute) || []\n\n          if (\n            disabledHours.includes(toHour) ||\n            disabledMinutes.includes(toMinute) ||\n            disabledSeconds.includes(toSecond)\n          ) {\n            setRangeError('Selected end time is not allowed.')\n            return\n          }\n        }\n      }\n\n      // No errors\n      setRangeError(null)\n    } else {\n      setRangeError(null)\n    }\n  }, [fromDateTime, toDateTime, selectableHours, disabledDate, disabledTime])\n\n  // Handle fromDate change: update fromDateTime with new date, keeping time\n  const handleFromDateChange = useMemoizedFn((date: Dayjs | null) => {\n    if (date && fromDateTime) {\n      // Keep the time part from fromDateTime, but apply it to the new date\n      const newFromDateTime = date\n        .hour(fromDateTime.hour())\n        .minute(fromDateTime.minute())\n        .second(fromDateTime.second())\n        .millisecond(fromDateTime.millisecond())\n      setFromDateTime(newFromDateTime)\n\n      // If toDateTime exists, check if we need to adjust\n      if (toDateTime && newFromDateTime.isAfter(toDateTime)) {\n        // Set toDateTime to be slightly after fromDateTime\n        setToDateTime(newFromDateTime.add(1, 'second'))\n      }\n    } else if (date) {\n      // If date is set but no fromDateTime, set default time (00:00:00)\n      setFromDateTime(date.startOf('day'))\n    } else {\n      // If date is cleared, clear fromDateTime\n      setFromDateTime(null)\n    }\n  })\n\n  // Handle fromTime change: update fromDateTime with new time, keeping date\n  const handleFromTimeChange = useMemoizedFn((time: Dayjs | null) => {\n    if (time && fromDateTime) {\n      // Keep the date part from fromDateTime, but apply new time\n      const newFromDateTime = fromDateTime\n        .hour(time.hour())\n        .minute(time.minute())\n        .second(time.second())\n        .millisecond(time.millisecond())\n      setFromDateTime(newFromDateTime)\n\n      // If toDateTime exists, check if we need to adjust\n      if (toDateTime && newFromDateTime.isAfter(toDateTime)) {\n        // Set toDateTime to be slightly after fromDateTime\n        setToDateTime(newFromDateTime.add(1, 'second'))\n      }\n    } else if (time) {\n      // If time is set but no fromDateTime, use today's date\n      const today = dayjs().startOf('day')\n      setFromDateTime(\n        today\n          .hour(time.hour())\n          .minute(time.minute())\n          .second(time.second())\n          .millisecond(time.millisecond())\n      )\n    } else {\n      // If time is cleared, clear fromDateTime\n      setFromDateTime(null)\n    }\n  })\n\n  // Handle toDate change: update toDateTime with new date, keeping time\n  const handleToDateChange = useMemoizedFn((date: Dayjs | null) => {\n    if (date && toDateTime) {\n      // Keep the time part from toDateTime, but apply it to the new date\n      const newToDateTime = date\n        .hour(toDateTime.hour())\n        .minute(toDateTime.minute())\n        .second(toDateTime.second())\n        .millisecond(toDateTime.millisecond())\n      setToDateTime(newToDateTime)\n\n      // If fromDateTime exists, check if we need to adjust\n      if (fromDateTime && fromDateTime.isAfter(newToDateTime)) {\n        // Set fromDateTime to be slightly before toDateTime\n        setFromDateTime(newToDateTime.subtract(1, 'second'))\n      }\n    } else if (date) {\n      // If date is set but no toDateTime, set default time (23:59:59)\n      setToDateTime(date.startOf('day'))\n    } else {\n      // If date is cleared, clear toDateTime\n      setToDateTime(null)\n    }\n  })\n\n  // Handle toTime change: update toDateTime with new time, keeping date\n  const handleToTimeChange = useMemoizedFn((time: Dayjs | null) => {\n    if (time && toDateTime) {\n      // Keep the date part from toDateTime, but apply new time\n      const newToDateTime = toDateTime\n        .hour(time.hour())\n        .minute(time.minute())\n        .second(time.second())\n        .millisecond(time.millisecond())\n      setToDateTime(newToDateTime)\n\n      // If fromDateTime exists, check if we need to adjust\n      if (fromDateTime && fromDateTime.isAfter(newToDateTime)) {\n        // Set fromDateTime to be slightly before toDateTime\n        setFromDateTime(newToDateTime.subtract(1, 'second'))\n      }\n    } else if (time) {\n      // If time is set but no toDateTime, use today's date\n      const today = dayjs().startOf('day')\n      setToDateTime(\n        today\n          .hour(time.hour())\n          .minute(time.minute())\n          .second(time.second())\n          .millisecond(time.millisecond())\n      )\n    } else {\n      // If time is cleared, clear toDateTime\n      setToDateTime(null)\n    }\n  })\n\n  const handleRecentChange = useMemoizedFn((seconds: number) => {\n    onChange?.({\n      type: 'recent',\n      value: seconds\n    })\n    setDropdownVisible(false)\n  })\n\n  const handleOk = useMemoizedFn(() => {\n    if (fromDateTime && toDateTime) {\n      // Validate: from cannot be greater than to\n      if (fromDateTime.isAfter(toDateTime)) {\n        // If from > to, prevent the change\n        return\n      }\n\n      // Validate: time range cannot exceed selectableHours\n      if (selectableHours) {\n        const diffInHours = toDateTime.diff(fromDateTime, 'hour', true)\n        if (diffInHours > selectableHours) {\n          // Prevent submission if range exceeds selectableHours\n          return\n        }\n      }\n\n      // Validate: disabledDate for fromDateTime\n      if (disabledDate && disabledDate(fromDateTime)) {\n        return\n      }\n\n      // Validate: disabledDate for toDateTime\n      if (disabledDate && disabledDate(toDateTime)) {\n        return\n      }\n\n      // Validate: disabledTime for fromDateTime\n      if (disabledTime) {\n        const disabledTimeResult = disabledTime(fromDateTime, 'start')\n        if (disabledTimeResult) {\n          const fromHour = fromDateTime.hour()\n          const fromMinute = fromDateTime.minute()\n          const fromSecond = fromDateTime.second()\n\n          const disabledHours = disabledTimeResult.disabledHours?.() || []\n          const disabledMinutes =\n            disabledTimeResult.disabledMinutes?.(fromHour) || []\n          const disabledSeconds =\n            disabledTimeResult.disabledSeconds?.(fromHour, fromMinute) || []\n\n          if (\n            disabledHours.includes(fromHour) ||\n            disabledMinutes.includes(fromMinute) ||\n            disabledSeconds.includes(fromSecond)\n          ) {\n            return\n          }\n        }\n      }\n\n      // Validate: disabledTime for toDateTime\n      if (disabledTime) {\n        const disabledTimeResult = disabledTime(toDateTime, 'end')\n        if (disabledTimeResult) {\n          const toHour = toDateTime.hour()\n          const toMinute = toDateTime.minute()\n          const toSecond = toDateTime.second()\n\n          const disabledHours = disabledTimeResult.disabledHours?.() || []\n          const disabledMinutes =\n            disabledTimeResult.disabledMinutes?.(toHour) || []\n          const disabledSeconds =\n            disabledTimeResult.disabledSeconds?.(toHour, toMinute) || []\n\n          if (\n            disabledHours.includes(toHour) ||\n            disabledMinutes.includes(toMinute) ||\n            disabledSeconds.includes(toSecond)\n          ) {\n            return\n          }\n        }\n      }\n\n      onChange?.({\n        type: 'absolute',\n        value: [fromDateTime.unix(), toDateTime.unix()]\n      })\n      setDropdownVisible(false)\n      onOpenChange?.(false)\n    }\n  })\n\n  // Pass through external disabledDate to DatePicker (no additional restrictions)\n  const getDisabledDateForFrom = useMemoizedFn((current: Dayjs) => {\n    // Only apply external disabledDate if provided\n    if (disabledDate) {\n      return disabledDate(current)\n    }\n    return false\n  })\n\n  // Pass through external disabledDate to DatePicker (no additional restrictions)\n  const getDisabledDateForTo = useMemoizedFn((current: Dayjs) => {\n    // Only apply external disabledDate if provided\n    if (disabledDate) {\n      return disabledDate(current)\n    }\n    return false\n  })\n\n  // Pass through external disabledTime to TimePicker (no additional restrictions)\n  const getDisabledTimeForPicker = useMemoizedFn((type: 'start' | 'end') => {\n    return () => {\n      const date = type === 'start' ? fromDateTime : toDateTime\n      const result: {\n        disabledHours?: () => number[]\n        disabledMinutes?: (selectedHour: number) => number[]\n        disabledSeconds?: (\n          selectedHour: number,\n          selectedMinute: number\n        ) => number[]\n      } = {}\n\n      // Apply external disabledTime if provided and date exists\n      if (disabledTime && date) {\n        const originalResult = disabledTime(date, type)\n        if (originalResult) {\n          Object.assign(result, originalResult)\n        }\n      }\n\n      // Always return an object, even if empty\n      return result\n    }\n  })\n\n  const dropdownContent = (\n    <div\n      className={styles.dropdown_content_container}\n      data-e2e=\"timerange_selector_dropdown\"\n    >\n      <div className={styles.usual_time_ranges}>\n        <span>\n          {t(\n            'statement.pages.overview.toolbar.time_range_selector.usual_time_ranges'\n          )}\n        </span>\n        <div className={styles.time_range_items} data-e2e=\"common-timeranges\">\n          {recent_seconds.map((seconds) => (\n            <div\n              tabIndex={-1}\n              key={seconds}\n              className={cx(styles.time_range_item, {\n                [styles.time_range_item_active]:\n                  value && value.type === 'recent' && value.value === seconds\n              })}\n              onClick={() => handleRecentChange(seconds)}\n              data-e2e={`timerange-${seconds}`}\n            >\n              {t('statement.pages.overview.toolbar.time_range_selector.recent')}{' '}\n              {customAbsoluteRangePicker\n                ? customValueFormat(seconds, 0)\n                : getValueFormat('s')(seconds, 0)}\n            </div>\n          ))}\n        </div>\n      </div>\n      <div className={styles.custom_time_ranges}>\n        <span>\n          {t(\n            'statement.pages.overview.toolbar.time_range_selector.custom_time_ranges'\n          )}\n        </span>\n        <div style={{ marginTop: 8 }}>\n          <div style={{ marginBottom: 12 }}>\n            <div style={{ marginBottom: 8, fontWeight: 500 }}>From:</div>\n            <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>\n              <DatePicker\n                value={fromDateTime}\n                onChange={handleFromDateChange}\n                format=\"YYYY-MM-DD\"\n                disabledDate={getDisabledDateForFrom}\n                style={{ flex: 1 }}\n              />\n              <TimePicker\n                value={fromDateTime as any}\n                onChange={handleFromTimeChange as any}\n                format=\"HH:mm:ss\"\n                disabledTime={getDisabledTimeForPicker('start')}\n                style={{ flex: 1 }}\n              />\n            </div>\n          </div>\n          <div style={{ marginBottom: 12 }}>\n            <div style={{ marginBottom: 8, fontWeight: 500 }}>To:</div>\n            <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>\n              <DatePicker\n                value={toDateTime}\n                onChange={handleToDateChange}\n                format=\"YYYY-MM-DD\"\n                disabledDate={getDisabledDateForTo}\n                style={{ flex: 1 }}\n              />\n              <TimePicker\n                value={toDateTime as any}\n                onChange={handleToTimeChange as any}\n                format=\"HH:mm:ss\"\n                disabledTime={getDisabledTimeForPicker('end')}\n                style={{ flex: 1 }}\n              />\n            </div>\n          </div>\n          <div\n            style={{\n              display: 'flex',\n              flexDirection: 'column',\n              alignItems: 'flex-end',\n              marginTop: 12\n            }}\n          >\n            <Button type=\"primary\" onClick={handleOk} disabled={!!rangeError}>\n              Ok\n            </Button>\n            {rangeError && (\n              <div\n                style={{\n                  color: '#ff4d4f',\n                  fontSize: '12px',\n                  marginTop: 8,\n                  textAlign: 'right'\n                }}\n              >\n                {rangeError}\n              </div>\n            )}\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n\n  return (\n    <Dropdown\n      overlay={dropdownContent}\n      trigger={['click']}\n      visible={dropdownVisible}\n      onVisibleChange={setDropdownVisible}\n      disabled={disabled}\n    >\n      <Button icon={<ClockCircleOutlined />} data-e2e=\"timerange-selector\">\n        {value && value.type === 'recent' && (\n          <span data-e2e=\"selected_timerange\">\n            {t('statement.pages.overview.toolbar.time_range_selector.recent')}{' '}\n            {customAbsoluteRangePicker\n              ? customValueFormat(value.value, 0)\n              : getValueFormat('s')(value.value, 0)}\n          </span>\n        )}\n        {value && value.type === 'absolute' && (\n          <span data-e2e=\"selected_timerange\">\n            {value.value\n              .map((v) =>\n                dayjs\n                  .unix(v)\n                  .utcOffset(tz.getTimeZone())\n                  .format('MM-DD HH:mm:ss (UTCZ)')\n              )\n              .join(' ~ ')}\n          </span>\n        )}\n        {!value && 'Select Time'}\n        <DownOutlined />\n      </Button>\n    </Dropdown>\n  )\n}\n\nconst c = Object.assign(React.memo(TimeRangeSelector), {\n  WithZoomOut: React.memo(WithZoomOut)\n})\n\nexport default c\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Toolbar/index.module.less",
    "content": ".toolbar_container {\n  display: flex;\n\n  :global(.ant-space-item) {\n    margin-bottom: 8px;\n  }\n\n  .left_space {\n    flex: 1;\n    display: flex;\n    flex-wrap: wrap;\n  }\n\n  .right_space {\n    align-self: flex-start;\n    margin-top: 6px;\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/Toolbar/index.tsx",
    "content": "import React from 'react'\nimport cx from 'classnames'\nimport { Space } from 'antd'\n\nimport styles from './index.module.less'\n\nexport default function Toolbar(props: React.HTMLAttributes<HTMLDivElement>) {\n  const { className, children, ...rest } = props\n  const c = cx(className, styles.toolbar_container)\n\n  // https://stackoverflow.com/questions/27366077\n  React.Children.forEach(children, (child) => {\n    if (!React.isValidElement(child) || child.type !== Space) {\n      console.error('Toolbar children only can be Space component')\n    }\n  })\n\n  return (\n    <div className={c} {...rest}>\n      {React.Children.map(children, (child, idx) => {\n        // https://stackoverflow.com/questions/42261783\n        if (React.isValidElement(child) && child.type === Space) {\n          const extraClassName =\n            idx === 0 ? styles.left_space : styles.right_space\n          return React.cloneElement(child, {\n            className: cx(child.props.className, extraClassName),\n            size: child.props.size || 'middle'\n          })\n        }\n      })}\n    </div>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/TxtDownloadLink/index.module.less",
    "content": "@import 'antd/es/style/themes/default.less';\n\n.successTxt {\n  color: @success-color;\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/TxtDownloadLink/index.tsx",
    "content": "import React, { useState } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { useTimeoutFn } from 'react-use'\nimport { CheckOutlined, DownloadOutlined } from '@ant-design/icons'\nimport { addTranslationResource } from '@lib/utils/i18n'\nimport { downloadTxt } from '@lib/utils/local-download'\n\nimport styles from './index.module.less'\n\nexport interface ITxtDownloadLinkProps\n  extends React.DetailedHTMLProps<\n    React.HTMLAttributes<HTMLSpanElement>,\n    HTMLSpanElement\n  > {\n  data?: string\n  fileName?: string\n}\n\nconst translations = {\n  en: {\n    download: 'Download',\n    success: 'Downloaded'\n  },\n  zh: {\n    download: '下载',\n    success: '已下载'\n  }\n}\n\nfor (const key in translations) {\n  addTranslationResource(key, {\n    component: {\n      txtDownloadLink: translations[key]\n    }\n  })\n}\n\nfunction TxtDownloadLink({\n  data,\n  fileName,\n  ...otherProps\n}: ITxtDownloadLinkProps) {\n  const { t } = useTranslation()\n  const [showDownload, setShowDownloaded] = useState(false)\n\n  const reset = useTimeoutFn(() => {\n    setShowDownloaded(false)\n  }, 1500)[2]\n\n  const handleDownload = () => {\n    downloadTxt(data ?? '', fileName ?? 'data.txt')\n    setShowDownloaded(true)\n    reset()\n  }\n\n  return (\n    <span {...otherProps}>\n      {!showDownload && (\n        <a data-e2e={`download_txt`} onClick={handleDownload}>\n          {t(`component.txtDownloadLink.download`)} <DownloadOutlined />\n        </a>\n      )}\n      {showDownload && (\n        <span className={styles.successTxt} data-e2e=\"download_success\">\n          <CheckOutlined /> {t('component.txtDownloadLink.success')}\n        </span>\n      )}\n    </span>\n  )\n}\n\nexport default React.memo(TxtDownloadLink)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/ValueWithTooltip/index.tsx",
    "content": "import React from 'react'\nimport { Tooltip } from 'antd'\nimport { getValueFormat, scaledUnits } from '@baurine/grafana-value-formats'\n\ninterface IValueWithTooltip extends IInternalValueWithTooltip {\n  Short: typeof ShortValueWithTooltip\n  ScaledBytes: typeof ScaledBytesWithTooltip\n}\n\ninterface IInternalValueWithTooltip {\n  title: string\n  value: any\n}\n\nfunction InternalValueWithTooltip({ title, value }: IValueWithTooltip) {\n  return (\n    <Tooltip title={title}>\n      <span>{value}</span>\n    </Tooltip>\n  )\n}\n\nexport interface IValueWithTooltipProps {\n  value?: number\n  scaledDecimal?: number\n}\n\nfunction ShortValueWithTooltip({\n  value = 0,\n  scaledDecimal = 1\n}: IValueWithTooltipProps) {\n  return (\n    <Tooltip title={value}>\n      <span>{getValueFormat('short')(value || 0, 0, scaledDecimal)}</span>\n    </Tooltip>\n  )\n}\n\nconst bytesScaler = scaledUnits(1024, ['', 'K', 'M', 'G', 'T'])\n\nfunction ScaledBytesWithTooltip({\n  value = 0,\n  scaledDecimal = 2\n}: IValueWithTooltipProps) {\n  return (\n    <Tooltip title={getValueFormat('bytes')(value || 0, 0, scaledDecimal)}>\n      <span>{bytesScaler(value || 0, 0, scaledDecimal)}</span>\n    </Tooltip>\n  )\n}\n\nconst ValueWithTooltip =\n  InternalValueWithTooltip as unknown as IValueWithTooltip\n\nValueWithTooltip.Short = ShortValueWithTooltip\nValueWithTooltip.ScaledBytes = ScaledBytesWithTooltip\n\nexport { ValueWithTooltip }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/VisualPlan/DetailDrawer.tsx",
    "content": "import React, { useMemo } from 'react'\nimport ReactJson from 'react-json-view'\nimport { Tabs, Tooltip, Drawer, DrawerProps } from 'antd'\nimport { InfoCircleTwoTone } from '@ant-design/icons'\nimport { RawNodeDatum, Theme } from 'visual-plan'\n\nimport { addTranslations } from '@lib/utils/i18n'\nimport { useTranslation } from 'react-i18next'\nimport translations from './translations'\nimport { toFixed, getValueFormat } from '@baurine/grafana-value-formats'\n\naddTranslations(translations)\n\ninterface DetailDrawerProps {\n  data: RawNodeDatum\n  theme?: Theme\n}\n\nfunction getTableName(node: RawNodeDatum): string {\n  let tableName = ''\n  if (!node?.accessObjects?.length) return ''\n\n  const scanObject = node.accessObjects.find((obj) =>\n    Object.keys(obj).includes('scanObject')\n  )\n\n  if (scanObject) {\n    tableName = scanObject['scanObject']['table']\n  }\n\n  return tableName\n}\n\nconst DetailDrawer: React.FC<DetailDrawerProps & DrawerProps> = ({\n  data,\n  theme = 'light',\n  ...props\n}) => {\n  const tableName = useMemo(() => getTableName(data), [data])\n  const { t } = useTranslation()\n\n  return (\n    data && (\n      <Drawer\n        title={data.name}\n        placement=\"right\"\n        width={window.innerWidth * 0.3}\n        closable={false}\n        destroyOnClose={true}\n        style={{ position: 'absolute' }}\n        className={theme}\n        key=\"right\"\n        getContainer={false}\n        {...props}\n      >\n        <Tabs\n          defaultActiveKey=\"1\"\n          type=\"card\"\n          size=\"middle\"\n          popupClassName={theme}\n        >\n          <Tabs.TabPane\n            tab={t(`binary_plan.tabs.general`)}\n            key=\"1\"\n            style={{ padding: '1rem' }}\n          >\n            <p>\n              Duration{' '}\n              <Tooltip title={t(`binary_plan.tooltip.duration`)}>\n                <InfoCircleTwoTone style={{ paddingRight: 5 }} />\n              </Tooltip>\n              : <span>{data.duration} </span>\n            </p>\n\n            <p>\n              Actual Rows: <span>{data.actRows}</span>\n            </p>\n            <p>\n              Estimate Rows: <span>{toFixed(data.estRows, 0)}</span>\n            </p>\n            <p>\n              Run at: <span>{data.storeType}</span>\n            </p>\n            {tableName && (\n              <p className=\"content\">\n                Table: <span>{tableName}</span>\n              </p>\n            )}\n            {data.cost && (\n              <p>\n                Cost: <span>{data.cost}</span>\n              </p>\n            )}\n          </Tabs.TabPane>\n          <Tabs.TabPane\n            tab={t(`binary_plan.tabs.hardware_usage`)}\n            key=\"2\"\n            style={{ padding: '1rem' }}\n          >\n            <p>\n              Disk:{' '}\n              <span>\n                {Number(data.diskBytes)\n                  ? getValueFormat('deckbytes')(Number(data.diskBytes), 2, null)\n                  : data.diskBytes}{' '}\n              </span>\n            </p>\n            <p>\n              Memory:{' '}\n              <span>\n                {Number(data.memoryBytes)\n                  ? getValueFormat('deckbytes')(\n                      Number(data.memoryBytes),\n                      2,\n                      null\n                    )\n                  : data.memoryBytes}{' '}\n              </span>\n            </p>\n          </Tabs.TabPane>\n          <Tabs.TabPane\n            tab={t(`binary_plan.tabs.advanced_info`)}\n            key=\"3\"\n            style={{ padding: '1rem' }}\n          >\n            <p>\n              Task Type: <span>{data.taskType}</span>\n            </p>\n            {data.labels.length > 0 && (\n              <p>\n                Labels:{' '}\n                <span>\n                  {data.labels.map((label, idx) => (\n                    <>\n                      {idx > 0 ? ',' : ''}\n                      {label}\n                    </>\n                  ))}\n                </span>\n              </p>\n            )}\n            {data.operatorInfo && (\n              <p>\n                Operator Info: <span>{data.operatorInfo}</span>\n              </p>\n            )}\n            {Object.keys(data.rootBasicExecInfo).length > 0 && (\n              <div>\n                Root Basic Exec Info:{' '}\n                <ReactJson\n                  src={data.rootBasicExecInfo}\n                  enableClipboard={false}\n                  displayObjectSize={false}\n                  displayDataTypes={false}\n                  name={false}\n                  theme={theme === 'dark' ? 'monokai' : 'rjv-default'}\n                  iconStyle=\"circle\"\n                />\n              </div>\n            )}\n            {data.rootGroupExecInfo.length > 0 && (\n              <div>\n                Root Group Exec Info:{' '}\n                <ReactJson\n                  src={data.rootGroupExecInfo}\n                  enableClipboard={false}\n                  displayObjectSize={false}\n                  displayDataTypes={false}\n                  theme={theme === 'dark' ? 'monokai' : 'rjv-default'}\n                  name={false}\n                  iconStyle=\"circle\"\n                />\n              </div>\n            )}\n            {Object.keys(data.copExecInfo).length > 0 && (\n              <div>\n                Coprocessor Exec Info:{' '}\n                <ReactJson\n                  src={data.copExecInfo}\n                  enableClipboard={false}\n                  displayObjectSize={false}\n                  displayDataTypes={false}\n                  theme={theme === 'dark' ? 'monokai' : 'rjv-default'}\n                  name={false}\n                  iconStyle=\"circle\"\n                />\n              </div>\n            )}\n            {data.accessObjects.length > 0 && (\n              <div>\n                Access Object:\n                <>\n                  {data.accessObjects.map((obj, idx) => (\n                    <ReactJson\n                      key={idx}\n                      src={obj}\n                      enableClipboard={false}\n                      displayObjectSize={false}\n                      displayDataTypes={false}\n                      theme={theme === 'dark' ? 'monokai' : 'rjv-default'}\n                      name={false}\n                      iconStyle=\"circle\"\n                    />\n                  ))}\n                </>\n              </div>\n            )}\n          </Tabs.TabPane>\n          {data.diagnosis.length > 0 && (\n            <Tabs.TabPane tab={t(`binary_plan.tabs.diagnosis`)} key=\"4\">\n              <ol type=\"1\">\n                {data.diagnosis.map((d: string, idx) => (\n                  <li key={idx} style={{ padding: '1rem 0' }}>\n                    {t(`binary_plan.diagnosis.${d}`)}\n                  </li>\n                ))}\n              </ol>\n            </Tabs.TabPane>\n          )}\n        </Tabs>\n      </Drawer>\n    )\n  )\n}\n\nexport default DetailDrawer\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/VisualPlan/VisualPlan.tsx",
    "content": "import React, { useState } from 'react'\nimport { VisualPlanThumbnail, VisualPlan, RawNodeDatum } from 'visual-plan'\n\nimport DetailDrawer from './DetailDrawer'\n\nexport const VisualPlanThumbnailView = (props) => {\n  const binaryPlan = props.data\n  const minimap = false\n  const cte = { gap: 10 }\n  return (\n    <VisualPlanThumbnail\n      data={binaryPlan}\n      minimap={minimap}\n      cte={cte}\n      theme={'light'}\n    />\n  )\n}\n\nexport const VisualPlanView = (props) => {\n  const binaryPlan = props.data\n  const minimap = { scale: 0.15 }\n  const [showDetailDrawer, setShowDetailDrawer] = useState(false)\n  const [detailData, setDetailData] = useState<RawNodeDatum | null>(null)\n\n  return (\n    <>\n      <VisualPlan\n        data={binaryPlan}\n        onNodeClick={(n) => {\n          setDetailData(n)\n          setShowDetailDrawer(true)\n        }}\n        minimap={minimap}\n        cte={{ gap: 10 }}\n      />\n      <DetailDrawer\n        data={detailData!}\n        theme={'light'}\n        visible={showDetailDrawer}\n        onClose={() => setShowDetailDrawer(false)}\n      />\n    </>\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/VisualPlan/index.ts",
    "content": "export * from './VisualPlan'\nexport * from './DetailDrawer'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/VisualPlan/translations/en.yaml",
    "content": "binary_plan:\n  tooltip:\n    duration: 'The time taken by the parent operator includes the time taken by all children.'\n  tabs:\n    general: General\n    hardware_usage: Hardware Usage\n    advanced_info: Advanced Information\n    diagnosis: Diagnosis\n  diagnosis:\n    high_est_error: 'The estimation error is high. Consider checking the health state of the statistics.'\n    disk_spill: \"Disk spill is triggered for this operator because the memory quota is exceeded. The execution might be slow. Consider increasing the memory quota if there's enough memory.\"\n    pseudo_est: 'This operator used pseudo statistics and the estimation might be inaccurate. It might be caused by unavailable or outdated statistics. Consider collecting statistics or setting variable tidb_enable_pseudo_for_outdated_stats to OFF.'\n    good_filter_on_table_fullscan: 'This Selection filters a high proportion of data. Using an index on this column might achieve better performance. Consider adding an index on this column if there is not one.'\n    bad_index_for_index_lookup: 'This IndexLookup read a lot of data from the index side. It might be slow and cause heavy pressure on TiKV. Consider using the optimizer hints to guide the optimizer to choose a better index or not to use index.'\n    index_join_build_side_too_large: 'This index join has a large build side. It might be slow and cause heavy pressure on TiKV. Consider using the optimizer hints to guide the optimizer to choose hash join.'\n    tikv_huge_table_scan: \"The TiKV read a lot of data. Consider using TiFlash to get better performance if it's necessary to read so much data.\"\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/VisualPlan/translations/index.ts",
    "content": "import zh from './zh.yaml'\nimport en from './en.yaml'\n\nexport default { zh, en }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/VisualPlan/translations/zh.yaml",
    "content": "binary_plan:\n  tooltip:\n    duration: '父算子的耗时包含所有子算子的耗时。'\n  tabs:\n    general: 概览\n    hardware_usage: 硬件使用\n    advanced_info: 高级信息\n    diagnosis: 诊断\n  diagnosis:\n    high_est_error: '估算误差较大。可以考虑检查统计信息的健康度。'\n    disk_spill: 执行过程中由于到达内存限制，产生了落盘，执行可能会变慢。内存足够的情况下，可以考虑提高内存使用阈值。\"\n    pseudo_est: '该算子使用了 pseudo 统计信息，行数估算可能不准确。原因可能是统计信息不可用或者统计信息过期。可以考虑收集统计信息或者将系统变量 tidb_enable_pseudo_for_outdated_stats 设为 OFF。'\n    good_filter_on_table_fullscan: '该过滤条件的过滤性较好。使用该列上的索引可能能够达到更好的性能。如果没有该列上的索引，可以考虑创建该列上的索引。'\n    bad_index_for_index_lookup: '该 IndexLookup 算子从索引中读取了大量数据。这可能导致执行较慢以及对 TiKV 造成较大压力。可以考虑使用 Optimizer Hints 指导 TiDB 选择更好的索引或者不使用索引。'\n    index_join_build_side_too_large: '该 IndexJoin 算子从 build 端读取了大量数据。这可能导致执行较慢以及对 TiKV 造成较大压力。可以考虑使用 Optimizer Hints 指导 TiDB 选择 Hash Join。'\n    tikv_huge_table_scan: '该算子在 TiKV 读取了大量数据。如果确实需要读取大量数据，可以考虑使用 TiFlash 达到更好的性能。'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/components/index.ts",
    "content": "export * from './Root'\nexport { default as Root } from './Root'\nexport * from './Head'\nexport { default as Head } from './Head'\nexport * from './Card'\nexport { default as Card } from './Card'\nexport * from './CardTabs'\nexport { default as CardTabs } from './CardTabs'\nexport * from './CardTable'\nexport { default as CardTable } from './CardTable'\nexport * from './Bar'\nexport { default as Bar } from './Bar'\nexport * from './HighlightSQL'\nexport { default as HighlightSQL } from './HighlightSQL'\nexport * from './TextWrap'\nexport { default as TextWrap } from './TextWrap'\nexport * from './Pre'\nexport { default as Pre } from './Pre'\nexport * from './Descriptions'\nexport { default as Descriptions } from './Descriptions'\nexport * from './TextWithInfo'\nexport { default as TextWithInfo } from './TextWithInfo'\nexport * from './DateTime'\nexport { default as DateTime } from './DateTime'\nexport * from './Expand'\nexport { default as Expand } from './Expand'\nexport * from './CopyLink'\nexport { default as CopyLink } from './CopyLink'\nexport { default as TxtDownloadLink } from './TxtDownloadLink'\nexport * from './ColumnsSelector'\nexport { default as ColumnsSelector } from './ColumnsSelector'\nexport * from './Toolbar'\nexport { default as Toolbar } from './Toolbar'\nexport * from './TimeRangeSelector'\nexport { default as TimeRangeSelector } from './TimeRangeSelector'\nexport * from './AnimatedSkeleton'\nexport { default as AnimatedSkeleton } from './AnimatedSkeleton'\nexport * from './InstanceStatusBadge'\nexport { default as InstanceStatusBadge } from './InstanceStatusBadge'\nexport * from './BaseSelect'\nexport { default as BaseSelect } from './BaseSelect'\nexport * from './InstanceSelect'\nexport { default as InstanceSelect } from './InstanceSelect'\nexport * from './MultiSelect'\nexport { default as MultiSelect } from './MultiSelect'\nexport * from './ValueWithTooltip'\nexport * from './DatePicker'\nexport { default as DatePicker } from './DatePicker'\nexport * from './ErrorBar'\nexport { default as ErrorBar } from './ErrorBar'\nexport * from './AppearAnimate'\nexport { default as AppearAnimate } from './AppearAnimate'\nexport * from './Blink'\nexport { default as Blink } from './Blink'\nexport * from './DrawerFooter'\nexport { default as DrawerFooter } from './DrawerFooter'\n\nexport * from './VisualPlan'\nexport * from './BinaryPlanTable'\nexport * from './PlanText'\n\nexport { default as LanguageDropdown } from './LanguageDropdown'\nexport { default as ParamsPageWrapper } from './ParamsPageWrapper'\n\nexport * from './AutoRefreshButton'\nexport * from './Ngm'\nexport * from './LimitTimeRange'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/hooks/useLocationChange.ts",
    "content": "import { useEffect } from 'react'\nimport { useLocation } from 'react-router-dom'\n\nexport function useLocationChange() {\n  // https://thewebdev.info/2022/03/07/how-to-detect-route-change-with-react-router/\n  const location = useLocation()\n  useEffect(() => {\n    const event = new CustomEvent('dashboard:route-change', {\n      detail: { location }\n    })\n    window.dispatchEvent(event)\n  }, [location])\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/hooks/useQueryParams.ts",
    "content": "import { useEffect, useState } from 'react'\n\nimport { NavigateOptions, useLocation, useNavigate } from 'react-router-dom'\n\nexport interface IQueryParams {\n  [key: string]: any\n}\n\nexport function useQueryParams<T = IQueryParams>(\n  defParams: T,\n  override?: T,\n  options?: NavigateOptions\n) {\n  const location = useLocation()\n  const navigate = useNavigate()\n\n  const [queryParams, _setQueryParams] = useState(() => {\n    let newParams = { ...defParams, ...override }\n    const searchParams = new URLSearchParams(location.search)\n\n    for (const [key, value] of searchParams.entries()) {\n      const defVal = defParams[key]\n      if (defVal !== undefined) {\n        if (typeof defVal === 'number') {\n          newParams[key] = Number(value)\n        } else if (Array.isArray(defVal)) {\n          if (value === '') {\n            newParams[key] = []\n          } else {\n            newParams[key] = value.split(',')\n          }\n        } else {\n          newParams[key] = value\n        }\n      } else {\n        newParams[key] = value\n      }\n    }\n    return newParams\n  })\n\n  useEffect(() => {\n    // redirect if params are not default when mount\n    if (override) {\n      setQueryParams(queryParams)\n    }\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [])\n\n  function setQueryParams(p: T) {\n    const params = { ...queryParams, ...p }\n    _setQueryParams(params)\n\n    const prevSearchStr = location.search\n    const searchParams = new URLSearchParams()\n    Object.keys(params).forEach((k) => {\n      searchParams.set(k, params[k] + '')\n    })\n    const currentSearchStr = `?${searchParams.toString()}`\n\n    if (prevSearchStr === currentSearchStr) {\n      return\n    }\n    navigate(`${location.pathname}?${searchParams.toString()}`, options)\n  }\n\n  return { queryParams, setQueryParams }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/hooks/useURLTimeRange.ts",
    "content": "import { TimeRange } from '@lib/components'\nimport { useMemo } from 'react'\nimport { useQueryParams } from './useQueryParams'\n\nexport const useURLTimeRange = () => {\n  const { queryParams, setQueryParams } = useQueryParams<{\n    from: number | string\n    to: number | string\n  }>({\n    from: 30 * 60,\n    to: 'now'\n  })\n  const { from, to } = queryParams\n  const isRecent = to === 'now'\n  const timeRange: TimeRange = useMemo(\n    () =>\n      ({\n        type: isRecent ? 'recent' : 'absolute',\n        value: isRecent\n          ? parseInt(`${from}`)\n          : [parseInt(`${from}`), parseInt(`${to}`)]\n      } as TimeRange),\n    [from, to, isRecent]\n  )\n  const setTimeRange = (tr: TimeRange) => {\n    const isRecent = tr.type === 'recent'\n    setQueryParams({\n      from: isRecent ? tr.value : tr.value[0],\n      to: isRecent ? 'now' : tr.value[1]\n    })\n  }\n\n  return { timeRange, setTimeRange }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/index.ts",
    "content": "export * from './types'\nexport * from './utils'\nexport * from './components'\nexport * from './apps'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/react-app-env.d.ts",
    "content": "declare module '*.module.css' {\n  const classes: { readonly [key: string]: string }\n  export default classes\n}\n\ndeclare module '*.module.less' {\n  const classes: { readonly [key: string]: string }\n  export default classes\n}\n\ndeclare module '*.yaml' {\n  const classes: { readonly [key: string]: string }\n  export default classes\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/types/index.ts",
    "content": "import { AxiosRequestConfig } from 'axios'\n\nexport interface ReqConfig extends AxiosRequestConfig {\n  handleError?: 'default' | 'custom'\n}\n\nexport interface IContextConfig {\n  apiPathBase: string\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/types/reqConfig.ts",
    "content": "import { AxiosRequestConfig } from 'axios'\n\nexport interface ReqConfig extends AxiosRequestConfig {\n  handleError?: 'default' | 'custom'\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/charts.ts",
    "content": "import {\n  PartialTheme,\n  SettingsProps,\n  TickFormatter,\n  timeFormatter,\n  TooltipSettings,\n  TooltipStickTo,\n  TooltipType,\n  TooltipValue\n} from '@elastic/charts'\nimport { TimeRangeValue } from '@lib/components'\nimport dayjs from 'dayjs'\nimport React, { useRef } from 'react'\nimport { DEFAULT_MIN_INTERVAL_SEC } from './prometheus'\nimport '@elastic/charts/dist/theme_only_light.css'\nimport tz from './timezone'\n\n/**\n * A human readable tick label formatter for time series data. It scales according to the data domain.\n */\nexport function timeTickFormatter(range: TimeRangeValue): TickFormatter {\n  // const minDate = moment(range[0] * 1000)\n  // const maxDate = moment(range[1] * 1000)\n  // const diff = maxDate.diff(minDate, 'minutes')\n\n  const minDate = dayjs(range[0] * 1000)\n  const maxDate = dayjs(range[1] * 1000)\n  const diff = maxDate.diff(minDate, 'minutes')\n  const format = niceTimeFormatByDay(diff)\n\n  function formatter(v): string {\n    return timeFormatter(format)(v, { timeZone: tz.getTimeZoneStr() })\n  }\n  return formatter\n}\n\nfunction niceTimeFormatByDay(days: number) {\n  if (days > 5 * 60 * 24) return 'MM-DD'\n  if (days > 1 * 60 * 24) return 'MM-DD HH:mm'\n  if (days > 5) return 'HH:mm'\n  return 'HH:mm:ss'\n}\n\nexport function timeTooltipFormatter({ value }: TooltipValue): string {\n  return timeFormatter('YYYY-MM-DD HH:mm:ss (UTCZ)')(value, {\n    timeZone: tz.getTimeZoneStr()\n  })\n}\n\nexport const DEFAULT_TOOLTIP_SETTINGS: TooltipSettings = {\n  type: TooltipType.Crosshairs,\n  headerFormatter: timeTooltipFormatter,\n  stickTo: TooltipStickTo.MousePosition\n}\n\nexport const DEFAULT_THEME: PartialTheme = {\n  axes: {\n    tickLine: { visible: false },\n    tickLabel: { padding: { inner: 10 } },\n    gridLine: {\n      horizontal: {\n        visible: true,\n        dash: [3, 3]\n      },\n      vertical: {\n        visible: true,\n        dash: [3, 3]\n      }\n    }\n  },\n  crosshair: {\n    crossLine: {\n      dash: []\n    },\n    line: {\n      dash: []\n    }\n  }\n}\n\nexport const DEFAULT_CHART_SETTINGS: SettingsProps = {\n  showLegend: true,\n  showLegendExtra: true,\n  tooltip: DEFAULT_TOOLTIP_SETTINGS,\n  theme: DEFAULT_THEME\n}\n\nexport type ChartHandle = {\n  calcIntervalSec: (range: TimeRangeValue, minIntervalSec?: number) => number\n}\n\n/**\n * Align the time range according to the minimal interval and minimal range size.\n */\nexport function alignRange(\n  range: TimeRangeValue,\n  minIntervalSec = DEFAULT_MIN_INTERVAL_SEC,\n  minRangeSec = 60\n): TimeRangeValue {\n  let [min, max] = range\n  if (max - min < minRangeSec) {\n    min = max - minRangeSec\n  }\n  min = Math.floor(min / minIntervalSec) * minIntervalSec\n  max = Math.ceil(max / minIntervalSec) * minIntervalSec\n  return [min, max]\n}\n\nexport function useChartHandle(\n  containerRef: React.RefObject<HTMLDivElement>,\n  legendWidth: number = 0,\n  minBinWidth: number = 5\n): [ChartHandle] {\n  const chartRef = useRef<ChartHandle>({\n    calcIntervalSec: (range, minIntervalSec = DEFAULT_MIN_INTERVAL_SEC) => {\n      const maxDataPoints =\n        ((containerRef.current?.offsetWidth ?? 0) - legendWidth) / minBinWidth\n      if (maxDataPoints <= 0) {\n        return minIntervalSec\n      }\n      const interval = (range[1] - range[0]) / maxDataPoints\n      const roundedInterval =\n        Math.floor(interval / minIntervalSec) * minIntervalSec\n      return Math.max(minIntervalSec, roundedInterval)\n    }\n  })\n  return [chartRef.current]\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/distro.ts",
    "content": "import i18next from 'i18next'\n\ninterface IDistro {\n  pd: string\n  tidb: string\n  tikv: string\n  tiflash: string\n  ticdc: string\n  is_distro: boolean\n}\n\nconst DEF_DISTRO: IDistro = {\n  pd: 'PD',\n  tidb: 'TiDB',\n  tikv: 'TiKV',\n  tiflash: 'TiFlash',\n  ticdc: 'TiCDC',\n  is_distro: false\n}\n\nlet _distro = DEF_DISTRO\n\nexport function distro() {\n  return _distro\n}\n\nexport function isDistro() {\n  return Boolean(_distro.is_distro)\n}\n\n// newDistro example: { tidb:'TieDB', tikv: 'TieKV' }\nexport function updateDistro(newDistro: Partial<IDistro>) {\n  _distro = { ..._distro, ...newDistro }\n\n  // update i18n resource\n  i18next.addResourceBundle(\n    'en',\n    'translation',\n    { distro: _distro },\n    true,\n    true\n  )\n\n  // update i18n interpolation defaultVariables by hack way\n  // https://stackoverflow.com/a/71031838/2998877\n  const interpolator = i18next.services.interpolator as any\n  interpolator.options.interpolation.defaultVariables = { distro: _distro }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/format.ts",
    "content": "import { getValueFormat } from '@baurine/grafana-value-formats'\n\nexport function formatNumByUnit(\n  value: number,\n  unit: string,\n  precision: number = 1\n) {\n  if (isNaN(value)) {\n    return ''\n  }\n  const formatFn = getValueFormat(unit)\n  if (!formatFn) {\n    return value + ''\n  }\n  if (unit === 'short') {\n    return formatFn(value, 0, precision)\n  }\n  return formatFn(value, precision)\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/i18n.ts",
    "content": "import 'dayjs/locale/zh'\n\nimport dayjs from 'dayjs'\nimport i18next from 'i18next'\nimport LanguageDetector from 'i18next-browser-languagedetector'\nimport { initReactI18next } from 'react-i18next'\n\nimport { distro } from './distro'\n\ni18next.on('languageChanged', function (lng) {\n  dayjs.locale(lng.toLowerCase())\n})\n\nexport function addTranslations(translations) {\n  Object.keys(translations).forEach((key) => {\n    addTranslationResource(key, translations[key])\n  })\n}\n\nexport function addTranslationResource(lang, translations) {\n  i18next.addResourceBundle(lang, 'translation', translations, true, false)\n}\n\nexport const ALL_LANGUAGES = {\n  zh: '简体中文',\n  en: 'English'\n}\n\ni18next\n  .use(LanguageDetector)\n  .use(initReactI18next)\n  .init({\n    resources: {\n      en: {\n        translation: {\n          distro: distro()\n        }\n      }\n    },\n    fallbackLng: 'en', // fallbackLng won't change the detected language\n    supportedLngs: ['zh', 'en'], // supportedLngs will change the detected lanuage\n    interpolation: {\n      escapeValue: false,\n      defaultVariables: { distro: distro() }\n    }\n  })\n\nexport default {\n  addTranslations,\n  addTranslationResource,\n  ALL_LANGUAGES\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/index.ts",
    "content": "export { default as routing } from './routing'\nexport { default as i18n } from './i18n'\nexport { default as telemetry } from './telemetry'\nexport { default as tz } from './timezone'\n\nexport * from './distro'\nexport * from './store'\nexport * from './useVersionedLocalStorageState'\nexport * from './prometheus'\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/instanceTable.ts",
    "content": "import _ from 'lodash'\nimport i18next from 'i18next'\nimport { IGroup } from 'office-ui-fabric-react/lib/DetailsList'\n\nimport {\n  TopologyPDInfo,\n  TopologyTiDBInfo,\n  TopologyStoreInfo,\n  TopologyTiCDCInfo,\n  TopologyTiProxyInfo,\n  TopologyTSOInfo,\n  TopologySchedulingInfo\n} from '@lib/client'\n\nexport const InstanceKinds = [\n  'pd',\n  'tidb',\n  'tikv',\n  'tiflash',\n  'ticdc',\n  'tiproxy',\n  'tso',\n  'scheduling'\n] as const\nexport type InstanceKind = typeof InstanceKinds[number]\n\nexport const InstanceStatus = {\n  Unreachable: 0,\n  Up: 1,\n  Tombstone: 2,\n  Offline: 3,\n  Down: 4\n}\n\nexport function instanceKindName(kind: InstanceKind) {\n  return i18next.t(`distro.${kind}`)\n}\n\nexport interface IInstanceTableItem\n  extends TopologyPDInfo,\n    TopologyTiDBInfo,\n    TopologyStoreInfo,\n    TopologyTiCDCInfo,\n    TopologyTiProxyInfo,\n    TopologyTSOInfo,\n    TopologySchedulingInfo {\n  key: string\n  instanceKind: InstanceKind\n}\n\nexport interface IBuildInstanceTableProps {\n  dataPD?: TopologyPDInfo[]\n  dataTiDB?: TopologyTiDBInfo[]\n  dataTiKV?: TopologyStoreInfo[]\n  dataTiFlash?: TopologyStoreInfo[]\n  dataTiCDC?: TopologyTiCDCInfo[]\n  dataTiProxy?: TopologyTiProxyInfo[]\n  dataTSO?: TopologyTSOInfo[]\n  dataScheduling?: TopologySchedulingInfo[]\n  includeTiFlash?: boolean\n}\n\nexport function buildInstanceTable({\n  dataPD,\n  dataTiDB,\n  dataTiKV,\n  dataTiFlash,\n  dataTiCDC,\n  dataTiProxy,\n  dataTSO,\n  dataScheduling,\n  includeTiFlash\n}: IBuildInstanceTableProps): [IInstanceTableItem[], IGroup[]] {\n  const tableData: IInstanceTableItem[] = []\n  const groupData: IGroup[] = []\n  let startIndex = 0\n\n  const kinds: {\n    [key in InstanceKind]?:\n      | TopologyPDInfo[]\n      | TopologyTiDBInfo[]\n      | TopologyStoreInfo[]\n      | TopologyTiCDCInfo[]\n      | TopologyTiProxyInfo[]\n      | TopologyTSOInfo[]\n      | TopologySchedulingInfo[]\n      | undefined\n  } = {}\n  kinds.pd = dataPD\n  kinds.tidb = dataTiDB\n  kinds.tikv = dataTiKV\n  kinds.ticdc = dataTiCDC\n  kinds.tiproxy = dataTiProxy\n  kinds.tso = dataTSO\n  kinds.scheduling = dataScheduling\n  if (includeTiFlash) {\n    kinds.tiflash = dataTiFlash\n  }\n\n  for (const ik of InstanceKinds) {\n    const instances = kinds[ik]\n    if (!instances || instances.length === 0) {\n      continue\n    }\n    groupData.push({\n      key: ik,\n      name: instanceKindName(ik),\n      startIndex: startIndex,\n      count: instances.length,\n      level: 0\n    })\n    startIndex += instances.length\n    instances.forEach((instance) => {\n      const key = `${instance.ip}:${instance.port}`\n      tableData.push({\n        key: key,\n        instanceKind: ik,\n        ...instance\n      })\n    })\n  }\n  return [tableData, groupData]\n}\n\nexport function filterInstanceTable(\n  items: IInstanceTableItem[],\n  filterKeyword: string\n): [IInstanceTableItem[], IGroup[]] {\n  const tableData: IInstanceTableItem[] = []\n  const groupData: IGroup[] = []\n  let startIndex = 0\n\n  const kw = filterKeyword.toLowerCase()\n  const filteredItems = items.filter((i) => {\n    if (filterKeyword.length === 0) {\n      return true\n    }\n    return (\n      i.key.toLowerCase().indexOf(kw) > -1 || i.instanceKind.indexOf(kw) > -1\n    )\n  })\n  const itemsByIk = _.groupBy(filteredItems, 'instanceKind') as {\n    [key in InstanceKind]: IInstanceTableItem[]\n  }\n  for (const ik of InstanceKinds) {\n    const instances = itemsByIk[ik]\n    if (!instances || instances.length === 0) {\n      continue\n    }\n    groupData.push({\n      key: ik,\n      name: instanceKindName(ik),\n      startIndex: startIndex,\n      count: instances.length,\n      level: 0\n    })\n    startIndex += instances.length\n    instances.forEach((instance) => {\n      tableData.push(instance)\n    })\n  }\n  return [tableData, groupData]\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/local-download.ts",
    "content": "export function downloadTxt(data: string, fileName: string) {\n  const fileUrl = URL.createObjectURL(\n    new Blob([data], {\n      type: 'text/plain;charset=utf-8;'\n    })\n  )\n  const a = document.createElement('a')\n  document.body.appendChild(a)\n  a.href = fileUrl\n  a.download = fileName\n  a.click()\n  setTimeout(() => {\n    document.body.removeChild(a)\n  }, 0)\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/openLink.ts",
    "content": "import { NavigateFunction } from 'react-router-dom'\nimport React from 'react'\n\n// the url param starts with '/', for example: '/statement/detail'\nexport default function openLink(\n  url: string,\n  ev: React.MouseEvent<HTMLElement>,\n  navigate: NavigateFunction\n) {\n  const { origin, pathname, search } = window.location\n  const fullUrl = `${origin}${pathname}${search}#${url}`\n\n  if (ev.metaKey || ev.altKey || ev.ctrlKey) {\n    // open in a new tab\n    window.open(fullUrl, '_blank')\n  } else if (ev.shiftKey) {\n    // open in a new window\n    window.open(fullUrl)\n  } else {\n    navigate(url, { state: { historyBack: true } })\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/prometheus/data.ts",
    "content": "// Copyright Grafana. Licensed under Apache-2.0.\n\n// Extracted from:\n// https://github.com/grafana/grafana/blob/c986aaa0a8e7fb167b9d10304129f6aea85ad45c/public/app/plugins/datasource/prometheus/result_transformer.ts\n\nimport { DEFAULT_MIN_INTERVAL_SEC } from '.'\nimport {\n  DataPoint,\n  isMatrixData,\n  MatrixOrVectorResult,\n  QueryOptions\n} from './types'\n\nconst POSITIVE_INFINITY_SAMPLE_VALUE = '+Inf'\nconst NEGATIVE_INFINITY_SAMPLE_VALUE = '-Inf'\n\nfunction parseSampleValue(value: string): number {\n  switch (value) {\n    case POSITIVE_INFINITY_SAMPLE_VALUE:\n      return Number.POSITIVE_INFINITY\n    case NEGATIVE_INFINITY_SAMPLE_VALUE:\n      return Number.NEGATIVE_INFINITY\n    default:\n      return parseFloat(value)\n  }\n}\n\nexport function processRawData(\n  data: MatrixOrVectorResult,\n  options: QueryOptions\n): DataPoint[] | null {\n  if (isMatrixData(data)) {\n    const stepMs = options.step ? options.step * 1000 : NaN\n    let baseTimestamp = options.start * 1000\n    const dps: DataPoint[] = []\n\n    for (const value of data.values) {\n      let dpValue: number | null = parseSampleValue(value[1])\n\n      if (isNaN(dpValue)) {\n        dpValue = null\n      }\n\n      const timestamp = value[0] * 1000\n      for (let t = baseTimestamp; t < timestamp; t += stepMs) {\n        dps.push([t, null])\n      }\n      baseTimestamp = timestamp + stepMs\n      dps.push([timestamp, dpValue])\n    }\n\n    const endTimestamp = options.end * 1000\n    for (let t = baseTimestamp; t <= endTimestamp; t += stepMs) {\n      dps.push([t, null])\n    }\n\n    return dps\n  }\n  return null\n}\n\nexport function resolveQueryTemplate(\n  template: string,\n  options: QueryOptions\n): string {\n  return template.replaceAll(\n    '$__rate_interval',\n    `${Math.max(options.step, 4 * DEFAULT_MIN_INTERVAL_SEC)}s`\n  )\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/prometheus/index.ts",
    "content": "export * from './data'\nexport * from './types'\n\nexport const DEFAULT_MIN_INTERVAL_SEC = 30\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/prometheus/types.ts",
    "content": "// Copyright Grafana. Licensed under Apache-2.0.\n\n// Extracted from:\n// https://github.com/grafana/grafana/blob/c986aaa0a8e7fb167b9d10304129f6aea85ad45c/public/app/plugins/datasource/prometheus/types.ts\n\nexport interface PromMetricsMetadataItem {\n  type: string\n  help: string\n  unit?: string\n}\n\nexport interface PromMetricsMetadata {\n  [metric: string]: PromMetricsMetadataItem[]\n}\n\nexport interface PromDataSuccessResponse<T = PromData> {\n  status: 'success'\n  data: T\n}\n\nexport interface PromDataErrorResponse<T = PromData> {\n  status: 'error'\n  errorType: string\n  error: string\n  data: T\n}\n\nexport type PromData =\n  | PromMatrixData\n  | PromVectorData\n  | PromScalarData\n  | PromExemplarData[]\n\nexport interface Labels {\n  [index: string]: any\n}\n\nexport interface Exemplar {\n  labels: Labels\n  value: number\n  timestamp: number\n}\n\nexport interface PromExemplarData {\n  seriesLabels: PromMetric\n  exemplars: Exemplar[]\n}\n\nexport interface PromVectorData {\n  resultType: 'vector'\n  result: Array<{\n    metric: PromMetric\n    value: PromValue\n  }>\n}\n\nexport interface PromMatrixData {\n  resultType: 'matrix'\n  result: Array<{\n    metric: PromMetric\n    values: PromValue[]\n  }>\n}\n\nexport interface PromScalarData {\n  resultType: 'scalar'\n  result: PromValue\n}\n\nexport type PromValue = [number, any]\n\nexport interface PromMetric {\n  __name__?: string\n  [index: string]: any\n}\n\nexport function isMatrixData(\n  result: MatrixOrVectorResult\n): result is PromMatrixData['result'][0] {\n  return 'values' in result\n}\n\nexport type MatrixOrVectorResult =\n  | PromMatrixData['result'][0]\n  | PromVectorData['result'][0]\n\nexport enum TransformNullValue {\n  NULL = 'null',\n  AS_ZERO = 'as_zero'\n}\n\nexport enum ColorType {\n  BLUE_1 = '#C0D8FF',\n  BLUE_2 = '#8AB8FF',\n  BLUE_3 = '#3274D9',\n  BLUE_4 = '#1F60C4',\n  GREEN_1 = '#C8F2C2',\n  GREEN_2 = '#96D98D',\n  GREEN_3 = '#56A64B',\n  GREEN_4 = '#37872D',\n  RED_1 = '#FFA6B0',\n  RED_2 = '#FF7383',\n  RED_3 = '#E02F44',\n  RED_4 = '#C4162A',\n  RED_5 = '#701313',\n  PURPLE = '#8778ee',\n  ORANGE = '#FF9830',\n  YELLOW = '#FADE2A',\n  PINK = '#F2495C'\n}\n\n// Our customized types\n\nexport interface QueryOptions {\n  step: number\n  start: number\n  end: number\n}\n\nexport type DataPoint = [msTimestamp: number, value: number | null]\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/query.ts",
    "content": "export interface IQueryParams {\n  [key: string]: any\n}\n\nexport function parseQueryFn<T = IQueryParams>() {\n  return (qs: string): T => {\n    const p = new URLSearchParams(qs)\n    const json = p.get('query')\n    if (json == null) {\n      return {} as T\n    }\n    const r = JSON.parse(json)\n    if (!!r && r.constructor === Object) {\n      return r as T\n    }\n    return {} as T\n  }\n}\n\nexport function buildQueryFn<T = IQueryParams>() {\n  return (q: T): string => {\n    const json = JSON.stringify(q)\n    const p = new URLSearchParams()\n    p.set('query', json)\n    return p.toString()\n  }\n}\n\nexport function stripQueryString(url: string) {\n  return url.split('?')[0]\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/routing.ts",
    "content": "export const signInRoute = '/signin'\nexport const portalRoute = '/portal'\n\nexport function isLocationMatch(s, matchPrefix = false): boolean {\n  let hash = window.location.hash\n  if (!hash || hash === '#') {\n    hash = '#/'\n  }\n  if (matchPrefix) {\n    return hash.indexOf(`#${s}`) === 0\n  } else {\n    return hash.trim() === `#${s}`\n  }\n}\n\nexport function isLocationMatchPrefix(s): boolean {\n  return isLocationMatch(s, true)\n}\n\nexport function isSignInPage(): boolean {\n  return isLocationMatchPrefix(signInRoute)\n}\n\nexport function isPortalPage(): boolean {\n  return isLocationMatchPrefix(portalRoute)\n}\n\nexport function getPathInLocationHash(): string {\n  const hash = window.location.hash\n  const pos = hash.indexOf('?')\n  if (pos === -1) {\n    return hash\n  }\n  return hash.substring(0, pos)\n}\n\nexport default {\n  signInRoute,\n  portalRoute,\n  isLocationMatch,\n  isLocationMatchPrefix,\n  isSignInPage,\n  isPortalPage,\n  getPathInLocationHash\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/selectionWithFilter.ts",
    "content": "import {\n  ISelection,\n  IObjectWithKey,\n  Selection,\n  SelectionMode,\n  ISelectionOptions,\n  ISelectionOptionsWithRequiredGetKey,\n  EventGroup,\n  SELECTION_CHANGE\n} from 'office-ui-fabric-react/lib/Utilities'\n\nexport default class SelectionWithFilter<T = IObjectWithKey>\n  implements ISelection<T>\n{\n  private _inner: Selection<T>\n\n  private _allItems: T[] = []\n  private _allItemsMap: Map<string, T> = new Map()\n  private _allSelectedKeysSet: Set<string> = new Set()\n  private _itemKeysSet: Set<string> = new Set()\n\n  private _allSelectionCache: T[] | null = null\n  private _onSelectionChangedOriginal?: () => void\n\n  get count(): number {\n    return this._inner.count\n  }\n  set count(v: number) {\n    this._inner.count = v\n  }\n  get mode(): SelectionMode {\n    return this._inner.mode\n  }\n  canSelectItem(item: T, index?: number): boolean {\n    return this._inner.canSelectItem(item, index)\n  }\n  setChangeEvents(isEnabled: boolean, suppressChange?: boolean) {\n    return this._inner.setChangeEvents(isEnabled, suppressChange)\n  }\n  getItems(): T[] {\n    return this._inner.getItems()\n  }\n  getSelection(): T[] {\n    return this._inner.getSelection()\n  }\n  getSelectedIndices(): number[] {\n    return this._inner.getSelectedIndices()\n  }\n  getSelectedCount(): number {\n    return this._inner.getSelectedCount()\n  }\n  isRangeSelected(fromIndex: number, count: number): boolean {\n    return this._inner.isRangeSelected(fromIndex, count)\n  }\n  isAllSelected(): boolean {\n    return this._inner.isAllSelected()\n  }\n  isKeySelected(key: string): boolean {\n    return this._inner.isKeySelected(key)\n  }\n  isIndexSelected(index: number): boolean {\n    return this._inner.isIndexSelected(index)\n  }\n  setKeySelected(\n    key: string,\n    isSelected: boolean,\n    shouldAnchor: boolean\n  ): void {\n    this._inner.setKeySelected(key, isSelected, shouldAnchor)\n  }\n  setIndexSelected(\n    index: number,\n    isSelected: boolean,\n    shouldAnchor: boolean\n  ): void {\n    this._inner.setIndexSelected(index, isSelected, shouldAnchor)\n  }\n  selectToKey(key: string, clearSelection?: boolean | undefined): void {\n    this._inner.selectToKey(key, clearSelection)\n  }\n  selectToIndex(index: number, clearSelection?: boolean | undefined): void {\n    this._inner.selectToIndex(index, clearSelection)\n  }\n  toggleAllSelected(): void {\n    this.setAllSelected(!this._inner.isAllSelected())\n  }\n  toggleKeySelected(key: string): void {\n    this._inner.toggleKeySelected(key)\n  }\n  toggleIndexSelected(index: number): void {\n    this._inner.toggleIndexSelected(index)\n  }\n  toggleRangeSelected(fromIndex: number, count: number): void {\n    this._inner.toggleRangeSelected(fromIndex, count)\n  }\n  // Override\n  setItems(items: T[], shouldClear?: boolean) {\n    this._allSelectionCache = null\n    if (shouldClear) {\n      this._allSelectedKeysSet.clear()\n    }\n\n    // Only items in AllItems can be added\n    const itemSubset: T[] = []\n    this._itemKeysSet.clear()\n    for (const item of items) {\n      const key = this._inner.getKey(item)\n      if (this._allItemsMap.has(key)) {\n        this._itemKeysSet.add(key)\n        itemSubset.push(item)\n      } else {\n        if (process.env.NODE_ENV === 'development') {\n          console.warn(\n            'Warning: SelectionWithFilter::setItems is called with an item not in allItems',\n            item,\n            key\n          )\n        }\n      }\n    }\n\n    this._inner.setChangeEvents(false)\n    this._inner.setItems(itemSubset, shouldClear)\n    // Re-select if newly added items are selected in allSelected\n    for (const key of this._allSelectedKeysSet) {\n      if (this._itemKeysSet.has(key)) {\n        this._inner.setKeySelected(key, true, false)\n      }\n    }\n    this._inner.setChangeEvents(true)\n  }\n  // Override\n  setAllSelected(isAllSelected: boolean) {\n    if (isAllSelected && this._itemKeysSet.size !== this._allItemsMap.size) {\n      // If items is a true subset of allItems, we emulate a selectAll by selecting one by one.\n      this._inner.setChangeEvents(false)\n      for (const key of this._itemKeysSet) {\n        this._inner.setKeySelected(key, true, false)\n      }\n      this._inner.setChangeEvents(true)\n    } else {\n      this._inner.setAllSelected(isAllSelected)\n    }\n  }\n\n  constructor(\n    ...options: T extends IObjectWithKey\n      ? [] | [ISelectionOptions<T>]\n      : [ISelectionOptionsWithRequiredGetKey<T>]\n  ) {\n    const { onSelectionChanged, ...rest } =\n      options[0] || ({} as ISelectionOptions<T>)\n    this._onSelectionChangedOriginal = onSelectionChanged\n    this._inner = new (Selection as any)({\n      onSelectionChanged: () => this._handleSelectionChanged(),\n      ...rest\n    })\n  }\n\n  private _handleSelectionChanged() {\n    this._triggerSelectionChanged()\n  }\n\n  private _triggerSelectionChanged() {\n    this._allSelectionCache = null\n    EventGroup.raise(this, SELECTION_CHANGE)\n    if (this._onSelectionChangedOriginal) {\n      this._onSelectionChangedOriginal()\n    }\n  }\n\n  setAllItems(items: T[]) {\n    this._allSelectionCache = null\n    this._allItems = items\n    this._allItemsMap.clear()\n    for (const item of items) {\n      const key = this._inner.getKey(item)\n      this._allItemsMap.set(key, item)\n    }\n    // Ensure `items` is a subset of `alllItems`. If not, update `items`.\n    const filteredItems = this._inner.getItems()\n    const newItems: T[] = []\n    for (const item of filteredItems) {\n      const key = this._inner.getKey(item)\n      if (this._allItemsMap.has(key)) {\n        newItems.push(item)\n      } else {\n        if (process.env.NODE_ENV === 'development') {\n          console.log(\n            'Note: SelectionWithFilter::setAllItems is filtering away an item previously in items but not in allItems',\n            item,\n            key\n          )\n        }\n      }\n    }\n    if (filteredItems.length !== newItems.length) {\n      this.setItems(newItems)\n    }\n  }\n\n  getAllItems(): T[] {\n    return this._allItems\n  }\n\n  getAllSelection(): T[] {\n    if (!this._allSelectionCache) {\n      this._allSelectionCache = []\n      for (const [key, item] of this._allItemsMap) {\n        // Selected state of the internal Selection takes precedence\n        if (this._itemKeysSet.has(key)) {\n          if (this._inner.isKeySelected(key)) {\n            this._allSelectionCache.push(item)\n          }\n        } else {\n          if (this._allSelectedKeysSet.has(key)) {\n            this._allSelectionCache.push(item)\n          }\n        }\n      }\n      // Sync current selection to _allSelectedKeysSet. This is optional but\n      // can avoid unnecessary selectionChanged event when calling `resetAllSelection`\n      // again with the same selection.\n      this._allSelectedKeysSet.clear()\n      for (const key of this._allSelectionCache) {\n        this._allSelectedKeysSet.add(this._inner.getKey(key))\n      }\n    }\n\n    return this._allSelectionCache\n  }\n\n  resetAllSelection(selectedKeys: string[]) {\n    if (process.env.NODE_ENV === 'development') {\n      console.groupCollapsed('SelectionWithFilter.resetAllSelection')\n      console.log('selectedKeys', selectedKeys)\n      console.log('_allSelectedKeysSet', this._allSelectedKeysSet)\n      console.groupEnd()\n    }\n    // Check whether update can be avoided\n    let unChanged = true\n    let validSelectedKeysCount = 0\n    for (const key of selectedKeys) {\n      if (this._allItemsMap.has(key)) {\n        validSelectedKeysCount++\n        if (!this._allSelectedKeysSet.has(key)) {\n          unChanged = false\n          break\n        }\n      }\n    }\n    if (validSelectedKeysCount !== this._allSelectedKeysSet.size) {\n      unChanged = false\n    }\n    if (unChanged) {\n      return\n    }\n\n    this._allSelectedKeysSet.clear()\n    for (const key of selectedKeys) {\n      if (this._allItemsMap.has(key)) {\n        this._allSelectedKeysSet.add(key)\n      }\n    }\n    // Update selection subset\n    this._inner.setChangeEvents(false)\n    this._inner.setAllSelected(false)\n    for (const key of selectedKeys) {\n      if (this._itemKeysSet.has(key)) {\n        this._inner.setKeySelected(key, true, false)\n      }\n    }\n    this._inner.setChangeEvents(true, true)\n    this._triggerSelectionChanged() // Force trigger a selection change anyway\n  }\n\n  setAllSelectionSelected(isAllSelected: boolean) {\n    this._inner.setChangeEvents(false)\n    if (!isAllSelected) {\n      this._allSelectedKeysSet.clear()\n      this._inner.setAllSelected(false)\n    } else {\n      for (const key of this._allItemsMap.keys()) {\n        this._allSelectedKeysSet.add(key)\n      }\n      this._inner.setAllSelected(true)\n    }\n    this._inner.setChangeEvents(true, true)\n    this._triggerSelectionChanged() // Force trigger a selection change anyway\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/sqlFormatter/index.ts",
    "content": "import { format } from '@baurine/sql-formatter-plus'\n\nexport default function formatSql(sql?: string): string {\n  let formatedSQL = sql || ''\n  try {\n    formatedSQL = format(sql || '', { uppercase: true, language: 'tidb' })\n  } catch (err) {\n    console.log(err)\n    console.log(sql)\n  }\n  return formatedSQL\n}\n\n// ------------------\n// a hack way to do unit test for formatSQL method\nif (process.env.NODE_ENV === 'development' || process.env.E2E_TEST === 'true') {\n  function test() {\n    const oriSQLs = [\n      'select distinct `floor` ( `unix_timestamp` ( `summary_begin_time` ) ) as `begin_time` , `floor` ( `unix_timestamp` ( `summary_end_time` ) ) as `end_time` from `information_schema` . `cluster_statements_summary_history` order by `begin_time` desc , `end_time` desc',\n\n      'select `topics` . `id` from `topics` left outer join `categories` on `categories` . `id` = `topics` . `category_id` where ( `topics` . `archetype` <> ? ) and ( coalesce ( `categories` . `topic_id` , ? ) <> `topics` . `id` ) and `topics` . `visible` = true and ( `topics` . `deleted_at` is ? ) and ( `topics` . `category_id` is ? or `topics` . `category_id` in ( ... ) ) and ( `topics` . `category_id` != ? ) and `topics` . `closed` = false and `topics` . `archived` = false and ( `topics` . `created_at` > ? ) order by `rand` ( ) limit ?',\n\n      'update `app_tidb_en`.`useranyone` set `Today` = \\'2022-12-28 15:29:35.604\\', `DigTreasureNoPrizeCount` = 1, `DigTreasureDrawCount` = 4, `IntegralExchangeSendName` = NULL, `IntegralExchangeSendPhone` = NULL, `IntegralExchangeSendAddress` = NULL, `DayResetTaskData` = \\'{\"38\":{\"Value\":5,\"HasGet\":true,\"GetCount\":5},\"23\":{\"Value\":1,\"HasGet\":true,\"GetCount\":0},\"15\":{\"Value\":10,\"HasGet\":true,\"GetCount\":1},\"16\":{\"Value\":30,\"HasGet\":true,\"GetCount\":1},\"17\":{\"Value\":60,\"HasGet\":true,\"GetCount\":1},\"22\":{\"Value\":1,\"HasGet\":false,\"GetCount\":0}}\\',`NotDayResetTaskData` = \\'{\"27\":{\"Value\":1,\"HasGet\":true,\"GetCount\":0},\"34\":{\"Value\":1,\"HasGet\":true,\"GetCount\":0},\"28\":{\"Value\":1,\"HasGet\":true,\"GetCount\":0},\"18\":{\"Value\":1,\"HasGet\":false,\"GetCount\":0},\"45\":{\"Value\":1,\"HasGet\":true,\"GetCount\":1}}\\', `Id` = 11111 WHERE `Id` = 11111 LIMIT 1;',\n\n      'insert into event_log(`all_json`,`track_id`,`distinct_id`,`lib`,`event`,`type`,`created_at`,`date`,`hour`,`user_agent`,`host`,`connection`,`pragma`,`cache_control`,`accept`,`accept_encoding`,`accept_language`,`ip`,`ip_city`,`ip_asn`,`url`,`referrer`,`remark`,`ua_platform`,`ua_browser`,`ua_version`,`ua_language`) values (\\'{   \"login_id\": \"11111\",   \"time\": 1640768088867,   \"anonymous_id\": \"12345\",   \"event\": \"$AppViewScreen\",   \"_track_id\": 12345,   \"_flush_time\": 1640768092287,   \"properties\": {     \"$os\": \"iOS\",     \"$app_id\": \"com.app\",     \"$screen_width\": 375,     \"$app_version\": \"1.0.0\",     \"deviceLang\": \"zh_TW\",     \"$is_first_day\": false,     \"appChannel\": \"ios_apple\",     \"$device_id\": \"12345\",     \"$model\": \"iPhone9,4\",     \"$carrier\": \"Chunghwa Telecom LDM\",     \"appCoreVer\": \"3\",     \"$network_type\": \"3G\",     \"$app_name\": \"app_name\",     \"$wifi\": false,     \"appProductX\": \"172\",     \"$timezone_offset\": -480,     \"$url\": \"app_name.CDPageViewController\",     \"mac\": \"\",     \"appVersionCode\": \"1\",     \"$screen_height\": 667,     \"appId\": \"11111\",     \"$referrer\": \"app.CDPageBeforeViewController\",     \"$lib_method\": \"autoTrack\",     \"$screen_name\": \"app.CDPageViewController\",     \"$lib_version\": \"1.0.0\",     \"$os_version\": \"14.8.1\",     \"$lib\": \"iOS\",     \"appLangId\": \"2\",     \"$manufacturer\": \"Apple\",     \"idfa\": \"00000000-0000-0000-0000-000000000000\"   },   \"lib\": {     \"$lib_detail\": \"app.CDPageViewController######\",     \"$lib_version\": \"1.0.0\",     \"$lib\": \"iOS\",     \"$app_version\": \"1.0.0\",     \"$lib_method\": \"autoTrack\"   },   \"distinct_id\": \"1111\",   \"type\": \"track\" }\\',\"1111\",\"111\",\"iOS\",\"$AppViewScreen\",\"track\",111,timestamp(\"2022-12-29 00:00:00.000000\"),16,\"SensorsAnalytics iOS SDK\",\"log.app.com\",NULL,NULL,NULL,\"\",\"gzip, deflate, br\",\"\",\"111.111\",\"{}\",\"{}\",\"url\",NULL,\"online\",\"\",\"\",\"\",\"\");'\n    ]\n\n    const expects = [\n      'SELECT\\n  DISTINCT `floor` (`unix_timestamp` (`summary_begin_time`)) AS `begin_time`,\\n  `floor` (`unix_timestamp` (`summary_end_time`)) AS `end_time`\\nFROM\\n  `information_schema`.`cluster_statements_summary_history`\\nORDER BY\\n  `begin_time` DESC,\\n  `end_time` DESC',\n\n      'SELECT\\n  `topics`.`id`\\nFROM\\n  `topics`\\n  LEFT OUTER JOIN `categories` ON `categories`.`id` = `topics`.`category_id`\\nWHERE\\n  (`topics`.`archetype` <> ?)\\n  AND (\\n    coalesce (`categories`.`topic_id`, ?) <> `topics`.`id`\\n  )\\n  AND `topics`.`visible` = TRUE\\n  AND (`topics`.`deleted_at` IS ?)\\n  AND (\\n    `topics`.`category_id` IS ?\\n    OR `topics`.`category_id` IN (...)\\n  )\\n  AND (`topics`.`category_id` != ?)\\n  AND `topics`.`closed` = false\\n  AND `topics`.`archived` = false\\n  AND (`topics`.`created_at` > ?)\\nORDER BY\\n  `rand` ()\\nLIMIT\\n  ?',\n\n      'UPDATE\\n  `app_tidb_en`.`useranyone`\\nSET\\n  `Today` = \\'2022-12-28 15:29:35.604\\',\\n  `DigTreasureNoPrizeCount` = 1,\\n  `DigTreasureDrawCount` = 4,\\n  `IntegralExchangeSendName` = NULL,\\n  `IntegralExchangeSendPhone` = NULL,\\n  `IntegralExchangeSendAddress` = NULL,\\n  `DayResetTaskData` = \\'{\"38\":{\"Value\":5,\"HasGet\":true,\"GetCount\":5},\"23\":{\"Value\":1,\"HasGet\":true,\"GetCount\":0},\"15\":{\"Value\":10,\"HasGet\":true,\"GetCount\":1},\"16\":{\"Value\":30,\"HasGet\":true,\"GetCount\":1},\"17\":{\"Value\":60,\"HasGet\":true,\"GetCount\":1},\"22\":{\"Value\":1,\"HasGet\":false,\"GetCount\":0}}\\',\\n  `NotDayResetTaskData` = \\'{\"27\":{\"Value\":1,\"HasGet\":true,\"GetCount\":0},\"34\":{\"Value\":1,\"HasGet\":true,\"GetCount\":0},\"28\":{\"Value\":1,\"HasGet\":true,\"GetCount\":0},\"18\":{\"Value\":1,\"HasGet\":false,\"GetCount\":0},\"45\":{\"Value\":1,\"HasGet\":true,\"GetCount\":1}}\\',\\n  `Id` = 11111\\nWHERE\\n  `Id` = 11111\\nLIMIT\\n  1;',\n\n      'INSERT INTO\\n  event_log(\\n    `all_json`,\\n    `track_id`,\\n    `distinct_id`,\\n    `lib`,\\n    `event`,\\n    `type`,\\n    `created_at`,\\n    `date`,\\n    `hour`,\\n    `user_agent`,\\n    `host`,\\n    `connection`,\\n    `pragma`,\\n    `cache_control`,\\n    `accept`,\\n    `accept_encoding`,\\n    `accept_language`,\\n    `ip`,\\n    `ip_city`,\\n    `ip_asn`,\\n    `url`,\\n    `referrer`,\\n    `remark`,\\n    `ua_platform`,\\n    `ua_browser`,\\n    `ua_version`,\\n    `ua_language`\\n  )\\nVALUES\\n  (\\n    \\'{   \"login_id\": \"11111\",   \"time\": 1640768088867,   \"anonymous_id\": \"12345\",   \"event\": \"$AppViewScreen\",   \"_track_id\": 12345,   \"_flush_time\": 1640768092287,   \"properties\": {     \"$os\": \"iOS\",     \"$app_id\": \"com.app\",     \"$screen_width\": 375,     \"$app_version\": \"1.0.0\",     \"deviceLang\": \"zh_TW\",     \"$is_first_day\": false,     \"appChannel\": \"ios_apple\",     \"$device_id\": \"12345\",     \"$model\": \"iPhone9,4\",     \"$carrier\": \"Chunghwa Telecom LDM\",     \"appCoreVer\": \"3\",     \"$network_type\": \"3G\",     \"$app_name\": \"app_name\",     \"$wifi\": false,     \"appProductX\": \"172\",     \"$timezone_offset\": -480,     \"$url\": \"app_name.CDPageViewController\",     \"mac\": \"\",     \"appVersionCode\": \"1\",     \"$screen_height\": 667,     \"appId\": \"11111\",     \"$referrer\": \"app.CDPageBeforeViewController\",     \"$lib_method\": \"autoTrack\",     \"$screen_name\": \"app.CDPageViewController\",     \"$lib_version\": \"1.0.0\",     \"$os_version\": \"14.8.1\",     \"$lib\": \"iOS\",     \"appLangId\": \"2\",     \"$manufacturer\": \"Apple\",     \"idfa\": \"00000000-0000-0000-0000-000000000000\"   },   \"lib\": {     \"$lib_detail\": \"app.CDPageViewController######\",     \"$lib_version\": \"1.0.0\",     \"$lib\": \"iOS\",     \"$app_version\": \"1.0.0\",     \"$lib_method\": \"autoTrack\"   },   \"distinct_id\": \"1111\",   \"type\": \"track\" }\\',\\n    \"1111\",\\n    \"111\",\\n    \"iOS\",\\n    \"$AppViewScreen\",\\n    \"track\",\\n    111,\\n    timestamp(\"2022-12-29 00:00:00.000000\"),\\n    16,\\n    \"SensorsAnalytics iOS SDK\",\\n    \"log.app.com\",\\n    NULL,\\n    NULL,\\n    NULL,\\n    \"\",\\n    \"gzip, deflate, br\",\\n    \"\",\\n    \"111.111\",\\n    \"{}\",\\n    \"{}\",\\n    \"url\",\\n    NULL,\\n    \"online\",\\n    \"\",\\n    \"\",\\n    \"\",\\n    \"\"\\n  );'\n    ]\n\n    oriSQLs.forEach((s, idx) => {\n      const f = formatSql(s)\n      if (f !== expects[idx]) {\n        console.log('expected:', expects[idx])\n        console.log('received:', f)\n        throw new Error(`Format sql failed!, idx: ${idx}`)\n      }\n    })\n  }\n\n  test()\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/store.ts",
    "content": "import { InfoInfoResponse, InfoWhoAmIResponse } from '@lib/client'\nimport { Store } from 'pullstate'\n\ninterface StoreState {\n  whoAmI?: InfoWhoAmIResponse\n  appInfo?: InfoInfoResponse\n}\n\n// sync with /tidb-dashboard/pkg/apiserver/utils/ngm.go NgmState\nexport enum NgmState {\n  NotSupported = 'not_supported',\n  NotStarted = 'not_started',\n  Started = 'started'\n}\n\nexport const store = new Store<StoreState>({})\n\nexport const useIsWriteable = () =>\n  store.useState((s) => Boolean(s.whoAmI && s.whoAmI.is_writeable))\n\nexport const useIsFeatureSupport = (feature: string) =>\n  store.useState(\n    (s) => (s.appInfo?.supported_features?.indexOf(feature) ?? -1) !== -1\n  )\n\nexport const useNgmState = () => store.useState((s) => s.appInfo?.ngm_state)\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/tableColumnFactory.tsx",
    "content": "import { Tooltip } from 'antd'\nimport { max as _max, capitalize } from 'lodash'\nimport {\n  IColumn,\n  ColumnActionsMode\n} from 'office-ui-fabric-react/lib/DetailsList'\nimport React from 'react'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\n\nimport {\n  Bar,\n  Pre,\n  TextWithInfo,\n  TextWrap,\n  DateTime,\n  HighlightSQL,\n  IColumnKeys\n} from '@lib/components'\nimport { formatNumByUnit } from './format'\n\nexport type DerivedField<T> = {\n  displayTransKey?: string // it is same as avg field name default\n  sources: T[]\n}\n\nexport type DerivedBar = DerivedField<{\n  tooltipPrefix: string\n  fieldName: string\n}>\n\nexport type DerivedCol = DerivedField<string>\n\nexport function formatVal(val: number, unit: string, decimals: number = 1) {\n  return formatNumByUnit(val, unit, decimals)\n}\n\nexport function TranslatedColumnName(\n  transPrefix: string,\n  fieldName: string\n): any {\n  const fullTransKey = `${transPrefix}.${fieldName}`\n  return <TextWithInfo.TransKey transKey={fullTransKey} />\n}\n\nexport class Column {\n  constructor(\n    private config: IColumn,\n    private options: { transPrefix?: string } = {}\n  ) {}\n\n  getConfig(): IColumn {\n    const { transPrefix } = this.options\n    return {\n      ...this.config,\n      name: transPrefix\n        ? TranslatedColumnName(transPrefix, this.config.name)\n        : this.config.name\n    }\n  }\n\n  setConfig(config: IColumn) {\n    this.config = config\n    return this\n  }\n\n  patchConfig(config: Partial<IColumn>) {\n    this.config = { ...this.config, ...config }\n    return this\n  }\n}\n\nexport class ColumnArray {\n  controls: Column[]\n\n  // could be IColumn or Column mixed array type, not (IColumn | Column)[] type\n  constructor(controlsConfig: any[]) {\n    this.controls = controlsConfig.map((c) =>\n      c instanceof Column ? c : new Column(c)\n    )\n  }\n\n  getConfig(): IColumn[] {\n    return this.controls.map((c) => c.getConfig())\n  }\n}\n\nexport class TableColumnFactory {\n  bar: BarColumn = new BarColumn(this)\n\n  private allowColumnsMap: { [f: string]: boolean }\n\n  constructor(\n    private transPrefix: string,\n    private allowColumns: string[] = []\n  ) {\n    this.allowColumnsMap = allowColumns.reduce((prev, f) => {\n      prev[f] = true\n      return prev\n    }, {} as { [f: string]: boolean })\n  }\n\n  control(config: IColumn): Column {\n    return new Column(config, { transPrefix: this.transPrefix })\n  }\n\n  array(controlsConfig: any[]): ColumnArray {\n    return new ColumnArray(controlsConfig)\n  }\n\n  columns(controlsConfig: any[]): IColumn[] {\n    const columns = this.array(controlsConfig).getConfig()\n    const needFilterColumn = this.allowColumns.length\n    if (!needFilterColumn) {\n      return columns\n    }\n    return columns.filter((c) => this.allowColumnsMap[c.fieldName!])\n  }\n\n  textWithTooltip<T extends string, U extends { [K in T]?: any }>(\n    fieldName: T,\n    _rows?: U[]\n  ): Column {\n    return this.control({\n      ...this.getDefaultColumnConfig(fieldName),\n      maxWidth: 150,\n      onRender: (rec: U) => (\n        <Tooltip title={rec[fieldName]} data-e2e=\"text_with_tooltip\">\n          <TextWrap>{rec[fieldName]}</TextWrap>\n        </Tooltip>\n      )\n    })\n  }\n\n  singleBar<T extends string, U extends { [K in T]?: number }>(\n    fieldName: T,\n    unit: string,\n    rows?: U[]\n  ): Column {\n    const capacity = rows ? _max(rows.map((v) => v[fieldName])) ?? 0 : 0\n    return this.control({\n      ...this.getDefaultColumnConfig(fieldName),\n      minWidth: 140,\n      maxWidth: 200,\n      columnActionsMode: ColumnActionsMode.clickable,\n      onRender: (rec: U) => {\n        const fmtVal = formatVal(rec[fieldName]!, unit)\n        return (\n          <Bar textWidth={70} value={rec[fieldName]!} capacity={capacity}>\n            {fmtVal}\n          </Bar>\n        )\n      }\n    })\n  }\n\n  multipleBar<T>(barsConfig: DerivedBar, unit: string, rows?: T[]): Column {\n    const {\n      displayTransKey,\n      sources: [avg, max, min]\n    } = barsConfig\n\n    const tooltipPrefixLens: number[] = []\n\n    tooltipPrefixLens.push(avg.tooltipPrefix.length)\n    tooltipPrefixLens.push(max.tooltipPrefix.length)\n    if (min) {\n      tooltipPrefixLens.push(min.tooltipPrefix.length)\n    }\n\n    const maxTooltipPrefixLen = _max(tooltipPrefixLens) || 0\n\n    const capacity = rows ? _max(rows.map((v) => v[max.fieldName])) ?? 0 : 0\n\n    return this.control({\n      ...this.getDefaultColumnConfig(avg.fieldName),\n      name: displayTransKey || avg.fieldName,\n      minWidth: 140,\n      maxWidth: 200,\n      columnActionsMode: ColumnActionsMode.clickable,\n      onRender: (rec) => {\n        const avgVal = rec[avg.fieldName]\n        const maxVal = rec[max.fieldName]\n        const minVal = min ? rec[min.fieldName] : undefined\n        const tooltips = [avg, min, max]\n          .filter((el) => el !== undefined)\n          .map((bar) => {\n            const prefix = capitalize(bar!.tooltipPrefix + ':').padEnd(\n              maxTooltipPrefixLen + 2\n            )\n            const fmtVal = formatVal(rec[bar!.fieldName], unit)\n            return `${prefix}${fmtVal}`\n          })\n          .join('\\n')\n        return (\n          <Tooltip title={<Pre>{tooltips.trim()}</Pre>}>\n            <Bar\n              textWidth={70}\n              value={avgVal}\n              max={maxVal}\n              min={minVal}\n              capacity={capacity as number}\n            >\n              {formatVal(avgVal, unit)}\n            </Bar>\n          </Tooltip>\n        )\n      }\n    })\n  }\n\n  timestamp<T extends string, U extends { [K in T]?: number }>(\n    fieldName: T,\n    _rows?: U[]\n  ): Column {\n    return this.control({\n      ...this.getDefaultColumnConfig(fieldName),\n      maxWidth: 150,\n      columnActionsMode: ColumnActionsMode.clickable,\n      onRender: (rec: U) => (\n        <TextWrap>\n          <DateTime.Calendar unixTimestampMs={rec[fieldName]! * 1000} />\n        </TextWrap>\n      )\n    })\n  }\n\n  sqlText<T extends string, U extends { [K in T]?: string }>(\n    fieldName: T,\n    showFullSQL?: boolean,\n    _rows?: U[]\n  ): Column {\n    return this.control({\n      ...this.getDefaultColumnConfig(fieldName),\n      maxWidth: 500,\n      isMultiline: showFullSQL,\n      onRender: (rec: U) =>\n        showFullSQL ? (\n          <TextWrap multiline>\n            <HighlightSQL sql={rec[fieldName] || ''} maxLen={1000} />\n          </TextWrap>\n        ) : (\n          <Tooltip\n            title={\n              <HighlightSQL\n                sql={rec[fieldName] || ''}\n                theme=\"dark\"\n                maxLen={1000}\n              />\n            }\n            placement=\"right\"\n            overlayInnerStyle={{ maxHeight: '700px', overflowY: 'auto' }}\n          >\n            <TextWrap>\n              <HighlightSQL sql={rec[fieldName] || ''} compact maxLen={1000} />\n            </TextWrap>\n          </Tooltip>\n        )\n    })\n  }\n\n  private getDefaultColumnConfig(fieldName: string) {\n    return {\n      name: fieldName,\n      key: fieldName,\n      fieldName: fieldName,\n      minWidth: 100\n    }\n  }\n}\n\nexport class BarColumn {\n  constructor(public factory: TableColumnFactory) {}\n\n  single<T extends string, U extends { [K in T]?: number }>(\n    fieldName: T,\n    unit: string,\n    rows?: U[]\n  ) {\n    return this.factory.singleBar(fieldName, unit, rows)\n  }\n\n  multiple<T>(bars: DerivedBar, unit: string, rows?: T[]) {\n    return this.factory.multipleBar(bars, unit, rows)\n  }\n}\n\n////////////////////////////////////////////\n\nexport type DerivedFields = Record<\n  string,\n  DerivedBar['sources'] | DerivedCol['sources']\n>\n\nexport function genDerivedBarSources(\n  avg: string,\n  max: string,\n  min?: string\n): DerivedBar['sources'] {\n  const res = [\n    {\n      tooltipPrefix: 'mean',\n      fieldName: avg\n    },\n    {\n      tooltipPrefix: 'max',\n      fieldName: max\n    }\n  ]\n  if (min) {\n    res.push({\n      tooltipPrefix: 'min',\n      fieldName: min\n    })\n  }\n  return res\n}\n\nfunction isDerivedBarSources(v: any): v is DerivedBar['sources'] {\n  return !!v[0].fieldName\n}\n\nexport function getSelectedFields(\n  visibleColumnKeys: IColumnKeys,\n  derivedFields: DerivedFields\n) {\n  let fields: string[] = []\n  let sources: DerivedFields[keyof DerivedFields]\n  for (const columnKey in visibleColumnKeys) {\n    if (visibleColumnKeys[columnKey]) {\n      if ((sources = derivedFields[columnKey])) {\n        if (isDerivedBarSources(sources)) {\n          fields.push(...sources.map((b) => b.fieldName))\n        } else {\n          fields.push(...sources)\n        }\n      } else {\n        fields.push(columnKey)\n      }\n    }\n  }\n  return fields\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/tableColumns.tsx",
    "content": "import { Tooltip } from 'antd'\nimport { max } from 'lodash'\nimport { IColumn } from 'office-ui-fabric-react/lib/DetailsList'\nimport React from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { getValueFormat } from '@baurine/grafana-value-formats'\n\nimport { Bar, Pre } from '@lib/components'\nimport { addTranslationResource } from './i18n'\nimport { TranslatedColumnName } from './tableColumnFactory'\n\nconst translations = {\n  en: {\n    name: 'Name',\n    value: 'Value',\n    time: 'Time',\n    desc: 'Description'\n  },\n  zh: {\n    name: '名称',\n    value: '值',\n    time: '时间',\n    desc: '描述'\n  }\n}\n\nfor (const key in translations) {\n  addTranslationResource(key, {\n    component: {\n      commonColumn: translations[key]\n    }\n  })\n}\n\nfunction TransText({\n  transKey,\n  noFallback\n}: {\n  transKey: string\n  noFallback?: boolean\n}) {\n  const { t } = useTranslation()\n  let opt\n  if (noFallback) {\n    opt = {\n      defaultValue: '',\n      fallbackLng: '_'\n    }\n  }\n  return <span>{t(transKey, opt)}</span>\n}\n\n////////////////////////////////////\nconst TRANS_KEY_PREFIX = 'component.commonColumn'\n\nfunction fieldsKeyColumn(transKeyPrefix: string): IColumn {\n  return {\n    name: TranslatedColumnName(TRANS_KEY_PREFIX, 'name'),\n    key: 'key',\n    minWidth: 200,\n    maxWidth: 400,\n    onRender: (rec) => {\n      return (\n        <div style={{ paddingLeft: (rec.indentLevel || 0) * 24 }}>\n          {rec.keyDisplay ?? (\n            <TransText transKey={`${transKeyPrefix}${rec.key}`} />\n          )}\n        </div>\n      )\n    }\n  }\n}\n\nfunction fieldsValueColumn(): IColumn {\n  return {\n    name: TranslatedColumnName(TRANS_KEY_PREFIX, 'value'),\n    key: 'value',\n    fieldName: 'value',\n    minWidth: 150,\n    maxWidth: 250\n  }\n}\n\nfunction fieldsTimeValueColumn(\n  rows?: { avg?: number; min?: number; max?: number; value?: number }[]\n): IColumn {\n  const capacity = rows\n    ? max(rows.map((v) => max([v.max, v.min, v.avg, v.value]))) ?? 0\n    : 0\n  return {\n    name: TranslatedColumnName(TRANS_KEY_PREFIX, 'time'),\n    key: 'time',\n    minWidth: 150,\n    maxWidth: 200,\n    onRender: (rec) => {\n      const tooltipContent: string[] = []\n      if (rec.avg) {\n        tooltipContent.push(`Mean: ${getValueFormat('ns')(rec.avg, 1)}`)\n      }\n      if (rec.min) {\n        tooltipContent.push(`Min:  ${getValueFormat('ns')(rec.min, 1)}`)\n      }\n      if (rec.max) {\n        tooltipContent.push(`Max:  ${getValueFormat('ns')(rec.max, 1)}`)\n      }\n      const bar = (\n        <Bar\n          textWidth={70}\n          value={rec.avg ?? rec.value}\n          max={rec.max}\n          min={rec.min}\n          capacity={capacity}\n        >\n          {rec.avg != null\n            ? getValueFormat('ns')(rec.avg, 1)\n            : getValueFormat('ns')(rec.value, 1)}\n        </Bar>\n      )\n      if (tooltipContent.length > 0) {\n        return (\n          <Tooltip title={<Pre>{tooltipContent.join('\\n').trim()}</Pre>}>\n            {bar}\n          </Tooltip>\n        )\n      } else {\n        return bar\n      }\n    }\n  }\n}\n\nfunction fieldsDescriptionColumn(transKeyPrefix: string): IColumn {\n  return {\n    name: TranslatedColumnName(TRANS_KEY_PREFIX, 'desc'),\n    key: 'description',\n    minWidth: 150,\n    maxWidth: 300,\n    onRender: (rec) => {\n      const content = (\n        <TransText\n          transKey={`${transKeyPrefix}${rec.key}_tooltip`}\n          noFallback\n        />\n      )\n      return (\n        <Tooltip title={content}>\n          <span>{content}</span>\n        </Tooltip>\n      )\n    }\n  }\n}\n\n////////////////////////////////////////////\n\nexport function valueColumns(transKeyPrefix: string) {\n  return [\n    fieldsKeyColumn(transKeyPrefix),\n    fieldsValueColumn(),\n    fieldsDescriptionColumn(transKeyPrefix)\n  ]\n}\n\nexport function timeValueColumns(\n  transKeyPrefix: string,\n  items?: { avg?: number; min?: number; max?: number; value?: number }[]\n) {\n  return [\n    fieldsKeyColumn(transKeyPrefix),\n    fieldsTimeValueColumn(items),\n    fieldsDescriptionColumn(transKeyPrefix)\n  ]\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/telemetry.ts",
    "content": "import mixpanel, { Config } from 'mixpanel-browser'\nimport { getPathInLocationHash } from './routing'\n\nexport { mixpanel }\n\nfunction init(apiHost?: string, token?: string) {\n  let options: Partial<Config> = {\n    autotrack: false,\n    opt_out_tracking_by_default: true,\n    batch_requests: true,\n    persistence: 'localStorage',\n    property_blacklist: [\n      '$initial_referrer',\n      '$initial_referring_domain',\n      '$referrer',\n      '$referring_domain'\n    ],\n    debug: process.env.NODE_ENV === 'development'\n  }\n  if (apiHost) {\n    options['api_host'] = apiHost\n  }\n  mixpanel.init(token || '00000000000000000000000000000000', options)\n  // disable mixpanel to report data immediately\n  mixpanel.opt_out_tracking()\n}\n\nfunction enable(\n  dashboardVersion: string,\n  extraData: { [k: string]: any } = {}\n) {\n  mixpanel.register({\n    $current_url: getPathInLocationHash(),\n    dashboard_version: dashboardVersion,\n    ...extraData\n  })\n  mixpanel.opt_in_tracking()\n}\n\nfunction identifyUser(userId: string) {\n  mixpanel.identify(userId)\n}\n\n// TODO: refine naming\nfunction trackRouteChange(curRoute: string) {\n  mixpanel.register({\n    $current_url: curRoute\n  })\n  mixpanel.track('Page Change')\n}\n\nexport default {\n  init,\n  enable,\n  identifyUser,\n  trackRouteChange\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/timezone.ts",
    "content": "import dayjs from 'dayjs'\nimport utc from 'dayjs/plugin/utc'\n\ndayjs.extend(utc)\n\n// value range: -12~14\n// 8 means UTC+08:00\nlet _tz: number | null = null\n\nfunction getLocalTimeZone(): number {\n  // in UTC+08:00 time zone, `dayjs().utcOffset()` will get 480\n  // while `new Date().getTimezoneOffset()` will get -480\n  return dayjs().utcOffset() / 60\n}\n\nfunction getTimeZone() {\n  if (_tz === null) {\n    _tz = getLocalTimeZone()\n  }\n  return _tz\n}\n\nfunction getTimeZoneStr() {\n  const z = getTimeZone()\n  if (getTimeZone() >= 0) {\n    return `utc+${z}`\n  }\n  return `utc${z}`\n}\n\nfunction setTimeZone(timezone: number) {\n  // https://en.wikipedia.org/wiki/List_of_UTC_offsets\n  if (timezone >= -12 && timezone <= 14) {\n    _tz = timezone\n    return\n  }\n  throw new Error('timezone value must be range in -12~14.')\n}\n\nexport default { getTimeZone, getTimeZoneStr, setTimeZone }\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/useCache.ts",
    "content": "import { createContext, useRef } from 'react'\n\ntype CacheItem = {\n  expireAt: number\n  data: any\n}\n\ntype CacheStorage = Record<string, CacheItem>\n\nconst ONE_HOUR_TIME = 1 * 60 * 60 * 1000\n\nexport const CacheContext = createContext<CacheMgr | undefined>(undefined)\n\nexport class CacheMgr {\n  private cache: CacheStorage = {}\n  private cacheItemKeys: string[] = []\n  private capacity: number\n  private globalExpire: number\n\n  constructor(capacity: number = 1, globalExpire: number = ONE_HOUR_TIME) {\n    this.capacity = capacity\n    this.globalExpire = globalExpire\n  }\n\n  get(key: string): any {\n    const item = this.cache[key]\n    if (item === undefined) {\n      return undefined\n    }\n    if (item.expireAt < new Date().valueOf()) {\n      this.remove(key)\n      return undefined\n    }\n    return item.data\n  }\n\n  set(key: string, val: any, expire?: number) {\n    const curTime = new Date().valueOf()\n    let expireAt: number\n    if (expire) {\n      expireAt = curTime + expire\n    } else {\n      expireAt = curTime + this.globalExpire\n    }\n    this.cache[key] = {\n      expireAt,\n      data: val\n    }\n\n    // put the latest key in the end of cacheItemKeys\n    this.cacheItemKeys = this.cacheItemKeys.filter((k) => k !== key).concat(key)\n    // if size beyonds the capacity\n    // remove the old ones\n    while (this.capacity > 0 && this.cacheItemKeys.length > this.capacity) {\n      this.remove(this.cacheItemKeys[0])\n    }\n  }\n\n  remove(key: string) {\n    delete this.cache[key]\n    this.cacheItemKeys = this.cacheItemKeys.filter((k) => k !== key)\n  }\n\n  clear() {\n    this.cache = {}\n    this.cacheItemKeys = []\n  }\n}\n\nexport default function useCache(\n  capacity: number = 1,\n  globalExpire: number = ONE_HOUR_TIME\n): CacheMgr {\n  const cache = useRef(new CacheMgr(capacity, globalExpire))\n  return cache.current\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/useCacheItemIndex.ts",
    "content": "import { useMemoizedFn } from 'ahooks'\nimport { CacheMgr } from './useCache'\n\nexport default function useCacheItemIndex(cacheMgr?: CacheMgr) {\n  const CLICKED_ITEM_INDEX = 'clicked_item_index'\n\n  const saveClickedItemIndex = useMemoizedFn((idx: number) => {\n    cacheMgr?.set(CLICKED_ITEM_INDEX, idx)\n  })\n\n  const getClickedItemIndex = useMemoizedFn(() => {\n    return Number(cacheMgr?.get(CLICKED_ITEM_INDEX) || -1)\n  })\n\n  return {\n    saveClickedItemIndex,\n    getClickedItemIndex\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/useChange.ts",
    "content": "import { useMemoizedFn } from 'ahooks'\nimport { DependencyList, useEffect } from 'react'\nimport { useDeepCompareEffect } from 'react-use'\n\n// useChange calls fn when changeList changes.\n// It's very similar to useEffect, but does not require fn and its dependencies to be matched.\nexport function useChange(fn: () => any, changeList: DependencyList) {\n  const mfn = useMemoizedFn(fn)\n  useEffect(() => {\n    mfn()\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, changeList)\n}\n\nexport function useDeepCompareChange(\n  fn: () => any,\n  changeList: DependencyList\n) {\n  const mfn = useMemoizedFn(fn)\n  useDeepCompareEffect(() => {\n    mfn()\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, changeList)\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/useClientRequest.ts",
    "content": "import { useMount, useUnmount, useMemoizedFn } from 'ahooks'\nimport { useState, useRef, useEffect } from 'react'\nimport axios, { CancelToken, AxiosPromise, CancelTokenSource } from 'axios'\n\nimport { ReqConfig } from '@lib/types'\n\ninterface ClientReqConfig extends ReqConfig {\n  cancelToken: CancelToken\n}\n\nexport interface RequestFactory<T> {\n  (reqConfig: ClientReqConfig): AxiosPromise<T>\n}\n\ninterface Options {\n  immediate?: boolean\n  afterRequest?: () => void\n  beforeRequest?: () => void\n}\n\ninterface State<T> {\n  isLoading: boolean\n  data?: T\n  error?: any\n}\n\nexport function useClientRequest<T>(\n  reqFactory?: RequestFactory<T>,\n  options?: Options\n) {\n  const {\n    immediate = true,\n    afterRequest = null,\n    beforeRequest = null\n  } = options || {}\n\n  const [state, setState] = useState<State<T>>({\n    isLoading: immediate\n  })\n\n  // If `cancelTokenSource` is null, it means there is no running requests.\n  const cancelTokenSource = useRef<CancelTokenSource | null>(null)\n  const mounted = useRef(false)\n\n  const sendRequest = useMemoizedFn(async () => {\n    if (!mounted.current) {\n      return\n    }\n    if (cancelTokenSource.current) {\n      return\n    }\n\n    beforeRequest && beforeRequest()\n\n    cancelTokenSource.current = axios.CancelToken.source()\n\n    setState((s) => ({\n      ...s,\n      isLoading: true,\n      error: undefined\n    }))\n\n    try {\n      if (!reqFactory) {\n        setState({\n          isLoading: false\n        })\n      } else {\n        const reqConfig: ClientReqConfig = {\n          cancelToken: cancelTokenSource.current.token,\n          handleError: 'custom' // handle the error by component self\n        }\n        const resp = await reqFactory(reqConfig)\n        if (mounted.current) {\n          setState({\n            data: resp.data,\n            isLoading: false\n          })\n        }\n      }\n    } catch (e) {\n      if (mounted.current) {\n        setState({\n          error: e,\n          isLoading: false\n        })\n      }\n    }\n\n    cancelTokenSource.current = null\n\n    afterRequest && afterRequest()\n  })\n\n  useMount(() => {\n    mounted.current = true\n    if (immediate) {\n      sendRequest()\n    }\n  })\n\n  useUnmount(() => {\n    mounted.current = false\n    if (cancelTokenSource.current != null) {\n      cancelTokenSource.current.cancel()\n      cancelTokenSource.current = null\n    }\n  })\n\n  return {\n    ...state,\n    sendRequest\n  }\n}\n\ninterface OptionsWithPolling<T> extends Options {\n  pollingInterval?: number\n  shouldPoll?: ((data: T) => boolean) | null\n}\n\nexport function useClientRequestWithPolling<T = any>(\n  reqFactory: RequestFactory<T>,\n  options?: OptionsWithPolling<T>\n) {\n  const {\n    pollingInterval = 1000,\n    shouldPoll = null,\n    afterRequest = null,\n    beforeRequest = null,\n    immediate = true\n  } = options || {}\n  const mounted = useRef(false)\n  const pollingTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n  const scheduleNextPoll = () => {\n    if (pollingTimer.current == null && mounted.current) {\n      pollingTimer.current = setTimeout(() => {\n        retRef.current.sendRequest()\n        pollingTimer.current = null\n      }, pollingInterval)\n    }\n  }\n\n  const cancelNextPoll = () => {\n    if (pollingTimer.current != null) {\n      clearTimeout(pollingTimer.current)\n      pollingTimer.current = null\n    }\n  }\n\n  const myBeforeRequest = () => {\n    beforeRequest?.()\n    cancelNextPoll()\n  }\n\n  const myAfterRequest = () => {\n    let triggerPoll = true\n    if (retRef.current.error) {\n      triggerPoll = false\n    } else if (retRef.current.data && shouldPoll) {\n      triggerPoll = shouldPoll(retRef.current.data)\n    }\n    if (triggerPoll) {\n      scheduleNextPoll()\n    }\n    afterRequest?.()\n  }\n\n  const ret = useClientRequest(reqFactory, {\n    immediate,\n    beforeRequest: myBeforeRequest,\n    afterRequest: myAfterRequest\n  })\n\n  const retRef = useRef(ret)\n\n  useEffect(() => {\n    retRef.current = ret\n  }, [ret])\n\n  useMount(() => {\n    mounted.current = true\n  })\n\n  useUnmount(() => {\n    mounted.current = false\n    cancelNextPoll()\n  })\n\n  return ret\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/useOrderState.ts",
    "content": "import { useState, useMemo } from 'react'\n\nimport { useVersionedLocalStorageState } from './useVersionedLocalStorageState'\n\nexport interface IOrderOptions {\n  orderBy: string\n  desc: boolean\n}\n\nexport default function useOrderState(\n  storeKeyPrefix: string,\n  needSave: boolean,\n  options: IOrderOptions\n) {\n  const storeKey = `${storeKeyPrefix}.order_options`\n  const [memoryOrderOptions, setMemoryOrderOptions] = useState(options)\n  const [localOrderOptions, setLocalOrderOptions] =\n    useVersionedLocalStorageState(storeKey, { defaultValue: options })\n  const orderOptions = useMemo(\n    () => (needSave ? localOrderOptions : memoryOrderOptions),\n    [needSave, memoryOrderOptions, localOrderOptions]\n  )\n\n  function changeOrder(orderBy: string, desc: boolean) {\n    if (needSave) {\n      setLocalOrderOptions({\n        orderBy,\n        desc\n      })\n    } else {\n      setMemoryOrderOptions({\n        orderBy,\n        desc\n      })\n    }\n  }\n\n  return {\n    orderOptions,\n    changeOrder\n  }\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/useQueryParams.ts",
    "content": "import { useMemo } from 'react'\nimport { useLocation } from 'react-router-dom'\n\nexport default function useQueryParams() {\n  // Note: seems that history.location can be outdated sometimes.\n\n  const { search } = useLocation()\n\n  const params = useMemo(() => {\n    const searchParams = new URLSearchParams(search)\n    let _params: { [k: string]: any } = {}\n    for (const [k, v] of searchParams) {\n      _params[k] = v\n    }\n    return _params\n  }, [search])\n\n  return params\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/useVersionedLocalStorageState.ts",
    "content": "import { useLocalStorageState } from 'ahooks'\nimport { Options } from 'ahooks/lib/createUseStorageState'\nimport { useState } from 'react'\n\n// These type definitions is a workaround for https://github.com/alibaba/hooks/issues/1582\nexport interface IFuncUpdaterWithDefaultValue<T> {\n  (previousState: T): T\n}\n\n// export interface OptionsWithDefaultValue<T> extends Options<T> {\n//   defaultValue: T | IFuncUpdaterWithDefaultValue<T>\n// }\n\nexport type StorageStateResultHasDefaultValue<T> = [\n  T,\n  (value?: T | IFuncUpdaterWithDefaultValue<T>) => void\n]\n\n// Use the version field in package.json as the postfix for the localstorage key\n// we can **update version field in package.json** to upgrade local storage version key\nexport function useVersionedLocalStorageState<T>(\n  key: string,\n  // options: OptionsWithDefaultValue<T>\n  options: Options<T>,\n  enabled: boolean = true\n): StorageStateResultHasDefaultValue<T> {\n  const localStorageValue = useLocalStorageState(\n    `v${process.env.REACT_APP_VERSION}.${key}`,\n    options\n  ) as any\n  const stateVlaue = useState(options.defaultValue) as any\n  return enabled ? localStorageValue : stateVlaue\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/src/utils/wdyr.ts",
    "content": "import React from 'react'\n\nif (process.env.NODE_ENV === 'development') {\n  console.log('Development mode, enable render trackers')\n  const whyDidYouRender = require('@welldone-software/why-did-you-render')\n  whyDidYouRender(React)\n}\n"
  },
  {
    "path": "ui/packages/tidb-dashboard-lib/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    \"outDir\": \"./dist\",\n    \"noImplicitAny\": false,\n    \"noImplicitThis\": false,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@lib/*\": [\"src/*\"]\n    }\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "ui/pnpm-workspace.yaml",
    "content": "packages:\n  # all packages in subdirs of packages/\n  - 'packages/**'\n"
  },
  {
    "path": "ui/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"es6\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": false,\n    \"skipLibCheck\": true,\n    \"esModuleInterop\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noFallthroughCasesInSwitch\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"react-jsx\"\n  }\n}\n"
  },
  {
    "path": "ui-v2/.changeset/README.md",
    "content": "# Changesets\n\nHello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works\nwith multi-package repos, or single-package repos to help you version and publish your code. You can\nfind the full documentation for it [in our repository](https://github.com/changesets/changesets)\n\nWe have a quick list of common questions to get you started engaging with this project in\n[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)\n"
  },
  {
    "path": "ui-v2/.changeset/config.json",
    "content": "{\n  \"$schema\": \"https://unpkg.com/@changesets/config@3.0.3/schema.json\",\n  \"changelog\": \"@changesets/cli/changelog\",\n  \"commit\": false,\n  \"fixed\": [],\n  \"linked\": [],\n  \"access\": \"public\",\n  \"baseBranch\": \"ui-refactor\",\n  \"updateInternalDependencies\": \"patch\",\n  \"ignore\": []\n}\n"
  },
  {
    "path": "ui-v2/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n# tanstack router generated file\nrouteTree.gen.ts\n\n# Cloudflare Workers\n.wrangler\n"
  },
  {
    "path": "ui-v2/.husky/pre-commit",
    "content": "cd ui-v2\npnpm lint-staged\n"
  },
  {
    "path": "ui-v2/.prettierignore",
    "content": "# Ignore artifacts:\ndist\nbuild\ncoverage\n\npnpm-lock.yaml\n\n# orval generated files\npackages/api/server/src\nazores.json\n"
  },
  {
    "path": "ui-v2/.prettierrc",
    "content": "{\n  \"semi\": false\n}\n"
  },
  {
    "path": "ui-v2/README.md",
    "content": "# TiDB Dashboard UI Lib\n\n## Requirements\n\n- Node >= 22.0.0\n- [use corepack](https://www.totaltypescript.com/how-to-use-corepack): `corepack enable && corepack enable npm`\n\n## Development\n\n```bash\npnpm i\n# pnpm gen:api\n# pnpm gen:locales\npnpm dev:portals:test\n```\n\n## Build\n\n```bash\npnpm i\npnpm build\n```\n\n## Release\n\n```bash\npnpm changeset\npnpm changeset version\npnpm changeset publish\n```\n"
  },
  {
    "path": "ui-v2/api-specs/azores.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"Azores Open API\",\n    \"version\": \"2.0.0\"\n  },\n  \"tags\": [\n    {\n      \"name\": \"MetricsService\"\n    },\n    {\n      \"name\": \"UserService\"\n    },\n    {\n      \"name\": \"RoleService\"\n    },\n    {\n      \"name\": \"ApiKeyService\"\n    },\n    {\n      \"name\": \"AlertService\"\n    },\n    {\n      \"name\": \"AuditService\"\n    },\n    {\n      \"name\": \"ClusterBRService\"\n    },\n    {\n      \"name\": \"ClusterParamService\"\n    },\n    {\n      \"name\": \"ClusterParamTemplateService\"\n    },\n    {\n      \"name\": \"CredentialService\"\n    },\n    {\n      \"name\": \"HostService\"\n    },\n    {\n      \"name\": \"TagService\"\n    },\n    {\n      \"name\": \"TiupsService\"\n    },\n    {\n      \"name\": \"ClusterService\"\n    },\n    {\n      \"name\": \"CMServerService\"\n    },\n    {\n      \"name\": \"DiagnosisService\"\n    },\n    {\n      \"name\": \"DomainService\"\n    },\n    {\n      \"name\": \"GlobalBRService\"\n    },\n    {\n      \"name\": \"LicenseService\"\n    },\n    {\n      \"name\": \"LocationService\"\n    },\n    {\n      \"name\": \"TaskFlowService\"\n    }\n  ],\n  \"consumes\": [\"application/json\"],\n  \"produces\": [\"application/json\"],\n  \"paths\": {\n    \"/api/v2/alert/channels\": {\n      \"get\": {\n        \"summary\": \"Get Alert Channels\",\n        \"operationId\": \"AlertService_ListChannels\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListChannelsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"type\",\n            \"description\": \"The channel type\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"email\", \"webhook\"]\n          },\n          {\n            \"name\": \"nameLike\",\n            \"description\": \"Filter channels by name\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"application\",\n            \"description\": \"Monitor object application\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"alertObject\",\n            \"description\": \"Alert object\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"The number of users to retrieve per page.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Pagination token for retrieving the next page of users.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"The number of users to skip for pagination purposes.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"post\": {\n        \"summary\": \"Create Alert Channel\",\n        \"operationId\": \"AlertService_CreateChannel\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Channel\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"channel\",\n            \"description\": \"Channel\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Channel\"\n            }\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/channels/{channelId}\": {\n      \"delete\": {\n        \"summary\": \"Delete Alert Channel\",\n        \"operationId\": \"AlertService_DeleteChannel\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"channelId\",\n            \"description\": \"ID of the channel to delete\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"patch\": {\n        \"summary\": \"Update Alert Channel\",\n        \"operationId\": \"AlertService_UpdateChannel\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Channel\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"channelId\",\n            \"description\": \"ID of the channel to update\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/AlertServiceUpdateChannelBody\"\n            }\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/events\": {\n      \"get\": {\n        \"summary\": \"Get Alert Events\",\n        \"operationId\": \"AlertService_ListEvents\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListEventsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"ID of the cluster to filter events\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"level\",\n            \"description\": \"The event level\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"warning\", \"critical\", \"emergency\"]\n          },\n          {\n            \"name\": \"instance\",\n            \"description\": \"Instance name to filter events\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"status\",\n            \"description\": \"The event status\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"alerting\", \"resolved\", \"silenced\"]\n          },\n          {\n            \"name\": \"startTime\",\n            \"description\": \"Start time for event filtering\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"date-time\"\n          },\n          {\n            \"name\": \"endTime\",\n            \"description\": \"End time for event filtering\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"date-time\"\n          },\n          {\n            \"name\": \"keyword\",\n            \"description\": \"Keyword to search in event summary\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"The number of users to retrieve per page.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Pagination token for retrieving the next page of users.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"The number of users to skip for pagination purposes.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/events/overview\": {\n      \"get\": {\n        \"summary\": \"cluster events overview\",\n        \"operationId\": \"AlertService_GetEventsOverview\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2EventsOverview\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"startTime\",\n            \"description\": \"Start time for filtering\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"date-time\"\n          },\n          {\n            \"name\": \"endTime\",\n            \"description\": \"End time for filtering\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"date-time\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/events/{eventId}:silenceEvent\": {\n      \"post\": {\n        \"summary\": \"silence alert\",\n        \"operationId\": \"AlertService_SilenceEvent\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Event\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"eventId\",\n            \"description\": \"ID of the alert to silence\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/AlertServiceSilenceEventBody\"\n            }\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/monitorObjects\": {\n      \"get\": {\n        \"summary\": \"Get Monitor Objects\",\n        \"operationId\": \"AlertService_ListMonitorObjects\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListMonitorObjectsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"application\",\n            \"description\": \"Filter by application\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"objectType\",\n            \"description\": \"Filter by object type\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"post\": {\n        \"summary\": \"Create Monitor Object\",\n        \"operationId\": \"AlertService_CreateMonitorObject\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2MonitorObject\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"monitorObject\",\n            \"description\": \"The monitor object to create\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2MonitorObject\"\n            }\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/monitorObjects/{objectId}\": {\n      \"get\": {\n        \"summary\": \"Get Monitor Object\",\n        \"operationId\": \"AlertService_GetMonitorObject\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2MonitorObject\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"objectId\",\n            \"description\": \"The ID of the object to retrieve\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"delete\": {\n        \"summary\": \"Delete Monitor Object\",\n        \"operationId\": \"AlertService_DeleteMonitorObject\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"objectId\",\n            \"description\": \"The ID of the object to delete\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"patch\": {\n        \"summary\": \"Update Monitor Object\",\n        \"operationId\": \"AlertService_UpdateMonitorObject\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2MonitorObject\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"objectId\",\n            \"description\": \"The ID of the object to update\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/AlertServiceUpdateMonitorObjectBody\"\n            }\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/objectRules\": {\n      \"get\": {\n        \"summary\": \"Get Alert Object Rules\",\n        \"operationId\": \"AlertService_ListObjectRules\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListObjectRulesResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"skip\",\n            \"description\": \"The number of rules to skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"The number of rules to retrieve per page\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"The pagination token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"The sorting criteria\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"keyword\",\n            \"description\": \"Filter by  name ,expression\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"status\",\n            \"description\": \"The rule status\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"enabled\", \"disabled\"]\n          },\n          {\n            \"name\": \"level\",\n            \"description\": \"The alert level\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"warning\", \"critical\", \"emergency\"]\n          },\n          {\n            \"name\": \"alertObject\",\n            \"description\": \"Filter by alert object\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"monitorObject.id\",\n            \"description\": \"The unique identifier of the alert rule\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"monitorObject.application\",\n            \"description\": \"The monitor object application\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"monitorObject.objectType\",\n            \"description\": \"The type of the monitor object\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"post\": {\n        \"summary\": \"Create Alert Object Rule\",\n        \"operationId\": \"AlertService_CreateObjectRule\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ObjectRule\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"objectRule\",\n            \"description\": \"The object rule to create\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ObjectRule\"\n            }\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/objectRules/{objectRuleId}\": {\n      \"get\": {\n        \"summary\": \"Get Alert Object Rule\",\n        \"operationId\": \"AlertService_GetObjectRule\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ObjectRule\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"objectRuleId\",\n            \"description\": \"The ID of the rule to retrieve\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"delete\": {\n        \"summary\": \"Delete Alert Object Rule\",\n        \"operationId\": \"AlertService_DeleteObjectRule\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"objectRuleId\",\n            \"description\": \"The ID of the rule to delete\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"patch\": {\n        \"summary\": \"Update Alert Object Rule\",\n        \"operationId\": \"AlertService_UpdateObjectRule\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ObjectRule\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"objectRuleId\",\n            \"description\": \"The ID of the rule to update\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/AlertServiceUpdateObjectRuleBody\"\n            }\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/rules\": {\n      \"get\": {\n        \"summary\": \"Get Alert Rules\",\n        \"operationId\": \"AlertService_ListRules\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListRulesResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"skip\",\n            \"description\": \"The number of rules to skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"The number of rules to retrieve per page\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"The pagination token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"The sorting criteria\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"keyword\",\n            \"description\": \"Filter by  name ,expression\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"level\",\n            \"description\": \"The alert level\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"warning\", \"critical\", \"emergency\"]\n          },\n          {\n            \"name\": \"monitorObject.id\",\n            \"description\": \"The unique identifier of the alert rule\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"monitorObject.application\",\n            \"description\": \"The monitor object application\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"monitorObject.objectType\",\n            \"description\": \"The type of the monitor object\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"post\": {\n        \"summary\": \"Create Alert Rule\",\n        \"operationId\": \"AlertService_CreateRule\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Rule\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"rule\",\n            \"description\": \"The rule to create\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Rule\"\n            }\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/rules/{ruleId}\": {\n      \"get\": {\n        \"summary\": \"Get Alert Rule\",\n        \"operationId\": \"AlertService_GetRule\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Rule\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"ruleId\",\n            \"description\": \"The ID of the rule to retrieve\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"delete\": {\n        \"summary\": \"Delete Alert Rule\",\n        \"operationId\": \"AlertService_DeleteRule\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"ruleId\",\n            \"description\": \"The ID of the rule to delete\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"patch\": {\n        \"summary\": \"Update Alert Rule\",\n        \"operationId\": \"AlertService_UpdateRule\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Rule\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"ruleId\",\n            \"description\": \"The ID of the rule to update\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/AlertServiceUpdateRuleBody\"\n            }\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/templates\": {\n      \"get\": {\n        \"summary\": \"Get Alert Templates\",\n        \"operationId\": \"AlertService_ListTemplates\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListTemplatesResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"The number of templates to retrieve per page\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Pagination token for retrieving the next page of templates\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"The number of templates to skip for pagination purposes\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"name\",\n            \"description\": \"Filter templates by name\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"tidbVersion\",\n            \"description\": \"TiDB cluster version, used to filter templates\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"nameLike\",\n            \"description\": \"Filter templates by name use like\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"post\": {\n        \"summary\": \"Create Alert Template\",\n        \"operationId\": \"AlertService_CreateTemplate\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Template\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"template\",\n            \"description\": \"Template\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Template\"\n            }\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/templates/{templateId}\": {\n      \"delete\": {\n        \"summary\": \"Delete Alert Template\",\n        \"operationId\": \"AlertService_DeleteTemplate\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"templateId\",\n            \"description\": \"ID of the template to delete\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"patch\": {\n        \"summary\": \"Update Alert Template\",\n        \"operationId\": \"AlertService_UpdateTemplate\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TemplateWithOutRules\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"templateId\",\n            \"description\": \"ID of the template to update\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/AlertServiceUpdateTemplateBody\"\n            }\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/templates/{templateId}/rules\": {\n      \"get\": {\n        \"summary\": \"Get Alert Template rules\",\n        \"operationId\": \"AlertService_ListTemplateRules\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListTemplateRulesResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"templateId\",\n            \"description\": \"ID of the template\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"The number of rules to retrieve per page\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Pagination token for retrieving the next page\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"The number of rules to skip for pagination purposes\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"keyword\",\n            \"description\": \"Filter rules by keyword\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"level\",\n            \"description\": \"The alert level\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"warning\", \"critical\", \"emergency\"]\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"post\": {\n        \"summary\": \"Create rule in template\",\n        \"operationId\": \"AlertService_CreateTemplateRule\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TemplateRule\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"templateId\",\n            \"description\": \"ID of the template to update\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"templateRule\",\n            \"description\": \"Rule to create\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TemplateRule\"\n            }\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/alert/templates/{templateId}/rules/{ruleId}\": {\n      \"delete\": {\n        \"summary\": \"Delete rule in template\",\n        \"operationId\": \"AlertService_DeleteTemplateRule\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"templateId\",\n            \"description\": \"ID of the template to update\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"ruleId\",\n            \"description\": \"ID of the rule to delete\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      },\n      \"patch\": {\n        \"summary\": \"Update rule in template\",\n        \"operationId\": \"AlertService_UpdateTemplateRule\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TemplateRule\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"templateId\",\n            \"description\": \"ID of the template to update\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"ruleId\",\n            \"description\": \"ID of the rule to update\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/AlertServiceUpdateTemplateRuleBody\"\n            }\n          }\n        ],\n        \"tags\": [\"AlertService\"]\n      }\n    },\n    \"/api/v2/apiKeys\": {\n      \"get\": {\n        \"summary\": \"ListApiKeys retrieves a list of API keys.\",\n        \"operationId\": \"ApiKeyService_ListApiKeys\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListApiKeysResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"accessKey\",\n            \"description\": \"The access_key of the apikey\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"creator\",\n            \"description\": \"The access_key of the apikey\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"status\",\n            \"description\": \"The status of the apikey\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ApiKeyService\"]\n      },\n      \"post\": {\n        \"summary\": \"CreateApiKey creates a new API key.\",\n        \"operationId\": \"ApiKeyService_CreateApiKey\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ApiKey\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2CreateApiKeyRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"ApiKeyService\"]\n      }\n    },\n    \"/api/v2/apiKeys/{accessKey}\": {\n      \"get\": {\n        \"summary\": \"GetApiKeyRequest get an API key by its access key.\",\n        \"operationId\": \"ApiKeyService_GetApiKey\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ApiKey\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"accessKey\",\n            \"description\": \"The access key of the apiKey\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ApiKeyService\"]\n      },\n      \"delete\": {\n        \"summary\": \"DeleteApiKey deletes an API key by its access key.\",\n        \"operationId\": \"ApiKeyService_DeleteApiKey\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"accessKey\",\n            \"description\": \"The access key of the apiKey\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ApiKeyService\"]\n      },\n      \"patch\": {\n        \"summary\": \"UpdateApiKey updates an API key by its access key.\",\n        \"operationId\": \"ApiKeyService_UpdateApiKey\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ApiKey\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"accessKey\",\n            \"description\": \"The access_key of apikey\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ApiKeyServiceUpdateApiKeyBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ApiKeyService\"]\n      }\n    },\n    \"/api/v2/apiKeys/{accessKey}:resetSecretKey\": {\n      \"patch\": {\n        \"summary\": \"ResetSecretKey resets the secret key for an existing API key.\",\n        \"operationId\": \"ApiKeyService_ResetSecretKey\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ResetSecretKeyResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"accessKey\",\n            \"description\": \"The access key of the apiKey\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ApiKeyService\"]\n      }\n    },\n    \"/api/v2/audit/config\": {\n      \"get\": {\n        \"summary\": \"get audit configuration\",\n        \"operationId\": \"AuditService_GetAuditConfigs\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2AuditConfigs\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"tags\": [\"AuditService\"]\n      },\n      \"put\": {\n        \"summary\": \"Replace audit configuration\",\n        \"operationId\": \"AuditService_UpdateAuditConfigs\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2AuditConfigs\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2UpdateAuditConfigsRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"AuditService\"]\n      }\n    },\n    \"/api/v2/audit/logs\": {\n      \"get\": {\n        \"summary\": \"Get audit logs\",\n        \"operationId\": \"AuditService_GetAuditLogs\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2AuditLogs\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"startTime\",\n            \"description\": \"Start time\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"date-time\"\n          },\n          {\n            \"name\": \"endTime\",\n            \"description\": \"End time\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"date-time\"\n          },\n          {\n            \"name\": \"operatorId\",\n            \"description\": \"Operator id\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"operatorType\",\n            \"description\": \"The audit operator type\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"USER\", \"API_KEY\"]\n          },\n          {\n            \"name\": \"event\",\n            \"description\": \"The audit event\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"User\",\n              \"Role\",\n              \"ApiKey\",\n              \"Metrics\",\n              \"Host\",\n              \"Tag\",\n              \"Cluster\",\n              \"Credential\",\n              \"GlobalBR\",\n              \"ClusterBR\",\n              \"License\",\n              \"Diagnosis\",\n              \"Location\",\n              \"Tiups\",\n              \"Audit\"\n            ]\n          },\n          {\n            \"name\": \"operation\",\n            \"description\": \"operation\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"result\",\n            \"description\": \"The audit result\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"success\", \"failure\"]\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"AuditService\"]\n      }\n    },\n    \"/api/v2/audit/logs:download\": {\n      \"get\": {\n        \"summary\": \"Get download URL for audit logs\",\n        \"operationId\": \"AuditService_DownloadAuditLogs\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2DownloadAuditLogsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"startTime\",\n            \"description\": \"Start time for filtering audit logs\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"date-time\"\n          },\n          {\n            \"name\": \"endTime\",\n            \"description\": \"End time for filtering audit logs\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"date-time\"\n          },\n          {\n            \"name\": \"operatorType\",\n            \"description\": \"Operator type\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"operatorId\",\n            \"description\": \"Value of the Operator\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"event\",\n            \"description\": \"Event (e.g., \\\"cluster\\\", \\\"user\\\")\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"operation\",\n            \"description\": \"Operation type (e.g., \\\"create\\\", \\\"update\\\", \\\"delete\\\")\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"result\",\n            \"description\": \"Result of the operation (e.g., \\\"success\\\", \\\"failure\\\")\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"AuditService\"]\n      }\n    },\n    \"/api/v2/backup/policies\": {\n      \"get\": {\n        \"summary\": \"ListBackupPolicies lists Backup policies\",\n        \"operationId\": \"GlobalBRService_ListBackupPolicies\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListBackupPoliciesResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"isDesc\",\n            \"description\": \"Is descending order\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          }\n        ],\n        \"tags\": [\"GlobalBRService\"]\n      },\n      \"post\": {\n        \"summary\": \"CreateBackupPolicy creates a Backup policy\",\n        \"operationId\": \"GlobalBRService_CreateBackupPolicy\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BackupPolicy\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BackupPolicy\"\n            }\n          }\n        ],\n        \"tags\": [\"GlobalBRService\"]\n      }\n    },\n    \"/api/v2/backup/policies/precheck\": {\n      \"post\": {\n        \"summary\": \"PreCheckBackupPolicy pre-checks a Backup policy\",\n        \"operationId\": \"GlobalBRService_PreCheckBackupPolicy\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2PreCheckBackupPolicyResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BackupPolicy\"\n            }\n          }\n        ],\n        \"tags\": [\"GlobalBRService\"]\n      }\n    },\n    \"/api/v2/backup/policies/{policyId}\": {\n      \"get\": {\n        \"summary\": \"GetBackupPolicy gets a Backup policy\",\n        \"operationId\": \"GlobalBRService_GetBackupPolicy\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BackupPolicy\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"policyId\",\n            \"description\": \"The policy ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"GlobalBRService\"]\n      },\n      \"delete\": {\n        \"summary\": \"DeleteBackupPolicy deletes a Backup policy\",\n        \"operationId\": \"GlobalBRService_DeleteBackupPolicy\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"policyId\",\n            \"description\": \"The policy ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"GlobalBRService\"]\n      },\n      \"put\": {\n        \"summary\": \"UpdateBackupPolicy updates a Backup policy\",\n        \"operationId\": \"GlobalBRService_UpdateBackupPolicy\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BackupPolicy\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"policyId\",\n            \"description\": \"The policy ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/GlobalBRServiceUpdateBackupPolicyBody\"\n            }\n          }\n        ],\n        \"tags\": [\"GlobalBRService\"]\n      }\n    },\n    \"/api/v2/backup/summary\": {\n      \"get\": {\n        \"summary\": \"GetBRSummary retrieves the summary of BR\",\n        \"operationId\": \"GlobalBRService_GetBRSummary\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BRSummary\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"top\",\n            \"description\": \"Number of top clusters\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"GlobalBRService\"]\n      }\n    },\n    \"/api/v2/backup/tasks\": {\n      \"get\": {\n        \"summary\": \"ListBRTasks retrieves the tasks of BR\",\n        \"operationId\": \"GlobalBRService_ListBRTasks\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListBRTasksResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"isDesc\",\n            \"description\": \"Is descending order\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          },\n          {\n            \"name\": \"brTaskId\",\n            \"description\": \"The br task ID\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster ID\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"clusterName\",\n            \"description\": \"The cluster name\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"type\",\n            \"description\": \"Type of the br task\\n\\n - all: All\\n - full_backup: Full backup\\n - log_backup: Log backup\\n - restore_by_file: Restore by file\\n - restore_by_time: Restore by time\\n - all_backup: All backup\\n - all_restore: All restore\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"all\",\n              \"full_backup\",\n              \"log_backup\",\n              \"restore_by_file\",\n              \"restore_by_time\",\n              \"all_backup\",\n              \"all_restore\"\n            ],\n            \"default\": \"all\"\n          },\n          {\n            \"name\": \"status\",\n            \"description\": \"Status of the br task\\n\\n - all: All\\n - running: Running\\n - finished: Finished\\n - abnormal: Abnormal\\n - stopped: Stopped\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"all\", \"running\", \"finished\", \"abnormal\", \"stopped\"],\n            \"default\": \"all\"\n          }\n        ],\n        \"tags\": [\"GlobalBRService\"]\n      }\n    },\n    \"/api/v2/backup/tasks/{taskId}\": {\n      \"delete\": {\n        \"summary\": \"DeleteBRTask deletes a BR task\",\n        \"operationId\": \"GlobalBRService_DeleteBRTask\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"taskId\",\n            \"description\": \"The br task ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"deleteBackupFile\",\n            \"description\": \"delete_backup_file for whether delete the backup files or not\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          }\n        ],\n        \"tags\": [\"GlobalBRService\"]\n      }\n    },\n    \"/api/v2/backup/tasks/{taskId}/start\": {\n      \"post\": {\n        \"summary\": \"StartBRTask starts a BR task\",\n        \"operationId\": \"GlobalBRService_StartBRTask\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"taskId\",\n            \"description\": \"The br task ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"GlobalBRService\"]\n      }\n    },\n    \"/api/v2/backup/tasks/{taskId}/stop\": {\n      \"post\": {\n        \"summary\": \"StopBRTask stops a BR task\",\n        \"operationId\": \"GlobalBRService_StopBRTask\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"taskId\",\n            \"description\": \"The br task ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"GlobalBRService\"]\n      }\n    },\n    \"/api/v2/clusters\": {\n      \"get\": {\n        \"summary\": \"ListClusters retrieves the list of running processes in a cluster\",\n        \"operationId\": \"ClusterService_ListClusters\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListClustersResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"searchValue\",\n            \"description\": \"The name of the user\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"tagIds\",\n            \"description\": \"tag_ids\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/clusterTaskFlow\": {\n      \"get\": {\n        \"summary\": \"ClusterTaskFlow\",\n        \"operationId\": \"ClusterService_ClusterTaskFlow\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ClusterTaskFlowResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}\": {\n      \"get\": {\n        \"summary\": \"GetClusters in a cluster\",\n        \"operationId\": \"ClusterService_GetClusters\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Clusters\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the host\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/backup\": {\n      \"post\": {\n        \"summary\": \"CreateBackupTask backups a cluster\",\n        \"operationId\": \"ClusterBRService_CreateBackupTask\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ClusterBRTask\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterBRServiceCreateBackupTaskBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterBRService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/backup/policy\": {\n      \"get\": {\n        \"summary\": \"GetClusterBackupPolicy gets the backup info of a specific cluster\",\n        \"operationId\": \"ClusterBRService_GetClusterBackupPolicy\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ClusterBackupPolicy\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterBRService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/backup/records\": {\n      \"get\": {\n        \"summary\": \"ListBackupRecords lists the valid full backup records of a specific cluster\",\n        \"operationId\": \"ClusterBRService_ListClusterBackupRecords\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListClusterBackupRecordsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"isDesc\",\n            \"description\": \"Is descending order\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          }\n        ],\n        \"tags\": [\"ClusterBRService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/backup/restore\": {\n      \"post\": {\n        \"summary\": \"CreateRestoreTask restores a cluster\",\n        \"operationId\": \"ClusterBRService_CreateRestoreTask\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ClusterBRTask\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID represents the cluster to be restored\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterBRServiceCreateRestoreTaskBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterBRService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/backup/tasks\": {\n      \"get\": {\n        \"summary\": \"ListClusterBRTasks lists the backup tasks of a specific cluster\",\n        \"operationId\": \"ClusterBRService_ListClusterBRTasks\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListClusterBRTasksResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"isDesc\",\n            \"description\": \"Is descending order\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          },\n          {\n            \"name\": \"brTaskId\",\n            \"description\": \"The br task ID\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"clusterName\",\n            \"description\": \"The cluster name\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"type\",\n            \"description\": \"Type of the br task\\n\\n - full_backup: Full backup\\n - log_backup: Log backup\\n - restore_by_file: Restore by file\\n - restore_by_time: Restore by time\\n - all_backup: All backup\\n - all_restore: All restore\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"full_backup\",\n              \"log_backup\",\n              \"restore_by_file\",\n              \"restore_by_time\",\n              \"all_backup\",\n              \"all_restore\"\n            ],\n            \"default\": \"full_backup\"\n          },\n          {\n            \"name\": \"status\",\n            \"description\": \"Status of the br task\\n\\n - running: Running\\n - finished: Finished\\n - abnormal: Abnormal\\n - stopped: Stopped\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"running\", \"finished\", \"abnormal\", \"stopped\"],\n            \"default\": \"running\"\n          }\n        ],\n        \"tags\": [\"ClusterBRService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/backup:detect\": {\n      \"post\": {\n        \"summary\": \"DetectCluster detects the if the cluster exist\",\n        \"operationId\": \"ClusterBRService_DetectCluster\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2DetectClusterResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterBRService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/clusterYaml\": {\n      \"get\": {\n        \"summary\": \"ClusterYaml cluster\",\n        \"operationId\": \"ClusterService_ClusterYaml\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ClusterYamlResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/config/components\": {\n      \"get\": {\n        \"summary\": \"GetClusterAvailableConfigComponents retrieves a list of available config components\",\n        \"operationId\": \"ClusterParamService_GetClusterAvailableConfigComponents\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ClusterAvailableConfigComponents\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"ClusterID is the ID of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterParamService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/configs\": {\n      \"get\": {\n        \"summary\": \"ListClusterConfigs retrieves a list of cluster configs\",\n        \"operationId\": \"ClusterParamService_ListClusterConfigs\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListClusterConfigsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"ClusterID is the ID of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"nonDefault\",\n            \"description\": \"NonDefault is if the config is non-default\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          },\n          {\n            \"name\": \"needReload\",\n            \"description\": \"NeedReload is if the config needs to be reloaded\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          },\n          {\n            \"name\": \"dynamic\",\n            \"description\": \"Dynamic is if the config is dynamic\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          },\n          {\n            \"name\": \"instanceType\",\n            \"description\": \"InstanceType is the instance type of the config\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"name\",\n            \"description\": \"Name is the name of the config\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterParamService\"]\n      },\n      \"patch\": {\n        \"summary\": \"UpdateClusterConfig updates a cluster config\",\n        \"operationId\": \"ClusterParamService_UpdateClusterConfig\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"ClusterID is the ID of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterParamServiceUpdateClusterConfigBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterParamService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/metrics/{name}/data\": {\n      \"get\": {\n        \"summary\": \"Get cluster metric data\",\n        \"operationId\": \"MetricsService_GetClusterMetricData\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ClusterMetricData\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"name\",\n            \"description\": \"Metric Name\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"startTime\",\n            \"description\": \"Start time in Unix timestamp format\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"endTime\",\n            \"description\": \"End time in Unix timestamp format\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"step\",\n            \"description\": \"Step time in seconds\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"label\",\n            \"description\": \"Line Label for the metric\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"range\",\n            \"description\": \"Time Range for the query\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"MetricsService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/metrics/{name}/instance\": {\n      \"get\": {\n        \"summary\": \"Get metric instances\",\n        \"operationId\": \"MetricsService_GetClusterMetricInstance\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ClusterMetricInstance\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"name\",\n            \"description\": \"Metric name\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"MetricsService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/nodes\": {\n      \"get\": {\n        \"summary\": \"Nodes\",\n        \"operationId\": \"ClusterService_Nodes\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2NodesResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"component\",\n            \"description\": \"The component\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"status\",\n            \"description\": \"The status\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"ipLike\",\n            \"description\": \"The ip_like\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/nodes:batchPause\": {\n      \"post\": {\n        \"summary\": \"BatchPause cluster\",\n        \"operationId\": \"ClusterService_BatchPause\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BatchPauseResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceBatchPauseBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/nodes:batchReload\": {\n      \"post\": {\n        \"summary\": \"BatchReload\",\n        \"operationId\": \"ClusterService_BatchReload\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BatchReloadResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceBatchReloadBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/nodes:batchRestart\": {\n      \"post\": {\n        \"summary\": \"BatchStart cluster\",\n        \"operationId\": \"ClusterService_BatchRestart\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BatchRestartResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceBatchRestartBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/nodes:batchResume\": {\n      \"post\": {\n        \"summary\": \"BatchResume cluster\",\n        \"operationId\": \"ClusterService_BatchResume\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BatchResumeResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceBatchResumeBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/resourceUsage\": {\n      \"get\": {\n        \"summary\": \"Get cluster metric resource level\",\n        \"operationId\": \"MetricsService_GetClusterResourceUsage\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ClusterResourceUsage\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"MetricsService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/resourcegroups\": {\n      \"get\": {\n        \"summary\": \"Get resource group list\",\n        \"operationId\": \"DiagnosisService_GetResourceGroupList\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ResourceGroupList\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/sessions\": {\n      \"get\": {\n        \"summary\": \"GetProcessList retrieves the list of running processes in a cluster\",\n        \"operationId\": \"ClusterService_GetProcessList\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ProcessList\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID uniquely identifies the target cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/sessions/{sessionId}\": {\n      \"delete\": {\n        \"summary\": \"DeleteProcess terminates a specific process in the cluster\",\n        \"operationId\": \"ClusterService_DeleteProcess\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID uniquely identifies the target cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"sessionId\",\n            \"description\": \"Session ID identifies the process to be terminated\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/slowqueries\": {\n      \"get\": {\n        \"summary\": \"GetSlowQueryList retrieves the list of slow queries\",\n        \"operationId\": \"DiagnosisService_GetSlowQueryList\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2SlowQueryList\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"beginTime\",\n            \"description\": \"Begin time in Unix timestamp\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"endTime\",\n            \"description\": \"End time in Unix timestamp\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"db\",\n            \"description\": \"List of databases\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          },\n          {\n            \"name\": \"text\",\n            \"description\": \"Search text\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"Order by field\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"isDesc\",\n            \"description\": \"Is descending order\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          },\n          {\n            \"name\": \"fields\",\n            \"description\": \"Fields to select, e.g., \\\"Query,Digest\\\"\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token for pagination\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Number of records to skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"advancedFilter\",\n            \"description\": \"Advanced filters, such as \\\"digest = xxx\\\"\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/slowqueries/advancedFilters\": {\n      \"get\": {\n        \"summary\": \"GetSlowQueryAvailableAdvancedFilters retrieves the list of available advanced filters\",\n        \"operationId\": \"DiagnosisService_GetSlowQueryAvailableAdvancedFilters\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2SlowQueryAvailableAdvancedFilters\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/slowqueries/advancedFilters/{filterName}\": {\n      \"get\": {\n        \"summary\": \"GetSlowQueryAvailableAdvancedFilterInfo retrieves the list of available advanced filter info\",\n        \"operationId\": \"DiagnosisService_GetSlowQueryAvailableAdvancedFilterInfo\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2SlowQueryAvailableAdvancedFilterInfo\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"filterName\",\n            \"description\": \"filter name\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/slowqueries/download\": {\n      \"get\": {\n        \"summary\": \"DownloadSlowQueryList downloads the list of slow queries\",\n        \"operationId\": \"DiagnosisService_DownloadSlowQueryList\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2SlowQueryDownloadResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"beginTime\",\n            \"description\": \"Begin time in Unix timestamp\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"endTime\",\n            \"description\": \"End time in Unix timestamp\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"db\",\n            \"description\": \"List of databases\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          },\n          {\n            \"name\": \"text\",\n            \"description\": \"Search text\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"Order by field\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"isDesc\",\n            \"description\": \"Is descending order\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          },\n          {\n            \"name\": \"fields\",\n            \"description\": \"Fields to select, e.g., \\\"Query,Digest\\\"\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token for pagination\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Number of records to skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"advancedFilter\",\n            \"description\": \"Advanced filters, such as \\\"digest = xxx\\\"\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/slowqueries/fields\": {\n      \"get\": {\n        \"summary\": \"GetSlowQueryAvailableFields retrieves the list of available fields for slow queries\",\n        \"operationId\": \"DiagnosisService_GetSlowQueryAvailableFields\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2SlowQueryAvailableFields\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/slowqueries/{digest}\": {\n      \"get\": {\n        \"summary\": \"GetSlowQueryDetail retrieves the details of a specific slow query\",\n        \"operationId\": \"DiagnosisService_GetSlowQueryDetail\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2SlowQueryDetail\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"digest\",\n            \"description\": \"Query digest\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"timestamp\",\n            \"description\": \"Timestamp\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"number\",\n            \"format\": \"double\"\n          },\n          {\n            \"name\": \"connectionId\",\n            \"description\": \"Connection ID\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/sqllimits:addSqlLimit\": {\n      \"post\": {\n        \"summary\": \"Create SQL limit\",\n        \"operationId\": \"DiagnosisService_AddSqlLimit\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster Id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/DiagnosisServiceAddSqlLimitBody\"\n            }\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/sqllimits:checkSupport\": {\n      \"get\": {\n        \"summary\": \"Check if SQL limit is supported\",\n        \"operationId\": \"DiagnosisService_CheckSqlLimitSupport\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2CheckSupportResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/sqllimits:removeSqlLimit\": {\n      \"post\": {\n        \"summary\": \"Remove SQL limit\",\n        \"operationId\": \"DiagnosisService_RemoveSqlLimit\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster Id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/DiagnosisServiceRemoveSqlLimitBody\"\n            }\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/sqllimits:showSqlLimit\": {\n      \"get\": {\n        \"summary\": \"Query SQL limit\",\n        \"operationId\": \"DiagnosisService_GetSqlLimitList\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2SqlLimitList\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster Id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"watchText\",\n            \"description\": \"Watch text\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/sqlplans\": {\n      \"get\": {\n        \"summary\": \"GetSqlPlanList retrieves the list of plans\",\n        \"operationId\": \"DiagnosisService_GetSqlPlanList\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2SqlPlanList\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"beginTime\",\n            \"description\": \"Begin time\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"endTime\",\n            \"description\": \"End time\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"digest\",\n            \"description\": \"SQL digest\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"schemaName\",\n            \"description\": \"Table name\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/sqlplans/{planDigest}:bindSqlPlan\": {\n      \"post\": {\n        \"summary\": \"BindSqlPlan binds a plan to a specific sql\",\n        \"operationId\": \"DiagnosisService_BindSqlPlan\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"planDigest\",\n            \"description\": \"SQL plan digest\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/sqlplans:checkSupport\": {\n      \"get\": {\n        \"summary\": \"CheckSupport returns whether sql plan binding is supported\",\n        \"operationId\": \"DiagnosisService_CheckSqlPlanSupport\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2CheckSupportResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/sqlplans:showSqlPlanBinding\": {\n      \"get\": {\n        \"summary\": \"GetSQLBindInfo\",\n        \"operationId\": \"DiagnosisService_GetSqlPlanBindingList\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2SqlPlanBindingList\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"beginTime\",\n            \"description\": \"Begin time\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"endTime\",\n            \"description\": \"End time\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"digest\",\n            \"description\": \"SQL digest\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/sqlplans:unbindSqlPlan\": {\n      \"post\": {\n        \"summary\": \"UnbindSqlPlan unbinds a plan from a specific sql\",\n        \"operationId\": \"DiagnosisService_UnbindSqlPlan\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"digest\",\n            \"description\": \"SQL digest\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/topologySummary\": {\n      \"get\": {\n        \"summary\": \"TopologySummary\",\n        \"operationId\": \"ClusterService_TopologySummary\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TopologySummaryResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/topsqls\": {\n      \"get\": {\n        \"summary\": \"GetTopSqlList retrieves the list of top sql\",\n        \"operationId\": \"DiagnosisService_GetTopSqlList\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TopSqlList\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"beginTime\",\n            \"description\": \"Begin time\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"endTime\",\n            \"description\": \"End time\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"db\",\n            \"description\": \"Database list\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          },\n          {\n            \"name\": \"text\",\n            \"description\": \"SQL Text, used for fuzzy query\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"Order by field\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"isDesc\",\n            \"description\": \"Is descending order\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          },\n          {\n            \"name\": \"fields\",\n            \"description\": \"Fields to select, e.g., \\\"Query,Digest\\\"\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"advancedFilter\",\n            \"description\": \"Advanced filters, such as \\\"digest = xxx\\\"\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          },\n          {\n            \"name\": \"isGroupByTime\",\n            \"description\": \"Is group by time\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/topsqls/advancedFilters\": {\n      \"get\": {\n        \"summary\": \"GetTopSqlAvailableAdvancedFilters retrieves the list of available advanced filters\",\n        \"operationId\": \"DiagnosisService_GetTopSqlAvailableAdvancedFilters\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TopSqlAvailableAdvancedFilters\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/topsqls/advancedFilters/{filterName}\": {\n      \"get\": {\n        \"summary\": \"GetTopSqlAvailableAdvancedFilterInfo retrieves the list of available advanced filter info\",\n        \"operationId\": \"DiagnosisService_GetTopSqlAvailableAdvancedFilterInfo\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TopSqlAvailableAdvancedFilterInfo\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"filterName\",\n            \"description\": \"filter name\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/topsqls/configs\": {\n      \"get\": {\n        \"summary\": \"GetTopSqlConfigs retrieves the list of top sql configs\",\n        \"operationId\": \"DiagnosisService_GetTopSqlConfigs\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TopSqlConfigs\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      },\n      \"patch\": {\n        \"summary\": \"UpdateTopSqlConfigs updates the list of top sql configs\",\n        \"operationId\": \"DiagnosisService_UpdateTopSqlConfigs\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TopSqlConfigs\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/DiagnosisServiceUpdateTopSqlConfigsBody\"\n            }\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/topsqls/fields\": {\n      \"get\": {\n        \"summary\": \"GetTopSqlAvailableFields retrieves the list of available fields for top sqls\",\n        \"operationId\": \"DiagnosisService_GetTopSqlAvailableFields\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TopSqlAvailableFields\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/topsqls/{digest}\": {\n      \"get\": {\n        \"summary\": \"GetTopSqlDetail retrieves the details of a specific top sql\",\n        \"operationId\": \"DiagnosisService_GetTopSqlDetail\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TopSqlDetail\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"digest\",\n            \"description\": \"Query digest\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"beginTime\",\n            \"description\": \"Begin time\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"endTime\",\n            \"description\": \"End time\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"planDigest\",\n            \"description\": \"Plan digest list\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          }\n        ],\n        \"tags\": [\"DiagnosisService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}/variables\": {\n      \"get\": {\n        \"summary\": \"ListClusterVariables retrieves a list of cluster variables\",\n        \"operationId\": \"ClusterParamService_ListClusterVariables\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListClusterVariablesResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"ClusterID is the ID of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"nonDefault\",\n            \"description\": \"NonDefault is if the variable is non-default\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          },\n          {\n            \"name\": \"name\",\n            \"description\": \"Name is the name of the variable\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterParamService\"]\n      },\n      \"patch\": {\n        \"summary\": \"UpdateClusterVariable updates a cluster variable\",\n        \"operationId\": \"ClusterParamService_UpdateClusterVariable\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"ClusterID is the ID of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterParamServiceUpdateClusterVariableBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterParamService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}:cancelTaskFlow\": {\n      \"post\": {\n        \"summary\": \"Retry cluster\",\n        \"operationId\": \"ClusterService_CancelTaskFlow\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2CancelTaskFlowResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceCancelTaskFlowBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}:deployCluster\": {\n      \"post\": {\n        \"summary\": \"Deploy cluster\",\n        \"operationId\": \"ClusterService_DeployCluster\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2DeployClusterResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceDeployClusterBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}:destroyCluster\": {\n      \"post\": {\n        \"summary\": \"DestroyCluster cluster\",\n        \"operationId\": \"ClusterService_DestroyCluster\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2DestroyClusterResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceDestroyClusterBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}:offlineCluster\": {\n      \"post\": {\n        \"summary\": \"OfflineCluster cluster\",\n        \"operationId\": \"ClusterService_OfflineCluster\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2OfflineClusterResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceOfflineClusterBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}:pauseCluster\": {\n      \"post\": {\n        \"summary\": \"PauseCluster cluster\",\n        \"operationId\": \"ClusterService_PauseCluster\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2PauseClusterResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServicePauseClusterBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}:reloadCluster\": {\n      \"post\": {\n        \"summary\": \"ReloadCluster cluster\",\n        \"operationId\": \"ClusterService_ReloadCluster\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ReloadClusterResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceReloadClusterBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}:restartCluster\": {\n      \"post\": {\n        \"summary\": \"StartCluster cluster\",\n        \"operationId\": \"ClusterService_RestartCluster\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2RestartClusterResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceRestartClusterBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}:resumeCluster\": {\n      \"post\": {\n        \"summary\": \"ResumeCluster cluster\",\n        \"operationId\": \"ClusterService_ResumeCluster\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ResumeClusterResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceResumeClusterBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}:retryTaskFlow\": {\n      \"post\": {\n        \"summary\": \"Retry cluster\",\n        \"operationId\": \"ClusterService_RetryTaskFlow\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2RetryTaskFlowResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceRetryTaskFlowBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}:scaleInCluster\": {\n      \"post\": {\n        \"summary\": \"ScaleInCluster cluster\",\n        \"operationId\": \"ClusterService_ScaleInCluster\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ScaleInClusterResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceScaleInClusterBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters/{clusterId}:scaleOutCluster\": {\n      \"post\": {\n        \"summary\": \"ScaleOut cluster\",\n        \"operationId\": \"ClusterService_ScaleOutCluster\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ScaleOutClusterResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"clusterId\",\n            \"description\": \"The cluster_id of the cluster\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterServiceScaleOutClusterBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters:clusterConfig\": {\n      \"post\": {\n        \"summary\": \"ClusterConfig\",\n        \"operationId\": \"ClusterService_ClusterConfig\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ClusterConfigResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ClusterConfigRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters:clusterVersions\": {\n      \"get\": {\n        \"summary\": \"ClusterVersions cluster\",\n        \"operationId\": \"ClusterService_ClusterVersions\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ClusterVersionsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tidbVersion\",\n            \"description\": \"The tidb_version\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"tiupId\",\n            \"description\": \"The tiup_id\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters:configTemplate\": {\n      \"post\": {\n        \"summary\": \"ConfigTemplate cluster\",\n        \"operationId\": \"ClusterService_ConfigTemplate\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ConfigTemplateResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ConfigTemplateRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters:download\": {\n      \"get\": {\n        \"summary\": \"HostConfirm one host\",\n        \"operationId\": \"ClusterService_DownloadListClusters\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2DownloadListClustersResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"searchValue\",\n            \"description\": \"The name of the user\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"tagIds\",\n            \"description\": \"tag_ids\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/clusters:takeoverCluster\": {\n      \"post\": {\n        \"summary\": \"Takeover cluster\",\n        \"operationId\": \"ClusterService_TakeoverCluster\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TakeoverClusterResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TakeoverClusterRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v2/cmservers\": {\n      \"get\": {\n        \"summary\": \"ListCMServers\",\n        \"operationId\": \"CMServerService_ListCMServers\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListCMServersResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"searchValue\",\n            \"description\": \"name or ip like\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"CMServerService\"]\n      },\n      \"post\": {\n        \"summary\": \"CreateCMServer\",\n        \"operationId\": \"CMServerService_CreateCMServer\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2CMServer\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"cmserver\",\n            \"description\": \"create cm server resource\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/cmserverv2CreateCMServer\"\n            }\n          }\n        ],\n        \"tags\": [\"CMServerService\"]\n      }\n    },\n    \"/api/v2/cmservers/{id}\": {\n      \"delete\": {\n        \"summary\": \"delete one host by host_id\",\n        \"operationId\": \"CMServerService_DeleteCMServer\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"description\": \"the CM Server id of the CMServer\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"CMServerService\"]\n      },\n      \"patch\": {\n        \"summary\": \"UpdateCMServer\",\n        \"operationId\": \"CMServerService_UpdateCMServer\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2CMServer\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"description\": \"The id of the CMServer\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/CMServerServiceUpdateCMServerBody\"\n            }\n          }\n        ],\n        \"tags\": [\"CMServerService\"]\n      }\n    },\n    \"/api/v2/cmservers/{id}/clusters\": {\n      \"get\": {\n        \"summary\": \"Get CMServerCluster\",\n        \"operationId\": \"CMServerService_GetCMServerCluster\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2CMServerClustersResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"description\": \"the CMServer id of the CMServer\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"CMServerService\"]\n      }\n    },\n    \"/api/v2/cmservers/{id}:getWithTiup\": {\n      \"get\": {\n        \"summary\": \"get CMServer with tiup\",\n        \"operationId\": \"CMServerService_GetCMServerWithTiup\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2GetCMServerWithTiupResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"description\": \"the CM Server id of the CMServer\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"CMServerService\"]\n      }\n    },\n    \"/api/v2/cmservers/{tiupId}/clusters/{clusterName}\": {\n      \"get\": {\n        \"summary\": \"GetClusterTopology\",\n        \"operationId\": \"CMServerService_GetClusterTopology\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2GetClusterTopologyResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tiupId\",\n            \"description\": \"tiup_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"clusterName\",\n            \"description\": \"the cluster_name\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"CMServerService\"]\n      }\n    },\n    \"/api/v2/credentials\": {\n      \"get\": {\n        \"summary\": \"List credentials\",\n        \"operationId\": \"CredentialService_ListCredentials\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListCredentialsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"credentialType\",\n            \"description\": \"the credential type of the credential\\n\\n - CREDENTIAL_TYPE_UNSPECIFIED: resource type unspecified\\n - HOST: credential type host\\n - TIDB: credential type tidb\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"CREDENTIAL_TYPE_UNSPECIFIED\", \"HOST\", \"TIDB\"],\n            \"default\": \"CREDENTIAL_TYPE_UNSPECIFIED\"\n          },\n          {\n            \"name\": \"credentialId\",\n            \"description\": \"the credential id of the credential\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"CredentialService\"]\n      },\n      \"post\": {\n        \"summary\": \"Create credential\",\n        \"operationId\": \"CredentialService_CreateCredential\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Credential\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"credential\",\n            \"description\": \"\\nthe credential resource\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Credential\"\n            }\n          }\n        ],\n        \"tags\": [\"CredentialService\"]\n      }\n    },\n    \"/api/v2/credentials/{credentialId}\": {\n      \"get\": {\n        \"summary\": \"Get credential\",\n        \"operationId\": \"CredentialService_GetCredential\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Credential\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"credentialId\",\n            \"description\": \"the credential id of the credential\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"CredentialService\"]\n      },\n      \"delete\": {\n        \"summary\": \"Delete credential by credential id\",\n        \"operationId\": \"CredentialService_DeleteCredential\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"credentialId\",\n            \"description\": \"the credential id of the credential\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"CredentialService\"]\n      },\n      \"patch\": {\n        \"summary\": \"Update credential by credential id\",\n        \"operationId\": \"CredentialService_UpdateCredential\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Credential\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"credentialId\",\n            \"description\": \"the credential id of the credential\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/CredentialServiceUpdateCredentialBody\"\n            }\n          }\n        ],\n        \"tags\": [\"CredentialService\"]\n      }\n    },\n    \"/api/v2/credentials/{credentialId}:downloadRsaKey\": {\n      \"get\": {\n        \"summary\": \"Download credential public key and private key\",\n        \"operationId\": \"CredentialService_DownloadRSAKey\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2DownloadRSAKeyResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"credentialId\",\n            \"description\": \"the credential id of the credential\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"CredentialService\"]\n      }\n    },\n    \"/api/v2/credentials:generateRsaKey\": {\n      \"post\": {\n        \"summary\": \"Generate credential public key and private key\",\n        \"operationId\": \"CredentialService_GenerateRSAKey\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2GenerateRSAKeyResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2GenerateRSAKeyRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"CredentialService\"]\n      }\n    },\n    \"/api/v2/credentials:validateConnection\": {\n      \"post\": {\n        \"summary\": \"Validate credential is accessible\",\n        \"operationId\": \"CredentialService_ValidateConnection\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ValidateConnectionResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ValidateConnectionRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"CredentialService\"]\n      }\n    },\n    \"/api/v2/domains\": {\n      \"get\": {\n        \"summary\": \"List domains\",\n        \"operationId\": \"DomainService_ListDomains\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListDomainsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DomainService\"]\n      },\n      \"post\": {\n        \"summary\": \"Create domain\",\n        \"operationId\": \"DomainService_CreateDomain\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Domain\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"domain\",\n            \"description\": \"the domain with basic resource\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2CreateDomainBody\"\n            }\n          }\n        ],\n        \"tags\": [\"DomainService\"]\n      }\n    },\n    \"/api/v2/domains/{id}\": {\n      \"get\": {\n        \"summary\": \"Get domain\",\n        \"operationId\": \"DomainService_GetDomain\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Domain\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"description\": \"the domain id of the domain\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"DomainService\"]\n      },\n      \"delete\": {\n        \"summary\": \"Delete domain by domain id\",\n        \"operationId\": \"DomainService_DeleteDomain\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"description\": \"the domain id of the domain\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"DomainService\"]\n      },\n      \"patch\": {\n        \"summary\": \"Update domain basic info by domain id\",\n        \"operationId\": \"DomainService_UpdateDomain\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Domain\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"description\": \"the domain id of the domain\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/DomainServiceUpdateDomainBody\"\n            }\n          }\n        ],\n        \"tags\": [\"DomainService\"]\n      }\n    },\n    \"/api/v2/domains/{id}:getWithCMServer\": {\n      \"get\": {\n        \"summary\": \"Get domain\",\n        \"operationId\": \"DomainService_GetDomainWithCMServer\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2DomainWithCMServer\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"id\",\n            \"description\": \"the domain id of the domain\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"DomainService\"]\n      }\n    },\n    \"/api/v2/domains:getWithCMServer\": {\n      \"get\": {\n        \"summary\": \"List domains with cm server\",\n        \"operationId\": \"DomainService_ListDomainsWithCMServer\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListDomainsWithCMServerResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"DomainService\"]\n      }\n    },\n    \"/api/v2/hosts\": {\n      \"get\": {\n        \"summary\": \"ListHosts\",\n        \"operationId\": \"HostService_ListHosts\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListHostsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"searchValue\",\n            \"description\": \"The name of the user\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"locationIds\",\n            \"description\": \"location_ids\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          },\n          {\n            \"name\": \"tagIds\",\n            \"description\": \"tag_ids\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      },\n      \"post\": {\n        \"summary\": \"CreateHosts\",\n        \"operationId\": \"HostService_CreateHosts\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2HostCreateResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"host\",\n            \"description\": \"\\nhost resource\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2CreateHost\"\n            }\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      }\n    },\n    \"/api/v2/hosts/import/tasks\": {\n      \"post\": {\n        \"consumes\": [\"multipart/form-data\"],\n        \"description\": \"Upload a csv form data to host.\",\n        \"operationId\": \"HostService_Import\",\n        \"parameters\": [\n          {\n            \"description\": \"Upload a csv form data to host.\",\n            \"format\": \"binary\",\n            \"in\": \"formData\",\n            \"name\": \"hostData\",\n            \"required\": true,\n            \"type\": \"file\"\n          },\n          {\n            \"description\": \"The Credential_Id of the Import\",\n            \"in\": \"formData\",\n            \"name\": \"credentialId\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ImportTaskResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"summary\": \"import host\",\n        \"tags\": [\"HostService\"]\n      }\n    },\n    \"/api/v2/hosts/import/tasks/{taskId}\": {\n      \"get\": {\n        \"summary\": \"Import one host\",\n        \"operationId\": \"HostService_ImportTask\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ImportTaskResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"taskId\",\n            \"description\": \"task_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      }\n    },\n    \"/api/v2/hosts/import/tasks/{taskId}:confirm\": {\n      \"post\": {\n        \"summary\": \"HostConfirm one host\",\n        \"operationId\": \"HostService_HostConfirm\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ConfirmResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"taskId\",\n            \"description\": \"task_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/HostServiceHostConfirmBody\"\n            }\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      }\n    },\n    \"/api/v2/hosts/{hostId}\": {\n      \"get\": {\n        \"summary\": \"Get\",\n        \"operationId\": \"HostService_GetHost\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Host\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"hostId\",\n            \"description\": \"The host_id of the host\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      },\n      \"delete\": {\n        \"summary\": \"delete one host by host_id\",\n        \"operationId\": \"HostService_Delete\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"hostId\",\n            \"description\": \"The host_id of the host\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      },\n      \"patch\": {\n        \"summary\": \"update one host by host_id\",\n        \"operationId\": \"HostService_UpdateHost\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Host\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"hostId\",\n            \"description\": \"The host_id of the Host\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2HostServiceUpdateHostBody\"\n            }\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      }\n    },\n    \"/api/v2/hosts/{hostId}/disks\": {\n      \"get\": {\n        \"summary\": \"GetDisks\",\n        \"operationId\": \"HostService_GetDisks\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2HostDiskResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"hostId\",\n            \"description\": \"The host_id of the host\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      }\n    },\n    \"/api/v2/hosts/{hostId}/metrics/{name}/data\": {\n      \"get\": {\n        \"summary\": \"Get host metric data\",\n        \"operationId\": \"MetricsService_GetHostMetricData\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2HostMetricData\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"hostId\",\n            \"description\": \"Cluster ID\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"name\",\n            \"description\": \"Metric Name\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"startTime\",\n            \"description\": \"Start time in Unix timestamp format\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"endTime\",\n            \"description\": \"End time in Unix timestamp format\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"step\",\n            \"description\": \"Step time in seconds\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"label\",\n            \"description\": \"Line Label for the metric\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"range\",\n            \"description\": \"Time Range for the query\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"MetricsService\"]\n      }\n    },\n    \"/api/v2/hosts/{hostId}/report/{reportId}\": {\n      \"get\": {\n        \"summary\": \"Report\",\n        \"operationId\": \"HostService_Report\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ReportResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"hostId\",\n            \"description\": \"The host_id of the host\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"reportId\",\n            \"description\": \"The report_id of the host\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      }\n    },\n    \"/api/v2/hosts/{hostId}/tidbProcesses\": {\n      \"get\": {\n        \"summary\": \"GetInstances\",\n        \"operationId\": \"HostService_GetTiDBProcesses\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2HostTiDBProcessesResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"hostId\",\n            \"description\": \"The host_id of the host\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      }\n    },\n    \"/api/v2/hosts/{hostId}:fix\": {\n      \"post\": {\n        \"summary\": \"Fix\",\n        \"operationId\": \"HostService_Fix\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2HostFixResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"hostId\",\n            \"description\": \"The host_id of the host\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      }\n    },\n    \"/api/v2/hosts/{hostId}:systemCheck\": {\n      \"post\": {\n        \"summary\": \"Check\",\n        \"operationId\": \"HostService_Check\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2HostCheckResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"hostId\",\n            \"description\": \"The host_id of the host\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      }\n    },\n    \"/api/v2/hosts:batchDelete\": {\n      \"post\": {\n        \"summary\": \"delete one host by host_id\",\n        \"operationId\": \"HostService_BatchDelete\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BatchDeleteRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      }\n    },\n    \"/api/v2/hosts:download\": {\n      \"get\": {\n        \"summary\": \"HostConfirm one host\",\n        \"operationId\": \"HostService_DownloadListHosts\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2DownloadListHostResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"searchValue\",\n            \"description\": \"The name of the user\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"locationIds\",\n            \"description\": \"location_ids\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          },\n          {\n            \"name\": \"tagIds\",\n            \"description\": \"tag_ids\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          }\n        ],\n        \"tags\": [\"HostService\"]\n      }\n    },\n    \"/api/v2/hosts:downloadHostTemplate\": {\n      \"get\": {\n        \"summary\": \"DownloadHostTemplate one host\",\n        \"operationId\": \"HostService_DownloadHostTemplate\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2DownloadHostTemplateResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"tags\": [\"HostService\"]\n      }\n    },\n    \"/api/v2/license\": {\n      \"get\": {\n        \"summary\": \"GetLicense returns the license details\",\n        \"operationId\": \"LicenseService_GetLicense\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/licensev2License\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"tags\": [\"LicenseService\"]\n      }\n    },\n    \"/api/v2/license/devicecode\": {\n      \"get\": {\n        \"summary\": \"GetDeviceCode returns the device code to help activate the license\",\n        \"operationId\": \"LicenseService_GetDeviceCode\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2DeviceCode\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"tags\": [\"LicenseService\"]\n      }\n    },\n    \"/api/v2/license:activate\": {\n      \"post\": {\n        \"consumes\": [\"multipart/form-data\"],\n        \"description\": \"Upload a license using form data to activate.\",\n        \"operationId\": \"LicenseService_ActivateLicense\",\n        \"parameters\": [\n          {\n            \"description\": \"The content of the license file\\n\\nThe license file to upload to activate the license\",\n            \"format\": \"binary\",\n            \"in\": \"formData\",\n            \"name\": \"license\",\n            \"required\": true,\n            \"type\": \"file\"\n          }\n        ],\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/licensev2License\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"summary\": \"Activate a license\",\n        \"tags\": [\"LicenseService\"]\n      }\n    },\n    \"/api/v2/license:trial\": {\n      \"post\": {\n        \"summary\": \"ActivateFreeLicense activate the embedded free license\",\n        \"operationId\": \"LicenseService_ActivateFreeLicense\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/licensev2License\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"tags\": [\"LicenseService\"]\n      }\n    },\n    \"/api/v2/locations\": {\n      \"get\": {\n        \"summary\": \"list location\",\n        \"operationId\": \"LocationService_ListLocations\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListLocationsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"locationKey\",\n            \"description\": \"location key  (e.g., \\\"zone\\\", \\\"dc\\\")\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"zone\", \"dc\", \"rack\"]\n          },\n          {\n            \"name\": \"locationValue\",\n            \"description\": \"the Location value of the Location\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"parentId\",\n            \"description\": \"the Location parent_Id of the Location\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"LocationService\"]\n      },\n      \"post\": {\n        \"summary\": \"create CreateLocationRequest\",\n        \"operationId\": \"LocationService_CreateLocations\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Locations\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"location\",\n            \"description\": \"the Location basic resource\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Locations\"\n            }\n          }\n        ],\n        \"tags\": [\"LocationService\"]\n      }\n    },\n    \"/api/v2/locations/{locationId}\": {\n      \"get\": {\n        \"summary\": \"get Location\",\n        \"operationId\": \"LocationService_GetLocations\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Locations\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"locationId\",\n            \"description\": \"the Location id of the Location\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"LocationService\"]\n      },\n      \"delete\": {\n        \"summary\": \"delete Location by Location id\",\n        \"operationId\": \"LocationService_DeleteLocation\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"locationId\",\n            \"description\": \"the Location id of the Location\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"LocationService\"]\n      },\n      \"patch\": {\n        \"summary\": \"update Location basic info by Location id\",\n        \"operationId\": \"LocationService_UpdateLocations\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Locations\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"locationId\",\n            \"description\": \"the Location id of the Location\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/LocationServiceUpdateLocationsBody\"\n            }\n          }\n        ],\n        \"tags\": [\"LocationService\"]\n      }\n    },\n    \"/api/v2/login\": {\n      \"post\": {\n        \"summary\": \"Login allows a user to log in and start a session.\",\n        \"operationId\": \"UserService_Login\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2LoginRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"UserService\"]\n      }\n    },\n    \"/api/v2/logout\": {\n      \"post\": {\n        \"summary\": \"Logout allows a user to log out and end their session.\",\n        \"operationId\": \"UserService_Logout\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"tags\": [\"UserService\"]\n      }\n    },\n    \"/api/v2/metrics\": {\n      \"get\": {\n        \"summary\": \"Get metrics info\",\n        \"operationId\": \"MetricsService_GetMetrics\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Metrics\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"class\",\n            \"description\": \"Level 1 classification\\n\\n - unspecified: Unspecified\\n - cluster: Cluster metrics\\n - host: Host metrics\\n - overview: Overview metrics\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\"unspecified\", \"cluster\", \"host\", \"overview\"],\n            \"default\": \"unspecified\"\n          },\n          {\n            \"name\": \"group\",\n            \"description\": \"Level 2 grouping\\n\\n - unspecified: Unspecified group\\n - overview: Overview group\\n - basic: Basic group\\n - advanced: Advanced group\\n - resource: Resource group\\n - performance: Performance group\\n - process: Process group\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"unspecified\",\n              \"overview\",\n              \"basic\",\n              \"advanced\",\n              \"resource\",\n              \"performance\",\n              \"process\"\n            ],\n            \"default\": \"unspecified\"\n          },\n          {\n            \"name\": \"type\",\n            \"description\": \"Level 3 type\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"name\",\n            \"description\": \"The metric name\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"MetricsService\"]\n      }\n    },\n    \"/api/v2/overview/metrics/config\": {\n      \"get\": {\n        \"summary\": \"Get top metric config\",\n        \"operationId\": \"MetricsService_GetTopMetricConfig\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TopMetricConfig\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"tags\": [\"MetricsService\"]\n      }\n    },\n    \"/api/v2/overview/metrics/{name}/data\": {\n      \"get\": {\n        \"summary\": \"Get top metric data\",\n        \"operationId\": \"MetricsService_GetTopMetricData\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TopMetricData\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"name\",\n            \"description\": \"Metric name to query\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"startTime\",\n            \"description\": \"Start time for the query\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"endTime\",\n            \"description\": \"End time for the query\",\n            \"in\": \"query\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"step\",\n            \"description\": \"Step time for the query\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"limit\",\n            \"description\": \"Limit for the number of top results\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          }\n        ],\n        \"tags\": [\"MetricsService\"]\n      }\n    },\n    \"/api/v2/overview/status\": {\n      \"get\": {\n        \"summary\": \"Get overview status\",\n        \"operationId\": \"MetricsService_GetOverviewStatus\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2OverviewStatus\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"taskStartTime\",\n            \"description\": \"Task start time in Unix timestamp format\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"taskEndTime\",\n            \"description\": \"Task end time in Unix timestamp format\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          }\n        ],\n        \"tags\": [\"MetricsService\"]\n      }\n    },\n    \"/api/v2/roles\": {\n      \"get\": {\n        \"summary\": \"ListRoles retrieves a list of roles.\",\n        \"operationId\": \"RoleService_ListRoles\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListRolesResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"roleNameLike\",\n            \"description\": \"role_name_like\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"roleName\",\n            \"description\": \"The name of the role\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"RoleService\"]\n      },\n      \"post\": {\n        \"summary\": \"CreateRole creates a new role.\",\n        \"operationId\": \"RoleService_CreateRole\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Role\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"role\",\n            \"description\": \"\\nUser resource\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Role\"\n            }\n          }\n        ],\n        \"tags\": [\"RoleService\"]\n      }\n    },\n    \"/api/v2/roles/{roleId}\": {\n      \"delete\": {\n        \"summary\": \"DeleteRole deletes a role by role ID.\",\n        \"operationId\": \"RoleService_DeleteRole\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"roleId\",\n            \"description\": \"The id of the role\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          }\n        ],\n        \"tags\": [\"RoleService\"]\n      },\n      \"patch\": {\n        \"summary\": \"UpdateRole updates a role by role ID.\",\n        \"operationId\": \"RoleService_UpdateRole\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Role\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"roleId\",\n            \"description\": \"The id of the role\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/RoleServiceUpdateRoleBody\"\n            }\n          }\n        ],\n        \"tags\": [\"RoleService\"]\n      }\n    },\n    \"/api/v2/tags\": {\n      \"get\": {\n        \"summary\": \"List tags\",\n        \"operationId\": \"TagService_ListTags\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListTagsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"TagService\"]\n      },\n      \"post\": {\n        \"summary\": \"Create tag\",\n        \"operationId\": \"TagService_CreateTag\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/tagv2Tag\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tag\",\n            \"description\": \"the tag with basic resource\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/tagv2Tag\"\n            }\n          }\n        ],\n        \"tags\": [\"TagService\"]\n      }\n    },\n    \"/api/v2/tags/{tagId}\": {\n      \"get\": {\n        \"summary\": \"Get tag\",\n        \"operationId\": \"TagService_GetTag\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/tagv2Tag\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tagId\",\n            \"description\": \"the tag id of the tag\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"TagService\"]\n      },\n      \"delete\": {\n        \"summary\": \"Delete tag by tag id\",\n        \"operationId\": \"TagService_DeleteTag\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tagId\",\n            \"description\": \"the tag id of the tag\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"TagService\"]\n      },\n      \"patch\": {\n        \"summary\": \"Update tag basic info by tag id\",\n        \"operationId\": \"TagService_UpdateTag\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/tagv2Tag\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tagId\",\n            \"description\": \"the tag id of the tag\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/TagServiceUpdateTagBody\"\n            }\n          }\n        ],\n        \"tags\": [\"TagService\"]\n      }\n    },\n    \"/api/v2/tags/{tagId}:getWithBindings\": {\n      \"get\": {\n        \"summary\": \"Get tag with bindings\",\n        \"operationId\": \"TagService_GetTagWithBindings\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2GetTagWithBindingsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tagId\",\n            \"description\": \"the tag id of the tag\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"TagService\"]\n      }\n    },\n    \"/api/v2/tags:batchCreate\": {\n      \"post\": {\n        \"summary\": \"Batch create tags\",\n        \"operationId\": \"TagService_BatchCreateTags\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BatchCreateTagsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BatchCreateTagsRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"TagService\"]\n      }\n    },\n    \"/api/v2/tags:bindResource\": {\n      \"post\": {\n        \"summary\": \"Modify bind object by resource id\",\n        \"operationId\": \"TagService_BindResource\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BindResourceResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BindResourceRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"TagService\"]\n      }\n    },\n    \"/api/v2/tags:bindTag\": {\n      \"post\": {\n        \"summary\": \"Modify bind object by tag id\",\n        \"operationId\": \"TagService_BindTag\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BindTagResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2BindTagRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"TagService\"]\n      }\n    },\n    \"/api/v2/tags:listByResourceType\": {\n      \"get\": {\n        \"summary\": \"List tags by resource type\",\n        \"operationId\": \"TagService_ListTagsByResourceType\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListTagsByResourceTypeResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"tagKey\",\n            \"description\": \"the tag key which the tag values belong to\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"keyword\",\n            \"description\": \"the keyword which tag values similar to\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"resourceType\",\n            \"description\": \"the resource type of the tag has bound with\\n\\n - TAG_BIND_RESOURCE_TYPE_UNSPECIFIED: resource type unspecified\\n - HOST: resource type host\\n - TIUP: resource type tiup\\n - CLUSTER: resource type cluster\\n - CM_SERVER: resource type cm server\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"TAG_BIND_RESOURCE_TYPE_UNSPECIFIED\",\n              \"HOST\",\n              \"TIUP\",\n              \"CLUSTER\",\n              \"CM_SERVER\"\n            ],\n            \"default\": \"TAG_BIND_RESOURCE_TYPE_UNSPECIFIED\"\n          }\n        ],\n        \"tags\": [\"TagService\"]\n      }\n    },\n    \"/api/v2/tags:listKeys\": {\n      \"get\": {\n        \"summary\": \"List tag keys\",\n        \"operationId\": \"TagService_ListTagKeys\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListTagKeysResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"keyword\",\n            \"description\": \"the keyword which tag key similar to\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"resourceType\",\n            \"description\": \"the resource type of the tag has bound with\\n\\n - TAG_BIND_RESOURCE_TYPE_UNSPECIFIED: resource type unspecified\\n - HOST: resource type host\\n - TIUP: resource type tiup\\n - CLUSTER: resource type cluster\\n - CM_SERVER: resource type cm server\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"enum\": [\n              \"TAG_BIND_RESOURCE_TYPE_UNSPECIFIED\",\n              \"HOST\",\n              \"TIUP\",\n              \"CLUSTER\",\n              \"CM_SERVER\"\n            ],\n            \"default\": \"TAG_BIND_RESOURCE_TYPE_UNSPECIFIED\"\n          }\n        ],\n        \"tags\": [\"TagService\"]\n      }\n    },\n    \"/api/v2/tags:listWithBindings\": {\n      \"get\": {\n        \"summary\": \"List tags with bindings\",\n        \"operationId\": \"TagService_ListTagsWithBindings\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListTagsWithBindingsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"tagKeys\",\n            \"description\": \"the tag which the tag key in\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          },\n          {\n            \"name\": \"tagValueLike\",\n            \"description\": \"the tag which the tag value like\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"TagService\"]\n      }\n    },\n    \"/api/v2/taskflows\": {\n      \"get\": {\n        \"summary\": \"ListTasks retrieves a list of tasks.\",\n        \"operationId\": \"TaskFlowService_ListTaskFlows\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListTaskFlowsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"The number of tasks to retrieve per page.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Pagination token for retrieving the next page of tasks.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"The number of tasks to skip for pagination purposes.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"The sorting criteria for the task list.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"gtStartTime\",\n            \"description\": \"Filter tasks by start_time gt.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"date-time\"\n          },\n          {\n            \"name\": \"ltStartTime\",\n            \"description\": \"Filter tasks by start_time lt.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\",\n            \"format\": \"date-time\"\n          },\n          {\n            \"name\": \"parentId\",\n            \"description\": \"Filter tasks by parent_id.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"status\",\n            \"description\": \"Filter tasks by status.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          },\n          {\n            \"name\": \"taskId\",\n            \"description\": \"The unique identifier of the task.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"taskIdLike\",\n            \"description\": \"The unique identifier of the task ,fuzzy query.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"templateId\",\n            \"description\": \"The template_idr of the task.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          }\n        ],\n        \"tags\": [\"TaskFlowService\"]\n      }\n    },\n    \"/api/v2/taskflows/{taskId}\": {\n      \"get\": {\n        \"summary\": \"GetTask retrieves a task by task ID.\",\n        \"operationId\": \"TaskFlowService_GetTaskFlow\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TaskFlowDetail\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"taskId\",\n            \"description\": \"The unique identifier of the task.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"TaskFlowService\"]\n      }\n    },\n    \"/api/v2/taskflows/{taskId}/{nodeKey}\": {\n      \"get\": {\n        \"summary\": \"GetTask retrieves a task by task ID.\",\n        \"operationId\": \"TaskFlowService_GetTaskFlowInfo\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TaskFlowInfoResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"taskId\",\n            \"description\": \"The task_id of the task.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"nodeKey\",\n            \"description\": \"The node_key of the task.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"TaskFlowService\"]\n      }\n    },\n    \"/api/v2/taskflows/{taskId}:restartTaskFlow\": {\n      \"get\": {\n        \"summary\": \"RestartTaskFlow\",\n        \"operationId\": \"TaskFlowService_RestartTaskFlow\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2RestartTaskFlowResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"taskId\",\n            \"description\": \"The task_id of the task.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"TaskFlowService\"]\n      }\n    },\n    \"/api/v2/tidbParamTemplates\": {\n      \"get\": {\n        \"summary\": \"ListParameterTemplates retrieves a list of parameter templates\",\n        \"operationId\": \"ClusterParamTemplateService_ListParameterTemplates\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListParameterTemplatesResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterParamTemplateService\"]\n      },\n      \"post\": {\n        \"summary\": \"CreateParameterTemplate creates a new parameter template\",\n        \"operationId\": \"ClusterParamTemplateService_CreateParameterTemplate\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2CreateParameterTemplateRequest\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2CreateParameterTemplateRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterParamTemplateService\"]\n      }\n    },\n    \"/api/v2/tidbParamTemplates/params\": {\n      \"get\": {\n        \"summary\": \"ListParameters retrieves a list of parameters\",\n        \"operationId\": \"ClusterParamTemplateService_ListParameters\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListParametersResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"Page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"version\",\n            \"description\": \"version\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"name\",\n            \"description\": \"The name of the parameter\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"configSource\",\n            \"description\": \"The parameter instance type. e.g.: CLUSTER_CONFIG, GLOBAL_VARIABLES\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"instanceType\",\n            \"description\": \"The parameter instance type. e.g.: TiDB, TiKV, PD\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"ClusterParamTemplateService\"]\n      }\n    },\n    \"/api/v2/tidbParamTemplates/params/versions\": {\n      \"get\": {\n        \"summary\": \"GetSupportVersions retrieves a list of supported versions\",\n        \"operationId\": \"ClusterParamTemplateService_GetSupportVersions\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2SupportVersions\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"tags\": [\"ClusterParamTemplateService\"]\n      }\n    },\n    \"/api/v2/tidbParamTemplates/unsupportedParams:check\": {\n      \"post\": {\n        \"summary\": \"CheckUnsupportedParams checks if the parameters are supported in the specified version\",\n        \"operationId\": \"ClusterParamTemplateService_CheckUnsupportedParams\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2CheckUnsupportedParamsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2CheckUnsupportedParamsRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterParamTemplateService\"]\n      }\n    },\n    \"/api/v2/tidbParamTemplates/{templateId}\": {\n      \"get\": {\n        \"summary\": \"GetParameterTemplate retrieves a parameter template by ID\",\n        \"operationId\": \"ClusterParamTemplateService_GetParameterTemplate\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2GetParameterTemplateResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"templateId\",\n            \"description\": \"The ID of the parameter template\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          }\n        ],\n        \"tags\": [\"ClusterParamTemplateService\"]\n      },\n      \"delete\": {\n        \"summary\": \"DeleteParameterTemplate deletes a parameter template by ID\",\n        \"operationId\": \"ClusterParamTemplateService_DeleteParameterTemplate\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"templateId\",\n            \"description\": \"The ID of the parameter template\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          }\n        ],\n        \"tags\": [\"ClusterParamTemplateService\"]\n      },\n      \"put\": {\n        \"summary\": \"UpdateParameterTemplate updates a parameter template\",\n        \"operationId\": \"ClusterParamTemplateService_UpdateParameterTemplate\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2UpdateParameterTemplateRequest\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"templateId\",\n            \"description\": \"The ID of the parameter template\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/ClusterParamTemplateServiceUpdateParameterTemplateBody\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterParamTemplateService\"]\n      }\n    },\n    \"/api/v2/tidbParamTemplates/{templateId}/{paramId}\": {\n      \"delete\": {\n        \"summary\": \"DeleteParameter deletes a parameter by ID\",\n        \"operationId\": \"ClusterParamTemplateService_DeleteParameter\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"templateId\",\n            \"description\": \"The ID of the parameter template\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          },\n          {\n            \"name\": \"paramId\",\n            \"description\": \"The ID of the parameter\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"int64\"\n          }\n        ],\n        \"tags\": [\"ClusterParamTemplateService\"]\n      }\n    },\n    \"/api/v2/tiups\": {\n      \"get\": {\n        \"summary\": \"list Tiups\",\n        \"operationId\": \"TiupsService_ListTiups\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListTiupsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"page size\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"page token\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"Skip\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"order_by\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"searchValue\",\n            \"description\": \"the Tiups key of the Tiups\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"tagIds\",\n            \"description\": \"the Tiups tag_ids of the tagIds\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          },\n          {\n            \"name\": \"hostIds\",\n            \"description\": \"the Tiups host_ids of the tagIds\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            },\n            \"collectionFormat\": \"multi\"\n          }\n        ],\n        \"tags\": [\"TiupsService\"]\n      },\n      \"post\": {\n        \"summary\": \"create Tiups\",\n        \"operationId\": \"TiupsService_CreateTiups\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Tiups\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tiups\",\n            \"description\": \"the Tiups basic resource\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/tiupv2CreateTiups\"\n            }\n          }\n        ],\n        \"tags\": [\"TiupsService\"]\n      }\n    },\n    \"/api/v2/tiups/{tiupId}\": {\n      \"get\": {\n        \"summary\": \"get Tiups\",\n        \"operationId\": \"TiupsService_GetTiups\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Tiups\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tiupId\",\n            \"description\": \"the Tiups id of the Tiups\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"TiupsService\"]\n      },\n      \"delete\": {\n        \"summary\": \"delete Tiups by Tiups id\",\n        \"operationId\": \"TiupsService_DeleteTiups\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tiupId\",\n            \"description\": \"the Tiups id of the Tiups\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"TiupsService\"]\n      },\n      \"patch\": {\n        \"summary\": \"update Tiups basic info by Tiups id\",\n        \"operationId\": \"TiupsService_UpdateTiups\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2Tiups\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tiupId\",\n            \"description\": \"the tiup_id id of the Tiups\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TiupsServiceUpdateTiupsBody\"\n            }\n          }\n        ],\n        \"tags\": [\"TiupsService\"]\n      }\n    },\n    \"/api/v2/tiups/{tiupId}/clusters\": {\n      \"get\": {\n        \"summary\": \"Get TiupsCluster\",\n        \"operationId\": \"TiupsService_GetTiupsCluster\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2TiupsClustersResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tiupId\",\n            \"description\": \"the Tiups id of the Tiups\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"TiupsService\"]\n      }\n    },\n    \"/api/v2/tiups/{tiupId}/clusters/{clusterName}\": {\n      \"get\": {\n        \"summary\": \"GetClusterTopology\",\n        \"operationId\": \"TiupsService_GetClusterTopology\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2GetClusterTopologyResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tiupId\",\n            \"description\": \"tiup_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"clusterName\",\n            \"description\": \"the cluster_name\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"TiupsService\"]\n      }\n    },\n    \"/api/v2/tiups/{tiupId}/tidbVersions\": {\n      \"get\": {\n        \"summary\": \"GetTidbVersions\",\n        \"operationId\": \"TiupsService_GetTidbVersions\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2GetTidbVersionsResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"tiupId\",\n            \"description\": \"tiup_id\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"TiupsService\"]\n      }\n    },\n    \"/api/v2/users\": {\n      \"get\": {\n        \"summary\": \"ListUsers retrieves a list of users.\",\n        \"operationId\": \"UserService_ListUsers\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ListUsersResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"pageSize\",\n            \"description\": \"The number of users to retrieve per page.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"pageToken\",\n            \"description\": \"Pagination token for retrieving the next page of users.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"skip\",\n            \"description\": \"The number of users to skip for pagination purposes.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          {\n            \"name\": \"orderBy\",\n            \"description\": \"The sorting criteria for the user list.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"nameLike\",\n            \"description\": \"Filter users by username using a \\\"like\\\" operation.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"emailLike\",\n            \"description\": \"Filter users by email using a \\\"like\\\" operation.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"roleName\",\n            \"description\": \"Filter users by role name.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"UserService\"]\n      },\n      \"post\": {\n        \"summary\": \"CreateUser creates a new user.\",\n        \"operationId\": \"UserService_CreateUser\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2User\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"user\",\n            \"description\": \"\\nUser resource\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2User\"\n            }\n          }\n        ],\n        \"tags\": [\"UserService\"]\n      }\n    },\n    \"/api/v2/users/profile\": {\n      \"get\": {\n        \"summary\": \"GetUserProfile retrieves the profile information of the authenticated user.\",\n        \"operationId\": \"UserService_GetUserProfile\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2UserProfile\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"tags\": [\"UserService\"]\n      }\n    },\n    \"/api/v2/users/{userId}\": {\n      \"get\": {\n        \"summary\": \"GetUser retrieves a user by user ID.\",\n        \"operationId\": \"UserService_GetUser\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2User\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"userId\",\n            \"description\": \"The user_id of the user\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"UserService\"]\n      },\n      \"delete\": {\n        \"summary\": \"DeleteUser deletes a user by user ID.\",\n        \"operationId\": \"UserService_DeleteUser\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"userId\",\n            \"description\": \"The id of the user\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          }\n        ],\n        \"tags\": [\"UserService\"]\n      },\n      \"patch\": {\n        \"summary\": \"UpdateUser updates a user's information by user ID.\",\n        \"operationId\": \"UserService_UpdateUser\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2User\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"userId\",\n            \"description\": \"The unique user ID of the user.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/UserServiceUpdateUserBody\"\n            }\n          }\n        ],\n        \"tags\": [\"UserService\"]\n      }\n    },\n    \"/api/v2/users/{userId}:resetPassword\": {\n      \"patch\": {\n        \"summary\": \"ResetPassword allows an admin user to reset the password of another user.\",\n        \"operationId\": \"UserService_ResetPassword\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"userId\",\n            \"description\": \"The id of the user\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/UserServiceResetPasswordBody\"\n            }\n          }\n        ],\n        \"tags\": [\"UserService\"]\n      }\n    },\n    \"/api/v2/users:changePassword\": {\n      \"patch\": {\n        \"summary\": \"ChangePassword allows the authenticated user to change their password.\",\n        \"operationId\": \"UserService_ChangePassword\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"type\": \"object\",\n              \"properties\": {}\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"body\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ChangePasswordRequest\"\n            }\n          }\n        ],\n        \"tags\": [\"UserService\"]\n      }\n    },\n    \"/api/v2/users:validateSession\": {\n      \"get\": {\n        \"summary\": \"ValidateSession verifies the validity of the current session.\",\n        \"operationId\": \"UserService_ValidateSession\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ValidateSessionResponse\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"tags\": [\"UserService\"]\n      }\n    },\n    \"/documentation/errorDetail\": {\n      \"get\": {\n        \"summary\": \"GetTemErrorDetail\",\n        \"operationId\": \"ApiKeyService_GetTemErrorDetail\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/v2ErrorDetail\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/rpcStatus\"\n            }\n          }\n        },\n        \"tags\": [\"ApiKeyService\"]\n      }\n    }\n  },\n  \"definitions\": {\n    \"AlertServiceSilenceEventBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"silenceStartTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Silence start time\"\n        },\n        \"silenceEndTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Silence end time\"\n        }\n      },\n      \"title\": \"SilenceAlertRequest represents a request to silence an alert\",\n      \"required\": [\"silenceStartTime\", \"silenceEndTime\"]\n    },\n    \"AlertServiceUpdateChannelBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"type\": {\n          \"type\": \"string\",\n          \"enum\": [\"email\", \"webhook\"],\n          \"title\": \"The channel type\"\n        },\n        \"emailConfig\": {\n          \"$ref\": \"#/definitions/v2EmailConfig\",\n          \"title\": \"Email configuration, required if type is EMAIL\"\n        },\n        \"webhookConfig\": {\n          \"$ref\": \"#/definitions/v2WebhookConfig\",\n          \"title\": \"Webhook configuration, required if type is WEBHOOK\"\n        },\n        \"channelMonitorObjects\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ChannelMonitorObject\"\n          },\n          \"title\": \"Channel Object List\"\n        },\n        \"updateChannelObjects\": {\n          \"type\": \"boolean\",\n          \"title\": \"Whether to update monitor object associations. If true, existing associations will be replaced with the provided monitor_objects. If false, monitor object associations will remain unchanged\"\n        }\n      },\n      \"title\": \"UpdateAlertChannelRequest represents a request to update an alert channel\"\n    },\n    \"AlertServiceUpdateMonitorObjectBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"application\": {\n          \"type\": \"string\",\n          \"title\": \"The application of the monitor object\"\n        },\n        \"objectType\": {\n          \"type\": \"string\",\n          \"title\": \"The type of the monitor object\"\n        }\n      },\n      \"title\": \"UpdateMonitorObjectRequest defines the request for updating an existing monitor object\"\n    },\n    \"AlertServiceUpdateObjectRuleBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The name of the rule\"\n        },\n        \"expr\": {\n          \"type\": \"string\",\n          \"title\": \"The Prometheus expression\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\"enabled\", \"disabled\"],\n          \"title\": \"The rule status\"\n        },\n        \"duration\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The duration in seconds\"\n        },\n        \"labels\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The labels\"\n        },\n        \"annotations\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The annotations\"\n        },\n        \"level\": {\n          \"type\": \"string\",\n          \"enum\": [\"warning\", \"critical\", \"emergency\"],\n          \"title\": \"The alert level\"\n        }\n      },\n      \"title\": \"UpdateObjectRuleRequest defines the request for updating an existing object rule\"\n    },\n    \"AlertServiceUpdateRuleBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"metricId\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The metric ID\"\n        },\n        \"monitorObjectId\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The id of   monitor object\"\n        },\n        \"application\": {\n          \"type\": \"string\",\n          \"title\": \"The application of  monitor object\"\n        },\n        \"duration\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The duration in seconds\"\n        },\n        \"labels\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The labels\"\n        },\n        \"annotations\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The annotations\"\n        }\n      },\n      \"title\": \"UpdateRuleRequest defines the request for updating an existing rule\"\n    },\n    \"AlertServiceUpdateTemplateBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Template name\"\n        },\n        \"tidbVersion\": {\n          \"type\": \"string\",\n          \"title\": \"TiDB version this template applies to\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"Template description\"\n        }\n      },\n      \"title\": \"UpdateAlertTemplateRequest represents a request to update an alert template\"\n    },\n    \"AlertServiceUpdateTemplateRuleBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"duration\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Rule to update\"\n        },\n        \"labels\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The labels of the Prometheus rule\"\n        },\n        \"annotations\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The annotations of the Prometheus rule\"\n        },\n        \"level\": {\n          \"type\": \"string\",\n          \"enum\": [\"warning\", \"critical\", \"emergency\"],\n          \"title\": \"The alert level\"\n        }\n      },\n      \"title\": \"UpdateTemplateRuleRequest represents a request to update a rule in a template\"\n    },\n    \"ApiKeyServiceUpdateApiKeyBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"secretKey\": {\n          \"type\": \"string\",\n          \"title\": \"The secret_key of apikey\"\n        },\n        \"creator\": {\n          \"type\": \"string\",\n          \"title\": \"The creator of apikey\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\"disable\", \"enable\"],\n          \"title\": \"The apikey status\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"The description of apikey\"\n        }\n      },\n      \"title\": \"UpdateApikeyRequest Request\"\n    },\n    \"CMServerServiceUpdateCMServerBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"sshPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The SSHPort of the CMServer Host\"\n        },\n        \"credentialId\": {\n          \"type\": \"string\",\n          \"title\": \"The Credential_Id of the CMServer Host\"\n        },\n        \"serverName\": {\n          \"type\": \"string\",\n          \"title\": \"The CMServer name of the CMServer Host\"\n        },\n        \"tagIds\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The tagIds of the CMServer Host\"\n        },\n        \"servicePort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The proxy port of the CMServer Host\"\n        }\n      },\n      \"title\": \"UpdateCMServerRequest\",\n      \"required\": [\"serverName\", \"servicePort\"]\n    },\n    \"ClusterBRServiceCreateBackupTaskBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the task\"\n        },\n        \"destination\": {\n          \"type\": \"string\",\n          \"title\": \"Destination of the task or source for restore\"\n        },\n        \"accessKeyId\": {\n          \"type\": \"string\",\n          \"title\": \"Access key ID for the task\"\n        },\n        \"secretAccessKey\": {\n          \"type\": \"string\",\n          \"title\": \"Secret access key for the task\"\n        },\n        \"rateLimit\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Rate limit for the task\"\n        },\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Concurrency for the task\"\n        },\n        \"logFile\": {\n          \"type\": \"string\",\n          \"title\": \"Log file for the task\"\n        },\n        \"retention\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Retention for the task\"\n        }\n      },\n      \"title\": \"CreateBackupTaskRequest represents the request to create a backup task\",\n      \"required\": [\"destination\"]\n    },\n    \"ClusterBRServiceCreateRestoreTaskBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"targetClusterId\": {\n          \"type\": \"string\",\n          \"title\": \"Target cluster ID, only for restore\"\n        },\n        \"type\": {\n          \"$ref\": \"#/definitions/v2ClusterBRTypeEnumData\",\n          \"title\": \"Type of the task, only restore by file and restore by time allowed\"\n        },\n        \"backupTaskId\": {\n          \"type\": \"string\",\n          \"title\": \"Backup task ID\"\n        },\n        \"restoreTime\": {\n          \"type\": \"string\",\n          \"title\": \"Restore time for the task\"\n        },\n        \"destination\": {\n          \"type\": \"string\",\n          \"title\": \"Destination of the task or source for restore\"\n        },\n        \"accessKeyId\": {\n          \"type\": \"string\",\n          \"title\": \"Access key ID for the task\"\n        },\n        \"secretAccessKey\": {\n          \"type\": \"string\",\n          \"title\": \"Secret access key for the task\"\n        },\n        \"rateLimit\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Rate limit for the task\"\n        },\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Concurrency for the task\"\n        },\n        \"logFile\": {\n          \"type\": \"string\",\n          \"title\": \"Log file for the task\"\n        }\n      },\n      \"title\": \"CreateRestoreTaskRequest represents the request to create a br task\",\n      \"required\": [\"targetClusterId\"]\n    },\n    \"ClusterParamServiceUpdateClusterConfigBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"instanceType\": {\n          \"type\": \"string\",\n          \"title\": \"InstanceType is the instance type of the parameter\"\n        },\n        \"instance\": {\n          \"type\": \"string\",\n          \"title\": \"Instance is the instance of the parameter\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Name is the name of the parameter\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"title\": \"Value is the value of the parameter\"\n        },\n        \"oldValue\": {\n          \"type\": \"string\",\n          \"title\": \"OldValue is the old value of the parameter\"\n        },\n        \"resetDefault\": {\n          \"type\": \"boolean\",\n          \"title\": \"ResetDefault is if the parameter will be reset to default\"\n        }\n      },\n      \"title\": \"UpdateClusterConfigRequest is the request message for UpdateClusterConfig\",\n      \"required\": [\n        \"instanceType\",\n        \"instance\",\n        \"name\",\n        \"value\",\n        \"oldValue\",\n        \"resetDefault\"\n      ]\n    },\n    \"ClusterParamServiceUpdateClusterVariableBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Name is the name of the parameter variable\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"title\": \"Value is the value of the parameter variable\"\n        },\n        \"oldValue\": {\n          \"type\": \"string\",\n          \"title\": \"OldValue is the old value of the parameter variable\"\n        }\n      },\n      \"title\": \"UpdateClusterVariableRequest is the request message for UpdateClusterVariable\",\n      \"required\": [\"name\", \"value\", \"oldValue\"]\n    },\n    \"ClusterParamTemplateServiceUpdateParameterTemplateBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"parameterTemplate\": {\n          \"$ref\": \"#/definitions/v2ParameterTemplate\",\n          \"title\": \"The parameter template\"\n        },\n        \"templateParameterMappings\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TemplateParameterMapping\"\n          },\n          \"title\": \"The mappings of the parameter template\"\n        }\n      },\n      \"title\": \"UpdateParameterTemplateRequest represents an update parameter template request\",\n      \"required\": [\"parameterTemplate\", \"templateParameterMappings\"]\n    },\n    \"ClusterServiceBatchPauseBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"instanceId\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The instance_id of the cluster\"\n        },\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"concurrency\"\n        },\n        \"waitTime\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"wait_time\"\n        }\n      },\n      \"title\": \"BatchPauseRequest\",\n      \"required\": [\"instanceId\"]\n    },\n    \"ClusterServiceBatchReloadBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"instanceId\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The instance_id of the cluster\"\n        },\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"concurrency\"\n        },\n        \"waitTime\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"wait_time\"\n        },\n        \"transferTime\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"transfer_time\"\n        },\n        \"skipRestart\": {\n          \"type\": \"boolean\",\n          \"title\": \"skip_restart\"\n        },\n        \"force\": {\n          \"type\": \"boolean\",\n          \"title\": \"force\"\n        }\n      },\n      \"title\": \"BatchReloadRequest\",\n      \"required\": [\"instanceId\"]\n    },\n    \"ClusterServiceBatchRestartBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"instanceId\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The instance_id of the cluster\"\n        },\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"concurrency\"\n        },\n        \"waitTime\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"wait_time\"\n        }\n      },\n      \"title\": \"BatchRestartRequest\",\n      \"required\": [\"instanceId\"]\n    },\n    \"ClusterServiceBatchResumeBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"instanceId\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The instance_id of the cluster\"\n        },\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"concurrency\"\n        },\n        \"waitTime\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"wait_time\"\n        }\n      },\n      \"title\": \"BatchResumeRequest\",\n      \"required\": [\"instanceId\"]\n    },\n    \"ClusterServiceCancelTaskFlowBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id\"\n        }\n      },\n      \"title\": \"CancelTaskFlowRequest\",\n      \"required\": [\"taskId\"]\n    },\n    \"ClusterServiceDeployClusterBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"concurrency\"\n        },\n        \"waitTime\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"wait_time\"\n        }\n      },\n      \"title\": \"DeployRequest\"\n    },\n    \"ClusterServiceDestroyClusterBody\": {\n      \"type\": \"object\",\n      \"title\": \"DestroyClusterRequest\"\n    },\n    \"ClusterServiceOfflineClusterBody\": {\n      \"type\": \"object\",\n      \"title\": \"OfflineClusterRequest\"\n    },\n    \"ClusterServicePauseClusterBody\": {\n      \"type\": \"object\",\n      \"title\": \"DeployRequest\"\n    },\n    \"ClusterServiceReloadClusterBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"concurrency\"\n        },\n        \"waitTime\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"wait_time\"\n        },\n        \"skipRestart\": {\n          \"type\": \"boolean\",\n          \"title\": \"skip_restart\"\n        },\n        \"transferTimeout\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"wait_time\"\n        },\n        \"force\": {\n          \"type\": \"boolean\",\n          \"title\": \"force\"\n        }\n      },\n      \"title\": \"ReloadClusterRequest\"\n    },\n    \"ClusterServiceRestartClusterBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"concurrency\"\n        },\n        \"waitTime\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"wait_time\"\n        }\n      },\n      \"title\": \"StartClusterRequest\"\n    },\n    \"ClusterServiceResumeClusterBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"concurrency\"\n        },\n        \"waitTime\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"wait_time\"\n        }\n      },\n      \"title\": \"ResumeClusterRequest\"\n    },\n    \"ClusterServiceRetryTaskFlowBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id\"\n        }\n      },\n      \"title\": \"RetryTaskFlowRequest\",\n      \"required\": [\"taskId\"]\n    },\n    \"ClusterServiceScaleInClusterBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"instanceId\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The instance_id of the cluster\"\n        }\n      },\n      \"title\": \"ScaleInClusterRequest\",\n      \"required\": [\"instanceId\"]\n    },\n    \"ClusterServiceScaleOutClusterBody\": {\n      \"type\": \"object\",\n      \"title\": \"ScaleOutClusterRequest\"\n    },\n    \"CredentialServiceUpdateCredentialBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"userName\": {\n          \"type\": \"string\",\n          \"title\": \"the user name of the credential\"\n        },\n        \"credentialType\": {\n          \"$ref\": \"#/definitions/v2CredentialType\",\n          \"title\": \"the credential type of the credential\"\n        },\n        \"validateType\": {\n          \"$ref\": \"#/definitions/v2CredentialValidateType\",\n          \"title\": \"the validate type of the credential\"\n        },\n        \"credentialName\": {\n          \"type\": \"string\",\n          \"title\": \"the credential name of the credential\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"the description of the credential\"\n        },\n        \"hostCredential\": {\n          \"$ref\": \"#/definitions/v2HostCredentialObject\",\n          \"title\": \"the host credential object\"\n        },\n        \"tidbCredential\": {\n          \"$ref\": \"#/definitions/v2TiDBCredentialObject\",\n          \"title\": \"the tidb cluster credential object\"\n        },\n        \"forceUpdate\": {\n          \"type\": \"boolean\",\n          \"title\": \"auto ssh-copy or set password when change validate type host credential\"\n        }\n      },\n      \"title\": \"UpdateCredential Request\",\n      \"required\": [\"userName\", \"credentialType\", \"validateType\"]\n    },\n    \"DiagnosisServiceAddSqlLimitBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"resourceGroup\": {\n          \"type\": \"string\",\n          \"title\": \"Resource group\"\n        },\n        \"action\": {\n          \"type\": \"string\",\n          \"enum\": [\"DRYRUN\", \"COOLDOWN\", \"KILL\"],\n          \"title\": \"Action\"\n        },\n        \"watchText\": {\n          \"type\": \"string\",\n          \"title\": \"Watch text\"\n        }\n      },\n      \"title\": \"Request message for creating SQL limit\",\n      \"required\": [\"resourceGroup\", \"action\", \"watchText\"]\n    },\n    \"DiagnosisServiceRemoveSqlLimitBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"watchText\": {\n          \"type\": \"string\",\n          \"title\": \"Watch text\"\n        },\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"SQl limit id\"\n        }\n      },\n      \"title\": \"Request message for removing SQL limit\",\n      \"required\": [\"watchText\", \"id\"]\n    },\n    \"DiagnosisServiceUpdateTopSqlConfigsBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"enable\": {\n          \"type\": \"boolean\",\n          \"title\": \"tidb_enable_stmt_summary\"\n        },\n        \"refreshInterval\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"tidb_stmt_summary_refresh_interval\"\n        },\n        \"historySize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"tidb_stmt_summary_history_size\"\n        },\n        \"maxSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"tidb_stmt_summary_max_stmt_count\"\n        },\n        \"internalQuery\": {\n          \"type\": \"boolean\",\n          \"title\": \"tidb_stmt_summary_internal_query\"\n        }\n      },\n      \"title\": \"Request message for updating top sql configs\",\n      \"required\": [\"enable\"]\n    },\n    \"DomainServiceUpdateDomainBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"the domain name of the domain\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"the domain description of the domain\"\n        }\n      },\n      \"title\": \"UpdateDomain Request\"\n    },\n    \"GlobalBRServiceUpdateBackupPolicyBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The policy name\"\n        },\n        \"logBackup\": {\n          \"type\": \"boolean\",\n          \"title\": \"LogBackup means whether to backup log\"\n        },\n        \"destination\": {\n          \"type\": \"string\",\n          \"title\": \"Destination of the backup\"\n        },\n        \"accessKeyId\": {\n          \"type\": \"string\",\n          \"title\": \"AccessKeyID\"\n        },\n        \"secretAccessKey\": {\n          \"type\": \"string\",\n          \"title\": \"SecretAccessKey\"\n        },\n        \"rateLimit\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"RateLimit\"\n        },\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Concurrency\"\n        },\n        \"logFile\": {\n          \"type\": \"string\",\n          \"title\": \"LogFile\"\n        },\n        \"cycle\": {\n          \"$ref\": \"#/definitions/v2CycleEnumData\",\n          \"title\": \"Cycle of backup\"\n        },\n        \"frequency\": {\n          \"type\": \"string\",\n          \"title\": \"Frequency: week:0~6 for Sunday To Saturday, month:1~31 for Date.example:1,2,3,4,5,6,7\"\n        },\n        \"time\": {\n          \"type\": \"string\",\n          \"title\": \"Time\"\n        },\n        \"retention\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Retention\"\n        },\n        \"clusters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Cluster\"\n          },\n          \"title\": \"Clusters\"\n        },\n        \"clusterIds\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"ClusterIDs\"\n        }\n      },\n      \"title\": \"BackupPolicy represents a backup policy\",\n      \"required\": [\n        \"name\",\n        \"logBackup\",\n        \"destination\",\n        \"cycle\",\n        \"frequency\",\n        \"time\",\n        \"retention\"\n      ]\n    },\n    \"HostServiceHostConfirmBody\": {\n      \"type\": \"object\",\n      \"title\": \"ConfirmRequest\"\n    },\n    \"LocationServiceUpdateLocationsBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"location\": {\n          \"$ref\": \"#/definitions/v2Locations\",\n          \"title\": \"the Location basic resource\"\n        }\n      },\n      \"title\": \"UpdateLocation Request\"\n    },\n    \"RoleServiceUpdateRoleBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"roleName\": {\n          \"type\": \"string\",\n          \"title\": \"The name of the role\"\n        },\n        \"roleType\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The id of the role\"\n        },\n        \"detail\": {\n          \"type\": \"string\",\n          \"title\": \"The detail of the role\"\n        },\n        \"note\": {\n          \"type\": \"string\",\n          \"title\": \"The note of the role\"\n        }\n      },\n      \"title\": \"Update Role Request\",\n      \"required\": [\"roleName\"]\n    },\n    \"TagServiceUpdateTagBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tagKey\": {\n          \"type\": \"string\",\n          \"title\": \"the tag key of the tag\"\n        },\n        \"tagValue\": {\n          \"type\": \"string\",\n          \"title\": \"the tag value of the tag\"\n        }\n      },\n      \"title\": \"UpdateTag Request\",\n      \"required\": [\"tagValue\"]\n    },\n    \"UserServiceResetPasswordBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"newPassword\": {\n          \"type\": \"string\",\n          \"title\": \"User new password\"\n        }\n      },\n      \"title\": \"ResetPasswordRequest Request\",\n      \"required\": [\"newPassword\"]\n    },\n    \"UserServiceUpdateUserBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"email\": {\n          \"type\": \"string\",\n          \"description\": \"The email address of the user.\"\n        },\n        \"note\": {\n          \"type\": \"string\",\n          \"description\": \"Additional notes about the user.\"\n        },\n        \"userType\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"description\": \"The type of the user (e.g., admin, regular user).\"\n        },\n        \"phone\": {\n          \"type\": \"string\",\n          \"description\": \"The user's phone number.\"\n        },\n        \"roles\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2UserRole\"\n          },\n          \"description\": \"The roles assigned to the user.\"\n        }\n      },\n      \"title\": \"UpdateUser Request\"\n    },\n    \"clusterv2ClusterTaskFlow\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"description\": \"The unique identifier of the task.\"\n        },\n        \"templateId\": {\n          \"type\": \"string\",\n          \"description\": \"The template ID associated with the task.\"\n        },\n        \"creator\": {\n          \"type\": \"string\",\n          \"description\": \"The creator of the task.\"\n        },\n        \"parentId\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"tidb:deploy\",\n            \"tidb:takeover\",\n            \"tidb:scaleOut\",\n            \"tidb:scaleIn\",\n            \"tidb:destroy\",\n            \"tidb:stop\",\n            \"tidb:start\",\n            \"tidb:restart\",\n            \"tidb:reload\"\n          ],\n          \"description\": \"The parent task identifier.\",\n          \"title\": \"The parent enum\"\n        },\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"description\": \"The creator of the task.\"\n        },\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"description\": \"The creator of the task.\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"success\",\n            \"abort\",\n            \"timeout\",\n            \"failed\",\n            \"running\",\n            \"pending\"\n          ],\n          \"description\": \"The status of the task.\",\n          \"title\": \"The task status\"\n        },\n        \"startTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"description\": \"The timestamp when the task started.\",\n          \"readOnly\": true\n        },\n        \"endTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"description\": \"The timestamp when the task ended.\",\n          \"readOnly\": true\n        }\n      },\n      \"title\": \"Task ClusterTaskFlow\",\n      \"required\": [\"taskId\", \"templateId\"]\n    },\n    \"clusterv2ConfigTemplate\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"templateId\": {\n          \"type\": \"string\",\n          \"title\": \"The tidb_version\"\n        },\n        \"component\": {\n          \"type\": \"string\",\n          \"title\": \"The component\"\n        },\n        \"componentKey\": {\n          \"type\": \"string\",\n          \"title\": \"The tidb_version\"\n        },\n        \"componentValue\": {\n          \"type\": \"string\",\n          \"title\": \"The tidb_version\"\n        }\n      },\n      \"title\": \"ConfigTemplate\",\n      \"required\": [\"templateId\", \"component\", \"componentKey\", \"componentValue\"]\n    },\n    \"clusterv2TakeoverCluster\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_name for cluster\"\n        },\n        \"user\": {\n          \"type\": \"string\",\n          \"title\": \"The user for cluster\"\n        },\n        \"password\": {\n          \"type\": \"string\",\n          \"title\": \"The password for cluster\"\n        }\n      },\n      \"title\": \"TakeoverCluster\",\n      \"required\": [\"clusterName\", \"user\"]\n    },\n    \"cmserverv2CreateCMServer\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"ip\": {\n          \"type\": \"string\",\n          \"title\": \"The ip of the CMServer Host\"\n        },\n        \"sshPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The SSHPort of the CMServer Host\"\n        },\n        \"credentialId\": {\n          \"type\": \"string\",\n          \"title\": \"The credential_id of the CMServer Host\"\n        },\n        \"tagIds\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The tagIds of the CMServer\"\n        },\n        \"serverName\": {\n          \"type\": \"string\",\n          \"title\": \"The CMServer name of the CMServer Host\"\n        },\n        \"domainId\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The domain id of the CMServer\"\n        },\n        \"servicePort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The proxy port of the CMServer Host\"\n        },\n        \"serviceHome\": {\n          \"type\": \"string\",\n          \"title\": \"The CMServer home of the CMServer Host\"\n        },\n        \"tiupHome\": {\n          \"type\": \"string\",\n          \"title\": \"The Tiup home of the CMServer Host\"\n        }\n      },\n      \"title\": \"CreateCMServer\",\n      \"required\": [\n        \"ip\",\n        \"sshPort\",\n        \"credentialId\",\n        \"serverName\",\n        \"domainId\",\n        \"servicePort\",\n        \"serviceHome\",\n        \"tiupHome\"\n      ]\n    },\n    \"hostv2Report\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"reportId\": {\n          \"type\": \"string\",\n          \"title\": \"The report_id of the Report\"\n        },\n        \"hostId\": {\n          \"type\": \"string\",\n          \"title\": \"The host_id of the Report\"\n        },\n        \"checkId\": {\n          \"type\": \"string\",\n          \"title\": \"The checkId of the Report\"\n        },\n        \"checkName\": {\n          \"type\": \"string\",\n          \"title\": \"The checkName of the Report\"\n        },\n        \"checkOut\": {\n          \"type\": \"string\",\n          \"title\": \"The checkOut of the Report\"\n        },\n        \"checkDesc\": {\n          \"type\": \"string\",\n          \"title\": \"The checkDesc of the Report\"\n        },\n        \"checkResult\": {\n          \"type\": \"string\",\n          \"enum\": [\"passed\", \"failed\", \"warned\"],\n          \"title\": \"check optional (e.g., \\\"passed\\\", \\\"failed\\\")\"\n        },\n        \"optional\": {\n          \"type\": \"boolean\",\n          \"enum\": [\"true\", \"false\"],\n          \"title\": \"check optional (e.g., \\\"true\\\", \\\"false\\\")\"\n        },\n        \"fixable\": {\n          \"type\": \"boolean\",\n          \"enum\": [\"true\", \"false\"],\n          \"title\": \"check fixable (e.g., \\\"true\\\", \\\"false\\\")\"\n        },\n        \"checkBody\": {\n          \"type\": \"string\",\n          \"title\": \"The checkBody of the Report\"\n        }\n      },\n      \"title\": \"Report\",\n      \"required\": [\"reportId\"]\n    },\n    \"hostv2UpdateHost\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"hostId\": {\n          \"type\": \"string\",\n          \"title\": \"The host_id of the Host\"\n        },\n        \"sshPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The SSHPort of the Host\"\n        },\n        \"credentialId\": {\n          \"type\": \"string\",\n          \"title\": \"The Credential_Id of the Host\"\n        },\n        \"locationId\": {\n          \"type\": \"string\",\n          \"title\": \"The locationId of the Host\"\n        },\n        \"tagIds\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The tagIds of the Host\"\n        },\n        \"comment\": {\n          \"type\": \"string\",\n          \"title\": \"The comment of the Host\"\n        }\n      },\n      \"title\": \"UpdateHost\"\n    },\n    \"licensev2License\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"licenseId\": {\n          \"type\": \"string\",\n          \"title\": \"License ID\"\n        },\n        \"version\": {\n          \"type\": \"string\",\n          \"title\": \"Version represents the supported version of TEM\"\n        },\n        \"licenseType\": {\n          \"$ref\": \"#/definitions/v2LicenseTypeEnumData\",\n          \"title\": \"LicenseTypeEnum represents the type of license: free, ultimate\"\n        },\n        \"allow\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"Array of supported features in url prefix\"\n        },\n        \"deny\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"Array of unsupported features in url prefix\"\n        },\n        \"activateAt\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Activation date of license\"\n        },\n        \"expirationAt\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Expiration date of license\"\n        },\n        \"signature\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"Signature of license\"\n        },\n        \"hosts\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Number of hosts\"\n        },\n        \"vcpu\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Number of vcpu\"\n        },\n        \"alerts\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Number of alters\"\n        },\n        \"customerCode\": {\n          \"type\": \"string\",\n          \"title\": \"Customer code to restrict the range of features\"\n        },\n        \"deviceCode\": {\n          \"type\": \"string\",\n          \"title\": \"Device code which is bound to the license\"\n        },\n        \"status\": {\n          \"$ref\": \"#/definitions/v2LicenseStatusEnumData\",\n          \"title\": \"License status\"\n        }\n      },\n      \"title\": \"License\"\n    },\n    \"metricsv2Value\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"timestamp\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Timestamp of the value\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"title\": \"The actual value\"\n        }\n      },\n      \"title\": \"Value represents a single value in the query result\"\n    },\n    \"protobufAny\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"@type\": {\n          \"type\": \"string\",\n          \"description\": \"A URL/resource name that uniquely identifies the type of the serialized\\nprotocol buffer message. This string must contain at least\\none \\\"/\\\" character. The last segment of the URL's path must represent\\nthe fully qualified name of the type (as in\\n`path/google.protobuf.Duration`). The name should be in a canonical form\\n(e.g., leading \\\".\\\" is not accepted).\\n\\nIn practice, teams usually precompile into the binary all types that they\\nexpect it to use in the context of Any. However, for URLs which use the\\nscheme `http`, `https`, or no scheme, one can optionally set up a type\\nserver that maps type URLs to message definitions as follows:\\n\\n* If no scheme is provided, `https` is assumed.\\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\\n  value in binary format, or produce an error.\\n* Applications are allowed to cache lookup results based on the\\n  URL, or have them precompiled into a binary to avoid any\\n  lookup. Therefore, binary compatibility needs to be preserved\\n  on changes to types. (Use versioned type names to manage\\n  breaking changes.)\\n\\nNote: this functionality is not currently available in the official\\nprotobuf release, and it is not used for type URLs beginning with\\ntype.googleapis.com. As of May 2023, there are no widely used type server\\nimplementations and no plans to implement one.\\n\\nSchemes other than `http`, `https` (or the empty scheme) might be\\nused with implementation specific semantics.\"\n        }\n      },\n      \"additionalProperties\": {},\n      \"description\": \"`Any` contains an arbitrary serialized protocol buffer message along with a\\nURL that describes the type of the serialized message.\\n\\nProtobuf library provides support to pack/unpack Any values in the form\\nof utility functions or additional generated methods of the Any type.\\n\\nExample 1: Pack and unpack a message in C++.\\n\\n    Foo foo = ...;\\n    Any any;\\n    any.PackFrom(foo);\\n    ...\\n    if (any.UnpackTo(\\u0026foo)) {\\n      ...\\n    }\\n\\nExample 2: Pack and unpack a message in Java.\\n\\n    Foo foo = ...;\\n    Any any = Any.pack(foo);\\n    ...\\n    if (any.is(Foo.class)) {\\n      foo = any.unpack(Foo.class);\\n    }\\n    // or ...\\n    if (any.isSameTypeAs(Foo.getDefaultInstance())) {\\n      foo = any.unpack(Foo.getDefaultInstance());\\n    }\\n\\n Example 3: Pack and unpack a message in Python.\\n\\n    foo = Foo(...)\\n    any = Any()\\n    any.Pack(foo)\\n    ...\\n    if any.Is(Foo.DESCRIPTOR):\\n      any.Unpack(foo)\\n      ...\\n\\n Example 4: Pack and unpack a message in Go\\n\\n     foo := \\u0026pb.Foo{...}\\n     any, err := anypb.New(foo)\\n     if err != nil {\\n       ...\\n     }\\n     ...\\n     foo := \\u0026pb.Foo{}\\n     if err := any.UnmarshalTo(foo); err != nil {\\n       ...\\n     }\\n\\nThe pack methods provided by protobuf library will by default use\\n'type.googleapis.com/full.type.name' as the type URL and the unpack\\nmethods only use the fully qualified type name after the last '/'\\nin the type URL, for example \\\"foo.bar.com/x/y.z\\\" will yield type\\nname \\\"y.z\\\".\\n\\nJSON\\n====\\nThe JSON representation of an `Any` value uses the regular\\nrepresentation of the deserialized, embedded message, with an\\nadditional field `@type` which contains the type URL. Example:\\n\\n    package google.profile;\\n    message Person {\\n      string first_name = 1;\\n      string last_name = 2;\\n    }\\n\\n    {\\n      \\\"@type\\\": \\\"type.googleapis.com/google.profile.Person\\\",\\n      \\\"firstName\\\": \\u003cstring\\u003e,\\n      \\\"lastName\\\": \\u003cstring\\u003e\\n    }\\n\\nIf the embedded message type is well-known and has a custom JSON\\nrepresentation, that representation will be embedded adding a field\\n`value` which holds the custom JSON in addition to the `@type`\\nfield. Example (for message [google.protobuf.Duration][]):\\n\\n    {\\n      \\\"@type\\\": \\\"type.googleapis.com/google.protobuf.Duration\\\",\\n      \\\"value\\\": \\\"1.212s\\\"\\n    }\"\n    },\n    \"rpcStatus\": {\n      \"properties\": {\n        \"error\": {\n          \"properties\": {\n            \"code\": {\n              \"format\": \"int32\",\n              \"type\": \"integer\"\n            },\n            \"details\": {\n              \"items\": {\n                \"$ref\": \"#/definitions/v2ErrorDetail\",\n                \"type\": \"object\"\n              },\n              \"type\": \"array\"\n            },\n            \"message\": {\n              \"type\": \"string\"\n            },\n            \"status\": {\n              \"type\": \"string\"\n            }\n          },\n          \"type\": \"object\"\n        }\n      },\n      \"type\": \"object\"\n    },\n    \"tagv2Tag\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tagId\": {\n          \"type\": \"string\",\n          \"title\": \"the tag id of the tag\"\n        },\n        \"tagKey\": {\n          \"type\": \"string\",\n          \"title\": \"the tag key of the tag\"\n        },\n        \"tagValue\": {\n          \"type\": \"string\",\n          \"title\": \"the tag value of the tag\"\n        }\n      },\n      \"title\": \"Tag basic resource\",\n      \"required\": [\"tagValue\"]\n    },\n    \"taskv2Response\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"Reply\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"description\": \"The response content.\"\n        },\n        \"ReturnCode\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"description\": \"The return code.\"\n        }\n      },\n      \"description\": \"Response represents an action response.\"\n    },\n    \"tiupv2CreateTiups\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"hostId\": {\n          \"type\": \"string\",\n          \"title\": \"host_id\"\n        },\n        \"tiupHome\": {\n          \"type\": \"string\",\n          \"title\": \"The SSHPort of the Host\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"The Credential_Id of the Host\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The locationId of the Host\"\n        },\n        \"tagIds\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The tagIds of the Host\"\n        }\n      },\n      \"title\": \"CreateTiups\"\n    },\n    \"tiupv2UpdateTiups\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tagIds\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The tagIds of the Host\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"The Credential_Id of the Host\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The locationId of the Host\"\n        }\n      },\n      \"title\": \"UpdateTiups\"\n    },\n    \"v2Abstract\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"TaskId\": {\n          \"type\": \"string\",\n          \"description\": \"The task identifier.\"\n        },\n        \"Status\": {\n          \"type\": \"string\",\n          \"description\": \"The task status.\"\n        },\n        \"Message\": {\n          \"type\": \"string\",\n          \"description\": \"The task message.\"\n        },\n        \"CreateTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"description\": \"The task creation time.\",\n          \"readOnly\": true\n        },\n        \"Retry\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"description\": \"The retry count.\"\n        }\n      },\n      \"description\": \"Abstract represents the abstract information of a task.\",\n      \"required\": [\"TaskId\"]\n    },\n    \"v2Action\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"step\": {\n          \"$ref\": \"#/definitions/v2Step\",\n          \"description\": \"The step information.\"\n        },\n        \"TaskId\": {\n          \"type\": \"string\",\n          \"description\": \"The task identifier.\"\n        },\n        \"response\": {\n          \"$ref\": \"#/definitions/taskv2Response\",\n          \"description\": \"The response information.\"\n        },\n        \"indegree\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"description\": \"The indegree count.\"\n        }\n      },\n      \"description\": \"Action represents a task action.\"\n    },\n    \"v2ActivateLicenseRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"fileName\": {\n          \"type\": \"string\",\n          \"title\": \"The File name of the license\"\n        },\n        \"content\": {\n          \"type\": \"string\",\n          \"format\": \"binary\",\n          \"description\": \"The license file to upload to activate the license\",\n          \"title\": \"The content of the license file\"\n        },\n        \"headers\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The header of the license file\"\n        }\n      },\n      \"title\": \"ActivateLicenseRequest is the request message for ActivateLicense\",\n      \"required\": [\"fileName\", \"content\", \"headers\"]\n    },\n    \"v2AlertmanagerSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"webPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"clusterPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"ng_port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"data_dir\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"log_dir\"\n        },\n        \"numaNode\": {\n          \"type\": \"string\",\n          \"title\": \"numa_node\"\n        }\n      },\n      \"title\": \"AlertmanagerSpec represents the AlertManager topology specification in topology.yaml\"\n    },\n    \"v2ApiKey\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"accessKey\": {\n          \"type\": \"string\",\n          \"title\": \"The access_key of apikey\"\n        },\n        \"secretKey\": {\n          \"type\": \"string\",\n          \"title\": \"The secret_key of apikey\"\n        },\n        \"creator\": {\n          \"type\": \"string\",\n          \"title\": \"The creator of apikey\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\"disable\", \"enable\"],\n          \"title\": \"The apikey status\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"The description of apikey\"\n        },\n        \"createTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"The create time of the apikey\",\n          \"readOnly\": true\n        },\n        \"updateTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"The update time of the role\",\n          \"readOnly\": true\n        }\n      },\n      \"title\": \"ApiKey resource\",\n      \"required\": [\"accessKey\"]\n    },\n    \"v2AssociatedClusters\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"the tag id of the tag\"\n        },\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"title\": \"the tag key of the tag\"\n        }\n      },\n      \"title\": \"AssociatedClusters\"\n    },\n    \"v2AuditConfigs\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"enabled\": {\n          \"type\": \"boolean\",\n          \"title\": \"Whether auditing is enabled\"\n        },\n        \"retentionDays\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Log retention period in days\"\n        }\n      },\n      \"title\": \"Audit configuration\"\n    },\n    \"v2AuditLogEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"endsAt\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Creation time\"\n        },\n        \"operatorId\": {\n          \"type\": \"string\",\n          \"title\": \"Operator id\"\n        },\n        \"operatorType\": {\n          \"type\": \"string\",\n          \"title\": \"Operator type (e.g., USER, API_KEY)\"\n        },\n        \"event\": {\n          \"type\": \"string\",\n          \"title\": \"Event (e.g., cluster, host, user, parameter group)\"\n        },\n        \"operation\": {\n          \"type\": \"string\",\n          \"title\": \"Specific operation (e.g., create, delete, start, restart)\"\n        },\n        \"detail\": {\n          \"type\": \"string\",\n          \"title\": \"Details (including full URL and request/response)\"\n        },\n        \"traceId\": {\n          \"type\": \"string\",\n          \"title\": \"Trace ID\"\n        },\n        \"clientIp\": {\n          \"type\": \"string\",\n          \"title\": \"Client IP address\"\n        },\n        \"result\": {\n          \"type\": \"string\",\n          \"title\": \"Operation result (e.g., success, failure)\"\n        }\n      },\n      \"title\": \"Audit log entry\"\n    },\n    \"v2AuditLogs\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"logs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2AuditLogEntry\"\n          },\n          \"title\": \"List of audit log entries\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"Get audit logs response\"\n    },\n    \"v2BRSummary\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"topClustersWithBrSize\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ClusterWithBRSize\"\n          },\n          \"title\": \"List of top BR size clusters\"\n        },\n        \"topClustersWithBrAlert\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ClusterWithBRAlert\"\n          },\n          \"title\": \"List of top BR alert clusters\"\n        },\n        \"topClustersWithoutBrPolicy\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ClusterWithoutBRPolicy\"\n          },\n          \"title\": \"List of top size clusters without BR policy\"\n        }\n      },\n      \"title\": \"GetBRSummaryResp represents the response to get br summary\"\n    },\n    \"v2BRTask\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The br task ID\"\n        },\n        \"type\": {\n          \"$ref\": \"#/definitions/v2TypeEnumData\",\n          \"title\": \"Type of the br task\"\n        },\n        \"triggerType\": {\n          \"$ref\": \"#/definitions/v2TriggerTypeEnumData\",\n          \"title\": \"Trigger type of the br task\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the br task\"\n        },\n        \"status\": {\n          \"$ref\": \"#/definitions/v2StatusEnumData\",\n          \"title\": \"Status of the br task\"\n        },\n        \"restoredTs\": {\n          \"type\": \"string\",\n          \"title\": \"Restored ts\"\n        },\n        \"startTime\": {\n          \"type\": \"string\",\n          \"title\": \"Start time\"\n        },\n        \"endTime\": {\n          \"type\": \"string\",\n          \"title\": \"End time\"\n        },\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster ID\"\n        },\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster name\"\n        },\n        \"destination\": {\n          \"type\": \"string\",\n          \"title\": \"Destination of the br task\"\n        },\n        \"size\": {\n          \"type\": \"string\",\n          \"title\": \"Size of the br task, 10TB/100GB\"\n        },\n        \"sizeByte\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Size of the br task in byte\"\n        },\n        \"errorMessage\": {\n          \"type\": \"string\",\n          \"title\": \"Error message\"\n        },\n        \"policyId\": {\n          \"type\": \"string\",\n          \"title\": \"Policy ID of the br task\"\n        },\n        \"policyName\": {\n          \"type\": \"string\",\n          \"title\": \"Policy name of the br task\"\n        },\n        \"log\": {\n          \"type\": \"string\",\n          \"title\": \"Log\"\n        },\n        \"accessKeyId\": {\n          \"type\": \"string\",\n          \"title\": \"AccessKeyID\"\n        },\n        \"secretAccessKey\": {\n          \"type\": \"string\",\n          \"title\": \"SecretAccessKey\"\n        },\n        \"rateLimit\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"RateLimit\"\n        },\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Concurrency\"\n        },\n        \"logFile\": {\n          \"type\": \"string\",\n          \"title\": \"LogFile\"\n        },\n        \"expireTime\": {\n          \"type\": \"string\",\n          \"title\": \"ExpireTime\"\n        }\n      },\n      \"title\": \"BRTask represents a br task\"\n    },\n    \"v2BackupCycleEnumData\": {\n      \"type\": \"string\",\n      \"enum\": [\"week\", \"month\"],\n      \"default\": \"week\",\n      \"description\": \"- week: \\nWeek\\n - month: \\nMonth\",\n      \"title\": \"Data of CycleEnum\"\n    },\n    \"v2BackupPolicy\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"policyId\": {\n          \"type\": \"string\",\n          \"title\": \"The policy ID\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The policy name\"\n        },\n        \"logBackup\": {\n          \"type\": \"boolean\",\n          \"title\": \"LogBackup means whether to backup log\"\n        },\n        \"destination\": {\n          \"type\": \"string\",\n          \"title\": \"Destination of the backup\"\n        },\n        \"accessKeyId\": {\n          \"type\": \"string\",\n          \"title\": \"AccessKeyID\"\n        },\n        \"secretAccessKey\": {\n          \"type\": \"string\",\n          \"title\": \"SecretAccessKey\"\n        },\n        \"rateLimit\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"RateLimit\"\n        },\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Concurrency\"\n        },\n        \"logFile\": {\n          \"type\": \"string\",\n          \"title\": \"LogFile\"\n        },\n        \"cycle\": {\n          \"$ref\": \"#/definitions/v2CycleEnumData\",\n          \"title\": \"Cycle of backup\"\n        },\n        \"frequency\": {\n          \"type\": \"string\",\n          \"title\": \"Frequency: week:0~6 for Sunday To Saturday, month:1~31 for Date.example:1,2,3,4,5,6,7\"\n        },\n        \"time\": {\n          \"type\": \"string\",\n          \"title\": \"Time\"\n        },\n        \"retention\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Retention\"\n        },\n        \"clusters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Cluster\"\n          },\n          \"title\": \"Clusters\"\n        },\n        \"clusterIds\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"ClusterIDs\"\n        }\n      },\n      \"title\": \"BackupPolicy represents a backup policy\",\n      \"required\": [\n        \"name\",\n        \"logBackup\",\n        \"destination\",\n        \"cycle\",\n        \"frequency\",\n        \"time\",\n        \"retention\"\n      ]\n    },\n    \"v2BasicClusterInfo\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster ID\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster name\"\n        }\n      },\n      \"title\": \"BasicCluster represents a cluster basic info\"\n    },\n    \"v2BatchCreateTagsRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/tagv2Tag\"\n          },\n          \"title\": \"the tags with basic resource\"\n        }\n      },\n      \"title\": \"Batch Create Tags Request\",\n      \"required\": [\"tags\"]\n    },\n    \"v2BatchCreateTagsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/tagv2Tag\"\n          },\n          \"title\": \"the tags\"\n        }\n      },\n      \"title\": \"Create Tags Response\"\n    },\n    \"v2BatchDeleteRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"hostId\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The host_id of the host\"\n        }\n      },\n      \"title\": \"Delete BatchDeleteRequest\",\n      \"required\": [\"hostId\"]\n    },\n    \"v2BatchPauseResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the cluster\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id of the cluster\"\n        }\n      },\n      \"title\": \"BatchPauseResponse\",\n      \"required\": [\"clusterId\", \"taskId\"]\n    },\n    \"v2BatchReloadResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the cluster\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id of the host\"\n        }\n      },\n      \"title\": \"BatchReloadResponse\",\n      \"required\": [\"clusterId\", \"taskId\"]\n    },\n    \"v2BatchRestartResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the cluster\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id of the host\"\n        }\n      },\n      \"title\": \"BatchStartResponse\",\n      \"required\": [\"clusterId\", \"taskId\"]\n    },\n    \"v2BatchResumeResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the cluster\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id of the cluster\"\n        }\n      },\n      \"title\": \"BatchResumeResponse\",\n      \"required\": [\"clusterId\", \"taskId\"]\n    },\n    \"v2BindCMServer\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"the cm server id\"\n        },\n        \"hostIp\": {\n          \"type\": \"string\",\n          \"title\": \"the host ip of the cm server\"\n        }\n      },\n      \"title\": \"BindCMServer\"\n    },\n    \"v2BindObject\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"resourceType\": {\n          \"$ref\": \"#/definitions/v2TagBindResourceType\",\n          \"title\": \"the resource type of the bind object\"\n        },\n        \"resources\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ResourceObject\"\n          },\n          \"title\": \"the resources of the resource type\"\n        }\n      },\n      \"title\": \"Bind object resource\",\n      \"required\": [\"resourceType\", \"resources\"]\n    },\n    \"v2BindResourceRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"resourceType\": {\n          \"$ref\": \"#/definitions/v2TagBindResourceType\",\n          \"title\": \"the resource type of the bind object\"\n        },\n        \"resourceId\": {\n          \"type\": \"string\",\n          \"title\": \"the resource id of the resource type\"\n        },\n        \"tagIds\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"the tag ids to be bound\"\n        }\n      },\n      \"title\": \"BindResource Request\",\n      \"required\": [\"resourceType\", \"resourceId\"]\n    },\n    \"v2BindResourceResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/tagv2Tag\"\n          },\n          \"title\": \"the tags bound with the bind object\"\n        }\n      },\n      \"title\": \"BindResource Response\"\n    },\n    \"v2BindTagRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tagId\": {\n          \"type\": \"string\",\n          \"title\": \"the tag id of the tag\"\n        },\n        \"bindObjects\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2BindObject\"\n          },\n          \"title\": \"the bind objects to be bound\"\n        }\n      },\n      \"title\": \"BindTag Request\",\n      \"required\": [\"tagId\"]\n    },\n    \"v2BindTagResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tag\": {\n          \"$ref\": \"#/definitions/v2TagWithBindObject\",\n          \"title\": \"the tag resource with bound objects\"\n        }\n      },\n      \"title\": \"BindTag Response\"\n    },\n    \"v2CDCSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"port\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"data_dir\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"log_dir\"\n        },\n        \"numaNode\": {\n          \"type\": \"string\",\n          \"title\": \"numa_node\"\n        },\n        \"config\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"config\"\n        }\n      },\n      \"title\": \"CDCSpec represents the CDC topology specification in topology.yaml\"\n    },\n    \"v2CMServer\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"CMServer id\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"name\"\n        },\n        \"createTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"create time\"\n        },\n        \"updateTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"update time\"\n        },\n        \"hostId\": {\n          \"type\": \"string\",\n          \"title\": \"host id\"\n        },\n        \"hostIp\": {\n          \"type\": \"string\",\n          \"title\": \"host ip\"\n        },\n        \"domainId\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"domain id\"\n        },\n        \"domainName\": {\n          \"type\": \"string\",\n          \"title\": \"domain name\"\n        },\n        \"status\": {\n          \"$ref\": \"#/definitions/v2State\",\n          \"title\": \"server state\",\n          \"readOnly\": true\n        },\n        \"serviceStatus\": {\n          \"$ref\": \"#/definitions/v2ServiceState\",\n          \"title\": \"service status\",\n          \"readOnly\": true\n        },\n        \"credentialId\": {\n          \"type\": \"string\",\n          \"title\": \"credential_id\"\n        },\n        \"serviceVersion\": {\n          \"type\": \"string\",\n          \"title\": \"CMServer version\"\n        },\n        \"servicePort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The proxy port of the CMServer Host\"\n        },\n        \"serviceHome\": {\n          \"type\": \"string\",\n          \"title\": \"deploy path\"\n        },\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/tagv2Tag\"\n          },\n          \"title\": \"tags\"\n        }\n      },\n      \"title\": \"CMServer basic resource\",\n      \"required\": [\n        \"createTime\",\n        \"updateTime\",\n        \"credentialId\",\n        \"servicePort\",\n        \"serviceHome\"\n      ]\n    },\n    \"v2CMServerClusters\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"cluster_id\"\n        },\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"title\": \"cluster_name\"\n        },\n        \"user\": {\n          \"type\": \"string\",\n          \"title\": \"user\"\n        },\n        \"version\": {\n          \"type\": \"string\",\n          \"title\": \"version\"\n        },\n        \"metaPath\": {\n          \"type\": \"string\",\n          \"title\": \"patch\"\n        },\n        \"privateKeyPath\": {\n          \"type\": \"string\",\n          \"title\": \"private_key\"\n        },\n        \"managed\": {\n          \"type\": \"boolean\",\n          \"title\": \"managed\"\n        }\n      },\n      \"title\": \"CMServerClusters\"\n    },\n    \"v2CMServerClustersResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2CMServerClusters\"\n          },\n          \"title\": \"list of clusters\"\n        }\n      },\n      \"title\": \"CMServerClustersResponse\"\n    },\n    \"v2CMServerHost\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"hostId\": {\n          \"type\": \"string\",\n          \"title\": \"The host_id of the Host\"\n        },\n        \"ip\": {\n          \"type\": \"string\",\n          \"title\": \"The ip of the Host\"\n        },\n        \"sshPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The ssh_port of the Host\"\n        },\n        \"osName\": {\n          \"type\": \"string\",\n          \"title\": \"os_name\"\n        },\n        \"cpuModel\": {\n          \"type\": \"string\",\n          \"title\": \"cpu_model\"\n        },\n        \"cpus\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"cpus\"\n        },\n        \"cpuCores\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"cpu_cores\"\n        },\n        \"cpuThreads\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"cpu_threads\"\n        },\n        \"cpuArch\": {\n          \"type\": \"string\",\n          \"title\": \"cpu_arch\"\n        },\n        \"memorySize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"memory_size\"\n        },\n        \"memoryUnit\": {\n          \"type\": \"string\",\n          \"title\": \"memory_unit\"\n        },\n        \"storageUnit\": {\n          \"type\": \"string\",\n          \"title\": \"storage_unit\"\n        },\n        \"cpuNumaNodes\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The CpuNumaNodes of the Host\"\n        },\n        \"storageTotalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The Storage of the Host\"\n        },\n        \"diskType\": {\n          \"type\": \"string\",\n          \"title\": \"The DiskType of the Host\"\n        },\n        \"hostType\": {\n          \"type\": \"string\",\n          \"enum\": [\"VM\", \"PM\"],\n          \"title\": \"host Type (e.g., \\\"VM\\\", \\\"PM\\\")\"\n        }\n      },\n      \"title\": \"CMServerHost\",\n      \"required\": [\"hostId\"]\n    },\n    \"v2CMServerWithTiup\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"server\": {\n          \"$ref\": \"#/definitions/v2CMServer\",\n          \"title\": \"CM Server basic resource\"\n        },\n        \"tiup\": {\n          \"$ref\": \"#/definitions/v2Tiup\",\n          \"title\": \"Tiup Resource\"\n        },\n        \"host\": {\n          \"$ref\": \"#/definitions/v2CMServerHost\",\n          \"title\": \"Host Resource\"\n        }\n      },\n      \"title\": \"CMServerWithTiup\"\n    },\n    \"v2CancelTaskFlowResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id\"\n        }\n      },\n      \"title\": \"CancelTaskFlowResponse\",\n      \"required\": [\"taskId\"]\n    },\n    \"v2CategoryMetricDetail\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"class\": {\n          \"type\": \"string\",\n          \"title\": \"Level 1 classification\"\n        },\n        \"group\": {\n          \"type\": \"string\",\n          \"title\": \"Level 2 grouping\"\n        },\n        \"type\": {\n          \"type\": \"string\",\n          \"title\": \"Level 3 type\"\n        },\n        \"order\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Display order of charts\"\n        },\n        \"displayName\": {\n          \"type\": \"string\",\n          \"title\": \"Display name of the metric\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Metric Name\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"Description of the metric\"\n        },\n        \"metric\": {\n          \"$ref\": \"#/definitions/v2MetricWithExpressions\",\n          \"title\": \"Metric with its expressions\"\n        }\n      },\n      \"title\": \"CategoryMetricDetail represents the details of a metric category\"\n    },\n    \"v2ChangePasswordRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"userId\": {\n          \"type\": \"string\",\n          \"title\": \"The id of the user\"\n        },\n        \"oldPassword\": {\n          \"type\": \"string\",\n          \"title\": \"User old password\"\n        },\n        \"newPassword\": {\n          \"type\": \"string\",\n          \"title\": \"User new password\"\n        }\n      },\n      \"title\": \"ChangePasswordRequest Request\",\n      \"required\": [\"userId\", \"newPassword\"]\n    },\n    \"v2Channel\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"ID\",\n          \"readOnly\": true\n        },\n        \"createTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Creation time of the channel\",\n          \"readOnly\": true\n        },\n        \"updateTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Last update time of the channel\",\n          \"readOnly\": true\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the channel\"\n        },\n        \"type\": {\n          \"type\": \"string\",\n          \"enum\": [\"email\", \"webhook\"],\n          \"title\": \"The channel type\"\n        },\n        \"creator\": {\n          \"type\": \"string\",\n          \"title\": \"Creator of the channel\",\n          \"readOnly\": true\n        },\n        \"emailConfig\": {\n          \"$ref\": \"#/definitions/v2EmailConfig\",\n          \"title\": \"Email configuration, required if type is EMAIL\"\n        },\n        \"webhookConfig\": {\n          \"$ref\": \"#/definitions/v2WebhookConfig\",\n          \"title\": \"Webhook configuration, required if type is WEBHOOK\"\n        },\n        \"channelMonitorObjects\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ChannelMonitorObject\"\n          },\n          \"title\": \"Channel Object List\"\n        },\n        \"updateChannelObjects\": {\n          \"type\": \"boolean\",\n          \"title\": \"update channel object\"\n        }\n      },\n      \"title\": \"AlertChannel represents an alert channel configuration\",\n      \"required\": [\"name\", \"type\"]\n    },\n    \"v2ChannelMonitorObject\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"application\": {\n          \"type\": \"string\",\n          \"enum\": [\"Cluster\"],\n          \"title\": \"The monitor object application\"\n        },\n        \"alertObject\": {\n          \"type\": \"string\",\n          \"description\": \"Object, such as cluster id, host id.\"\n        }\n      },\n      \"title\": \"ChannelObject represents an object to monitor in an alert channel\",\n      \"required\": [\"application\", \"alertObject\"]\n    },\n    \"v2CheckSupportResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"isSupport\": {\n          \"type\": \"boolean\",\n          \"title\": \"Is support sql plan binding\"\n        }\n      },\n      \"title\": \"Response of checking whether cluster support sql plan binding\"\n    },\n    \"v2CheckUnsupportedParamsRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"configs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ParamBase\"\n          },\n          \"title\": \"The configs to check\"\n        },\n        \"checkClusterVersion\": {\n          \"type\": \"string\",\n          \"title\": \"The version of the cluster to check\"\n        }\n      },\n      \"title\": \"CheckUnsupportedParamsRequest represents a check unsupported params request\",\n      \"required\": [\"checkClusterVersion\"]\n    },\n    \"v2CheckUnsupportedParamsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"unsupportedParams\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ParamBase\"\n          },\n          \"title\": \"The unsupported params\"\n        }\n      },\n      \"title\": \"CheckUnsupportedParamsResponse represents a check unsupported params response\"\n    },\n    \"v2ClassEnumData\": {\n      \"type\": \"string\",\n      \"enum\": [\"unspecified\", \"cluster\", \"host\", \"overview\"],\n      \"default\": \"unspecified\",\n      \"description\": \"- unspecified: Unspecified\\n - cluster: Cluster metrics\\n - host: Host metrics\\n - overview: Overview metrics\",\n      \"title\": \"Data of ClassEnum\"\n    },\n    \"v2Cluster\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster ID\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster name\"\n        }\n      },\n      \"title\": \"Cluster represents a cluster basic info\"\n    },\n    \"v2ClusterAvailableConfigComponents\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"components\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"Components is the list of components\"\n        }\n      },\n      \"title\": \"GetClusterAvailableConfigComponentsResponse is the response message for GetClusterAvailableConfigComponents\",\n      \"required\": [\"components\"]\n    },\n    \"v2ClusterBRStatusEnumData\": {\n      \"type\": \"string\",\n      \"enum\": [\"running\", \"finished\", \"abnormal\", \"stopped\"],\n      \"default\": \"running\",\n      \"description\": \"- running: Running\\n - finished: Finished\\n - abnormal: Abnormal\\n - stopped: Stopped\",\n      \"title\": \"Data of StatusEnum\"\n    },\n    \"v2ClusterBRTask\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The br task ID\"\n        },\n        \"type\": {\n          \"$ref\": \"#/definitions/v2ClusterBRTypeEnumData\",\n          \"title\": \"Type of the br task\"\n        },\n        \"triggerType\": {\n          \"$ref\": \"#/definitions/v2ClusterBRTriggerTypeEnumData\",\n          \"title\": \"Trigger type of the br task\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the br task\"\n        },\n        \"status\": {\n          \"$ref\": \"#/definitions/v2ClusterBRStatusEnumData\",\n          \"title\": \"Status of the br task\"\n        },\n        \"restoredTs\": {\n          \"type\": \"string\",\n          \"title\": \"Restored ts\"\n        },\n        \"startTime\": {\n          \"type\": \"string\",\n          \"title\": \"Start time\"\n        },\n        \"endTime\": {\n          \"type\": \"string\",\n          \"title\": \"End time\"\n        },\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster ID\"\n        },\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster name\"\n        },\n        \"destination\": {\n          \"type\": \"string\",\n          \"title\": \"Destination of the br task\"\n        },\n        \"size\": {\n          \"type\": \"string\",\n          \"title\": \"Size of the br task, 10TB/100GB\"\n        },\n        \"sizeByte\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Size of the br task in byte\"\n        },\n        \"errorMessage\": {\n          \"type\": \"string\",\n          \"title\": \"Error message\"\n        },\n        \"policyId\": {\n          \"type\": \"string\",\n          \"title\": \"Policy ID of the br task\"\n        },\n        \"policyName\": {\n          \"type\": \"string\",\n          \"title\": \"Policy name of the br task\"\n        },\n        \"log\": {\n          \"type\": \"string\",\n          \"title\": \"Log\"\n        },\n        \"accessKeyId\": {\n          \"type\": \"string\",\n          \"title\": \"AccessKeyID\"\n        },\n        \"secretAccessKey\": {\n          \"type\": \"string\",\n          \"title\": \"SecretAccessKey\"\n        },\n        \"rateLimit\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"RateLimit\"\n        },\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Concurrency\"\n        },\n        \"logFile\": {\n          \"type\": \"string\",\n          \"title\": \"LogFile\"\n        },\n        \"expireTime\": {\n          \"type\": \"string\",\n          \"title\": \"ExpireTime\"\n        }\n      },\n      \"title\": \"ClusterBRTask represents a br task\"\n    },\n    \"v2ClusterBRTriggerTypeEnumData\": {\n      \"type\": \"string\",\n      \"enum\": [\"automatic\", \"manual\"],\n      \"default\": \"automatic\",\n      \"description\": \"- automatic: automatic\\n - manual: manual\",\n      \"title\": \"Data of TriggerTypeEnum\"\n    },\n    \"v2ClusterBRTypeEnumData\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"full_backup\",\n        \"log_backup\",\n        \"restore_by_file\",\n        \"restore_by_time\",\n        \"all_backup\",\n        \"all_restore\"\n      ],\n      \"default\": \"full_backup\",\n      \"description\": \"- full_backup: Full backup\\n - log_backup: Log backup\\n - restore_by_file: Restore by file\\n - restore_by_time: Restore by time\\n - all_backup: All backup\\n - all_restore: All restore\",\n      \"title\": \"Data of TypeEnum\"\n    },\n    \"v2ClusterBackupPolicy\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"policyId\": {\n          \"type\": \"string\",\n          \"title\": \"The policy ID\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The policy name\"\n        },\n        \"logBackup\": {\n          \"type\": \"boolean\",\n          \"title\": \"LogBackup means whether to backup log\"\n        },\n        \"destination\": {\n          \"type\": \"string\",\n          \"title\": \"Destination of the backup\"\n        },\n        \"accessKeyId\": {\n          \"type\": \"string\",\n          \"title\": \"AccessKeyID\"\n        },\n        \"secretAccessKey\": {\n          \"type\": \"string\",\n          \"title\": \"SecretAccessKey\"\n        },\n        \"rateLimit\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"RateLimit\"\n        },\n        \"concurrency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Concurrency\"\n        },\n        \"logFile\": {\n          \"type\": \"string\",\n          \"title\": \"LogFile\"\n        },\n        \"cycle\": {\n          \"$ref\": \"#/definitions/v2BackupCycleEnumData\",\n          \"title\": \"Cycle of backup\"\n        },\n        \"frequency\": {\n          \"type\": \"string\",\n          \"title\": \"Frequency: week:0~6 for Sunday To Saturday, month:1~31 for Date.example:1,2,3,4,5,6,7\"\n        },\n        \"time\": {\n          \"type\": \"string\",\n          \"title\": \"Time\"\n        },\n        \"retention\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Retention\"\n        },\n        \"clusters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2BasicClusterInfo\"\n          },\n          \"title\": \"Clusters\"\n        },\n        \"lastBackupTime\": {\n          \"type\": \"string\",\n          \"title\": \"LastBackupTime\"\n        },\n        \"size\": {\n          \"type\": \"string\",\n          \"title\": \"Size\"\n        },\n        \"sizeByte\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"SizeByte\"\n        },\n        \"lastLogBackupTime\": {\n          \"type\": \"string\",\n          \"title\": \"LastLogBackupTime\"\n        },\n        \"logBackupDelay\": {\n          \"type\": \"string\",\n          \"title\": \"LogBackupDelay\"\n        }\n      },\n      \"title\": \"ClusterBackupPolicy represents a backup policy and backup status\",\n      \"required\": [\n        \"name\",\n        \"logBackup\",\n        \"destination\",\n        \"cycle\",\n        \"frequency\",\n        \"time\",\n        \"retention\"\n      ]\n    },\n    \"v2ClusterConfigRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id\"\n        },\n        \"tidbVersion\": {\n          \"type\": \"string\",\n          \"title\": \"The tidb_version\"\n        },\n        \"tiupVersion\": {\n          \"type\": \"string\",\n          \"title\": \"The tiup_version\"\n        },\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_name\"\n        },\n        \"dbUser\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_name\"\n        },\n        \"dbPassword\": {\n          \"type\": \"string\",\n          \"title\": \"The db_password\"\n        },\n        \"arch\": {\n          \"type\": \"string\",\n          \"title\": \"The arch\"\n        },\n        \"tiupId\": {\n          \"type\": \"string\",\n          \"title\": \"The cm_id\"\n        },\n        \"cmId\": {\n          \"type\": \"string\",\n          \"title\": \"The cm_id\"\n        },\n        \"locations\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The locations\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"data_dir\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"log_dir\"\n        },\n        \"deployUser\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"spec\": {\n          \"$ref\": \"#/definitions/v2Spec\",\n          \"title\": \"The spec\"\n        }\n      },\n      \"title\": \"ClusterConfigRequest\",\n      \"required\": [\n        \"clusterId\",\n        \"tidbVersion\",\n        \"tiupVersion\",\n        \"clusterName\",\n        \"dbUser\",\n        \"dbPassword\",\n        \"arch\",\n        \"tiupId\",\n        \"cmId\",\n        \"locations\",\n        \"spec\"\n      ]\n    },\n    \"v2ClusterConfigResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id\"\n        },\n        \"tiupId\": {\n          \"type\": \"string\",\n          \"title\": \"The cm_id\"\n        },\n        \"cmId\": {\n          \"type\": \"string\",\n          \"title\": \"The cm_id\"\n        },\n        \"spec\": {\n          \"$ref\": \"#/definitions/v2Spec\",\n          \"title\": \"The spec\"\n        }\n      },\n      \"title\": \"ClusterConfigResponse\",\n      \"required\": [\"clusterId\", \"tiupId\", \"cmId\", \"spec\"]\n    },\n    \"v2ClusterInstances\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The tidb_version\"\n        },\n        \"component\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"grafana\",\n            \"tikv\",\n            \"tidb\",\n            \"prometheus\",\n            \"pd\",\n            \"tiflash\",\n            \"pump\",\n            \"drainer\",\n            \"monitor\",\n            \"alertManager\",\n            \"cdc\",\n            \"dashboard\"\n          ],\n          \"title\": \"Cluster Instances type (e.g., \\\"tikv\\\", \\\"tidb\\\")\"\n        },\n        \"instancesId\": {\n          \"type\": \"string\",\n          \"title\": \"The instances_id\"\n        },\n        \"componentValue\": {\n          \"type\": \"string\",\n          \"title\": \"The tidb_version\"\n        },\n        \"ip\": {\n          \"type\": \"string\",\n          \"title\": \"ip\"\n        },\n        \"port\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"hostId\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"Up\",\n            \"Down\",\n            \"Unreachable\",\n            \"Tombstone\",\n            \"GoingOffline\",\n            \"unknow\"\n          ],\n          \"title\": \"Cluster Instances status (e.g., \\\"Up\\\", \\\"Down\\\")\"\n        },\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Tags\"\n          },\n          \"title\": \"The Tag of the Host\"\n        },\n        \"locationMappings\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2LocationMappings\"\n          },\n          \"title\": \"location_mappings\"\n        },\n        \"ports\": {\n          \"type\": \"string\",\n          \"title\": \"ports\"\n        },\n        \"arch\": {\n          \"type\": \"string\",\n          \"title\": \"arch\"\n        },\n        \"version\": {\n          \"type\": \"string\",\n          \"title\": \"version\"\n        },\n        \"os\": {\n          \"type\": \"string\",\n          \"title\": \"os\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"os\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"os\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"os\"\n        },\n        \"numaNode\": {\n          \"type\": \"string\",\n          \"title\": \"os\"\n        },\n        \"numaCores\": {\n          \"type\": \"string\",\n          \"title\": \"os\"\n        },\n        \"runtimeDuration\": {\n          \"type\": \"string\",\n          \"title\": \"The runtime_duration\"\n        }\n      },\n      \"title\": \"ClusterInstances\",\n      \"required\": [\n        \"clusterId\",\n        \"instancesId\",\n        \"componentValue\",\n        \"ip\",\n        \"port\",\n        \"hostId\",\n        \"ports\",\n        \"arch\",\n        \"version\",\n        \"os\",\n        \"dataDir\",\n        \"deployDir\",\n        \"logDir\",\n        \"numaNode\",\n        \"numaCores\",\n        \"runtimeDuration\"\n      ]\n    },\n    \"v2ClusterMetricData\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"status\": {\n          \"type\": \"string\",\n          \"title\": \"Response Status (e.g., \\\"success\\\", \\\"error\\\")\"\n        },\n        \"data\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ExprQueryData\"\n          },\n          \"title\": \"Response Data containing the queried metrics\"\n        }\n      },\n      \"title\": \"ClusterMetricData represents the response for querying cluster metric data\"\n    },\n    \"v2ClusterMetricInstance\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"type\": {\n          \"type\": \"string\",\n          \"title\": \"Target type (e.g., tikv, tidb, host)\"\n        },\n        \"instanceList\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"List of instances for the specified metric\"\n        }\n      },\n      \"title\": \"QueryClusterMetricInstanceResponse represents the response for querying cluster metric instances\"\n    },\n    \"v2ClusterNodeTopology\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The tidb_version\"\n        },\n        \"component\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"grafana\",\n            \"tikv\",\n            \"tidb\",\n            \"prometheus\",\n            \"pd\",\n            \"tiflash\",\n            \"pump\",\n            \"drainer\",\n            \"monitor\",\n            \"alertManager\",\n            \"cdc\",\n            \"dashboard\"\n          ],\n          \"title\": \"Cluster Instances type (e.g., \\\"tikv\\\", \\\"tidb\\\")\"\n        },\n        \"nodes\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"nodes\"\n        },\n        \"nodesDown\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"nodes_down\"\n        },\n        \"versions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"versions\"\n        },\n        \"endpoints\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"endpoints\"\n        }\n      },\n      \"title\": \"Nodes\",\n      \"required\": [\"clusterId\", \"nodes\", \"nodesDown\"]\n    },\n    \"v2ClusterProcess\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"instance\": {\n          \"type\": \"string\",\n          \"title\": \"TiDB instance identifier running this process\"\n        },\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"Unique process ID within the cluster\"\n        },\n        \"user\": {\n          \"type\": \"string\",\n          \"title\": \"user that started the process\"\n        },\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"Client host information\"\n        },\n        \"db\": {\n          \"type\": \"string\",\n          \"title\": \"Database being accessed by the process\"\n        },\n        \"command\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"Sleep\",\n            \"Quit\",\n            \"Init DB\",\n            \"Query\",\n            \"Field List\",\n            \"Create DB\",\n            \"Drop DB\",\n            \"Refresh\",\n            \"Shutdown\",\n            \"Statistics\",\n            \"Processlist\",\n            \"Connect\",\n            \"Kill\",\n            \"Debug\",\n            \"Ping\",\n            \"Time\",\n            \"Delayed Insert\",\n            \"Change User\",\n            \"Binlog Dump\",\n            \"Table Dump\",\n            \"Connect out\",\n            \"Register Slave\",\n            \"Prepare\",\n            \"Execute\",\n            \"Long Data\",\n            \"Close stmt\",\n            \"Reset stmt\",\n            \"Set option\",\n            \"Fetch\",\n            \"Daemon\",\n            \"Reset connect\"\n          ],\n          \"title\": \"Current command being executed (e.g., \\\"Query\\\", \\\"Sleep\\\")\"\n        },\n        \"time\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Time in seconds that the process has been running\"\n        },\n        \"state\": {\n          \"type\": \"string\",\n          \"title\": \"Current state of the process\"\n        },\n        \"info\": {\n          \"type\": \"string\",\n          \"title\": \"SQL statement or other information about the process\"\n        },\n        \"digest\": {\n          \"type\": \"string\",\n          \"title\": \"Query digest for identifying similar queries\"\n        },\n        \"mem\": {\n          \"type\": \"string\",\n          \"title\": \"Memory usage of the process\"\n        },\n        \"disk\": {\n          \"type\": \"string\",\n          \"title\": \"Disk usage of the process\"\n        },\n        \"txnStart\": {\n          \"type\": \"string\",\n          \"title\": \"Transaction start timestamp\"\n        },\n        \"resourceGroup\": {\n          \"type\": \"string\",\n          \"title\": \"Resource group assigned to the process\"\n        },\n        \"sessionAlias\": {\n          \"type\": \"string\",\n          \"title\": \"Session alias\"\n        },\n        \"rowsAffected\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Rows affected\"\n        },\n        \"tidbCpu\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"tidb cpu\"\n        },\n        \"tikvCpu\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"tikv cpu\"\n        }\n      },\n      \"title\": \"ClusterProcess represents a single process in the cluster\"\n    },\n    \"v2ClusterResourceUsage\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tikv\": {\n          \"$ref\": \"#/definitions/v2ComponentStorageUsage\",\n          \"title\": \"TiKV storage resource usage information\"\n        },\n        \"tiflash\": {\n          \"$ref\": \"#/definitions/v2ComponentStorageUsage\",\n          \"title\": \"TiFlash storage resource usage information\"\n        }\n      },\n      \"title\": \"ClusterResourceUsage represents storage resource usage information for TiKV and TiFlash\"\n    },\n    \"v2ClusterStatus\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id\"\n        },\n        \"statusType\": {\n          \"type\": \"string\",\n          \"enum\": [\"parameter_changed\", \"node_down\", \"routing\", \"rule_changed\"],\n          \"title\": \"Cluster run status type (e.g., \\\"parameter_changed\\\", \\\"node_down\\\")\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"title\": \"The status\"\n        },\n        \"statusInfo\": {\n          \"type\": \"string\",\n          \"title\": \"The status\"\n        }\n      },\n      \"title\": \"ClusterStatus\",\n      \"required\": [\"clusterId\", \"status\", \"statusInfo\"]\n    },\n    \"v2ClusterTaskFlowResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterTaskFlow\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/clusterv2ClusterTaskFlow\"\n          },\n          \"title\": \"The cluster_task_flow\"\n        }\n      },\n      \"title\": \"ClusterTaskFlowResponse\",\n      \"required\": [\"clusterTaskFlow\"]\n    },\n    \"v2ClusterTopology\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"id\"\n        },\n        \"role\": {\n          \"type\": \"string\",\n          \"title\": \"role\"\n        },\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"ports\": {\n          \"type\": \"string\",\n          \"title\": \"ports\"\n        },\n        \"osArch\": {\n          \"type\": \"string\",\n          \"title\": \"os_arch\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"title\": \"status\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"data_dir\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        }\n      },\n      \"title\": \"ClusterTopology\",\n      \"required\": [\n        \"id\",\n        \"role\",\n        \"host\",\n        \"ports\",\n        \"osArch\",\n        \"status\",\n        \"dataDir\",\n        \"deployDir\"\n      ]\n    },\n    \"v2ClusterVersionsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tidbVersion\": {\n          \"type\": \"string\",\n          \"title\": \"The tidb_version\"\n        },\n        \"tiupId\": {\n          \"type\": \"string\",\n          \"title\": \"The tiup_id\"\n        },\n        \"versions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The versions\"\n        },\n        \"errMsg\": {\n          \"type\": \"string\",\n          \"title\": \"err_msg\"\n        }\n      },\n      \"title\": \"ClusterVersionsResponse\",\n      \"required\": [\"tidbVersion\", \"tiupId\"]\n    },\n    \"v2ClusterWithBRAlert\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"Cluster ID\"\n        },\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"title\": \"Cluster Name\"\n        },\n        \"alertCount\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Alert count\"\n        }\n      },\n      \"title\": \"ClusterWithBRAlert represents a cluster with BR alert\"\n    },\n    \"v2ClusterWithBRSize\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"Cluster ID\"\n        },\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"title\": \"Cluster Name\"\n        },\n        \"totalSizeByte\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"total size in byte\"\n        },\n        \"totalSize\": {\n          \"type\": \"string\",\n          \"title\": \"Total size with unit, e.g. 10TB, other form of TotalSizeByte\"\n        }\n      },\n      \"title\": \"ClusterWithBRSize represents a cluster with BR size\"\n    },\n    \"v2ClusterWithoutBRPolicy\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"Cluster ID\"\n        },\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"title\": \"Cluster Name\"\n        },\n        \"lastBackupTime\": {\n          \"type\": \"string\",\n          \"title\": \"Last backup time\"\n        },\n        \"sizeByte\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Backup size in byte\"\n        }\n      },\n      \"title\": \"ClusterWithoutBRPolicy represents a cluster without BR policy\"\n    },\n    \"v2ClusterYamlResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id\"\n        },\n        \"yaml\": {\n          \"type\": \"string\",\n          \"title\": \"The yaml\"\n        }\n      },\n      \"title\": \"ClusterYamlResponse\",\n      \"required\": [\"clusterId\", \"yaml\"]\n    },\n    \"v2Clusters\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"Cluster ID uniquely identifies the target cluster\"\n        },\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"title\": \"Cluster Name uniquely identifies the target cluster\"\n        },\n        \"version\": {\n          \"type\": \"string\",\n          \"title\": \"Cluster version uniquely identifies the target cluster\"\n        },\n        \"arch\": {\n          \"type\": \"string\",\n          \"title\": \"Cluster arch uniquely identifies the target cluster\"\n        },\n        \"tiupId\": {\n          \"type\": \"string\",\n          \"title\": \"Cluster tiup_id uniquely identifies the target cluster\"\n        },\n        \"tiupName\": {\n          \"type\": \"string\",\n          \"title\": \"Cluster tiup_name uniquely identifies the target cluster\"\n        },\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/tagv2Tag\"\n          },\n          \"title\": \"The Tag of the Cluster\"\n        },\n        \"nodes\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Cluster nodes uniquely identifies the target cluster\"\n        },\n        \"cpu\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Cluster cpu uniquely identifies the target cluster\"\n        },\n        \"memory\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Cluster memory uniquely identifies the target cluster\"\n        },\n        \"storage\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Cluster storage uniquely identifies the target cluster\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"created\",\n            \"destroyed\",\n            \"stopped\",\n            \"running\",\n            \"offlining\",\n            \"offlined\"\n          ],\n          \"title\": \"Cluster run status (e.g., \\\"created\\\", \\\"running\\\")\"\n        },\n        \"clusterStatus\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ClusterStatus\"\n          },\n          \"title\": \"Cluster status uniquely identifies the target cluster\"\n        },\n        \"pdNodes\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Cluster nodes uniquely identifies the target cluster\"\n        },\n        \"tidbNodes\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Cluster nodes uniquely identifies the target cluster\"\n        },\n        \"tikvNodes\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Cluster nodes uniquely identifies the target cluster\"\n        },\n        \"tiflashNodes\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Cluster nodes uniquely identifies the target cluster\"\n        },\n        \"alertAll\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"alert_all\"\n        },\n        \"alertWarning\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"alert_warning\"\n        },\n        \"alertCritical\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"alert_critical\"\n        },\n        \"alertEmergency\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"alert_emergency\"\n        },\n        \"taskStatus\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"running\",\n            \"creating\",\n            \"scaling\",\n            \"destroying\",\n            \"reloading\",\n            \"taking\",\n            \"stopping\",\n            \"restarting\",\n            \"starting\",\n            \"offlining\",\n            \"offlined\"\n          ],\n          \"title\": \"Cluster run status (e.g., \\\"running\\\", \\\"creating\\\")\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"Cluster task_id uniquely identifies the target cluster\"\n        },\n        \"source\": {\n          \"type\": \"string\",\n          \"enum\": [\"create\", \"takeover\"],\n          \"title\": \"Cluster source (e.g., \\\"create\\\", \\\"takeover\\\")\"\n        },\n        \"creator\": {\n          \"type\": \"string\",\n          \"title\": \"creator\"\n        },\n        \"createTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"create_time\"\n        },\n        \"tiup\": {\n          \"$ref\": \"#/definitions/v2Tiups\",\n          \"title\": \"tiup\"\n        }\n      },\n      \"title\": \"Clusters\",\n      \"required\": [\"clusterId\"]\n    },\n    \"v2ComponentStorageUsage\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"storageFreeGb\": {\n          \"type\": \"number\",\n          \"format\": \"float\",\n          \"title\": \"Free storage space in GB\"\n        },\n        \"storageCapacityGb\": {\n          \"type\": \"number\",\n          \"format\": \"float\",\n          \"title\": \"Total storage capacity in GB\"\n        },\n        \"storageUsedGb\": {\n          \"type\": \"number\",\n          \"format\": \"float\",\n          \"title\": \"Used storage space in GB\"\n        },\n        \"storageDailyGrowthGb\": {\n          \"type\": \"number\",\n          \"format\": \"float\",\n          \"title\": \"Daily storage growth in GB\"\n        }\n      },\n      \"title\": \"ComponentStorageUsage represents storage resource usage for a specific component\"\n    },\n    \"v2Config\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"instanceType\": {\n          \"type\": \"string\",\n          \"title\": \"InstanceType is the instance type of the parameter\"\n        },\n        \"instance\": {\n          \"type\": \"string\",\n          \"title\": \"Instance is the instance of the parameter\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Name is the name of the parameter\"\n        },\n        \"currentValue\": {\n          \"type\": \"string\",\n          \"title\": \"CurrentValue is the current value of the parameter\"\n        },\n        \"settingValue\": {\n          \"type\": \"string\",\n          \"title\": \"SettingValue is the setting value of the parameter\"\n        },\n        \"settingValueValid\": {\n          \"type\": \"boolean\",\n          \"title\": \"SettingValueValid is if the setting value is valid\"\n        },\n        \"type\": {\n          \"$ref\": \"#/definitions/v2ParamTypeEnumData\",\n          \"title\": \"Type is the type of the parameter\"\n        },\n        \"edited\": {\n          \"type\": \"boolean\",\n          \"title\": \"Edited is if the parameter is edited\"\n        },\n        \"defaultValue\": {\n          \"type\": \"string\",\n          \"title\": \"DefaultValue is the default value of the parameter\"\n        },\n        \"dynamic\": {\n          \"type\": \"boolean\",\n          \"title\": \"Dynamic is if the parameter is dynamic\"\n        },\n        \"willBeResetDefault\": {\n          \"type\": \"boolean\",\n          \"title\": \"WillBeResetDefault is if the parameter will be reset to default\"\n        }\n      },\n      \"title\": \"Config is the config of the parameter\",\n      \"required\": [\n        \"instanceType\",\n        \"instance\",\n        \"name\",\n        \"currentValue\",\n        \"settingValue\",\n        \"settingValueValid\",\n        \"type\",\n        \"edited\",\n        \"defaultValue\",\n        \"dynamic\",\n        \"willBeResetDefault\"\n      ]\n    },\n    \"v2ConfigTemplateRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tiupVersion\": {\n          \"type\": \"string\",\n          \"title\": \"The tiup_version\"\n        }\n      },\n      \"title\": \"ConfigTemplateRequest\",\n      \"required\": [\"tiupVersion\"]\n    },\n    \"v2ConfigTemplateResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tiupVersion\": {\n          \"type\": \"string\",\n          \"title\": \"The tiup_version\"\n        },\n        \"configTemplate\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/clusterv2ConfigTemplate\"\n          },\n          \"title\": \"The ConfigTemplate\"\n        }\n      },\n      \"title\": \"ConfigTemplateResponse\",\n      \"required\": [\"tiupVersion\", \"configTemplate\"]\n    },\n    \"v2ConfirmResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"task_id\"\n        }\n      },\n      \"title\": \"ConfirmResponse\",\n      \"required\": [\"taskId\"]\n    },\n    \"v2CreateApiKeyRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"The description of the apiKey\"\n        }\n      },\n      \"title\": \"CreateApiKey Request\",\n      \"required\": [\"description\"]\n    },\n    \"v2CreateDomainBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"the domain name of the domain\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"the domain description of the domain\"\n        }\n      },\n      \"title\": \"CreateDomainBody\",\n      \"required\": [\"name\"]\n    },\n    \"v2CreateHost\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"ips\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"ips\"\n        },\n        \"sshPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The SSHPort of the Host\"\n        },\n        \"credentialId\": {\n          \"type\": \"string\",\n          \"title\": \"The credential_id of the Host\"\n        },\n        \"locationId\": {\n          \"type\": \"string\",\n          \"title\": \"The locationId of the Host\"\n        },\n        \"tagIds\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The tagIds of the Host\"\n        },\n        \"comment\": {\n          \"type\": \"string\",\n          \"title\": \"The comment of the Host\"\n        },\n        \"domainId\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The domain id of the host\"\n        }\n      },\n      \"title\": \"CreateHost\"\n    },\n    \"v2CreateParameterTemplateRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"parameterTemplate\": {\n          \"$ref\": \"#/definitions/v2ParameterTemplate\",\n          \"title\": \"The parameter template\"\n        },\n        \"templateParameterMappings\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TemplateParameterMapping\"\n          },\n          \"title\": \"The mappings of the parameter template\"\n        }\n      },\n      \"title\": \"CreateParameterTemplate creates a new parameter template\",\n      \"required\": [\"parameterTemplate\", \"templateParameterMappings\"]\n    },\n    \"v2Credential\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"credentialId\": {\n          \"type\": \"string\",\n          \"title\": \"the credential id of the credential\"\n        },\n        \"userName\": {\n          \"type\": \"string\",\n          \"title\": \"the user name of the credential\"\n        },\n        \"credentialType\": {\n          \"$ref\": \"#/definitions/v2CredentialType\",\n          \"title\": \"the credential type of the credential\"\n        },\n        \"validateType\": {\n          \"$ref\": \"#/definitions/v2CredentialValidateType\",\n          \"title\": \"the validate type of the credential\"\n        },\n        \"credentialName\": {\n          \"type\": \"string\",\n          \"title\": \"the credential name of the credential\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"the description of the credential\"\n        },\n        \"hostCredential\": {\n          \"$ref\": \"#/definitions/v2HostCredentialObject\",\n          \"title\": \"the host credential object\"\n        },\n        \"tidbCredential\": {\n          \"$ref\": \"#/definitions/v2TiDBCredentialObject\",\n          \"title\": \"the tidb cluster credential object\"\n        }\n      },\n      \"title\": \"Credential basic resource\",\n      \"required\": [\"userName\", \"credentialType\", \"validateType\"]\n    },\n    \"v2CredentialType\": {\n      \"type\": \"string\",\n      \"enum\": [\"CREDENTIAL_TYPE_UNSPECIFIED\", \"HOST\", \"TIDB\"],\n      \"default\": \"CREDENTIAL_TYPE_UNSPECIFIED\",\n      \"description\": \"- CREDENTIAL_TYPE_UNSPECIFIED: resource type unspecified\\n - HOST: credential type host\\n - TIDB: credential type tidb\",\n      \"title\": \"define credential type\"\n    },\n    \"v2CredentialValidateType\": {\n      \"type\": \"string\",\n      \"enum\": [\"CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED\", \"PASSWORD\", \"RSAKEY\"],\n      \"default\": \"CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED\",\n      \"description\": \"- CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED: validate type unspecified\\n - PASSWORD: validate by password\\n - RSAKEY: validate by rsa key\",\n      \"title\": \"define validate type of credential\"\n    },\n    \"v2CycleEnumData\": {\n      \"type\": \"string\",\n      \"enum\": [\"week\", \"month\"],\n      \"default\": \"week\",\n      \"description\": \"- week: \\nWeek\\n - month: \\nMonth\",\n      \"title\": \"Data of CycleEnum\"\n    },\n    \"v2DashboardSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"port\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"data_dir\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"log_dir\"\n        },\n        \"numaNode\": {\n          \"type\": \"string\",\n          \"title\": \"numa_node\"\n        },\n        \"config\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"config\"\n        }\n      },\n      \"title\": \"DashboardSpec represents the Dashboard topology specification in topology.yam\"\n    },\n    \"v2DeployClusterResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the cluster\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id of the host\"\n        }\n      },\n      \"title\": \"DeployResponse represents a request to get process list\",\n      \"required\": [\"clusterId\", \"taskId\"]\n    },\n    \"v2DestroyClusterResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the cluster\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id of the host\"\n        }\n      },\n      \"title\": \"DestroyClusterResponse represents a request to get process list\",\n      \"required\": [\"clusterId\", \"taskId\"]\n    },\n    \"v2DetectClusterResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"exist\": {\n          \"type\": \"boolean\",\n          \"title\": \"Whether the cluster exist\"\n        }\n      },\n      \"title\": \"DetectClusterResponse represents the response to detect the if the cluster exist\"\n    },\n    \"v2DeviceCode\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"deviceCode\": {\n          \"type\": \"string\",\n          \"title\": \"Device code, separated by comma\"\n        }\n      },\n      \"title\": \"DeviceCode\"\n    },\n    \"v2Disk\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"path\": {\n          \"type\": \"string\",\n          \"title\": \"host resource\\nThe Name of the Host Disk\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The Size of the Host Disk\"\n        },\n        \"usedSpace\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The Used of the Host Disk\"\n        },\n        \"availableSpace\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The Avail of the Host Disk\"\n        },\n        \"mountingDir\": {\n          \"type\": \"string\",\n          \"title\": \"The Mounted of the Host Disk\"\n        },\n        \"diskType\": {\n          \"type\": \"string\",\n          \"enum\": [\"HDD\", \"SSD\"],\n          \"title\": \"disk type (e.g., \\\"HDD\\\", \\\"SSD\\\")\"\n        }\n      },\n      \"title\": \"Disk\"\n    },\n    \"v2Domain\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"the domain id of the domain\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"the domain name of the domain\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"the domain description of the domain\"\n        },\n        \"createTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"create time\"\n        },\n        \"updateTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"update time\"\n        }\n      },\n      \"title\": \"Domain\",\n      \"required\": [\"name\", \"createTime\", \"updateTime\"]\n    },\n    \"v2DomainWithCMServer\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"domain\": {\n          \"$ref\": \"#/definitions/v2Domain\",\n          \"title\": \"domain basic resource\"\n        },\n        \"servers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2BindCMServer\"\n          },\n          \"title\": \"cm servers in the domain\"\n        }\n      },\n      \"title\": \"DomainWithCMServer\"\n    },\n    \"v2DownloadAuditLogsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"data\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"data\"\n        }\n      },\n      \"title\": \"Response for audit log download\"\n    },\n    \"v2DownloadHostTemplateResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"data\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"data\"\n        }\n      },\n      \"title\": \"DownloadHostTemplateResponse\"\n    },\n    \"v2DownloadListClustersResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"data\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"data\"\n        }\n      },\n      \"title\": \"DownloadListClustersResponse\"\n    },\n    \"v2DownloadListHostResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"data\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"data\"\n        }\n      },\n      \"title\": \"DownloadListHostResponse\"\n    },\n    \"v2DownloadRSAKeyResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"data\": {\n          \"type\": \"string\",\n          \"format\": \"byte\",\n          \"title\": \"the data of file\"\n        }\n      },\n      \"title\": \"DownLoadRSAKey Response\"\n    },\n    \"v2DrainerSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"port\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"data_dir\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"log_dir\"\n        },\n        \"numaNode\": {\n          \"type\": \"string\",\n          \"title\": \"numa_node\"\n        },\n        \"config\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"config\"\n        }\n      },\n      \"title\": \"DrainerSpec represents the Dashboard topology specification in topology.yam\"\n    },\n    \"v2EmailConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"smtpAddress\": {\n          \"type\": \"string\",\n          \"title\": \"SMTP server address\"\n        },\n        \"smtpUsername\": {\n          \"type\": \"string\",\n          \"title\": \"SMTP username\"\n        },\n        \"smtpPassword\": {\n          \"type\": \"string\",\n          \"title\": \"SMTP password\"\n        },\n        \"emailFrom\": {\n          \"type\": \"string\",\n          \"title\": \"Email sender address\"\n        },\n        \"emailTo\": {\n          \"type\": \"string\",\n          \"title\": \"Email recipient address\"\n        },\n        \"sender\": {\n          \"type\": \"string\",\n          \"title\": \"Sender\"\n        },\n        \"emailSubject\": {\n          \"type\": \"string\",\n          \"title\": \"Email subject template\"\n        },\n        \"emailTemplate\": {\n          \"type\": \"string\",\n          \"title\": \"Email content template\"\n        }\n      },\n      \"title\": \"EmailConfig represents email channel configuration\",\n      \"required\": [\n        \"smtpAddress\",\n        \"smtpUsername\",\n        \"smtpPassword\",\n        \"emailFrom\",\n        \"emailTo\",\n        \"sender\",\n        \"emailSubject\",\n        \"emailTemplate\"\n      ]\n    },\n    \"v2ErrorDetail\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"type\": {\n          \"type\": \"string\",\n          \"title\": \"The  error detail type\"\n        },\n        \"locale\": {\n          \"type\": \"string\",\n          \"title\": \"the languages used in i18n\"\n        },\n        \"message\": {\n          \"type\": \"string\",\n          \"title\": \"The  i18n message of the error\"\n        }\n      },\n      \"title\": \"ErrorDetail\"\n    },\n    \"v2Event\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"id\"\n        },\n        \"alertName\": {\n          \"type\": \"string\",\n          \"title\": \"Name of the alert\"\n        },\n        \"level\": {\n          \"type\": \"string\",\n          \"title\": \"Severity level of the alert\"\n        },\n        \"instance\": {\n          \"type\": \"string\",\n          \"title\": \"Instance where the alert occurred\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"title\": \"Current status of the alert\"\n        },\n        \"summary\": {\n          \"type\": \"string\",\n          \"title\": \"Brief summary of the alert\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"Detailed description of the alert\"\n        },\n        \"startTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Time when the alert started\"\n        },\n        \"resolvedTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Time when the alert was resolved\"\n        },\n        \"updateTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Time when the alert was last updated\"\n        },\n        \"silenceStartTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Silence start time\"\n        },\n        \"silenceEndTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Silence end time\"\n        },\n        \"expr\": {\n          \"type\": \"string\",\n          \"title\": \"promql of the alert\"\n        },\n        \"value\": {\n          \"type\": \"string\",\n          \"title\": \"Value of the alert\"\n        },\n        \"operator\": {\n          \"type\": \"string\",\n          \"title\": \"Operator of the alert\"\n        },\n        \"application\": {\n          \"type\": \"string\",\n          \"title\": \"Application\"\n        },\n        \"objectType\": {\n          \"type\": \"string\",\n          \"title\": \"Object Type\"\n        },\n        \"alertObject\": {\n          \"type\": \"string\",\n          \"title\": \"Alert Object\"\n        }\n      },\n      \"title\": \"AlertEvent represents an alert event\"\n    },\n    \"v2EventsOverview\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"levelStats\": {\n          \"$ref\": \"#/definitions/v2LevelStatistics\",\n          \"title\": \"Statistics by alert level\"\n        },\n        \"statusStats\": {\n          \"$ref\": \"#/definitions/v2StatusStatistics\",\n          \"title\": \"Statistics by alert status\"\n        }\n      },\n      \"title\": \"GetAlertEventsOverviewResponse represents the response containing alert events overview\"\n    },\n    \"v2ExprQueryData\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"expr\": {\n          \"type\": \"string\",\n          \"title\": \"The expression used in the query\"\n        },\n        \"legend\": {\n          \"type\": \"string\",\n          \"title\": \"The legend associated with the expression\"\n        },\n        \"prometheusAddress\": {\n          \"type\": \"string\",\n          \"title\": \"prometheus address\"\n        },\n        \"result\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2QueryResult\"\n          },\n          \"title\": \"The results of the query\"\n        }\n      },\n      \"title\": \"ExprQueryData represents the data for an expression query\"\n    },\n    \"v2ExpressionWithLegend\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Expression name\"\n        },\n        \"promql\": {\n          \"type\": \"string\",\n          \"title\": \"PromQL expression\"\n        },\n        \"promMetric\": {\n          \"type\": \"string\",\n          \"title\": \"Prometheus metric name\"\n        },\n        \"labels\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"List of labels associated with the expression\"\n        },\n        \"type\": {\n          \"type\": \"string\",\n          \"title\": \"Type of the expression\"\n        },\n        \"legend\": {\n          \"type\": \"string\",\n          \"title\": \"Legend name for the expression\"\n        },\n        \"minTidbVersion\": {\n          \"type\": \"string\",\n          \"title\": \"Minimum supported TiDB version\"\n        },\n        \"maxTidbVersion\": {\n          \"type\": \"string\",\n          \"title\": \"Maximum supported TiDB version\"\n        }\n      },\n      \"title\": \"ExpressionWithLegend represents an expression with its legend\"\n    },\n    \"v2GenerateRSAKeyRequest\": {\n      \"type\": \"object\",\n      \"title\": \"GenerateRSAKey Request\"\n    },\n    \"v2GenerateRSAKeyResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"publicKey\": {\n          \"type\": \"string\",\n          \"title\": \"the public key of the rsa key\"\n        },\n        \"privateKey\": {\n          \"type\": \"string\",\n          \"title\": \"the private key of the rsa key\"\n        }\n      },\n      \"title\": \"GenerateRSAKey response\"\n    },\n    \"v2GetCMServerWithTiupResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"cmServer\": {\n          \"$ref\": \"#/definitions/v2CMServerWithTiup\",\n          \"title\": \"CM server with tiup\"\n        }\n      },\n      \"title\": \"GetCMServerWithTiupResponse\"\n    },\n    \"v2GetClusterTopologyResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterTopology\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ClusterTopology\"\n          },\n          \"title\": \"the cluster_topology\"\n        },\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"title\": \"cluster_name\"\n        }\n      },\n      \"title\": \"GetClusterTopologyResponse\",\n      \"required\": [\"clusterTopology\", \"clusterName\"]\n    },\n    \"v2GetParameterTemplateResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"parameterTemplate\": {\n          \"$ref\": \"#/definitions/v2ParameterTemplate\",\n          \"title\": \"The parameter template\"\n        },\n        \"templateParameterMappings\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TemplateParameterMapping\"\n          },\n          \"title\": \"The mappings of the parameter template\"\n        }\n      },\n      \"title\": \"GetParameterTemplateResponse represents a get parameter template response\",\n      \"required\": [\"parameterTemplate\", \"templateParameterMappings\"]\n    },\n    \"v2GetTagWithBindingsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tag\": {\n          \"$ref\": \"#/definitions/v2TagWithBindObject\",\n          \"title\": \"tag info\"\n        }\n      },\n      \"title\": \"GetTagWithBindings Response\"\n    },\n    \"v2GetTidbVersionsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tidbVersions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"the cluster_topology\"\n        },\n        \"tiupId\": {\n          \"type\": \"string\",\n          \"title\": \"tiup_id\"\n        },\n        \"ip\": {\n          \"type\": \"string\",\n          \"title\": \"tiup_id\"\n        },\n        \"tiupHome\": {\n          \"type\": \"string\",\n          \"title\": \"tiup_home\"\n        }\n      },\n      \"title\": \"GetTidbVersionsResponse\",\n      \"required\": [\"tiupId\"]\n    },\n    \"v2GlobalOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"user\": {\n          \"type\": \"string\",\n          \"title\": \"user\"\n        },\n        \"group\": {\n          \"type\": \"string\",\n          \"title\": \"group\"\n        },\n        \"sshPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"ssh_port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"data_dir\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"log_dir\"\n        },\n        \"os\": {\n          \"type\": \"string\",\n          \"title\": \"os\"\n        },\n        \"arch\": {\n          \"type\": \"string\",\n          \"title\": \"arch\"\n        }\n      },\n      \"title\": \"GlobalOptions\"\n    },\n    \"v2GrafanaSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"port\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"config\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"config\"\n        }\n      },\n      \"title\": \"GrafanaSpec\"\n    },\n    \"v2GroupEnumData\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"unspecified\",\n        \"overview\",\n        \"basic\",\n        \"advanced\",\n        \"resource\",\n        \"performance\",\n        \"process\"\n      ],\n      \"default\": \"unspecified\",\n      \"description\": \"- unspecified: Unspecified group\\n - overview: Overview group\\n - basic: Basic group\\n - advanced: Advanced group\\n - resource: Resource group\\n - performance: Performance group\\n - process: Process group\",\n      \"title\": \"Data of GroupEnum\"\n    },\n    \"v2Host\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"hostId\": {\n          \"type\": \"string\",\n          \"title\": \"The host_id of the Host\"\n        },\n        \"ip\": {\n          \"type\": \"string\",\n          \"title\": \"The ip of the Host\"\n        },\n        \"hostName\": {\n          \"type\": \"string\",\n          \"title\": \"The host_name of the Host\"\n        },\n        \"sshPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The ssh_port of the Host\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\"initializing\", \"deleting\", \"deleted\", \"used\", \"idle\"],\n          \"title\": \"host Status (e.g., \\\"initializing\\\", \\\"used\\\")\"\n        },\n        \"connectionStatus\": {\n          \"type\": \"string\",\n          \"enum\": [\"online\", \"offline\"],\n          \"title\": \"host connection Status (e.g., \\\"online\\\", \\\"offline\\\")\"\n        },\n        \"checkStatus\": {\n          \"type\": \"string\",\n          \"enum\": [\"checking\", \"failed\", \"warning\", \"succeeded\"],\n          \"title\": \"host check (e.g., \\\"checking\\\", \\\"failed\\\")\"\n        },\n        \"credentialId\": {\n          \"type\": \"string\",\n          \"title\": \"The credential_id of the Host\"\n        },\n        \"reportId\": {\n          \"type\": \"string\",\n          \"title\": \"The report_id of the Host\"\n        },\n        \"osName\": {\n          \"type\": \"string\",\n          \"title\": \"os_name\"\n        },\n        \"osVendor\": {\n          \"type\": \"string\",\n          \"title\": \"os_vendor\"\n        },\n        \"osVersion\": {\n          \"type\": \"string\",\n          \"title\": \"os_version\"\n        },\n        \"osRelease\": {\n          \"type\": \"string\",\n          \"title\": \"os_release\"\n        },\n        \"osArchitecture\": {\n          \"type\": \"string\",\n          \"title\": \"os_architecture\"\n        },\n        \"cpuVendor\": {\n          \"type\": \"string\",\n          \"title\": \"cpu_vendor\"\n        },\n        \"cpuModel\": {\n          \"type\": \"string\",\n          \"title\": \"cpu_model\"\n        },\n        \"cpuSpeed\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"cpu_speed\"\n        },\n        \"cpuCache\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"cpu_cache\"\n        },\n        \"cpus\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"cpus\"\n        },\n        \"cpuThreads\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"cpu_threads\"\n        },\n        \"cpuGovernor\": {\n          \"type\": \"string\",\n          \"title\": \"cpu_governor\"\n        },\n        \"cpuArch\": {\n          \"type\": \"string\",\n          \"title\": \"cpu_arch\"\n        },\n        \"memoryType\": {\n          \"type\": \"string\",\n          \"title\": \"memory_type\"\n        },\n        \"memorySpeed\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"memory_speed\"\n        },\n        \"memorySize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"memory_size\"\n        },\n        \"memorySwap\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"memory_swap\"\n        },\n        \"cpuNumaNodes\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The CpuNumaNodes of the Host\"\n        },\n        \"storageTotalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The Storage of the Host\"\n        },\n        \"storageAvailable\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"storage_available\"\n        },\n        \"storageUsed\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"storage_used\"\n        },\n        \"diskType\": {\n          \"type\": \"string\",\n          \"title\": \"The DiskType of the Host\"\n        },\n        \"clusters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2AssociatedClusters\"\n          },\n          \"title\": \"The Clusters of the Host\"\n        },\n        \"nodeExporterPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Other info\\nThe node_exporter_port of the Host\"\n        },\n        \"tiupIds\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The tiup_ids of the Host\"\n        },\n        \"hostType\": {\n          \"type\": \"string\",\n          \"enum\": [\"VM\", \"PM\"],\n          \"title\": \"host Type (e.g., \\\"VM\\\", \\\"PM\\\")\"\n        },\n        \"comment\": {\n          \"type\": \"string\",\n          \"title\": \"The HostComment_Id of the Host\"\n        },\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Tags\"\n          },\n          \"title\": \"The Tag of the Host\"\n        },\n        \"createdTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"created_time\"\n        },\n        \"updatedTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"updated_time\"\n        },\n        \"credential\": {\n          \"$ref\": \"#/definitions/v2Credential\",\n          \"title\": \"credential\"\n        },\n        \"locationMappings\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2LocationMappings\"\n          },\n          \"title\": \"The Status of the Host\\nlocation_mappings\"\n        },\n        \"memoryUnit\": {\n          \"type\": \"string\",\n          \"title\": \"memory_unit\"\n        },\n        \"storageUnit\": {\n          \"type\": \"string\",\n          \"title\": \"storage_unit\"\n        },\n        \"locationId\": {\n          \"type\": \"string\",\n          \"title\": \"location_id\"\n        },\n        \"cpuCores\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"cpu_cores\"\n        },\n        \"domainId\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"domain id\"\n        },\n        \"domainName\": {\n          \"type\": \"string\",\n          \"title\": \"domain name\"\n        }\n      },\n      \"title\": \"Hosts resource\",\n      \"required\": [\"hostId\"]\n    },\n    \"v2HostCheckResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"hostId\": {\n          \"type\": \"string\",\n          \"title\": \"task_id\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"task_id\"\n        },\n        \"reportId\": {\n          \"type\": \"string\",\n          \"title\": \"importId\"\n        }\n      },\n      \"title\": \"HostCheckResponse\",\n      \"required\": [\"hostId\", \"taskId\", \"reportId\"]\n    },\n    \"v2HostCreateResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"task_id\"\n        }\n      },\n      \"title\": \"HostCreate Response\"\n    },\n    \"v2HostCredentialObject\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"password\": {\n          \"type\": \"string\",\n          \"title\": \"the password of the user\"\n        },\n        \"publicKey\": {\n          \"type\": \"string\",\n          \"title\": \"the public key of the user\"\n        },\n        \"privateKey\": {\n          \"type\": \"string\",\n          \"title\": \"the private key of the user\"\n        },\n        \"hostIps\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"the list of host ips bound with current credential\"\n        }\n      },\n      \"title\": \"Host credential object\"\n    },\n    \"v2HostDiskResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"disk\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Disk\"\n          },\n          \"title\": \"Disk\"\n        }\n      },\n      \"title\": \"Disk Response\"\n    },\n    \"v2HostFixResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"hostId\": {\n          \"type\": \"string\",\n          \"title\": \"task_id\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"task_id\"\n        },\n        \"reportId\": {\n          \"type\": \"string\",\n          \"title\": \"importId\"\n        }\n      },\n      \"title\": \"HostFixResponse\",\n      \"required\": [\"hostId\", \"taskId\", \"reportId\"]\n    },\n    \"v2HostMetricData\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"status\": {\n          \"type\": \"string\",\n          \"title\": \"Response Status (e.g., \\\"success\\\", \\\"error\\\")\"\n        },\n        \"data\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ExprQueryData\"\n          },\n          \"title\": \"Response Data containing the queried metrics\"\n        }\n      },\n      \"title\": \"HostMetricData represents the response for querying cluster metric data\"\n    },\n    \"v2HostServiceUpdateHostBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"$ref\": \"#/definitions/hostv2UpdateHost\",\n          \"title\": \"host resource\"\n        }\n      },\n      \"title\": \"Create Request\"\n    },\n    \"v2HostTask\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"importId\"\n        },\n        \"hostId\": {\n          \"type\": \"string\",\n          \"title\": \"host_id\"\n        },\n        \"reportId\": {\n          \"type\": \"string\",\n          \"title\": \"report_id\"\n        },\n        \"ip\": {\n          \"type\": \"string\",\n          \"title\": \"ip\"\n        },\n        \"userName\": {\n          \"type\": \"string\",\n          \"title\": \"user_name\"\n        },\n        \"sshPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"ssh_port\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\"init\", \"existed\", \"succeeded\", \"failed\"],\n          \"title\": \"create task state (e.g., \\\"init\\\", \\\"existed\\\")\"\n        },\n        \"tags\": {\n          \"type\": \"string\",\n          \"title\": \"tags\"\n        },\n        \"locationId\": {\n          \"type\": \"string\",\n          \"title\": \"location_id\"\n        },\n        \"credentialId\": {\n          \"type\": \"string\",\n          \"title\": \"credential_id\"\n        },\n        \"hostName\": {\n          \"type\": \"string\",\n          \"title\": \"host_name\"\n        },\n        \"tagsList\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Tags\"\n          },\n          \"title\": \"tags\"\n        },\n        \"locationMappings\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2LocationMappings\"\n          },\n          \"title\": \"location_mappings\"\n        },\n        \"credential\": {\n          \"$ref\": \"#/definitions/v2Credential\",\n          \"title\": \"Credential\"\n        }\n      },\n      \"title\": \"HostTask\",\n      \"required\": [\"taskId\", \"hostId\", \"ip\", \"sshPort\"]\n    },\n    \"v2HostTiDBProcessesResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tiDBProcesses\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TiDBProcesses\"\n          },\n          \"title\": \"tiDBProcesses\"\n        }\n      },\n      \"title\": \"HostTiDBProcessesResponse Response\"\n    },\n    \"v2ImportRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"hostData\": {\n          \"type\": \"string\",\n          \"format\": \"binary\",\n          \"description\": \"Upload a csv form data to host.\",\n          \"title\": \"host_data\"\n        },\n        \"fileName\": {\n          \"type\": \"string\",\n          \"title\": \"file_name\"\n        },\n        \"credentialId\": {\n          \"type\": \"string\",\n          \"title\": \"The credential_id of the Import\"\n        },\n        \"domainId\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"domain id\"\n        },\n        \"headers\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The header of the license file\"\n        }\n      },\n      \"title\": \"Import Request\",\n      \"required\": [\"hostData\", \"headers\"]\n    },\n    \"v2ImportTaskResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"task\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2HostTask\"\n          },\n          \"title\": \"List of users\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"importId\"\n        }\n      },\n      \"title\": \"ImportTaskResponse\",\n      \"required\": [\"task\", \"taskId\"]\n    },\n    \"v2LevelStatistics\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"emergencyCount\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Number of emergency alerts\"\n        },\n        \"criticalCount\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Number of critical alerts\"\n        },\n        \"warningCount\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Number of warning alerts\"\n        },\n        \"totalCount\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total number of alerts\"\n        }\n      },\n      \"title\": \"LevelStatistics represents statistics by alert level\"\n    },\n    \"v2LicenseStatusEnumData\": {\n      \"type\": \"string\",\n      \"enum\": [\"active\", \"expired\", \"expiring\", \"invalid\", \"revoked\"],\n      \"default\": \"active\",\n      \"description\": \"- active: active\\n - expired: inactive\\n - expiring: expired\\n - invalid: invalid\\n - revoked: revoked\",\n      \"title\": \"Data of LicenseStatusEnum\"\n    },\n    \"v2LicenseTypeEnumData\": {\n      \"type\": \"string\",\n      \"enum\": [\"free\", \"ultimate\"],\n      \"default\": \"free\",\n      \"description\": \"- free: free\\n - ultimate: ultimate\",\n      \"title\": \"Data of TriggerTypeEnum\"\n    },\n    \"v2ListApiKeysResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"apikeys\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ApiKey\"\n          },\n          \"title\": \"List of users\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"ListApiKeyRequest\"\n    },\n    \"v2ListBRTasksResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"brTasks\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2BRTask\"\n          },\n          \"title\": \"List of br tasks\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"ListBRTasksResponse represents the response to list br tasks\"\n    },\n    \"v2ListBackupPoliciesResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"backupPolicies\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2BackupPolicy\"\n          },\n          \"title\": \"List of br policies\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"ListBRPoliciesResponse represents the response to get br policies\"\n    },\n    \"v2ListCMServersResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"servers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2CMServer\"\n          },\n          \"title\": \"list of tiups\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"total size\"\n        }\n      },\n      \"title\": \"ListCMServers Response\"\n    },\n    \"v2ListChannelsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"channels\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Channel\"\n          },\n          \"title\": \"List of alert channels\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"description\": \"Token for the next page of results.\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"description\": \"The total number of users that match the filter criteria.\"\n        }\n      },\n      \"title\": \"GetAlertChannelsResponse represents a response containing alert channels\"\n    },\n    \"v2ListClusterBRTasksResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"brTasks\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ClusterBRTask\"\n          },\n          \"title\": \"List of br tasks\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"ListClusterBRTasksResponse represents the response to list br tasks for a specific cluster\"\n    },\n    \"v2ListClusterBackupRecordsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"backupRecords\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ClusterBRTask\"\n          },\n          \"title\": \"List of valid full backup records\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"ListClusterBackupRecordsResponse represents the response to list valid full backup records for a specific cluster\"\n    },\n    \"v2ListClusterConfigsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"configs\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Config\"\n          },\n          \"title\": \"Configs is the list of configs\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"ListClusterConfigsResponse is the response message for ListClusterConfigs\",\n      \"required\": [\"configs\"]\n    },\n    \"v2ListClusterVariablesResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"variables\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Variable\"\n          },\n          \"title\": \"Variables is the list of variables\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"ListClusterVariablesResponse is the response message for ListClusterVariables\",\n      \"required\": [\"variables\"]\n    },\n    \"v2ListClustersResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Clusters\"\n          },\n          \"title\": \"clusters uniquely identifies the target cluster\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"ListClustersRequest represents a request to get process list\",\n      \"required\": [\"clusters\", \"nextPageToken\", \"totalSize\"]\n    },\n    \"v2ListCredentialsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"credentials\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Credential\"\n          },\n          \"title\": \"list of credentials\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"total size\"\n        }\n      },\n      \"title\": \"ListCredentials Response\"\n    },\n    \"v2ListDomainsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"domains\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Domain\"\n          },\n          \"title\": \"list of domains\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"total size\"\n        }\n      },\n      \"title\": \"ListDomains Response\"\n    },\n    \"v2ListDomainsWithCMServerResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"domains\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2DomainWithCMServer\"\n          },\n          \"title\": \"list of domains\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"total size\"\n        }\n      },\n      \"title\": \"ListDomainsWithCMServer Response\"\n    },\n    \"v2ListEventsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"events\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Event\"\n          },\n          \"title\": \"List of alert events\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"description\": \"Token for the next page of results.\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"description\": \"The total number of users that match the filter criteria.\"\n        }\n      },\n      \"title\": \"GetAlertEventsResponse represents a response containing alert events\"\n    },\n    \"v2ListHostsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"hosts\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Host\"\n          },\n          \"title\": \"List of users\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"ListResponse\",\n      \"required\": [\"hosts\", \"nextPageToken\", \"totalSize\"]\n    },\n    \"v2ListLocationsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"locations\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Locations\"\n          },\n          \"title\": \"list of Locations\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"total size\"\n        }\n      },\n      \"title\": \"ListLocations Response\"\n    },\n    \"v2ListMonitorObjectsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"monitorObjects\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2MonitorObject\"\n          },\n          \"title\": \"The list of monitor objects\"\n        }\n      },\n      \"title\": \"ListMonitorObjectsResponse defines the response containing all monitor objects\"\n    },\n    \"v2ListObjectRulesResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"rules\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ObjectRule\"\n          },\n          \"title\": \"The list of object rules\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Token for the next page of results\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The total number of rules\"\n        }\n      },\n      \"title\": \"ListObjectRulesResponse defines the response containing a list of object rules\"\n    },\n    \"v2ListParameterTemplatesResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"parameterTemplates\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ParameterTemplate\"\n          },\n          \"title\": \"The parameter templates\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"ListParameterTemplatesResponse represents a list parameter templates response\",\n      \"required\": [\"parameterTemplates\"]\n    },\n    \"v2ListParametersResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"parameters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Parameter\"\n          },\n          \"title\": \"The parameters\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"ListParametersResponse represents a list parameters response\",\n      \"required\": [\"parameters\"]\n    },\n    \"v2ListRolesResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"roles\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Role\"\n          },\n          \"title\": \"List of users\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"ListRolesResponse\"\n    },\n    \"v2ListRulesResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"rules\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Rule\"\n          },\n          \"title\": \"The list of rules\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Token for the next page of results\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The total number of rules\"\n        }\n      },\n      \"title\": \"ListRulesResponse defines the response containing a list of rules\"\n    },\n    \"v2ListTagKeysResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tagKeys\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"list of tag keys\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"total size\"\n        }\n      },\n      \"title\": \"ListTagKeys Response\"\n    },\n    \"v2ListTagsByResourceTypeResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/tagv2Tag\"\n          },\n          \"title\": \"list of tags\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"total size\"\n        }\n      },\n      \"title\": \"ListTagsByResourceType Response\"\n    },\n    \"v2ListTagsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/tagv2Tag\"\n          },\n          \"title\": \"list of tags\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"total size\"\n        }\n      },\n      \"title\": \"ListTags Response\"\n    },\n    \"v2ListTagsWithBindingsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TagWithBindObject\"\n          },\n          \"title\": \"list of tags\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"total size\"\n        }\n      },\n      \"title\": \"ListTagsWithBindings Response\"\n    },\n    \"v2ListTaskFlowsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskFlows\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TaskFlow\"\n          },\n          \"description\": \"The list of tasks retrieved.\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"description\": \"Token for the next page of results.\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"description\": \"The total number of tasks that match the filter criteria.\"\n        }\n      },\n      \"description\": \"ListTasksResponse defines the response containing a list of tasks and pagination information.\"\n    },\n    \"v2ListTemplateRulesResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"templateRules\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TemplateRule\"\n          },\n          \"title\": \"Alert rules\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Token for retrieving the next page of results\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The total number of rules that match the filter criteria\"\n        },\n        \"templateId\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Template ID\"\n        }\n      },\n      \"title\": \"AlertTemplateRules represents a response containing alert template rules\"\n    },\n    \"v2ListTemplatesResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"templates\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TemplateWithOutRules\"\n          },\n          \"title\": \"List of alert templates\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Token for retrieving the next page of results\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The total number of templates that match the filter criteria\"\n        }\n      },\n      \"title\": \"GetTemplatesResponse represents a response containing alert templates\"\n    },\n    \"v2ListTiupsResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tiups\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Tiups\"\n          },\n          \"title\": \"list of tiups\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"total size\"\n        }\n      },\n      \"title\": \"ListTiups Response\"\n    },\n    \"v2ListUsersResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"users\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2User\"\n          },\n          \"description\": \"The list of users retrieved.\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"description\": \"Token for the next page of results.\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"description\": \"The total number of users that match the filter criteria.\"\n        }\n      },\n      \"description\": \"ListUsersResponse defines the response containing a list of users and pagination information.\"\n    },\n    \"v2LocationMappings\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"locationId\": {\n          \"type\": \"string\",\n          \"title\": \"the Location id of the Location\"\n        },\n        \"parentId\": {\n          \"type\": \"string\",\n          \"title\": \"the Location key of the Location\"\n        },\n        \"locationKey\": {\n          \"type\": \"string\",\n          \"title\": \"the Location value of the Location\"\n        },\n        \"locationValue\": {\n          \"type\": \"string\",\n          \"title\": \"the Location value of the Location\"\n        }\n      },\n      \"title\": \"LocationMappings\",\n      \"required\": [\"locationKey\", \"locationValue\"]\n    },\n    \"v2Locations\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"locationId\": {\n          \"type\": \"string\",\n          \"title\": \"the Location id of the Location\"\n        },\n        \"parentId\": {\n          \"type\": \"string\",\n          \"title\": \"the Location key of the Location\"\n        },\n        \"locationKey\": {\n          \"type\": \"string\",\n          \"enum\": [\"zone\", \"dc\", \"rack\"],\n          \"title\": \"location key  (e.g., \\\"zone\\\", \\\"dc\\\")\"\n        },\n        \"locationValue\": {\n          \"type\": \"string\",\n          \"title\": \"the Location value of the Location\"\n        }\n      },\n      \"title\": \"Location basic resource\"\n    },\n    \"v2LoginRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"userId\": {\n          \"type\": \"string\",\n          \"title\": \"The id of the user\"\n        },\n        \"password\": {\n          \"type\": \"string\",\n          \"title\": \"The password of the user\"\n        }\n      },\n      \"title\": \"Login Request\",\n      \"required\": [\"userId\"]\n    },\n    \"v2MetricWithExpressions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Metric name\"\n        },\n        \"unit\": {\n          \"type\": \"string\",\n          \"title\": \"Unit of the metric\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"Description of the metric\"\n        },\n        \"minTidbVersion\": {\n          \"type\": \"string\",\n          \"title\": \"Minimum supported TiDB version\"\n        },\n        \"maxTidbVersion\": {\n          \"type\": \"string\",\n          \"title\": \"Maximum supported TiDB version\"\n        },\n        \"isBuiltin\": {\n          \"type\": \"boolean\",\n          \"title\": \"Whether it is a built-in metric\"\n        },\n        \"expressions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ExpressionWithLegend\"\n          },\n          \"title\": \"List of associated expressions\"\n        }\n      },\n      \"title\": \"MetricWithExpressions represents a metric with its expressions\"\n    },\n    \"v2Metrics\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"metrics\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2CategoryMetricDetail\"\n          },\n          \"title\": \"List of metrics info\"\n        }\n      },\n      \"title\": \"Metrics represents the list of metrics info\"\n    },\n    \"v2MonitorObject\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The unique identifier of the alert rule\"\n        },\n        \"application\": {\n          \"type\": \"string\",\n          \"title\": \"The monitor object application\"\n        },\n        \"objectType\": {\n          \"type\": \"string\",\n          \"title\": \"The type of the monitor object\"\n        }\n      },\n      \"title\": \"MonitorObject resource\"\n    },\n    \"v2NodesResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterInstances\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ClusterInstances\"\n          },\n          \"title\": \"The cluster_instances list\"\n        },\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"// NodesResponse\",\n      \"required\": [\n        \"clusterInstances\",\n        \"clusterId\",\n        \"nextPageToken\",\n        \"totalSize\"\n      ]\n    },\n    \"v2ObjectRule\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The unique identifier of the alert rule\"\n        },\n        \"createTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"The creation timestamp of the alert rule\",\n          \"readOnly\": true\n        },\n        \"updateTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"The last updated timestamp of the alert rule\",\n          \"readOnly\": true\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The name of the alert rule\"\n        },\n        \"expr\": {\n          \"type\": \"string\",\n          \"title\": \"The Prometheus expression for the rule\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"title\": \"The status of the alert rule\"\n        },\n        \"metricId\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The associated metric ID\"\n        },\n        \"monitorCategoryIds\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          \"title\": \"the category id of the rule\"\n        },\n        \"monitorObject\": {\n          \"$ref\": \"#/definitions/v2MonitorObject\",\n          \"title\": \"MonitorObject resource\"\n        },\n        \"duration\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The rule duration in seconds\"\n        },\n        \"labels\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The labels of the Prometheus rule\"\n        },\n        \"annotations\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The annotations of the Prometheus rule\"\n        },\n        \"level\": {\n          \"type\": \"string\",\n          \"enum\": [\"warning\", \"critical\", \"emergency\"],\n          \"title\": \"The alert level\"\n        },\n        \"alertObject\": {\n          \"type\": \"string\",\n          \"title\": \"The TiDB version for which the rule is applicable\"\n        },\n        \"creator\": {\n          \"type\": \"string\",\n          \"title\": \"The creator of the rule\"\n        }\n      },\n      \"title\": \"AlertRule resource\",\n      \"required\": [\n        \"id\",\n        \"name\",\n        \"expr\",\n        \"labels\",\n        \"annotations\",\n        \"level\",\n        \"alertObject\"\n      ]\n    },\n    \"v2OfflineClusterResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the cluster\"\n        }\n      },\n      \"title\": \"OfflineClusterResponse represents a request to get process list\",\n      \"required\": [\"clusterId\"]\n    },\n    \"v2OverviewStatus\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2StatusCount\"\n          },\n          \"title\": \"List of clusters' status\"\n        },\n        \"hosts\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2StatusCount\"\n          },\n          \"title\": \"List of hosts' status\"\n        },\n        \"alerts\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2StatusCount\"\n          },\n          \"title\": \"List of alerts' status\"\n        },\n        \"alertLevels\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2StatusCount\"\n          },\n          \"title\": \"List of alert levels' status\"\n        },\n        \"brTasks\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2StatusCount\"\n          },\n          \"title\": \"List of backup \\u0026 restore tasks' status\"\n        },\n        \"sysTasks\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2StatusCount\"\n          },\n          \"title\": \"List of system tasks' status\"\n        },\n        \"otherTasks\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2StatusCount\"\n          },\n          \"title\": \"List of other tasks' status\"\n        }\n      },\n      \"title\": \"OverviewStatus represents the response for querying overview data\"\n    },\n    \"v2PDSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"clientPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"peerPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"status_port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"data_dir\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"log_dir\"\n        },\n        \"numaNode\": {\n          \"type\": \"string\",\n          \"title\": \"numa_node\"\n        },\n        \"numaCores\": {\n          \"type\": \"string\",\n          \"title\": \"numa_cores\"\n        },\n        \"config\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"config\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"name\"\n        }\n      },\n      \"title\": \"PDSpec represents the PD topology specification in topology.yaml\"\n    },\n    \"v2ParamBase\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The name of the variable\"\n        },\n        \"instanceType\": {\n          \"type\": \"string\",\n          \"title\": \"The instance type of the variable\"\n        },\n        \"configSource\": {\n          \"type\": \"string\",\n          \"title\": \"The parameter instance type. e.g.: CLUSTER_CONFIG, GLOBAL_VARIABLES\"\n        }\n      },\n      \"title\": \"ConfigBase represents a config base\"\n    },\n    \"v2ParamTypeEnumData\": {\n      \"type\": \"string\",\n      \"enum\": [\"INT\", \"STRING\", \"BOOL\", \"FLOAT\", \"ARRAY\"],\n      \"default\": \"INT\",\n      \"description\": \"- INT: int\\n - STRING: string\\n - BOOL: bool\\n - FLOAT: float\\n - ARRAY: array\",\n      \"title\": \"Data is the type of the parameter\"\n    },\n    \"v2Parameter\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"The ID of the parameter\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The name of the parameter\"\n        },\n        \"configSource\": {\n          \"type\": \"string\",\n          \"title\": \"The parameter instance type. e.g.: CLUSTER_CONFIG, GLOBAL_VARIABLES\"\n        },\n        \"instanceType\": {\n          \"type\": \"string\",\n          \"title\": \"The parameter instance type. e.g.: TiDB, TiKV, PD\"\n        },\n        \"type\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The type of the parameter\"\n        },\n        \"defaultValue\": {\n          \"type\": \"string\",\n          \"title\": \"The default value of the parameter\"\n        },\n        \"range\": {\n          \"type\": \"string\",\n          \"title\": \"The range of the parameter\"\n        },\n        \"version\": {\n          \"type\": \"string\",\n          \"title\": \"The version of the parameter\"\n        },\n        \"dynamic\": {\n          \"type\": \"boolean\",\n          \"title\": \"Whether the parameter is dynamic\"\n        }\n      },\n      \"title\": \"Parameter represents a parameter\",\n      \"required\": [\n        \"id\",\n        \"name\",\n        \"configSource\",\n        \"instanceType\",\n        \"type\",\n        \"defaultValue\",\n        \"range\",\n        \"version\",\n        \"dynamic\"\n      ]\n    },\n    \"v2ParameterTemplate\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"The ID of the parameter template\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The name of the parameter template\"\n        },\n        \"type\": {\n          \"type\": \"string\",\n          \"title\": \"The type of the parameter template, e.g.: TiDB\"\n        },\n        \"parentId\": {\n          \"type\": \"string\",\n          \"title\": \"The parent ID of the parameter template\"\n        },\n        \"clusterSpec\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster specification of the parameter template\"\n        },\n        \"hasDefault\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Whether the parameter template is the default\"\n        },\n        \"dbType\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The database type of the parameter template\"\n        },\n        \"templateType\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The parameter template type, e.g.: 1: system, 2: custom\"\n        },\n        \"clusterVersion\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster version of the parameter template, e.g.: v6.1\"\n        },\n        \"note\": {\n          \"type\": \"string\",\n          \"title\": \"The note of the parameter template\"\n        }\n      },\n      \"title\": \"ParameterTemplate represents a parameter template\",\n      \"required\": [\"name\", \"type\", \"templateType\", \"clusterVersion\", \"note\"]\n    },\n    \"v2PauseClusterResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the cluster\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id of the host\"\n        }\n      },\n      \"title\": \"DeployResponse represents a request to get process list\",\n      \"required\": [\"clusterId\", \"taskId\"]\n    },\n    \"v2PreCheckBackupPolicyResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2Cluster\"\n          },\n          \"title\": \"Clusters that already have backup policies\"\n        }\n      },\n      \"title\": \"PreCheckBackupPolicyResponse represents the conflict clusters which already have backup policies\"\n    },\n    \"v2ProcessList\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterProcessList\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ClusterProcess\"\n          },\n          \"title\": \"List of cluster processes containing detailed information about each process\"\n        },\n        \"isSupportKill\": {\n          \"type\": \"boolean\",\n          \"title\": \"Whether kill operation is supported by the cluster\\nDepends on TiDB version and cluster configuration\"\n        },\n        \"totalProcessCount\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Total number of processes in the cluster\\nIncludes both active and sleeping processes\"\n        },\n        \"activeProcessCount\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"Number of active processes in the cluster\\nExcludes processes in \\\"sleep\\\" state\"\n        }\n      },\n      \"title\": \"ProcessList represents the response of process list query\"\n    },\n    \"v2PrometheusSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"port\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"ngPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"ng_port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"data_dir\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"log_dir\"\n        },\n        \"numaNode\": {\n          \"type\": \"string\",\n          \"title\": \"numa_node\"\n        },\n        \"remoteConfig\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"config\"\n        }\n      },\n      \"title\": \"PrometheusSpec\"\n    },\n    \"v2PumpSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"port\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"data_dir\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"log_dir\"\n        },\n        \"numaNode\": {\n          \"type\": \"string\",\n          \"title\": \"numa_node\"\n        },\n        \"config\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"config\"\n        }\n      },\n      \"title\": \"PumpSpec represents the Dashboard topology specification in topology.yam\"\n    },\n    \"v2QueryMetric\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"instance\": {\n          \"type\": \"string\",\n          \"title\": \"Instance of the metric\"\n        },\n        \"sqlType\": {\n          \"type\": \"string\",\n          \"title\": \"SQL type of the metric\"\n        },\n        \"type\": {\n          \"type\": \"string\",\n          \"title\": \"Type of the metric\"\n        },\n        \"result\": {\n          \"type\": \"string\",\n          \"title\": \"Result of the metric\"\n        },\n        \"txnMode\": {\n          \"type\": \"string\",\n          \"title\": \"Transaction mode of the metric\"\n        },\n        \"job\": {\n          \"type\": \"string\",\n          \"title\": \"Job type of the metric\"\n        },\n        \"device\": {\n          \"type\": \"string\",\n          \"title\": \"Device type of the metric\"\n        },\n        \"fstype\": {\n          \"type\": \"string\",\n          \"title\": \"FSType of the metric\"\n        },\n        \"mountpoint\": {\n          \"type\": \"string\",\n          \"title\": \"MountPoint of the metric\"\n        },\n        \"module\": {\n          \"type\": \"string\",\n          \"title\": \"Module of the metric\"\n        },\n        \"kind\": {\n          \"type\": \"string\",\n          \"title\": \"Kind of the metric\"\n        },\n        \"ping\": {\n          \"type\": \"string\",\n          \"title\": \"Ping of the metric\"\n        },\n        \"le\": {\n          \"type\": \"string\",\n          \"title\": \"Le of the metric\"\n        },\n        \"to\": {\n          \"type\": \"string\",\n          \"title\": \"To of the metric\"\n        },\n        \"cf\": {\n          \"type\": \"string\",\n          \"title\": \"cf of the metric\"\n        },\n        \"store\": {\n          \"type\": \"string\",\n          \"title\": \"store of the metric\"\n        }\n      },\n      \"title\": \"QueryMetric represents the metric details in the query result\"\n    },\n    \"v2QueryResult\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"metric\": {\n          \"$ref\": \"#/definitions/v2QueryMetric\",\n          \"title\": \"Metric details\"\n        },\n        \"values\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/metricsv2Value\"\n          },\n          \"title\": \"Values associated with the metric\"\n        }\n      },\n      \"title\": \"QueryResult represents the result of a query\"\n    },\n    \"v2ReloadClusterResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the cluster\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id of the host\"\n        }\n      },\n      \"title\": \"ReloadClusterResponse represents a request to get process list\",\n      \"required\": [\"clusterId\", \"taskId\"]\n    },\n    \"v2ReportResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"task_id\"\n        },\n        \"taskState\": {\n          \"type\": \"string\",\n          \"enum\": [\"init\", \"running\", \"success\", \"fail\"],\n          \"title\": \"check task state (e.g., \\\"init\\\", \\\"running\\\")\"\n        },\n        \"reports\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/hostv2Report\"\n          },\n          \"title\": \"List of reports\"\n        }\n      },\n      \"title\": \"ReportResponse\",\n      \"required\": [\"taskId\"]\n    },\n    \"v2ResetSecretKeyResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"accessKey\": {\n          \"type\": \"string\",\n          \"title\": \"The access key of the apiKey\"\n        },\n        \"secretKey\": {\n          \"type\": \"string\",\n          \"title\": \"The secret key of the apiKey\"\n        }\n      },\n      \"title\": \"ResetSecretKey Request\",\n      \"required\": [\"accessKey\", \"secretKey\"]\n    },\n    \"v2ResourceGroup\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Resource group name\"\n        },\n        \"ruPerSec\": {\n          \"type\": \"string\",\n          \"title\": \"RU per second\"\n        },\n        \"priority\": {\n          \"type\": \"string\",\n          \"title\": \"Priority\"\n        },\n        \"burstable\": {\n          \"type\": \"string\",\n          \"title\": \"Burstable\"\n        }\n      },\n      \"title\": \"Resource group information\"\n    },\n    \"v2ResourceGroupList\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"resourceGroups\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ResourceGroup\"\n          },\n          \"title\": \"List of resource groups\"\n        }\n      },\n      \"title\": \"Response message for GetResourceGroupList\"\n    },\n    \"v2ResourceObject\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"resourceId\": {\n          \"type\": \"string\",\n          \"title\": \"the resource id of the resource object\"\n        },\n        \"resourceName\": {\n          \"type\": \"string\",\n          \"title\": \"the resource name of the resource object\"\n        }\n      },\n      \"title\": \"Resource object\",\n      \"required\": [\"resourceName\"]\n    },\n    \"v2RestartClusterResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the cluster\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id of the host\"\n        }\n      },\n      \"title\": \"RestartCluster represents a request to get process list\",\n      \"required\": [\"clusterId\", \"taskId\"]\n    },\n    \"v2RestartTaskFlowResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"description\": \"The task_id of the task flow.\"\n        },\n        \"status\": {\n          \"type\": \"boolean\",\n          \"description\": \"The status of the task flow.\"\n        }\n      },\n      \"title\": \"RestartTaskFlowResponse\",\n      \"required\": [\"taskId\", \"status\"]\n    },\n    \"v2ResumeClusterResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the cluster\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id of the host\"\n        }\n      },\n      \"title\": \"ResumeCluster represents a request to get process list\",\n      \"required\": [\"clusterId\", \"taskId\"]\n    },\n    \"v2RetryTaskFlowResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id\"\n        }\n      },\n      \"title\": \"RetryTaskFlowResponse\",\n      \"required\": [\"taskId\"]\n    },\n    \"v2Role\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The id of the role\"\n        },\n        \"roleName\": {\n          \"type\": \"string\",\n          \"title\": \"The name of the role\"\n        },\n        \"roleType\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"The id of the role\"\n        },\n        \"roleTypeDesc\": {\n          \"type\": \"string\",\n          \"title\": \"The id of the role\"\n        },\n        \"detail\": {\n          \"type\": \"string\",\n          \"title\": \"The note of the role\"\n        },\n        \"note\": {\n          \"type\": \"string\",\n          \"title\": \"The note of the role\"\n        },\n        \"createTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"The create time of the role\",\n          \"readOnly\": true\n        },\n        \"updateTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"The update time of the role\",\n          \"readOnly\": true\n        }\n      },\n      \"title\": \"the role resource\"\n    },\n    \"v2Rule\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The unique identifier of the alert rule\"\n        },\n        \"createTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"The creation timestamp of the alert rule\",\n          \"readOnly\": true\n        },\n        \"updateTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"The last updated timestamp of the alert rule\",\n          \"readOnly\": true\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The name of the alert rule\"\n        },\n        \"expr\": {\n          \"type\": \"string\",\n          \"title\": \"The Prometheus expression for the rule\"\n        },\n        \"metricId\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The associated metric ID\"\n        },\n        \"monitorObject\": {\n          \"$ref\": \"#/definitions/v2MonitorObject\",\n          \"title\": \"MonitorObject resource\"\n        },\n        \"monitorCategoryIds\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"integer\",\n            \"format\": \"int32\"\n          },\n          \"title\": \"the category id of the rule\"\n        },\n        \"duration\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The rule duration in seconds\"\n        },\n        \"labels\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The labels of the Prometheus rule\"\n        },\n        \"annotations\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The annotations of the Prometheus rule\"\n        },\n        \"level\": {\n          \"type\": \"string\",\n          \"enum\": [\"warning\", \"critical\", \"emergency\"],\n          \"title\": \"The alert level\"\n        },\n        \"creator\": {\n          \"type\": \"string\",\n          \"title\": \"The creator of the rule\"\n        }\n      },\n      \"title\": \"AlertRule resource\",\n      \"required\": [\"id\", \"name\", \"expr\", \"annotations\", \"level\"]\n    },\n    \"v2ScaleInClusterResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the cluster\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id of the host\"\n        }\n      },\n      \"title\": \"ScaleInClusterResponse represents a request to get process list\",\n      \"required\": [\"clusterId\", \"taskId\"]\n    },\n    \"v2ScaleOutClusterResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the cluster\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id of the host\"\n        }\n      },\n      \"title\": \"ScaleOutClusterResponse represents a request to get process list\",\n      \"required\": [\"clusterId\", \"taskId\"]\n    },\n    \"v2ServerConfigs\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tidb\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"tidb\"\n        },\n        \"tikv\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"tidb\"\n        },\n        \"pd\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"tidb\"\n        },\n        \"grafana\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"grafana\"\n        },\n        \"tidbDashboard\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"tidb_dashboard\"\n        },\n        \"tiflash\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"tiflash\"\n        },\n        \"tiproxy\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"tiproxy\"\n        },\n        \"tiflashLearner\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"tiflash_learner\"\n        },\n        \"pump\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"pump\"\n        },\n        \"drainer\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"drainer\"\n        },\n        \"cdc\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"cdc\"\n        },\n        \"kvcdc\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"kvcdc\"\n        }\n      },\n      \"title\": \"ServerConfigs\"\n    },\n    \"v2ServiceState\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"SERVICE_STATE_UNSPECIFIED\",\n        \"RUNNING\",\n        \"STARTING\",\n        \"RESTARTING\",\n        \"UPGRADING\",\n        \"ABNORMAL\",\n        \"STOPPED\"\n      ],\n      \"default\": \"SERVICE_STATE_UNSPECIFIED\",\n      \"description\": \"- SERVICE_STATE_UNSPECIFIED: unspecified\\n - RUNNING: running\\n - STARTING: starting\\n - RESTARTING: starting\\n - UPGRADING: upgrading\\n - ABNORMAL: abnormal\\n - STOPPED: stopped\",\n      \"title\": \"CM server state\"\n    },\n    \"v2SlowQueryAvailableAdvancedFilterInfo\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Filter name\"\n        },\n        \"unit\": {\n          \"type\": \"string\",\n          \"title\": \"Filter unit\"\n        },\n        \"valueList\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"Filter value list\"\n        },\n        \"type\": {\n          \"type\": \"string\",\n          \"title\": \"Type\"\n        }\n      },\n      \"title\": \"Response message for available filter info\"\n    },\n    \"v2SlowQueryAvailableAdvancedFilters\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"filters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"List of available fields\"\n        }\n      },\n      \"title\": \"Response message for available filters\"\n    },\n    \"v2SlowQueryAvailableFields\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"fields\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"List of available fields\"\n        }\n      },\n      \"title\": \"Response message for available fields\"\n    },\n    \"v2SlowQueryDetail\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"digest\": {\n          \"type\": \"string\",\n          \"title\": \"Digest\"\n        },\n        \"query\": {\n          \"type\": \"string\",\n          \"title\": \"Query\"\n        },\n        \"instance\": {\n          \"type\": \"string\",\n          \"title\": \"Instance\"\n        },\n        \"db\": {\n          \"type\": \"string\",\n          \"title\": \"Database\"\n        },\n        \"connection_id\": {\n          \"type\": \"string\",\n          \"title\": \"Connection ID\"\n        },\n        \"success\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Success\"\n        },\n        \"timestamp\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Finish time\"\n        },\n        \"query_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Latency\"\n        },\n        \"parse_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Parse time\"\n        },\n        \"compile_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Compile time\"\n        },\n        \"rewrite_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Rewrite time\"\n        },\n        \"preproc_subqueries_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Preprocessing subqueries time\"\n        },\n        \"optimize_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Optimize time\"\n        },\n        \"wait_ts\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Wait timestamp\"\n        },\n        \"cop_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Coprocessor time\"\n        },\n        \"lock_keys_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Lock keys time\"\n        },\n        \"write_sql_response_total\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Write SQL response time\"\n        },\n        \"exec_retry_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Execution retry time\"\n        },\n        \"memory_max\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Max memory\"\n        },\n        \"disk_max\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Max disk\"\n        },\n        \"txn_start_ts\": {\n          \"type\": \"string\",\n          \"title\": \"Transaction start timestamp\"\n        },\n        \"prev_stmt\": {\n          \"type\": \"string\",\n          \"title\": \"Previous statement\"\n        },\n        \"plan\": {\n          \"type\": \"string\",\n          \"title\": \"Execution plan\"\n        },\n        \"binary_plan\": {\n          \"type\": \"string\",\n          \"title\": \"Binary execution plan\"\n        },\n        \"warnings\": {\n          \"type\": \"string\",\n          \"title\": \"Warnings\"\n        },\n        \"is_internal\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Is internal\"\n        },\n        \"index_names\": {\n          \"type\": \"string\",\n          \"title\": \"Index names\"\n        },\n        \"stats\": {\n          \"type\": \"string\",\n          \"title\": \"Stats\"\n        },\n        \"backoff_types\": {\n          \"type\": \"string\",\n          \"title\": \"Backoff types\"\n        },\n        \"prepared\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Prepared statement\"\n        },\n        \"plan_from_cache\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Plan from cache\"\n        },\n        \"plan_from_binding\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Plan from binding\"\n        },\n        \"user\": {\n          \"type\": \"string\",\n          \"title\": \"User\"\n        },\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"Host\"\n        },\n        \"process_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Process time\"\n        },\n        \"wait_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Wait time\"\n        },\n        \"backoff_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Backoff time\"\n        },\n        \"get_commit_ts_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Get commit timestamp time\"\n        },\n        \"local_latch_wait_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Local latch wait time\"\n        },\n        \"resolve_lock_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Resolve lock time\"\n        },\n        \"prewrite_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Prewrite time\"\n        },\n        \"wait_prewrite_binlog_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Wait for prewrite binlog time\"\n        },\n        \"commit_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Commit time\"\n        },\n        \"commit_backoff_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Commit backoff time\"\n        },\n        \"cop_proc_avg\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Coprocessor process average\"\n        },\n        \"cop_proc_p90\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Coprocessor process P90\"\n        },\n        \"cop_proc_max\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Coprocessor process max\"\n        },\n        \"cop_wait_avg\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Coprocessor wait average\"\n        },\n        \"cop_wait_p90\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Coprocessor wait P90\"\n        },\n        \"cop_wait_max\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Coprocessor wait max\"\n        },\n        \"write_keys\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Write keys\"\n        },\n        \"write_size\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Write size\"\n        },\n        \"prewrite_region\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Prewrite region\"\n        },\n        \"txn_retry\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Transaction retry\"\n        },\n        \"request_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Request count\"\n        },\n        \"process_keys\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Process keys\"\n        },\n        \"total_keys\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Total keys\"\n        },\n        \"cop_proc_addr\": {\n          \"type\": \"string\",\n          \"title\": \"Coprocessor address\"\n        },\n        \"cop_wait_addr\": {\n          \"type\": \"string\",\n          \"title\": \"Coprocessor wait address\"\n        },\n        \"rocksdb_delete_skipped_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"RocksDB delete skipped count\"\n        },\n        \"rocksdb_key_skipped_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"RocksDB key skipped count\"\n        },\n        \"rocksdb_block_cache_hit_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"RocksDB block cache hit count\"\n        },\n        \"rocksdb_block_read_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"RocksDB block read count\"\n        },\n        \"rocksdb_block_read_byte\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"RocksDB block read byte\"\n        },\n        \"binary_plan_text\": {\n          \"type\": \"string\",\n          \"title\": \"Binary plan in plain text\"\n        },\n        \"session_alias\": {\n          \"type\": \"string\",\n          \"title\": \"Session alias\"\n        },\n        \"exec_retry_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Execution retry count\"\n        },\n        \"preproc_subqueries\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Preprocessing subqueries\"\n        },\n        \"kv_total\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"KV total\"\n        },\n        \"pd_total\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"PD total\"\n        },\n        \"backoff_total\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Backoff total\"\n        },\n        \"time_queued_by_rc\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Time queued by RC\"\n        },\n        \"tidb_cpu_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"TiDB CPU time\"\n        },\n        \"tikv_cpu_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"TiKV CPU time\"\n        },\n        \"backoff_detail\": {\n          \"type\": \"string\",\n          \"title\": \"Backoff detail\"\n        },\n        \"is_explicit_txn\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Is explicit transaction\"\n        },\n        \"plan_digest\": {\n          \"type\": \"string\",\n          \"title\": \"Plan digest\"\n        },\n        \"has_more_results\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Has more results\"\n        },\n        \"resource_group\": {\n          \"type\": \"string\",\n          \"title\": \"Resource group\"\n        },\n        \"request_unit_read\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Request unit read\"\n        },\n        \"request_unit_write\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Request unit write\"\n        },\n        \"result_rows\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Result rows\"\n        },\n        \"ru\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"Resource unit\"\n        }\n      },\n      \"title\": \"Model message representing a slow query log\"\n    },\n    \"v2SlowQueryDownloadResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"filename\": {\n          \"type\": \"string\",\n          \"title\": \"File name\"\n        },\n        \"fileContent\": {\n          \"type\": \"string\",\n          \"title\": \"File content\"\n        }\n      },\n      \"title\": \"SlowQueryDownloadResponse represents the response for downloading slow query list\"\n    },\n    \"v2SlowQueryList\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"data\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2SlowQueryDetail\"\n          },\n          \"title\": \"List of slow sql models\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"Response message for querying slow log list\"\n    },\n    \"v2Spec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"globalOptions\": {\n          \"$ref\": \"#/definitions/v2GlobalOptions\",\n          \"title\": \"GlobalOptions\"\n        },\n        \"serverConfigs\": {\n          \"$ref\": \"#/definitions/v2ServerConfigs\",\n          \"title\": \"MonitoredOptions  MonitoredOptions     `yaml:\\\"monitored,omitempty\\\" validate:\\\"monitored:editable\\\"`\\nComponentVersions ComponentVersions    `yaml:\\\"component_versions,omitempty\\\" validate:\\\"component_versions:editable\\\"`\\nComponentSources  ComponentSources     `yaml:\\\"component_sources,omitempty\\\" validate:\\\"component_sources:editable\\\"`\\nServerConfigs\"\n        },\n        \"tidbServers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TiDBSpec\"\n          },\n          \"title\": \"tidb_servers\"\n        },\n        \"tikvServers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TiKVSpec\"\n          },\n          \"title\": \"tikv_servers\"\n        },\n        \"tiflashServers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TiFlashSpec\"\n          },\n          \"title\": \"tiflash_servers\"\n        },\n        \"tiproxyServers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TiProxySpec\"\n          },\n          \"title\": \"tiproxy_servers\"\n        },\n        \"pdServers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2PDSpec\"\n          },\n          \"title\": \"pd_servers\"\n        },\n        \"tidbDashboardServers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2DashboardSpec\"\n          },\n          \"title\": \"tidb_dashboard_servers\"\n        },\n        \"pumpServers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2PumpSpec\"\n          },\n          \"title\": \"pump_servers\"\n        },\n        \"drainerServers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2DrainerSpec\"\n          },\n          \"title\": \"drainer_servers\"\n        },\n        \"cdcServers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2CDCSpec\"\n          },\n          \"title\": \"cdc_servers\"\n        },\n        \"kvcdcServers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TiKVCDCSpec\"\n          },\n          \"title\": \"kvcdc_servers\"\n        },\n        \"tisparkMasters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TiSparkMasterSpec\"\n          },\n          \"title\": \"tispark_masters\"\n        },\n        \"tisparkWorkers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TiSparkWorkerSpec\"\n          },\n          \"title\": \"tispark_workers\"\n        },\n        \"monitoringServers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2PrometheusSpec\"\n          },\n          \"title\": \"monitoring_servers\"\n        },\n        \"grafanaServers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2GrafanaSpec\"\n          },\n          \"title\": \"grafana_servers\"\n        },\n        \"alertmanagerServers\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2AlertmanagerSpec\"\n          },\n          \"title\": \"alertmanager_servers\"\n        }\n      },\n      \"title\": \"Spec\"\n    },\n    \"v2SqlLimit\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\",\n          \"title\": \"ID\"\n        },\n        \"resourceGroupName\": {\n          \"type\": \"string\",\n          \"title\": \"Resource group name\"\n        },\n        \"startTime\": {\n          \"type\": \"string\",\n          \"title\": \"Start time\"\n        },\n        \"endTime\": {\n          \"type\": \"string\",\n          \"title\": \"End time\"\n        },\n        \"watch\": {\n          \"type\": \"string\",\n          \"title\": \"Watch\"\n        },\n        \"watchText\": {\n          \"type\": \"string\",\n          \"title\": \"Watch text\"\n        },\n        \"source\": {\n          \"type\": \"string\",\n          \"title\": \"Source\"\n        },\n        \"action\": {\n          \"type\": \"string\",\n          \"enum\": [\"DRYRUN\", \"COOLDOWN\", \"KILL\"],\n          \"title\": \"Action\"\n        }\n      },\n      \"title\": \"SQL limit model\"\n    },\n    \"v2SqlLimitList\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"data\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2SqlLimit\"\n          },\n          \"title\": \"List of SQL limit\"\n        }\n      },\n      \"title\": \"Response message for getting SQL limit\"\n    },\n    \"v2SqlPlanBindingDetail\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"enabled\",\n            \"using\",\n            \"disabled\",\n            \"deleted\",\n            \"invalid\",\n            \"rejected\",\n            \"pending verify\"\n          ],\n          \"title\": \"SQL plan binding status\"\n        },\n        \"source\": {\n          \"type\": \"string\",\n          \"enum\": [\"manual\", \"history\", \"capture\", \"evolve\"],\n          \"title\": \"SQL plan binding source\"\n        },\n        \"digest\": {\n          \"type\": \"string\",\n          \"title\": \"sql digest\"\n        },\n        \"planDigest\": {\n          \"type\": \"string\",\n          \"title\": \"plan digest\"\n        }\n      },\n      \"title\": \"Detail of sql plan binding\"\n    },\n    \"v2SqlPlanBindingList\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"data\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2SqlPlanBindingDetail\"\n          },\n          \"title\": \"List of sql plan binding\"\n        }\n      },\n      \"title\": \"Response of get sql plan binding\"\n    },\n    \"v2SqlPlanList\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"data\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TopSqlDetail\"\n          },\n          \"title\": \"List of sql plan\"\n        }\n      },\n      \"title\": \"Response message for getting sql plan\"\n    },\n    \"v2Stack\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"error\": {\n          \"type\": \"string\",\n          \"description\": \"The error message if any.\"\n        },\n        \"Actives\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/v2Abstract\"\n          },\n          \"description\": \"The active tasks information.\"\n        },\n        \"Actions\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"$ref\": \"#/definitions/v2Action\"\n          },\n          \"description\": \"The actions information.\"\n        }\n      },\n      \"description\": \"Stack represents the execution stack of a task flow.\"\n    },\n    \"v2State\": {\n      \"type\": \"string\",\n      \"enum\": [\"STATE_UNSPECIFIED\", \"ONLINE\", \"OFFLINE\"],\n      \"default\": \"STATE_UNSPECIFIED\",\n      \"description\": \"- STATE_UNSPECIFIED: unspecified\\n - ONLINE: online\\n - OFFLINE: offline\",\n      \"title\": \"Server state\"\n    },\n    \"v2StatusCount\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"status\": {\n          \"type\": \"string\",\n          \"title\": \"Targets status (e.g., \\\"healthy\\\", \\\"unhealthy\\\")\"\n        },\n        \"count\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The number of targets with this status\"\n        }\n      },\n      \"title\": \"StatusCount represents the status of a target\"\n    },\n    \"v2StatusEnumData\": {\n      \"type\": \"string\",\n      \"enum\": [\"all\", \"running\", \"finished\", \"abnormal\", \"stopped\"],\n      \"default\": \"all\",\n      \"description\": \"- all: All\\n - running: Running\\n - finished: Finished\\n - abnormal: Abnormal\\n - stopped: Stopped\",\n      \"title\": \"Data of StatusEnum\"\n    },\n    \"v2StatusStatistics\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"alertingCount\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Number of alerting alerts\"\n        },\n        \"resolvedCount\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Number of resolved alerts\"\n        },\n        \"silencedCount\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Number of silenced alerts\"\n        },\n        \"totalCount\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total number of alerts\"\n        }\n      },\n      \"title\": \"StatusStatistics represents statistics by alert status\"\n    },\n    \"v2Step\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"Function\": {\n          \"type\": \"string\",\n          \"description\": \"The stepFunction.\"\n        },\n        \"ArgsTemplate\": {\n          \"type\": \"string\",\n          \"description\": \"The arguments template.\"\n        },\n        \"ReplyTemplate\": {\n          \"type\": \"string\",\n          \"description\": \"The reply template.\"\n        },\n        \"NextHops\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"The next steps.\"\n        },\n        \"Requires\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"The required steps.\"\n        },\n        \"TimeLimit\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"description\": \"The time limit in seconds.\"\n        },\n        \"MaxRetry\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"description\": \"The maximum retry count.\"\n        },\n        \"RetryWaitTime\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"description\": \"The retry wait time in seconds.\"\n        },\n        \"Description\": {\n          \"type\": \"string\",\n          \"description\": \"The step description.\"\n        }\n      },\n      \"description\": \"Step represents a task step.\",\n      \"required\": [\"Function\"]\n    },\n    \"v2SupportVersions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"versions\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The versions\"\n        }\n      },\n      \"title\": \"SupportVersions represents support version list\",\n      \"required\": [\"versions\"]\n    },\n    \"v2TagBindResourceType\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"TAG_BIND_RESOURCE_TYPE_UNSPECIFIED\",\n        \"HOST\",\n        \"TIUP\",\n        \"CLUSTER\",\n        \"CM_SERVER\"\n      ],\n      \"default\": \"TAG_BIND_RESOURCE_TYPE_UNSPECIFIED\",\n      \"description\": \"- TAG_BIND_RESOURCE_TYPE_UNSPECIFIED: resource type unspecified\\n - HOST: resource type host\\n - TIUP: resource type tiup\\n - CLUSTER: resource type cluster\\n - CM_SERVER: resource type cm server\",\n      \"title\": \"define tag bind resource type\"\n    },\n    \"v2TagWithBindObject\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tagInfo\": {\n          \"$ref\": \"#/definitions/tagv2Tag\",\n          \"title\": \"the tag basic info of the tag\"\n        },\n        \"bindObjects\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2BindObject\"\n          },\n          \"title\": \"the bound objects of the tag\"\n        }\n      },\n      \"title\": \"Tag resource with bound object\"\n    },\n    \"v2Tags\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tagId\": {\n          \"type\": \"string\",\n          \"title\": \"the tag id of the tag\"\n        },\n        \"tagKey\": {\n          \"type\": \"string\",\n          \"title\": \"the tag key of the tag\"\n        },\n        \"tagValue\": {\n          \"type\": \"string\",\n          \"title\": \"the tag value of the tag\"\n        }\n      },\n      \"title\": \"Tags\",\n      \"required\": [\"tagValue\"]\n    },\n    \"v2TakeoverClusterRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tiupId\": {\n          \"type\": \"string\",\n          \"title\": \"tiup_id\"\n        },\n        \"takeoverCluster\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/clusterv2TakeoverCluster\"\n          },\n          \"title\": \"The TakeoverCluster\"\n        }\n      },\n      \"title\": \"TakeoverClusterRequest\",\n      \"required\": [\"tiupId\", \"takeoverCluster\"]\n    },\n    \"v2TakeoverClusterResp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id of the host\"\n        },\n        \"taskId\": {\n          \"type\": \"string\",\n          \"title\": \"The task_id of the host\"\n        }\n      },\n      \"title\": \"TakeoverCluster\",\n      \"required\": [\"clusterId\", \"taskId\"]\n    },\n    \"v2TakeoverClusterResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"takeoverClusterResp\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TakeoverClusterResp\"\n          },\n          \"title\": \"The takeover_cluster_resp of the host\"\n        }\n      },\n      \"title\": \"DeployResponse represents a request to get process list\",\n      \"required\": [\"takeoverClusterResp\"]\n    },\n    \"v2TaskFlow\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"description\": \"The unique identifier of the task.\"\n        },\n        \"templateId\": {\n          \"type\": \"string\",\n          \"description\": \"The template ID associated with the task.\"\n        },\n        \"creator\": {\n          \"type\": \"string\",\n          \"description\": \"The creator of the task.\"\n        },\n        \"parentId\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"tidb:deploy\",\n            \"tidb:takeover\",\n            \"tidb:scaleOut\",\n            \"tidb:scaleIn\",\n            \"tidb:destroy\",\n            \"tidb:stop\",\n            \"tidb:start\",\n            \"tidb:restart\",\n            \"tidb:reload\",\n            \"tidb:install_sql_audit_plugin\",\n            \"host:import\",\n            \"host:delete\",\n            \"host:pre:check\"\n          ],\n          \"description\": \"The parent task identifier.\",\n          \"title\": \"The parent enum\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\n            \"success\",\n            \"abort\",\n            \"timeout\",\n            \"failed\",\n            \"running\",\n            \"pending\"\n          ],\n          \"description\": \"The status of the task.\",\n          \"title\": \"The task status\"\n        },\n        \"startTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"description\": \"The timestamp when the task started.\",\n          \"readOnly\": true\n        },\n        \"endTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"description\": \"The timestamp when the task ended.\",\n          \"readOnly\": true\n        }\n      },\n      \"description\": \"Task represents a task resource containing detailed information.\",\n      \"required\": [\"taskId\", \"templateId\"]\n    },\n    \"v2TaskFlowDetail\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"description\": \"The unique identifier of the task flow.\"\n        },\n        \"parentId\": {\n          \"type\": \"string\",\n          \"description\": \"The identifier of the parent task flow.\"\n        },\n        \"templateId\": {\n          \"type\": \"string\",\n          \"description\": \"The identifier of the template.\"\n        },\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\"pending\", \"running\", \"finished\", \"failed\", \"success\"],\n          \"description\": \"The current status of the task flow.\",\n          \"title\": \"The task flow status\"\n        },\n        \"startTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"description\": \"The time when the task flow started.\",\n          \"readOnly\": true\n        },\n        \"endTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"description\": \"The time when the task flow ended.\",\n          \"readOnly\": true\n        },\n        \"args\": {\n          \"type\": \"string\",\n          \"description\": \"The args of task flow.\"\n        },\n        \"stack\": {\n          \"type\": \"string\",\n          \"description\": \"The execution stack information.\"\n        },\n        \"context\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"The context information.\"\n        },\n        \"creator\": {\n          \"type\": \"string\",\n          \"description\": \"The creator of the task flow.\"\n        },\n        \"ignoreField\": {\n          \"$ref\": \"#/definitions/v2Stack\",\n          \"description\": \"The execution stack information.\"\n        }\n      },\n      \"description\": \"TaskFlowDetail represents detailed information about a task flow.\",\n      \"required\": [\"taskId\", \"templateId\"]\n    },\n    \"v2TaskFlowInfo\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"status\": {\n          \"type\": \"string\",\n          \"enum\": [\"pending\", \"running\", \"finished\", \"failed\", \"success\"],\n          \"description\": \"The status of the task flow.\",\n          \"title\": \"The task flow status\"\n        },\n        \"startTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"description\": \"The start_time when the task flow started.\",\n          \"readOnly\": true\n        },\n        \"taskMsg\": {\n          \"type\": \"string\",\n          \"description\": \"The task_msg of the task flow.\"\n        },\n        \"taskInput\": {\n          \"type\": \"string\",\n          \"description\": \"The task_input of the task flow.\"\n        }\n      },\n      \"description\": \"TaskFlowDetail represents detailed information about a task flow.\"\n    },\n    \"v2TaskFlowInfoResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"taskId\": {\n          \"type\": \"string\",\n          \"description\": \"The task_id of the task flow.\"\n        },\n        \"nodeKey\": {\n          \"type\": \"string\",\n          \"description\": \"The parent_id of the task flow.\"\n        },\n        \"taskFlowInfo\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TaskFlowInfo\"\n          },\n          \"description\": \"The task_flow_info of the task flow.\"\n        }\n      },\n      \"description\": \"TaskFlowInfoResponse\\nTaskFlowInfoResponse defines the response containing task flow information.\",\n      \"required\": [\"taskId\", \"nodeKey\"]\n    },\n    \"v2Template\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"ID\",\n          \"readOnly\": true\n        },\n        \"createTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Creation time of the template\",\n          \"readOnly\": true\n        },\n        \"updateTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Last update time of the template\",\n          \"readOnly\": true\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Template name\"\n        },\n        \"tidbVersion\": {\n          \"type\": \"string\",\n          \"title\": \"TiDB version this template applies to\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"Template description\"\n        },\n        \"creator\": {\n          \"type\": \"string\",\n          \"title\": \"Creator of template\",\n          \"readOnly\": true\n        },\n        \"templateRules\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TemplateRule\"\n          },\n          \"title\": \"Alert rules\"\n        }\n      },\n      \"title\": \"AlertTemplate represents an alert rule template\",\n      \"required\": [\"name\", \"tidbVersion\"]\n    },\n    \"v2TemplateParameterMapping\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"parameterTemplateId\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"The ID of the parameter template\"\n        },\n        \"parameterId\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"The ID of the parameter\"\n        },\n        \"defaultValue\": {\n          \"type\": \"string\",\n          \"title\": \"The default value of the parameter\"\n        },\n        \"parameter\": {\n          \"$ref\": \"#/definitions/v2Parameter\",\n          \"title\": \"The parameter\"\n        },\n        \"note\": {\n          \"type\": \"string\",\n          \"title\": \"The note of the parameter template mapping\"\n        }\n      },\n      \"title\": \"TemplateParameterMapping represents template to parameter mapping\",\n      \"required\": [\"parameterId\", \"defaultValue\"]\n    },\n    \"v2TemplateRule\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"ID of the rule\",\n          \"readOnly\": true\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"The name of the alert rule\"\n        },\n        \"expr\": {\n          \"type\": \"string\",\n          \"title\": \"The Prometheus expression for the rule\"\n        },\n        \"metricId\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The associated metric ID\"\n        },\n        \"monitorObjectId\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The associated monitor object ID\"\n        },\n        \"duration\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The rule duration in seconds\"\n        },\n        \"labels\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The labels of the Prometheus rule\"\n        },\n        \"annotations\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"The annotations of the Prometheus rule\"\n        },\n        \"level\": {\n          \"type\": \"string\",\n          \"enum\": [\"warning\", \"critical\", \"emergency\"],\n          \"title\": \"The alert level\"\n        }\n      },\n      \"title\": \"TemplateRule represents a request to create a rule in a template\",\n      \"required\": [\"name\"]\n    },\n    \"v2TemplateWithOutRules\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"ID\",\n          \"readOnly\": true\n        },\n        \"createTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Creation time of the template\",\n          \"readOnly\": true\n        },\n        \"updateTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"Last update time of the template\",\n          \"readOnly\": true\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Template name\"\n        },\n        \"tidbVersion\": {\n          \"type\": \"string\",\n          \"title\": \"TiDB version this template applies to\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"Template description\"\n        },\n        \"creator\": {\n          \"type\": \"string\",\n          \"title\": \"Creator of template\",\n          \"readOnly\": true\n        }\n      },\n      \"title\": \"AlertTemplate represents an alert rule template\",\n      \"required\": [\"name\", \"tidbVersion\"]\n    },\n    \"v2TiDBCredentialObject\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"password\": {\n          \"type\": \"string\",\n          \"title\": \"the password of the user\"\n        },\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"the cluster id of cluster bound with current credential\"\n        },\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"title\": \"the cluster name of cluster bound with current credential\"\n        }\n      },\n      \"title\": \"TiDB cluster credential object\",\n      \"required\": [\"password\"]\n    },\n    \"v2TiDBProcesses\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"uid\": {\n          \"type\": \"string\",\n          \"title\": \"host Processes\\nThe Uid of the Host Processes\"\n        },\n        \"pid\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The Pid of the Host Processes\"\n        },\n        \"ppid\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The Ppid of the Host Processes\"\n        },\n        \"startTime\": {\n          \"type\": \"string\",\n          \"title\": \"The stime of the Host Processes\"\n        },\n        \"runningTime\": {\n          \"type\": \"string\",\n          \"title\": \"The time of the Host Processes\"\n        },\n        \"cmd\": {\n          \"type\": \"string\",\n          \"title\": \"The cmd of the Host Processes\"\n        }\n      },\n      \"title\": \"TiDBProcesses\"\n    },\n    \"v2TiDBSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"port\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"statusPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"status_port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"log_dir\"\n        },\n        \"numaNode\": {\n          \"type\": \"string\",\n          \"title\": \"numa_node\"\n        },\n        \"numaCores\": {\n          \"type\": \"string\",\n          \"title\": \"numa_cores\"\n        },\n        \"config\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"config\"\n        }\n      },\n      \"title\": \"TiDBSpec\"\n    },\n    \"v2TiFlashSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"tcpPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"tcp_port\"\n        },\n        \"httpPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"http_port\"\n        },\n        \"flashServicePort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"flash_service_port\"\n        },\n        \"flashProxyPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"flash_proxy_port\"\n        },\n        \"flashProxyStatusPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"flash_proxy_status_port\"\n        },\n        \"metricsPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"metrics_port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"data_dir\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"log_dir\"\n        },\n        \"numaNode\": {\n          \"type\": \"string\",\n          \"title\": \"numa_node\"\n        },\n        \"numaCores\": {\n          \"type\": \"string\",\n          \"title\": \"numa_cores\"\n        },\n        \"config\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"config\"\n        },\n        \"learnerConfig\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"learner_config\"\n        }\n      },\n      \"title\": \"TiFlashSpec\"\n    },\n    \"v2TiKVCDCSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"port\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"data_dir\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"log_dir\"\n        },\n        \"numaNode\": {\n          \"type\": \"string\",\n          \"title\": \"numa_node\"\n        },\n        \"config\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"config\"\n        }\n      },\n      \"title\": \"TiKVCDCSpec represents the CDC topology specification in topology.yaml\"\n    },\n    \"v2TiKVSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"port\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"statusPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"status_port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"dataDir\": {\n          \"type\": \"string\",\n          \"title\": \"data_dir\"\n        },\n        \"logDir\": {\n          \"type\": \"string\",\n          \"title\": \"log_dir\"\n        },\n        \"numaNode\": {\n          \"type\": \"string\",\n          \"title\": \"numa_node\"\n        },\n        \"numaCores\": {\n          \"type\": \"string\",\n          \"title\": \"numa_cores\"\n        },\n        \"config\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"config\"\n        }\n      },\n      \"title\": \"TiKVSpec\"\n    },\n    \"v2TiProxySpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"port\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"statusPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"status_port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"numaNode\": {\n          \"type\": \"string\",\n          \"title\": \"numa_node\"\n        },\n        \"config\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"config\"\n        }\n      },\n      \"title\": \"TiProxySpec\"\n    },\n    \"v2TiSparkMasterSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"port\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"webPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"web_port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        },\n        \"sparkConfig\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"config\"\n        }\n      },\n      \"title\": \"TiSparkMasterSpec is the topology specification for TiSpark master node\"\n    },\n    \"v2TiSparkWorkerSpec\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"host\": {\n          \"type\": \"string\",\n          \"title\": \"host\"\n        },\n        \"port\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"port\"\n        },\n        \"webPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"web_port\"\n        },\n        \"deployDir\": {\n          \"type\": \"string\",\n          \"title\": \"deploy_dir\"\n        }\n      },\n      \"title\": \"TiSparkWorkerSpec is the topology specification for TiSpark master node\"\n    },\n    \"v2Tiup\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tiupHome\": {\n          \"type\": \"string\",\n          \"title\": \"tiup_home\"\n        },\n        \"version\": {\n          \"type\": \"string\",\n          \"title\": \"version\"\n        }\n      },\n      \"title\": \"Tiup Resource\"\n    },\n    \"v2TiupCredential\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"credentialId\": {\n          \"type\": \"string\",\n          \"title\": \"the credentialId value of the Credential\"\n        },\n        \"credentialName\": {\n          \"type\": \"string\",\n          \"title\": \"the credentialName value of the Credential\"\n        },\n        \"credentialType\": {\n          \"type\": \"string\",\n          \"title\": \"the credentialType value of the Credential\"\n        },\n        \"userName\": {\n          \"type\": \"string\",\n          \"title\": \"the userName value of the Credential\"\n        }\n      },\n      \"title\": \"TiupCredential\"\n    },\n    \"v2TiupHost\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"hostId\": {\n          \"type\": \"string\",\n          \"title\": \"The Host_Id of the Host\"\n        },\n        \"ip\": {\n          \"type\": \"string\",\n          \"title\": \"The IP of the Host\"\n        },\n        \"hostName\": {\n          \"type\": \"string\",\n          \"title\": \"The HostName of the Host\"\n        },\n        \"sshPort\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The SSHPort of the Host\"\n        },\n        \"credentialId\": {\n          \"type\": \"string\",\n          \"title\": \"The Credential_Id of the Host\"\n        },\n        \"osName\": {\n          \"type\": \"string\",\n          \"title\": \"os_name\"\n        },\n        \"osVersion\": {\n          \"type\": \"string\",\n          \"title\": \"os_version\"\n        },\n        \"osRelease\": {\n          \"type\": \"string\",\n          \"title\": \"os_release\"\n        },\n        \"osArchitecture\": {\n          \"type\": \"string\",\n          \"title\": \"os_architecture\"\n        },\n        \"hostType\": {\n          \"type\": \"string\",\n          \"enum\": [\"VM\", \"PM\"],\n          \"title\": \"host Type (e.g., \\\"VM\\\", \\\"PM\\\")\"\n        },\n        \"locationId\": {\n          \"type\": \"string\",\n          \"title\": \"location_id\"\n        },\n        \"createdTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"created_time\"\n        },\n        \"updatedTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"title\": \"updated_time\"\n        },\n        \"credential\": {\n          \"$ref\": \"#/definitions/v2TiupCredential\",\n          \"title\": \"credential\"\n        }\n      },\n      \"title\": \"Host resource\",\n      \"required\": [\"hostId\"]\n    },\n    \"v2TiupTags\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tagId\": {\n          \"type\": \"string\",\n          \"title\": \"the tag id of the tag\"\n        },\n        \"tagKey\": {\n          \"type\": \"string\",\n          \"title\": \"the tag key of the tag\"\n        },\n        \"tagValue\": {\n          \"type\": \"string\",\n          \"title\": \"the tag value of the tag\"\n        }\n      },\n      \"title\": \"TiupTags\",\n      \"required\": [\"tagValue\"]\n    },\n    \"v2Tiups\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tiupId\": {\n          \"type\": \"string\",\n          \"title\": \"tiup_id\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"name\"\n        },\n        \"tiupHome\": {\n          \"type\": \"string\",\n          \"title\": \"tiup_home\"\n        },\n        \"version\": {\n          \"type\": \"string\",\n          \"title\": \"version\"\n        },\n        \"credentialId\": {\n          \"type\": \"string\",\n          \"title\": \"credential_id\"\n        },\n        \"tags\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TiupTags\"\n          },\n          \"title\": \"The tags of the Host\"\n        },\n        \"description\": {\n          \"type\": \"string\",\n          \"title\": \"description\"\n        },\n        \"hostId\": {\n          \"type\": \"string\",\n          \"title\": \"host_id\"\n        },\n        \"host\": {\n          \"$ref\": \"#/definitions/v2TiupHost\",\n          \"title\": \"host\"\n        }\n      },\n      \"title\": \"Tiups basic resource\"\n    },\n    \"v2TiupsClusters\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"cluster_id\"\n        },\n        \"clusterName\": {\n          \"type\": \"string\",\n          \"title\": \"cluster_id\"\n        },\n        \"user\": {\n          \"type\": \"string\",\n          \"title\": \"user\"\n        },\n        \"version\": {\n          \"type\": \"string\",\n          \"title\": \"version\"\n        },\n        \"metaPath\": {\n          \"type\": \"string\",\n          \"title\": \"patch\"\n        },\n        \"privateKeyPath\": {\n          \"type\": \"string\",\n          \"title\": \"private_key\"\n        },\n        \"managed\": {\n          \"type\": \"boolean\",\n          \"title\": \"managed\"\n        }\n      },\n      \"title\": \"TiupsClusters\"\n    },\n    \"v2TiupsClustersResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tiupsClusters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TiupsClusters\"\n          },\n          \"title\": \"list of tiups\"\n        }\n      },\n      \"title\": \"ListTiups Response\"\n    },\n    \"v2TiupsServiceUpdateTiupsBody\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tiups\": {\n          \"$ref\": \"#/definitions/tiupv2UpdateTiups\",\n          \"title\": \"the tiups resource\"\n        }\n      },\n      \"title\": \"UpdateTiupsRequest Request\"\n    },\n    \"v2TopMetricConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"cacheFlushIntervalInMinutes\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"overview cache flush interval in minute, 0 means no cache\"\n        }\n      },\n      \"title\": \"TopMetricConfig represents the response for querying top metric config\"\n    },\n    \"v2TopMetricData\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"status\": {\n          \"type\": \"string\",\n          \"title\": \"Response Status (e.g., \\\"success\\\", \\\"error\\\")\"\n        },\n        \"data\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ExprQueryData\"\n          },\n          \"title\": \"Response Data containing the top queried metrics\"\n        }\n      },\n      \"title\": \"TopMetricData represents the response for querying top metric data\"\n    },\n    \"v2TopSqlAvailableAdvancedFilterInfo\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Filter name\"\n        },\n        \"unit\": {\n          \"type\": \"string\",\n          \"title\": \"Filter unit\"\n        },\n        \"valueList\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"Filter value list\"\n        },\n        \"type\": {\n          \"type\": \"string\",\n          \"title\": \"Type\"\n        }\n      },\n      \"title\": \"Response message for available filter info\"\n    },\n    \"v2TopSqlAvailableAdvancedFilters\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"filters\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"List of available fields\"\n        }\n      },\n      \"title\": \"Response message for available filters\"\n    },\n    \"v2TopSqlAvailableFields\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"fields\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"List of available fields\"\n        }\n      },\n      \"title\": \"Response message for getting top sql available fields\"\n    },\n    \"v2TopSqlConfigs\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"enable\": {\n          \"type\": \"boolean\",\n          \"title\": \"tidb_enable_stmt_summary\"\n        },\n        \"refreshInterval\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"tidb_stmt_summary_refresh_interval\"\n        },\n        \"historySize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"tidb_stmt_summary_history_size\"\n        },\n        \"maxSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"tidb_stmt_summary_max_stmt_count\"\n        },\n        \"internalQuery\": {\n          \"type\": \"boolean\",\n          \"title\": \"tidb_stmt_summary_internal_query\"\n        }\n      },\n      \"title\": \"Response message for getting top sql configs\"\n    },\n    \"v2TopSqlDetail\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"summary_begin_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggBeginTime\"\n        },\n        \"summary_end_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggEndTime\"\n        },\n        \"digest_text\": {\n          \"type\": \"string\",\n          \"title\": \"AggDigestText\"\n        },\n        \"digest\": {\n          \"type\": \"string\",\n          \"title\": \"AggDigest\"\n        },\n        \"exec_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggExecCount\"\n        },\n        \"stmt_type\": {\n          \"type\": \"string\",\n          \"title\": \"AggStmtType\"\n        },\n        \"sum_errors\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggSumErrors\"\n        },\n        \"sum_warnings\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggSumWarnings\"\n        },\n        \"sum_latency\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggSumLatency\"\n        },\n        \"max_latency\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxLatency\"\n        },\n        \"min_latency\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMinLatency\"\n        },\n        \"avg_latency\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgLatency\"\n        },\n        \"avg_parse_latency\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgParseLatency\"\n        },\n        \"max_parse_latency\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxParseLatency\"\n        },\n        \"avg_compile_latency\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgCompileLatency\"\n        },\n        \"max_compile_latency\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxCompileLatency\"\n        },\n        \"sum_cop_task_num\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggSumCopTaskNum\"\n        },\n        \"avg_cop_process_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgCopProcessTime\"\n        },\n        \"max_cop_process_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxCopProcessTime\"\n        },\n        \"avg_cop_wait_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgCopWaitTime\"\n        },\n        \"max_cop_wait_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxCopWaitTime\"\n        },\n        \"avg_process_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgProcessTime\"\n        },\n        \"max_process_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxProcessTime\"\n        },\n        \"avg_wait_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgWaitTime\"\n        },\n        \"max_wait_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxWaitTime\"\n        },\n        \"avg_backoff_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgBackoffTime\"\n        },\n        \"max_backoff_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxBackoffTime\"\n        },\n        \"avg_total_keys\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgTotalKeys\"\n        },\n        \"max_total_keys\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxTotalKeys\"\n        },\n        \"avg_processed_keys\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgProcessedKeys\"\n        },\n        \"max_processed_keys\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxProcessedKeys\"\n        },\n        \"avg_prewrite_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgPrewriteTime\"\n        },\n        \"max_prewrite_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxPrewriteTime\"\n        },\n        \"avg_commit_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgCommitTime\"\n        },\n        \"max_commit_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxCommitTime\"\n        },\n        \"avg_get_commit_ts_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgGetCommitTsTime\"\n        },\n        \"max_get_commit_ts_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxGetCommitTsTime\"\n        },\n        \"avg_commit_backoff_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgCommitBackoffTime\"\n        },\n        \"max_commit_backoff_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxCommitBackoffTime\"\n        },\n        \"avg_resolve_lock_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgResolveLockTime\"\n        },\n        \"max_resolve_lock_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxResolveLockTime\"\n        },\n        \"avg_local_latch_wait_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgLocalLatchWaitTime\"\n        },\n        \"max_local_latch_wait_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxLocalLatchWaitTime\"\n        },\n        \"avg_write_keys\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgWriteKeys\"\n        },\n        \"max_write_keys\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxWriteKeys\"\n        },\n        \"avg_write_size\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgWriteSize\"\n        },\n        \"max_write_size\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxWriteSize\"\n        },\n        \"avg_prewrite_regions\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgPrewriteRegions\"\n        },\n        \"max_prewrite_regions\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxPrewriteRegions\"\n        },\n        \"avg_txn_retry\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgTxnRetry\"\n        },\n        \"max_txn_retry\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxTxnRetry\"\n        },\n        \"sum_backoff_times\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggSumBackoffTimes\"\n        },\n        \"avg_mem\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgMem\"\n        },\n        \"max_mem\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxMem\"\n        },\n        \"avg_disk\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgDisk\"\n        },\n        \"max_disk\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxDisk\"\n        },\n        \"avg_affected_rows\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgAffectedRows\"\n        },\n        \"first_seen\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggFirstSeen\"\n        },\n        \"last_seen\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggLastSeen\"\n        },\n        \"sample_user\": {\n          \"type\": \"string\",\n          \"title\": \"AggSampleUser\"\n        },\n        \"query_sample_text\": {\n          \"type\": \"string\",\n          \"title\": \"AggQuerySampleText\"\n        },\n        \"prev_sample_text\": {\n          \"type\": \"string\",\n          \"title\": \"AggPrevSampleText\"\n        },\n        \"schema_name\": {\n          \"type\": \"string\",\n          \"title\": \"AggSchemaName\"\n        },\n        \"table_names\": {\n          \"type\": \"string\",\n          \"title\": \"AggTableNames\"\n        },\n        \"index_names\": {\n          \"type\": \"string\",\n          \"title\": \"AggIndexNames\"\n        },\n        \"plan_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggPlanCount\"\n        },\n        \"plan\": {\n          \"type\": \"string\",\n          \"title\": \"AggPlan (deprecated)\"\n        },\n        \"binary_plan\": {\n          \"type\": \"string\",\n          \"title\": \"AggBinaryPlan\"\n        },\n        \"plan_digest\": {\n          \"type\": \"string\",\n          \"title\": \"AggPlanDigest\"\n        },\n        \"plan_hint\": {\n          \"type\": \"string\",\n          \"title\": \"AggPlanHint\"\n        },\n        \"max_rocksdb_delete_skipped_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxRocksdbDeleteSkippedCount\"\n        },\n        \"avg_rocksdb_delete_skipped_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgRocksdbDeleteSkippedCount\"\n        },\n        \"max_rocksdb_key_skipped_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxRocksdbKeySkippedCount\"\n        },\n        \"avg_rocksdb_key_skipped_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgRocksdbKeySkippedCount\"\n        },\n        \"max_rocksdb_block_cache_hit_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxRocksdbBlockCacheHitCount\"\n        },\n        \"avg_rocksdb_block_cache_hit_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgRocksdbBlockCacheHitCount\"\n        },\n        \"max_rocksdb_block_read_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxRocksdbBlockReadCount\"\n        },\n        \"avg_rocksdb_block_read_count\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgRocksdbBlockReadCount\"\n        },\n        \"max_rocksdb_block_read_byte\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggMaxRocksdbBlockReadByte\"\n        },\n        \"avg_rocksdb_block_read_byte\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AggAvgRocksdbBlockReadByte\"\n        },\n        \"related_schemas\": {\n          \"type\": \"string\",\n          \"title\": \"RelatedSchemas\"\n        },\n        \"plan_can_be_bound\": {\n          \"type\": \"boolean\",\n          \"title\": \"PlanCanBeBound\"\n        },\n        \"binary_plan_text\": {\n          \"type\": \"string\",\n          \"title\": \"BinaryPlanText\"\n        },\n        \"resource_group\": {\n          \"type\": \"string\",\n          \"title\": \"ResourceGroup\"\n        },\n        \"avg_ru\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AvgRu\"\n        },\n        \"max_ru\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"MaxRu\"\n        },\n        \"sum_ru\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"SumRu\"\n        },\n        \"avg_time_queued_by_rc\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"AvgTimeQueuedByRc\"\n        },\n        \"max_time_queued_by_rc\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"MaxTimeQueuedBNyRc\"\n        },\n        \"avg_tidb_cpu_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"TiDB CPU time\"\n        },\n        \"avg_tikv_cpu_time\": {\n          \"type\": \"number\",\n          \"format\": \"double\",\n          \"title\": \"TiKV CPU time\"\n        }\n      },\n      \"title\": \"Top SQL detail\"\n    },\n    \"v2TopSqlList\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"data\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TopSqlDetail\"\n          },\n          \"title\": \"List of top sql\"\n        },\n        \"nextPageToken\": {\n          \"type\": \"string\",\n          \"title\": \"Next page token\"\n        },\n        \"totalSize\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Total size\"\n        }\n      },\n      \"title\": \"Top SQL list\"\n    },\n    \"v2TopologySummaryResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"clusterNodeTopology\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2ClusterNodeTopology\"\n          },\n          \"title\": \"The cluster_topology list\"\n        },\n        \"clusterId\": {\n          \"type\": \"string\",\n          \"title\": \"The cluster_id\"\n        }\n      },\n      \"title\": \"TopologySummaryResponse\",\n      \"required\": [\"clusterNodeTopology\", \"clusterId\"]\n    },\n    \"v2TriggerTypeEnumData\": {\n      \"type\": \"string\",\n      \"enum\": [\"automatic\", \"manual\"],\n      \"default\": \"automatic\",\n      \"description\": \"- automatic: automatic\\n - manual: manual\",\n      \"title\": \"Data of TriggerTypeEnum\"\n    },\n    \"v2TypeEnumData\": {\n      \"type\": \"string\",\n      \"enum\": [\n        \"all\",\n        \"full_backup\",\n        \"log_backup\",\n        \"restore_by_file\",\n        \"restore_by_time\",\n        \"all_backup\",\n        \"all_restore\"\n      ],\n      \"default\": \"all\",\n      \"description\": \"- all: All\\n - full_backup: Full backup\\n - log_backup: Log backup\\n - restore_by_file: Restore by file\\n - restore_by_time: Restore by time\\n - all_backup: All backup\\n - all_restore: All restore\",\n      \"title\": \"Data of TypeEnum\"\n    },\n    \"v2UpdateAuditConfigsRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"enabled\": {\n          \"type\": \"boolean\",\n          \"title\": \"Whether auditing is enabled\"\n        },\n        \"retentionDays\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"Log retention period in days\"\n        }\n      },\n      \"title\": \"Update audit configuration request\"\n    },\n    \"v2UpdateParameterTemplateRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"parameterTemplate\": {\n          \"$ref\": \"#/definitions/v2ParameterTemplate\",\n          \"title\": \"The parameter template\"\n        },\n        \"templateParameterMappings\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2TemplateParameterMapping\"\n          },\n          \"title\": \"The mappings of the parameter template\"\n        },\n        \"templateId\": {\n          \"type\": \"string\",\n          \"format\": \"int64\",\n          \"title\": \"The ID of the parameter template\"\n        }\n      },\n      \"title\": \"UpdateParameterTemplateRequest represents an update parameter template request\",\n      \"required\": [\n        \"parameterTemplate\",\n        \"templateParameterMappings\",\n        \"templateId\"\n      ]\n    },\n    \"v2User\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"userId\": {\n          \"type\": \"string\",\n          \"description\": \"The unique user ID of the user.\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"description\": \"The full name of the user.\"\n        },\n        \"email\": {\n          \"type\": \"string\",\n          \"description\": \"The email address of the user.\"\n        },\n        \"note\": {\n          \"type\": \"string\",\n          \"description\": \"Additional notes about the user.\"\n        },\n        \"password\": {\n          \"type\": \"string\",\n          \"description\": \"The user's password (optional).\"\n        },\n        \"userType\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"description\": \"The type of the user (e.g., admin, regular user).\"\n        },\n        \"userTypeDesc\": {\n          \"type\": \"string\",\n          \"description\": \"A description of the user's type.\"\n        },\n        \"phone\": {\n          \"type\": \"string\",\n          \"description\": \"The user's phone number.\"\n        },\n        \"roles\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/v2UserRole\",\n            \"enum\": [\n              \"ADMIN\",\n              \"ALERT_MANAGER\",\n              \"ALERT_READER\",\n              \"BACKUP_MANAGER\",\n              \"BACKUP_READER\",\n              \"CLUSTER_MANAGER\",\n              \"CLUSTER_READER\",\n              \"HOST_MANAGER\",\n              \"HOST_READER\",\n              \"USER_MANAGER\",\n              \"AUDIT_MANAGER\",\n              \"SYSTEM_MANAGER\",\n              \"SYSTEM_READER\"\n            ]\n          },\n          \"description\": \"The roles assigned to the user.\",\n          \"title\": \"The role name\"\n        },\n        \"createTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"description\": \"The timestamp when the user was created.\",\n          \"readOnly\": true\n        },\n        \"updateTime\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\",\n          \"description\": \"The timestamp when the user was last updated.\",\n          \"readOnly\": true\n        }\n      },\n      \"description\": \"User represents a user resource containing detailed information about a user.\",\n      \"required\": [\"userId\", \"name\"]\n    },\n    \"v2UserProfile\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"userId\": {\n          \"type\": \"string\",\n          \"description\": \"The unique identifier of the user.\"\n        },\n        \"name\": {\n          \"type\": \"string\",\n          \"description\": \"The  name of the user.\"\n        },\n        \"email\": {\n          \"type\": \"string\",\n          \"description\": \"The email address of the user.\"\n        },\n        \"note\": {\n          \"type\": \"string\",\n          \"description\": \"The note of the user.\"\n        },\n        \"phone\": {\n          \"type\": \"string\",\n          \"description\": \"The phone of the user.\"\n        }\n      },\n      \"description\": \"UserProfile represents the profile information of the authenticated user.\",\n      \"required\": [\"userId\", \"name\", \"email\"]\n    },\n    \"v2UserRole\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"roleName\": {\n          \"type\": \"string\",\n          \"title\": \"The name of the role\"\n        },\n        \"roleId\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\",\n          \"title\": \"The id of the role\"\n        }\n      },\n      \"title\": \"the role of user\",\n      \"required\": [\"roleId\"]\n    },\n    \"v2ValidateConnectionRequest\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"credentialId\": {\n          \"type\": \"string\",\n          \"title\": \"the credential id of the credential\"\n        }\n      },\n      \"title\": \"ValidateConnection Request\",\n      \"required\": [\"credentialId\"]\n    },\n    \"v2ValidateConnectionResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"connectionResult\": {\n          \"type\": \"string\",\n          \"title\": \"the connection result of the validate\"\n        },\n        \"inaccessibleHosts\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"the hosts which not accessible\"\n        }\n      },\n      \"title\": \"ValidateConnection Response\"\n    },\n    \"v2ValidateSessionResponse\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"userId\": {\n          \"type\": \"string\",\n          \"title\": \"The id of the user\"\n        }\n      },\n      \"title\": \"The Response of ValidateSession\",\n      \"required\": [\"userId\"]\n    },\n    \"v2Variable\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"name\": {\n          \"type\": \"string\",\n          \"title\": \"Name is the name of the parameter\"\n        },\n        \"scope\": {\n          \"type\": \"string\",\n          \"title\": \"Scope is the scope of the parameter\"\n        },\n        \"currentValue\": {\n          \"type\": \"string\",\n          \"title\": \"CurrentValue is the current value of the parameter\"\n        },\n        \"defaultValue\": {\n          \"type\": \"string\",\n          \"title\": \"DefaultValue is the default value of the parameter\"\n        },\n        \"type\": {\n          \"$ref\": \"#/definitions/v2ParamTypeEnumData\",\n          \"title\": \"Type is the type of the parameter\"\n        },\n        \"range\": {\n          \"type\": \"string\",\n          \"title\": \"Range is the range of the parameter\"\n        }\n      },\n      \"title\": \"Variable is the variable of the parameter\",\n      \"required\": [\n        \"name\",\n        \"scope\",\n        \"currentValue\",\n        \"defaultValue\",\n        \"type\",\n        \"range\"\n      ]\n    },\n    \"v2WebhookConfig\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"url\": {\n          \"type\": \"string\",\n          \"title\": \"Webhook URL\"\n        }\n      },\n      \"title\": \"WebhookConfig represents webhook channel configuration\",\n      \"required\": [\"url\"]\n    }\n  }\n}\n"
  },
  {
    "path": "ui-v2/eslint.config.js",
    "content": "import js from \"@eslint/js\"\nimport importPlugin from \"eslint-plugin-import\"\nimport reactHooks from \"eslint-plugin-react-hooks\"\nimport reactRefresh from \"eslint-plugin-react-refresh\"\nimport globals from \"globals\"\nimport tseslint from \"typescript-eslint\"\n\nexport default tseslint.config(\n  { ignores: [\"**/dist/**\", \"packages/api/server/src/**\"] },\n  {\n    files: [\"**/*.{ts,tsx}\", \"eslint.config.js\"],\n    extends: [js.configs.recommended, ...tseslint.configs.recommended],\n    languageOptions: {\n      ecmaVersion: 2020,\n      globals: globals.browser,\n    },\n    plugins: {\n      import: importPlugin,\n      \"react-hooks\": reactHooks,\n      \"react-refresh\": reactRefresh,\n    },\n    rules: {\n      ...reactHooks.configs.recommended.rules,\n      \"react-refresh/only-export-components\": [\n        \"warn\",\n        { allowConstantExport: true },\n      ],\n      // `import/order` can't sort the multiple imported members from a lib, but `sort-imports` can\n      // so we use them both\n      \"sort-imports\": [\n        \"error\",\n        {\n          ignoreCase: false,\n          ignoreDeclarationSort: true,\n          ignoreMemberSort: false,\n          allowSeparatedGroups: true,\n        },\n      ],\n      // prevents you from having duplicate import statements from the same module\n      \"import/no-duplicates\": \"error\",\n      \"import/order\": [\n        \"error\",\n        {\n          groups: [\n            \"builtin\",\n            \"external\",\n            \"internal\",\n            \"parent\",\n            \"sibling\",\n            \"index\",\n            \"object\",\n          ],\n          pathGroups: [\n            {\n              pattern: \"*.{css,svg}\",\n              group: \"index\",\n              position: \"after\",\n            },\n          ],\n          pathGroupsExcludedImportTypes: [\"builtin\"],\n          \"newlines-between\": \"always\",\n          alphabetize: {\n            order: \"asc\",\n            caseInsensitive: true,\n          },\n        },\n      ],\n      \"no-restricted-imports\": [\n        \"error\",\n        {\n          paths: [\n            {\n              name: \"lodash\",\n              message: \"Please use lodash-es instead.\",\n            },\n          ],\n        },\n      ],\n      \"@typescript-eslint/no-unused-vars\": [\n        \"error\",\n        {\n          args: \"all\",\n          argsIgnorePattern: \"^_\",\n          caughtErrors: \"all\",\n          caughtErrorsIgnorePattern: \"^_\",\n          destructuredArrayIgnorePattern: \"^_\",\n          varsIgnorePattern: \"^_\",\n          ignoreRestSiblings: true,\n        },\n      ],\n    },\n  },\n)\n"
  },
  {
    "path": "ui-v2/orval.config.ts",
    "content": "import { defineConfig } from \"orval\"\n\nexport default defineConfig({\n  azores: {\n    input: \"./api-specs/azores.json\",\n    output: {\n      target: \"packages/api/client/src/azores/index.ts\",\n      schemas: \"packages/api/client/src/azores/models\",\n      client: \"react-query\",\n      mode: \"tags-split\",\n      clean: true,\n      prettier: true,\n      override: {\n        mutator: {\n          path: \"packages/api/client/src/http/client.ts\",\n          name: \"httpClient\",\n        },\n      },\n    },\n  },\n  azoresHono: {\n    input: \"./api-specs/azores.json\",\n    output: {\n      mode: \"split\",\n      client: \"hono\",\n      target: \"packages/api/server/src/azores/index.ts\",\n      override: {\n        hono: {\n          handlers: \"packages/api/server/src/azores/handlers\",\n        },\n      },\n    },\n  },\n})\n"
  },
  {
    "path": "ui-v2/package.json",
    "content": "{\n  \"name\": \"tidb-dashboard-ui-v2\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"fmt-check\": \"prettier --check .\",\n    \"fmt-fix\": \"prettier --write .\",\n    \"lint\": \"eslint .\",\n    \"prepare\": \"cd .. && husky ui-v2/.husky\",\n    \"dev\": \"pnpm -r --parallel dev\",\n    \"build\": \"pnpm -r build\",\n    \"dev:portals:test\": \"pnpm -r --parallel --filter test-tidb-dashboard-ui-lib... dev\",\n    \"gen:locales\": \"tsx scripts/gen-locales.ts\",\n    \"gen:api\": \"orval\"\n  },\n  \"devDependencies\": {\n    \"@changesets/cli\": \"^2.27.9\",\n    \"@eslint/js\": \"^9.11.1\",\n    \"concurrently\": \"^9.0.1\",\n    \"eslint\": \"^9.11.1\",\n    \"eslint-plugin-import\": \"^2.31.0\",\n    \"eslint-plugin-react-hooks\": \"^5.1.0-rc.0\",\n    \"eslint-plugin-react-refresh\": \"^0.4.12\",\n    \"glob\": \"^11.0.0\",\n    \"globals\": \"^15.9.0\",\n    \"gogocode\": \"^1.0.55\",\n    \"orval\": \"^7.3.0\",\n    \"husky\": \"^9.1.6\",\n    \"lint-staged\": \"^15.2.10\",\n    \"prettier\": \"^3.3.3\",\n    \"tsx\": \"^4.19.2\",\n    \"typescript\": \"^5.5.3\",\n    \"typescript-eslint\": \"^8.7.0\"\n  },\n  \"lint-staged\": {\n    \"*.+(ts|tsx|js)\": [\n      \"eslint --fix\",\n      \"prettier --write\"\n    ],\n    \"*.+(json|css|md|html)\": \"prettier --write\"\n  },\n  \"engines\": {\n    \"node\": \">=22.0.0\"\n  },\n  \"packageManager\": \"pnpm@9.12.2\"\n}\n"
  },
  {
    "path": "ui-v2/packages/api/client/.gitignore",
    "content": "src/azores\n"
  },
  {
    "path": "ui-v2/packages/api/client/CHANGELOG.md",
    "content": "# @pingcap-incubator/tidb-dashboard-lib-api-client\n\n## 0.13.0\n\n### Minor Changes\n\n- bump version\n\n## 0.12.0\n\n### Minor Changes\n\n- refactor: re-exports lib-uitls/lib-charts/lib-primitive-ui/lib-biz-ui from lib-apps\n\n## 0.11.0\n\n### Minor Changes\n\n- fix time-range-picker, refine cols-multi-select\n\n## 0.10.0\n\n### Minor Changes\n\n- i18n for slow query and statement app\n\n## 0.9.0\n\n### Minor Changes\n\n- upgrade uikit\n\n## 0.8.0\n\n### Minor Changes\n\n- support advanced filters for diagnosis\n\n## 0.7.0\n\n### Minor Changes\n\n- refine pagination and sort url state\n\n## 0.6.0\n\n### Minor Changes\n\n- refine\n\n## 0.5.0\n\n### Minor Changes\n\n- refine slow-query and statement apps\n"
  },
  {
    "path": "ui-v2/packages/api/client/package.json",
    "content": "{\n  \"name\": \"@pingcap-incubator/tidb-dashboard-lib-api-client\",\n  \"private\": true,\n  \"version\": \"0.13.0\",\n  \"description\": \"\",\n  \"type\": \"module\",\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"files\": [\n    \"dist\",\n    \"README.md\",\n    \"CHANGELOG.md\"\n  ],\n  \"scripts\": {\n    \"tsc:watch\": \"tsc --watch\",\n    \"rollup:watch\": \"rollup -c --watch\",\n    \"dev\": \"concurrently --kill-others \\\"pnpm tsc:watch\\\" \\\"pnpm rollup:watch\\\"\",\n    \"build\": \"tsc && rollup -c\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@rollup/plugin-typescript\": \"^12.1.1\",\n    \"@tanstack/react-query\": \"^5.59.16\",\n    \"axios\": \"^1.7.9\",\n    \"rollup\": \"^4.24.0\",\n    \"tslib\": \"^2.8.0\"\n  },\n  \"peerDependencies\": {\n    \"@tanstack/react-query\": \"^5.59.16\",\n    \"axios\": \"^1.7.9\"\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/api/client/rollup.config.js",
    "content": "import typescript from \"@rollup/plugin-typescript\"\n\nexport default {\n  input: \"src/index.ts\",\n  output: {\n    dir: \"dist\",\n    format: \"es\",\n  },\n  plugins: [typescript()],\n}\n"
  },
  {
    "path": "ui-v2/packages/api/client/src/http/client.ts",
    "content": "import axios, { AxiosRequestConfig } from \"axios\"\n\ndeclare module \"axios\" {\n  interface AxiosRequestConfig {\n    skipGlobalErrorHandling?: boolean\n  }\n}\n\nconst DEFAULT_TIMEOUT = 30 * 1000\nexport const axiosClient = axios.create({\n  baseURL: \"\",\n  timeout: DEFAULT_TIMEOUT,\n})\n\n/**\n * Add a second `options` argument to\n * pass extra options to each generated query\n */\nexport const httpClient = <T>(\n  config: AxiosRequestConfig,\n  options?: AxiosRequestConfig,\n): Promise<T> => {\n  const promise = axiosClient({\n    ...config,\n    ...options,\n  }).then(({ data }) => data)\n\n  return promise\n}\n"
  },
  {
    "path": "ui-v2/packages/api/client/src/index.ts",
    "content": "export * from \"./http/client\"\n\nexport * from \"./azores/models\"\nexport * from \"./azores/metrics-service/metrics-service\"\nexport * from \"./azores/diagnosis-service/diagnosis-service\"\n"
  },
  {
    "path": "ui-v2/packages/api/client/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.app.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\"\n  },\n  \"include\": [\"./src\"]\n}\n"
  },
  {
    "path": "ui-v2/packages/api/server/package.json",
    "content": "{\n  \"name\": \"tidb-dashboard-lib-api-server\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"wrangler dev\",\n    \"deploy\": \"wrangler deploy --minify\"\n  },\n  \"dependencies\": {\n    \"@hono/zod-validator\": \"^0.4.2\",\n    \"hono\": \"^4.6.14\",\n    \"zod\": \"^3.24.1\"\n  },\n  \"devDependencies\": {\n    \"@cloudflare/workers-types\": \"^4.20241112.0\",\n    \"wrangler\": \"^3.88.0\"\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/apiKeyServiceCreateApiKey.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ApiKeyServiceCreateApiKeyContext } from '../index.context';\nimport { apiKeyServiceCreateApiKeyBody,\napiKeyServiceCreateApiKeyResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const apiKeyServiceCreateApiKeyHandlers = factory.createHandlers(\nzValidator('json', apiKeyServiceCreateApiKeyBody),\nzValidator('response', apiKeyServiceCreateApiKeyResponse),\nasync (c: ApiKeyServiceCreateApiKeyContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/apiKeyServiceDeleteApiKey.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ApiKeyServiceDeleteApiKeyContext } from '../index.context';\nimport { apiKeyServiceDeleteApiKeyParams,\napiKeyServiceDeleteApiKeyResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const apiKeyServiceDeleteApiKeyHandlers = factory.createHandlers(\nzValidator('param', apiKeyServiceDeleteApiKeyParams),\nzValidator('response', apiKeyServiceDeleteApiKeyResponse),\nasync (c: ApiKeyServiceDeleteApiKeyContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/apiKeyServiceGetApiKey.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ApiKeyServiceGetApiKeyContext } from '../index.context';\nimport { apiKeyServiceGetApiKeyParams,\napiKeyServiceGetApiKeyResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const apiKeyServiceGetApiKeyHandlers = factory.createHandlers(\nzValidator('param', apiKeyServiceGetApiKeyParams),\nzValidator('response', apiKeyServiceGetApiKeyResponse),\nasync (c: ApiKeyServiceGetApiKeyContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/apiKeyServiceGetTemErrorDetail.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ApiKeyServiceGetTemErrorDetailContext } from '../index.context';\nimport { apiKeyServiceGetTemErrorDetailResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const apiKeyServiceGetTemErrorDetailHandlers = factory.createHandlers(\nzValidator('response', apiKeyServiceGetTemErrorDetailResponse),\nasync (c: ApiKeyServiceGetTemErrorDetailContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/apiKeyServiceListApiKeys.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ApiKeyServiceListApiKeysContext } from '../index.context';\nimport { apiKeyServiceListApiKeysQueryParams,\napiKeyServiceListApiKeysResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const apiKeyServiceListApiKeysHandlers = factory.createHandlers(\nzValidator('query', apiKeyServiceListApiKeysQueryParams),\nzValidator('response', apiKeyServiceListApiKeysResponse),\nasync (c: ApiKeyServiceListApiKeysContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/apiKeyServiceResetSecretKey.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ApiKeyServiceResetSecretKeyContext } from '../index.context';\nimport { apiKeyServiceResetSecretKeyParams,\napiKeyServiceResetSecretKeyResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const apiKeyServiceResetSecretKeyHandlers = factory.createHandlers(\nzValidator('param', apiKeyServiceResetSecretKeyParams),\nzValidator('response', apiKeyServiceResetSecretKeyResponse),\nasync (c: ApiKeyServiceResetSecretKeyContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/apiKeyServiceUpdateApiKey.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ApiKeyServiceUpdateApiKeyContext } from '../index.context';\nimport { apiKeyServiceUpdateApiKeyParams,\napiKeyServiceUpdateApiKeyBody,\napiKeyServiceUpdateApiKeyResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const apiKeyServiceUpdateApiKeyHandlers = factory.createHandlers(\nzValidator('param', apiKeyServiceUpdateApiKeyParams),\nzValidator('json', apiKeyServiceUpdateApiKeyBody),\nzValidator('response', apiKeyServiceUpdateApiKeyResponse),\nasync (c: ApiKeyServiceUpdateApiKeyContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/clusterBRServiceCreateBackupTask.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ClusterBRServiceCreateBackupTaskContext } from '../index.context';\nimport { clusterBRServiceCreateBackupTaskParams,\nclusterBRServiceCreateBackupTaskBody,\nclusterBRServiceCreateBackupTaskResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const clusterBRServiceCreateBackupTaskHandlers = factory.createHandlers(\nzValidator('param', clusterBRServiceCreateBackupTaskParams),\nzValidator('json', clusterBRServiceCreateBackupTaskBody),\nzValidator('response', clusterBRServiceCreateBackupTaskResponse),\nasync (c: ClusterBRServiceCreateBackupTaskContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/clusterBRServiceCreateRestoreTask.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ClusterBRServiceCreateRestoreTaskContext } from '../index.context';\nimport { clusterBRServiceCreateRestoreTaskParams,\nclusterBRServiceCreateRestoreTaskBody,\nclusterBRServiceCreateRestoreTaskResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const clusterBRServiceCreateRestoreTaskHandlers = factory.createHandlers(\nzValidator('param', clusterBRServiceCreateRestoreTaskParams),\nzValidator('json', clusterBRServiceCreateRestoreTaskBody),\nzValidator('response', clusterBRServiceCreateRestoreTaskResponse),\nasync (c: ClusterBRServiceCreateRestoreTaskContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/clusterBRServiceDetectCluster.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ClusterBRServiceDetectClusterContext } from '../index.context';\nimport { clusterBRServiceDetectClusterParams,\nclusterBRServiceDetectClusterResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const clusterBRServiceDetectClusterHandlers = factory.createHandlers(\nzValidator('param', clusterBRServiceDetectClusterParams),\nzValidator('response', clusterBRServiceDetectClusterResponse),\nasync (c: ClusterBRServiceDetectClusterContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/clusterBRServiceGetClusterBackupPolicy.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ClusterBRServiceGetClusterBackupPolicyContext } from '../index.context';\nimport { clusterBRServiceGetClusterBackupPolicyParams,\nclusterBRServiceGetClusterBackupPolicyResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const clusterBRServiceGetClusterBackupPolicyHandlers = factory.createHandlers(\nzValidator('param', clusterBRServiceGetClusterBackupPolicyParams),\nzValidator('response', clusterBRServiceGetClusterBackupPolicyResponse),\nasync (c: ClusterBRServiceGetClusterBackupPolicyContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/clusterBRServiceListClusterBRTasks.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ClusterBRServiceListClusterBRTasksContext } from '../index.context';\nimport { clusterBRServiceListClusterBRTasksParams,\nclusterBRServiceListClusterBRTasksQueryParams,\nclusterBRServiceListClusterBRTasksResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const clusterBRServiceListClusterBRTasksHandlers = factory.createHandlers(\nzValidator('param', clusterBRServiceListClusterBRTasksParams),\nzValidator('query', clusterBRServiceListClusterBRTasksQueryParams),\nzValidator('response', clusterBRServiceListClusterBRTasksResponse),\nasync (c: ClusterBRServiceListClusterBRTasksContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/clusterBRServiceListClusterBackupRecords.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ClusterBRServiceListClusterBackupRecordsContext } from '../index.context';\nimport { clusterBRServiceListClusterBackupRecordsParams,\nclusterBRServiceListClusterBackupRecordsQueryParams,\nclusterBRServiceListClusterBackupRecordsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const clusterBRServiceListClusterBackupRecordsHandlers = factory.createHandlers(\nzValidator('param', clusterBRServiceListClusterBackupRecordsParams),\nzValidator('query', clusterBRServiceListClusterBackupRecordsQueryParams),\nzValidator('response', clusterBRServiceListClusterBackupRecordsResponse),\nasync (c: ClusterBRServiceListClusterBackupRecordsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/clusterServiceDeleteProcess.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ClusterServiceDeleteProcessContext } from '../index.context';\nimport { clusterServiceDeleteProcessParams,\nclusterServiceDeleteProcessResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const clusterServiceDeleteProcessHandlers = factory.createHandlers(\nzValidator('param', clusterServiceDeleteProcessParams),\nzValidator('response', clusterServiceDeleteProcessResponse),\nasync (c: ClusterServiceDeleteProcessContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/clusterServiceGetProcessList.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { ClusterServiceGetProcessListContext } from '../index.context';\nimport { clusterServiceGetProcessListParams,\nclusterServiceGetProcessListResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const clusterServiceGetProcessListHandlers = factory.createHandlers(\nzValidator('param', clusterServiceGetProcessListParams),\nzValidator('response', clusterServiceGetProcessListResponse),\nasync (c: ClusterServiceGetProcessListContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/credentialServiceCreateCredential.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { CredentialServiceCreateCredentialContext } from '../index.context';\nimport { credentialServiceCreateCredentialBody,\ncredentialServiceCreateCredentialResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const credentialServiceCreateCredentialHandlers = factory.createHandlers(\nzValidator('json', credentialServiceCreateCredentialBody),\nzValidator('response', credentialServiceCreateCredentialResponse),\nasync (c: CredentialServiceCreateCredentialContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/credentialServiceDeleteCredential.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { CredentialServiceDeleteCredentialContext } from '../index.context';\nimport { credentialServiceDeleteCredentialParams,\ncredentialServiceDeleteCredentialResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const credentialServiceDeleteCredentialHandlers = factory.createHandlers(\nzValidator('param', credentialServiceDeleteCredentialParams),\nzValidator('response', credentialServiceDeleteCredentialResponse),\nasync (c: CredentialServiceDeleteCredentialContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/credentialServiceDownloadRSAKey.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { CredentialServiceDownloadRSAKeyContext } from '../index.context';\nimport { credentialServiceDownloadRSAKeyBody,\ncredentialServiceDownloadRSAKeyResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const credentialServiceDownloadRSAKeyHandlers = factory.createHandlers(\nzValidator('json', credentialServiceDownloadRSAKeyBody),\nzValidator('response', credentialServiceDownloadRSAKeyResponse),\nasync (c: CredentialServiceDownloadRSAKeyContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/credentialServiceGenerateRSAKey.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { CredentialServiceGenerateRSAKeyContext } from '../index.context';\nimport { credentialServiceGenerateRSAKeyBody,\ncredentialServiceGenerateRSAKeyResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const credentialServiceGenerateRSAKeyHandlers = factory.createHandlers(\nzValidator('json', credentialServiceGenerateRSAKeyBody),\nzValidator('response', credentialServiceGenerateRSAKeyResponse),\nasync (c: CredentialServiceGenerateRSAKeyContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/credentialServiceGetCredential.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { CredentialServiceGetCredentialContext } from '../index.context';\nimport { credentialServiceGetCredentialParams,\ncredentialServiceGetCredentialResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const credentialServiceGetCredentialHandlers = factory.createHandlers(\nzValidator('param', credentialServiceGetCredentialParams),\nzValidator('response', credentialServiceGetCredentialResponse),\nasync (c: CredentialServiceGetCredentialContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/credentialServiceListCredentials.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { CredentialServiceListCredentialsContext } from '../index.context';\nimport { credentialServiceListCredentialsQueryParams,\ncredentialServiceListCredentialsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const credentialServiceListCredentialsHandlers = factory.createHandlers(\nzValidator('query', credentialServiceListCredentialsQueryParams),\nzValidator('response', credentialServiceListCredentialsResponse),\nasync (c: CredentialServiceListCredentialsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/credentialServiceUpdateCredential.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { CredentialServiceUpdateCredentialContext } from '../index.context';\nimport { credentialServiceUpdateCredentialParams,\ncredentialServiceUpdateCredentialBody,\ncredentialServiceUpdateCredentialResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const credentialServiceUpdateCredentialHandlers = factory.createHandlers(\nzValidator('param', credentialServiceUpdateCredentialParams),\nzValidator('json', credentialServiceUpdateCredentialBody),\nzValidator('response', credentialServiceUpdateCredentialResponse),\nasync (c: CredentialServiceUpdateCredentialContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/credentialServiceValidateConnection.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { CredentialServiceValidateConnectionContext } from '../index.context';\nimport { credentialServiceValidateConnectionBody,\ncredentialServiceValidateConnectionResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const credentialServiceValidateConnectionHandlers = factory.createHandlers(\nzValidator('json', credentialServiceValidateConnectionBody),\nzValidator('response', credentialServiceValidateConnectionResponse),\nasync (c: CredentialServiceValidateConnectionContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceAddSqlLimit.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceAddSqlLimitContext } from '../index.context';\nimport { diagnosisServiceAddSqlLimitParams,\ndiagnosisServiceAddSqlLimitBody,\ndiagnosisServiceAddSqlLimitResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceAddSqlLimitHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceAddSqlLimitParams),\nzValidator('json', diagnosisServiceAddSqlLimitBody),\nzValidator('response', diagnosisServiceAddSqlLimitResponse),\nasync (c: DiagnosisServiceAddSqlLimitContext) => {\n    return c.json({})\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceBindSqlPlan.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceBindSqlPlanContext } from '../index.context';\nimport { diagnosisServiceBindSqlPlanParams,\ndiagnosisServiceBindSqlPlanResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceBindSqlPlanHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceBindSqlPlanParams),\nzValidator('response', diagnosisServiceBindSqlPlanResponse),\nasync (c: DiagnosisServiceBindSqlPlanContext) => {\n    return c.json({})\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceCheckSqlLimitSupport.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceCheckSqlLimitSupportContext } from '../index.context';\nimport { diagnosisServiceCheckSqlLimitSupportParams,\ndiagnosisServiceCheckSqlLimitSupportResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceCheckSqlLimitSupportHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceCheckSqlLimitSupportParams),\nzValidator('response', diagnosisServiceCheckSqlLimitSupportResponse),\nasync (c: DiagnosisServiceCheckSqlLimitSupportContext) => {\n    return c.json({isSupport: true})\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceCheckSqlPlanSupport.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceCheckSqlPlanSupportContext } from '../index.context';\nimport { diagnosisServiceCheckSqlPlanSupportParams,\ndiagnosisServiceCheckSqlPlanSupportResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceCheckSqlPlanSupportHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceCheckSqlPlanSupportParams),\nzValidator('response', diagnosisServiceCheckSqlPlanSupportResponse),\nasync (c: DiagnosisServiceCheckSqlPlanSupportContext) => {\n    return c.json({isSupport: true})\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceDownloadSlowQueryList.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceDownloadSlowQueryListContext } from '../index.context';\nimport { diagnosisServiceDownloadSlowQueryListParams,\ndiagnosisServiceDownloadSlowQueryListQueryParams,\ndiagnosisServiceDownloadSlowQueryListResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceDownloadSlowQueryListHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceDownloadSlowQueryListParams),\nzValidator('query', diagnosisServiceDownloadSlowQueryListQueryParams),\nzValidator('response', diagnosisServiceDownloadSlowQueryListResponse),\nasync (c: DiagnosisServiceDownloadSlowQueryListContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetResourceGroupList.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetResourceGroupListContext } from '../index.context';\nimport { diagnosisServiceGetResourceGroupListParams,\ndiagnosisServiceGetResourceGroupListResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetResourceGroupListHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceGetResourceGroupListParams),\nzValidator('response', diagnosisServiceGetResourceGroupListResponse),\nasync (c: DiagnosisServiceGetResourceGroupListContext) => {\n\n    return c.json({\n      resourceGroups: [\n        {name: \"default\"},\n        {name: \"ru1\"},\n        {name: \"ru2\"},\n      ]\n    })\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetSlowQueryAvailableAdvancedFilterInfo.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetSlowQueryAvailableAdvancedFilterInfoContext } from '../index.context';\nimport { diagnosisServiceGetSlowQueryAvailableAdvancedFilterInfoParams,\ndiagnosisServiceGetSlowQueryAvailableAdvancedFilterInfoResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetSlowQueryAvailableAdvancedFilterInfoHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceGetSlowQueryAvailableAdvancedFilterInfoParams),\nzValidator('response', diagnosisServiceGetSlowQueryAvailableAdvancedFilterInfoResponse),\nasync (c: DiagnosisServiceGetSlowQueryAvailableAdvancedFilterInfoContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetSlowQueryAvailableAdvancedFilters.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetSlowQueryAvailableAdvancedFiltersContext } from '../index.context';\nimport { diagnosisServiceGetSlowQueryAvailableAdvancedFiltersParams,\ndiagnosisServiceGetSlowQueryAvailableAdvancedFiltersResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetSlowQueryAvailableAdvancedFiltersHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceGetSlowQueryAvailableAdvancedFiltersParams),\nzValidator('response', diagnosisServiceGetSlowQueryAvailableAdvancedFiltersResponse),\nasync (c: DiagnosisServiceGetSlowQueryAvailableAdvancedFiltersContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetSlowQueryAvailableFields.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetSlowQueryAvailableFieldsContext } from '../index.context';\nimport { diagnosisServiceGetSlowQueryAvailableFieldsParams,\ndiagnosisServiceGetSlowQueryAvailableFieldsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetSlowQueryAvailableFieldsHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceGetSlowQueryAvailableFieldsParams),\nzValidator('response', diagnosisServiceGetSlowQueryAvailableFieldsResponse),\nasync (c: DiagnosisServiceGetSlowQueryAvailableFieldsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetSlowQueryDetail.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetSlowQueryDetailContext } from '../index.context';\nimport { diagnosisServiceGetSlowQueryDetailParams,\ndiagnosisServiceGetSlowQueryDetailQueryParams,\ndiagnosisServiceGetSlowQueryDetailResponse } from '../index.zod';\n\nimport slowQueryDetailData from '../sample-res/slow-query-detail.json'\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetSlowQueryDetailHandlers = factory.createHandlers(\n// zValidator('param', diagnosisServiceGetSlowQueryDetailParams),\n// zValidator('query', diagnosisServiceGetSlowQueryDetailQueryParams),\n// zValidator('response', diagnosisServiceGetSlowQueryDetailResponse),\nasync (c: DiagnosisServiceGetSlowQueryDetailContext) => {\n  return c.json(slowQueryDetailData)\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetSlowQueryList.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetSlowQueryListContext } from '../index.context';\nimport { diagnosisServiceGetSlowQueryListParams,\ndiagnosisServiceGetSlowQueryListQueryParams,\ndiagnosisServiceGetSlowQueryListResponse } from '../index.zod';\n\nimport slowQueryListData from '../sample-res/slow-query-list.json'\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetSlowQueryListHandlers = factory.createHandlers(\n// zValidator('param', diagnosisServiceGetSlowQueryListParams),\n// zValidator('query', diagnosisServiceGetSlowQueryListQueryParams),\nzValidator('response', diagnosisServiceGetSlowQueryListResponse),\nasync (c: DiagnosisServiceGetSlowQueryListContext) => {\n    return c.json(slowQueryListData)\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetSqlLimitList.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetSqlLimitListContext } from '../index.context';\nimport {\n  diagnosisServiceGetSqlLimitListParams,\n  diagnosisServiceGetSqlLimitListQueryParams,\n  diagnosisServiceGetSqlLimitListResponse\n} from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetSqlLimitListHandlers = factory.createHandlers(\n  zValidator('param', diagnosisServiceGetSqlLimitListParams),\n  zValidator('query', diagnosisServiceGetSqlLimitListQueryParams),\n  zValidator('response', diagnosisServiceGetSqlLimitListResponse),\n  async (c: DiagnosisServiceGetSqlLimitListContext) => {\n    return c.json({\n      data: [\n        {\n          resourceGroupName: \"default\",\n          action: \"DRYRUN\",\n        },\n      ]\n    })\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetSqlPlanBindingList.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetSqlPlanBindingListContext } from '../index.context';\nimport {\n  diagnosisServiceGetSqlPlanBindingListParams,\n  diagnosisServiceGetSqlPlanBindingListQueryParams,\n  diagnosisServiceGetSqlPlanBindingListResponse\n} from '../index.zod';\n\nimport plansListData from '../sample-res/statement-plans-list.json'\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetSqlPlanBindingListHandlers = factory.createHandlers(\n  zValidator('param', diagnosisServiceGetSqlPlanBindingListParams),\n  zValidator('query', diagnosisServiceGetSqlPlanBindingListQueryParams),\n  zValidator('response', diagnosisServiceGetSqlPlanBindingListResponse),\n  async (c: DiagnosisServiceGetSqlPlanBindingListContext) => {\n\n    const plansDigest = plansListData.data.map((plan) => {\n      return {\n        planDigest: plan.plan_digest\n      }\n    })\n    return c.json({\n      data: plansDigest\n    })\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetSqlPlanList.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetSqlPlanListContext } from '../index.context';\nimport { diagnosisServiceGetSqlPlanListParams,\ndiagnosisServiceGetSqlPlanListQueryParams,\ndiagnosisServiceGetSqlPlanListResponse } from '../index.zod';\n\nimport statementPlansListData from '../sample-res/statement-plans-list.json'\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetSqlPlanListHandlers = factory.createHandlers(\n// zValidator('param', diagnosisServiceGetSqlPlanListParams),\n// zValidator('query', diagnosisServiceGetSqlPlanListQueryParams),\nzValidator('response', diagnosisServiceGetSqlPlanListResponse),\nasync (c: DiagnosisServiceGetSqlPlanListContext) => {\n    return c.json(statementPlansListData)\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetTopSqlAvailableAdvancedFilterInfo.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetTopSqlAvailableAdvancedFilterInfoContext } from '../index.context';\nimport { diagnosisServiceGetTopSqlAvailableAdvancedFilterInfoParams,\ndiagnosisServiceGetTopSqlAvailableAdvancedFilterInfoResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetTopSqlAvailableAdvancedFilterInfoHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceGetTopSqlAvailableAdvancedFilterInfoParams),\nzValidator('response', diagnosisServiceGetTopSqlAvailableAdvancedFilterInfoResponse),\nasync (c: DiagnosisServiceGetTopSqlAvailableAdvancedFilterInfoContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetTopSqlAvailableAdvancedFilters.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetTopSqlAvailableAdvancedFiltersContext } from '../index.context';\nimport { diagnosisServiceGetTopSqlAvailableAdvancedFiltersParams,\ndiagnosisServiceGetTopSqlAvailableAdvancedFiltersResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetTopSqlAvailableAdvancedFiltersHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceGetTopSqlAvailableAdvancedFiltersParams),\nzValidator('response', diagnosisServiceGetTopSqlAvailableAdvancedFiltersResponse),\nasync (c: DiagnosisServiceGetTopSqlAvailableAdvancedFiltersContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetTopSqlAvailableFields.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetTopSqlAvailableFieldsContext } from '../index.context';\nimport { diagnosisServiceGetTopSqlAvailableFieldsParams,\ndiagnosisServiceGetTopSqlAvailableFieldsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetTopSqlAvailableFieldsHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceGetTopSqlAvailableFieldsParams),\nzValidator('response', diagnosisServiceGetTopSqlAvailableFieldsResponse),\nasync (c: DiagnosisServiceGetTopSqlAvailableFieldsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetTopSqlConfigs.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetTopSqlConfigsContext } from '../index.context';\nimport { diagnosisServiceGetTopSqlConfigsParams,\ndiagnosisServiceGetTopSqlConfigsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetTopSqlConfigsHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceGetTopSqlConfigsParams),\nzValidator('response', diagnosisServiceGetTopSqlConfigsResponse),\nasync (c: DiagnosisServiceGetTopSqlConfigsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetTopSqlDetail.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetTopSqlDetailContext } from '../index.context';\nimport { diagnosisServiceGetTopSqlDetailParams,\ndiagnosisServiceGetTopSqlDetailQueryParams,\ndiagnosisServiceGetTopSqlDetailResponse } from '../index.zod';\n\nimport statementDetailData from '../sample-res/statement-plans-detail.json'\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetTopSqlDetailHandlers = factory.createHandlers(\n// zValidator('param', diagnosisServiceGetTopSqlDetailParams),\n// zValidator('query', diagnosisServiceGetTopSqlDetailQueryParams),\nzValidator('response', diagnosisServiceGetTopSqlDetailResponse),\nasync (c: DiagnosisServiceGetTopSqlDetailContext) => {\n    return c.json(statementDetailData)\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceGetTopSqlList.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceGetTopSqlListContext } from '../index.context';\nimport { diagnosisServiceGetTopSqlListParams,\ndiagnosisServiceGetTopSqlListQueryParams,\ndiagnosisServiceGetTopSqlListResponse } from '../index.zod';\n\nimport statementListData from '../sample-res/statement-list.json'\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceGetTopSqlListHandlers = factory.createHandlers(\n// zValidator('param', diagnosisServiceGetTopSqlListParams),\n// zValidator('query', diagnosisServiceGetTopSqlListQueryParams),\nzValidator('response', diagnosisServiceGetTopSqlListResponse),\nasync (c: DiagnosisServiceGetTopSqlListContext) => {\n    return c.json(statementListData)\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceRemoveSqlLimit.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceRemoveSqlLimitContext } from '../index.context';\nimport { diagnosisServiceRemoveSqlLimitParams,\ndiagnosisServiceRemoveSqlLimitBody,\ndiagnosisServiceRemoveSqlLimitResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceRemoveSqlLimitHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceRemoveSqlLimitParams),\nzValidator('json', diagnosisServiceRemoveSqlLimitBody),\nzValidator('response', diagnosisServiceRemoveSqlLimitResponse),\nasync (c: DiagnosisServiceRemoveSqlLimitContext) => {\n\n    return c.json({}) \n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceUnbindSqlPlan.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceUnbindSqlPlanContext } from '../index.context';\nimport { diagnosisServiceUnbindSqlPlanParams,\ndiagnosisServiceUnbindSqlPlanQueryParams,\ndiagnosisServiceUnbindSqlPlanResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceUnbindSqlPlanHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceUnbindSqlPlanParams),\nzValidator('query', diagnosisServiceUnbindSqlPlanQueryParams),\nzValidator('response', diagnosisServiceUnbindSqlPlanResponse),\nasync (c: DiagnosisServiceUnbindSqlPlanContext) => {\n\n    return c.json({})\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/diagnosisServiceUpdateTopSqlConfigs.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { DiagnosisServiceUpdateTopSqlConfigsContext } from '../index.context';\nimport { diagnosisServiceUpdateTopSqlConfigsParams,\ndiagnosisServiceUpdateTopSqlConfigsBody,\ndiagnosisServiceUpdateTopSqlConfigsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const diagnosisServiceUpdateTopSqlConfigsHandlers = factory.createHandlers(\nzValidator('param', diagnosisServiceUpdateTopSqlConfigsParams),\nzValidator('json', diagnosisServiceUpdateTopSqlConfigsBody),\nzValidator('response', diagnosisServiceUpdateTopSqlConfigsResponse),\nasync (c: DiagnosisServiceUpdateTopSqlConfigsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/globalBRServiceCreateBackupPolicy.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { GlobalBRServiceCreateBackupPolicyContext } from '../index.context';\nimport { globalBRServiceCreateBackupPolicyBody,\nglobalBRServiceCreateBackupPolicyResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const globalBRServiceCreateBackupPolicyHandlers = factory.createHandlers(\nzValidator('json', globalBRServiceCreateBackupPolicyBody),\nzValidator('response', globalBRServiceCreateBackupPolicyResponse),\nasync (c: GlobalBRServiceCreateBackupPolicyContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/globalBRServiceDeleteBRTask.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { GlobalBRServiceDeleteBRTaskContext } from '../index.context';\nimport { globalBRServiceDeleteBRTaskParams,\nglobalBRServiceDeleteBRTaskQueryParams,\nglobalBRServiceDeleteBRTaskResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const globalBRServiceDeleteBRTaskHandlers = factory.createHandlers(\nzValidator('param', globalBRServiceDeleteBRTaskParams),\nzValidator('query', globalBRServiceDeleteBRTaskQueryParams),\nzValidator('response', globalBRServiceDeleteBRTaskResponse),\nasync (c: GlobalBRServiceDeleteBRTaskContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/globalBRServiceDeleteBackupPolicy.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { GlobalBRServiceDeleteBackupPolicyContext } from '../index.context';\nimport { globalBRServiceDeleteBackupPolicyParams,\nglobalBRServiceDeleteBackupPolicyResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const globalBRServiceDeleteBackupPolicyHandlers = factory.createHandlers(\nzValidator('param', globalBRServiceDeleteBackupPolicyParams),\nzValidator('response', globalBRServiceDeleteBackupPolicyResponse),\nasync (c: GlobalBRServiceDeleteBackupPolicyContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/globalBRServiceGetBRSummary.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { GlobalBRServiceGetBRSummaryContext } from '../index.context';\nimport { globalBRServiceGetBRSummaryQueryParams,\nglobalBRServiceGetBRSummaryResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const globalBRServiceGetBRSummaryHandlers = factory.createHandlers(\nzValidator('query', globalBRServiceGetBRSummaryQueryParams),\nzValidator('response', globalBRServiceGetBRSummaryResponse),\nasync (c: GlobalBRServiceGetBRSummaryContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/globalBRServiceGetBackupPolicy.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { GlobalBRServiceGetBackupPolicyContext } from '../index.context';\nimport { globalBRServiceGetBackupPolicyParams,\nglobalBRServiceGetBackupPolicyResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const globalBRServiceGetBackupPolicyHandlers = factory.createHandlers(\nzValidator('param', globalBRServiceGetBackupPolicyParams),\nzValidator('response', globalBRServiceGetBackupPolicyResponse),\nasync (c: GlobalBRServiceGetBackupPolicyContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/globalBRServiceListBRTasks.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { GlobalBRServiceListBRTasksContext } from '../index.context';\nimport { globalBRServiceListBRTasksQueryParams,\nglobalBRServiceListBRTasksResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const globalBRServiceListBRTasksHandlers = factory.createHandlers(\nzValidator('query', globalBRServiceListBRTasksQueryParams),\nzValidator('response', globalBRServiceListBRTasksResponse),\nasync (c: GlobalBRServiceListBRTasksContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/globalBRServiceListBackupPolicies.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { GlobalBRServiceListBackupPoliciesContext } from '../index.context';\nimport { globalBRServiceListBackupPoliciesQueryParams,\nglobalBRServiceListBackupPoliciesResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const globalBRServiceListBackupPoliciesHandlers = factory.createHandlers(\nzValidator('query', globalBRServiceListBackupPoliciesQueryParams),\nzValidator('response', globalBRServiceListBackupPoliciesResponse),\nasync (c: GlobalBRServiceListBackupPoliciesContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/globalBRServicePreCheckBackupPolicy.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { GlobalBRServicePreCheckBackupPolicyContext } from '../index.context';\nimport { globalBRServicePreCheckBackupPolicyBody,\nglobalBRServicePreCheckBackupPolicyResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const globalBRServicePreCheckBackupPolicyHandlers = factory.createHandlers(\nzValidator('json', globalBRServicePreCheckBackupPolicyBody),\nzValidator('response', globalBRServicePreCheckBackupPolicyResponse),\nasync (c: GlobalBRServicePreCheckBackupPolicyContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/globalBRServiceStartBRTask.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { GlobalBRServiceStartBRTaskContext } from '../index.context';\nimport { globalBRServiceStartBRTaskParams,\nglobalBRServiceStartBRTaskResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const globalBRServiceStartBRTaskHandlers = factory.createHandlers(\nzValidator('param', globalBRServiceStartBRTaskParams),\nzValidator('response', globalBRServiceStartBRTaskResponse),\nasync (c: GlobalBRServiceStartBRTaskContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/globalBRServiceStopBRTask.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { GlobalBRServiceStopBRTaskContext } from '../index.context';\nimport { globalBRServiceStopBRTaskParams,\nglobalBRServiceStopBRTaskResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const globalBRServiceStopBRTaskHandlers = factory.createHandlers(\nzValidator('param', globalBRServiceStopBRTaskParams),\nzValidator('response', globalBRServiceStopBRTaskResponse),\nasync (c: GlobalBRServiceStopBRTaskContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/globalBRServiceUpdateBackupPolicy.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { GlobalBRServiceUpdateBackupPolicyContext } from '../index.context';\nimport { globalBRServiceUpdateBackupPolicyParams,\nglobalBRServiceUpdateBackupPolicyBody,\nglobalBRServiceUpdateBackupPolicyResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const globalBRServiceUpdateBackupPolicyHandlers = factory.createHandlers(\nzValidator('param', globalBRServiceUpdateBackupPolicyParams),\nzValidator('json', globalBRServiceUpdateBackupPolicyBody),\nzValidator('response', globalBRServiceUpdateBackupPolicyResponse),\nasync (c: GlobalBRServiceUpdateBackupPolicyContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceBatchDelete.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceBatchDeleteContext } from '../index.context';\nimport { hostServiceBatchDeleteBody,\nhostServiceBatchDeleteResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceBatchDeleteHandlers = factory.createHandlers(\nzValidator('json', hostServiceBatchDeleteBody),\nzValidator('response', hostServiceBatchDeleteResponse),\nasync (c: HostServiceBatchDeleteContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceCheck.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceCheckContext } from '../index.context';\nimport { hostServiceCheckParams,\nhostServiceCheckResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceCheckHandlers = factory.createHandlers(\nzValidator('param', hostServiceCheckParams),\nzValidator('response', hostServiceCheckResponse),\nasync (c: HostServiceCheckContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceCreateHosts.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceCreateHostsContext } from '../index.context';\nimport { hostServiceCreateHostsBody,\nhostServiceCreateHostsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceCreateHostsHandlers = factory.createHandlers(\nzValidator('json', hostServiceCreateHostsBody),\nzValidator('response', hostServiceCreateHostsResponse),\nasync (c: HostServiceCreateHostsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceDelete.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceDeleteContext } from '../index.context';\nimport { hostServiceDeleteParams,\nhostServiceDeleteResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceDeleteHandlers = factory.createHandlers(\nzValidator('param', hostServiceDeleteParams),\nzValidator('response', hostServiceDeleteResponse),\nasync (c: HostServiceDeleteContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceDownloadHostTemplate.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceDownloadHostTemplateContext } from '../index.context';\nimport { hostServiceDownloadHostTemplateResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceDownloadHostTemplateHandlers = factory.createHandlers(\nzValidator('response', hostServiceDownloadHostTemplateResponse),\nasync (c: HostServiceDownloadHostTemplateContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceDownloadListHosts.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceDownloadListHostsContext } from '../index.context';\nimport { hostServiceDownloadListHostsQueryParams,\nhostServiceDownloadListHostsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceDownloadListHostsHandlers = factory.createHandlers(\nzValidator('query', hostServiceDownloadListHostsQueryParams),\nzValidator('response', hostServiceDownloadListHostsResponse),\nasync (c: HostServiceDownloadListHostsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceFix.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceFixContext } from '../index.context';\nimport { hostServiceFixParams,\nhostServiceFixResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceFixHandlers = factory.createHandlers(\nzValidator('param', hostServiceFixParams),\nzValidator('response', hostServiceFixResponse),\nasync (c: HostServiceFixContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceGetDisks.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceGetDisksContext } from '../index.context';\nimport { hostServiceGetDisksParams,\nhostServiceGetDisksResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceGetDisksHandlers = factory.createHandlers(\nzValidator('param', hostServiceGetDisksParams),\nzValidator('response', hostServiceGetDisksResponse),\nasync (c: HostServiceGetDisksContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceGetHost.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceGetHostContext } from '../index.context';\nimport { hostServiceGetHostParams,\nhostServiceGetHostResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceGetHostHandlers = factory.createHandlers(\nzValidator('param', hostServiceGetHostParams),\nzValidator('response', hostServiceGetHostResponse),\nasync (c: HostServiceGetHostContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceGetTiDBProcesses.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceGetTiDBProcessesContext } from '../index.context';\nimport { hostServiceGetTiDBProcessesParams,\nhostServiceGetTiDBProcessesResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceGetTiDBProcessesHandlers = factory.createHandlers(\nzValidator('param', hostServiceGetTiDBProcessesParams),\nzValidator('response', hostServiceGetTiDBProcessesResponse),\nasync (c: HostServiceGetTiDBProcessesContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceHostConfirm.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceHostConfirmContext } from '../index.context';\nimport { hostServiceHostConfirmParams,\nhostServiceHostConfirmBody,\nhostServiceHostConfirmResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceHostConfirmHandlers = factory.createHandlers(\nzValidator('param', hostServiceHostConfirmParams),\nzValidator('json', hostServiceHostConfirmBody),\nzValidator('response', hostServiceHostConfirmResponse),\nasync (c: HostServiceHostConfirmContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceImport.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceImportContext } from '../index.context';\nimport { hostServiceImportBody,\nhostServiceImportResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceImportHandlers = factory.createHandlers(\nzValidator('json', hostServiceImportBody),\nzValidator('response', hostServiceImportResponse),\nasync (c: HostServiceImportContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceImportTask.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceImportTaskContext } from '../index.context';\nimport { hostServiceImportTaskParams,\nhostServiceImportTaskResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceImportTaskHandlers = factory.createHandlers(\nzValidator('param', hostServiceImportTaskParams),\nzValidator('response', hostServiceImportTaskResponse),\nasync (c: HostServiceImportTaskContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceListHosts.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceListHostsContext } from '../index.context';\nimport { hostServiceListHostsQueryParams,\nhostServiceListHostsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceListHostsHandlers = factory.createHandlers(\nzValidator('query', hostServiceListHostsQueryParams),\nzValidator('response', hostServiceListHostsResponse),\nasync (c: HostServiceListHostsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceReport.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceReportContext } from '../index.context';\nimport { hostServiceReportParams,\nhostServiceReportResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceReportHandlers = factory.createHandlers(\nzValidator('param', hostServiceReportParams),\nzValidator('response', hostServiceReportResponse),\nasync (c: HostServiceReportContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/hostServiceUpdateHost.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { HostServiceUpdateHostContext } from '../index.context';\nimport { hostServiceUpdateHostParams,\nhostServiceUpdateHostBody,\nhostServiceUpdateHostResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const hostServiceUpdateHostHandlers = factory.createHandlers(\nzValidator('param', hostServiceUpdateHostParams),\nzValidator('json', hostServiceUpdateHostBody),\nzValidator('response', hostServiceUpdateHostResponse),\nasync (c: HostServiceUpdateHostContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/labelServiceBatchCreateLabels.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LabelServiceBatchCreateLabelsContext } from '../index.context';\nimport { labelServiceBatchCreateLabelsBody,\nlabelServiceBatchCreateLabelsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const labelServiceBatchCreateLabelsHandlers = factory.createHandlers(\nzValidator('json', labelServiceBatchCreateLabelsBody),\nzValidator('response', labelServiceBatchCreateLabelsResponse),\nasync (c: LabelServiceBatchCreateLabelsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/labelServiceBindLabel.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LabelServiceBindLabelContext } from '../index.context';\nimport { labelServiceBindLabelBody,\nlabelServiceBindLabelResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const labelServiceBindLabelHandlers = factory.createHandlers(\nzValidator('json', labelServiceBindLabelBody),\nzValidator('response', labelServiceBindLabelResponse),\nasync (c: LabelServiceBindLabelContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/labelServiceBindResource.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LabelServiceBindResourceContext } from '../index.context';\nimport { labelServiceBindResourceBody,\nlabelServiceBindResourceResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const labelServiceBindResourceHandlers = factory.createHandlers(\nzValidator('json', labelServiceBindResourceBody),\nzValidator('response', labelServiceBindResourceResponse),\nasync (c: LabelServiceBindResourceContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/labelServiceCreateLabel.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LabelServiceCreateLabelContext } from '../index.context';\nimport { labelServiceCreateLabelBody,\nlabelServiceCreateLabelResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const labelServiceCreateLabelHandlers = factory.createHandlers(\nzValidator('json', labelServiceCreateLabelBody),\nzValidator('response', labelServiceCreateLabelResponse),\nasync (c: LabelServiceCreateLabelContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/labelServiceDeleteLabel.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LabelServiceDeleteLabelContext } from '../index.context';\nimport { labelServiceDeleteLabelParams,\nlabelServiceDeleteLabelResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const labelServiceDeleteLabelHandlers = factory.createHandlers(\nzValidator('param', labelServiceDeleteLabelParams),\nzValidator('response', labelServiceDeleteLabelResponse),\nasync (c: LabelServiceDeleteLabelContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/labelServiceGetLabel.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LabelServiceGetLabelContext } from '../index.context';\nimport { labelServiceGetLabelParams,\nlabelServiceGetLabelResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const labelServiceGetLabelHandlers = factory.createHandlers(\nzValidator('param', labelServiceGetLabelParams),\nzValidator('response', labelServiceGetLabelResponse),\nasync (c: LabelServiceGetLabelContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/labelServiceGetLabelWithBindings.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LabelServiceGetLabelWithBindingsContext } from '../index.context';\nimport { labelServiceGetLabelWithBindingsParams,\nlabelServiceGetLabelWithBindingsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const labelServiceGetLabelWithBindingsHandlers = factory.createHandlers(\nzValidator('param', labelServiceGetLabelWithBindingsParams),\nzValidator('response', labelServiceGetLabelWithBindingsResponse),\nasync (c: LabelServiceGetLabelWithBindingsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/labelServiceListLabelKeys.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LabelServiceListLabelKeysContext } from '../index.context';\nimport { labelServiceListLabelKeysQueryParams,\nlabelServiceListLabelKeysResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const labelServiceListLabelKeysHandlers = factory.createHandlers(\nzValidator('query', labelServiceListLabelKeysQueryParams),\nzValidator('response', labelServiceListLabelKeysResponse),\nasync (c: LabelServiceListLabelKeysContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/labelServiceListLabels.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LabelServiceListLabelsContext } from '../index.context';\nimport { labelServiceListLabelsQueryParams,\nlabelServiceListLabelsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const labelServiceListLabelsHandlers = factory.createHandlers(\nzValidator('query', labelServiceListLabelsQueryParams),\nzValidator('response', labelServiceListLabelsResponse),\nasync (c: LabelServiceListLabelsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/labelServiceListLabelsByResourceType.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LabelServiceListLabelsByResourceTypeContext } from '../index.context';\nimport { labelServiceListLabelsByResourceTypeQueryParams,\nlabelServiceListLabelsByResourceTypeResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const labelServiceListLabelsByResourceTypeHandlers = factory.createHandlers(\nzValidator('query', labelServiceListLabelsByResourceTypeQueryParams),\nzValidator('response', labelServiceListLabelsByResourceTypeResponse),\nasync (c: LabelServiceListLabelsByResourceTypeContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/labelServiceListLabelsWithBindings.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LabelServiceListLabelsWithBindingsContext } from '../index.context';\nimport { labelServiceListLabelsWithBindingsQueryParams,\nlabelServiceListLabelsWithBindingsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const labelServiceListLabelsWithBindingsHandlers = factory.createHandlers(\nzValidator('query', labelServiceListLabelsWithBindingsQueryParams),\nzValidator('response', labelServiceListLabelsWithBindingsResponse),\nasync (c: LabelServiceListLabelsWithBindingsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/labelServiceUpdateLabel.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LabelServiceUpdateLabelContext } from '../index.context';\nimport { labelServiceUpdateLabelParams,\nlabelServiceUpdateLabelBody,\nlabelServiceUpdateLabelResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const labelServiceUpdateLabelHandlers = factory.createHandlers(\nzValidator('param', labelServiceUpdateLabelParams),\nzValidator('json', labelServiceUpdateLabelBody),\nzValidator('response', labelServiceUpdateLabelResponse),\nasync (c: LabelServiceUpdateLabelContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/licenseServiceActivateFreeLicense.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LicenseServiceActivateFreeLicenseContext } from '../index.context';\nimport { licenseServiceActivateFreeLicenseResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const licenseServiceActivateFreeLicenseHandlers = factory.createHandlers(\nzValidator('response', licenseServiceActivateFreeLicenseResponse),\nasync (c: LicenseServiceActivateFreeLicenseContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/licenseServiceActivateLicense.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LicenseServiceActivateLicenseContext } from '../index.context';\nimport { licenseServiceActivateLicenseBody,\nlicenseServiceActivateLicenseResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const licenseServiceActivateLicenseHandlers = factory.createHandlers(\nzValidator('json', licenseServiceActivateLicenseBody),\nzValidator('response', licenseServiceActivateLicenseResponse),\nasync (c: LicenseServiceActivateLicenseContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/licenseServiceGetDeviceCode.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LicenseServiceGetDeviceCodeContext } from '../index.context';\nimport { licenseServiceGetDeviceCodeResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const licenseServiceGetDeviceCodeHandlers = factory.createHandlers(\nzValidator('response', licenseServiceGetDeviceCodeResponse),\nasync (c: LicenseServiceGetDeviceCodeContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/licenseServiceGetLicense.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LicenseServiceGetLicenseContext } from '../index.context';\nimport { licenseServiceGetLicenseResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const licenseServiceGetLicenseHandlers = factory.createHandlers(\nzValidator('response', licenseServiceGetLicenseResponse),\nasync (c: LicenseServiceGetLicenseContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/locationServiceCreateLocations.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LocationServiceCreateLocationsContext } from '../index.context';\nimport { locationServiceCreateLocationsBody,\nlocationServiceCreateLocationsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const locationServiceCreateLocationsHandlers = factory.createHandlers(\nzValidator('json', locationServiceCreateLocationsBody),\nzValidator('response', locationServiceCreateLocationsResponse),\nasync (c: LocationServiceCreateLocationsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/locationServiceDeleteLocation.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LocationServiceDeleteLocationContext } from '../index.context';\nimport { locationServiceDeleteLocationParams,\nlocationServiceDeleteLocationResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const locationServiceDeleteLocationHandlers = factory.createHandlers(\nzValidator('param', locationServiceDeleteLocationParams),\nzValidator('response', locationServiceDeleteLocationResponse),\nasync (c: LocationServiceDeleteLocationContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/locationServiceGetLocations.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LocationServiceGetLocationsContext } from '../index.context';\nimport { locationServiceGetLocationsParams,\nlocationServiceGetLocationsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const locationServiceGetLocationsHandlers = factory.createHandlers(\nzValidator('param', locationServiceGetLocationsParams),\nzValidator('response', locationServiceGetLocationsResponse),\nasync (c: LocationServiceGetLocationsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/locationServiceListLocations.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LocationServiceListLocationsContext } from '../index.context';\nimport { locationServiceListLocationsQueryParams,\nlocationServiceListLocationsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const locationServiceListLocationsHandlers = factory.createHandlers(\nzValidator('query', locationServiceListLocationsQueryParams),\nzValidator('response', locationServiceListLocationsResponse),\nasync (c: LocationServiceListLocationsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/locationServiceUpdateLocations.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { LocationServiceUpdateLocationsContext } from '../index.context';\nimport { locationServiceUpdateLocationsParams,\nlocationServiceUpdateLocationsBody,\nlocationServiceUpdateLocationsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const locationServiceUpdateLocationsHandlers = factory.createHandlers(\nzValidator('param', locationServiceUpdateLocationsParams),\nzValidator('json', locationServiceUpdateLocationsBody),\nzValidator('response', locationServiceUpdateLocationsResponse),\nasync (c: LocationServiceUpdateLocationsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/metricsServiceGetClusterMetricData.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { MetricsServiceGetClusterMetricDataContext } from '../index.context';\nimport { metricsServiceGetClusterMetricDataParams,\nmetricsServiceGetClusterMetricDataQueryParams,\nmetricsServiceGetClusterMetricDataResponse } from '../index.zod';\n\nimport metricsData from \"../sample-res/metrics-data-cpu-usage.json\"\n\nconst factory = createFactory()\n\nexport const metricsServiceGetClusterMetricDataHandlers =\n  factory.createHandlers(\n    zValidator(\"param\", metricsServiceGetClusterMetricDataParams),\n    zValidator(\"query\", metricsServiceGetClusterMetricDataQueryParams),\n    zValidator(\"response\", metricsServiceGetClusterMetricDataResponse),\n    async (c: MetricsServiceGetClusterMetricDataContext) => {\n      return c.json(metricsData)\n    },\n  )\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/metricsServiceGetClusterMetricInstance.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { MetricsServiceGetClusterMetricInstanceContext } from '../index.context';\nimport { metricsServiceGetClusterMetricInstanceParams,\nmetricsServiceGetClusterMetricInstanceResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const metricsServiceGetClusterMetricInstanceHandlers = factory.createHandlers(\nzValidator('param', metricsServiceGetClusterMetricInstanceParams),\nzValidator('response', metricsServiceGetClusterMetricInstanceResponse),\nasync (c: MetricsServiceGetClusterMetricInstanceContext) => {\n    return c.json({\n      type: \"tidb\",\n      instanceList: [\"10.2.12.107:10081\", \"10.2.12.107:10082\"],\n    })\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/metricsServiceGetHostMetricData.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { MetricsServiceGetHostMetricDataContext } from '../index.context';\nimport { metricsServiceGetHostMetricDataParams,\nmetricsServiceGetHostMetricDataQueryParams,\nmetricsServiceGetHostMetricDataResponse } from '../index.zod';\n\nimport metricsData from \"../sample-res/metrics-data-cpu-usage.json\"\n\nconst factory = createFactory()\n\nexport const metricsServiceGetHostMetricDataHandlers = factory.createHandlers(\n  zValidator(\"param\", metricsServiceGetHostMetricDataParams),\n  zValidator(\"query\", metricsServiceGetHostMetricDataQueryParams),\n  zValidator(\"response\", metricsServiceGetHostMetricDataResponse),\n  async (c: MetricsServiceGetHostMetricDataContext) => {\n    return c.json(metricsData)\n  },\n)\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/metricsServiceGetMetrics.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { MetricsServiceGetMetricsContext } from '../index.context';\nimport { metricsServiceGetMetricsQueryParams,\nmetricsServiceGetMetricsResponse } from '../index.zod';\n\nimport metricsConfigOverview from \"../sample-res/metrics-config-overview.json\"\nimport metricsConfigHost from \"../sample-res/metrics-config-host.json\"\nimport metricsConfigCluster from \"../sample-res/metrics-config-cluster.json\"\nimport metricsConfigClusterOverview from \"../sample-res/metrics-config-cluster-overview.json\"\n\nconst factory = createFactory()\n\nexport const metricsServiceGetMetricsHandlers = factory.createHandlers(\n  zValidator(\"query\", metricsServiceGetMetricsQueryParams),\n  zValidator(\"response\", metricsServiceGetMetricsResponse),\n  async (c: MetricsServiceGetMetricsContext) => {\n    const classType = c.req.query(\"class\")\n    const group = c.req.query(\"group\")\n\n    if (classType === \"overview\") {\n      return c.json(metricsConfigOverview)\n    } else if (classType === \"host\") {\n      return c.json(metricsConfigHost)\n    } else if (classType === \"cluster\") {\n      if (group === \"overview\") {\n        return c.json(metricsConfigClusterOverview)\n      } else {\n        return c.json(metricsConfigCluster)\n      }\n    }\n  },\n)\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/metricsServiceGetOverviewStatus.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { MetricsServiceGetOverviewStatusContext } from '../index.context';\nimport { metricsServiceGetOverviewStatusQueryParams,\nmetricsServiceGetOverviewStatusResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const metricsServiceGetOverviewStatusHandlers = factory.createHandlers(\nzValidator('query', metricsServiceGetOverviewStatusQueryParams),\nzValidator('response', metricsServiceGetOverviewStatusResponse),\nasync (c: MetricsServiceGetOverviewStatusContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/metricsServiceGetTopMetricConfig.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { MetricsServiceGetTopMetricConfigContext } from '../index.context';\nimport { metricsServiceGetTopMetricConfigResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const metricsServiceGetTopMetricConfigHandlers = factory.createHandlers(\nzValidator('response', metricsServiceGetTopMetricConfigResponse),\nasync (c: MetricsServiceGetTopMetricConfigContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/metricsServiceGetTopMetricData.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { MetricsServiceGetTopMetricDataContext } from '../index.context';\nimport { metricsServiceGetTopMetricDataParams,\nmetricsServiceGetTopMetricDataQueryParams,\nmetricsServiceGetTopMetricDataResponse } from '../index.zod';\n\nimport metricsData from \"../sample-res/metrics-data-cpu-usage.json\"\n\nconst factory = createFactory()\n\nexport const metricsServiceGetTopMetricDataHandlers = factory.createHandlers(\n  zValidator(\"param\", metricsServiceGetTopMetricDataParams),\n  zValidator(\"query\", metricsServiceGetTopMetricDataQueryParams),\n  zValidator(\"response\", metricsServiceGetTopMetricDataResponse),\n  async (c: MetricsServiceGetTopMetricDataContext) => {\n    return c.json(metricsData)\n  },\n)\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/roleServiceCreateRole.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { RoleServiceCreateRoleContext } from '../index.context';\nimport { roleServiceCreateRoleBody,\nroleServiceCreateRoleResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const roleServiceCreateRoleHandlers = factory.createHandlers(\nzValidator('json', roleServiceCreateRoleBody),\nzValidator('response', roleServiceCreateRoleResponse),\nasync (c: RoleServiceCreateRoleContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/roleServiceDeleteRole.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { RoleServiceDeleteRoleContext } from '../index.context';\nimport { roleServiceDeleteRoleParams,\nroleServiceDeleteRoleResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const roleServiceDeleteRoleHandlers = factory.createHandlers(\nzValidator('param', roleServiceDeleteRoleParams),\nzValidator('response', roleServiceDeleteRoleResponse),\nasync (c: RoleServiceDeleteRoleContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/roleServiceListRoles.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { RoleServiceListRolesContext } from '../index.context';\nimport { roleServiceListRolesQueryParams,\nroleServiceListRolesResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const roleServiceListRolesHandlers = factory.createHandlers(\nzValidator('query', roleServiceListRolesQueryParams),\nzValidator('response', roleServiceListRolesResponse),\nasync (c: RoleServiceListRolesContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/roleServiceUpdateRole.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { RoleServiceUpdateRoleContext } from '../index.context';\nimport { roleServiceUpdateRoleParams,\nroleServiceUpdateRoleBody,\nroleServiceUpdateRoleResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const roleServiceUpdateRoleHandlers = factory.createHandlers(\nzValidator('param', roleServiceUpdateRoleParams),\nzValidator('json', roleServiceUpdateRoleBody),\nzValidator('response', roleServiceUpdateRoleResponse),\nasync (c: RoleServiceUpdateRoleContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tagServiceBatchCreateTags.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TagServiceBatchCreateTagsContext } from '../index.context';\nimport { tagServiceBatchCreateTagsBody,\ntagServiceBatchCreateTagsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tagServiceBatchCreateTagsHandlers = factory.createHandlers(\nzValidator('json', tagServiceBatchCreateTagsBody),\nzValidator('response', tagServiceBatchCreateTagsResponse),\nasync (c: TagServiceBatchCreateTagsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tagServiceBindResource.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TagServiceBindResourceContext } from '../index.context';\nimport { tagServiceBindResourceBody,\ntagServiceBindResourceResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tagServiceBindResourceHandlers = factory.createHandlers(\nzValidator('json', tagServiceBindResourceBody),\nzValidator('response', tagServiceBindResourceResponse),\nasync (c: TagServiceBindResourceContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tagServiceBindTag.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TagServiceBindTagContext } from '../index.context';\nimport { tagServiceBindTagBody,\ntagServiceBindTagResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tagServiceBindTagHandlers = factory.createHandlers(\nzValidator('json', tagServiceBindTagBody),\nzValidator('response', tagServiceBindTagResponse),\nasync (c: TagServiceBindTagContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tagServiceCreateTag.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TagServiceCreateTagContext } from '../index.context';\nimport { tagServiceCreateTagBody,\ntagServiceCreateTagResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tagServiceCreateTagHandlers = factory.createHandlers(\nzValidator('json', tagServiceCreateTagBody),\nzValidator('response', tagServiceCreateTagResponse),\nasync (c: TagServiceCreateTagContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tagServiceDeleteTag.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TagServiceDeleteTagContext } from '../index.context';\nimport { tagServiceDeleteTagParams,\ntagServiceDeleteTagResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tagServiceDeleteTagHandlers = factory.createHandlers(\nzValidator('param', tagServiceDeleteTagParams),\nzValidator('response', tagServiceDeleteTagResponse),\nasync (c: TagServiceDeleteTagContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tagServiceGetTag.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TagServiceGetTagContext } from '../index.context';\nimport { tagServiceGetTagParams,\ntagServiceGetTagResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tagServiceGetTagHandlers = factory.createHandlers(\nzValidator('param', tagServiceGetTagParams),\nzValidator('response', tagServiceGetTagResponse),\nasync (c: TagServiceGetTagContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tagServiceGetTagWithBindings.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TagServiceGetTagWithBindingsContext } from '../index.context';\nimport { tagServiceGetTagWithBindingsParams,\ntagServiceGetTagWithBindingsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tagServiceGetTagWithBindingsHandlers = factory.createHandlers(\nzValidator('param', tagServiceGetTagWithBindingsParams),\nzValidator('response', tagServiceGetTagWithBindingsResponse),\nasync (c: TagServiceGetTagWithBindingsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tagServiceListTagKeys.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TagServiceListTagKeysContext } from '../index.context';\nimport { tagServiceListTagKeysQueryParams,\ntagServiceListTagKeysResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tagServiceListTagKeysHandlers = factory.createHandlers(\nzValidator('query', tagServiceListTagKeysQueryParams),\nzValidator('response', tagServiceListTagKeysResponse),\nasync (c: TagServiceListTagKeysContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tagServiceListTags.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TagServiceListTagsContext } from '../index.context';\nimport { tagServiceListTagsQueryParams,\ntagServiceListTagsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tagServiceListTagsHandlers = factory.createHandlers(\nzValidator('query', tagServiceListTagsQueryParams),\nzValidator('response', tagServiceListTagsResponse),\nasync (c: TagServiceListTagsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tagServiceListTagsByResourceType.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TagServiceListTagsByResourceTypeContext } from '../index.context';\nimport { tagServiceListTagsByResourceTypeQueryParams,\ntagServiceListTagsByResourceTypeResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tagServiceListTagsByResourceTypeHandlers = factory.createHandlers(\nzValidator('query', tagServiceListTagsByResourceTypeQueryParams),\nzValidator('response', tagServiceListTagsByResourceTypeResponse),\nasync (c: TagServiceListTagsByResourceTypeContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tagServiceListTagsWithBindings.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TagServiceListTagsWithBindingsContext } from '../index.context';\nimport { tagServiceListTagsWithBindingsQueryParams,\ntagServiceListTagsWithBindingsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tagServiceListTagsWithBindingsHandlers = factory.createHandlers(\nzValidator('query', tagServiceListTagsWithBindingsQueryParams),\nzValidator('response', tagServiceListTagsWithBindingsResponse),\nasync (c: TagServiceListTagsWithBindingsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tagServiceUpdateTag.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TagServiceUpdateTagContext } from '../index.context';\nimport { tagServiceUpdateTagParams,\ntagServiceUpdateTagBody,\ntagServiceUpdateTagResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tagServiceUpdateTagHandlers = factory.createHandlers(\nzValidator('param', tagServiceUpdateTagParams),\nzValidator('json', tagServiceUpdateTagBody),\nzValidator('response', tagServiceUpdateTagResponse),\nasync (c: TagServiceUpdateTagContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tiupsServiceCreateTiups.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TiupsServiceCreateTiupsContext } from '../index.context';\nimport { tiupsServiceCreateTiupsBody,\ntiupsServiceCreateTiupsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tiupsServiceCreateTiupsHandlers = factory.createHandlers(\nzValidator('json', tiupsServiceCreateTiupsBody),\nzValidator('response', tiupsServiceCreateTiupsResponse),\nasync (c: TiupsServiceCreateTiupsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tiupsServiceDeleteTiups.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TiupsServiceDeleteTiupsContext } from '../index.context';\nimport { tiupsServiceDeleteTiupsParams,\ntiupsServiceDeleteTiupsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tiupsServiceDeleteTiupsHandlers = factory.createHandlers(\nzValidator('param', tiupsServiceDeleteTiupsParams),\nzValidator('response', tiupsServiceDeleteTiupsResponse),\nasync (c: TiupsServiceDeleteTiupsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tiupsServiceGetTiups.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TiupsServiceGetTiupsContext } from '../index.context';\nimport { tiupsServiceGetTiupsParams,\ntiupsServiceGetTiupsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tiupsServiceGetTiupsHandlers = factory.createHandlers(\nzValidator('param', tiupsServiceGetTiupsParams),\nzValidator('response', tiupsServiceGetTiupsResponse),\nasync (c: TiupsServiceGetTiupsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tiupsServiceGetTiupsCluster.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TiupsServiceGetTiupsClusterContext } from '../index.context';\nimport { tiupsServiceGetTiupsClusterParams,\ntiupsServiceGetTiupsClusterResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tiupsServiceGetTiupsClusterHandlers = factory.createHandlers(\nzValidator('param', tiupsServiceGetTiupsClusterParams),\nzValidator('response', tiupsServiceGetTiupsClusterResponse),\nasync (c: TiupsServiceGetTiupsClusterContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tiupsServiceListTiups.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TiupsServiceListTiupsContext } from '../index.context';\nimport { tiupsServiceListTiupsQueryParams,\ntiupsServiceListTiupsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tiupsServiceListTiupsHandlers = factory.createHandlers(\nzValidator('query', tiupsServiceListTiupsQueryParams),\nzValidator('response', tiupsServiceListTiupsResponse),\nasync (c: TiupsServiceListTiupsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/tiupsServiceUpdateTiups.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { TiupsServiceUpdateTiupsContext } from '../index.context';\nimport { tiupsServiceUpdateTiupsParams,\ntiupsServiceUpdateTiupsBody,\ntiupsServiceUpdateTiupsResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const tiupsServiceUpdateTiupsHandlers = factory.createHandlers(\nzValidator('param', tiupsServiceUpdateTiupsParams),\nzValidator('json', tiupsServiceUpdateTiupsBody),\nzValidator('response', tiupsServiceUpdateTiupsResponse),\nasync (c: TiupsServiceUpdateTiupsContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/userServiceChangePassword.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { UserServiceChangePasswordContext } from '../index.context';\nimport { userServiceChangePasswordBody,\nuserServiceChangePasswordResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const userServiceChangePasswordHandlers = factory.createHandlers(\nzValidator('json', userServiceChangePasswordBody),\nzValidator('response', userServiceChangePasswordResponse),\nasync (c: UserServiceChangePasswordContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/userServiceCreateUser.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { UserServiceCreateUserContext } from '../index.context';\nimport { userServiceCreateUserBody,\nuserServiceCreateUserResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const userServiceCreateUserHandlers = factory.createHandlers(\nzValidator('json', userServiceCreateUserBody),\nzValidator('response', userServiceCreateUserResponse),\nasync (c: UserServiceCreateUserContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/userServiceDeleteUser.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { UserServiceDeleteUserContext } from '../index.context';\nimport { userServiceDeleteUserParams,\nuserServiceDeleteUserResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const userServiceDeleteUserHandlers = factory.createHandlers(\nzValidator('param', userServiceDeleteUserParams),\nzValidator('response', userServiceDeleteUserResponse),\nasync (c: UserServiceDeleteUserContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/userServiceGetUser.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { UserServiceGetUserContext } from '../index.context';\nimport { userServiceGetUserParams,\nuserServiceGetUserResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const userServiceGetUserHandlers = factory.createHandlers(\nzValidator('param', userServiceGetUserParams),\nzValidator('response', userServiceGetUserResponse),\nasync (c: UserServiceGetUserContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/userServiceGetUserProfile.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { UserServiceGetUserProfileContext } from '../index.context';\nimport { userServiceGetUserProfileResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const userServiceGetUserProfileHandlers = factory.createHandlers(\nzValidator('response', userServiceGetUserProfileResponse),\nasync (c: UserServiceGetUserProfileContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/userServiceListUserRoles.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { UserServiceListUserRolesContext } from '../index.context';\nimport { userServiceListUserRolesQueryParams,\nuserServiceListUserRolesResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const userServiceListUserRolesHandlers = factory.createHandlers(\nzValidator('query', userServiceListUserRolesQueryParams),\nzValidator('response', userServiceListUserRolesResponse),\nasync (c: UserServiceListUserRolesContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/userServiceListUsers.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { UserServiceListUsersContext } from '../index.context';\nimport { userServiceListUsersQueryParams,\nuserServiceListUsersResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const userServiceListUsersHandlers = factory.createHandlers(\nzValidator('query', userServiceListUsersQueryParams),\nzValidator('response', userServiceListUsersResponse),\nasync (c: UserServiceListUsersContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/userServiceLogin.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { UserServiceLoginContext } from '../index.context';\nimport { userServiceLoginBody,\nuserServiceLoginResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const userServiceLoginHandlers = factory.createHandlers(\nzValidator('json', userServiceLoginBody),\nzValidator('response', userServiceLoginResponse),\nasync (c: UserServiceLoginContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/userServiceLogout.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { UserServiceLogoutContext } from '../index.context';\nimport { userServiceLogoutResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const userServiceLogoutHandlers = factory.createHandlers(\nzValidator('response', userServiceLogoutResponse),\nasync (c: UserServiceLogoutContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/userServiceResetPassword.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { UserServiceResetPasswordContext } from '../index.context';\nimport { userServiceResetPasswordParams,\nuserServiceResetPasswordBody,\nuserServiceResetPasswordResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const userServiceResetPasswordHandlers = factory.createHandlers(\nzValidator('param', userServiceResetPasswordParams),\nzValidator('json', userServiceResetPasswordBody),\nzValidator('response', userServiceResetPasswordResponse),\nasync (c: UserServiceResetPasswordContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/userServiceUpdateUser.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { UserServiceUpdateUserContext } from '../index.context';\nimport { userServiceUpdateUserParams,\nuserServiceUpdateUserBody,\nuserServiceUpdateUserResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const userServiceUpdateUserHandlers = factory.createHandlers(\nzValidator('param', userServiceUpdateUserParams),\nzValidator('json', userServiceUpdateUserBody),\nzValidator('response', userServiceUpdateUserResponse),\nasync (c: UserServiceUpdateUserContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/handlers/userServiceValidateSession.ts",
    "content": "import { createFactory } from 'hono/factory';\nimport { zValidator } from '../index.validator';\nimport { UserServiceValidateSessionContext } from '../index.context';\nimport { userServiceValidateSessionResponse } from '../index.zod';\n\nconst factory = createFactory();\n\n\nexport const userServiceValidateSessionHandlers = factory.createHandlers(\nzValidator('response', userServiceValidateSessionResponse),\nasync (c: UserServiceValidateSessionContext) => {\n\n  },\n);\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/index.context.ts",
    "content": "/**\n * Generated by orval v7.3.0 🍺\n * Do not edit manually.\n * Azores Open API\n * OpenAPI spec version: 2.0.0\n */\nimport type { Context, Env } from 'hono';\n\n\n// https://stackoverflow.com/questions/49579094/typescript-conditional-types-filter-out-readonly-properties-pick-only-requir/49579497#49579497\ntype IfEquals<X, Y, A = X, B = never> = (<T>() => T extends X ? 1 : 2) extends <\nT,\n>() => T extends Y ? 1 : 2\n? A\n: B;\n\ntype WritableKeys<T> = {\n[P in keyof T]-?: IfEquals<\n  { [Q in P]: T[P] },\n  { -readonly [Q in P]: T[P] },\n  P\n>;\n}[keyof T];\n\ntype UnionToIntersection<U> =\n  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never;\ntype DistributeReadOnlyOverUnions<T> = T extends any ? NonReadonly<T> : never;\n\ntype Writable<T> = Pick<T, WritableKeys<T>>;\ntype NonReadonly<T> = [T] extends [UnionToIntersection<T>] ? {\n  [P in keyof Writable<T>]: T[P] extends object\n    ? NonReadonly<NonNullable<T[P]>>\n    : T[P];\n} : DistributeReadOnlyOverUnions<T>;\n\nimport { ApiKeyServiceListApiKeysParams,\nV2CreateApiKeyRequest,\nApiKeyServiceUpdateApiKeyBody,\nGlobalBRServiceListBackupPoliciesParams,\nV2BackupPolicyBody,\nGlobalBRServiceUpdateBackupPolicyBody,\nGlobalBRServiceGetBRSummaryParams,\nGlobalBRServiceListBRTasksParams,\nGlobalBRServiceDeleteBRTaskParams,\nClusterBRServiceCreateBackupTaskBody,\nClusterBRServiceListClusterBackupRecordsParams,\nClusterBRServiceCreateRestoreTaskBody,\nClusterBRServiceListClusterBRTasksParams,\nMetricsServiceGetClusterMetricDataParams,\nDiagnosisServiceGetSlowQueryListParams,\nDiagnosisServiceDownloadSlowQueryListParams,\nDiagnosisServiceGetSlowQueryDetailParams,\nDiagnosisServiceAddSqlLimitBody,\nDiagnosisServiceRemoveSqlLimitBody,\nDiagnosisServiceGetSqlLimitListParams,\nDiagnosisServiceGetSqlPlanListParams,\nDiagnosisServiceGetSqlPlanBindingListParams,\nDiagnosisServiceUnbindSqlPlanParams,\nDiagnosisServiceGetTopSqlListParams,\nDiagnosisServiceUpdateTopSqlConfigsBody,\nDiagnosisServiceGetTopSqlDetailParams,\nCredentialServiceListCredentialsParams,\nV2Credential,\nCredentialServiceUpdateCredentialBody,\nV2GenerateRSAKeyRequest,\nV2ValidateConnectionRequest,\nHostServiceListHostsParams,\nV2CreateHost,\nHostServiceImportBody,\nHostServiceHostConfirmBody,\nV2HostServiceUpdateHostBody,\nMetricsServiceGetHostMetricDataParams,\nV2BatchDeleteRequest,\nHostServiceDownloadListHostsParams,\nLicenseServiceActivateLicenseBody,\nLocationServiceListLocationsParams,\nV2Locations,\nLocationServiceUpdateLocationsBody,\nV2LoginRequest,\nMetricsServiceGetMetricsParams,\nMetricsServiceGetTopMetricDataParams,\nMetricsServiceGetOverviewStatusParams,\nRoleServiceListRolesParams,\nV2Role,\nRoleServiceUpdateRoleBody,\nTagServiceListTagsParams,\nTagv2Tag,\nTagServiceUpdateTagBody,\nV2BatchCreateTagsRequest,\nV2BindResourceRequest,\nV2BindTagRequest,\nTagServiceListTagsByResourceTypeParams,\nTagServiceListTagKeysParams,\nTagServiceListTagsWithBindingsParams,\nTiupsServiceListTiupsParams,\nTiupv2CreateTiups,\nV2TiupsServiceUpdateTiupsBody,\nUserServiceListUsersParams,\nV2User,\nUserServiceUpdateUserBody,\nUserServiceResetPasswordBody,\nV2ChangePasswordRequest } from './index.schemas';\n\nexport type ApiKeyServiceListApiKeysContext<E extends Env = any> = Context<E, '/api/v2/apiKeys', { in: { query: ApiKeyServiceListApiKeysParams, }, out: { query: ApiKeyServiceListApiKeysParams, } }>\nexport type ApiKeyServiceCreateApiKeyContext<E extends Env = any> = Context<E, '/api/v2/apiKeys', { in: { json: V2CreateApiKeyRequest, }, out: { json: V2CreateApiKeyRequest, } }>\nexport type ApiKeyServiceGetApiKeyContext<E extends Env = any> = Context<E, '/api/v2/apiKeys/:accessKey', { in: { param: {\n accessKey: string,\n }, }, out: { param: {\n accessKey: string,\n }, } }>\nexport type ApiKeyServiceDeleteApiKeyContext<E extends Env = any> = Context<E, '/api/v2/apiKeys/:accessKey', { in: { param: {\n accessKey: string,\n }, }, out: { param: {\n accessKey: string,\n }, } }>\nexport type ApiKeyServiceUpdateApiKeyContext<E extends Env = any> = Context<E, '/api/v2/apiKeys/:accessKey', { in: { param: {\n accessKey: string,\n },json: ApiKeyServiceUpdateApiKeyBody, }, out: { param: {\n accessKey: string,\n },json: ApiKeyServiceUpdateApiKeyBody, } }>\nexport type ApiKeyServiceResetSecretKeyContext<E extends Env = any> = Context<E, '/api/v2/apiKeys/:accessKey:resetSecretKey', { in: { param: {\n accessKey: string,\n }, }, out: { param: {\n accessKey: string,\n }, } }>\nexport type GlobalBRServiceListBackupPoliciesContext<E extends Env = any> = Context<E, '/api/v2/backup/policies', { in: { query: GlobalBRServiceListBackupPoliciesParams, }, out: { query: GlobalBRServiceListBackupPoliciesParams, } }>\nexport type GlobalBRServiceCreateBackupPolicyContext<E extends Env = any> = Context<E, '/api/v2/backup/policies', { in: { json: V2BackupPolicyBody, }, out: { json: V2BackupPolicyBody, } }>\nexport type GlobalBRServicePreCheckBackupPolicyContext<E extends Env = any> = Context<E, '/api/v2/backup/policies/precheck', { in: { json: V2BackupPolicyBody, }, out: { json: V2BackupPolicyBody, } }>\nexport type GlobalBRServiceGetBackupPolicyContext<E extends Env = any> = Context<E, '/api/v2/backup/policies/:policyId', { in: { param: {\n policyId: string,\n }, }, out: { param: {\n policyId: string,\n }, } }>\nexport type GlobalBRServiceDeleteBackupPolicyContext<E extends Env = any> = Context<E, '/api/v2/backup/policies/:policyId', { in: { param: {\n policyId: string,\n }, }, out: { param: {\n policyId: string,\n }, } }>\nexport type GlobalBRServiceUpdateBackupPolicyContext<E extends Env = any> = Context<E, '/api/v2/backup/policies/:policyId', { in: { param: {\n policyId: string,\n },json: GlobalBRServiceUpdateBackupPolicyBody, }, out: { param: {\n policyId: string,\n },json: GlobalBRServiceUpdateBackupPolicyBody, } }>\nexport type GlobalBRServiceGetBRSummaryContext<E extends Env = any> = Context<E, '/api/v2/backup/summary', { in: { query: GlobalBRServiceGetBRSummaryParams, }, out: { query: GlobalBRServiceGetBRSummaryParams, } }>\nexport type GlobalBRServiceListBRTasksContext<E extends Env = any> = Context<E, '/api/v2/backup/tasks', { in: { query: GlobalBRServiceListBRTasksParams, }, out: { query: GlobalBRServiceListBRTasksParams, } }>\nexport type GlobalBRServiceDeleteBRTaskContext<E extends Env = any> = Context<E, '/api/v2/backup/tasks/:taskId', { in: { param: {\n taskId: string,\n },query: GlobalBRServiceDeleteBRTaskParams, }, out: { param: {\n taskId: string,\n },query: GlobalBRServiceDeleteBRTaskParams, } }>\nexport type GlobalBRServiceStartBRTaskContext<E extends Env = any> = Context<E, '/api/v2/backup/tasks/:taskId/start', { in: { param: {\n taskId: string,\n }, }, out: { param: {\n taskId: string,\n }, } }>\nexport type GlobalBRServiceStopBRTaskContext<E extends Env = any> = Context<E, '/api/v2/backup/tasks/:taskId/stop', { in: { param: {\n taskId: string,\n }, }, out: { param: {\n taskId: string,\n }, } }>\nexport type ClusterBRServiceCreateBackupTaskContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/backup', { in: { param: {\n clusterId: string,\n },json: ClusterBRServiceCreateBackupTaskBody, }, out: { param: {\n clusterId: string,\n },json: ClusterBRServiceCreateBackupTaskBody, } }>\nexport type ClusterBRServiceGetClusterBackupPolicyContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/backup/policy', { in: { param: {\n clusterId: string,\n }, }, out: { param: {\n clusterId: string,\n }, } }>\nexport type ClusterBRServiceListClusterBackupRecordsContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/backup/records', { in: { param: {\n clusterId: string,\n },query: ClusterBRServiceListClusterBackupRecordsParams, }, out: { param: {\n clusterId: string,\n },query: ClusterBRServiceListClusterBackupRecordsParams, } }>\nexport type ClusterBRServiceCreateRestoreTaskContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/backup/restore', { in: { param: {\n clusterId: string,\n },json: ClusterBRServiceCreateRestoreTaskBody, }, out: { param: {\n clusterId: string,\n },json: ClusterBRServiceCreateRestoreTaskBody, } }>\nexport type ClusterBRServiceListClusterBRTasksContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/backup/tasks', { in: { param: {\n clusterId: string,\n },query: ClusterBRServiceListClusterBRTasksParams, }, out: { param: {\n clusterId: string,\n },query: ClusterBRServiceListClusterBRTasksParams, } }>\nexport type ClusterBRServiceDetectClusterContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/backup:detect', { in: { param: {\n clusterId: string,\n }, }, out: { param: {\n clusterId: string,\n }, } }>\nexport type MetricsServiceGetClusterMetricDataContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/metrics/:name/data', { in: { param: {\n clusterId: string,\n    name: string,\n },query: MetricsServiceGetClusterMetricDataParams, }, out: { param: {\n clusterId: string,\n    name: string,\n },query: MetricsServiceGetClusterMetricDataParams, } }>\nexport type MetricsServiceGetClusterMetricInstanceContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/metrics/:name/instance', { in: { param: {\n clusterId: string,\n    name: string,\n }, }, out: { param: {\n clusterId: string,\n    name: string,\n }, } }>\nexport type DiagnosisServiceGetResourceGroupListContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/resourcegroups', { in: { param: {\n clusterId: string,\n }, }, out: { param: {\n clusterId: string,\n }, } }>\nexport type ClusterServiceGetProcessListContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/sessions', { in: { param: {\n clusterId: string,\n }, }, out: { param: {\n clusterId: string,\n }, } }>\nexport type ClusterServiceDeleteProcessContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/sessions/:sessionId', { in: { param: {\n clusterId: string,\n    sessionId: string,\n }, }, out: { param: {\n clusterId: string,\n    sessionId: string,\n }, } }>\nexport type DiagnosisServiceGetSlowQueryListContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/slowqueries', { in: { param: {\n clusterId: string,\n },query: DiagnosisServiceGetSlowQueryListParams, }, out: { param: {\n clusterId: string,\n },query: DiagnosisServiceGetSlowQueryListParams, } }>\nexport type DiagnosisServiceGetSlowQueryAvailableAdvancedFiltersContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/slowqueries/advancedFilters', { in: { param: {\n clusterId: string,\n }, }, out: { param: {\n clusterId: string,\n }, } }>\nexport type DiagnosisServiceGetSlowQueryAvailableAdvancedFilterInfoContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/slowqueries/advancedFilters/:filterName', { in: { param: {\n clusterId: string,\n    filterName: string,\n }, }, out: { param: {\n clusterId: string,\n    filterName: string,\n }, } }>\nexport type DiagnosisServiceDownloadSlowQueryListContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/slowqueries/download', { in: { param: {\n clusterId: string,\n },query: DiagnosisServiceDownloadSlowQueryListParams, }, out: { param: {\n clusterId: string,\n },query: DiagnosisServiceDownloadSlowQueryListParams, } }>\nexport type DiagnosisServiceGetSlowQueryAvailableFieldsContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/slowqueries/fields', { in: { param: {\n clusterId: string,\n }, }, out: { param: {\n clusterId: string,\n }, } }>\nexport type DiagnosisServiceGetSlowQueryDetailContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/slowqueries/:digest', { in: { param: {\n clusterId: string,\n    digest: string,\n },query: DiagnosisServiceGetSlowQueryDetailParams, }, out: { param: {\n clusterId: string,\n    digest: string,\n },query: DiagnosisServiceGetSlowQueryDetailParams, } }>\nexport type DiagnosisServiceAddSqlLimitContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/sqllimits:addSqlLimit', { in: { param: {\n clusterId: string,\n },json: DiagnosisServiceAddSqlLimitBody, }, out: { param: {\n clusterId: string,\n },json: DiagnosisServiceAddSqlLimitBody, } }>\nexport type DiagnosisServiceCheckSqlLimitSupportContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/sqllimits:checkSupport', { in: { param: {\n clusterId: string,\n }, }, out: { param: {\n clusterId: string,\n }, } }>\nexport type DiagnosisServiceRemoveSqlLimitContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/sqllimits:removeSqlLimit', { in: { param: {\n clusterId: string,\n },json: DiagnosisServiceRemoveSqlLimitBody, }, out: { param: {\n clusterId: string,\n },json: DiagnosisServiceRemoveSqlLimitBody, } }>\nexport type DiagnosisServiceGetSqlLimitListContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/sqllimits:showSqlLimit', { in: { param: {\n clusterId: string,\n },query: DiagnosisServiceGetSqlLimitListParams, }, out: { param: {\n clusterId: string,\n },query: DiagnosisServiceGetSqlLimitListParams, } }>\nexport type DiagnosisServiceGetSqlPlanListContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/sqlplans', { in: { param: {\n clusterId: string,\n },query: DiagnosisServiceGetSqlPlanListParams, }, out: { param: {\n clusterId: string,\n },query: DiagnosisServiceGetSqlPlanListParams, } }>\nexport type DiagnosisServiceBindSqlPlanContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/sqlplans/:planDigest:bindSqlPlan', { in: { param: {\n clusterId: string,\n    planDigest: string,\n }, }, out: { param: {\n clusterId: string,\n    planDigest: string,\n }, } }>\nexport type DiagnosisServiceCheckSqlPlanSupportContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/sqlplans:checkSupport', { in: { param: {\n clusterId: string,\n }, }, out: { param: {\n clusterId: string,\n }, } }>\nexport type DiagnosisServiceGetSqlPlanBindingListContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/sqlplans:showSqlPlanBinding', { in: { param: {\n clusterId: string,\n },query: DiagnosisServiceGetSqlPlanBindingListParams, }, out: { param: {\n clusterId: string,\n },query: DiagnosisServiceGetSqlPlanBindingListParams, } }>\nexport type DiagnosisServiceUnbindSqlPlanContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/sqlplans:unbindSqlPlan', { in: { param: {\n clusterId: string,\n },query: DiagnosisServiceUnbindSqlPlanParams, }, out: { param: {\n clusterId: string,\n },query: DiagnosisServiceUnbindSqlPlanParams, } }>\nexport type DiagnosisServiceGetTopSqlListContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/topsqls', { in: { param: {\n clusterId: string,\n },query: DiagnosisServiceGetTopSqlListParams, }, out: { param: {\n clusterId: string,\n },query: DiagnosisServiceGetTopSqlListParams, } }>\nexport type DiagnosisServiceGetTopSqlAvailableAdvancedFiltersContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/topsqls/advancedFilters', { in: { param: {\n clusterId: string,\n }, }, out: { param: {\n clusterId: string,\n }, } }>\nexport type DiagnosisServiceGetTopSqlAvailableAdvancedFilterInfoContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/topsqls/advancedFilters/:filterName', { in: { param: {\n clusterId: string,\n    filterName: string,\n }, }, out: { param: {\n clusterId: string,\n    filterName: string,\n }, } }>\nexport type DiagnosisServiceGetTopSqlConfigsContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/topsqls/configs', { in: { param: {\n clusterId: string,\n }, }, out: { param: {\n clusterId: string,\n }, } }>\nexport type DiagnosisServiceUpdateTopSqlConfigsContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/topsqls/configs', { in: { param: {\n clusterId: string,\n },json: DiagnosisServiceUpdateTopSqlConfigsBody, }, out: { param: {\n clusterId: string,\n },json: DiagnosisServiceUpdateTopSqlConfigsBody, } }>\nexport type DiagnosisServiceGetTopSqlAvailableFieldsContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/topsqls/fields', { in: { param: {\n clusterId: string,\n }, }, out: { param: {\n clusterId: string,\n }, } }>\nexport type DiagnosisServiceGetTopSqlDetailContext<E extends Env = any> = Context<E, '/api/v2/clusters/:clusterId/topsqls/:digest', { in: { param: {\n clusterId: string,\n    digest: string,\n },query: DiagnosisServiceGetTopSqlDetailParams, }, out: { param: {\n clusterId: string,\n    digest: string,\n },query: DiagnosisServiceGetTopSqlDetailParams, } }>\nexport type CredentialServiceListCredentialsContext<E extends Env = any> = Context<E, '/api/v2/credentials', { in: { query: CredentialServiceListCredentialsParams, }, out: { query: CredentialServiceListCredentialsParams, } }>\nexport type CredentialServiceCreateCredentialContext<E extends Env = any> = Context<E, '/api/v2/credentials', { in: { json: V2Credential, }, out: { json: V2Credential, } }>\nexport type CredentialServiceGetCredentialContext<E extends Env = any> = Context<E, '/api/v2/credentials/:credentialId', { in: { param: {\n credentialId: string,\n }, }, out: { param: {\n credentialId: string,\n }, } }>\nexport type CredentialServiceDeleteCredentialContext<E extends Env = any> = Context<E, '/api/v2/credentials/:credentialId', { in: { param: {\n credentialId: string,\n }, }, out: { param: {\n credentialId: string,\n }, } }>\nexport type CredentialServiceUpdateCredentialContext<E extends Env = any> = Context<E, '/api/v2/credentials/:credentialId', { in: { param: {\n credentialId: string,\n },json: CredentialServiceUpdateCredentialBody, }, out: { param: {\n credentialId: string,\n },json: CredentialServiceUpdateCredentialBody, } }>\nexport type CredentialServiceDownloadRSAKeyContext<E extends Env = any> = Context<E, '/api/v2/credentials/:credentialId:downloadRsaKey', { in: { param: {\n credentialId: string,\n }, }, out: { param: {\n credentialId: string,\n }, } }>\nexport type CredentialServiceGenerateRSAKeyContext<E extends Env = any> = Context<E, '/api/v2/credentials:generateRsaKey', { in: { json: V2GenerateRSAKeyRequest, }, out: { json: V2GenerateRSAKeyRequest, } }>\nexport type CredentialServiceValidateConnectionContext<E extends Env = any> = Context<E, '/api/v2/credentials:validateConnection', { in: { json: V2ValidateConnectionRequest, }, out: { json: V2ValidateConnectionRequest, } }>\nexport type HostServiceListHostsContext<E extends Env = any> = Context<E, '/api/v2/hosts', { in: { query: HostServiceListHostsParams, }, out: { query: HostServiceListHostsParams, } }>\nexport type HostServiceCreateHostsContext<E extends Env = any> = Context<E, '/api/v2/hosts', { in: { json: V2CreateHost, }, out: { json: V2CreateHost, } }>\nexport type HostServiceImportContext<E extends Env = any> = Context<E, '/api/v2/hosts/import/tasks', { in: { json: HostServiceImportBody, }, out: { json: HostServiceImportBody, } }>\nexport type HostServiceImportTaskContext<E extends Env = any> = Context<E, '/api/v2/hosts/import/tasks/:taskId', { in: { param: {\n taskId: string,\n }, }, out: { param: {\n taskId: string,\n }, } }>\nexport type HostServiceHostConfirmContext<E extends Env = any> = Context<E, '/api/v2/hosts/import/tasks/:taskId:confirm', { in: { param: {\n taskId: string,\n },json: HostServiceHostConfirmBody, }, out: { param: {\n taskId: string,\n },json: HostServiceHostConfirmBody, } }>\nexport type HostServiceGetHostContext<E extends Env = any> = Context<E, '/api/v2/hosts/:hostId', { in: { param: {\n hostId: string,\n }, }, out: { param: {\n hostId: string,\n }, } }>\nexport type HostServiceDeleteContext<E extends Env = any> = Context<E, '/api/v2/hosts/:hostId', { in: { param: {\n hostId: string,\n }, }, out: { param: {\n hostId: string,\n }, } }>\nexport type HostServiceUpdateHostContext<E extends Env = any> = Context<E, '/api/v2/hosts/:hostId', { in: { param: {\n hostId: string,\n },json: V2HostServiceUpdateHostBody, }, out: { param: {\n hostId: string,\n },json: V2HostServiceUpdateHostBody, } }>\nexport type HostServiceGetDisksContext<E extends Env = any> = Context<E, '/api/v2/hosts/:hostId/disks', { in: { param: {\n hostId: string,\n }, }, out: { param: {\n hostId: string,\n }, } }>\nexport type MetricsServiceGetHostMetricDataContext<E extends Env = any> = Context<E, '/api/v2/hosts/:hostId/metrics/:name/data', { in: { param: {\n hostId: string,\n    name: string,\n },query: MetricsServiceGetHostMetricDataParams, }, out: { param: {\n hostId: string,\n    name: string,\n },query: MetricsServiceGetHostMetricDataParams, } }>\nexport type HostServiceReportContext<E extends Env = any> = Context<E, '/api/v2/hosts/:hostId/report/:reportId', { in: { param: {\n hostId: string,\n    reportId: string,\n }, }, out: { param: {\n hostId: string,\n    reportId: string,\n }, } }>\nexport type HostServiceGetTiDBProcessesContext<E extends Env = any> = Context<E, '/api/v2/hosts/:hostId/tidbProcesses', { in: { param: {\n hostId: string,\n }, }, out: { param: {\n hostId: string,\n }, } }>\nexport type HostServiceFixContext<E extends Env = any> = Context<E, '/api/v2/hosts/:hostId:fix', { in: { param: {\n hostId: string,\n }, }, out: { param: {\n hostId: string,\n }, } }>\nexport type HostServiceCheckContext<E extends Env = any> = Context<E, '/api/v2/hosts/:hostId:systemCheck', { in: { param: {\n hostId: string,\n }, }, out: { param: {\n hostId: string,\n }, } }>\nexport type HostServiceBatchDeleteContext<E extends Env = any> = Context<E, '/api/v2/hosts:batchDelete', { in: { json: V2BatchDeleteRequest, }, out: { json: V2BatchDeleteRequest, } }>\nexport type HostServiceDownloadListHostsContext<E extends Env = any> = Context<E, '/api/v2/hosts:download', { in: { query: HostServiceDownloadListHostsParams, }, out: { query: HostServiceDownloadListHostsParams, } }>\nexport type HostServiceDownloadHostTemplateContext<E extends Env = any> = Context<E, '/api/v2/hosts:downloadHostTemplate'>\nexport type LicenseServiceGetLicenseContext<E extends Env = any> = Context<E, '/api/v2/license'>\nexport type LicenseServiceGetDeviceCodeContext<E extends Env = any> = Context<E, '/api/v2/license/devicecode'>\nexport type LicenseServiceActivateLicenseContext<E extends Env = any> = Context<E, '/api/v2/license:activate', { in: { json: LicenseServiceActivateLicenseBody, }, out: { json: LicenseServiceActivateLicenseBody, } }>\nexport type LicenseServiceActivateFreeLicenseContext<E extends Env = any> = Context<E, '/api/v2/license:trial'>\nexport type LocationServiceListLocationsContext<E extends Env = any> = Context<E, '/api/v2/locations', { in: { query: LocationServiceListLocationsParams, }, out: { query: LocationServiceListLocationsParams, } }>\nexport type LocationServiceCreateLocationsContext<E extends Env = any> = Context<E, '/api/v2/locations', { in: { json: V2Locations, }, out: { json: V2Locations, } }>\nexport type LocationServiceGetLocationsContext<E extends Env = any> = Context<E, '/api/v2/locations/:locationId', { in: { param: {\n locationId: string,\n }, }, out: { param: {\n locationId: string,\n }, } }>\nexport type LocationServiceDeleteLocationContext<E extends Env = any> = Context<E, '/api/v2/locations/:locationId', { in: { param: {\n locationId: string,\n }, }, out: { param: {\n locationId: string,\n }, } }>\nexport type LocationServiceUpdateLocationsContext<E extends Env = any> = Context<E, '/api/v2/locations/:locationId', { in: { param: {\n locationId: string,\n },json: LocationServiceUpdateLocationsBody, }, out: { param: {\n locationId: string,\n },json: LocationServiceUpdateLocationsBody, } }>\nexport type UserServiceLoginContext<E extends Env = any> = Context<E, '/api/v2/login', { in: { json: V2LoginRequest, }, out: { json: V2LoginRequest, } }>\nexport type UserServiceLogoutContext<E extends Env = any> = Context<E, '/api/v2/logout'>\nexport type MetricsServiceGetMetricsContext<E extends Env = any> = Context<E, '/api/v2/metrics', { in: { query: MetricsServiceGetMetricsParams, }, out: { query: MetricsServiceGetMetricsParams, } }>\nexport type MetricsServiceGetTopMetricConfigContext<E extends Env = any> = Context<E, '/api/v2/overview/metrics/config'>\nexport type MetricsServiceGetTopMetricDataContext<E extends Env = any> = Context<E, '/api/v2/overview/metrics/:name/data', { in: { param: {\n name: string,\n },query: MetricsServiceGetTopMetricDataParams, }, out: { param: {\n name: string,\n },query: MetricsServiceGetTopMetricDataParams, } }>\nexport type MetricsServiceGetOverviewStatusContext<E extends Env = any> = Context<E, '/api/v2/overview/status', { in: { query: MetricsServiceGetOverviewStatusParams, }, out: { query: MetricsServiceGetOverviewStatusParams, } }>\nexport type RoleServiceListRolesContext<E extends Env = any> = Context<E, '/api/v2/roles', { in: { query: RoleServiceListRolesParams, }, out: { query: RoleServiceListRolesParams, } }>\nexport type RoleServiceCreateRoleContext<E extends Env = any> = Context<E, '/api/v2/roles', { in: { json: NonReadonly<V2Role>, }, out: { json: NonReadonly<V2Role>, } }>\nexport type RoleServiceDeleteRoleContext<E extends Env = any> = Context<E, '/api/v2/roles/:roleId', { in: { param: {\n roleId: number,\n }, }, out: { param: {\n roleId: number,\n }, } }>\nexport type RoleServiceUpdateRoleContext<E extends Env = any> = Context<E, '/api/v2/roles/:roleId', { in: { param: {\n roleId: number,\n },json: RoleServiceUpdateRoleBody, }, out: { param: {\n roleId: number,\n },json: RoleServiceUpdateRoleBody, } }>\nexport type TagServiceListTagsContext<E extends Env = any> = Context<E, '/api/v2/tags', { in: { query: TagServiceListTagsParams, }, out: { query: TagServiceListTagsParams, } }>\nexport type TagServiceCreateTagContext<E extends Env = any> = Context<E, '/api/v2/tags', { in: { json: Tagv2Tag, }, out: { json: Tagv2Tag, } }>\nexport type TagServiceGetTagContext<E extends Env = any> = Context<E, '/api/v2/tags/:tagId', { in: { param: {\n tagId: string,\n }, }, out: { param: {\n tagId: string,\n }, } }>\nexport type TagServiceDeleteTagContext<E extends Env = any> = Context<E, '/api/v2/tags/:tagId', { in: { param: {\n tagId: string,\n }, }, out: { param: {\n tagId: string,\n }, } }>\nexport type TagServiceUpdateTagContext<E extends Env = any> = Context<E, '/api/v2/tags/:tagId', { in: { param: {\n tagId: string,\n },json: TagServiceUpdateTagBody, }, out: { param: {\n tagId: string,\n },json: TagServiceUpdateTagBody, } }>\nexport type TagServiceGetTagWithBindingsContext<E extends Env = any> = Context<E, '/api/v2/tags/:tagId:getWithBindings', { in: { param: {\n tagId: string,\n }, }, out: { param: {\n tagId: string,\n }, } }>\nexport type TagServiceBatchCreateTagsContext<E extends Env = any> = Context<E, '/api/v2/tags:batchCreate', { in: { json: V2BatchCreateTagsRequest, }, out: { json: V2BatchCreateTagsRequest, } }>\nexport type TagServiceBindResourceContext<E extends Env = any> = Context<E, '/api/v2/tags:bindResource', { in: { json: V2BindResourceRequest, }, out: { json: V2BindResourceRequest, } }>\nexport type TagServiceBindTagContext<E extends Env = any> = Context<E, '/api/v2/tags:bindTag', { in: { json: V2BindTagRequest, }, out: { json: V2BindTagRequest, } }>\nexport type TagServiceListTagsByResourceTypeContext<E extends Env = any> = Context<E, '/api/v2/tags:listByResourceType', { in: { query: TagServiceListTagsByResourceTypeParams, }, out: { query: TagServiceListTagsByResourceTypeParams, } }>\nexport type TagServiceListTagKeysContext<E extends Env = any> = Context<E, '/api/v2/tags:listKeys', { in: { query: TagServiceListTagKeysParams, }, out: { query: TagServiceListTagKeysParams, } }>\nexport type TagServiceListTagsWithBindingsContext<E extends Env = any> = Context<E, '/api/v2/tags:listWithBindings', { in: { query: TagServiceListTagsWithBindingsParams, }, out: { query: TagServiceListTagsWithBindingsParams, } }>\nexport type TiupsServiceListTiupsContext<E extends Env = any> = Context<E, '/api/v2/tiups', { in: { query: TiupsServiceListTiupsParams, }, out: { query: TiupsServiceListTiupsParams, } }>\nexport type TiupsServiceCreateTiupsContext<E extends Env = any> = Context<E, '/api/v2/tiups', { in: { json: Tiupv2CreateTiups, }, out: { json: Tiupv2CreateTiups, } }>\nexport type TiupsServiceGetTiupsContext<E extends Env = any> = Context<E, '/api/v2/tiups/:tiupId', { in: { param: {\n tiupId: string,\n }, }, out: { param: {\n tiupId: string,\n }, } }>\nexport type TiupsServiceDeleteTiupsContext<E extends Env = any> = Context<E, '/api/v2/tiups/:tiupId', { in: { param: {\n tiupId: string,\n }, }, out: { param: {\n tiupId: string,\n }, } }>\nexport type TiupsServiceUpdateTiupsContext<E extends Env = any> = Context<E, '/api/v2/tiups/:tiupId', { in: { param: {\n tiupId: string,\n },json: V2TiupsServiceUpdateTiupsBody, }, out: { param: {\n tiupId: string,\n },json: V2TiupsServiceUpdateTiupsBody, } }>\nexport type TiupsServiceGetTiupsClusterContext<E extends Env = any> = Context<E, '/api/v2/tiups/:tiupId/clusters', { in: { param: {\n tiupId: string,\n }, }, out: { param: {\n tiupId: string,\n }, } }>\nexport type UserServiceListUsersContext<E extends Env = any> = Context<E, '/api/v2/users', { in: { query: UserServiceListUsersParams, }, out: { query: UserServiceListUsersParams, } }>\nexport type UserServiceCreateUserContext<E extends Env = any> = Context<E, '/api/v2/users', { in: { json: NonReadonly<V2User>, }, out: { json: NonReadonly<V2User>, } }>\nexport type UserServiceGetUserProfileContext<E extends Env = any> = Context<E, '/api/v2/users/profile'>\nexport type UserServiceGetUserContext<E extends Env = any> = Context<E, '/api/v2/users/:userId', { in: { param: {\n userId: string,\n }, }, out: { param: {\n userId: string,\n }, } }>\nexport type UserServiceDeleteUserContext<E extends Env = any> = Context<E, '/api/v2/users/:userId', { in: { param: {\n userId: string,\n }, }, out: { param: {\n userId: string,\n }, } }>\nexport type UserServiceUpdateUserContext<E extends Env = any> = Context<E, '/api/v2/users/:userId', { in: { param: {\n userId: string,\n },json: UserServiceUpdateUserBody, }, out: { param: {\n userId: string,\n },json: UserServiceUpdateUserBody, } }>\nexport type UserServiceResetPasswordContext<E extends Env = any> = Context<E, '/api/v2/users/:userId:resetPassword', { in: { param: {\n userId: string,\n },json: UserServiceResetPasswordBody, }, out: { param: {\n userId: string,\n },json: UserServiceResetPasswordBody, } }>\nexport type UserServiceChangePasswordContext<E extends Env = any> = Context<E, '/api/v2/users:changePassword', { in: { json: V2ChangePasswordRequest, }, out: { json: V2ChangePasswordRequest, } }>\nexport type UserServiceValidateSessionContext<E extends Env = any> = Context<E, '/api/v2/users:validateSession'>\nexport type ApiKeyServiceGetTemErrorDetailContext<E extends Env = any> = Context<E, '/documentation/errorDetail'>"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/index.schemas.ts",
    "content": "/**\n * Generated by orval v7.3.0 🍺\n * Do not edit manually.\n * Azores Open API\n * OpenAPI spec version: 2.0.0\n */\nexport type UserServiceChangePassword200 = { [key: string]: unknown };\n\nexport type UserServiceResetPassword200 = { [key: string]: unknown };\n\nexport type UserServiceDeleteUser200 = { [key: string]: unknown };\n\nexport type UserServiceListUsersParams = {\n/**\n * The number of users to retrieve per page.\n */\npageSize?: number;\n/**\n * Pagination token for retrieving the next page of users.\n */\npageToken?: string;\n/**\n * The number of users to skip for pagination purposes.\n */\nskip?: number;\n/**\n * The sorting criteria for the user list.\n */\norderBy?: string;\n/**\n * Filter users by username using a \"like\" operation.\n */\nnameLike?: string;\n/**\n * Filter users by email using a \"like\" operation.\n */\nemailLike?: string;\n/**\n * Filter users by role name.\n */\nroleName?: string;\n};\n\nexport type TiupsServiceDeleteTiups200 = { [key: string]: unknown };\n\nexport type TiupsServiceListTiupsParams = {\n/**\n * page size\n */\npageSize?: number;\n/**\n * page token\n */\npageToken?: string;\n/**\n * Skip\n */\nskip?: number;\n/**\n * order_by\n */\norderBy?: string;\n/**\n * the Tiups key of the Tiups\n */\nsearchValue?: string;\n/**\n * the Tiups tag_ids of the tagIds\n */\ntagIds?: string[];\n/**\n * the Tiups host_ids of the tagIds\n */\nhostIds?: string[];\n};\n\nexport type TagServiceListTagsWithBindingsParams = {\n/**\n * page size\n */\npageSize?: number;\n/**\n * page token\n */\npageToken?: string;\n/**\n * skip\n */\nskip?: number;\n/**\n * order_by\n */\norderBy?: string;\n/**\n * the tag which the tag key in\n */\ntagKeys?: string[];\n/**\n * the tag which the tag value like\n */\ntagValueLike?: string;\n};\n\nexport type TagServiceListTagKeysResourceType = typeof TagServiceListTagKeysResourceType[keyof typeof TagServiceListTagKeysResourceType];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const TagServiceListTagKeysResourceType = {\n  TAG_BIND_RESOURCE_TYPE_UNSPECIFIED: 'TAG_BIND_RESOURCE_TYPE_UNSPECIFIED',\n  HOST: 'HOST',\n  TIUP: 'TIUP',\n  CLUSTER: 'CLUSTER',\n} as const;\n\nexport type TagServiceListTagKeysParams = {\n/**\n * page size\n */\npageSize?: number;\n/**\n * page token\n */\npageToken?: string;\n/**\n * skip\n */\nskip?: number;\n/**\n * the keyword which tag key similar to\n */\nkeyword?: string;\n/**\n * the resource type of the tag has bound with\n\n - TAG_BIND_RESOURCE_TYPE_UNSPECIFIED: resource type unspecified\n - HOST: resource type host\n - TIUP: resource type tiup\n - CLUSTER: resource type cluster\n */\nresourceType?: TagServiceListTagKeysResourceType;\n};\n\nexport type TagServiceListTagsByResourceTypeResourceType = typeof TagServiceListTagsByResourceTypeResourceType[keyof typeof TagServiceListTagsByResourceTypeResourceType];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const TagServiceListTagsByResourceTypeResourceType = {\n  TAG_BIND_RESOURCE_TYPE_UNSPECIFIED: 'TAG_BIND_RESOURCE_TYPE_UNSPECIFIED',\n  HOST: 'HOST',\n  TIUP: 'TIUP',\n  CLUSTER: 'CLUSTER',\n} as const;\n\nexport type TagServiceListTagsByResourceTypeParams = {\n/**\n * page size\n */\npageSize?: number;\n/**\n * page token\n */\npageToken?: string;\n/**\n * skip\n */\nskip?: number;\n/**\n * the tag key which the tag values belong to\n */\ntagKey?: string;\n/**\n * the keyword which tag values similar to\n */\nkeyword?: string;\n/**\n * the resource type of the tag has bound with\n\n - TAG_BIND_RESOURCE_TYPE_UNSPECIFIED: resource type unspecified\n - HOST: resource type host\n - TIUP: resource type tiup\n - CLUSTER: resource type cluster\n */\nresourceType?: TagServiceListTagsByResourceTypeResourceType;\n};\n\nexport type TagServiceDeleteTag200 = { [key: string]: unknown };\n\nexport type TagServiceListTagsParams = {\n/**\n * page size\n */\npageSize?: number;\n/**\n * page token\n */\npageToken?: string;\n/**\n * skip\n */\nskip?: number;\n/**\n * order_by\n */\norderBy?: string;\n};\n\nexport type RoleServiceDeleteRole200 = { [key: string]: unknown };\n\nexport type RoleServiceListRolesParams = {\n/**\n * Page size\n */\npageSize?: number;\n/**\n * Page token\n */\npageToken?: string;\n/**\n * Skip\n */\nskip?: number;\n/**\n * order_by\n */\norderBy?: string;\n/**\n * role_name_like\n */\nroleNameLike?: string;\n/**\n * The name of the role\n */\nroleName?: string;\n};\n\nexport type MetricsServiceGetOverviewStatusParams = {\n/**\n * Task start time in Unix timestamp format\n */\ntaskStartTime?: string;\n/**\n * Task end time in Unix timestamp format\n */\ntaskEndTime?: string;\n};\n\nexport type MetricsServiceGetTopMetricDataParams = {\n/**\n * Start time for the query\n */\nstartTime: string;\n/**\n * End time for the query\n */\nendTime: string;\n/**\n * Step time for the query\n */\nstep?: string;\n/**\n * Limit for the number of top results\n */\nlimit?: string;\n};\n\nexport type MetricsServiceGetMetricsGroup = typeof MetricsServiceGetMetricsGroup[keyof typeof MetricsServiceGetMetricsGroup];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const MetricsServiceGetMetricsGroup = {\n  unspecified: 'unspecified',\n  overview: 'overview',\n  basic: 'basic',\n  advanced: 'advanced',\n  resource: 'resource',\n  performance: 'performance',\n  process: 'process',\n} as const;\n\nexport type MetricsServiceGetMetricsClass = typeof MetricsServiceGetMetricsClass[keyof typeof MetricsServiceGetMetricsClass];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const MetricsServiceGetMetricsClass = {\n  unspecified: 'unspecified',\n  cluster: 'cluster',\n  host: 'host',\n  overview: 'overview',\n} as const;\n\nexport type MetricsServiceGetMetricsParams = {\n/**\n * Level 1 classification\n\n - unspecified: Unspecified\n - cluster: Cluster metrics\n - host: Host metrics\n - overview: Overview metrics\n */\nclass?: MetricsServiceGetMetricsClass;\n/**\n * Level 2 grouping\n\n - unspecified: Unspecified group\n - overview: Overview group\n - basic: Basic group\n - advanced: Advanced group\n - resource: Resource group\n - performance: Performance group\n - process: Process group\n */\ngroup?: MetricsServiceGetMetricsGroup;\n/**\n * Level 3 type\n */\ntype?: string;\n/**\n * The metric name\n */\nname?: string;\n};\n\nexport type UserServiceLogout200 = { [key: string]: unknown };\n\nexport type UserServiceLogin200 = { [key: string]: unknown };\n\nexport type LocationServiceDeleteLocation200 = { [key: string]: unknown };\n\nexport type LocationServiceListLocationsLocationKey = typeof LocationServiceListLocationsLocationKey[keyof typeof LocationServiceListLocationsLocationKey];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const LocationServiceListLocationsLocationKey = {\n  zone: 'zone',\n  dc: 'dc',\n  rack: 'rack',\n} as const;\n\nexport type LocationServiceListLocationsParams = {\n/**\n * page size\n */\npageSize?: number;\n/**\n * page token\n */\npageToken?: string;\n/**\n * Skip\n */\nskip?: number;\n/**\n * order_by\n */\norderBy?: string;\n/**\n * location key  (e.g., \"zone\", \"dc\")\n */\nlocationKey?: LocationServiceListLocationsLocationKey;\n/**\n * the Location value of the Location\n */\nlocationValue?: string;\n/**\n * the Location parent_Id of the Location\n */\nparentId?: string;\n};\n\nexport type LicenseServiceActivateLicenseBody = {\n  /** The content of the license file\n\nThe license file to upload to activate the license */\n  license: Blob;\n};\n\nexport type HostServiceDownloadListHostsParams = {\n/**\n * Page size\n */\npageSize?: number;\n/**\n * Page token\n */\npageToken?: string;\n/**\n * Skip\n */\nskip?: number;\n/**\n * order_by\n */\norderBy?: string;\n/**\n * The name of the user\n */\nsearchValue?: string;\n/**\n * location_ids\n */\nlocationIds?: string[];\n/**\n * tag_ids\n */\ntagIds?: string[];\n};\n\nexport type HostServiceBatchDelete200 = { [key: string]: unknown };\n\nexport type MetricsServiceGetHostMetricDataParams = {\n/**\n * Start time in Unix timestamp format\n */\nstartTime: string;\n/**\n * End time in Unix timestamp format\n */\nendTime: string;\n/**\n * Step time in seconds\n */\nstep?: string;\n/**\n * Line Label for the metric\n */\nlabel?: string;\n/**\n * Time Range for the query\n */\nrange?: string;\n};\n\nexport type HostServiceDelete200 = { [key: string]: unknown };\n\nexport type HostServiceImportBody = {\n  /** The Credential_Id of the Import */\n  credentialId: string;\n  /** Upload a csv form data to host. */\n  hostData: Blob;\n};\n\nexport type HostServiceListHostsParams = {\n/**\n * Page size\n */\npageSize?: number;\n/**\n * Page token\n */\npageToken?: string;\n/**\n * Skip\n */\nskip?: number;\n/**\n * order_by\n */\norderBy?: string;\n/**\n * The name of the user\n */\nsearchValue?: string;\n/**\n * location_ids\n */\nlocationIds?: string[];\n/**\n * tag_ids\n */\ntagIds?: string[];\n};\n\nexport type CredentialServiceDeleteCredential200 = { [key: string]: unknown };\n\nexport type CredentialServiceListCredentialsCredentialType = typeof CredentialServiceListCredentialsCredentialType[keyof typeof CredentialServiceListCredentialsCredentialType];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const CredentialServiceListCredentialsCredentialType = {\n  CREDENTIAL_TYPE_UNSPECIFIED: 'CREDENTIAL_TYPE_UNSPECIFIED',\n  HOST: 'HOST',\n  TIDB: 'TIDB',\n} as const;\n\nexport type CredentialServiceListCredentialsParams = {\n/**\n * page size\n */\npageSize?: number;\n/**\n * page token\n */\npageToken?: string;\n/**\n * Skip\n */\nskip?: number;\n/**\n * order_by\n */\norderBy?: string;\n/**\n * the credential type of the credential\n\n - CREDENTIAL_TYPE_UNSPECIFIED: resource type unspecified\n - HOST: credential type host\n - TIDB: credential type tidb\n */\ncredentialType?: CredentialServiceListCredentialsCredentialType;\n/**\n * the credential id of the credential\n */\ncredentialId?: string;\n};\n\nexport type DiagnosisServiceGetTopSqlDetailParams = {\n/**\n * Begin time\n */\nbeginTime: string;\n/**\n * End time\n */\nendTime: string;\n/**\n * Plan digest list\n */\nplanDigest?: string[];\n};\n\nexport type DiagnosisServiceGetTopSqlListParams = {\n/**\n * Begin time\n */\nbeginTime: string;\n/**\n * End time\n */\nendTime: string;\n/**\n * Database list\n */\ndb?: string[];\n/**\n * SQL Text, used for fuzzy query\n */\ntext?: string;\n/**\n * Order by field\n */\norderBy?: string;\n/**\n * Is descending order\n */\nisDesc?: boolean;\n/**\n * Fields to select, e.g., \"Query,Digest\"\n */\nfields?: string;\n/**\n * Page size\n */\npageSize?: number;\n/**\n * Page token\n */\npageToken?: string;\n/**\n * Skip\n */\nskip?: number;\n/**\n * Advanced filters, such as \"digest = xxx\"\n */\nadvancedFilter?: string[];\n/**\n * Is group by time\n */\nisGroupByTime?: boolean;\n};\n\nexport type DiagnosisServiceUnbindSqlPlan200 = { [key: string]: unknown };\n\nexport type DiagnosisServiceUnbindSqlPlanParams = {\n/**\n * SQL digest\n */\ndigest: string;\n};\n\nexport type DiagnosisServiceGetSqlPlanBindingListParams = {\n/**\n * Begin time\n */\nbeginTime: string;\n/**\n * End time\n */\nendTime: string;\n/**\n * SQL digest\n */\ndigest: string;\n};\n\nexport type DiagnosisServiceBindSqlPlan200 = { [key: string]: unknown };\n\nexport type DiagnosisServiceGetSqlPlanListParams = {\n/**\n * Begin time\n */\nbeginTime: string;\n/**\n * End time\n */\nendTime: string;\n/**\n * SQL digest\n */\ndigest?: string;\n/**\n * Table name\n */\nschemaName?: string;\n};\n\nexport type DiagnosisServiceGetSqlLimitListParams = {\n/**\n * Watch text\n */\nwatchText: string;\n};\n\nexport type DiagnosisServiceRemoveSqlLimit200 = { [key: string]: unknown };\n\nexport type DiagnosisServiceAddSqlLimit200 = { [key: string]: unknown };\n\nexport type DiagnosisServiceGetSlowQueryDetailParams = {\n/**\n * Timestamp\n */\ntimestamp: number;\n/**\n * Connection ID\n */\nconnectionId: string;\n};\n\nexport type DiagnosisServiceDownloadSlowQueryListParams = {\n/**\n * Begin time in Unix timestamp\n */\nbeginTime: string;\n/**\n * End time in Unix timestamp\n */\nendTime: string;\n/**\n * List of databases\n */\ndb?: string[];\n/**\n * Search text\n */\ntext?: string;\n/**\n * Order by field\n */\norderBy?: string;\n/**\n * Is descending order\n */\nisDesc?: boolean;\n/**\n * Fields to select, e.g., \"Query,Digest\"\n */\nfields?: string;\n/**\n * Page size\n */\npageSize?: number;\n/**\n * Page token for pagination\n */\npageToken?: string;\n/**\n * Number of records to skip\n */\nskip?: number;\n/**\n * Advanced filters, such as \"digest = xxx\"\n */\nadvancedFilter?: string[];\n};\n\nexport type DiagnosisServiceGetSlowQueryListParams = {\n/**\n * Begin time in Unix timestamp\n */\nbeginTime: string;\n/**\n * End time in Unix timestamp\n */\nendTime: string;\n/**\n * List of databases\n */\ndb?: string[];\n/**\n * Search text\n */\ntext?: string;\n/**\n * Order by field\n */\norderBy?: string;\n/**\n * Is descending order\n */\nisDesc?: boolean;\n/**\n * Fields to select, e.g., \"Query,Digest\"\n */\nfields?: string;\n/**\n * Page size\n */\npageSize?: number;\n/**\n * Page token for pagination\n */\npageToken?: string;\n/**\n * Number of records to skip\n */\nskip?: number;\n/**\n * Advanced filters, such as \"digest = xxx\"\n */\nadvancedFilter?: string[];\n};\n\nexport type ClusterServiceDeleteProcess200 = { [key: string]: unknown };\n\nexport type MetricsServiceGetClusterMetricDataParams = {\n/**\n * Start time in Unix timestamp format\n */\nstartTime: string;\n/**\n * End time in Unix timestamp format\n */\nendTime: string;\n/**\n * Step time in seconds\n */\nstep?: string;\n/**\n * Line Label for the metric\n */\nlabel?: string;\n/**\n * Time Range for the query\n */\nrange?: string;\n};\n\nexport type ClusterBRServiceListClusterBRTasksStatus = typeof ClusterBRServiceListClusterBRTasksStatus[keyof typeof ClusterBRServiceListClusterBRTasksStatus];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const ClusterBRServiceListClusterBRTasksStatus = {\n  running: 'running',\n  finished: 'finished',\n  abnormal: 'abnormal',\n  stopped: 'stopped',\n} as const;\n\nexport type ClusterBRServiceListClusterBRTasksType = typeof ClusterBRServiceListClusterBRTasksType[keyof typeof ClusterBRServiceListClusterBRTasksType];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const ClusterBRServiceListClusterBRTasksType = {\n  full_backup: 'full_backup',\n  log_backup: 'log_backup',\n  restore_by_file: 'restore_by_file',\n  restore_by_time: 'restore_by_time',\n  all_backup: 'all_backup',\n  all_restore: 'all_restore',\n} as const;\n\nexport type ClusterBRServiceListClusterBRTasksParams = {\n/**\n * Page size\n */\npageSize?: number;\n/**\n * Page token\n */\npageToken?: string;\n/**\n * Skip\n */\nskip?: number;\n/**\n * order_by\n */\norderBy?: string;\n/**\n * The br task ID\n */\nbrTaskId?: string;\n/**\n * The cluster name\n */\nclusterName?: string;\n/**\n * Type of the br task\n\n - full_backup: Full backup\n - log_backup: Log backup\n - restore_by_file: Restore by file\n - restore_by_time: Restore by time\n - all_backup: All backup\n - all_restore: All restore\n */\ntype?: ClusterBRServiceListClusterBRTasksType;\n/**\n * Status of the br task\n\n - running: Running\n - finished: Finished\n - abnormal: Abnormal\n - stopped: Stopped\n */\nstatus?: ClusterBRServiceListClusterBRTasksStatus;\n};\n\nexport type ClusterBRServiceListClusterBackupRecordsParams = {\n/**\n * Page size\n */\npageSize?: number;\n/**\n * Page token\n */\npageToken?: string;\n/**\n * Skip\n */\nskip?: number;\n/**\n * order_by\n */\norderBy?: string;\n};\n\nexport type GlobalBRServiceStopBRTask200 = { [key: string]: unknown };\n\nexport type GlobalBRServiceStartBRTask200 = { [key: string]: unknown };\n\nexport type GlobalBRServiceDeleteBRTask200 = { [key: string]: unknown };\n\nexport type GlobalBRServiceDeleteBRTaskParams = {\n/**\n * delete_backup_file for whether delete the backup files or not\n */\ndeleteBackupFile?: boolean;\n};\n\nexport type GlobalBRServiceListBRTasksStatus = typeof GlobalBRServiceListBRTasksStatus[keyof typeof GlobalBRServiceListBRTasksStatus];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const GlobalBRServiceListBRTasksStatus = {\n  all: 'all',\n  running: 'running',\n  finished: 'finished',\n  abnormal: 'abnormal',\n  stopped: 'stopped',\n} as const;\n\nexport type GlobalBRServiceListBRTasksType = typeof GlobalBRServiceListBRTasksType[keyof typeof GlobalBRServiceListBRTasksType];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const GlobalBRServiceListBRTasksType = {\n  all: 'all',\n  full_backup: 'full_backup',\n  log_backup: 'log_backup',\n  restore_by_file: 'restore_by_file',\n  restore_by_time: 'restore_by_time',\n  all_backup: 'all_backup',\n  all_restore: 'all_restore',\n} as const;\n\nexport type GlobalBRServiceListBRTasksParams = {\n/**\n * Page size\n */\npageSize?: number;\n/**\n * Page token\n */\npageToken?: string;\n/**\n * Skip\n */\nskip?: number;\n/**\n * order_by\n */\norderBy?: string;\n/**\n * The br task ID\n */\nbrTaskId?: string;\n/**\n * The cluster ID\n */\nclusterId?: string;\n/**\n * The cluster name\n */\nclusterName?: string;\n/**\n * Type of the br task\n\n - all: All\n - full_backup: Full backup\n - log_backup: Log backup\n - restore_by_file: Restore by file\n - restore_by_time: Restore by time\n - all_backup: All backup\n - all_restore: All restore\n */\ntype?: GlobalBRServiceListBRTasksType;\n/**\n * Status of the br task\n\n - all: All\n - running: Running\n - finished: Finished\n - abnormal: Abnormal\n - stopped: Stopped\n */\nstatus?: GlobalBRServiceListBRTasksStatus;\n};\n\nexport type GlobalBRServiceGetBRSummaryParams = {\n/**\n * Number of top clusters\n */\ntop?: number;\n};\n\nexport type GlobalBRServiceDeleteBackupPolicy200 = { [key: string]: unknown };\n\nexport type GlobalBRServiceListBackupPoliciesParams = {\n/**\n * Page size\n */\npageSize?: number;\n/**\n * Page token\n */\npageToken?: string;\n/**\n * Skip\n */\nskip?: number;\n/**\n * order_by\n */\norderBy?: string;\n};\n\nexport type ApiKeyServiceDeleteApiKey200 = { [key: string]: unknown };\n\nexport type ApiKeyServiceListApiKeysParams = {\n/**\n * Page size\n */\npageSize?: number;\n/**\n * Page token\n */\npageToken?: string;\n/**\n * Skip\n */\nskip?: number;\n/**\n * order_by\n */\norderBy?: string;\n/**\n * The access_key of the apikey\n */\naccessKey?: string;\n/**\n * The access_key of the apikey\n */\ncreator?: string;\n/**\n * The status of the apikey\n */\nstatus?: string;\n};\n\nexport type V2BackupPolicyBody = V2BackupPolicy;\n\nexport interface V2ValidateSessionResponse {\n  userId: string;\n}\n\nexport interface V2ValidateConnectionResponse {\n  connectionResult?: string;\n  inaccessibleHosts?: string[];\n}\n\nexport interface V2ValidateConnectionRequest {\n  credentialId: string;\n}\n\nexport interface V2UserRole {\n  roleId: number;\n  roleName?: string;\n}\n\n/**\n * UserProfile represents the profile information of the authenticated user.\n */\nexport interface V2UserProfile {\n  /** The email address of the user. */\n  email: string;\n  /** The  name of the user. */\n  name: string;\n  /** The note of the user. */\n  note?: string;\n  /** The phone of the user. */\n  phone?: string;\n  /** The unique identifier of the user. */\n  userId: string;\n}\n\n/**\n * User represents a user resource containing detailed information about a user.\n */\nexport interface V2User {\n  /** The timestamp when the user was created. */\n  readonly createTime?: string;\n  /** The email address of the user. */\n  email?: string;\n  /** The full name of the user. */\n  name: string;\n  /** Additional notes about the user. */\n  note?: string;\n  /** The user's password (optional). */\n  password?: string;\n  /** The user's phone number. */\n  phone?: string;\n  /** The roles assigned to the user. */\n  roles?: V2UserRole[];\n  /** The timestamp when the user was last updated. */\n  readonly updateTime?: string;\n  /** The unique user ID of the user. */\n  userId: string;\n  /** The type of the user (e.g., admin, regular user). */\n  userType?: number;\n  /** A description of the user's type. */\n  userTypeDesc?: string;\n}\n\n/**\n * - all: All\n - full_backup: Full backup\n - log_backup: Log backup\n - restore_by_file: Restore by file\n - restore_by_time: Restore by time\n - all_backup: All backup\n - all_restore: All restore\n */\nexport type V2TypeEnumData = typeof V2TypeEnumData[keyof typeof V2TypeEnumData];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2TypeEnumData = {\n  all: 'all',\n  full_backup: 'full_backup',\n  log_backup: 'log_backup',\n  restore_by_file: 'restore_by_file',\n  restore_by_time: 'restore_by_time',\n  all_backup: 'all_backup',\n  all_restore: 'all_restore',\n} as const;\n\n/**\n * - automatic: automatic\n - manual: manual\n */\nexport type V2TriggerTypeEnumData = typeof V2TriggerTypeEnumData[keyof typeof V2TriggerTypeEnumData];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2TriggerTypeEnumData = {\n  automatic: 'automatic',\n  manual: 'manual',\n} as const;\n\nexport interface V2TopSqlDetail {\n  avg_affected_rows?: number;\n  avg_backoff_time?: number;\n  avg_commit_backoff_time?: number;\n  avg_commit_time?: number;\n  avg_compile_latency?: number;\n  avg_cop_process_time?: number;\n  avg_cop_wait_time?: number;\n  avg_disk?: number;\n  avg_get_commit_ts_time?: number;\n  avg_latency?: number;\n  avg_local_latch_wait_time?: number;\n  avg_mem?: number;\n  avg_parse_latency?: number;\n  avg_prewrite_regions?: number;\n  avg_prewrite_time?: number;\n  avg_process_time?: number;\n  avg_processed_keys?: number;\n  avg_resolve_lock_time?: number;\n  avg_rocksdb_block_cache_hit_count?: number;\n  avg_rocksdb_block_read_byte?: number;\n  avg_rocksdb_block_read_count?: number;\n  avg_rocksdb_delete_skipped_count?: number;\n  avg_rocksdb_key_skipped_count?: number;\n  avg_ru?: number;\n  avg_tidb_cpu_time?: number;\n  avg_tikv_cpu_time?: number;\n  avg_time_queued_by_rc?: number;\n  avg_total_keys?: number;\n  avg_txn_retry?: number;\n  avg_wait_time?: number;\n  avg_write_keys?: number;\n  avg_write_size?: number;\n  binary_plan?: string;\n  binary_plan_text?: string;\n  digest?: string;\n  digest_text?: string;\n  exec_count?: number;\n  first_seen?: number;\n  index_names?: string;\n  last_seen?: number;\n  max_backoff_time?: number;\n  max_commit_backoff_time?: number;\n  max_commit_time?: number;\n  max_compile_latency?: number;\n  max_cop_process_time?: number;\n  max_cop_wait_time?: number;\n  max_disk?: number;\n  max_get_commit_ts_time?: number;\n  max_latency?: number;\n  max_local_latch_wait_time?: number;\n  max_mem?: number;\n  max_parse_latency?: number;\n  max_prewrite_regions?: number;\n  max_prewrite_time?: number;\n  max_process_time?: number;\n  max_processed_keys?: number;\n  max_resolve_lock_time?: number;\n  max_rocksdb_block_cache_hit_count?: number;\n  max_rocksdb_block_read_byte?: number;\n  max_rocksdb_block_read_count?: number;\n  max_rocksdb_delete_skipped_count?: number;\n  max_rocksdb_key_skipped_count?: number;\n  max_ru?: number;\n  max_time_queued_by_rc?: number;\n  max_total_keys?: number;\n  max_txn_retry?: number;\n  max_wait_time?: number;\n  max_write_keys?: number;\n  max_write_size?: number;\n  min_latency?: number;\n  plan?: string;\n  plan_can_be_bound?: boolean;\n  plan_count?: number;\n  plan_digest?: string;\n  plan_hint?: string;\n  prev_sample_text?: string;\n  query_sample_text?: string;\n  related_schemas?: string;\n  resource_group?: string;\n  sample_user?: string;\n  schema_name?: string;\n  stmt_type?: string;\n  sum_backoff_times?: number;\n  sum_cop_task_num?: number;\n  sum_errors?: number;\n  sum_latency?: number;\n  sum_ru?: number;\n  sum_warnings?: number;\n  summary_begin_time?: number;\n  summary_end_time?: number;\n  table_names?: string;\n}\n\nexport interface V2TopSqlList {\n  data?: V2TopSqlDetail[];\n  nextPageToken?: string;\n  totalSize?: number;\n}\n\nexport interface V2TopSqlConfigs {\n  enable?: boolean;\n  historySize?: number;\n  internalQuery?: boolean;\n  maxSize?: number;\n  refreshInterval?: number;\n}\n\nexport interface V2TopSqlAvailableFields {\n  fields?: string[];\n}\n\nexport interface V2TopSqlAvailableAdvancedFilters {\n  filters?: string[];\n}\n\nexport interface V2TopSqlAvailableAdvancedFilterInfo {\n  name?: string;\n  type?: string;\n  unit?: string;\n  valueList?: string[];\n}\n\nexport interface V2TopMetricData {\n  data?: V2ExprQueryData[];\n  status?: string;\n}\n\nexport interface V2TopMetricConfig {\n  cacheFlushIntervalInMinutes?: number;\n}\n\nexport interface V2TiupsServiceUpdateTiupsBody {\n  tiups?: Tiupv2UpdateTiups;\n}\n\nexport interface V2TiupsClusters {\n  clusterId?: string;\n  clusterName?: string;\n  managed?: boolean;\n  metaPath?: string;\n  privateKeyPath?: string;\n  user?: string;\n  version?: string;\n}\n\nexport interface V2TiupsClustersResponse {\n  tiupsClusters?: V2TiupsClusters[];\n}\n\nexport interface V2TiupTags {\n  tagId?: string;\n  tagKey?: string;\n  tagValue: string;\n}\n\nexport interface V2Tiups {\n  credentialId?: string;\n  description?: string;\n  host?: V2TiupHost;\n  hostId?: string;\n  name?: string;\n  tags?: V2TiupTags[];\n  tiupHome?: string;\n  tiupId?: string;\n  version?: string;\n}\n\nexport type V2TiupHostHostType = typeof V2TiupHostHostType[keyof typeof V2TiupHostHostType];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2TiupHostHostType = {\n  VM: 'VM',\n  PM: 'PM',\n} as const;\n\nexport interface V2TiupCredential {\n  credentialId?: string;\n  credentialName?: string;\n  credentialType?: string;\n  userName?: string;\n}\n\nexport interface V2TiupHost {\n  createdTime?: string;\n  credential?: V2TiupCredential;\n  credentialId?: string;\n  hostId: string;\n  hostName?: string;\n  hostType?: V2TiupHostHostType;\n  ip?: string;\n  locationId?: string;\n  osArchitecture?: string;\n  osName?: string;\n  osRelease?: string;\n  osVersion?: string;\n  sshPort?: number;\n  updatedTime?: string;\n}\n\nexport interface V2TiDBProcesses {\n  cmd?: string;\n  pid?: number;\n  ppid?: number;\n  runningTime?: string;\n  startTime?: string;\n  uid?: string;\n}\n\nexport interface V2TiDBCredentialObject {\n  clusterId?: string;\n  clusterName?: string;\n  password: string;\n}\n\nexport interface V2Tags {\n  tagId?: string;\n  tagKey?: string;\n  tagValue: string;\n}\n\nexport interface V2TagWithBindObject {\n  bindObjects?: V2BindObject[];\n  tagInfo?: Tagv2Tag;\n}\n\n/**\n * - TAG_BIND_RESOURCE_TYPE_UNSPECIFIED: resource type unspecified\n - HOST: resource type host\n - TIUP: resource type tiup\n - CLUSTER: resource type cluster\n */\nexport type V2TagBindResourceType = typeof V2TagBindResourceType[keyof typeof V2TagBindResourceType];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2TagBindResourceType = {\n  TAG_BIND_RESOURCE_TYPE_UNSPECIFIED: 'TAG_BIND_RESOURCE_TYPE_UNSPECIFIED',\n  HOST: 'HOST',\n  TIUP: 'TIUP',\n  CLUSTER: 'CLUSTER',\n} as const;\n\n/**\n * - all: All\n - running: Running\n - finished: Finished\n - abnormal: Abnormal\n - stopped: Stopped\n */\nexport type V2StatusEnumData = typeof V2StatusEnumData[keyof typeof V2StatusEnumData];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2StatusEnumData = {\n  all: 'all',\n  running: 'running',\n  finished: 'finished',\n  abnormal: 'abnormal',\n  stopped: 'stopped',\n} as const;\n\nexport interface V2StatusCount {\n  count?: number;\n  status?: string;\n}\n\nexport interface V2SqlPlanList {\n  data?: V2TopSqlDetail[];\n}\n\nexport interface V2SqlPlanBindingList {\n  data?: V2SqlPlanBindingDetail[];\n}\n\nexport type V2SqlPlanBindingDetailStatus = typeof V2SqlPlanBindingDetailStatus[keyof typeof V2SqlPlanBindingDetailStatus];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2SqlPlanBindingDetailStatus = {\n  enabled: 'enabled',\n  using: 'using',\n  disabled: 'disabled',\n  deleted: 'deleted',\n  invalid: 'invalid',\n  rejected: 'rejected',\n  pending_verify: 'pending verify',\n} as const;\n\nexport type V2SqlPlanBindingDetailSource = typeof V2SqlPlanBindingDetailSource[keyof typeof V2SqlPlanBindingDetailSource];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2SqlPlanBindingDetailSource = {\n  manual: 'manual',\n  history: 'history',\n  capture: 'capture',\n  evolve: 'evolve',\n} as const;\n\nexport interface V2SqlPlanBindingDetail {\n  digest?: string;\n  planDigest?: string;\n  source?: V2SqlPlanBindingDetailSource;\n  status?: V2SqlPlanBindingDetailStatus;\n}\n\nexport type V2SqlLimitAction = typeof V2SqlLimitAction[keyof typeof V2SqlLimitAction];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2SqlLimitAction = {\n  DRYRUN: 'DRYRUN',\n  COOLDOWN: 'COOLDOWN',\n  KILL: 'KILL',\n} as const;\n\nexport interface V2SqlLimit {\n  action?: V2SqlLimitAction;\n  endTime?: string;\n  id?: string;\n  resourceGroupName?: string;\n  source?: string;\n  startTime?: string;\n  watch?: string;\n  watchText?: string;\n}\n\nexport interface V2SqlLimitList {\n  data?: V2SqlLimit[];\n}\n\nexport interface V2SlowQueryDownloadResponse {\n  fileContent?: string;\n  filename?: string;\n}\n\nexport interface V2SlowQueryDetail {\n  backoff_detail?: string;\n  backoff_time?: number;\n  backoff_total?: number;\n  backoff_types?: string;\n  binary_plan?: string;\n  binary_plan_text?: string;\n  commit_backoff_time?: number;\n  commit_time?: number;\n  compile_time?: number;\n  connection_id?: string;\n  cop_proc_addr?: string;\n  cop_proc_avg?: number;\n  cop_proc_max?: number;\n  cop_proc_p90?: number;\n  cop_time?: number;\n  cop_wait_addr?: string;\n  cop_wait_avg?: number;\n  cop_wait_max?: number;\n  cop_wait_p90?: number;\n  db?: string;\n  digest?: string;\n  disk_max?: number;\n  exec_retry_count?: number;\n  exec_retry_time?: number;\n  get_commit_ts_time?: number;\n  has_more_results?: number;\n  host?: string;\n  index_names?: string;\n  instance?: string;\n  is_explicit_txn?: number;\n  is_internal?: number;\n  kv_total?: number;\n  local_latch_wait_time?: number;\n  lock_keys_time?: number;\n  memory_max?: number;\n  optimize_time?: number;\n  parse_time?: number;\n  pd_total?: number;\n  plan?: string;\n  plan_digest?: string;\n  plan_from_binding?: number;\n  plan_from_cache?: number;\n  prepared?: number;\n  preproc_subqueries?: number;\n  preproc_subqueries_time?: number;\n  prev_stmt?: string;\n  prewrite_region?: number;\n  prewrite_time?: number;\n  process_keys?: number;\n  process_time?: number;\n  query?: string;\n  query_time?: number;\n  request_count?: number;\n  request_unit_read?: number;\n  request_unit_write?: number;\n  resolve_lock_time?: number;\n  resource_group?: string;\n  result_rows?: number;\n  rewrite_time?: number;\n  rocksdb_block_cache_hit_count?: number;\n  rocksdb_block_read_byte?: number;\n  rocksdb_block_read_count?: number;\n  rocksdb_delete_skipped_count?: number;\n  rocksdb_key_skipped_count?: number;\n  ru?: number;\n  session_alias?: string;\n  stats?: string;\n  success?: number;\n  tidb_cpu_time?: number;\n  tikv_cpu_time?: number;\n  time_queued_by_rc?: number;\n  timestamp?: number;\n  total_keys?: number;\n  txn_retry?: number;\n  txn_start_ts?: string;\n  user?: string;\n  wait_prewrite_binlog_time?: number;\n  wait_time?: number;\n  wait_ts?: number;\n  warnings?: string;\n  write_keys?: number;\n  write_size?: number;\n  write_sql_response_total?: number;\n}\n\nexport interface V2SlowQueryList {\n  data?: V2SlowQueryDetail[];\n  nextPageToken?: string;\n  totalSize?: number;\n}\n\nexport interface V2SlowQueryAvailableFields {\n  fields?: string[];\n}\n\nexport interface V2SlowQueryAvailableAdvancedFilters {\n  filters?: string[];\n}\n\nexport interface V2SlowQueryAvailableAdvancedFilterInfo {\n  name?: string;\n  type?: string;\n  unit?: string;\n  valueList?: string[];\n}\n\nexport interface V2Role {\n  readonly createTime?: string;\n  detail?: string;\n  id?: number;\n  note?: string;\n  roleName?: string;\n  roleType?: number;\n  roleTypeDesc?: string;\n  readonly updateTime?: string;\n}\n\nexport interface V2ResourceObject {\n  resourceId?: string;\n  resourceName: string;\n}\n\nexport interface V2ResourceGroup {\n  burstable?: string;\n  name?: string;\n  priority?: string;\n  ruPerSec?: string;\n}\n\nexport interface V2ResourceGroupList {\n  resourceGroups?: V2ResourceGroup[];\n}\n\nexport interface V2ResetSecretKeyResponse {\n  accessKey: string;\n  secretKey: string;\n}\n\nexport type V2ReportResponseTaskState = typeof V2ReportResponseTaskState[keyof typeof V2ReportResponseTaskState];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2ReportResponseTaskState = {\n  init: 'init',\n  running: 'running',\n  success: 'success',\n  fail: 'fail',\n} as const;\n\nexport interface V2ReportResponse {\n  reports?: Hostv2Report[];\n  taskId: string;\n  taskState?: V2ReportResponseTaskState;\n}\n\nexport interface V2QueryMetric {\n  device?: string;\n  fstype?: string;\n  instance?: string;\n  job?: string;\n  kind?: string;\n  module?: string;\n  mountpoint?: string;\n  ping?: string;\n  result?: string;\n  sqlType?: string;\n  txnMode?: string;\n  type?: string;\n}\n\nexport interface V2QueryResult {\n  metric?: V2QueryMetric;\n  values?: Metricsv2Value[];\n}\n\nexport interface V2ProcessList {\n  activeProcessCount?: string;\n  clusterProcessList?: V2ClusterProcess[];\n  isSupportKill?: boolean;\n  totalProcessCount?: string;\n}\n\nexport interface V2PreCheckBackupPolicyResponse {\n  clusters?: V2Cluster[];\n}\n\nexport interface V2OverviewStatus {\n  alertLevels?: V2StatusCount[];\n  alerts?: V2StatusCount[];\n  brTasks?: V2StatusCount[];\n  clusters?: V2StatusCount[];\n  hosts?: V2StatusCount[];\n  otherTasks?: V2StatusCount[];\n  sysTasks?: V2StatusCount[];\n}\n\nexport interface V2Metrics {\n  metrics?: V2CategoryMetricDetail[];\n}\n\nexport interface V2MetricWithExpressions {\n  description?: string;\n  expressions?: V2ExpressionWithLegend[];\n  isBuiltin?: boolean;\n  maxTidbVersion?: string;\n  minTidbVersion?: string;\n  name?: string;\n  unit?: string;\n}\n\nexport interface V2LoginRequest {\n  password?: string;\n  userId: string;\n}\n\nexport type V2LocationsLocationKey = typeof V2LocationsLocationKey[keyof typeof V2LocationsLocationKey];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2LocationsLocationKey = {\n  zone: 'zone',\n  dc: 'dc',\n  rack: 'rack',\n} as const;\n\nexport interface V2Locations {\n  locationId?: string;\n  locationKey?: V2LocationsLocationKey;\n  locationValue?: string;\n  parentId?: string;\n}\n\nexport interface V2LocationMappings {\n  locationId?: string;\n  locationKey: string;\n  locationValue: string;\n  parentId?: string;\n}\n\n/**\n * ListUsersResponse defines the response containing a list of users and pagination information.\n */\nexport interface V2ListUsersResponse {\n  /** Token for the next page of results. */\n  nextPageToken?: string;\n  /** The total number of users that match the filter criteria. */\n  totalSize?: number;\n  /** The list of users retrieved. */\n  users?: V2User[];\n}\n\nexport interface V2ListTiupsResponse {\n  nextPageToken?: string;\n  tiups?: V2Tiups[];\n  totalSize?: number;\n}\n\nexport interface V2ListTagsWithBindingsResponse {\n  nextPageToken?: string;\n  tags?: V2TagWithBindObject[];\n  totalSize?: number;\n}\n\nexport interface V2ListTagsResponse {\n  nextPageToken?: string;\n  tags?: Tagv2Tag[];\n  totalSize?: number;\n}\n\nexport interface V2ListTagsByResourceTypeResponse {\n  nextPageToken?: string;\n  tags?: Tagv2Tag[];\n  totalSize?: number;\n}\n\nexport interface V2ListTagKeysResponse {\n  nextPageToken?: string;\n  tagKeys?: string[];\n  totalSize?: number;\n}\n\nexport interface V2ListRolesResponse {\n  nextPageToken?: string;\n  roles?: V2Role[];\n  totalSize?: number;\n}\n\nexport interface V2ListLocationsResponse {\n  locations?: V2Locations[];\n  nextPageToken?: string;\n  totalSize?: number;\n}\n\nexport interface V2ListHostsResponse {\n  hosts: V2Host[];\n  nextPageToken: string;\n  totalSize: number;\n}\n\nexport interface V2ListCredentialsResponse {\n  credentials?: V2Credential[];\n  nextPageToken?: string;\n  totalSize?: number;\n}\n\nexport interface V2ListClusterBackupRecordsResponse {\n  backupRecords?: V2ClusterBRTask[];\n  nextPageToken?: string;\n  totalSize?: number;\n}\n\nexport interface V2ListClusterBRTasksResponse {\n  brTasks?: V2ClusterBRTask[];\n  nextPageToken?: string;\n  totalSize?: number;\n}\n\nexport interface V2ListBackupPoliciesResponse {\n  backupPolicies?: V2BackupPolicy[];\n  nextPageToken?: string;\n  totalSize?: number;\n}\n\nexport interface V2ListBRTasksResponse {\n  brTasks?: V2BRTask[];\n  nextPageToken?: string;\n  totalSize?: number;\n}\n\nexport interface V2ListApiKeysResponse {\n  apikeys?: V2ApiKey[];\n  nextPageToken?: string;\n  totalSize?: number;\n}\n\n/**\n * - free: free\n - ultimate: ultimate\n */\nexport type V2LicenseTypeEnumData = typeof V2LicenseTypeEnumData[keyof typeof V2LicenseTypeEnumData];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2LicenseTypeEnumData = {\n  free: 'free',\n  ultimate: 'ultimate',\n} as const;\n\n/**\n * - active: active\n - expired: inactive\n - expiring: expired\n - invalid: invalid\n - revoked: revoked\n */\nexport type V2LicenseStatusEnumData = typeof V2LicenseStatusEnumData[keyof typeof V2LicenseStatusEnumData];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2LicenseStatusEnumData = {\n  active: 'active',\n  expired: 'expired',\n  expiring: 'expiring',\n  invalid: 'invalid',\n  revoked: 'revoked',\n} as const;\n\nexport type V2ImportRequestHeaders = {[key: string]: string};\n\nexport interface V2ImportRequest {\n  credentialId?: string;\n  fileName?: string;\n  headers: V2ImportRequestHeaders;\n  /** Upload a csv form data to host. */\n  hostData: Blob;\n}\n\nexport interface V2HostTiDBProcessesResponse {\n  tiDBProcesses?: V2TiDBProcesses[];\n}\n\nexport type V2HostTaskStatus = typeof V2HostTaskStatus[keyof typeof V2HostTaskStatus];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2HostTaskStatus = {\n  init: 'init',\n  existed: 'existed',\n  succeeded: 'succeeded',\n  failed: 'failed',\n} as const;\n\nexport interface V2HostTask {\n  credential?: V2Credential;\n  credentialId?: string;\n  hostId: string;\n  hostName?: string;\n  ip: string;\n  locationId?: string;\n  locationMappings?: V2LocationMappings[];\n  reportId?: string;\n  sshPort: number;\n  status?: V2HostTaskStatus;\n  tags?: string;\n  tagsList?: V2Tags[];\n  taskId: string;\n  userName?: string;\n}\n\nexport interface V2ImportTaskResponse {\n  task: V2HostTask[];\n  taskId: string;\n}\n\nexport interface V2HostServiceUpdateHostBody {\n  host?: Hostv2UpdateHost;\n}\n\nexport interface V2HostFixResponse {\n  hostId: string;\n  reportId: string;\n  taskId: string;\n}\n\nexport interface V2HostDiskResponse {\n  disk?: V2Disk[];\n}\n\nexport interface V2HostCredentialObject {\n  hostIps?: string[];\n  password?: string;\n  privateKey?: string;\n  publicKey?: string;\n}\n\nexport interface V2HostCreateResponse {\n  taskId?: string;\n}\n\nexport interface V2HostCheckResponse {\n  hostId: string;\n  reportId: string;\n  taskId: string;\n}\n\nexport type V2HostStatus = typeof V2HostStatus[keyof typeof V2HostStatus];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2HostStatus = {\n  initializing: 'initializing',\n  deleting: 'deleting',\n  deleted: 'deleted',\n  used: 'used',\n  idle: 'idle',\n} as const;\n\nexport type V2HostHostType = typeof V2HostHostType[keyof typeof V2HostHostType];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2HostHostType = {\n  VM: 'VM',\n  PM: 'PM',\n} as const;\n\nexport type V2HostConnectionStatus = typeof V2HostConnectionStatus[keyof typeof V2HostConnectionStatus];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2HostConnectionStatus = {\n  online: 'online',\n  offline: 'offline',\n} as const;\n\nexport type V2HostCheckStatus = typeof V2HostCheckStatus[keyof typeof V2HostCheckStatus];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2HostCheckStatus = {\n  checking: 'checking',\n  failed: 'failed',\n  warning: 'warning',\n  succeeded: 'succeeded',\n} as const;\n\nexport interface V2Host {\n  checkStatus?: V2HostCheckStatus;\n  clusters?: V2AssociatedClusters[];\n  comment?: string;\n  connectionStatus?: V2HostConnectionStatus;\n  cpuArch?: string;\n  cpuCache?: number;\n  cpuCores?: number;\n  cpuGovernor?: string;\n  cpuModel?: string;\n  cpuNumaNodes?: number;\n  cpus?: number;\n  cpuSpeed?: number;\n  cpuThreads?: number;\n  cpuVendor?: string;\n  createdTime?: string;\n  credential?: V2Credential;\n  credentialId?: string;\n  diskType?: string;\n  hostId: string;\n  hostName?: string;\n  hostType?: V2HostHostType;\n  ip?: string;\n  locationId?: string;\n  locationMappings?: V2LocationMappings[];\n  memorySize?: number;\n  memorySpeed?: number;\n  memorySwap?: number;\n  memoryType?: string;\n  memoryUnit?: string;\n  nodeExporterPort?: number;\n  osArchitecture?: string;\n  osName?: string;\n  osRelease?: string;\n  osVendor?: string;\n  osVersion?: string;\n  reportId?: string;\n  sshPort?: number;\n  status?: V2HostStatus;\n  storageAvailable?: number;\n  storageTotalSize?: number;\n  storageUnit?: string;\n  storageUsed?: number;\n  tags?: V2Tags[];\n  tiupIds?: string[];\n  updatedTime?: string;\n}\n\n/**\n * - unspecified: Unspecified group\n - overview: Overview group\n - basic: Basic group\n - advanced: Advanced group\n - resource: Resource group\n - performance: Performance group\n - process: Process group\n */\nexport type V2GroupEnumData = typeof V2GroupEnumData[keyof typeof V2GroupEnumData];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2GroupEnumData = {\n  unspecified: 'unspecified',\n  overview: 'overview',\n  basic: 'basic',\n  advanced: 'advanced',\n  resource: 'resource',\n  performance: 'performance',\n  process: 'process',\n} as const;\n\nexport interface V2GetTagWithBindingsResponse {\n  tag?: V2TagWithBindObject;\n}\n\nexport interface V2GenerateRSAKeyResponse {\n  privateKey?: string;\n  publicKey?: string;\n}\n\nexport interface V2GenerateRSAKeyRequest { [key: string]: unknown }\n\nexport interface V2ExpressionWithLegend {\n  labels?: string[];\n  legend?: string;\n  maxTidbVersion?: string;\n  minTidbVersion?: string;\n  name?: string;\n  promMetric?: string;\n  promql?: string;\n  type?: string;\n}\n\nexport interface V2ExprQueryData {\n  expr?: string;\n  legend?: string;\n  result?: V2QueryResult[];\n}\n\nexport interface V2HostMetricData {\n  data?: V2ExprQueryData[];\n  status?: string;\n}\n\nexport interface V2ErrorDetail {\n  locale?: string;\n  message?: string;\n  type?: string;\n}\n\nexport interface V2DownloadRSAKeyResponse {\n  data?: string;\n}\n\nexport interface V2DownloadListHostResponse {\n  data?: string;\n}\n\nexport interface V2DownloadHostTemplateResponse {\n  data?: string;\n}\n\nexport type V2DiskDiskType = typeof V2DiskDiskType[keyof typeof V2DiskDiskType];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2DiskDiskType = {\n  HDD: 'HDD',\n  SSD: 'SSD',\n} as const;\n\nexport interface V2Disk {\n  availableSpace?: number;\n  diskType?: V2DiskDiskType;\n  mountingDir?: string;\n  path?: string;\n  totalSize?: number;\n  usedSpace?: number;\n}\n\nexport interface V2DeviceCode {\n  deviceCode?: string;\n}\n\nexport interface V2DetectClusterResponse {\n  exist?: boolean;\n}\n\n/**\n * - week: \nWeek\n - month: \nMonth\n */\nexport type V2CycleEnumData = typeof V2CycleEnumData[keyof typeof V2CycleEnumData];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2CycleEnumData = {\n  week: 'week',\n  month: 'month',\n} as const;\n\n/**\n * - CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED: validate type unspecified\n - PASSWORD: validate by password\n - RSAKEY: validate by rsa key\n */\nexport type V2CredentialValidateType = typeof V2CredentialValidateType[keyof typeof V2CredentialValidateType];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2CredentialValidateType = {\n  CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED: 'CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED',\n  PASSWORD: 'PASSWORD',\n  RSAKEY: 'RSAKEY',\n} as const;\n\n/**\n * - CREDENTIAL_TYPE_UNSPECIFIED: resource type unspecified\n - HOST: credential type host\n - TIDB: credential type tidb\n */\nexport type V2CredentialType = typeof V2CredentialType[keyof typeof V2CredentialType];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2CredentialType = {\n  CREDENTIAL_TYPE_UNSPECIFIED: 'CREDENTIAL_TYPE_UNSPECIFIED',\n  HOST: 'HOST',\n  TIDB: 'TIDB',\n} as const;\n\nexport interface V2Credential {\n  credentialId?: string;\n  credentialName?: string;\n  credentialType: V2CredentialType;\n  description?: string;\n  hostCredential?: V2HostCredentialObject;\n  tidbCredential?: V2TiDBCredentialObject;\n  userName: string;\n  validateType: V2CredentialValidateType;\n}\n\nexport interface V2CreateHost {\n  comment?: string;\n  credentialId?: string;\n  ips?: string[];\n  locationId?: string;\n  sshPort?: number;\n  tagIds?: string[];\n}\n\nexport interface V2CreateApiKeyRequest {\n  description: string;\n}\n\nexport interface V2ConfirmResponse {\n  taskId: string;\n}\n\nexport interface V2ClusterWithoutBRPolicy {\n  clusterId?: string;\n  clusterName?: string;\n  lastBackupTime?: string;\n  sizeByte?: string;\n}\n\nexport interface V2ClusterWithBRSize {\n  clusterId?: string;\n  clusterName?: string;\n  totalSize?: string;\n  totalSizeByte?: string;\n}\n\nexport interface V2ClusterWithBRAlert {\n  alertCount?: string;\n  clusterId?: string;\n  clusterName?: string;\n}\n\nexport type V2ClusterProcessCommand = typeof V2ClusterProcessCommand[keyof typeof V2ClusterProcessCommand];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2ClusterProcessCommand = {\n  Sleep: 'Sleep',\n  Quit: 'Quit',\n  Init_DB: 'Init DB',\n  Query: 'Query',\n  Field_List: 'Field List',\n  Create_DB: 'Create DB',\n  Drop_DB: 'Drop DB',\n  Refresh: 'Refresh',\n  Shutdown: 'Shutdown',\n  Statistics: 'Statistics',\n  Processlist: 'Processlist',\n  Connect: 'Connect',\n  Kill: 'Kill',\n  Debug: 'Debug',\n  Ping: 'Ping',\n  Time: 'Time',\n  Delayed_Insert: 'Delayed Insert',\n  Change_User: 'Change User',\n  Binlog_Dump: 'Binlog Dump',\n  Table_Dump: 'Table Dump',\n  Connect_out: 'Connect out',\n  Register_Slave: 'Register Slave',\n  Prepare: 'Prepare',\n  Execute: 'Execute',\n  Long_Data: 'Long Data',\n  Close_stmt: 'Close stmt',\n  Reset_stmt: 'Reset stmt',\n  Set_option: 'Set option',\n  Fetch: 'Fetch',\n  Daemon: 'Daemon',\n  Reset_connect: 'Reset connect',\n} as const;\n\nexport interface V2ClusterProcess {\n  command?: V2ClusterProcessCommand;\n  db?: string;\n  digest?: string;\n  disk?: string;\n  host?: string;\n  id?: string;\n  info?: string;\n  instance?: string;\n  mem?: string;\n  resourceGroup?: string;\n  rowsAffected?: string;\n  sessionAlias?: string;\n  state?: string;\n  tidbCpu?: string;\n  tikvCpu?: string;\n  time?: string;\n  txnStart?: string;\n  user?: string;\n}\n\nexport interface V2ClusterMetricInstance {\n  instanceList?: string[];\n  type?: string;\n}\n\nexport interface V2ClusterMetricData {\n  data?: V2ExprQueryData[];\n  status?: string;\n}\n\nexport interface V2ClusterBackupPolicy {\n  accessKeyId?: string;\n  clusters?: V2BasicClusterInfo[];\n  concurrency?: number;\n  cycle: V2BackupCycleEnumData;\n  destination: string;\n  frequency: string;\n  lastBackupTime?: string;\n  lastLogBackupTime?: string;\n  logBackup: boolean;\n  logBackupDelay?: string;\n  logFile?: string;\n  name: string;\n  policyId?: string;\n  rateLimit?: number;\n  retention: number;\n  secretAccessKey?: string;\n  size?: string;\n  sizeByte?: string;\n  time: string;\n}\n\n/**\n * - full_backup: Full backup\n - log_backup: Log backup\n - restore_by_file: Restore by file\n - restore_by_time: Restore by time\n - all_backup: All backup\n - all_restore: All restore\n */\nexport type V2ClusterBRTypeEnumData = typeof V2ClusterBRTypeEnumData[keyof typeof V2ClusterBRTypeEnumData];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2ClusterBRTypeEnumData = {\n  full_backup: 'full_backup',\n  log_backup: 'log_backup',\n  restore_by_file: 'restore_by_file',\n  restore_by_time: 'restore_by_time',\n  all_backup: 'all_backup',\n  all_restore: 'all_restore',\n} as const;\n\n/**\n * - automatic: automatic\n - manual: manual\n */\nexport type V2ClusterBRTriggerTypeEnumData = typeof V2ClusterBRTriggerTypeEnumData[keyof typeof V2ClusterBRTriggerTypeEnumData];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2ClusterBRTriggerTypeEnumData = {\n  automatic: 'automatic',\n  manual: 'manual',\n} as const;\n\n/**\n * - running: Running\n - finished: Finished\n - abnormal: Abnormal\n - stopped: Stopped\n */\nexport type V2ClusterBRStatusEnumData = typeof V2ClusterBRStatusEnumData[keyof typeof V2ClusterBRStatusEnumData];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2ClusterBRStatusEnumData = {\n  running: 'running',\n  finished: 'finished',\n  abnormal: 'abnormal',\n  stopped: 'stopped',\n} as const;\n\nexport interface V2ClusterBRTask {\n  accessKeyId?: string;\n  clusterId?: string;\n  clusterName?: string;\n  concurrency?: number;\n  destination?: string;\n  endTime?: string;\n  errorMessage?: string;\n  expireTime?: string;\n  log?: string;\n  logFile?: string;\n  name?: string;\n  policyId?: string;\n  policyName?: string;\n  rateLimit?: number;\n  restoredTs?: string;\n  secretAccessKey?: string;\n  size?: string;\n  sizeByte?: string;\n  startTime?: string;\n  status?: V2ClusterBRStatusEnumData;\n  taskId?: string;\n  triggerType?: V2ClusterBRTriggerTypeEnumData;\n  type?: V2ClusterBRTypeEnumData;\n}\n\nexport interface V2Cluster {\n  id?: string;\n  name?: string;\n}\n\n/**\n * - unspecified: Unspecified\n - cluster: Cluster metrics\n - host: Host metrics\n - overview: Overview metrics\n */\nexport type V2ClassEnumData = typeof V2ClassEnumData[keyof typeof V2ClassEnumData];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2ClassEnumData = {\n  unspecified: 'unspecified',\n  cluster: 'cluster',\n  host: 'host',\n  overview: 'overview',\n} as const;\n\nexport interface V2CheckSupportResponse {\n  isSupport?: boolean;\n}\n\nexport interface V2ChangePasswordRequest {\n  newPassword: string;\n  oldPassword?: string;\n  userId: string;\n}\n\nexport interface V2CategoryMetricDetail {\n  class?: string;\n  description?: string;\n  displayName?: string;\n  group?: string;\n  metric?: V2MetricWithExpressions;\n  name?: string;\n  order?: number;\n  type?: string;\n}\n\nexport interface V2BindTagResponse {\n  tag?: V2TagWithBindObject;\n}\n\nexport interface V2BindResourceResponse {\n  tags?: Tagv2Tag[];\n}\n\nexport interface V2BindResourceRequest {\n  resourceId: string;\n  resourceType: V2TagBindResourceType;\n  tagIds?: string[];\n}\n\nexport interface V2BindObject {\n  resources: V2ResourceObject[];\n  resourceType: V2TagBindResourceType;\n}\n\nexport interface V2BindTagRequest {\n  bindObjects?: V2BindObject[];\n  tagId: string;\n}\n\nexport interface V2BatchDeleteRequest {\n  hostId: string[];\n}\n\nexport interface V2BatchCreateTagsResponse {\n  tags?: Tagv2Tag[];\n}\n\nexport interface V2BatchCreateTagsRequest {\n  tags: Tagv2Tag[];\n}\n\nexport interface V2BasicClusterInfo {\n  id?: string;\n  name?: string;\n}\n\nexport interface V2BackupPolicy {\n  accessKeyId?: string;\n  clusterIds?: string[];\n  clusters?: V2Cluster[];\n  concurrency?: number;\n  cycle: V2CycleEnumData;\n  destination: string;\n  frequency: string;\n  logBackup: boolean;\n  logFile?: string;\n  name: string;\n  policyId?: string;\n  rateLimit?: number;\n  retention: number;\n  secretAccessKey?: string;\n  time: string;\n}\n\n/**\n * - week: \nWeek\n - month: \nMonth\n */\nexport type V2BackupCycleEnumData = typeof V2BackupCycleEnumData[keyof typeof V2BackupCycleEnumData];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2BackupCycleEnumData = {\n  week: 'week',\n  month: 'month',\n} as const;\n\nexport interface V2BRTask {\n  accessKeyId?: string;\n  clusterId?: string;\n  clusterName?: string;\n  concurrency?: number;\n  destination?: string;\n  endTime?: string;\n  errorMessage?: string;\n  expireTime?: string;\n  log?: string;\n  logFile?: string;\n  name?: string;\n  policyId?: string;\n  policyName?: string;\n  rateLimit?: number;\n  restoredTs?: string;\n  secretAccessKey?: string;\n  size?: string;\n  sizeByte?: string;\n  startTime?: string;\n  status?: V2StatusEnumData;\n  taskId?: string;\n  triggerType?: V2TriggerTypeEnumData;\n  type?: V2TypeEnumData;\n}\n\nexport interface V2BRSummary {\n  topClustersWithBrAlert?: V2ClusterWithBRAlert[];\n  topClustersWithBrSize?: V2ClusterWithBRSize[];\n  topClustersWithoutBrPolicy?: V2ClusterWithoutBRPolicy[];\n}\n\nexport interface V2AssociatedClusters {\n  clusterId?: string;\n  clusterName?: string;\n}\n\nexport type V2ApiKeyStatus = typeof V2ApiKeyStatus[keyof typeof V2ApiKeyStatus];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const V2ApiKeyStatus = {\n  disable: 'disable',\n  enable: 'enable',\n} as const;\n\nexport interface V2ApiKey {\n  accessKey: string;\n  readonly createTime?: string;\n  creator?: string;\n  description?: string;\n  secretKey?: string;\n  status?: V2ApiKeyStatus;\n  readonly updateTime?: string;\n}\n\nexport type V2ActivateLicenseRequestHeaders = {[key: string]: string};\n\nexport interface V2ActivateLicenseRequest {\n  /** The license file to upload to activate the license */\n  content: Blob;\n  fileName: string;\n  headers: V2ActivateLicenseRequestHeaders;\n}\n\nexport interface Tiupv2UpdateTiups {\n  description?: string;\n  name?: string;\n  tagIds?: string[];\n}\n\nexport interface Tiupv2CreateTiups {\n  description?: string;\n  hostId?: string;\n  name?: string;\n  tagIds?: string[];\n  tiupHome?: string;\n}\n\nexport interface Tagv2Tag {\n  tagId?: string;\n  tagKey?: string;\n  tagValue: string;\n}\n\nexport type RpcStatusError = {\n  code?: number;\n  details?: V2ErrorDetail[];\n  message?: string;\n  status?: string;\n};\n\nexport interface RpcStatus {\n  error?: RpcStatusError;\n}\n\n/**\n * `Any` contains an arbitrary serialized protocol buffer message along with a\nURL that describes the type of the serialized message.\n\nProtobuf library provides support to pack/unpack Any values in the form\nof utility functions or additional generated methods of the Any type.\n\nExample 1: Pack and unpack a message in C++.\n\n    Foo foo = ...;\n    Any any;\n    any.PackFrom(foo);\n    ...\n    if (any.UnpackTo(&foo)) {\n      ...\n    }\n\nExample 2: Pack and unpack a message in Java.\n\n    Foo foo = ...;\n    Any any = Any.pack(foo);\n    ...\n    if (any.is(Foo.class)) {\n      foo = any.unpack(Foo.class);\n    }\n    // or ...\n    if (any.isSameTypeAs(Foo.getDefaultInstance())) {\n      foo = any.unpack(Foo.getDefaultInstance());\n    }\n\n Example 3: Pack and unpack a message in Python.\n\n    foo = Foo(...)\n    any = Any()\n    any.Pack(foo)\n    ...\n    if any.Is(Foo.DESCRIPTOR):\n      any.Unpack(foo)\n      ...\n\n Example 4: Pack and unpack a message in Go\n\n     foo := &pb.Foo{...}\n     any, err := anypb.New(foo)\n     if err != nil {\n       ...\n     }\n     ...\n     foo := &pb.Foo{}\n     if err := any.UnmarshalTo(foo); err != nil {\n       ...\n     }\n\nThe pack methods provided by protobuf library will by default use\n'type.googleapis.com/full.type.name' as the type URL and the unpack\nmethods only use the fully qualified type name after the last '/'\nin the type URL, for example \"foo.bar.com/x/y.z\" will yield type\nname \"y.z\".\n\nJSON\n====\nThe JSON representation of an `Any` value uses the regular\nrepresentation of the deserialized, embedded message, with an\nadditional field `@type` which contains the type URL. Example:\n\n    package google.profile;\n    message Person {\n      string first_name = 1;\n      string last_name = 2;\n    }\n\n    {\n      \"@type\": \"type.googleapis.com/google.profile.Person\",\n      \"firstName\": <string>,\n      \"lastName\": <string>\n    }\n\nIf the embedded message type is well-known and has a custom JSON\nrepresentation, that representation will be embedded adding a field\n`value` which holds the custom JSON in addition to the `@type`\nfield. Example (for message [google.protobuf.Duration][]):\n\n    {\n      \"@type\": \"type.googleapis.com/google.protobuf.Duration\",\n      \"value\": \"1.212s\"\n    }\n */\nexport interface ProtobufAny {\n  /** A URL/resource name that uniquely identifies the type of the serialized\nprotocol buffer message. This string must contain at least\none \"/\" character. The last segment of the URL's path must represent\nthe fully qualified name of the type (as in\n`path/google.protobuf.Duration`). The name should be in a canonical form\n(e.g., leading \".\" is not accepted).\n\nIn practice, teams usually precompile into the binary all types that they\nexpect it to use in the context of Any. However, for URLs which use the\nscheme `http`, `https`, or no scheme, one can optionally set up a type\nserver that maps type URLs to message definitions as follows:\n\n* If no scheme is provided, `https` is assumed.\n* An HTTP GET on the URL must yield a [google.protobuf.Type][]\n  value in binary format, or produce an error.\n* Applications are allowed to cache lookup results based on the\n  URL, or have them precompiled into a binary to avoid any\n  lookup. Therefore, binary compatibility needs to be preserved\n  on changes to types. (Use versioned type names to manage\n  breaking changes.)\n\nNote: this functionality is not currently available in the official\nprotobuf release, and it is not used for type URLs beginning with\ntype.googleapis.com. As of May 2023, there are no widely used type server\nimplementations and no plans to implement one.\n\nSchemes other than `http`, `https` (or the empty scheme) might be\nused with implementation specific semantics. */\n  '@type'?: string;\n  [key: string]: unknown;\n}\n\nexport interface Metricsv2Value {\n  timestamp?: number;\n  value?: string;\n}\n\nexport interface Licensev2License {\n  activateAt?: string;\n  alerts?: string;\n  allow?: string[];\n  customerCode?: string;\n  deny?: string[];\n  deviceCode?: string;\n  expirationAt?: string;\n  hosts?: string;\n  licenseId?: string;\n  licenseType?: V2LicenseTypeEnumData;\n  signature?: string;\n  status?: V2LicenseStatusEnumData;\n  vcpu?: string;\n  version?: string;\n}\n\nexport interface Hostv2UpdateHost {\n  comment?: string;\n  credentialId?: string;\n  hostId?: string;\n  locationId?: string;\n  sshPort?: number;\n  tagIds?: string[];\n}\n\nexport type Hostv2ReportCheckResult = typeof Hostv2ReportCheckResult[keyof typeof Hostv2ReportCheckResult];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const Hostv2ReportCheckResult = {\n  passed: 'passed',\n  failed: 'failed',\n  warned: 'warned',\n} as const;\n\nexport interface Hostv2Report {\n  checkBody?: string;\n  checkDesc?: string;\n  checkId?: string;\n  checkName?: string;\n  checkOut?: string;\n  checkResult?: Hostv2ReportCheckResult;\n  fixable?: boolean;\n  hostId?: string;\n  optional?: boolean;\n  reportId: string;\n}\n\nexport interface UserServiceUpdateUserBody {\n  /** The email address of the user. */\n  email?: string;\n  /** Additional notes about the user. */\n  note?: string;\n  /** The user's phone number. */\n  phone?: string;\n  /** The roles assigned to the user. */\n  roles?: V2UserRole[];\n  /** The type of the user (e.g., admin, regular user). */\n  userType?: number;\n}\n\nexport interface UserServiceResetPasswordBody {\n  newPassword: string;\n}\n\nexport interface TagServiceUpdateTagBody {\n  tagKey?: string;\n  tagValue: string;\n}\n\nexport interface RoleServiceUpdateRoleBody {\n  detail?: string;\n  note?: string;\n  roleName: string;\n  roleType?: number;\n}\n\nexport interface LocationServiceUpdateLocationsBody {\n  location?: V2Locations;\n}\n\nexport interface HostServiceHostConfirmBody { [key: string]: unknown }\n\nexport interface GlobalBRServiceUpdateBackupPolicyBody {\n  accessKeyId?: string;\n  clusterIds?: string[];\n  clusters?: V2Cluster[];\n  concurrency?: number;\n  cycle: V2CycleEnumData;\n  destination: string;\n  frequency: string;\n  logBackup: boolean;\n  logFile?: string;\n  name: string;\n  rateLimit?: number;\n  retention: number;\n  secretAccessKey?: string;\n  time: string;\n}\n\nexport interface DiagnosisServiceUpdateTopSqlConfigsBody {\n  enable: boolean;\n  historySize?: number;\n  internalQuery?: boolean;\n  maxSize?: number;\n  refreshInterval?: number;\n}\n\nexport interface DiagnosisServiceRemoveSqlLimitBody {\n  id: string;\n  watchText: string;\n}\n\nexport type DiagnosisServiceAddSqlLimitBodyAction = typeof DiagnosisServiceAddSqlLimitBodyAction[keyof typeof DiagnosisServiceAddSqlLimitBodyAction];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const DiagnosisServiceAddSqlLimitBodyAction = {\n  DRYRUN: 'DRYRUN',\n  COOLDOWN: 'COOLDOWN',\n  KILL: 'KILL',\n} as const;\n\nexport interface DiagnosisServiceAddSqlLimitBody {\n  action: DiagnosisServiceAddSqlLimitBodyAction;\n  resourceGroup: string;\n  watchText: string;\n}\n\nexport interface CredentialServiceUpdateCredentialBody {\n  credentialName?: string;\n  credentialType: V2CredentialType;\n  description?: string;\n  forceUpdate?: boolean;\n  hostCredential?: V2HostCredentialObject;\n  tidbCredential?: V2TiDBCredentialObject;\n  userName: string;\n  validateType: V2CredentialValidateType;\n}\n\nexport interface ClusterBRServiceCreateRestoreTaskBody {\n  accessKeyId?: string;\n  backupTaskId?: string;\n  concurrency?: number;\n  destination?: string;\n  logFile?: string;\n  rateLimit?: number;\n  restoreTime?: string;\n  secretAccessKey?: string;\n  targetClusterId: string;\n  type?: V2ClusterBRTypeEnumData;\n}\n\nexport interface ClusterBRServiceCreateBackupTaskBody {\n  accessKeyId?: string;\n  concurrency?: number;\n  destination: string;\n  logFile?: string;\n  name?: string;\n  rateLimit?: number;\n  retention?: number;\n  secretAccessKey?: string;\n}\n\nexport type ApiKeyServiceUpdateApiKeyBodyStatus = typeof ApiKeyServiceUpdateApiKeyBodyStatus[keyof typeof ApiKeyServiceUpdateApiKeyBodyStatus];\n\n\n// eslint-disable-next-line @typescript-eslint/no-redeclare\nexport const ApiKeyServiceUpdateApiKeyBodyStatus = {\n  disable: 'disable',\n  enable: 'enable',\n} as const;\n\nexport interface ApiKeyServiceUpdateApiKeyBody {\n  creator?: string;\n  description?: string;\n  secretKey?: string;\n  status?: ApiKeyServiceUpdateApiKeyBodyStatus;\n}\n\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/index.ts",
    "content": "/**\n * Generated by orval v7.3.0 🍺\n * Do not edit manually.\n * Azores Open API\n * OpenAPI spec version: 2.0.0\n */\nimport {\n  Hono\n} from 'hono'\nimport { cors } from 'hono/cors'\n\nimport { apiKeyServiceListApiKeysHandlers } from './handlers/apiKeyServiceListApiKeys';\nimport { apiKeyServiceCreateApiKeyHandlers } from './handlers/apiKeyServiceCreateApiKey';\nimport { apiKeyServiceGetApiKeyHandlers } from './handlers/apiKeyServiceGetApiKey';\nimport { apiKeyServiceDeleteApiKeyHandlers } from './handlers/apiKeyServiceDeleteApiKey';\nimport { apiKeyServiceUpdateApiKeyHandlers } from './handlers/apiKeyServiceUpdateApiKey';\nimport { apiKeyServiceResetSecretKeyHandlers } from './handlers/apiKeyServiceResetSecretKey';\nimport { globalBRServiceListBackupPoliciesHandlers } from './handlers/globalBRServiceListBackupPolicies';\nimport { globalBRServiceCreateBackupPolicyHandlers } from './handlers/globalBRServiceCreateBackupPolicy';\nimport { globalBRServicePreCheckBackupPolicyHandlers } from './handlers/globalBRServicePreCheckBackupPolicy';\nimport { globalBRServiceGetBackupPolicyHandlers } from './handlers/globalBRServiceGetBackupPolicy';\nimport { globalBRServiceDeleteBackupPolicyHandlers } from './handlers/globalBRServiceDeleteBackupPolicy';\nimport { globalBRServiceUpdateBackupPolicyHandlers } from './handlers/globalBRServiceUpdateBackupPolicy';\nimport { globalBRServiceGetBRSummaryHandlers } from './handlers/globalBRServiceGetBRSummary';\nimport { globalBRServiceListBRTasksHandlers } from './handlers/globalBRServiceListBRTasks';\nimport { globalBRServiceDeleteBRTaskHandlers } from './handlers/globalBRServiceDeleteBRTask';\nimport { globalBRServiceStartBRTaskHandlers } from './handlers/globalBRServiceStartBRTask';\nimport { globalBRServiceStopBRTaskHandlers } from './handlers/globalBRServiceStopBRTask';\nimport { clusterBRServiceCreateBackupTaskHandlers } from './handlers/clusterBRServiceCreateBackupTask';\nimport { clusterBRServiceGetClusterBackupPolicyHandlers } from './handlers/clusterBRServiceGetClusterBackupPolicy';\nimport { clusterBRServiceListClusterBackupRecordsHandlers } from './handlers/clusterBRServiceListClusterBackupRecords';\nimport { clusterBRServiceCreateRestoreTaskHandlers } from './handlers/clusterBRServiceCreateRestoreTask';\nimport { clusterBRServiceListClusterBRTasksHandlers } from './handlers/clusterBRServiceListClusterBRTasks';\nimport { clusterBRServiceDetectClusterHandlers } from './handlers/clusterBRServiceDetectCluster';\nimport { metricsServiceGetClusterMetricDataHandlers } from './handlers/metricsServiceGetClusterMetricData';\nimport { metricsServiceGetClusterMetricInstanceHandlers } from './handlers/metricsServiceGetClusterMetricInstance';\nimport { diagnosisServiceGetResourceGroupListHandlers } from './handlers/diagnosisServiceGetResourceGroupList';\nimport { clusterServiceGetProcessListHandlers } from './handlers/clusterServiceGetProcessList';\nimport { clusterServiceDeleteProcessHandlers } from './handlers/clusterServiceDeleteProcess';\nimport { diagnosisServiceGetSlowQueryListHandlers } from './handlers/diagnosisServiceGetSlowQueryList';\nimport { diagnosisServiceGetSlowQueryAvailableAdvancedFiltersHandlers } from './handlers/diagnosisServiceGetSlowQueryAvailableAdvancedFilters';\nimport { diagnosisServiceGetSlowQueryAvailableAdvancedFilterInfoHandlers } from './handlers/diagnosisServiceGetSlowQueryAvailableAdvancedFilterInfo';\nimport { diagnosisServiceDownloadSlowQueryListHandlers } from './handlers/diagnosisServiceDownloadSlowQueryList';\nimport { diagnosisServiceGetSlowQueryAvailableFieldsHandlers } from './handlers/diagnosisServiceGetSlowQueryAvailableFields';\nimport { diagnosisServiceGetSlowQueryDetailHandlers } from './handlers/diagnosisServiceGetSlowQueryDetail';\nimport { diagnosisServiceAddSqlLimitHandlers } from './handlers/diagnosisServiceAddSqlLimit';\nimport { diagnosisServiceCheckSqlLimitSupportHandlers } from './handlers/diagnosisServiceCheckSqlLimitSupport';\nimport { diagnosisServiceRemoveSqlLimitHandlers } from './handlers/diagnosisServiceRemoveSqlLimit';\nimport { diagnosisServiceGetSqlLimitListHandlers } from './handlers/diagnosisServiceGetSqlLimitList';\nimport { diagnosisServiceGetSqlPlanListHandlers } from './handlers/diagnosisServiceGetSqlPlanList';\nimport { diagnosisServiceBindSqlPlanHandlers } from './handlers/diagnosisServiceBindSqlPlan';\nimport { diagnosisServiceCheckSqlPlanSupportHandlers } from './handlers/diagnosisServiceCheckSqlPlanSupport';\nimport { diagnosisServiceGetSqlPlanBindingListHandlers } from './handlers/diagnosisServiceGetSqlPlanBindingList';\nimport { diagnosisServiceUnbindSqlPlanHandlers } from './handlers/diagnosisServiceUnbindSqlPlan';\nimport { diagnosisServiceGetTopSqlListHandlers } from './handlers/diagnosisServiceGetTopSqlList';\nimport { diagnosisServiceGetTopSqlAvailableAdvancedFiltersHandlers } from './handlers/diagnosisServiceGetTopSqlAvailableAdvancedFilters';\nimport { diagnosisServiceGetTopSqlAvailableAdvancedFilterInfoHandlers } from './handlers/diagnosisServiceGetTopSqlAvailableAdvancedFilterInfo';\nimport { diagnosisServiceGetTopSqlConfigsHandlers } from './handlers/diagnosisServiceGetTopSqlConfigs';\nimport { diagnosisServiceUpdateTopSqlConfigsHandlers } from './handlers/diagnosisServiceUpdateTopSqlConfigs';\nimport { diagnosisServiceGetTopSqlAvailableFieldsHandlers } from './handlers/diagnosisServiceGetTopSqlAvailableFields';\nimport { diagnosisServiceGetTopSqlDetailHandlers } from './handlers/diagnosisServiceGetTopSqlDetail';\nimport { credentialServiceListCredentialsHandlers } from './handlers/credentialServiceListCredentials';\nimport { credentialServiceCreateCredentialHandlers } from './handlers/credentialServiceCreateCredential';\nimport { credentialServiceGetCredentialHandlers } from './handlers/credentialServiceGetCredential';\nimport { credentialServiceDeleteCredentialHandlers } from './handlers/credentialServiceDeleteCredential';\nimport { credentialServiceUpdateCredentialHandlers } from './handlers/credentialServiceUpdateCredential';\nimport { credentialServiceDownloadRSAKeyHandlers } from './handlers/credentialServiceDownloadRSAKey';\nimport { credentialServiceGenerateRSAKeyHandlers } from './handlers/credentialServiceGenerateRSAKey';\nimport { credentialServiceValidateConnectionHandlers } from './handlers/credentialServiceValidateConnection';\nimport { hostServiceListHostsHandlers } from './handlers/hostServiceListHosts';\nimport { hostServiceCreateHostsHandlers } from './handlers/hostServiceCreateHosts';\nimport { hostServiceImportHandlers } from './handlers/hostServiceImport';\nimport { hostServiceImportTaskHandlers } from './handlers/hostServiceImportTask';\nimport { hostServiceHostConfirmHandlers } from './handlers/hostServiceHostConfirm';\nimport { hostServiceGetHostHandlers } from './handlers/hostServiceGetHost';\nimport { hostServiceDeleteHandlers } from './handlers/hostServiceDelete';\nimport { hostServiceUpdateHostHandlers } from './handlers/hostServiceUpdateHost';\nimport { hostServiceGetDisksHandlers } from './handlers/hostServiceGetDisks';\nimport { metricsServiceGetHostMetricDataHandlers } from './handlers/metricsServiceGetHostMetricData';\nimport { hostServiceReportHandlers } from './handlers/hostServiceReport';\nimport { hostServiceGetTiDBProcessesHandlers } from './handlers/hostServiceGetTiDBProcesses';\nimport { hostServiceFixHandlers } from './handlers/hostServiceFix';\nimport { hostServiceCheckHandlers } from './handlers/hostServiceCheck';\nimport { hostServiceBatchDeleteHandlers } from './handlers/hostServiceBatchDelete';\nimport { hostServiceDownloadListHostsHandlers } from './handlers/hostServiceDownloadListHosts';\nimport { hostServiceDownloadHostTemplateHandlers } from './handlers/hostServiceDownloadHostTemplate';\nimport { licenseServiceGetLicenseHandlers } from './handlers/licenseServiceGetLicense';\nimport { licenseServiceGetDeviceCodeHandlers } from './handlers/licenseServiceGetDeviceCode';\nimport { licenseServiceActivateLicenseHandlers } from './handlers/licenseServiceActivateLicense';\nimport { licenseServiceActivateFreeLicenseHandlers } from './handlers/licenseServiceActivateFreeLicense';\nimport { locationServiceListLocationsHandlers } from './handlers/locationServiceListLocations';\nimport { locationServiceCreateLocationsHandlers } from './handlers/locationServiceCreateLocations';\nimport { locationServiceGetLocationsHandlers } from './handlers/locationServiceGetLocations';\nimport { locationServiceDeleteLocationHandlers } from './handlers/locationServiceDeleteLocation';\nimport { locationServiceUpdateLocationsHandlers } from './handlers/locationServiceUpdateLocations';\nimport { userServiceLoginHandlers } from './handlers/userServiceLogin';\nimport { userServiceLogoutHandlers } from './handlers/userServiceLogout';\nimport { metricsServiceGetMetricsHandlers } from './handlers/metricsServiceGetMetrics';\nimport { metricsServiceGetTopMetricConfigHandlers } from './handlers/metricsServiceGetTopMetricConfig';\nimport { metricsServiceGetTopMetricDataHandlers } from './handlers/metricsServiceGetTopMetricData';\nimport { metricsServiceGetOverviewStatusHandlers } from './handlers/metricsServiceGetOverviewStatus';\nimport { roleServiceListRolesHandlers } from './handlers/roleServiceListRoles';\nimport { roleServiceCreateRoleHandlers } from './handlers/roleServiceCreateRole';\nimport { roleServiceDeleteRoleHandlers } from './handlers/roleServiceDeleteRole';\nimport { roleServiceUpdateRoleHandlers } from './handlers/roleServiceUpdateRole';\nimport { tagServiceListTagsHandlers } from './handlers/tagServiceListTags';\nimport { tagServiceCreateTagHandlers } from './handlers/tagServiceCreateTag';\nimport { tagServiceGetTagHandlers } from './handlers/tagServiceGetTag';\nimport { tagServiceDeleteTagHandlers } from './handlers/tagServiceDeleteTag';\nimport { tagServiceUpdateTagHandlers } from './handlers/tagServiceUpdateTag';\nimport { tagServiceGetTagWithBindingsHandlers } from './handlers/tagServiceGetTagWithBindings';\nimport { tagServiceBatchCreateTagsHandlers } from './handlers/tagServiceBatchCreateTags';\nimport { tagServiceBindResourceHandlers } from './handlers/tagServiceBindResource';\nimport { tagServiceBindTagHandlers } from './handlers/tagServiceBindTag';\nimport { tagServiceListTagsByResourceTypeHandlers } from './handlers/tagServiceListTagsByResourceType';\nimport { tagServiceListTagKeysHandlers } from './handlers/tagServiceListTagKeys';\nimport { tagServiceListTagsWithBindingsHandlers } from './handlers/tagServiceListTagsWithBindings';\nimport { tiupsServiceListTiupsHandlers } from './handlers/tiupsServiceListTiups';\nimport { tiupsServiceCreateTiupsHandlers } from './handlers/tiupsServiceCreateTiups';\nimport { tiupsServiceGetTiupsHandlers } from './handlers/tiupsServiceGetTiups';\nimport { tiupsServiceDeleteTiupsHandlers } from './handlers/tiupsServiceDeleteTiups';\nimport { tiupsServiceUpdateTiupsHandlers } from './handlers/tiupsServiceUpdateTiups';\nimport { tiupsServiceGetTiupsClusterHandlers } from './handlers/tiupsServiceGetTiupsCluster';\nimport { userServiceListUsersHandlers } from './handlers/userServiceListUsers';\nimport { userServiceCreateUserHandlers } from './handlers/userServiceCreateUser';\nimport { userServiceGetUserProfileHandlers } from './handlers/userServiceGetUserProfile';\nimport { userServiceGetUserHandlers } from './handlers/userServiceGetUser';\nimport { userServiceDeleteUserHandlers } from './handlers/userServiceDeleteUser';\nimport { userServiceUpdateUserHandlers } from './handlers/userServiceUpdateUser';\nimport { userServiceResetPasswordHandlers } from './handlers/userServiceResetPassword';\nimport { userServiceChangePasswordHandlers } from './handlers/userServiceChangePassword';\nimport { userServiceValidateSessionHandlers } from './handlers/userServiceValidateSession';\nimport { apiKeyServiceGetTemErrorDetailHandlers } from './handlers/apiKeyServiceGetTemErrorDetail';\n\n\nconst app = new Hono()\n\napp.use('/api/v2/*', cors())\n\n/**\n * @summary ListApiKeys retrieves a list of API keys.\n */\n\napp.get('/api/v2/apiKeys',...apiKeyServiceListApiKeysHandlers)\n\n\n/**\n * @summary CreateApiKey creates a new API key.\n */\n\napp.post('/api/v2/apiKeys',...apiKeyServiceCreateApiKeyHandlers)\n\n\n/**\n * @summary GetApiKeyRequest get an API key by its access key.\n */\n\napp.get('/api/v2/apiKeys/:accessKey',...apiKeyServiceGetApiKeyHandlers)\n\n\n/**\n * @summary DeleteApiKey deletes an API key by its access key.\n */\n\napp.delete('/api/v2/apiKeys/:accessKey',...apiKeyServiceDeleteApiKeyHandlers)\n\n\n/**\n * @summary UpdateApiKey updates an API key by its access key.\n */\n\napp.patch('/api/v2/apiKeys/:accessKey',...apiKeyServiceUpdateApiKeyHandlers)\n\n\n/**\n * @summary ResetSecretKey resets the secret key for an existing API key.\n */\n\napp.patch('/api/v2/apiKeys/:accessKey:resetSecretKey',...apiKeyServiceResetSecretKeyHandlers)\n\n\n/**\n * @summary ListBackupPolicies lists Backup policies\n */\n\napp.get('/api/v2/backup/policies',...globalBRServiceListBackupPoliciesHandlers)\n\n\n/**\n * @summary CreateBackupPolicy creates a Backup policy\n */\n\napp.post('/api/v2/backup/policies',...globalBRServiceCreateBackupPolicyHandlers)\n\n\n/**\n * @summary PreCheckBackupPolicy pre-checks a Backup policy\n */\n\napp.post('/api/v2/backup/policies/precheck',...globalBRServicePreCheckBackupPolicyHandlers)\n\n\n/**\n * @summary GetBackupPolicy gets a Backup policy\n */\n\napp.get('/api/v2/backup/policies/:policyId',...globalBRServiceGetBackupPolicyHandlers)\n\n\n/**\n * @summary DeleteBackupPolicy deletes a Backup policy\n */\n\napp.delete('/api/v2/backup/policies/:policyId',...globalBRServiceDeleteBackupPolicyHandlers)\n\n\n/**\n * @summary UpdateBackupPolicy updates a Backup policy\n */\n\napp.put('/api/v2/backup/policies/:policyId',...globalBRServiceUpdateBackupPolicyHandlers)\n\n\n/**\n * @summary GetBRSummary retrieves the summary of BR\n */\n\napp.get('/api/v2/backup/summary',...globalBRServiceGetBRSummaryHandlers)\n\n\n/**\n * @summary ListBRTasks retrieves the tasks of BR\n */\n\napp.get('/api/v2/backup/tasks',...globalBRServiceListBRTasksHandlers)\n\n\n/**\n * @summary DeleteBRTask deletes a BR task\n */\n\napp.delete('/api/v2/backup/tasks/:taskId',...globalBRServiceDeleteBRTaskHandlers)\n\n\n/**\n * @summary StartBRTask starts a BR task\n */\n\napp.post('/api/v2/backup/tasks/:taskId/start',...globalBRServiceStartBRTaskHandlers)\n\n\n/**\n * @summary StopBRTask stops a BR task\n */\n\napp.post('/api/v2/backup/tasks/:taskId/stop',...globalBRServiceStopBRTaskHandlers)\n\n\n/**\n * @summary CreateBackupTask backups a cluster\n */\n\napp.post('/api/v2/clusters/:clusterId/backup',...clusterBRServiceCreateBackupTaskHandlers)\n\n\n/**\n * @summary GetClusterBackupPolicy gets the backup info of a specific cluster\n */\n\napp.get('/api/v2/clusters/:clusterId/backup/policy',...clusterBRServiceGetClusterBackupPolicyHandlers)\n\n\n/**\n * @summary ListBackupRecords lists the valid full backup records of a specific cluster\n */\n\napp.get('/api/v2/clusters/:clusterId/backup/records',...clusterBRServiceListClusterBackupRecordsHandlers)\n\n\n/**\n * @summary CreateRestoreTask restores a cluster\n */\n\napp.post('/api/v2/clusters/:clusterId/backup/restore',...clusterBRServiceCreateRestoreTaskHandlers)\n\n\n/**\n * @summary ListClusterBRTasks lists the backup tasks of a specific cluster\n */\n\napp.get('/api/v2/clusters/:clusterId/backup/tasks',...clusterBRServiceListClusterBRTasksHandlers)\n\n\n/**\n * @summary DetectCluster detects the if the cluster exist\n */\n\napp.post('/api/v2/clusters/:clusterId/backup:detect',...clusterBRServiceDetectClusterHandlers)\n\n\n/**\n * @summary Get cluster metric data\n */\n\napp.get('/api/v2/clusters/:clusterId/metrics/:name/data',...metricsServiceGetClusterMetricDataHandlers)\n\n\n/**\n * @summary Get metric instances\n */\n\napp.get('/api/v2/clusters/:clusterId/metrics/:name/instance',...metricsServiceGetClusterMetricInstanceHandlers)\n\n\n/**\n * @summary Get resource group list\n */\n\napp.get('/api/v2/clusters/:clusterId/resourcegroups',...diagnosisServiceGetResourceGroupListHandlers)\n\n\n/**\n * @summary GetProcessList retrieves the list of running processes in a cluster\n */\n\napp.get('/api/v2/clusters/:clusterId/sessions',...clusterServiceGetProcessListHandlers)\n\n\n/**\n * @summary DeleteProcess terminates a specific process in the cluster\n */\n\napp.delete('/api/v2/clusters/:clusterId/sessions/:sessionId',...clusterServiceDeleteProcessHandlers)\n\n\n/**\n * @summary GetSlowQueryList retrieves the list of slow queries\n */\n\napp.get('/api/v2/clusters/:clusterId/slowqueries',...diagnosisServiceGetSlowQueryListHandlers)\n\n\n/**\n * @summary GetSlowQueryAvailableAdvancedFilters retrieves the list of available advanced filters\n */\n\napp.get('/api/v2/clusters/:clusterId/slowqueries/advancedFilters',...diagnosisServiceGetSlowQueryAvailableAdvancedFiltersHandlers)\n\n\n/**\n * @summary GetSlowQueryAvailableAdvancedFilterInfo retrieves the list of available advanced filter info\n */\n\napp.get('/api/v2/clusters/:clusterId/slowqueries/advancedFilters/:filterName',...diagnosisServiceGetSlowQueryAvailableAdvancedFilterInfoHandlers)\n\n\n/**\n * @summary DownloadSlowQueryList downloads the list of slow queries\n */\n\napp.get('/api/v2/clusters/:clusterId/slowqueries/download',...diagnosisServiceDownloadSlowQueryListHandlers)\n\n\n/**\n * @summary GetSlowQueryAvailableFields retrieves the list of available fields for slow queries\n */\n\napp.get('/api/v2/clusters/:clusterId/slowqueries/fields',...diagnosisServiceGetSlowQueryAvailableFieldsHandlers)\n\n\n/**\n * @summary GetSlowQueryDetail retrieves the details of a specific slow query\n */\n\napp.get('/api/v2/clusters/:clusterId/slowqueries/:digest',...diagnosisServiceGetSlowQueryDetailHandlers)\n\n\n/**\n * @summary Create SQL limit\n */\n\napp.post('/api/v2/clusters/:clusterId/sqllimits:addSqlLimit',...diagnosisServiceAddSqlLimitHandlers)\n\n\n/**\n * @summary Check if SQL limit is supported\n */\n\napp.get('/api/v2/clusters/:clusterId/sqllimits:checkSupport',...diagnosisServiceCheckSqlLimitSupportHandlers)\n\n\n/**\n * @summary Remove SQL limit\n */\n\napp.post('/api/v2/clusters/:clusterId/sqllimits:removeSqlLimit',...diagnosisServiceRemoveSqlLimitHandlers)\n\n\n/**\n * @summary Query SQL limit\n */\n\napp.get('/api/v2/clusters/:clusterId/sqllimits:showSqlLimit',...diagnosisServiceGetSqlLimitListHandlers)\n\n\n/**\n * @summary GetSqlPlanList retrieves the list of plans\n */\n\napp.get('/api/v2/clusters/:clusterId/sqlplans',...diagnosisServiceGetSqlPlanListHandlers)\n\n\n/**\n * @summary BindSqlPlan binds a plan to a specific sql\n */\n\napp.post('/api/v2/clusters/:clusterId/sqlplans/:planDigest:bindSqlPlan',...diagnosisServiceBindSqlPlanHandlers)\n\n\n/**\n * @summary CheckSupport returns whether sql plan binding is supported\n */\n\napp.get('/api/v2/clusters/:clusterId/sqlplans:checkSupport',...diagnosisServiceCheckSqlPlanSupportHandlers)\n\n\n/**\n * @summary GetSQLBindInfo\n */\n\napp.get('/api/v2/clusters/:clusterId/sqlplans:showSqlPlanBinding',...diagnosisServiceGetSqlPlanBindingListHandlers)\n\n\n/**\n * @summary UnbindSqlPlan unbinds a plan from a specific sql\n */\n\napp.post('/api/v2/clusters/:clusterId/sqlplans:unbindSqlPlan',...diagnosisServiceUnbindSqlPlanHandlers)\n\n\n/**\n * @summary GetTopSqlList retrieves the list of top sql\n */\n\napp.get('/api/v2/clusters/:clusterId/topsqls',...diagnosisServiceGetTopSqlListHandlers)\n\n\n/**\n * @summary GetTopSqlAvailableAdvancedFilters retrieves the list of available advanced filters\n */\n\napp.get('/api/v2/clusters/:clusterId/topsqls/advancedFilters',...diagnosisServiceGetTopSqlAvailableAdvancedFiltersHandlers)\n\n\n/**\n * @summary GetTopSqlAvailableAdvancedFilterInfo retrieves the list of available advanced filter info\n */\n\napp.get('/api/v2/clusters/:clusterId/topsqls/advancedFilters/:filterName',...diagnosisServiceGetTopSqlAvailableAdvancedFilterInfoHandlers)\n\n\n/**\n * @summary GetTopSqlConfigs retrieves the list of top sql configs\n */\n\napp.get('/api/v2/clusters/:clusterId/topsqls/configs',...diagnosisServiceGetTopSqlConfigsHandlers)\n\n\n/**\n * @summary UpdateTopSqlConfigs updates the list of top sql configs\n */\n\napp.patch('/api/v2/clusters/:clusterId/topsqls/configs',...diagnosisServiceUpdateTopSqlConfigsHandlers)\n\n\n/**\n * @summary GetTopSqlAvailableFields retrieves the list of available fields for top sqls\n */\n\napp.get('/api/v2/clusters/:clusterId/topsqls/fields',...diagnosisServiceGetTopSqlAvailableFieldsHandlers)\n\n\n/**\n * @summary GetTopSqlDetail retrieves the details of a specific top sql\n */\n\napp.get('/api/v2/clusters/:clusterId/topsqls/:digest',...diagnosisServiceGetTopSqlDetailHandlers)\n\n\n/**\n * @summary List credentials\n */\n\napp.get('/api/v2/credentials',...credentialServiceListCredentialsHandlers)\n\n\n/**\n * @summary Create credential\n */\n\napp.post('/api/v2/credentials',...credentialServiceCreateCredentialHandlers)\n\n\n/**\n * @summary Get credential\n */\n\napp.get('/api/v2/credentials/:credentialId',...credentialServiceGetCredentialHandlers)\n\n\n/**\n * @summary Delete credential by credential id\n */\n\napp.delete('/api/v2/credentials/:credentialId',...credentialServiceDeleteCredentialHandlers)\n\n\n/**\n * @summary Update credential by credential id\n */\n\napp.patch('/api/v2/credentials/:credentialId',...credentialServiceUpdateCredentialHandlers)\n\n\n/**\n * @summary Download credential public key and private key\n */\n\napp.get('/api/v2/credentials/:credentialId:downloadRsaKey',...credentialServiceDownloadRSAKeyHandlers)\n\n\n/**\n * @summary Generate credential public key and private key\n */\n\napp.post('/api/v2/credentials:generateRsaKey',...credentialServiceGenerateRSAKeyHandlers)\n\n\n/**\n * @summary Validate credential is accessible\n */\n\napp.post('/api/v2/credentials:validateConnection',...credentialServiceValidateConnectionHandlers)\n\n\n/**\n * @summary ListHosts\n */\n\napp.get('/api/v2/hosts',...hostServiceListHostsHandlers)\n\n\n/**\n * @summary CreateHosts\n */\n\napp.post('/api/v2/hosts',...hostServiceCreateHostsHandlers)\n\n\n/**\n * Upload a csv form data to host.\n * @summary import host\n */\n\napp.post('/api/v2/hosts/import/tasks',...hostServiceImportHandlers)\n\n\n/**\n * @summary Import one host\n */\n\napp.get('/api/v2/hosts/import/tasks/:taskId',...hostServiceImportTaskHandlers)\n\n\n/**\n * @summary HostConfirm one host\n */\n\napp.post('/api/v2/hosts/import/tasks/:taskId:confirm',...hostServiceHostConfirmHandlers)\n\n\n/**\n * @summary Get\n */\n\napp.get('/api/v2/hosts/:hostId',...hostServiceGetHostHandlers)\n\n\n/**\n * @summary delete one host by host_id\n */\n\napp.delete('/api/v2/hosts/:hostId',...hostServiceDeleteHandlers)\n\n\n/**\n * @summary update one host by host_id\n */\n\napp.patch('/api/v2/hosts/:hostId',...hostServiceUpdateHostHandlers)\n\n\n/**\n * @summary GetDisks\n */\n\napp.get('/api/v2/hosts/:hostId/disks',...hostServiceGetDisksHandlers)\n\n\n/**\n * @summary Get host metric data\n */\n\napp.get('/api/v2/hosts/:hostId/metrics/:name/data',...metricsServiceGetHostMetricDataHandlers)\n\n\n/**\n * @summary Report\n */\n\napp.get('/api/v2/hosts/:hostId/report/:reportId',...hostServiceReportHandlers)\n\n\n/**\n * @summary GetInstances\n */\n\napp.get('/api/v2/hosts/:hostId/tidbProcesses',...hostServiceGetTiDBProcessesHandlers)\n\n\n/**\n * @summary Fix\n */\n\napp.post('/api/v2/hosts/:hostId:fix',...hostServiceFixHandlers)\n\n\n/**\n * @summary Check\n */\n\napp.post('/api/v2/hosts/:hostId:systemCheck',...hostServiceCheckHandlers)\n\n\n/**\n * @summary delete one host by host_id\n */\n\napp.post('/api/v2/hosts:batchDelete',...hostServiceBatchDeleteHandlers)\n\n\n/**\n * @summary HostConfirm one host\n */\n\napp.get('/api/v2/hosts:download',...hostServiceDownloadListHostsHandlers)\n\n\n/**\n * @summary DownloadHostTemplate one host\n */\n\napp.get('/api/v2/hosts:downloadHostTemplate',...hostServiceDownloadHostTemplateHandlers)\n\n\n/**\n * @summary GetLicense returns the license details\n */\n\napp.get('/api/v2/license',...licenseServiceGetLicenseHandlers)\n\n\n/**\n * @summary GetDeviceCode returns the device code to help activate the license\n */\n\napp.get('/api/v2/license/devicecode',...licenseServiceGetDeviceCodeHandlers)\n\n\n/**\n * Upload a license using form data to activate.\n * @summary Activate a license\n */\n\napp.post('/api/v2/license:activate',...licenseServiceActivateLicenseHandlers)\n\n\n/**\n * @summary ActivateFreeLicense activate the embedded free license\n */\n\napp.post('/api/v2/license:trial',...licenseServiceActivateFreeLicenseHandlers)\n\n\n/**\n * @summary list location\n */\n\napp.get('/api/v2/locations',...locationServiceListLocationsHandlers)\n\n\n/**\n * @summary create CreateLocationRequest\n */\n\napp.post('/api/v2/locations',...locationServiceCreateLocationsHandlers)\n\n\n/**\n * @summary get Location\n */\n\napp.get('/api/v2/locations/:locationId',...locationServiceGetLocationsHandlers)\n\n\n/**\n * @summary delete Location by Location id\n */\n\napp.delete('/api/v2/locations/:locationId',...locationServiceDeleteLocationHandlers)\n\n\n/**\n * @summary update Location basic info by Location id\n */\n\napp.patch('/api/v2/locations/:locationId',...locationServiceUpdateLocationsHandlers)\n\n\n/**\n * @summary Login allows a user to log in and start a session.\n */\n\napp.post('/api/v2/login',...userServiceLoginHandlers)\n\n\n/**\n * @summary Logout allows a user to log out and end their session.\n */\n\napp.post('/api/v2/logout',...userServiceLogoutHandlers)\n\n\n/**\n * @summary Get metrics info\n */\n\napp.get('/api/v2/metrics',...metricsServiceGetMetricsHandlers)\n\n\n/**\n * @summary Get top metric config\n */\n\napp.get('/api/v2/overview/metrics/config',...metricsServiceGetTopMetricConfigHandlers)\n\n\n/**\n * @summary Get top metric data\n */\n\napp.get('/api/v2/overview/metrics/:name/data',...metricsServiceGetTopMetricDataHandlers)\n\n\n/**\n * @summary Get overview status\n */\n\napp.get('/api/v2/overview/status',...metricsServiceGetOverviewStatusHandlers)\n\n\n/**\n * @summary ListRoles retrieves a list of roles.\n */\n\napp.get('/api/v2/roles',...roleServiceListRolesHandlers)\n\n\n/**\n * @summary CreateRole creates a new role.\n */\n\napp.post('/api/v2/roles',...roleServiceCreateRoleHandlers)\n\n\n/**\n * @summary DeleteRole deletes a role by role ID.\n */\n\napp.delete('/api/v2/roles/:roleId',...roleServiceDeleteRoleHandlers)\n\n\n/**\n * @summary UpdateRole updates a role by role ID.\n */\n\napp.patch('/api/v2/roles/:roleId',...roleServiceUpdateRoleHandlers)\n\n\n/**\n * @summary List tags\n */\n\napp.get('/api/v2/tags',...tagServiceListTagsHandlers)\n\n\n/**\n * @summary Create tag\n */\n\napp.post('/api/v2/tags',...tagServiceCreateTagHandlers)\n\n\n/**\n * @summary Get tag\n */\n\napp.get('/api/v2/tags/:tagId',...tagServiceGetTagHandlers)\n\n\n/**\n * @summary Delete tag by tag id\n */\n\napp.delete('/api/v2/tags/:tagId',...tagServiceDeleteTagHandlers)\n\n\n/**\n * @summary Update tag basic info by tag id\n */\n\napp.patch('/api/v2/tags/:tagId',...tagServiceUpdateTagHandlers)\n\n\n/**\n * @summary Get tag with bindings\n */\n\napp.get('/api/v2/tags/:tagId:getWithBindings',...tagServiceGetTagWithBindingsHandlers)\n\n\n/**\n * @summary Batch create tags\n */\n\napp.post('/api/v2/tags:batchCreate',...tagServiceBatchCreateTagsHandlers)\n\n\n/**\n * @summary Modify bind object by resource id\n */\n\napp.post('/api/v2/tags:bindResource',...tagServiceBindResourceHandlers)\n\n\n/**\n * @summary Modify bind object by tag id\n */\n\napp.post('/api/v2/tags:bindTag',...tagServiceBindTagHandlers)\n\n\n/**\n * @summary List tags by resource type\n */\n\napp.get('/api/v2/tags:listByResourceType',...tagServiceListTagsByResourceTypeHandlers)\n\n\n/**\n * @summary List tag keys\n */\n\napp.get('/api/v2/tags:listKeys',...tagServiceListTagKeysHandlers)\n\n\n/**\n * @summary List tags with bindings\n */\n\napp.get('/api/v2/tags:listWithBindings',...tagServiceListTagsWithBindingsHandlers)\n\n\n/**\n * @summary list Tiups\n */\n\napp.get('/api/v2/tiups',...tiupsServiceListTiupsHandlers)\n\n\n/**\n * @summary create Tiups\n */\n\napp.post('/api/v2/tiups',...tiupsServiceCreateTiupsHandlers)\n\n\n/**\n * @summary get Tiups\n */\n\napp.get('/api/v2/tiups/:tiupId',...tiupsServiceGetTiupsHandlers)\n\n\n/**\n * @summary delete Tiups by Tiups id\n */\n\napp.delete('/api/v2/tiups/:tiupId',...tiupsServiceDeleteTiupsHandlers)\n\n\n/**\n * @summary update Tiups basic info by Tiups id\n */\n\napp.patch('/api/v2/tiups/:tiupId',...tiupsServiceUpdateTiupsHandlers)\n\n\n/**\n * @summary Get TiupsCluster\n */\n\napp.get('/api/v2/tiups/:tiupId/clusters',...tiupsServiceGetTiupsClusterHandlers)\n\n\n/**\n * @summary ListUsers retrieves a list of users.\n */\n\napp.get('/api/v2/users',...userServiceListUsersHandlers)\n\n\n/**\n * @summary CreateUser creates a new user.\n */\n\napp.post('/api/v2/users',...userServiceCreateUserHandlers)\n\n\n/**\n * @summary GetUserProfile retrieves the profile information of the authenticated user.\n */\n\napp.get('/api/v2/users/profile',...userServiceGetUserProfileHandlers)\n\n\n/**\n * @summary GetUser retrieves a user by user ID.\n */\n\napp.get('/api/v2/users/:userId',...userServiceGetUserHandlers)\n\n\n/**\n * @summary DeleteUser deletes a user by user ID.\n */\n\napp.delete('/api/v2/users/:userId',...userServiceDeleteUserHandlers)\n\n\n/**\n * @summary UpdateUser updates a user's information by user ID.\n */\n\napp.patch('/api/v2/users/:userId',...userServiceUpdateUserHandlers)\n\n\n/**\n * @summary ResetPassword allows an admin user to reset the password of another user.\n */\n\napp.patch('/api/v2/users/:userId:resetPassword',...userServiceResetPasswordHandlers)\n\n\n/**\n * @summary ChangePassword allows the authenticated user to change their password.\n */\n\napp.patch('/api/v2/users:changePassword',...userServiceChangePasswordHandlers)\n\n\n/**\n * @summary ValidateSession verifies the validity of the current session.\n */\n\napp.get('/api/v2/users:validateSession',...userServiceValidateSessionHandlers)\n\n\n/**\n * @summary GetTemErrorDetail\n */\n\napp.get('/documentation/errorDetail',...apiKeyServiceGetTemErrorDetailHandlers)\n\n\nexport default app"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/index.validator.ts",
    "content": "/**\n * Generated by orval v7.3.0 🍺\n * Do not edit manually.\n * Azores Open API\n * OpenAPI spec version: 2.0.0\n */\n\n// based on https://github.com/honojs/middleware/blob/main/packages/zod-validator/src/index.ts\nimport type { z, ZodSchema, ZodError } from 'zod';\nimport {\n  Context,\n  Env,\n  Input,\n  MiddlewareHandler,\n  TypedResponse,\n  ValidationTargets,\n} from 'hono';\n\ntype HasUndefined<T> = undefined extends T ? true : false;\n\ntype Hook<T, E extends Env, P extends string, O = {}> = (\n  result:\n    | { success: true; data: T }\n    | { success: false; error: ZodError; data: T },\n  c: Context<E, P>,\n) =>\n  | Response\n  | Promise<Response>\n  | void\n  | Promise<Response | void>\n  | TypedResponse<O>;\nimport { zValidator as zValidatorBase } from '@hono/zod-validator';\n\ntype ValidationTargetsWithResponse = ValidationTargets & { response: any };\n\nexport const zValidator =\n  <\n    T extends ZodSchema,\n    Target extends keyof ValidationTargetsWithResponse,\n    E extends Env,\n    P extends string,\n    In = z.input<T>,\n    Out = z.output<T>,\n    I extends Input = {\n      in: HasUndefined<In> extends true\n        ? {\n            [K in Target]?: K extends 'json'\n              ? In\n              : HasUndefined<\n                  keyof ValidationTargetsWithResponse[K]\n                > extends true\n              ? { [K2 in keyof In]?: ValidationTargetsWithResponse[K][K2] }\n              : { [K2 in keyof In]: ValidationTargetsWithResponse[K][K2] };\n          }\n        : {\n            [K in Target]: K extends 'json'\n              ? In\n              : HasUndefined<\n                  keyof ValidationTargetsWithResponse[K]\n                > extends true\n              ? { [K2 in keyof In]?: ValidationTargetsWithResponse[K][K2] }\n              : { [K2 in keyof In]: ValidationTargetsWithResponse[K][K2] };\n          };\n      out: { [K in Target]: Out };\n    },\n    V extends I = I,\n  >(\n    target: Target,\n    schema: T,\n    hook?: Hook<z.infer<T>, E, P>,\n  ): MiddlewareHandler<E, P, V> =>\n  async (c, next) => {\n    if (target !== 'response') {\n      const value = await zValidatorBase<\n        T,\n        keyof ValidationTargets,\n        E,\n        P,\n        In,\n        Out,\n        I,\n        V\n      >(\n        target,\n        schema,\n        hook,\n      )(c, next);\n\n      if (value instanceof Response) {\n        return value;\n      }\n    } else {\n      await next();\n\n      if (\n        c.res.status !== 200 ||\n       !c.res.headers.get('Content-Type')?.includes('application/json')\n      ) {\n        return;\n      }\n\n      let value: unknown;\n      try {\n        value = await c.res.json();\n      } catch {\n        const message = 'Malformed JSON in response';\n        c.res = new Response(message, { status: 400 });\n\n        return;\n      }\n\n      const result = await schema.safeParseAsync(value);\n\n      if (hook) {\n        const hookResult = hook({ data: value, ...result }, c);\n        if (hookResult) {\n          if (hookResult instanceof Response || hookResult instanceof Promise) {\n            const hookResponse = await hookResult;\n\n            if (hookResponse instanceof Response) {\n              c.res = new Response(hookResponse.body, hookResponse);\n            }\n          }\n          if (\n            'response' in hookResult &&\n            hookResult.response instanceof Response\n          ) {\n            c.res = new Response(hookResult.response.body, hookResult.response);\n          }\n        }\n      }\n\n      if (!result.success) {\n        c.res = new Response(JSON.stringify(result), {\n          status: 400,\n          headers: {\n            'Content-Type': 'application/json',\n          },\n        });\n      } else {\n        c.res = new Response(JSON.stringify(result.data), c.res);\n      }\n    }\n  };\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/index.zod.ts",
    "content": "/**\n * Generated by orval v7.3.0 🍺\n * Do not edit manually.\n * Azores Open API\n * OpenAPI spec version: 2.0.0\n */\nimport { z as zod } from 'zod';\n\nexport const apiKeyServiceListApiKeysQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"accessKey\": zod.string().optional(),\n  \"creator\": zod.string().optional(),\n  \"status\": zod.string().optional()\n})\n\nexport const apiKeyServiceListApiKeysResponse = zod.object({\n  \"apikeys\": zod.array(zod.object({\n  \"accessKey\": zod.string(),\n  \"secretKey\": zod.string().optional(),\n  \"creator\": zod.string().optional(),\n  \"status\": zod.enum(['disable', 'enable']).optional(),\n  \"description\": zod.string().optional(),\n  \"createTime\": zod.string().datetime().optional(),\n  \"updateTime\": zod.string().datetime().optional()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const apiKeyServiceCreateApiKeyBody = zod.object({\n  \"description\": zod.string()\n})\n\nexport const apiKeyServiceCreateApiKeyResponse = zod.object({\n  \"accessKey\": zod.string(),\n  \"secretKey\": zod.string().optional(),\n  \"creator\": zod.string().optional(),\n  \"status\": zod.enum(['disable', 'enable']).optional(),\n  \"description\": zod.string().optional(),\n  \"createTime\": zod.string().datetime().optional(),\n  \"updateTime\": zod.string().datetime().optional()\n})\n\n\nexport const apiKeyServiceGetApiKeyParams = zod.object({\n  \"accessKey\": zod.string()\n})\n\nexport const apiKeyServiceGetApiKeyResponse = zod.object({\n  \"accessKey\": zod.string(),\n  \"secretKey\": zod.string().optional(),\n  \"creator\": zod.string().optional(),\n  \"status\": zod.enum(['disable', 'enable']).optional(),\n  \"description\": zod.string().optional(),\n  \"createTime\": zod.string().datetime().optional(),\n  \"updateTime\": zod.string().datetime().optional()\n})\n\n\nexport const apiKeyServiceDeleteApiKeyParams = zod.object({\n  \"accessKey\": zod.string()\n})\n\nexport const apiKeyServiceDeleteApiKeyResponse = zod.object({\n\n})\n\n\nexport const apiKeyServiceUpdateApiKeyParams = zod.object({\n  \"accessKey\": zod.string()\n})\n\nexport const apiKeyServiceUpdateApiKeyBody = zod.object({\n  \"secretKey\": zod.string().optional(),\n  \"creator\": zod.string().optional(),\n  \"status\": zod.enum(['disable', 'enable']).optional(),\n  \"description\": zod.string().optional()\n})\n\nexport const apiKeyServiceUpdateApiKeyResponse = zod.object({\n  \"accessKey\": zod.string(),\n  \"secretKey\": zod.string().optional(),\n  \"creator\": zod.string().optional(),\n  \"status\": zod.enum(['disable', 'enable']).optional(),\n  \"description\": zod.string().optional(),\n  \"createTime\": zod.string().datetime().optional(),\n  \"updateTime\": zod.string().datetime().optional()\n})\n\n\nexport const apiKeyServiceResetSecretKeyParams = zod.object({\n  \"accessKey\": zod.string()\n})\n\nexport const apiKeyServiceResetSecretKeyResponse = zod.object({\n  \"accessKey\": zod.string(),\n  \"secretKey\": zod.string()\n})\n\n\nexport const globalBRServiceListBackupPoliciesQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional()\n})\n\nexport const globalBRServiceListBackupPoliciesResponse = zod.object({\n  \"backupPolicies\": zod.array(zod.object({\n  \"policyId\": zod.string().optional(),\n  \"name\": zod.string(),\n  \"logBackup\": zod.boolean(),\n  \"destination\": zod.string(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"cycle\": zod.enum(['week', 'month']).optional(),\n  \"frequency\": zod.string(),\n  \"time\": zod.string(),\n  \"retention\": zod.number(),\n  \"clusters\": zod.array(zod.object({\n  \"id\": zod.string().optional(),\n  \"name\": zod.string().optional()\n})).optional(),\n  \"clusterIds\": zod.array(zod.string()).optional()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const globalBRServiceCreateBackupPolicyBody = zod.object({\n  \"policyId\": zod.string().optional(),\n  \"name\": zod.string(),\n  \"logBackup\": zod.boolean(),\n  \"destination\": zod.string(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"cycle\": zod.enum(['week', 'month']).optional(),\n  \"frequency\": zod.string(),\n  \"time\": zod.string(),\n  \"retention\": zod.number(),\n  \"clusters\": zod.array(zod.object({\n  \"id\": zod.string().optional(),\n  \"name\": zod.string().optional()\n})).optional(),\n  \"clusterIds\": zod.array(zod.string()).optional()\n})\n\nexport const globalBRServiceCreateBackupPolicyResponse = zod.object({\n  \"policyId\": zod.string().optional(),\n  \"name\": zod.string(),\n  \"logBackup\": zod.boolean(),\n  \"destination\": zod.string(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"cycle\": zod.enum(['week', 'month']).optional(),\n  \"frequency\": zod.string(),\n  \"time\": zod.string(),\n  \"retention\": zod.number(),\n  \"clusters\": zod.array(zod.object({\n  \"id\": zod.string().optional(),\n  \"name\": zod.string().optional()\n})).optional(),\n  \"clusterIds\": zod.array(zod.string()).optional()\n})\n\n\nexport const globalBRServicePreCheckBackupPolicyBody = zod.object({\n  \"policyId\": zod.string().optional(),\n  \"name\": zod.string(),\n  \"logBackup\": zod.boolean(),\n  \"destination\": zod.string(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"cycle\": zod.enum(['week', 'month']).optional(),\n  \"frequency\": zod.string(),\n  \"time\": zod.string(),\n  \"retention\": zod.number(),\n  \"clusters\": zod.array(zod.object({\n  \"id\": zod.string().optional(),\n  \"name\": zod.string().optional()\n})).optional(),\n  \"clusterIds\": zod.array(zod.string()).optional()\n})\n\nexport const globalBRServicePreCheckBackupPolicyResponse = zod.object({\n  \"clusters\": zod.array(zod.object({\n  \"id\": zod.string().optional(),\n  \"name\": zod.string().optional()\n})).optional()\n})\n\n\nexport const globalBRServiceGetBackupPolicyParams = zod.object({\n  \"policyId\": zod.string()\n})\n\nexport const globalBRServiceGetBackupPolicyResponse = zod.object({\n  \"policyId\": zod.string().optional(),\n  \"name\": zod.string(),\n  \"logBackup\": zod.boolean(),\n  \"destination\": zod.string(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"cycle\": zod.enum(['week', 'month']).optional(),\n  \"frequency\": zod.string(),\n  \"time\": zod.string(),\n  \"retention\": zod.number(),\n  \"clusters\": zod.array(zod.object({\n  \"id\": zod.string().optional(),\n  \"name\": zod.string().optional()\n})).optional(),\n  \"clusterIds\": zod.array(zod.string()).optional()\n})\n\n\nexport const globalBRServiceDeleteBackupPolicyParams = zod.object({\n  \"policyId\": zod.string()\n})\n\nexport const globalBRServiceDeleteBackupPolicyResponse = zod.object({\n\n})\n\n\nexport const globalBRServiceUpdateBackupPolicyParams = zod.object({\n  \"policyId\": zod.string()\n})\n\nexport const globalBRServiceUpdateBackupPolicyBody = zod.object({\n  \"name\": zod.string(),\n  \"logBackup\": zod.boolean(),\n  \"destination\": zod.string(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"cycle\": zod.enum(['week', 'month']).optional(),\n  \"frequency\": zod.string(),\n  \"time\": zod.string(),\n  \"retention\": zod.number(),\n  \"clusters\": zod.array(zod.object({\n  \"id\": zod.string().optional(),\n  \"name\": zod.string().optional()\n})).optional(),\n  \"clusterIds\": zod.array(zod.string()).optional()\n})\n\nexport const globalBRServiceUpdateBackupPolicyResponse = zod.object({\n  \"policyId\": zod.string().optional(),\n  \"name\": zod.string(),\n  \"logBackup\": zod.boolean(),\n  \"destination\": zod.string(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"cycle\": zod.enum(['week', 'month']).optional(),\n  \"frequency\": zod.string(),\n  \"time\": zod.string(),\n  \"retention\": zod.number(),\n  \"clusters\": zod.array(zod.object({\n  \"id\": zod.string().optional(),\n  \"name\": zod.string().optional()\n})).optional(),\n  \"clusterIds\": zod.array(zod.string()).optional()\n})\n\n\nexport const globalBRServiceGetBRSummaryQueryParams = zod.object({\n  \"top\": zod.number().optional()\n})\n\nexport const globalBRServiceGetBRSummaryResponse = zod.object({\n  \"topClustersWithBrSize\": zod.array(zod.object({\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional(),\n  \"totalSizeByte\": zod.string().optional(),\n  \"totalSize\": zod.string().optional()\n})).optional(),\n  \"topClustersWithBrAlert\": zod.array(zod.object({\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional(),\n  \"alertCount\": zod.string().optional()\n})).optional(),\n  \"topClustersWithoutBrPolicy\": zod.array(zod.object({\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional(),\n  \"lastBackupTime\": zod.string().optional(),\n  \"sizeByte\": zod.string().optional()\n})).optional()\n})\n\n\nexport const globalBRServiceListBRTasksQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"brTaskId\": zod.string().optional(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional(),\n  \"type\": zod.enum(['all', 'full_backup', 'log_backup', 'restore_by_file', 'restore_by_time', 'all_backup', 'all_restore']).optional(),\n  \"status\": zod.enum(['all', 'running', 'finished', 'abnormal', 'stopped']).optional()\n})\n\nexport const globalBRServiceListBRTasksResponse = zod.object({\n  \"brTasks\": zod.array(zod.object({\n  \"taskId\": zod.string().optional(),\n  \"type\": zod.enum(['all', 'full_backup', 'log_backup', 'restore_by_file', 'restore_by_time', 'all_backup', 'all_restore']).optional(),\n  \"triggerType\": zod.enum(['automatic', 'manual']).optional(),\n  \"name\": zod.string().optional(),\n  \"status\": zod.enum(['all', 'running', 'finished', 'abnormal', 'stopped']).optional(),\n  \"restoredTs\": zod.string().optional(),\n  \"startTime\": zod.string().optional(),\n  \"endTime\": zod.string().optional(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional(),\n  \"destination\": zod.string().optional(),\n  \"size\": zod.string().optional(),\n  \"sizeByte\": zod.string().optional(),\n  \"errorMessage\": zod.string().optional(),\n  \"policyId\": zod.string().optional(),\n  \"policyName\": zod.string().optional(),\n  \"log\": zod.string().optional(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"expireTime\": zod.string().optional()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const globalBRServiceDeleteBRTaskParams = zod.object({\n  \"taskId\": zod.string()\n})\n\nexport const globalBRServiceDeleteBRTaskQueryParams = zod.object({\n  \"deleteBackupFile\": zod.boolean().optional()\n})\n\nexport const globalBRServiceDeleteBRTaskResponse = zod.object({\n\n})\n\n\nexport const globalBRServiceStartBRTaskParams = zod.object({\n  \"taskId\": zod.string()\n})\n\nexport const globalBRServiceStartBRTaskResponse = zod.object({\n\n})\n\n\nexport const globalBRServiceStopBRTaskParams = zod.object({\n  \"taskId\": zod.string()\n})\n\nexport const globalBRServiceStopBRTaskResponse = zod.object({\n\n})\n\n\nexport const clusterBRServiceCreateBackupTaskParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const clusterBRServiceCreateBackupTaskBody = zod.object({\n  \"name\": zod.string().optional(),\n  \"destination\": zod.string(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"retention\": zod.number().optional()\n})\n\nexport const clusterBRServiceCreateBackupTaskResponse = zod.object({\n  \"taskId\": zod.string().optional(),\n  \"type\": zod.enum(['full_backup', 'log_backup', 'restore_by_file', 'restore_by_time', 'all_backup', 'all_restore']).optional(),\n  \"triggerType\": zod.enum(['automatic', 'manual']).optional(),\n  \"name\": zod.string().optional(),\n  \"status\": zod.enum(['running', 'finished', 'abnormal', 'stopped']).optional(),\n  \"restoredTs\": zod.string().optional(),\n  \"startTime\": zod.string().optional(),\n  \"endTime\": zod.string().optional(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional(),\n  \"destination\": zod.string().optional(),\n  \"size\": zod.string().optional(),\n  \"sizeByte\": zod.string().optional(),\n  \"errorMessage\": zod.string().optional(),\n  \"policyId\": zod.string().optional(),\n  \"policyName\": zod.string().optional(),\n  \"log\": zod.string().optional(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"expireTime\": zod.string().optional()\n})\n\n\nexport const clusterBRServiceGetClusterBackupPolicyParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const clusterBRServiceGetClusterBackupPolicyResponse = zod.object({\n  \"policyId\": zod.string().optional(),\n  \"name\": zod.string(),\n  \"logBackup\": zod.boolean(),\n  \"destination\": zod.string(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"cycle\": zod.enum(['week', 'month']).optional(),\n  \"frequency\": zod.string(),\n  \"time\": zod.string(),\n  \"retention\": zod.number(),\n  \"clusters\": zod.array(zod.object({\n  \"id\": zod.string().optional(),\n  \"name\": zod.string().optional()\n})).optional(),\n  \"lastBackupTime\": zod.string().optional(),\n  \"size\": zod.string().optional(),\n  \"sizeByte\": zod.string().optional(),\n  \"lastLogBackupTime\": zod.string().optional(),\n  \"logBackupDelay\": zod.string().optional()\n})\n\n\nexport const clusterBRServiceListClusterBackupRecordsParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const clusterBRServiceListClusterBackupRecordsQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional()\n})\n\nexport const clusterBRServiceListClusterBackupRecordsResponse = zod.object({\n  \"backupRecords\": zod.array(zod.object({\n  \"taskId\": zod.string().optional(),\n  \"type\": zod.enum(['full_backup', 'log_backup', 'restore_by_file', 'restore_by_time', 'all_backup', 'all_restore']).optional(),\n  \"triggerType\": zod.enum(['automatic', 'manual']).optional(),\n  \"name\": zod.string().optional(),\n  \"status\": zod.enum(['running', 'finished', 'abnormal', 'stopped']).optional(),\n  \"restoredTs\": zod.string().optional(),\n  \"startTime\": zod.string().optional(),\n  \"endTime\": zod.string().optional(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional(),\n  \"destination\": zod.string().optional(),\n  \"size\": zod.string().optional(),\n  \"sizeByte\": zod.string().optional(),\n  \"errorMessage\": zod.string().optional(),\n  \"policyId\": zod.string().optional(),\n  \"policyName\": zod.string().optional(),\n  \"log\": zod.string().optional(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"expireTime\": zod.string().optional()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const clusterBRServiceCreateRestoreTaskParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const clusterBRServiceCreateRestoreTaskBody = zod.object({\n  \"targetClusterId\": zod.string(),\n  \"type\": zod.enum(['full_backup', 'log_backup', 'restore_by_file', 'restore_by_time', 'all_backup', 'all_restore']).optional(),\n  \"backupTaskId\": zod.string().optional(),\n  \"restoreTime\": zod.string().optional(),\n  \"destination\": zod.string().optional(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional()\n})\n\nexport const clusterBRServiceCreateRestoreTaskResponse = zod.object({\n  \"taskId\": zod.string().optional(),\n  \"type\": zod.enum(['full_backup', 'log_backup', 'restore_by_file', 'restore_by_time', 'all_backup', 'all_restore']).optional(),\n  \"triggerType\": zod.enum(['automatic', 'manual']).optional(),\n  \"name\": zod.string().optional(),\n  \"status\": zod.enum(['running', 'finished', 'abnormal', 'stopped']).optional(),\n  \"restoredTs\": zod.string().optional(),\n  \"startTime\": zod.string().optional(),\n  \"endTime\": zod.string().optional(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional(),\n  \"destination\": zod.string().optional(),\n  \"size\": zod.string().optional(),\n  \"sizeByte\": zod.string().optional(),\n  \"errorMessage\": zod.string().optional(),\n  \"policyId\": zod.string().optional(),\n  \"policyName\": zod.string().optional(),\n  \"log\": zod.string().optional(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"expireTime\": zod.string().optional()\n})\n\n\nexport const clusterBRServiceListClusterBRTasksParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const clusterBRServiceListClusterBRTasksQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"brTaskId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional(),\n  \"type\": zod.enum(['full_backup', 'log_backup', 'restore_by_file', 'restore_by_time', 'all_backup', 'all_restore']).optional(),\n  \"status\": zod.enum(['running', 'finished', 'abnormal', 'stopped']).optional()\n})\n\nexport const clusterBRServiceListClusterBRTasksResponse = zod.object({\n  \"brTasks\": zod.array(zod.object({\n  \"taskId\": zod.string().optional(),\n  \"type\": zod.enum(['full_backup', 'log_backup', 'restore_by_file', 'restore_by_time', 'all_backup', 'all_restore']).optional(),\n  \"triggerType\": zod.enum(['automatic', 'manual']).optional(),\n  \"name\": zod.string().optional(),\n  \"status\": zod.enum(['running', 'finished', 'abnormal', 'stopped']).optional(),\n  \"restoredTs\": zod.string().optional(),\n  \"startTime\": zod.string().optional(),\n  \"endTime\": zod.string().optional(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional(),\n  \"destination\": zod.string().optional(),\n  \"size\": zod.string().optional(),\n  \"sizeByte\": zod.string().optional(),\n  \"errorMessage\": zod.string().optional(),\n  \"policyId\": zod.string().optional(),\n  \"policyName\": zod.string().optional(),\n  \"log\": zod.string().optional(),\n  \"accessKeyId\": zod.string().optional(),\n  \"secretAccessKey\": zod.string().optional(),\n  \"rateLimit\": zod.number().optional(),\n  \"concurrency\": zod.number().optional(),\n  \"logFile\": zod.string().optional(),\n  \"expireTime\": zod.string().optional()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const clusterBRServiceDetectClusterParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const clusterBRServiceDetectClusterResponse = zod.object({\n  \"exist\": zod.boolean().optional()\n})\n\n\nexport const metricsServiceGetClusterMetricDataParams = zod.object({\n  \"clusterId\": zod.string(),\n  \"name\": zod.string()\n})\n\nexport const metricsServiceGetClusterMetricDataQueryParams = zod.object({\n  \"startTime\": zod.string(),\n  \"endTime\": zod.string(),\n  \"step\": zod.string().optional(),\n  \"label\": zod.string().optional(),\n  \"range\": zod.string().optional()\n})\n\nexport const metricsServiceGetClusterMetricDataResponse = zod.object({\n  \"status\": zod.string().optional(),\n  \"data\": zod.array(zod.object({\n  \"expr\": zod.string().optional(),\n  \"legend\": zod.string().optional(),\n  \"result\": zod.array(zod.object({\n  \"metric\": zod.object({\n  \"instance\": zod.string().optional(),\n  \"sqlType\": zod.string().optional(),\n  \"type\": zod.string().optional(),\n  \"result\": zod.string().optional(),\n  \"txnMode\": zod.string().optional(),\n  \"job\": zod.string().optional(),\n  \"device\": zod.string().optional(),\n  \"fstype\": zod.string().optional(),\n  \"mountpoint\": zod.string().optional(),\n  \"module\": zod.string().optional(),\n  \"kind\": zod.string().optional(),\n  \"ping\": zod.string().optional()\n}).optional(),\n  \"values\": zod.array(zod.object({\n  \"timestamp\": zod.number().optional(),\n  \"value\": zod.string().optional()\n})).optional()\n})).optional()\n})).optional()\n})\n\n\nexport const metricsServiceGetClusterMetricInstanceParams = zod.object({\n  \"clusterId\": zod.string(),\n  \"name\": zod.string()\n})\n\nexport const metricsServiceGetClusterMetricInstanceResponse = zod.object({\n  \"type\": zod.string().optional(),\n  \"instanceList\": zod.array(zod.string()).optional()\n})\n\n\nexport const diagnosisServiceGetResourceGroupListParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceGetResourceGroupListResponse = zod.object({\n  \"resourceGroups\": zod.array(zod.object({\n  \"name\": zod.string().optional(),\n  \"ruPerSec\": zod.string().optional(),\n  \"priority\": zod.string().optional(),\n  \"burstable\": zod.string().optional()\n})).optional()\n})\n\n\nexport const clusterServiceGetProcessListParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const clusterServiceGetProcessListResponse = zod.object({\n  \"clusterProcessList\": zod.array(zod.object({\n  \"instance\": zod.string().optional(),\n  \"id\": zod.string().optional(),\n  \"user\": zod.string().optional(),\n  \"host\": zod.string().optional(),\n  \"db\": zod.string().optional(),\n  \"command\": zod.enum(['Sleep', 'Quit', 'Init DB', 'Query', 'Field List', 'Create DB', 'Drop DB', 'Refresh', 'Shutdown', 'Statistics', 'Processlist', 'Connect', 'Kill', 'Debug', 'Ping', 'Time', 'Delayed Insert', 'Change User', 'Binlog Dump', 'Table Dump', 'Connect out', 'Register Slave', 'Prepare', 'Execute', 'Long Data', 'Close stmt', 'Reset stmt', 'Set option', 'Fetch', 'Daemon', 'Reset connect']).optional(),\n  \"time\": zod.string().optional(),\n  \"state\": zod.string().optional(),\n  \"info\": zod.string().optional(),\n  \"digest\": zod.string().optional(),\n  \"mem\": zod.string().optional(),\n  \"disk\": zod.string().optional(),\n  \"txnStart\": zod.string().optional(),\n  \"resourceGroup\": zod.string().optional(),\n  \"sessionAlias\": zod.string().optional(),\n  \"rowsAffected\": zod.string().optional(),\n  \"tidbCpu\": zod.string().optional(),\n  \"tikvCpu\": zod.string().optional()\n})).optional(),\n  \"isSupportKill\": zod.boolean().optional(),\n  \"totalProcessCount\": zod.string().optional(),\n  \"activeProcessCount\": zod.string().optional()\n})\n\n\nexport const clusterServiceDeleteProcessParams = zod.object({\n  \"clusterId\": zod.string(),\n  \"sessionId\": zod.string()\n})\n\nexport const clusterServiceDeleteProcessResponse = zod.object({\n\n})\n\n\nexport const diagnosisServiceGetSlowQueryListParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceGetSlowQueryListQueryParams = zod.object({\n  \"beginTime\": zod.string(),\n  \"endTime\": zod.string(),\n  \"db\": zod.array(zod.string()).optional(),\n  \"text\": zod.string().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"isDesc\": zod.boolean().optional(),\n  \"fields\": zod.string().optional(),\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"advancedFilter\": zod.array(zod.string()).optional()\n})\n\nexport const diagnosisServiceGetSlowQueryListResponse = zod.object({\n  \"data\": zod.array(zod.object({\n  \"digest\": zod.string().optional(),\n  \"query\": zod.string().optional(),\n  \"instance\": zod.string().optional(),\n  \"db\": zod.string().optional(),\n  \"connection_id\": zod.string().optional(),\n  \"success\": zod.number().optional(),\n  \"timestamp\": zod.number().optional(),\n  \"query_time\": zod.number().optional(),\n  \"parse_time\": zod.number().optional(),\n  \"compile_time\": zod.number().optional(),\n  \"rewrite_time\": zod.number().optional(),\n  \"preproc_subqueries_time\": zod.number().optional(),\n  \"optimize_time\": zod.number().optional(),\n  \"wait_ts\": zod.number().optional(),\n  \"cop_time\": zod.number().optional(),\n  \"lock_keys_time\": zod.number().optional(),\n  \"write_sql_response_total\": zod.number().optional(),\n  \"exec_retry_time\": zod.number().optional(),\n  \"memory_max\": zod.number().optional(),\n  \"disk_max\": zod.number().optional(),\n  \"txn_start_ts\": zod.string().optional(),\n  \"prev_stmt\": zod.string().optional(),\n  \"plan\": zod.string().optional(),\n  \"binary_plan\": zod.string().optional(),\n  \"warnings\": zod.string().optional(),\n  \"is_internal\": zod.number().optional(),\n  \"index_names\": zod.string().optional(),\n  \"stats\": zod.string().optional(),\n  \"backoff_types\": zod.string().optional(),\n  \"prepared\": zod.number().optional(),\n  \"plan_from_cache\": zod.number().optional(),\n  \"plan_from_binding\": zod.number().optional(),\n  \"user\": zod.string().optional(),\n  \"host\": zod.string().optional(),\n  \"ia_remote_read_segment_size\": zod.number().optional(),\n  \"ia_remote_read_segment_wait_time\": zod.number().optional(),\n  \"process_time\": zod.number().optional(),\n  \"wait_time\": zod.number().optional(),\n  \"backoff_time\": zod.number().optional(),\n  \"get_commit_ts_time\": zod.number().optional(),\n  \"local_latch_wait_time\": zod.number().optional(),\n  \"resolve_lock_time\": zod.number().optional(),\n  \"prewrite_time\": zod.number().optional(),\n  \"wait_prewrite_binlog_time\": zod.number().optional(),\n  \"commit_time\": zod.number().optional(),\n  \"commit_backoff_time\": zod.number().optional(),\n  \"cop_proc_avg\": zod.number().optional(),\n  \"cop_proc_p90\": zod.number().optional(),\n  \"cop_proc_max\": zod.number().optional(),\n  \"cop_wait_avg\": zod.number().optional(),\n  \"cop_wait_p90\": zod.number().optional(),\n  \"cop_wait_max\": zod.number().optional(),\n  \"write_keys\": zod.number().optional(),\n  \"write_size\": zod.number().optional(),\n  \"prewrite_region\": zod.number().optional(),\n  \"txn_retry\": zod.number().optional(),\n  \"request_count\": zod.number().optional(),\n  \"process_keys\": zod.number().optional(),\n  \"total_keys\": zod.number().optional(),\n  \"cop_proc_addr\": zod.string().optional(),\n  \"cop_wait_addr\": zod.string().optional(),\n  \"rocksdb_delete_skipped_count\": zod.number().optional(),\n  \"rocksdb_key_skipped_count\": zod.number().optional(),\n  \"rocksdb_block_cache_hit_count\": zod.number().optional(),\n  \"rocksdb_block_read_count\": zod.number().optional(),\n  \"rocksdb_block_read_byte\": zod.number().optional(),\n  \"binary_plan_text\": zod.string().optional(),\n  \"session_alias\": zod.string().optional(),\n  \"exec_retry_count\": zod.number().optional(),\n  \"preproc_subqueries\": zod.number().optional(),\n  \"kv_total\": zod.number().optional(),\n  \"pd_total\": zod.number().optional(),\n  \"backoff_total\": zod.number().optional(),\n  \"time_queued_by_rc\": zod.number().optional(),\n  \"tidb_cpu_time\": zod.number().optional(),\n  \"tikv_cpu_time\": zod.number().optional(),\n  \"backoff_detail\": zod.string().optional(),\n  \"is_explicit_txn\": zod.number().optional(),\n  \"plan_digest\": zod.string().optional(),\n  \"has_more_results\": zod.number().optional(),\n  \"resource_group\": zod.string().optional(),\n  \"request_unit_read\": zod.number().optional(),\n  \"request_unit_write\": zod.number().optional(),\n  \"result_rows\": zod.number().optional(),\n  \"ru\": zod.number().optional()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const diagnosisServiceGetSlowQueryAvailableAdvancedFiltersParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceGetSlowQueryAvailableAdvancedFiltersResponse = zod.object({\n  \"filters\": zod.array(zod.string()).optional()\n})\n\n\nexport const diagnosisServiceGetSlowQueryAvailableAdvancedFilterInfoParams = zod.object({\n  \"clusterId\": zod.string(),\n  \"filterName\": zod.string()\n})\n\nexport const diagnosisServiceGetSlowQueryAvailableAdvancedFilterInfoResponse = zod.object({\n  \"name\": zod.string().optional(),\n  \"unit\": zod.string().optional(),\n  \"valueList\": zod.array(zod.string()).optional(),\n  \"type\": zod.string().optional()\n})\n\n\nexport const diagnosisServiceDownloadSlowQueryListParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceDownloadSlowQueryListQueryParams = zod.object({\n  \"beginTime\": zod.string(),\n  \"endTime\": zod.string(),\n  \"db\": zod.array(zod.string()).optional(),\n  \"text\": zod.string().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"isDesc\": zod.boolean().optional(),\n  \"fields\": zod.string().optional(),\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"advancedFilter\": zod.array(zod.string()).optional()\n})\n\nexport const diagnosisServiceDownloadSlowQueryListResponse = zod.object({\n  \"filename\": zod.string().optional(),\n  \"fileContent\": zod.string().optional()\n})\n\n\nexport const diagnosisServiceGetSlowQueryAvailableFieldsParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceGetSlowQueryAvailableFieldsResponse = zod.object({\n  \"fields\": zod.array(zod.string()).optional()\n})\n\n\nexport const diagnosisServiceGetSlowQueryDetailParams = zod.object({\n  \"clusterId\": zod.string(),\n  \"digest\": zod.string()\n})\n\nexport const diagnosisServiceGetSlowQueryDetailQueryParams = zod.object({\n  \"timestamp\": zod.number(),\n  \"connectionId\": zod.string()\n})\n\nexport const diagnosisServiceGetSlowQueryDetailResponse = zod.object({\n  \"digest\": zod.string().optional(),\n  \"query\": zod.string().optional(),\n  \"instance\": zod.string().optional(),\n  \"db\": zod.string().optional(),\n  \"connection_id\": zod.string().optional(),\n  \"success\": zod.number().optional(),\n  \"timestamp\": zod.number().optional(),\n  \"query_time\": zod.number().optional(),\n  \"parse_time\": zod.number().optional(),\n  \"compile_time\": zod.number().optional(),\n  \"rewrite_time\": zod.number().optional(),\n  \"preproc_subqueries_time\": zod.number().optional(),\n  \"optimize_time\": zod.number().optional(),\n  \"wait_ts\": zod.number().optional(),\n  \"cop_time\": zod.number().optional(),\n  \"lock_keys_time\": zod.number().optional(),\n  \"write_sql_response_total\": zod.number().optional(),\n  \"exec_retry_time\": zod.number().optional(),\n  \"memory_max\": zod.number().optional(),\n  \"disk_max\": zod.number().optional(),\n  \"txn_start_ts\": zod.string().optional(),\n  \"prev_stmt\": zod.string().optional(),\n  \"plan\": zod.string().optional(),\n  \"binary_plan\": zod.string().optional(),\n  \"warnings\": zod.string().optional(),\n  \"is_internal\": zod.number().optional(),\n  \"index_names\": zod.string().optional(),\n  \"stats\": zod.string().optional(),\n  \"backoff_types\": zod.string().optional(),\n  \"prepared\": zod.number().optional(),\n  \"plan_from_cache\": zod.number().optional(),\n  \"plan_from_binding\": zod.number().optional(),\n  \"user\": zod.string().optional(),\n  \"host\": zod.string().optional(),\n  \"ia_remote_read_segment_size\": zod.number().optional(),\n  \"ia_remote_read_segment_wait_time\": zod.number().optional(),\n  \"process_time\": zod.number().optional(),\n  \"wait_time\": zod.number().optional(),\n  \"backoff_time\": zod.number().optional(),\n  \"get_commit_ts_time\": zod.number().optional(),\n  \"local_latch_wait_time\": zod.number().optional(),\n  \"resolve_lock_time\": zod.number().optional(),\n  \"prewrite_time\": zod.number().optional(),\n  \"wait_prewrite_binlog_time\": zod.number().optional(),\n  \"commit_time\": zod.number().optional(),\n  \"commit_backoff_time\": zod.number().optional(),\n  \"cop_proc_avg\": zod.number().optional(),\n  \"cop_proc_p90\": zod.number().optional(),\n  \"cop_proc_max\": zod.number().optional(),\n  \"cop_wait_avg\": zod.number().optional(),\n  \"cop_wait_p90\": zod.number().optional(),\n  \"cop_wait_max\": zod.number().optional(),\n  \"write_keys\": zod.number().optional(),\n  \"write_size\": zod.number().optional(),\n  \"prewrite_region\": zod.number().optional(),\n  \"txn_retry\": zod.number().optional(),\n  \"request_count\": zod.number().optional(),\n  \"process_keys\": zod.number().optional(),\n  \"total_keys\": zod.number().optional(),\n  \"cop_proc_addr\": zod.string().optional(),\n  \"cop_wait_addr\": zod.string().optional(),\n  \"rocksdb_delete_skipped_count\": zod.number().optional(),\n  \"rocksdb_key_skipped_count\": zod.number().optional(),\n  \"rocksdb_block_cache_hit_count\": zod.number().optional(),\n  \"rocksdb_block_read_count\": zod.number().optional(),\n  \"rocksdb_block_read_byte\": zod.number().optional(),\n  \"binary_plan_text\": zod.string().optional(),\n  \"session_alias\": zod.string().optional(),\n  \"exec_retry_count\": zod.number().optional(),\n  \"preproc_subqueries\": zod.number().optional(),\n  \"kv_total\": zod.number().optional(),\n  \"pd_total\": zod.number().optional(),\n  \"backoff_total\": zod.number().optional(),\n  \"time_queued_by_rc\": zod.number().optional(),\n  \"tidb_cpu_time\": zod.number().optional(),\n  \"tikv_cpu_time\": zod.number().optional(),\n  \"backoff_detail\": zod.string().optional(),\n  \"is_explicit_txn\": zod.number().optional(),\n  \"plan_digest\": zod.string().optional(),\n  \"has_more_results\": zod.number().optional(),\n  \"resource_group\": zod.string().optional(),\n  \"request_unit_read\": zod.number().optional(),\n  \"request_unit_write\": zod.number().optional(),\n  \"result_rows\": zod.number().optional(),\n  \"ru\": zod.number().optional()\n})\n\n\nexport const diagnosisServiceAddSqlLimitParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceAddSqlLimitBody = zod.object({\n  \"resourceGroup\": zod.string(),\n  \"action\": zod.enum(['DRYRUN', 'COOLDOWN', 'KILL']),\n  \"watchText\": zod.string()\n})\n\nexport const diagnosisServiceAddSqlLimitResponse = zod.object({\n\n})\n\n\nexport const diagnosisServiceCheckSqlLimitSupportParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceCheckSqlLimitSupportResponse = zod.object({\n  \"isSupport\": zod.boolean().optional()\n})\n\n\nexport const diagnosisServiceRemoveSqlLimitParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceRemoveSqlLimitBody = zod.object({\n  \"watchText\": zod.string(),\n  \"id\": zod.string()\n})\n\nexport const diagnosisServiceRemoveSqlLimitResponse = zod.object({\n\n})\n\n\nexport const diagnosisServiceGetSqlLimitListParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceGetSqlLimitListQueryParams = zod.object({\n  \"watchText\": zod.string()\n})\n\nexport const diagnosisServiceGetSqlLimitListResponse = zod.object({\n  \"data\": zod.array(zod.object({\n  \"id\": zod.string().optional(),\n  \"resourceGroupName\": zod.string().optional(),\n  \"startTime\": zod.string().optional(),\n  \"endTime\": zod.string().optional(),\n  \"watch\": zod.string().optional(),\n  \"watchText\": zod.string().optional(),\n  \"source\": zod.string().optional(),\n  \"action\": zod.enum(['DRYRUN', 'COOLDOWN', 'KILL']).optional()\n})).optional()\n})\n\n\nexport const diagnosisServiceGetSqlPlanListParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceGetSqlPlanListQueryParams = zod.object({\n  \"beginTime\": zod.string(),\n  \"endTime\": zod.string(),\n  \"digest\": zod.string().optional(),\n  \"schemaName\": zod.string().optional()\n})\n\nexport const diagnosisServiceGetSqlPlanListResponse = zod.object({\n  \"data\": zod.array(zod.object({\n  \"summary_begin_time\": zod.number().optional(),\n  \"summary_end_time\": zod.number().optional(),\n  \"digest_text\": zod.string().optional(),\n  \"digest\": zod.string().optional(),\n  \"exec_count\": zod.number().optional(),\n  \"stmt_type\": zod.string().optional(),\n  \"sum_errors\": zod.number().optional(),\n  \"sum_warnings\": zod.number().optional(),\n  \"sum_latency\": zod.number().optional(),\n  \"max_latency\": zod.number().optional(),\n  \"min_latency\": zod.number().optional(),\n  \"avg_latency\": zod.number().optional(),\n  \"avg_parse_latency\": zod.number().optional(),\n  \"max_parse_latency\": zod.number().optional(),\n  \"avg_compile_latency\": zod.number().optional(),\n  \"max_compile_latency\": zod.number().optional(),\n  \"sum_cop_task_num\": zod.number().optional(),\n  \"avg_cop_process_time\": zod.number().optional(),\n  \"max_cop_process_time\": zod.number().optional(),\n  \"avg_cop_wait_time\": zod.number().optional(),\n  \"max_cop_wait_time\": zod.number().optional(),\n  \"avg_process_time\": zod.number().optional(),\n  \"max_process_time\": zod.number().optional(),\n  \"avg_wait_time\": zod.number().optional(),\n  \"max_wait_time\": zod.number().optional(),\n  \"avg_backoff_time\": zod.number().optional(),\n  \"max_backoff_time\": zod.number().optional(),\n  \"avg_total_keys\": zod.number().optional(),\n  \"max_total_keys\": zod.number().optional(),\n  \"avg_processed_keys\": zod.number().optional(),\n  \"max_processed_keys\": zod.number().optional(),\n  \"avg_prewrite_time\": zod.number().optional(),\n  \"max_prewrite_time\": zod.number().optional(),\n  \"avg_commit_time\": zod.number().optional(),\n  \"max_commit_time\": zod.number().optional(),\n  \"avg_get_commit_ts_time\": zod.number().optional(),\n  \"max_get_commit_ts_time\": zod.number().optional(),\n  \"avg_commit_backoff_time\": zod.number().optional(),\n  \"max_commit_backoff_time\": zod.number().optional(),\n  \"avg_resolve_lock_time\": zod.number().optional(),\n  \"max_resolve_lock_time\": zod.number().optional(),\n  \"avg_local_latch_wait_time\": zod.number().optional(),\n  \"max_local_latch_wait_time\": zod.number().optional(),\n  \"avg_write_keys\": zod.number().optional(),\n  \"max_write_keys\": zod.number().optional(),\n  \"avg_write_size\": zod.number().optional(),\n  \"max_write_size\": zod.number().optional(),\n  \"avg_prewrite_regions\": zod.number().optional(),\n  \"max_prewrite_regions\": zod.number().optional(),\n  \"avg_txn_retry\": zod.number().optional(),\n  \"max_txn_retry\": zod.number().optional(),\n  \"sum_backoff_times\": zod.number().optional(),\n  \"avg_mem\": zod.number().optional(),\n  \"max_mem\": zod.number().optional(),\n  \"avg_disk\": zod.number().optional(),\n  \"max_disk\": zod.number().optional(),\n  \"avg_affected_rows\": zod.number().optional(),\n  \"first_seen\": zod.number().optional(),\n  \"last_seen\": zod.number().optional(),\n  \"sample_user\": zod.string().optional(),\n  \"query_sample_text\": zod.string().optional(),\n  \"prev_sample_text\": zod.string().optional(),\n  \"schema_name\": zod.string().optional(),\n  \"table_names\": zod.string().optional(),\n  \"index_names\": zod.string().optional(),\n  \"plan_count\": zod.number().optional(),\n  \"plan\": zod.string().optional(),\n  \"binary_plan\": zod.string().optional(),\n  \"plan_digest\": zod.string().optional(),\n  \"plan_hint\": zod.string().optional(),\n  \"max_rocksdb_delete_skipped_count\": zod.number().optional(),\n  \"avg_rocksdb_delete_skipped_count\": zod.number().optional(),\n  \"max_rocksdb_key_skipped_count\": zod.number().optional(),\n  \"avg_rocksdb_key_skipped_count\": zod.number().optional(),\n  \"max_rocksdb_block_cache_hit_count\": zod.number().optional(),\n  \"avg_rocksdb_block_cache_hit_count\": zod.number().optional(),\n  \"max_rocksdb_block_read_count\": zod.number().optional(),\n  \"avg_rocksdb_block_read_count\": zod.number().optional(),\n  \"max_rocksdb_block_read_byte\": zod.number().optional(),\n  \"avg_rocksdb_block_read_byte\": zod.number().optional(),\n  \"related_schemas\": zod.string().optional(),\n  \"plan_can_be_bound\": zod.boolean().optional(),\n  \"binary_plan_text\": zod.string().optional(),\n  \"resource_group\": zod.string().optional(),\n  \"avg_ru\": zod.number().optional(),\n  \"max_ru\": zod.number().optional(),\n  \"sum_ru\": zod.number().optional(),\n  \"avg_time_queued_by_rc\": zod.number().optional(),\n  \"max_time_queued_by_rc\": zod.number().optional(),\n  \"avg_tidb_cpu_time\": zod.number().optional(),\n  \"avg_tikv_cpu_time\": zod.number().optional()\n})).optional()\n})\n\n\nexport const diagnosisServiceBindSqlPlanParams = zod.object({\n  \"clusterId\": zod.string(),\n  \"planDigest\": zod.string()\n})\n\nexport const diagnosisServiceBindSqlPlanResponse = zod.object({\n\n})\n\n\nexport const diagnosisServiceCheckSqlPlanSupportParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceCheckSqlPlanSupportResponse = zod.object({\n  \"isSupport\": zod.boolean().optional()\n})\n\n\nexport const diagnosisServiceGetSqlPlanBindingListParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceGetSqlPlanBindingListQueryParams = zod.object({\n  \"beginTime\": zod.string(),\n  \"endTime\": zod.string(),\n  \"digest\": zod.string()\n})\n\nexport const diagnosisServiceGetSqlPlanBindingListResponse = zod.object({\n  \"data\": zod.array(zod.object({\n  \"status\": zod.enum(['enabled', 'using', 'disabled', 'deleted', 'invalid', 'rejected', 'pending verify']).optional(),\n  \"source\": zod.enum(['manual', 'history', 'capture', 'evolve']).optional(),\n  \"digest\": zod.string().optional(),\n  \"planDigest\": zod.string().optional()\n})).optional()\n})\n\n\nexport const diagnosisServiceUnbindSqlPlanParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceUnbindSqlPlanQueryParams = zod.object({\n  \"digest\": zod.string()\n})\n\nexport const diagnosisServiceUnbindSqlPlanResponse = zod.object({\n\n})\n\n\nexport const diagnosisServiceGetTopSqlListParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceGetTopSqlListQueryParams = zod.object({\n  \"beginTime\": zod.string(),\n  \"endTime\": zod.string(),\n  \"db\": zod.array(zod.string()).optional(),\n  \"text\": zod.string().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"isDesc\": zod.boolean().optional(),\n  \"fields\": zod.string().optional(),\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"advancedFilter\": zod.array(zod.string()).optional(),\n  \"isGroupByTime\": zod.boolean().optional()\n})\n\nexport const diagnosisServiceGetTopSqlListResponse = zod.object({\n  \"data\": zod.array(zod.object({\n  \"summary_begin_time\": zod.number().optional(),\n  \"summary_end_time\": zod.number().optional(),\n  \"digest_text\": zod.string().optional(),\n  \"digest\": zod.string().optional(),\n  \"exec_count\": zod.number().optional(),\n  \"stmt_type\": zod.string().optional(),\n  \"sum_errors\": zod.number().optional(),\n  \"sum_warnings\": zod.number().optional(),\n  \"sum_latency\": zod.number().optional(),\n  \"max_latency\": zod.number().optional(),\n  \"min_latency\": zod.number().optional(),\n  \"avg_latency\": zod.number().optional(),\n  \"avg_parse_latency\": zod.number().optional(),\n  \"max_parse_latency\": zod.number().optional(),\n  \"avg_compile_latency\": zod.number().optional(),\n  \"max_compile_latency\": zod.number().optional(),\n  \"sum_cop_task_num\": zod.number().optional(),\n  \"avg_cop_process_time\": zod.number().optional(),\n  \"max_cop_process_time\": zod.number().optional(),\n  \"avg_cop_wait_time\": zod.number().optional(),\n  \"max_cop_wait_time\": zod.number().optional(),\n  \"avg_process_time\": zod.number().optional(),\n  \"max_process_time\": zod.number().optional(),\n  \"avg_wait_time\": zod.number().optional(),\n  \"max_wait_time\": zod.number().optional(),\n  \"avg_backoff_time\": zod.number().optional(),\n  \"max_backoff_time\": zod.number().optional(),\n  \"avg_total_keys\": zod.number().optional(),\n  \"max_total_keys\": zod.number().optional(),\n  \"avg_processed_keys\": zod.number().optional(),\n  \"max_processed_keys\": zod.number().optional(),\n  \"avg_prewrite_time\": zod.number().optional(),\n  \"max_prewrite_time\": zod.number().optional(),\n  \"avg_commit_time\": zod.number().optional(),\n  \"max_commit_time\": zod.number().optional(),\n  \"avg_get_commit_ts_time\": zod.number().optional(),\n  \"max_get_commit_ts_time\": zod.number().optional(),\n  \"avg_commit_backoff_time\": zod.number().optional(),\n  \"max_commit_backoff_time\": zod.number().optional(),\n  \"avg_resolve_lock_time\": zod.number().optional(),\n  \"max_resolve_lock_time\": zod.number().optional(),\n  \"avg_local_latch_wait_time\": zod.number().optional(),\n  \"max_local_latch_wait_time\": zod.number().optional(),\n  \"avg_write_keys\": zod.number().optional(),\n  \"max_write_keys\": zod.number().optional(),\n  \"avg_write_size\": zod.number().optional(),\n  \"max_write_size\": zod.number().optional(),\n  \"avg_prewrite_regions\": zod.number().optional(),\n  \"max_prewrite_regions\": zod.number().optional(),\n  \"avg_txn_retry\": zod.number().optional(),\n  \"max_txn_retry\": zod.number().optional(),\n  \"sum_backoff_times\": zod.number().optional(),\n  \"avg_mem\": zod.number().optional(),\n  \"max_mem\": zod.number().optional(),\n  \"avg_disk\": zod.number().optional(),\n  \"max_disk\": zod.number().optional(),\n  \"avg_affected_rows\": zod.number().optional(),\n  \"first_seen\": zod.number().optional(),\n  \"last_seen\": zod.number().optional(),\n  \"sample_user\": zod.string().optional(),\n  \"query_sample_text\": zod.string().optional(),\n  \"prev_sample_text\": zod.string().optional(),\n  \"schema_name\": zod.string().optional(),\n  \"table_names\": zod.string().optional(),\n  \"index_names\": zod.string().optional(),\n  \"plan_count\": zod.number().optional(),\n  \"plan\": zod.string().optional(),\n  \"binary_plan\": zod.string().optional(),\n  \"plan_digest\": zod.string().optional(),\n  \"plan_hint\": zod.string().optional(),\n  \"max_rocksdb_delete_skipped_count\": zod.number().optional(),\n  \"avg_rocksdb_delete_skipped_count\": zod.number().optional(),\n  \"max_rocksdb_key_skipped_count\": zod.number().optional(),\n  \"avg_rocksdb_key_skipped_count\": zod.number().optional(),\n  \"max_rocksdb_block_cache_hit_count\": zod.number().optional(),\n  \"avg_rocksdb_block_cache_hit_count\": zod.number().optional(),\n  \"max_rocksdb_block_read_count\": zod.number().optional(),\n  \"avg_rocksdb_block_read_count\": zod.number().optional(),\n  \"max_rocksdb_block_read_byte\": zod.number().optional(),\n  \"avg_rocksdb_block_read_byte\": zod.number().optional(),\n  \"related_schemas\": zod.string().optional(),\n  \"plan_can_be_bound\": zod.boolean().optional(),\n  \"binary_plan_text\": zod.string().optional(),\n  \"resource_group\": zod.string().optional(),\n  \"avg_ru\": zod.number().optional(),\n  \"max_ru\": zod.number().optional(),\n  \"sum_ru\": zod.number().optional(),\n  \"avg_time_queued_by_rc\": zod.number().optional(),\n  \"max_time_queued_by_rc\": zod.number().optional(),\n  \"avg_tidb_cpu_time\": zod.number().optional(),\n  \"avg_tikv_cpu_time\": zod.number().optional()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const diagnosisServiceGetTopSqlAvailableAdvancedFiltersParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceGetTopSqlAvailableAdvancedFiltersResponse = zod.object({\n  \"filters\": zod.array(zod.string()).optional()\n})\n\n\nexport const diagnosisServiceGetTopSqlAvailableAdvancedFilterInfoParams = zod.object({\n  \"clusterId\": zod.string(),\n  \"filterName\": zod.string()\n})\n\nexport const diagnosisServiceGetTopSqlAvailableAdvancedFilterInfoResponse = zod.object({\n  \"name\": zod.string().optional(),\n  \"unit\": zod.string().optional(),\n  \"valueList\": zod.array(zod.string()).optional(),\n  \"type\": zod.string().optional()\n})\n\n\nexport const diagnosisServiceGetTopSqlConfigsParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceGetTopSqlConfigsResponse = zod.object({\n  \"enable\": zod.boolean().optional(),\n  \"refreshInterval\": zod.number().optional(),\n  \"historySize\": zod.number().optional(),\n  \"maxSize\": zod.number().optional(),\n  \"internalQuery\": zod.boolean().optional()\n})\n\n\nexport const diagnosisServiceUpdateTopSqlConfigsParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceUpdateTopSqlConfigsBody = zod.object({\n  \"enable\": zod.boolean(),\n  \"refreshInterval\": zod.number().optional(),\n  \"historySize\": zod.number().optional(),\n  \"maxSize\": zod.number().optional(),\n  \"internalQuery\": zod.boolean().optional()\n})\n\nexport const diagnosisServiceUpdateTopSqlConfigsResponse = zod.object({\n  \"enable\": zod.boolean().optional(),\n  \"refreshInterval\": zod.number().optional(),\n  \"historySize\": zod.number().optional(),\n  \"maxSize\": zod.number().optional(),\n  \"internalQuery\": zod.boolean().optional()\n})\n\n\nexport const diagnosisServiceGetTopSqlAvailableFieldsParams = zod.object({\n  \"clusterId\": zod.string()\n})\n\nexport const diagnosisServiceGetTopSqlAvailableFieldsResponse = zod.object({\n  \"fields\": zod.array(zod.string()).optional()\n})\n\n\nexport const diagnosisServiceGetTopSqlDetailParams = zod.object({\n  \"clusterId\": zod.string(),\n  \"digest\": zod.string()\n})\n\nexport const diagnosisServiceGetTopSqlDetailQueryParams = zod.object({\n  \"beginTime\": zod.string(),\n  \"endTime\": zod.string(),\n  \"planDigest\": zod.array(zod.string()).optional()\n})\n\nexport const diagnosisServiceGetTopSqlDetailResponse = zod.object({\n  \"summary_begin_time\": zod.number().optional(),\n  \"summary_end_time\": zod.number().optional(),\n  \"digest_text\": zod.string().optional(),\n  \"digest\": zod.string().optional(),\n  \"exec_count\": zod.number().optional(),\n  \"stmt_type\": zod.string().optional(),\n  \"sum_errors\": zod.number().optional(),\n  \"sum_warnings\": zod.number().optional(),\n  \"sum_latency\": zod.number().optional(),\n  \"max_latency\": zod.number().optional(),\n  \"min_latency\": zod.number().optional(),\n  \"avg_latency\": zod.number().optional(),\n  \"avg_parse_latency\": zod.number().optional(),\n  \"max_parse_latency\": zod.number().optional(),\n  \"avg_compile_latency\": zod.number().optional(),\n  \"max_compile_latency\": zod.number().optional(),\n  \"sum_cop_task_num\": zod.number().optional(),\n  \"avg_cop_process_time\": zod.number().optional(),\n  \"max_cop_process_time\": zod.number().optional(),\n  \"avg_cop_wait_time\": zod.number().optional(),\n  \"max_cop_wait_time\": zod.number().optional(),\n  \"avg_process_time\": zod.number().optional(),\n  \"max_process_time\": zod.number().optional(),\n  \"avg_wait_time\": zod.number().optional(),\n  \"max_wait_time\": zod.number().optional(),\n  \"avg_backoff_time\": zod.number().optional(),\n  \"max_backoff_time\": zod.number().optional(),\n  \"avg_total_keys\": zod.number().optional(),\n  \"max_total_keys\": zod.number().optional(),\n  \"avg_processed_keys\": zod.number().optional(),\n  \"max_processed_keys\": zod.number().optional(),\n  \"avg_prewrite_time\": zod.number().optional(),\n  \"max_prewrite_time\": zod.number().optional(),\n  \"avg_commit_time\": zod.number().optional(),\n  \"max_commit_time\": zod.number().optional(),\n  \"avg_get_commit_ts_time\": zod.number().optional(),\n  \"max_get_commit_ts_time\": zod.number().optional(),\n  \"avg_commit_backoff_time\": zod.number().optional(),\n  \"max_commit_backoff_time\": zod.number().optional(),\n  \"avg_resolve_lock_time\": zod.number().optional(),\n  \"max_resolve_lock_time\": zod.number().optional(),\n  \"avg_local_latch_wait_time\": zod.number().optional(),\n  \"max_local_latch_wait_time\": zod.number().optional(),\n  \"avg_write_keys\": zod.number().optional(),\n  \"max_write_keys\": zod.number().optional(),\n  \"avg_write_size\": zod.number().optional(),\n  \"max_write_size\": zod.number().optional(),\n  \"avg_prewrite_regions\": zod.number().optional(),\n  \"max_prewrite_regions\": zod.number().optional(),\n  \"avg_txn_retry\": zod.number().optional(),\n  \"max_txn_retry\": zod.number().optional(),\n  \"sum_backoff_times\": zod.number().optional(),\n  \"avg_mem\": zod.number().optional(),\n  \"max_mem\": zod.number().optional(),\n  \"avg_disk\": zod.number().optional(),\n  \"max_disk\": zod.number().optional(),\n  \"avg_affected_rows\": zod.number().optional(),\n  \"first_seen\": zod.number().optional(),\n  \"last_seen\": zod.number().optional(),\n  \"sample_user\": zod.string().optional(),\n  \"query_sample_text\": zod.string().optional(),\n  \"prev_sample_text\": zod.string().optional(),\n  \"schema_name\": zod.string().optional(),\n  \"table_names\": zod.string().optional(),\n  \"index_names\": zod.string().optional(),\n  \"plan_count\": zod.number().optional(),\n  \"plan\": zod.string().optional(),\n  \"binary_plan\": zod.string().optional(),\n  \"plan_digest\": zod.string().optional(),\n  \"plan_hint\": zod.string().optional(),\n  \"max_rocksdb_delete_skipped_count\": zod.number().optional(),\n  \"avg_rocksdb_delete_skipped_count\": zod.number().optional(),\n  \"max_rocksdb_key_skipped_count\": zod.number().optional(),\n  \"avg_rocksdb_key_skipped_count\": zod.number().optional(),\n  \"max_rocksdb_block_cache_hit_count\": zod.number().optional(),\n  \"avg_rocksdb_block_cache_hit_count\": zod.number().optional(),\n  \"max_rocksdb_block_read_count\": zod.number().optional(),\n  \"avg_rocksdb_block_read_count\": zod.number().optional(),\n  \"max_rocksdb_block_read_byte\": zod.number().optional(),\n  \"avg_rocksdb_block_read_byte\": zod.number().optional(),\n  \"related_schemas\": zod.string().optional(),\n  \"plan_can_be_bound\": zod.boolean().optional(),\n  \"binary_plan_text\": zod.string().optional(),\n  \"resource_group\": zod.string().optional(),\n  \"avg_ru\": zod.number().optional(),\n  \"max_ru\": zod.number().optional(),\n  \"sum_ru\": zod.number().optional(),\n  \"avg_time_queued_by_rc\": zod.number().optional(),\n  \"max_time_queued_by_rc\": zod.number().optional(),\n  \"avg_tidb_cpu_time\": zod.number().optional(),\n  \"avg_tikv_cpu_time\": zod.number().optional()\n})\n\n\nexport const credentialServiceListCredentialsQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"credentialType\": zod.enum(['CREDENTIAL_TYPE_UNSPECIFIED', 'HOST', 'TIDB']).optional(),\n  \"credentialId\": zod.string().optional()\n})\n\nexport const credentialServiceListCredentialsResponse = zod.object({\n  \"credentials\": zod.array(zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"userName\": zod.string(),\n  \"credentialType\": zod.enum(['CREDENTIAL_TYPE_UNSPECIFIED', 'HOST', 'TIDB']).optional(),\n  \"validateType\": zod.enum(['CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED', 'PASSWORD', 'RSAKEY']).optional(),\n  \"credentialName\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"hostCredential\": zod.object({\n  \"password\": zod.string().optional(),\n  \"publicKey\": zod.string().optional(),\n  \"privateKey\": zod.string().optional(),\n  \"hostIps\": zod.array(zod.string()).optional()\n}).optional(),\n  \"tidbCredential\": zod.object({\n  \"password\": zod.string(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n}).optional()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const credentialServiceCreateCredentialBody = zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"userName\": zod.string(),\n  \"credentialType\": zod.enum(['CREDENTIAL_TYPE_UNSPECIFIED', 'HOST', 'TIDB']).optional(),\n  \"validateType\": zod.enum(['CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED', 'PASSWORD', 'RSAKEY']).optional(),\n  \"credentialName\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"hostCredential\": zod.object({\n  \"password\": zod.string().optional(),\n  \"publicKey\": zod.string().optional(),\n  \"privateKey\": zod.string().optional(),\n  \"hostIps\": zod.array(zod.string()).optional()\n}).optional(),\n  \"tidbCredential\": zod.object({\n  \"password\": zod.string(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n}).optional()\n})\n\nexport const credentialServiceCreateCredentialResponse = zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"userName\": zod.string(),\n  \"credentialType\": zod.enum(['CREDENTIAL_TYPE_UNSPECIFIED', 'HOST', 'TIDB']).optional(),\n  \"validateType\": zod.enum(['CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED', 'PASSWORD', 'RSAKEY']).optional(),\n  \"credentialName\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"hostCredential\": zod.object({\n  \"password\": zod.string().optional(),\n  \"publicKey\": zod.string().optional(),\n  \"privateKey\": zod.string().optional(),\n  \"hostIps\": zod.array(zod.string()).optional()\n}).optional(),\n  \"tidbCredential\": zod.object({\n  \"password\": zod.string(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n}).optional()\n})\n\n\nexport const credentialServiceGetCredentialParams = zod.object({\n  \"credentialId\": zod.string()\n})\n\nexport const credentialServiceGetCredentialResponse = zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"userName\": zod.string(),\n  \"credentialType\": zod.enum(['CREDENTIAL_TYPE_UNSPECIFIED', 'HOST', 'TIDB']).optional(),\n  \"validateType\": zod.enum(['CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED', 'PASSWORD', 'RSAKEY']).optional(),\n  \"credentialName\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"hostCredential\": zod.object({\n  \"password\": zod.string().optional(),\n  \"publicKey\": zod.string().optional(),\n  \"privateKey\": zod.string().optional(),\n  \"hostIps\": zod.array(zod.string()).optional()\n}).optional(),\n  \"tidbCredential\": zod.object({\n  \"password\": zod.string(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n}).optional()\n})\n\n\nexport const credentialServiceDeleteCredentialParams = zod.object({\n  \"credentialId\": zod.string()\n})\n\nexport const credentialServiceDeleteCredentialResponse = zod.object({\n\n})\n\n\nexport const credentialServiceUpdateCredentialParams = zod.object({\n  \"credentialId\": zod.string()\n})\n\nexport const credentialServiceUpdateCredentialBody = zod.object({\n  \"userName\": zod.string(),\n  \"credentialType\": zod.enum(['CREDENTIAL_TYPE_UNSPECIFIED', 'HOST', 'TIDB']).optional(),\n  \"validateType\": zod.enum(['CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED', 'PASSWORD', 'RSAKEY']).optional(),\n  \"credentialName\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"hostCredential\": zod.object({\n  \"password\": zod.string().optional(),\n  \"publicKey\": zod.string().optional(),\n  \"privateKey\": zod.string().optional(),\n  \"hostIps\": zod.array(zod.string()).optional()\n}).optional(),\n  \"tidbCredential\": zod.object({\n  \"password\": zod.string(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n}).optional(),\n  \"forceUpdate\": zod.boolean().optional()\n})\n\nexport const credentialServiceUpdateCredentialResponse = zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"userName\": zod.string(),\n  \"credentialType\": zod.enum(['CREDENTIAL_TYPE_UNSPECIFIED', 'HOST', 'TIDB']).optional(),\n  \"validateType\": zod.enum(['CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED', 'PASSWORD', 'RSAKEY']).optional(),\n  \"credentialName\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"hostCredential\": zod.object({\n  \"password\": zod.string().optional(),\n  \"publicKey\": zod.string().optional(),\n  \"privateKey\": zod.string().optional(),\n  \"hostIps\": zod.array(zod.string()).optional()\n}).optional(),\n  \"tidbCredential\": zod.object({\n  \"password\": zod.string(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n}).optional()\n})\n\n\nexport const credentialServiceDownloadRSAKeyParams = zod.object({\n  \"credentialId\": zod.string()\n})\n\nexport const credentialServiceDownloadRSAKeyResponse = zod.object({\n  \"data\": zod.string().optional()\n})\n\n\nexport const credentialServiceGenerateRSAKeyBody = zod.object({\n\n})\n\nexport const credentialServiceGenerateRSAKeyResponse = zod.object({\n  \"publicKey\": zod.string().optional(),\n  \"privateKey\": zod.string().optional()\n})\n\n\nexport const credentialServiceValidateConnectionBody = zod.object({\n  \"credentialId\": zod.string()\n})\n\nexport const credentialServiceValidateConnectionResponse = zod.object({\n  \"connectionResult\": zod.string().optional(),\n  \"inaccessibleHosts\": zod.array(zod.string()).optional()\n})\n\n\nexport const hostServiceListHostsQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"searchValue\": zod.string().optional(),\n  \"locationIds\": zod.array(zod.string()).optional(),\n  \"tagIds\": zod.array(zod.string()).optional()\n})\n\nexport const hostServiceListHostsResponse = zod.object({\n  \"hosts\": zod.array(zod.object({\n  \"hostId\": zod.string(),\n  \"ip\": zod.string().optional(),\n  \"hostName\": zod.string().optional(),\n  \"sshPort\": zod.number().optional(),\n  \"status\": zod.enum(['initializing', 'deleting', 'deleted', 'used', 'idle']).optional(),\n  \"connectionStatus\": zod.enum(['online', 'offline']).optional(),\n  \"checkStatus\": zod.enum(['checking', 'failed', 'warning', 'succeeded']).optional(),\n  \"credentialId\": zod.string().optional(),\n  \"reportId\": zod.string().optional(),\n  \"osName\": zod.string().optional(),\n  \"osVendor\": zod.string().optional(),\n  \"osVersion\": zod.string().optional(),\n  \"osRelease\": zod.string().optional(),\n  \"osArchitecture\": zod.string().optional(),\n  \"cpuVendor\": zod.string().optional(),\n  \"cpuModel\": zod.string().optional(),\n  \"cpuSpeed\": zod.number().optional(),\n  \"cpuCache\": zod.number().optional(),\n  \"cpus\": zod.number().optional(),\n  \"cpuThreads\": zod.number().optional(),\n  \"cpuGovernor\": zod.string().optional(),\n  \"cpuArch\": zod.string().optional(),\n  \"memoryType\": zod.string().optional(),\n  \"memorySpeed\": zod.number().optional(),\n  \"memorySize\": zod.number().optional(),\n  \"memorySwap\": zod.number().optional(),\n  \"cpuNumaNodes\": zod.number().optional(),\n  \"storageTotalSize\": zod.number().optional(),\n  \"storageAvailable\": zod.number().optional(),\n  \"storageUsed\": zod.number().optional(),\n  \"diskType\": zod.string().optional(),\n  \"clusters\": zod.array(zod.object({\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n})).optional(),\n  \"nodeExporterPort\": zod.number().optional(),\n  \"tiupIds\": zod.array(zod.string()).optional(),\n  \"hostType\": zod.enum(['VM', 'PM']).optional(),\n  \"comment\": zod.string().optional(),\n  \"tags\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})).optional(),\n  \"createdTime\": zod.string().datetime().optional(),\n  \"updatedTime\": zod.string().datetime().optional(),\n  \"credential\": zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"userName\": zod.string(),\n  \"credentialType\": zod.enum(['CREDENTIAL_TYPE_UNSPECIFIED', 'HOST', 'TIDB']).optional(),\n  \"validateType\": zod.enum(['CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED', 'PASSWORD', 'RSAKEY']).optional(),\n  \"credentialName\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"hostCredential\": zod.object({\n  \"password\": zod.string().optional(),\n  \"publicKey\": zod.string().optional(),\n  \"privateKey\": zod.string().optional(),\n  \"hostIps\": zod.array(zod.string()).optional()\n}).optional(),\n  \"tidbCredential\": zod.object({\n  \"password\": zod.string(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n}).optional()\n}).optional(),\n  \"locationMappings\": zod.array(zod.object({\n  \"locationId\": zod.string().optional(),\n  \"parentId\": zod.string().optional(),\n  \"locationKey\": zod.string(),\n  \"locationValue\": zod.string()\n})).optional(),\n  \"memoryUnit\": zod.string().optional(),\n  \"storageUnit\": zod.string().optional(),\n  \"locationId\": zod.string().optional(),\n  \"cpuCores\": zod.number().optional()\n})),\n  \"nextPageToken\": zod.string(),\n  \"totalSize\": zod.number()\n})\n\n\nexport const hostServiceCreateHostsBody = zod.object({\n  \"ips\": zod.array(zod.string()).optional(),\n  \"sshPort\": zod.number().optional(),\n  \"credentialId\": zod.string().optional(),\n  \"locationId\": zod.string().optional(),\n  \"tagIds\": zod.array(zod.string()).optional(),\n  \"comment\": zod.string().optional()\n})\n\nexport const hostServiceCreateHostsResponse = zod.object({\n  \"taskId\": zod.string().optional()\n})\n\n\nexport const hostServiceImportResponse = zod.object({\n  \"task\": zod.array(zod.object({\n  \"taskId\": zod.string(),\n  \"hostId\": zod.string(),\n  \"reportId\": zod.string().optional(),\n  \"ip\": zod.string(),\n  \"userName\": zod.string().optional(),\n  \"sshPort\": zod.number(),\n  \"status\": zod.enum(['init', 'existed', 'succeeded', 'failed']).optional(),\n  \"tags\": zod.string().optional(),\n  \"locationId\": zod.string().optional(),\n  \"credentialId\": zod.string().optional(),\n  \"hostName\": zod.string().optional(),\n  \"tagsList\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})).optional(),\n  \"locationMappings\": zod.array(zod.object({\n  \"locationId\": zod.string().optional(),\n  \"parentId\": zod.string().optional(),\n  \"locationKey\": zod.string(),\n  \"locationValue\": zod.string()\n})).optional(),\n  \"credential\": zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"userName\": zod.string(),\n  \"credentialType\": zod.enum(['CREDENTIAL_TYPE_UNSPECIFIED', 'HOST', 'TIDB']).optional(),\n  \"validateType\": zod.enum(['CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED', 'PASSWORD', 'RSAKEY']).optional(),\n  \"credentialName\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"hostCredential\": zod.object({\n  \"password\": zod.string().optional(),\n  \"publicKey\": zod.string().optional(),\n  \"privateKey\": zod.string().optional(),\n  \"hostIps\": zod.array(zod.string()).optional()\n}).optional(),\n  \"tidbCredential\": zod.object({\n  \"password\": zod.string(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n}).optional()\n}).optional()\n})),\n  \"taskId\": zod.string()\n})\n\n\nexport const hostServiceImportTaskParams = zod.object({\n  \"taskId\": zod.string()\n})\n\nexport const hostServiceImportTaskResponse = zod.object({\n  \"task\": zod.array(zod.object({\n  \"taskId\": zod.string(),\n  \"hostId\": zod.string(),\n  \"reportId\": zod.string().optional(),\n  \"ip\": zod.string(),\n  \"userName\": zod.string().optional(),\n  \"sshPort\": zod.number(),\n  \"status\": zod.enum(['init', 'existed', 'succeeded', 'failed']).optional(),\n  \"tags\": zod.string().optional(),\n  \"locationId\": zod.string().optional(),\n  \"credentialId\": zod.string().optional(),\n  \"hostName\": zod.string().optional(),\n  \"tagsList\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})).optional(),\n  \"locationMappings\": zod.array(zod.object({\n  \"locationId\": zod.string().optional(),\n  \"parentId\": zod.string().optional(),\n  \"locationKey\": zod.string(),\n  \"locationValue\": zod.string()\n})).optional(),\n  \"credential\": zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"userName\": zod.string(),\n  \"credentialType\": zod.enum(['CREDENTIAL_TYPE_UNSPECIFIED', 'HOST', 'TIDB']).optional(),\n  \"validateType\": zod.enum(['CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED', 'PASSWORD', 'RSAKEY']).optional(),\n  \"credentialName\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"hostCredential\": zod.object({\n  \"password\": zod.string().optional(),\n  \"publicKey\": zod.string().optional(),\n  \"privateKey\": zod.string().optional(),\n  \"hostIps\": zod.array(zod.string()).optional()\n}).optional(),\n  \"tidbCredential\": zod.object({\n  \"password\": zod.string(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n}).optional()\n}).optional()\n})),\n  \"taskId\": zod.string()\n})\n\n\nexport const hostServiceHostConfirmParams = zod.object({\n  \"taskId\": zod.string()\n})\n\nexport const hostServiceHostConfirmBody = zod.object({\n\n})\n\nexport const hostServiceHostConfirmResponse = zod.object({\n  \"taskId\": zod.string()\n})\n\n\nexport const hostServiceGetHostParams = zod.object({\n  \"hostId\": zod.string()\n})\n\nexport const hostServiceGetHostResponse = zod.object({\n  \"hostId\": zod.string(),\n  \"ip\": zod.string().optional(),\n  \"hostName\": zod.string().optional(),\n  \"sshPort\": zod.number().optional(),\n  \"status\": zod.enum(['initializing', 'deleting', 'deleted', 'used', 'idle']).optional(),\n  \"connectionStatus\": zod.enum(['online', 'offline']).optional(),\n  \"checkStatus\": zod.enum(['checking', 'failed', 'warning', 'succeeded']).optional(),\n  \"credentialId\": zod.string().optional(),\n  \"reportId\": zod.string().optional(),\n  \"osName\": zod.string().optional(),\n  \"osVendor\": zod.string().optional(),\n  \"osVersion\": zod.string().optional(),\n  \"osRelease\": zod.string().optional(),\n  \"osArchitecture\": zod.string().optional(),\n  \"cpuVendor\": zod.string().optional(),\n  \"cpuModel\": zod.string().optional(),\n  \"cpuSpeed\": zod.number().optional(),\n  \"cpuCache\": zod.number().optional(),\n  \"cpus\": zod.number().optional(),\n  \"cpuThreads\": zod.number().optional(),\n  \"cpuGovernor\": zod.string().optional(),\n  \"cpuArch\": zod.string().optional(),\n  \"memoryType\": zod.string().optional(),\n  \"memorySpeed\": zod.number().optional(),\n  \"memorySize\": zod.number().optional(),\n  \"memorySwap\": zod.number().optional(),\n  \"cpuNumaNodes\": zod.number().optional(),\n  \"storageTotalSize\": zod.number().optional(),\n  \"storageAvailable\": zod.number().optional(),\n  \"storageUsed\": zod.number().optional(),\n  \"diskType\": zod.string().optional(),\n  \"clusters\": zod.array(zod.object({\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n})).optional(),\n  \"nodeExporterPort\": zod.number().optional(),\n  \"tiupIds\": zod.array(zod.string()).optional(),\n  \"hostType\": zod.enum(['VM', 'PM']).optional(),\n  \"comment\": zod.string().optional(),\n  \"tags\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})).optional(),\n  \"createdTime\": zod.string().datetime().optional(),\n  \"updatedTime\": zod.string().datetime().optional(),\n  \"credential\": zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"userName\": zod.string(),\n  \"credentialType\": zod.enum(['CREDENTIAL_TYPE_UNSPECIFIED', 'HOST', 'TIDB']).optional(),\n  \"validateType\": zod.enum(['CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED', 'PASSWORD', 'RSAKEY']).optional(),\n  \"credentialName\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"hostCredential\": zod.object({\n  \"password\": zod.string().optional(),\n  \"publicKey\": zod.string().optional(),\n  \"privateKey\": zod.string().optional(),\n  \"hostIps\": zod.array(zod.string()).optional()\n}).optional(),\n  \"tidbCredential\": zod.object({\n  \"password\": zod.string(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n}).optional()\n}).optional(),\n  \"locationMappings\": zod.array(zod.object({\n  \"locationId\": zod.string().optional(),\n  \"parentId\": zod.string().optional(),\n  \"locationKey\": zod.string(),\n  \"locationValue\": zod.string()\n})).optional(),\n  \"memoryUnit\": zod.string().optional(),\n  \"storageUnit\": zod.string().optional(),\n  \"locationId\": zod.string().optional(),\n  \"cpuCores\": zod.number().optional()\n})\n\n\nexport const hostServiceDeleteParams = zod.object({\n  \"hostId\": zod.string()\n})\n\nexport const hostServiceDeleteResponse = zod.object({\n\n})\n\n\nexport const hostServiceUpdateHostParams = zod.object({\n  \"hostId\": zod.string()\n})\n\nexport const hostServiceUpdateHostBody = zod.object({\n  \"host\": zod.object({\n  \"hostId\": zod.string().optional(),\n  \"sshPort\": zod.number().optional(),\n  \"credentialId\": zod.string().optional(),\n  \"locationId\": zod.string().optional(),\n  \"tagIds\": zod.array(zod.string()).optional(),\n  \"comment\": zod.string().optional()\n}).optional()\n})\n\nexport const hostServiceUpdateHostResponse = zod.object({\n  \"hostId\": zod.string(),\n  \"ip\": zod.string().optional(),\n  \"hostName\": zod.string().optional(),\n  \"sshPort\": zod.number().optional(),\n  \"status\": zod.enum(['initializing', 'deleting', 'deleted', 'used', 'idle']).optional(),\n  \"connectionStatus\": zod.enum(['online', 'offline']).optional(),\n  \"checkStatus\": zod.enum(['checking', 'failed', 'warning', 'succeeded']).optional(),\n  \"credentialId\": zod.string().optional(),\n  \"reportId\": zod.string().optional(),\n  \"osName\": zod.string().optional(),\n  \"osVendor\": zod.string().optional(),\n  \"osVersion\": zod.string().optional(),\n  \"osRelease\": zod.string().optional(),\n  \"osArchitecture\": zod.string().optional(),\n  \"cpuVendor\": zod.string().optional(),\n  \"cpuModel\": zod.string().optional(),\n  \"cpuSpeed\": zod.number().optional(),\n  \"cpuCache\": zod.number().optional(),\n  \"cpus\": zod.number().optional(),\n  \"cpuThreads\": zod.number().optional(),\n  \"cpuGovernor\": zod.string().optional(),\n  \"cpuArch\": zod.string().optional(),\n  \"memoryType\": zod.string().optional(),\n  \"memorySpeed\": zod.number().optional(),\n  \"memorySize\": zod.number().optional(),\n  \"memorySwap\": zod.number().optional(),\n  \"cpuNumaNodes\": zod.number().optional(),\n  \"storageTotalSize\": zod.number().optional(),\n  \"storageAvailable\": zod.number().optional(),\n  \"storageUsed\": zod.number().optional(),\n  \"diskType\": zod.string().optional(),\n  \"clusters\": zod.array(zod.object({\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n})).optional(),\n  \"nodeExporterPort\": zod.number().optional(),\n  \"tiupIds\": zod.array(zod.string()).optional(),\n  \"hostType\": zod.enum(['VM', 'PM']).optional(),\n  \"comment\": zod.string().optional(),\n  \"tags\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})).optional(),\n  \"createdTime\": zod.string().datetime().optional(),\n  \"updatedTime\": zod.string().datetime().optional(),\n  \"credential\": zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"userName\": zod.string(),\n  \"credentialType\": zod.enum(['CREDENTIAL_TYPE_UNSPECIFIED', 'HOST', 'TIDB']).optional(),\n  \"validateType\": zod.enum(['CREDENTIAL_VALIDATE_TYPE_UNSPECIFIED', 'PASSWORD', 'RSAKEY']).optional(),\n  \"credentialName\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"hostCredential\": zod.object({\n  \"password\": zod.string().optional(),\n  \"publicKey\": zod.string().optional(),\n  \"privateKey\": zod.string().optional(),\n  \"hostIps\": zod.array(zod.string()).optional()\n}).optional(),\n  \"tidbCredential\": zod.object({\n  \"password\": zod.string(),\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional()\n}).optional()\n}).optional(),\n  \"locationMappings\": zod.array(zod.object({\n  \"locationId\": zod.string().optional(),\n  \"parentId\": zod.string().optional(),\n  \"locationKey\": zod.string(),\n  \"locationValue\": zod.string()\n})).optional(),\n  \"memoryUnit\": zod.string().optional(),\n  \"storageUnit\": zod.string().optional(),\n  \"locationId\": zod.string().optional(),\n  \"cpuCores\": zod.number().optional()\n})\n\n\nexport const hostServiceGetDisksParams = zod.object({\n  \"hostId\": zod.string()\n})\n\nexport const hostServiceGetDisksResponse = zod.object({\n  \"disk\": zod.array(zod.object({\n  \"path\": zod.string().optional(),\n  \"totalSize\": zod.number().optional(),\n  \"usedSpace\": zod.number().optional(),\n  \"availableSpace\": zod.number().optional(),\n  \"mountingDir\": zod.string().optional(),\n  \"diskType\": zod.enum(['HDD', 'SSD']).optional()\n})).optional()\n})\n\n\nexport const metricsServiceGetHostMetricDataParams = zod.object({\n  \"hostId\": zod.string(),\n  \"name\": zod.string()\n})\n\nexport const metricsServiceGetHostMetricDataQueryParams = zod.object({\n  \"startTime\": zod.string(),\n  \"endTime\": zod.string(),\n  \"step\": zod.string().optional(),\n  \"label\": zod.string().optional(),\n  \"range\": zod.string().optional()\n})\n\nexport const metricsServiceGetHostMetricDataResponse = zod.object({\n  \"status\": zod.string().optional(),\n  \"data\": zod.array(zod.object({\n  \"expr\": zod.string().optional(),\n  \"legend\": zod.string().optional(),\n  \"result\": zod.array(zod.object({\n  \"metric\": zod.object({\n  \"instance\": zod.string().optional(),\n  \"sqlType\": zod.string().optional(),\n  \"type\": zod.string().optional(),\n  \"result\": zod.string().optional(),\n  \"txnMode\": zod.string().optional(),\n  \"job\": zod.string().optional(),\n  \"device\": zod.string().optional(),\n  \"fstype\": zod.string().optional(),\n  \"mountpoint\": zod.string().optional(),\n  \"module\": zod.string().optional(),\n  \"kind\": zod.string().optional(),\n  \"ping\": zod.string().optional()\n}).optional(),\n  \"values\": zod.array(zod.object({\n  \"timestamp\": zod.number().optional(),\n  \"value\": zod.string().optional()\n})).optional()\n})).optional()\n})).optional()\n})\n\n\nexport const hostServiceReportParams = zod.object({\n  \"hostId\": zod.string(),\n  \"reportId\": zod.string()\n})\n\nexport const hostServiceReportResponse = zod.object({\n  \"taskId\": zod.string(),\n  \"taskState\": zod.enum(['init', 'running', 'success', 'fail']).optional(),\n  \"reports\": zod.array(zod.object({\n  \"reportId\": zod.string(),\n  \"hostId\": zod.string().optional(),\n  \"checkId\": zod.string().optional(),\n  \"checkName\": zod.string().optional(),\n  \"checkOut\": zod.string().optional(),\n  \"checkDesc\": zod.string().optional(),\n  \"checkResult\": zod.enum(['passed', 'failed', 'warned']).optional(),\n  \"optional\": zod.boolean().enum(['true', 'false']).optional(),\n  \"fixable\": zod.boolean().enum(['true', 'false']).optional(),\n  \"checkBody\": zod.string().optional()\n})).optional()\n})\n\n\nexport const hostServiceGetTiDBProcessesParams = zod.object({\n  \"hostId\": zod.string()\n})\n\nexport const hostServiceGetTiDBProcessesResponse = zod.object({\n  \"tiDBProcesses\": zod.array(zod.object({\n  \"uid\": zod.string().optional(),\n  \"pid\": zod.number().optional(),\n  \"ppid\": zod.number().optional(),\n  \"startTime\": zod.string().optional(),\n  \"runningTime\": zod.string().optional(),\n  \"cmd\": zod.string().optional()\n})).optional()\n})\n\n\nexport const hostServiceFixParams = zod.object({\n  \"hostId\": zod.string()\n})\n\nexport const hostServiceFixResponse = zod.object({\n  \"hostId\": zod.string(),\n  \"taskId\": zod.string(),\n  \"reportId\": zod.string()\n})\n\n\nexport const hostServiceCheckParams = zod.object({\n  \"hostId\": zod.string()\n})\n\nexport const hostServiceCheckResponse = zod.object({\n  \"hostId\": zod.string(),\n  \"taskId\": zod.string(),\n  \"reportId\": zod.string()\n})\n\n\nexport const hostServiceBatchDeleteBody = zod.object({\n  \"hostId\": zod.array(zod.string())\n})\n\nexport const hostServiceBatchDeleteResponse = zod.object({\n\n})\n\n\nexport const hostServiceDownloadListHostsQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"searchValue\": zod.string().optional(),\n  \"locationIds\": zod.array(zod.string()).optional(),\n  \"tagIds\": zod.array(zod.string()).optional()\n})\n\nexport const hostServiceDownloadListHostsResponse = zod.object({\n  \"data\": zod.string().optional()\n})\n\n\nexport const hostServiceDownloadHostTemplateResponse = zod.object({\n  \"data\": zod.string().optional()\n})\n\n\nexport const licenseServiceGetLicenseResponse = zod.object({\n  \"licenseId\": zod.string().optional(),\n  \"version\": zod.string().optional(),\n  \"licenseType\": zod.enum(['free', 'ultimate']).optional(),\n  \"allow\": zod.array(zod.string()).optional(),\n  \"deny\": zod.array(zod.string()).optional(),\n  \"activateAt\": zod.string().datetime().optional(),\n  \"expirationAt\": zod.string().datetime().optional(),\n  \"signature\": zod.string().optional(),\n  \"hosts\": zod.string().optional(),\n  \"vcpu\": zod.string().optional(),\n  \"alerts\": zod.string().optional(),\n  \"customerCode\": zod.string().optional(),\n  \"deviceCode\": zod.string().optional(),\n  \"status\": zod.enum(['active', 'expired', 'expiring', 'invalid', 'revoked']).optional()\n})\n\n\nexport const licenseServiceGetDeviceCodeResponse = zod.object({\n  \"deviceCode\": zod.string().optional()\n})\n\n\nexport const licenseServiceActivateLicenseResponse = zod.object({\n  \"licenseId\": zod.string().optional(),\n  \"version\": zod.string().optional(),\n  \"licenseType\": zod.enum(['free', 'ultimate']).optional(),\n  \"allow\": zod.array(zod.string()).optional(),\n  \"deny\": zod.array(zod.string()).optional(),\n  \"activateAt\": zod.string().datetime().optional(),\n  \"expirationAt\": zod.string().datetime().optional(),\n  \"signature\": zod.string().optional(),\n  \"hosts\": zod.string().optional(),\n  \"vcpu\": zod.string().optional(),\n  \"alerts\": zod.string().optional(),\n  \"customerCode\": zod.string().optional(),\n  \"deviceCode\": zod.string().optional(),\n  \"status\": zod.enum(['active', 'expired', 'expiring', 'invalid', 'revoked']).optional()\n})\n\n\nexport const licenseServiceActivateFreeLicenseResponse = zod.object({\n  \"licenseId\": zod.string().optional(),\n  \"version\": zod.string().optional(),\n  \"licenseType\": zod.enum(['free', 'ultimate']).optional(),\n  \"allow\": zod.array(zod.string()).optional(),\n  \"deny\": zod.array(zod.string()).optional(),\n  \"activateAt\": zod.string().datetime().optional(),\n  \"expirationAt\": zod.string().datetime().optional(),\n  \"signature\": zod.string().optional(),\n  \"hosts\": zod.string().optional(),\n  \"vcpu\": zod.string().optional(),\n  \"alerts\": zod.string().optional(),\n  \"customerCode\": zod.string().optional(),\n  \"deviceCode\": zod.string().optional(),\n  \"status\": zod.enum(['active', 'expired', 'expiring', 'invalid', 'revoked']).optional()\n})\n\n\nexport const locationServiceListLocationsQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"locationKey\": zod.enum(['zone', 'dc', 'rack']).optional(),\n  \"locationValue\": zod.string().optional(),\n  \"parentId\": zod.string().optional()\n})\n\nexport const locationServiceListLocationsResponse = zod.object({\n  \"locations\": zod.array(zod.object({\n  \"locationId\": zod.string().optional(),\n  \"parentId\": zod.string().optional(),\n  \"locationKey\": zod.enum(['zone', 'dc', 'rack']).optional(),\n  \"locationValue\": zod.string().optional()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const locationServiceCreateLocationsBody = zod.object({\n  \"locationId\": zod.string().optional(),\n  \"parentId\": zod.string().optional(),\n  \"locationKey\": zod.enum(['zone', 'dc', 'rack']).optional(),\n  \"locationValue\": zod.string().optional()\n})\n\nexport const locationServiceCreateLocationsResponse = zod.object({\n  \"locationId\": zod.string().optional(),\n  \"parentId\": zod.string().optional(),\n  \"locationKey\": zod.enum(['zone', 'dc', 'rack']).optional(),\n  \"locationValue\": zod.string().optional()\n})\n\n\nexport const locationServiceGetLocationsParams = zod.object({\n  \"locationId\": zod.string()\n})\n\nexport const locationServiceGetLocationsResponse = zod.object({\n  \"locationId\": zod.string().optional(),\n  \"parentId\": zod.string().optional(),\n  \"locationKey\": zod.enum(['zone', 'dc', 'rack']).optional(),\n  \"locationValue\": zod.string().optional()\n})\n\n\nexport const locationServiceDeleteLocationParams = zod.object({\n  \"locationId\": zod.string()\n})\n\nexport const locationServiceDeleteLocationResponse = zod.object({\n\n})\n\n\nexport const locationServiceUpdateLocationsParams = zod.object({\n  \"locationId\": zod.string()\n})\n\nexport const locationServiceUpdateLocationsBody = zod.object({\n  \"location\": zod.object({\n  \"locationId\": zod.string().optional(),\n  \"parentId\": zod.string().optional(),\n  \"locationKey\": zod.enum(['zone', 'dc', 'rack']).optional(),\n  \"locationValue\": zod.string().optional()\n}).optional()\n})\n\nexport const locationServiceUpdateLocationsResponse = zod.object({\n  \"locationId\": zod.string().optional(),\n  \"parentId\": zod.string().optional(),\n  \"locationKey\": zod.enum(['zone', 'dc', 'rack']).optional(),\n  \"locationValue\": zod.string().optional()\n})\n\n\nexport const userServiceLoginBody = zod.object({\n  \"userId\": zod.string(),\n  \"password\": zod.string().optional()\n})\n\nexport const userServiceLoginResponse = zod.object({\n\n})\n\n\nexport const userServiceLogoutResponse = zod.object({\n\n})\n\n\nexport const metricsServiceGetMetricsQueryParams = zod.object({\n  \"class\": zod.enum(['unspecified', 'cluster', 'host', 'overview']).optional(),\n  \"group\": zod.enum(['unspecified', 'overview', 'basic', 'advanced', 'resource', 'performance', 'process']).optional(),\n  \"type\": zod.string().optional(),\n  \"name\": zod.string().optional()\n})\n\nexport const metricsServiceGetMetricsResponse = zod.object({\n  \"metrics\": zod.array(zod.object({\n  \"class\": zod.string().optional(),\n  \"group\": zod.string().optional(),\n  \"type\": zod.string().optional(),\n  \"order\": zod.number().optional(),\n  \"displayName\": zod.string().optional(),\n  \"name\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"metric\": zod.object({\n  \"name\": zod.string().optional(),\n  \"unit\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"minTidbVersion\": zod.string().optional(),\n  \"maxTidbVersion\": zod.string().optional(),\n  \"isBuiltin\": zod.boolean().optional(),\n  \"expressions\": zod.array(zod.object({\n  \"name\": zod.string().optional(),\n  \"promql\": zod.string().optional(),\n  \"promMetric\": zod.string().optional(),\n  \"labels\": zod.array(zod.string()).optional(),\n  \"type\": zod.string().optional(),\n  \"legend\": zod.string().optional(),\n  \"minTidbVersion\": zod.string().optional(),\n  \"maxTidbVersion\": zod.string().optional()\n})).optional()\n}).optional()\n})).optional()\n})\n\n\nexport const metricsServiceGetTopMetricConfigResponse = zod.object({\n  \"cacheFlushIntervalInMinutes\": zod.number().optional()\n})\n\n\nexport const metricsServiceGetTopMetricDataParams = zod.object({\n  \"name\": zod.string()\n})\n\nexport const metricsServiceGetTopMetricDataQueryParams = zod.object({\n  \"startTime\": zod.string(),\n  \"endTime\": zod.string(),\n  \"step\": zod.string().optional(),\n  \"limit\": zod.string().optional()\n})\n\nexport const metricsServiceGetTopMetricDataResponse = zod.object({\n  \"status\": zod.string().optional(),\n  \"data\": zod.array(zod.object({\n  \"expr\": zod.string().optional(),\n  \"legend\": zod.string().optional(),\n  \"result\": zod.array(zod.object({\n  \"metric\": zod.object({\n  \"instance\": zod.string().optional(),\n  \"sqlType\": zod.string().optional(),\n  \"type\": zod.string().optional(),\n  \"result\": zod.string().optional(),\n  \"txnMode\": zod.string().optional(),\n  \"job\": zod.string().optional(),\n  \"device\": zod.string().optional(),\n  \"fstype\": zod.string().optional(),\n  \"mountpoint\": zod.string().optional(),\n  \"module\": zod.string().optional(),\n  \"kind\": zod.string().optional(),\n  \"ping\": zod.string().optional()\n}).optional(),\n  \"values\": zod.array(zod.object({\n  \"timestamp\": zod.number().optional(),\n  \"value\": zod.string().optional()\n})).optional()\n})).optional()\n})).optional()\n})\n\n\nexport const metricsServiceGetOverviewStatusQueryParams = zod.object({\n  \"taskStartTime\": zod.string().optional(),\n  \"taskEndTime\": zod.string().optional()\n})\n\nexport const metricsServiceGetOverviewStatusResponse = zod.object({\n  \"clusters\": zod.array(zod.object({\n  \"status\": zod.string().optional(),\n  \"count\": zod.number().optional()\n})).optional(),\n  \"hosts\": zod.array(zod.object({\n  \"status\": zod.string().optional(),\n  \"count\": zod.number().optional()\n})).optional(),\n  \"alerts\": zod.array(zod.object({\n  \"status\": zod.string().optional(),\n  \"count\": zod.number().optional()\n})).optional(),\n  \"alertLevels\": zod.array(zod.object({\n  \"status\": zod.string().optional(),\n  \"count\": zod.number().optional()\n})).optional(),\n  \"brTasks\": zod.array(zod.object({\n  \"status\": zod.string().optional(),\n  \"count\": zod.number().optional()\n})).optional(),\n  \"sysTasks\": zod.array(zod.object({\n  \"status\": zod.string().optional(),\n  \"count\": zod.number().optional()\n})).optional(),\n  \"otherTasks\": zod.array(zod.object({\n  \"status\": zod.string().optional(),\n  \"count\": zod.number().optional()\n})).optional()\n})\n\n\nexport const roleServiceListRolesQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"roleNameLike\": zod.string().optional(),\n  \"roleName\": zod.string().optional()\n})\n\nexport const roleServiceListRolesResponse = zod.object({\n  \"roles\": zod.array(zod.object({\n  \"id\": zod.number().optional(),\n  \"roleName\": zod.string().optional(),\n  \"roleType\": zod.number().optional(),\n  \"roleTypeDesc\": zod.string().optional(),\n  \"detail\": zod.string().optional(),\n  \"note\": zod.string().optional(),\n  \"createTime\": zod.string().datetime().optional(),\n  \"updateTime\": zod.string().datetime().optional()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const roleServiceCreateRoleBody = zod.object({\n  \"id\": zod.number().optional(),\n  \"roleName\": zod.string().optional(),\n  \"roleType\": zod.number().optional(),\n  \"roleTypeDesc\": zod.string().optional(),\n  \"detail\": zod.string().optional(),\n  \"note\": zod.string().optional()\n})\n\nexport const roleServiceCreateRoleResponse = zod.object({\n  \"id\": zod.number().optional(),\n  \"roleName\": zod.string().optional(),\n  \"roleType\": zod.number().optional(),\n  \"roleTypeDesc\": zod.string().optional(),\n  \"detail\": zod.string().optional(),\n  \"note\": zod.string().optional(),\n  \"createTime\": zod.string().datetime().optional(),\n  \"updateTime\": zod.string().datetime().optional()\n})\n\n\nexport const roleServiceDeleteRoleParams = zod.object({\n  \"roleId\": zod.number()\n})\n\nexport const roleServiceDeleteRoleResponse = zod.object({\n\n})\n\n\nexport const roleServiceUpdateRoleParams = zod.object({\n  \"roleId\": zod.number()\n})\n\nexport const roleServiceUpdateRoleBody = zod.object({\n  \"roleName\": zod.string(),\n  \"roleType\": zod.number().optional(),\n  \"detail\": zod.string().optional(),\n  \"note\": zod.string().optional()\n})\n\nexport const roleServiceUpdateRoleResponse = zod.object({\n  \"id\": zod.number().optional(),\n  \"roleName\": zod.string().optional(),\n  \"roleType\": zod.number().optional(),\n  \"roleTypeDesc\": zod.string().optional(),\n  \"detail\": zod.string().optional(),\n  \"note\": zod.string().optional(),\n  \"createTime\": zod.string().datetime().optional(),\n  \"updateTime\": zod.string().datetime().optional()\n})\n\n\nexport const tagServiceListTagsQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional()\n})\n\nexport const tagServiceListTagsResponse = zod.object({\n  \"tags\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const tagServiceCreateTagBody = zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})\n\nexport const tagServiceCreateTagResponse = zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})\n\n\nexport const tagServiceGetTagParams = zod.object({\n  \"tagId\": zod.string()\n})\n\nexport const tagServiceGetTagResponse = zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})\n\n\nexport const tagServiceDeleteTagParams = zod.object({\n  \"tagId\": zod.string()\n})\n\nexport const tagServiceDeleteTagResponse = zod.object({\n\n})\n\n\nexport const tagServiceUpdateTagParams = zod.object({\n  \"tagId\": zod.string()\n})\n\nexport const tagServiceUpdateTagBody = zod.object({\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})\n\nexport const tagServiceUpdateTagResponse = zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})\n\n\nexport const tagServiceGetTagWithBindingsParams = zod.object({\n  \"tagId\": zod.string()\n})\n\nexport const tagServiceGetTagWithBindingsResponse = zod.object({\n  \"tag\": zod.object({\n  \"tagInfo\": zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n}).optional(),\n  \"bindObjects\": zod.array(zod.object({\n  \"resourceType\": zod.enum(['TAG_BIND_RESOURCE_TYPE_UNSPECIFIED', 'HOST', 'TIUP', 'CLUSTER']).optional(),\n  \"resources\": zod.array(zod.object({\n  \"resourceId\": zod.string().optional(),\n  \"resourceName\": zod.string()\n}))\n})).optional()\n}).optional()\n})\n\n\nexport const tagServiceBatchCreateTagsBody = zod.object({\n  \"tags\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n}))\n})\n\nexport const tagServiceBatchCreateTagsResponse = zod.object({\n  \"tags\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})).optional()\n})\n\n\nexport const tagServiceBindResourceBody = zod.object({\n  \"resourceType\": zod.enum(['TAG_BIND_RESOURCE_TYPE_UNSPECIFIED', 'HOST', 'TIUP', 'CLUSTER']).optional(),\n  \"resourceId\": zod.string(),\n  \"tagIds\": zod.array(zod.string()).optional()\n})\n\nexport const tagServiceBindResourceResponse = zod.object({\n  \"tags\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})).optional()\n})\n\n\nexport const tagServiceBindTagBody = zod.object({\n  \"tagId\": zod.string(),\n  \"bindObjects\": zod.array(zod.object({\n  \"resourceType\": zod.enum(['TAG_BIND_RESOURCE_TYPE_UNSPECIFIED', 'HOST', 'TIUP', 'CLUSTER']).optional(),\n  \"resources\": zod.array(zod.object({\n  \"resourceId\": zod.string().optional(),\n  \"resourceName\": zod.string()\n}))\n})).optional()\n})\n\nexport const tagServiceBindTagResponse = zod.object({\n  \"tag\": zod.object({\n  \"tagInfo\": zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n}).optional(),\n  \"bindObjects\": zod.array(zod.object({\n  \"resourceType\": zod.enum(['TAG_BIND_RESOURCE_TYPE_UNSPECIFIED', 'HOST', 'TIUP', 'CLUSTER']).optional(),\n  \"resources\": zod.array(zod.object({\n  \"resourceId\": zod.string().optional(),\n  \"resourceName\": zod.string()\n}))\n})).optional()\n}).optional()\n})\n\n\nexport const tagServiceListTagsByResourceTypeQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"keyword\": zod.string().optional(),\n  \"resourceType\": zod.enum(['TAG_BIND_RESOURCE_TYPE_UNSPECIFIED', 'HOST', 'TIUP', 'CLUSTER']).optional()\n})\n\nexport const tagServiceListTagsByResourceTypeResponse = zod.object({\n  \"tags\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const tagServiceListTagKeysQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"keyword\": zod.string().optional(),\n  \"resourceType\": zod.enum(['TAG_BIND_RESOURCE_TYPE_UNSPECIFIED', 'HOST', 'TIUP', 'CLUSTER']).optional()\n})\n\nexport const tagServiceListTagKeysResponse = zod.object({\n  \"tagKeys\": zod.array(zod.string()).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const tagServiceListTagsWithBindingsQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"tagKeys\": zod.array(zod.string()).optional(),\n  \"tagValueLike\": zod.string().optional()\n})\n\nexport const tagServiceListTagsWithBindingsResponse = zod.object({\n  \"tags\": zod.array(zod.object({\n  \"tagInfo\": zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n}).optional(),\n  \"bindObjects\": zod.array(zod.object({\n  \"resourceType\": zod.enum(['TAG_BIND_RESOURCE_TYPE_UNSPECIFIED', 'HOST', 'TIUP', 'CLUSTER']).optional(),\n  \"resources\": zod.array(zod.object({\n  \"resourceId\": zod.string().optional(),\n  \"resourceName\": zod.string()\n}))\n})).optional()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const tiupsServiceListTiupsQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"searchValue\": zod.string().optional(),\n  \"tagIds\": zod.array(zod.string()).optional(),\n  \"hostIds\": zod.array(zod.string()).optional()\n})\n\nexport const tiupsServiceListTiupsResponse = zod.object({\n  \"tiups\": zod.array(zod.object({\n  \"tiupId\": zod.string().optional(),\n  \"name\": zod.string().optional(),\n  \"tiupHome\": zod.string().optional(),\n  \"version\": zod.string().optional(),\n  \"credentialId\": zod.string().optional(),\n  \"tags\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})).optional(),\n  \"description\": zod.string().optional(),\n  \"hostId\": zod.string().optional(),\n  \"host\": zod.object({\n  \"hostId\": zod.string(),\n  \"ip\": zod.string().optional(),\n  \"hostName\": zod.string().optional(),\n  \"sshPort\": zod.number().optional(),\n  \"credentialId\": zod.string().optional(),\n  \"osName\": zod.string().optional(),\n  \"osVersion\": zod.string().optional(),\n  \"osRelease\": zod.string().optional(),\n  \"osArchitecture\": zod.string().optional(),\n  \"hostType\": zod.enum(['VM', 'PM']).optional(),\n  \"locationId\": zod.string().optional(),\n  \"createdTime\": zod.string().datetime().optional(),\n  \"updatedTime\": zod.string().datetime().optional(),\n  \"credential\": zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"credentialName\": zod.string().optional(),\n  \"credentialType\": zod.string().optional(),\n  \"userName\": zod.string().optional()\n}).optional()\n}).optional()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const tiupsServiceCreateTiupsBody = zod.object({\n  \"hostId\": zod.string().optional(),\n  \"tiupHome\": zod.string().optional(),\n  \"description\": zod.string().optional(),\n  \"name\": zod.string().optional(),\n  \"tagIds\": zod.array(zod.string()).optional()\n})\n\nexport const tiupsServiceCreateTiupsResponse = zod.object({\n  \"tiupId\": zod.string().optional(),\n  \"name\": zod.string().optional(),\n  \"tiupHome\": zod.string().optional(),\n  \"version\": zod.string().optional(),\n  \"credentialId\": zod.string().optional(),\n  \"tags\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})).optional(),\n  \"description\": zod.string().optional(),\n  \"hostId\": zod.string().optional(),\n  \"host\": zod.object({\n  \"hostId\": zod.string(),\n  \"ip\": zod.string().optional(),\n  \"hostName\": zod.string().optional(),\n  \"sshPort\": zod.number().optional(),\n  \"credentialId\": zod.string().optional(),\n  \"osName\": zod.string().optional(),\n  \"osVersion\": zod.string().optional(),\n  \"osRelease\": zod.string().optional(),\n  \"osArchitecture\": zod.string().optional(),\n  \"hostType\": zod.enum(['VM', 'PM']).optional(),\n  \"locationId\": zod.string().optional(),\n  \"createdTime\": zod.string().datetime().optional(),\n  \"updatedTime\": zod.string().datetime().optional(),\n  \"credential\": zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"credentialName\": zod.string().optional(),\n  \"credentialType\": zod.string().optional(),\n  \"userName\": zod.string().optional()\n}).optional()\n}).optional()\n})\n\n\nexport const tiupsServiceGetTiupsParams = zod.object({\n  \"tiupId\": zod.string()\n})\n\nexport const tiupsServiceGetTiupsResponse = zod.object({\n  \"tiupId\": zod.string().optional(),\n  \"name\": zod.string().optional(),\n  \"tiupHome\": zod.string().optional(),\n  \"version\": zod.string().optional(),\n  \"credentialId\": zod.string().optional(),\n  \"tags\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})).optional(),\n  \"description\": zod.string().optional(),\n  \"hostId\": zod.string().optional(),\n  \"host\": zod.object({\n  \"hostId\": zod.string(),\n  \"ip\": zod.string().optional(),\n  \"hostName\": zod.string().optional(),\n  \"sshPort\": zod.number().optional(),\n  \"credentialId\": zod.string().optional(),\n  \"osName\": zod.string().optional(),\n  \"osVersion\": zod.string().optional(),\n  \"osRelease\": zod.string().optional(),\n  \"osArchitecture\": zod.string().optional(),\n  \"hostType\": zod.enum(['VM', 'PM']).optional(),\n  \"locationId\": zod.string().optional(),\n  \"createdTime\": zod.string().datetime().optional(),\n  \"updatedTime\": zod.string().datetime().optional(),\n  \"credential\": zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"credentialName\": zod.string().optional(),\n  \"credentialType\": zod.string().optional(),\n  \"userName\": zod.string().optional()\n}).optional()\n}).optional()\n})\n\n\nexport const tiupsServiceDeleteTiupsParams = zod.object({\n  \"tiupId\": zod.string()\n})\n\nexport const tiupsServiceDeleteTiupsResponse = zod.object({\n\n})\n\n\nexport const tiupsServiceUpdateTiupsParams = zod.object({\n  \"tiupId\": zod.string()\n})\n\nexport const tiupsServiceUpdateTiupsBody = zod.object({\n  \"tiups\": zod.object({\n  \"tagIds\": zod.array(zod.string()).optional(),\n  \"description\": zod.string().optional(),\n  \"name\": zod.string().optional()\n}).optional()\n})\n\nexport const tiupsServiceUpdateTiupsResponse = zod.object({\n  \"tiupId\": zod.string().optional(),\n  \"name\": zod.string().optional(),\n  \"tiupHome\": zod.string().optional(),\n  \"version\": zod.string().optional(),\n  \"credentialId\": zod.string().optional(),\n  \"tags\": zod.array(zod.object({\n  \"tagId\": zod.string().optional(),\n  \"tagKey\": zod.string().optional(),\n  \"tagValue\": zod.string()\n})).optional(),\n  \"description\": zod.string().optional(),\n  \"hostId\": zod.string().optional(),\n  \"host\": zod.object({\n  \"hostId\": zod.string(),\n  \"ip\": zod.string().optional(),\n  \"hostName\": zod.string().optional(),\n  \"sshPort\": zod.number().optional(),\n  \"credentialId\": zod.string().optional(),\n  \"osName\": zod.string().optional(),\n  \"osVersion\": zod.string().optional(),\n  \"osRelease\": zod.string().optional(),\n  \"osArchitecture\": zod.string().optional(),\n  \"hostType\": zod.enum(['VM', 'PM']).optional(),\n  \"locationId\": zod.string().optional(),\n  \"createdTime\": zod.string().datetime().optional(),\n  \"updatedTime\": zod.string().datetime().optional(),\n  \"credential\": zod.object({\n  \"credentialId\": zod.string().optional(),\n  \"credentialName\": zod.string().optional(),\n  \"credentialType\": zod.string().optional(),\n  \"userName\": zod.string().optional()\n}).optional()\n}).optional()\n})\n\n\nexport const tiupsServiceGetTiupsClusterParams = zod.object({\n  \"tiupId\": zod.string()\n})\n\nexport const tiupsServiceGetTiupsClusterResponse = zod.object({\n  \"tiupsClusters\": zod.array(zod.object({\n  \"clusterId\": zod.string().optional(),\n  \"clusterName\": zod.string().optional(),\n  \"user\": zod.string().optional(),\n  \"version\": zod.string().optional(),\n  \"metaPath\": zod.string().optional(),\n  \"privateKeyPath\": zod.string().optional(),\n  \"managed\": zod.boolean().optional()\n})).optional()\n})\n\n\nexport const userServiceListUsersQueryParams = zod.object({\n  \"pageSize\": zod.number().optional(),\n  \"pageToken\": zod.string().optional(),\n  \"skip\": zod.number().optional(),\n  \"orderBy\": zod.string().optional(),\n  \"nameLike\": zod.string().optional(),\n  \"emailLike\": zod.string().optional(),\n  \"roleName\": zod.string().optional()\n})\n\nexport const userServiceListUsersResponse = zod.object({\n  \"users\": zod.array(zod.object({\n  \"userId\": zod.string(),\n  \"name\": zod.string(),\n  \"email\": zod.string().optional(),\n  \"note\": zod.string().optional(),\n  \"password\": zod.string().optional(),\n  \"userType\": zod.number().optional(),\n  \"userTypeDesc\": zod.string().optional(),\n  \"phone\": zod.string().optional(),\n  \"roles\": zod.array(zod.object({\n  \"roleName\": zod.string().optional(),\n  \"roleId\": zod.number()\n})).optional(),\n  \"createTime\": zod.string().datetime().optional(),\n  \"updateTime\": zod.string().datetime().optional()\n})).optional(),\n  \"nextPageToken\": zod.string().optional(),\n  \"totalSize\": zod.number().optional()\n})\n\n\nexport const userServiceCreateUserBody = zod.object({\n  \"userId\": zod.string(),\n  \"name\": zod.string(),\n  \"email\": zod.string().optional(),\n  \"note\": zod.string().optional(),\n  \"password\": zod.string().optional(),\n  \"userType\": zod.number().optional(),\n  \"userTypeDesc\": zod.string().optional(),\n  \"phone\": zod.string().optional(),\n  \"roles\": zod.array(zod.object({\n  \"roleName\": zod.string().optional(),\n  \"roleId\": zod.number()\n})).optional()\n})\n\nexport const userServiceCreateUserResponse = zod.object({\n  \"userId\": zod.string(),\n  \"name\": zod.string(),\n  \"email\": zod.string().optional(),\n  \"note\": zod.string().optional(),\n  \"password\": zod.string().optional(),\n  \"userType\": zod.number().optional(),\n  \"userTypeDesc\": zod.string().optional(),\n  \"phone\": zod.string().optional(),\n  \"roles\": zod.array(zod.object({\n  \"roleName\": zod.string().optional(),\n  \"roleId\": zod.number()\n})).optional(),\n  \"createTime\": zod.string().datetime().optional(),\n  \"updateTime\": zod.string().datetime().optional()\n})\n\n\nexport const userServiceGetUserProfileResponse = zod.object({\n  \"userId\": zod.string(),\n  \"name\": zod.string(),\n  \"email\": zod.string(),\n  \"note\": zod.string().optional(),\n  \"phone\": zod.string().optional()\n})\n\n\nexport const userServiceGetUserParams = zod.object({\n  \"userId\": zod.string()\n})\n\nexport const userServiceGetUserResponse = zod.object({\n  \"userId\": zod.string(),\n  \"name\": zod.string(),\n  \"email\": zod.string().optional(),\n  \"note\": zod.string().optional(),\n  \"password\": zod.string().optional(),\n  \"userType\": zod.number().optional(),\n  \"userTypeDesc\": zod.string().optional(),\n  \"phone\": zod.string().optional(),\n  \"roles\": zod.array(zod.object({\n  \"roleName\": zod.string().optional(),\n  \"roleId\": zod.number()\n})).optional(),\n  \"createTime\": zod.string().datetime().optional(),\n  \"updateTime\": zod.string().datetime().optional()\n})\n\n\nexport const userServiceDeleteUserParams = zod.object({\n  \"userId\": zod.string()\n})\n\nexport const userServiceDeleteUserResponse = zod.object({\n\n})\n\n\nexport const userServiceUpdateUserParams = zod.object({\n  \"userId\": zod.string()\n})\n\nexport const userServiceUpdateUserBody = zod.object({\n  \"email\": zod.string().optional(),\n  \"note\": zod.string().optional(),\n  \"userType\": zod.number().optional(),\n  \"phone\": zod.string().optional(),\n  \"roles\": zod.array(zod.object({\n  \"roleName\": zod.string().optional(),\n  \"roleId\": zod.number()\n})).optional()\n})\n\nexport const userServiceUpdateUserResponse = zod.object({\n  \"userId\": zod.string(),\n  \"name\": zod.string(),\n  \"email\": zod.string().optional(),\n  \"note\": zod.string().optional(),\n  \"password\": zod.string().optional(),\n  \"userType\": zod.number().optional(),\n  \"userTypeDesc\": zod.string().optional(),\n  \"phone\": zod.string().optional(),\n  \"roles\": zod.array(zod.object({\n  \"roleName\": zod.string().optional(),\n  \"roleId\": zod.number()\n})).optional(),\n  \"createTime\": zod.string().datetime().optional(),\n  \"updateTime\": zod.string().datetime().optional()\n})\n\n\nexport const userServiceResetPasswordParams = zod.object({\n  \"userId\": zod.string()\n})\n\nexport const userServiceResetPasswordBody = zod.object({\n  \"newPassword\": zod.string()\n})\n\nexport const userServiceResetPasswordResponse = zod.object({\n\n})\n\n\nexport const userServiceChangePasswordBody = zod.object({\n  \"userId\": zod.string(),\n  \"oldPassword\": zod.string().optional(),\n  \"newPassword\": zod.string()\n})\n\nexport const userServiceChangePasswordResponse = zod.object({\n\n})\n\n\nexport const userServiceValidateSessionResponse = zod.object({\n  \"userId\": zod.string()\n})\n\n\nexport const apiKeyServiceGetTemErrorDetailResponse = zod.object({\n  \"type\": zod.string().optional(),\n  \"locale\": zod.string().optional(),\n  \"message\": zod.string().optional()\n})\n\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/sample-res/metrics-config-cluster-overview.json",
    "content": "{\n  \"metrics\": [\n    {\n      \"class\": \"cluster\",\n      \"group\": \"overview\",\n      \"type\": \"\",\n      \"order\": 1,\n      \"displayName\": \"Duration\",\n      \"name\": \"duration\",\n      \"description\": \"Overall query execution duration across the cluster\",\n      \"metric\": {\n        \"name\": \"duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and percentile query durations\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_query_duration\",\n            \"promql\": \"sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\\\"internal\\\",@LABEL}[1m])) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\\\"internal\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_server_handle_query_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tidb_query_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket{sql_type!=\\\"internal\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_server_handle_query_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_query_duration_by_sql_type\",\n            \"promql\": \"sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\\\"internal\\\",@LABEL}[1m])) by (sql_type) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\\\"internal\\\",@LABEL}[1m])) by (sql_type)\",\n            \"promMetric\": \"tidb_server_handle_query_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"sql_type\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg-{sqlType}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"overview\",\n      \"type\": \"\",\n      \"order\": 2,\n      \"displayName\": \"QPS\",\n      \"name\": \"qps\",\n      \"description\": \"Total Queries Per Second across all TiDB instances\",\n      \"metric\": {\n        \"name\": \"qps\",\n        \"unit\": \"short\",\n        \"description\": \"Shows total QPS, QPS by type, and failed query rates\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"qps_by_type\",\n            \"promql\": \"sum(rate(tidb_executor_statement_total{@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tidb_executor_statement_total\",\n            \"labels\": [\n              \"instance\",\n              \"type\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"qps\",\n            \"promql\": \"sum(rate(tidb_executor_statement_total{@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_executor_statement\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"Total\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"qps_error\",\n            \"promql\": \"sum(rate(tidb_server_execute_error_total{@LABEL}[1m])) \",\n            \"promMetric\": \"tidb_server_execute_error_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"Failed\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"overview\",\n      \"type\": \"\",\n      \"order\": 3,\n      \"displayName\": \"Transaction OPS\",\n      \"name\": \"transaction_ops_by_type_and_txn_mode\",\n      \"description\": \"Transaction operations per second categorized by type and transaction mode\",\n      \"metric\": {\n        \"name\": \"transaction_ops_by_type_and_txn_mode\",\n        \"unit\": \"short\",\n        \"description\": \"Shows transaction rates categorized by type and transaction mode\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tps_by_type_and_txn_mode\",\n            \"promql\": \"sum(rate(tidb_session_transaction_duration_seconds_count{@LABEL}[1m])) by (type, txn_mode)\",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"type\",\n              \"txn_mode\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}-{txnMode}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"overview\",\n      \"type\": \"\",\n      \"order\": 4,\n      \"displayName\": \"Transaction Duration\",\n      \"name\": \"transaction_duration\",\n      \"description\": \"Time taken to execute transactions, indicating transaction processing efficiency\",\n      \"metric\": {\n        \"name\": \"transaction_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows transaction duration percentiles by transaction mode\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p99_transaction_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_session_transaction_duration_seconds_bucket{@LABEL}[1m])) by (le, txn_mode))\",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"txn_mode\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"99-{txnMode}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p95_transaction_duration\",\n            \"promql\": \"histogram_quantile(0.95, sum(rate(tidb_session_transaction_duration_seconds_bucket{@LABEL}[1m])) by (le, txn_mode))\",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"txn_mode\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"95-{txnMode}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p80_transaction_duration\",\n            \"promql\": \"histogram_quantile(0.80, sum(rate(tidb_session_transaction_duration_seconds_bucket{@LABEL}[1m])) by (le, txn_mode))\",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"txn_mode\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"80-{txnMode}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"overview\",\n      \"type\": \"\",\n      \"order\": 5,\n      \"displayName\": \"TiKV Increase of Storage Usage\",\n      \"name\": \"tikv_increase_of_storage_usage\",\n      \"description\": \"Rate of storage space consumption increase in TiKV nodes\",\n      \"metric\": {\n        \"name\": \"tikv_increase_of_storage_usage\",\n        \"unit\": \"Bps\",\n        \"description\": \"Shows the rate of storage usage increase in TiKV instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_increase_of_storage_usage\",\n            \"promql\": \"rate(tikv_store_size_bytes{type=\\\"used\\\",@LABEL}[5m])\",\n            \"promMetric\": \"tikv_store_size_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"overview\",\n      \"type\": \"\",\n      \"order\": 6,\n      \"displayName\": \"TiFlash Increase of Storage Usage\",\n      \"name\": \"tiflash_increase_of_storage_usage\",\n      \"description\": \"Rate of storage space consumption increase in TiFlash nodes\",\n      \"metric\": {\n        \"name\": \"tiflash_increase_of_storage_usage\",\n        \"unit\": \"Bps\",\n        \"description\": \"Shows the rate of storage usage increase in TiFlash instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tiflash_increase_of_storage_usage\",\n            \"promql\": \"rate(tiflash_system_current_metric_StoreSizeUsed{@LABEL}[5m])\",\n            \"promMetric\": \"tiflash_system_current_metric_StoreSizeUsed\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    }\n  ]\n}"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/sample-res/metrics-config-cluster.json",
    "content": "{\n  \"metrics\": [\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"optimizer_behavior\",\n      \"order\": 1,\n      \"displayName\": \"Parse Duration\",\n      \"name\": \"parse_duration\",\n      \"description\": \"Time taken by TiDB to parse SQL statements into abstract syntax trees (AST)\",\n      \"metric\": {\n        \"name\": \"parse_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and percentile parse durations\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p95_tidb_session_parse_duration\",\n            \"promql\": \"histogram_quantile(0.95, sum(rate(tidb_session_parse_duration_seconds_bucket{sql_type=\\\"general\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_session_parse_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"95\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_session_parse_duration\",\n            \"promql\": \"sum(rate(tidb_session_parse_duration_seconds_sum{sql_type=\\\"general\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_session_parse_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tidb_session_parse_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_session_parse_duration_seconds_bucket{sql_type=\\\"general\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_session_parse_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"sql_tuning\",\n      \"order\": 1,\n      \"displayName\": \"Slow query\",\n      \"name\": \"slow_query\",\n      \"description\": \"Statistics about slow query execution, including processing, coprocessor, and wait durations\",\n      \"metric\": {\n        \"name\": \"slow_query\",\n        \"unit\": \"s\",\n        \"description\": \"90th percentile of slow query durations, broken down by processing phases and SQL types\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p90_slow_query_prcess_duration\",\n            \"promql\": \"histogram_quantile(0.90, sum(rate(tidb_server_slow_query_process_duration_seconds_bucket{@LABEL}[1m])) by (le,sql_type))\",\n            \"promMetric\": \"tidb_server_slow_query_process_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"sql_type\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"all_proc_{sqlType}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p90_slow_query_cop_duration\",\n            \"promql\": \"histogram_quantile(0.90, sum(rate(tidb_server_slow_query_cop_duration_seconds_bucket{@LABEL}[1m])) by (le,sql_type))\",\n            \"promMetric\": \"tidb_server_slow_query_cop_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"sql_type\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"all_cop_proc_{sqlType}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p90_slow_query_wait_duration\",\n            \"promql\": \"histogram_quantile(0.90, sum(rate(tidb_server_slow_query_wait_duration_seconds_bucket{@LABEL}[1m])) by (le,sql_type))\",\n            \"promMetric\": \"tidb_server_slow_query_wait_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"sql_type\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"all_cop_wait_{sqlType}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"io_env\",\n      \"order\": 1,\n      \"displayName\": \"Disk Latency\",\n      \"name\": \"disk_latency\",\n      \"description\": \"Average time taken for read and write I/O operations to complete\",\n      \"metric\": {\n        \"name\": \"disk_latency\",\n        \"unit\": \"s\",\n        \"description\": \"Average time taken for read and write I/O operations to complete\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_io_write_latency\",\n            \"promql\": \"(rate(node_disk_write_time_seconds_total{@LABEL}[5m])/ rate(node_disk_writes_completed_total{@LABEL}[5m]))\",\n            \"promMetric\": \"node_disk_write_time_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"Write Latency: [{instance}-{device}]\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"host_io_read_latency\",\n            \"promql\": \"(rate(node_disk_read_time_seconds_total{@LABEL}[5m])/ rate(node_disk_reads_completed_total{@LABEL}[5m]))\",\n            \"promMetric\": \"node_disk_read_time_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"Read Latency: [{instance}-{device}]\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"load_analysis\",\n      \"order\": 1,\n      \"displayName\": \" QPS\",\n      \"name\": \"qps\",\n      \"description\": \"Queries Per Second (QPS) measures the number of SQL queries processed by TiDB per second, indicating the overall system workload\",\n      \"metric\": {\n        \"name\": \"qps\",\n        \"unit\": \"short\",\n        \"description\": \"Shows total QPS, QPS by type, and failed query rates\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"qps_by_type\",\n            \"promql\": \"sum(rate(tidb_executor_statement_total{@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tidb_executor_statement_total\",\n            \"labels\": [\n              \"instance\",\n              \"type\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"qps\",\n            \"promql\": \"sum(rate(tidb_executor_statement_total{@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_executor_statement\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"Total\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"qps_error\",\n            \"promql\": \"sum(rate(tidb_server_execute_error_total{@LABEL}[1m])) \",\n            \"promMetric\": \"tidb_server_execute_error_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"Failed\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"tiflash_related\",\n      \"order\": 1,\n      \"displayName\": \"TiFlash CPU Usage\",\n      \"name\": \"tiflash_cpu_usage\",\n      \"description\": \"CPU utilization percentage of TiFlash instances, indicating computational resource consumption for analytical processing\",\n      \"metric\": {\n        \"name\": \"tiflash_cpu_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Shows CPU usage percentage and core limits for TiFlash instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tiflash_cpu_usage_percentage\",\n            \"promql\": \"rate(tiflash_proxy_process_cpu_seconds_total{job=\\\"tiflash\\\",@LABEL}[1m])\",\n            \"promMetric\": \"tiflash_proxy_process_cpu_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tiflash_logic_cpu_cores\",\n            \"promql\": \"sum(tiflash_system_current_metric_LogicalCPUCores{@LABEL}) by (instance)\",\n            \"promMetric\": \"tiflash_system_current_metric_LogicalCPUCores\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"limit-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"connection\",\n      \"order\": 1,\n      \"displayName\": \"Connection Count\",\n      \"name\": \"connection_count\",\n      \"description\": \"Number of active connections to TiDB\",\n      \"metric\": {\n        \"name\": \"connection_count\",\n        \"unit\": \"short\",\n        \"description\": \"Tracks the number of active database connections, helping monitor client connection patterns and resource usage\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"connection_count\",\n            \"promql\": \"tidb_server_connections{@LABEL}\",\n            \"promMetric\": \"tidb_server_connections\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"connection_count_sum\",\n            \"promql\": \"sum(tidb_server_connections{@LABEL})\",\n            \"promMetric\": \"tidb_server_connections\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"total\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"pd_leader\",\n      \"order\": 1,\n      \"displayName\": \"Region health\",\n      \"name\": \"pd_region_health\",\n      \"description\": \"Health status of TiKV regions, including normal and abnormal states\",\n      \"metric\": {\n        \"name\": \"pd_region_health\",\n        \"unit\": \"short\",\n        \"description\": \"Monitors the health status of regions across the TiKV cluster\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_regions_status\",\n            \"promql\": \"sum(pd_regions_status{@LABEL}) by (instance, type)\",\n            \"promMetric\": \"pd_regions_status\",\n            \"labels\": [\n              \"instance\",\n              \"type\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"{instance}-{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_regions_offline_status\",\n            \"promql\": \"pd_regions_offline_status{type=\\\"offline-peer-region-count\\\",@LABEL}\",\n            \"promMetric\": \"pd_regions_offline_status\",\n            \"labels\": [\n              \"instance\",\n              \"type\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"{instance}-{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"raft_log\",\n      \"order\": 1,\n      \"displayName\": \"Append Log Duration\",\n      \"name\": \"append_log_duration\",\n      \"description\": \"Time taken to append a raft log at different percentiles\",\n      \"metric\": {\n        \"name\": \"append_log_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and 99th percentile durations for appending Raft logs\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_raftstore_append_log_duration\",\n            \"promql\": \"(sum(rate(tikv_raftstore_append_log_duration_seconds_sum{@LABEL}[1m])) / sum(rate(tikv_raftstore_append_log_duration_seconds_count{@LABEL}[1m])))\",\n            \"promMetric\": \"tikv_raftstore_append_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_raftstore_append_log_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_raftstore_append_log_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_raftstore_append_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"analyze_statistics\",\n      \"order\": 1,\n      \"displayName\": \"Auto Analyze Queries Per Minute\",\n      \"name\": \"auto_analyze_queries_per_minute\",\n      \"description\": \"Rate of automatic table statistics analysis operations per minute\",\n      \"metric\": {\n        \"name\": \"auto_analyze_queries_per_minute\",\n        \"unit\": \"short\",\n        \"description\": \"Frequency of automatic statistics collection operations by type\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"auto_analyze_queries_per_minute\",\n            \"promql\": \"sum(increase(tidb_statistics_auto_analyze_total{@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tidb_statistics_auto_analyze_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"sql_tuning\",\n      \"order\": 2,\n      \"displayName\": \"999 Duration\",\n      \"name\": \"999_duration\",\n      \"description\": \"99.9th percentile of query duration, indicating worst-case query performance\",\n      \"metric\": {\n        \"name\": \"999_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Tracks the 99.9th percentile of query execution times by SQL type\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p999_query_duration\",\n            \"promql\": \"histogram_quantile(0.999, sum(rate(tidb_server_handle_query_duration_seconds_bucket{@LABEL}[1m])) by (le,sql_type))\",\n            \"promMetric\": \"tidb_server_handle_query_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{sqlType}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"tiflash_related\",\n      \"order\": 2,\n      \"displayName\": \"TiFlash Memory\",\n      \"name\": \"tiflash_memory_usage\",\n      \"description\": \"Memory usage of TiFlash instances, showing RAM consumption for columnar storage and processing\",\n      \"metric\": {\n        \"name\": \"tiflash_memory_usage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Shows memory usage and limits for TiFlash instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tiflash_memory_usage\",\n            \"promql\": \"tiflash_proxy_process_resident_memory_bytes{job=\\\"tiflash\\\",@LABEL}\",\n            \"promMetric\": \"tiflash_proxy_process_resident_memory_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tiflash_memory_limit\",\n            \"promql\": \"sum(tiflash_system_current_metric_MemoryCapacity{@LABEL}) by (instance)\",\n            \"promMetric\": \"tiflash_system_current_metric_MemoryCapacity\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"limit-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"io_env\",\n      \"order\": 2,\n      \"displayName\": \"Ping Latency\",\n      \"name\": \"host_ping_latency\",\n      \"description\": \"Time taken to ping a host\",\n      \"metric\": {\n        \"name\": \"host_ping_latency\",\n        \"unit\": \"s\",\n        \"description\": \"Time taken to ping a host\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_ping_latency\",\n            \"promql\": \"probe_duration_seconds{job=~\\\".*blackbox_exporter.*\\\",@LABEL}\",\n            \"promMetric\": \"probe_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"instance: {instance}, ping: {ping}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"raft_log\",\n      \"order\": 2,\n      \"displayName\": \"Commit Log Duration\",\n      \"name\": \"commit_log_duration\",\n      \"description\": \"Time taken to commit a raft log at different percentiles\",\n      \"metric\": {\n        \"name\": \"commit_log_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and 99th percentile durations for committing Raft logs\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_raftstore_commit_log_duration\",\n            \"promql\": \"(sum(rate(tikv_raftstore_commit_log_duration_seconds_sum{@LABEL}[1m])) / sum(rate(tikv_raftstore_commit_log_duration_seconds_count{@LABEL}[1m])))\",\n            \"promMetric\": \"tikv_raftstore_commit_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_raftstore_commit_log_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_raftstore_commit_log_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_raftstore_commit_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"connection\",\n      \"order\": 2,\n      \"displayName\": \"Events OPM\",\n      \"name\": \"events_opm\",\n      \"description\": \"Number of TiDB server events per minute\",\n      \"metric\": {\n        \"name\": \"events_opm\",\n        \"unit\": \"short\",\n        \"description\": \"Events per minute by instance and type\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"events_opm\",\n            \"promql\": \"increase(tidb_server_event_total{@LABEL}[10m])\",\n            \"promMetric\": \"tidb_server_event_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}-server {type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"load_analysis\",\n      \"order\": 2,\n      \"displayName\": \"Database time by SQL Type\",\n      \"name\": \"database_time_by_sql_type\",\n      \"description\": \"Time spent by different types of SQL statements in the database, helping identify which SQL types consume the most resources\",\n      \"metric\": {\n        \"name\": \"database_time_by_sql_type\",\n        \"unit\": \"s\",\n        \"description\": \"Shows the database time consumed by different types of SQL statements\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_handle_query_duration\",\n            \"promql\": \"sum(rate(tidb_server_handle_query_duration_seconds_sum{ sql_type!=\\\"internal\\\",@LABEL}[1m])) by (sql_type)\",\n            \"promMetric\": \"tidb_server_handle_query_duration_seconds\",\n            \"labels\": [\n              \"sql_type\",\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{sqlType}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_tokens\",\n            \"promql\": \"sum(tidb_server_tokens{@LABEL})\",\n            \"promMetric\": \"tidb_server_tokens\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"database time\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"pd_leader\",\n      \"order\": 2,\n      \"displayName\": \"Storage used\",\n      \"name\": \"pd_storage_usage\",\n      \"description\": \"Storage space utilization across the TiKV cluster\",\n      \"metric\": {\n        \"name\": \"pd_storage_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Percentage of total storage capacity currently in use\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_storage_usage\",\n            \"promql\": \"(sum(pd_cluster_status{type=\\\"storage_size\\\",@LABEL}) by (instance))/ (sum(pd_cluster_status{type=\\\"storage_capacity\\\",@LABEL}) by (instance))\",\n            \"promMetric\": \"pd_cluster_status\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"analyze_statistics\",\n      \"order\": 2,\n      \"displayName\": \"Sync Load QPS\",\n      \"name\": \"sync_load_qps\",\n      \"description\": \"Rate of sync-load operations per second\",\n      \"metric\": {\n        \"name\": \"sync_load_qps\",\n        \"unit\": \"short\",\n        \"description\": \"Rate of sync-load operations per second\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"sync_load_qps_total\",\n            \"promql\": \"sum(increase(tidb_statistics_sync_load_total{@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tidb_statistics_sync_load_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"total sync-load\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"sync_load_timeout\",\n            \"promql\": \"sum(increase(tidb_statistics_sync_load_timeout_total{@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tidb_statistics_sync_load_timeout_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"timeout\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"sync_load_qps_dedup\",\n            \"promql\": \"sum(increase(tidb_statistics_sync_load_dedup_total{@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tidb_statistics_sync_load_dedup_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"dedup sync-load\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"optimizer_behavior\",\n      \"order\": 2,\n      \"displayName\": \"Compile Duration\",\n      \"name\": \"compile_duration\",\n      \"description\": \"Time taken by TiDB to compile SQL statements into execution plans\",\n      \"metric\": {\n        \"name\": \"compile_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and percentile compile durations\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"avg_tidb_session_compile_duration\",\n            \"promql\": \"(sum(rate(tidb_session_compile_duration_seconds_sum{sql_type=\\\"general\\\",@LABEL}[1m])) / sum(rate(tidb_session_compile_duration_seconds_count{sql_type=\\\"general\\\",@LABEL}[1m])))\",\n            \"promMetric\": \"tidb_session_compile_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tidb_session_compile_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_session_compile_duration_seconds_bucket{sql_type=\\\"general\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_session_compile_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"io_env\",\n      \"order\": 3,\n      \"displayName\": \"TiKV Write Pipeline Duration\",\n      \"name\": \"tikv_write_pipeline_duration\",\n      \"description\": \"Duration metrics for TiKV write operations pipeline\",\n      \"metric\": {\n        \"name\": \"tikv_write_pipeline_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Various duration metrics in the TiKV write pipeline process\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p99_tikv_write_raft_log_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_raftstore_append_log_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_raftstore_append_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"Write Raft Log .99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_propose_wait_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_raftstore_request_wait_time_duration_secs_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_raftstore_request_wait_time_duration_secs\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"Propose Wait .99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_apply_wait_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_raftstore_apply_wait_time_duration_secs_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_raftstore_apply_wait_time_duration_secs\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"Apply Wait .99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_replicate_raft_log_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_raftstore_commit_log_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_raftstore_commit_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"Replicate Raft Log .99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_apply_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_raftstore_apply_log_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_raftstore_apply_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"Apply Duration .99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"tiflash_related\",\n      \"order\": 3,\n      \"displayName\": \"TiFlash Request QPS\",\n      \"name\": \"tiflash_request_qps\",\n      \"description\": \"Number of requests processed by TiFlash coprocessor per second\",\n      \"metric\": {\n        \"name\": \"tiflash_request_qps\",\n        \"unit\": \"short\",\n        \"description\": \"Query processing rate in TiFlash, broken down by request type\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tiflash_request_qps\",\n            \"promql\": \"sum(rate(tiflash_coprocessor_request_count{@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tiflash_coprocessor_request_count\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"pd_leader\",\n      \"order\": 3,\n      \"displayName\": \"Number of Regions\",\n      \"name\": \"pd_number_of_regions\",\n      \"description\": \"Total number of regions in the TiKV cluster\",\n      \"metric\": {\n        \"name\": \"pd_number_of_regions\",\n        \"unit\": \"short\",\n        \"description\": \"Count of total regions managed by PD\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_number_of_regions\",\n            \"promql\": \"sum(pd_cluster_status{type=\\\"leader_count\\\",@LABEL})\",\n            \"promMetric\": \"pd_cluster_status\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"count\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"load_analysis\",\n      \"order\": 3,\n      \"displayName\": \"TiDB CPU\",\n      \"name\": \"tidb_cpu_usage\",\n      \"description\": \"CPU utilization percentage of TiDB servers, indicating computational resource consumption\",\n      \"metric\": {\n        \"name\": \"tidb_cpu_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Shows CPU usage percentage and MAXPROCS limit for TiDB instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_cpu_usage_percentage\",\n            \"promql\": \"irate(process_cpu_seconds_total{ job=\\\"tidb\\\",@LABEL}[1m])\",\n            \"promMetric\": \"process_cpu_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_server_maxprocs\",\n            \"promql\": \"tidb_server_maxprocs{job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"tidb_server_maxprocs\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"limit-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"connection\",\n      \"order\": 3,\n      \"displayName\": \"Connection Idle Duration\",\n      \"name\": \"connection_idle_duration\",\n      \"description\": \"Duration of idle connections in TiDB\",\n      \"metric\": {\n        \"name\": \"connection_idle_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Various percentiles of connection idle time for both transaction and non-transaction states\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p99_tidb_in_txn_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_server_conn_idle_duration_seconds_bucket{ in_txn='1',@LABEL}[1m])) by (le,in_txn))\",\n            \"promMetric\": \"tidb_server_conn_idle_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"99-in-txn\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tidb_not_in_txn_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_server_conn_idle_duration_seconds_bucket{ in_txn='0',@LABEL}[1m])) by (le,in_txn))\",\n            \"promMetric\": \"tidb_server_conn_idle_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"99-not-in-txn\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p90_tidb_in_txn_duration\",\n            \"promql\": \"histogram_quantile(0.90, sum(rate(tidb_server_conn_idle_duration_seconds_bucket{ in_txn='1',@LABEL}[1m])) by (le,in_txn))\",\n            \"promMetric\": \"tidb_server_conn_idle_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"90-in-txn\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p90_tidb_not_in_txn_duration\",\n            \"promql\": \"histogram_quantile(0.90, sum(rate(tidb_server_conn_idle_duration_seconds_bucket{ in_txn='0',@LABEL}[1m])) by (le,in_txn))\",\n            \"promMetric\": \"tidb_server_conn_idle_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"90-not-in-txn\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p80_tidb_in_txn_duration\",\n            \"promql\": \"histogram_quantile(0.80, sum(rate(tidb_server_conn_idle_duration_seconds_bucket{ in_txn='1',@LABEL}[1m])) by (le,in_txn))\",\n            \"promMetric\": \"tidb_server_conn_idle_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"80-in-txn\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p80_tidb_not_in_txn_duration\",\n            \"promql\": \"histogram_quantile(0.80, sum(rate(tidb_server_conn_idle_duration_seconds_bucket{ in_txn='0',@LABEL}[1m])) by (le,in_txn))\",\n            \"promMetric\": \"tidb_server_conn_idle_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"80-not-in-txn\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"optimizer_behavior\",\n      \"order\": 3,\n      \"displayName\": \"Queries Using Plan Cache OPS\",\n      \"name\": \"queries_using_plan_cache_ops\",\n      \"description\": \"Number of queries per second that utilize the execution plan cache\",\n      \"metric\": {\n        \"name\": \"queries_using_plan_cache_ops\",\n        \"unit\": \"short\",\n        \"description\": \"Shows plan cache hit and miss rates\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"queries_hit_plan_cache\",\n            \"promql\": \"sum(rate(tidb_server_plan_cache_total{@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_server_plan_cache_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg_hit\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"queries_miss_plan_cache\",\n            \"promql\": \"sum(rate(tidb_server_plan_cache_miss_total{@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_server_plan_cache_miss_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg_miss\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"analyze_statistics\",\n      \"order\": 3,\n      \"displayName\": \"Stats Healthy Distribution\",\n      \"name\": \"stats_healthy_distribution\",\n      \"description\": \"Distribution of healthy statistics across the cluster\",\n      \"metric\": {\n        \"name\": \"stats_healthy_distribution\",\n        \"unit\": \"short\",\n        \"description\": \"Distribution of healthy statistics across the cluster\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"stats_healthy_distribution\",\n            \"promql\": \"avg(tidb_statistics_stats_healthy{@LABEL}) by (type)\",\n            \"promMetric\": \"tidb_statistics_stats_healthy\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"raft_log\",\n      \"order\": 3,\n      \"displayName\": \"Apply Log Duration\",\n      \"name\": \"apply_log_duration\",\n      \"description\": \"Time taken to apply a raft log at different percentiles\",\n      \"metric\": {\n        \"name\": \"apply_log_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and 99th percentile durations for applying Raft logs\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_raftstore_apply_log_duration\",\n            \"promql\": \"(sum(rate(tikv_raftstore_apply_log_duration_seconds_sum{@LABEL}[1m])) / sum(rate(tikv_raftstore_apply_log_duration_seconds_count{@LABEL}[1m])))\",\n            \"promMetric\": \"tikv_raftstore_apply_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_raftstore_apply_log_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_raftstore_apply_log_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_raftstore_apply_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"sql_tuning\",\n      \"order\": 3,\n      \"displayName\": \"Expensive Executors OPS\",\n      \"name\": \"expensive_executors_ops\",\n      \"description\": \"Operations per second for resource-intensive query executors\",\n      \"metric\": {\n        \"name\": \"expensive_executors_ops\",\n        \"unit\": \"short\",\n        \"description\": \"Monitors the rate of expensive query execution operations by executor type\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"expensive_executors_ops_sum_by_type\",\n            \"promql\": \"sum(rate(tidb_executor_expensive_total{@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tidb_executor_expensive_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"analyze_statistics\",\n      \"order\": 4,\n      \"displayName\": \"Auto Analyze Duration\",\n      \"name\": \"auto_analyze_duration\",\n      \"description\": \"Time taken to perform automatic table statistics analysis at different percentiles\",\n      \"metric\": {\n        \"name\": \"auto_analyze_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Time taken to perform automatic table statistics analysis at different percentiles\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p95_auto_analyze_duration\",\n            \"promql\": \"histogram_quantile(0.95, sum(rate(tidb_statistics_auto_analyze_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_statistics_auto_analyze_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"95\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p80_auto_analyze_duration\",\n            \"promql\": \"histogram_quantile(0.80, sum(rate(tidb_statistics_auto_analyze_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_statistics_auto_analyze_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"80\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"pd_leader\",\n      \"order\": 4,\n      \"displayName\": \"CPU usage\",\n      \"name\": \"pd_cpu_usage_percentage\",\n      \"description\": \"CPU utilization of PD leader instance, showing processing overhead of cluster management\",\n      \"metric\": {\n        \"name\": \"pd_cpu_usage_percentage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Shows CPU usage percentage for PD instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_cpu_usage_percentage\",\n            \"promql\": \"irate(process_cpu_seconds_total{job=~\\\".*pd.*\\\",@LABEL}[30s])\",\n            \"promMetric\": \"process_cpu_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"load_analysis\",\n      \"order\": 4,\n      \"displayName\": \"TiDB Memory\",\n      \"name\": \"tidb_memory_usage\",\n      \"description\": \"Memory usage of TiDB servers, showing the amount of RAM being consumed\",\n      \"metric\": {\n        \"name\": \"tidb_memory_usage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Shows detailed memory usage metrics for TiDB instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_process_memory_usage\",\n            \"promql\": \"process_resident_memory_bytes{ job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"process_resident_memory_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"process-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_heap_sys_memory_usage\",\n            \"promql\": \"go_memory_classes_heap_objects_bytes{ job=\\\"tidb\\\",@LABEL} + go_memory_classes_heap_unused_bytes{ job=\\\"tidb\\\",@LABEL} + go_memory_classes_heap_released_bytes{ job=\\\"tidb\\\",@LABEL} + go_memory_classes_heap_free_bytes{ job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"go_memory_classes_heap_objects_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"HeapSys-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_heap_inuse_memory_usage\",\n            \"promql\": \"go_memory_classes_heap_objects_bytes{ job=\\\"tidb\\\",@LABEL} + go_memory_classes_heap_unused_bytes{ job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"go_memory_classes_heap_objects_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"HeapInuse-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_heap_alloc_memory_usage\",\n            \"promql\": \"go_memory_classes_heap_objects_bytes{ job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"go_memory_classes_heap_objects_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"HeapAlloc-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_heap_idle_memory_usage\",\n            \"promql\": \"go_memory_classes_heap_released_bytes{job=\\\"tidb\\\",@LABEL} + go_memory_classes_heap_free_bytes{ job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"go_memory_classes_heap_released_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"HeapIdle-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_heap_released_memory_usage\",\n            \"promql\": \"go_memory_classes_heap_released_bytes{job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"go_memory_classes_heap_released_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"HeapReleased-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_gc_trigger_memory_usage\",\n            \"promql\": \"go_gc_heap_goal_bytes{job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"go_gc_heap_goal_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"GCTrigger-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_server_memory_usage\",\n            \"promql\": \"tidb_server_memory_usage{job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"tidb_server_memory_usage\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{module}-{type}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_server_memory_usage_by_module\",\n            \"promql\": \"sum(tidb_server_memory_usage{job=\\\"tidb\\\",@LABEL}) by (module, instance)\",\n            \"promMetric\": \"tidb_server_memory_usage\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{module}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"io_env\",\n      \"order\": 4,\n      \"displayName\": \"TiKV Cop Read Duration\",\n      \"name\": \"tikv_cop_read_duration\",\n      \"description\": \"Duration metrics for TiKV coprocessor read operations\",\n      \"metric\": {\n        \"name\": \"tikv_cop_read_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Duration metrics for TiKV coprocessor read operations\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p99_tikv_get_snapshot_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(delta(tikv_storage_engine_async_request_duration_seconds_bucket{type=\\\"snapshot\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_storage_engine_async_request_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"Get Snapshot .99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_cop_wait_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_coprocessor_request_wait_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_coprocessor_request_wait_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"Cop Wait .99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_cop_handle_duration\",\n            \"promql\": \"histogram_quantile(0.95, sum(rate(tikv_coprocessor_request_handle_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_coprocessor_request_handle_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"Cop Handle .99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"tiflash_related\",\n      \"order\": 4,\n      \"displayName\": \"TiFlash Request Duration\",\n      \"name\": \"tiflash_request_duration\",\n      \"description\": \"Time taken to process requests in TiFlash coprocessor at different percentiles\",\n      \"metric\": {\n        \"name\": \"tiflash_request_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Distribution of request processing times in TiFlash, showing performance at various percentiles\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p999_tiflash_request_duration\",\n            \"promql\": \"histogram_quantile(0.999, sum(rate(tiflash_coprocessor_request_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tiflash_coprocessor_request_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"999\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tiflash_request_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tiflash_coprocessor_request_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tiflash_coprocessor_request_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p95_tiflash_request_duration\",\n            \"promql\": \"histogram_quantile(0.95, sum(rate(tiflash_coprocessor_request_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tiflash_coprocessor_request_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"95\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p80_tiflash_request_duration\",\n            \"promql\": \"histogram_quantile(0.80, sum(rate(tiflash_coprocessor_request_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tiflash_coprocessor_request_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"80\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"load_analysis\",\n      \"order\": 5,\n      \"displayName\": \"TiKV CPU\",\n      \"name\": \"tikv_cpu_usage\",\n      \"description\": \"CPU utilization percentage of TiKV storage nodes, indicating storage layer computational load\",\n      \"metric\": {\n        \"name\": \"tikv_cpu_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Shows CPU usage percentage for TiKV instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_cpu_usage_percentage\",\n            \"promql\": \"sum(rate(process_cpu_seconds_total{ job=~\\\".*tikv\\\",@LABEL}[1m])) by (instance)\",\n            \"promMetric\": \"process_cpu_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"pd_leader\",\n      \"order\": 5,\n      \"displayName\": \"Memory Usage\",\n      \"name\": \"pd_memory_usage\",\n      \"description\": \"Memory consumption of PD leader instance, indicating resource usage for cluster management\",\n      \"metric\": {\n        \"name\": \"pd_memory_usage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Shows detailed memory usage metrics for PD instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_memory_usage\",\n            \"promql\": \"process_resident_memory_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"process_resident_memory_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"process-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_sys_memory_usage\",\n            \"promql\": \"go_memstats_heap_sys_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_sys_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapSys-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_inuse_memory_usage\",\n            \"promql\": \"go_memstats_heap_inuse_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_inuse_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapInuse-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_alloc_memory_usage\",\n            \"promql\": \"go_memstats_heap_alloc_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_alloc_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapAlloc-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_idle_memory_usage\",\n            \"promql\": \"go_memstats_heap_idle_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_idle_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapIdle-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_released_memory_usage\",\n            \"promql\": \"go_memstats_heap_released_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_released_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapReleased-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_gc_trigger_memory_usage\",\n            \"promql\": \"go_memstats_next_gc_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_next_gc_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"GCTrigger-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"load_analysis\",\n      \"order\": 6,\n      \"displayName\": \"TiKV Memory\",\n      \"name\": \"tikv_memory_usage\",\n      \"description\": \"Memory usage of TiKV storage nodes, showing storage layer RAM consumption\",\n      \"metric\": {\n        \"name\": \"tikv_memory_usage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Shows memory usage for TiKV instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_memory_usage\",\n            \"promql\": \"avg(process_resident_memory_bytes{job=~\\\".*tikv\\\",@LABEL}) by (instance)\",\n            \"promMetric\": \"process_resident_memory_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"pd_leader\",\n      \"order\": 6,\n      \"displayName\": \"Goroutine Count\",\n      \"name\": \"pd_go_routine_count\",\n      \"description\": \"Number of active goroutines in PD processes\",\n      \"metric\": {\n        \"name\": \"pd_go_routine_count\",\n        \"unit\": \"short\",\n        \"description\": \"Current count of active goroutines in PD instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_go_routine_count\",\n            \"promql\": \"go_goroutines{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_goroutines\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"pd_leader\",\n      \"order\": 7,\n      \"displayName\": \"Schedule operator create\",\n      \"name\": \"pd_schedule_operator_create\",\n      \"description\": \"Rate of schedule operator creation by PD\",\n      \"metric\": {\n        \"name\": \"pd_schedule_operator_create\",\n        \"unit\": \"opm\",\n        \"description\": \"Monitors the creation rate of different types of scheduling operators\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_schedule_operator_create\",\n            \"promql\": \"sum(delta(pd_schedule_operators_count{event=\\\"create\\\",@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"pd_schedule_operators_count\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"load_analysis\",\n      \"order\": 7,\n      \"displayName\": \"PD CPU\",\n      \"name\": \"pd_cpu_usage_percentage\",\n      \"description\": \"CPU utilization percentage of PD (Placement Driver) servers, indicating cluster management overhead\",\n      \"metric\": {\n        \"name\": \"pd_cpu_usage_percentage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Shows CPU usage percentage for PD instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_cpu_usage_percentage\",\n            \"promql\": \"irate(process_cpu_seconds_total{job=~\\\".*pd.*\\\",@LABEL}[30s])\",\n            \"promMetric\": \"process_cpu_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"pd_leader\",\n      \"order\": 8,\n      \"displayName\": \"Scheduler is running\",\n      \"name\": \"pd_scheduler_is_running\",\n      \"description\": \"Status of different PD schedulers\",\n      \"metric\": {\n        \"name\": \"pd_scheduler_is_running\",\n        \"unit\": \"short\",\n        \"description\": \"Indicates whether specific PD schedulers are currently active\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_scheduler_is_running\",\n            \"promql\": \"pd_scheduler_status{ type=\\\"allow\\\",@LABEL}\",\n            \"promMetric\": \"pd_scheduler_status\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"{kind}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"load_analysis\",\n      \"order\": 8,\n      \"displayName\": \"PD Memory\",\n      \"name\": \"pd_memory_usage\",\n      \"description\": \"Memory usage of PD (Placement Driver) servers, showing cluster management resource consumption\",\n      \"metric\": {\n        \"name\": \"pd_memory_usage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Shows detailed memory usage metrics for PD instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_memory_usage\",\n            \"promql\": \"process_resident_memory_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"process_resident_memory_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"process-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_sys_memory_usage\",\n            \"promql\": \"go_memstats_heap_sys_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_sys_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapSys-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_inuse_memory_usage\",\n            \"promql\": \"go_memstats_heap_inuse_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_inuse_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapInuse-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_alloc_memory_usage\",\n            \"promql\": \"go_memstats_heap_alloc_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_alloc_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapAlloc-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_idle_memory_usage\",\n            \"promql\": \"go_memstats_heap_idle_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_idle_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapIdle-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_released_memory_usage\",\n            \"promql\": \"go_memstats_heap_released_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_released_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapReleased-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_gc_trigger_memory_usage\",\n            \"promql\": \"go_memstats_next_gc_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_next_gc_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"GCTrigger-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"load_analysis\",\n      \"order\": 9,\n      \"displayName\": \"Connection Count\",\n      \"name\": \"connection_count\",\n      \"description\": \"Number of active client connections to the TiDB cluster, both per instance and total\",\n      \"metric\": {\n        \"name\": \"connection_count\",\n        \"unit\": \"short\",\n        \"description\": \"Tracks the number of active database connections, helping monitor client connection patterns and resource usage\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"connection_count\",\n            \"promql\": \"tidb_server_connections{@LABEL}\",\n            \"promMetric\": \"tidb_server_connections\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"connection_count_sum\",\n            \"promql\": \"sum(tidb_server_connections{@LABEL})\",\n            \"promMetric\": \"tidb_server_connections\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"total\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"advanced\",\n      \"type\": \"pd_leader\",\n      \"order\": 9,\n      \"displayName\": \"Patrol Region time\",\n      \"name\": \"pd_patrol_region_time\",\n      \"description\": \"Time taken by PD to patrol and check region health\",\n      \"metric\": {\n        \"name\": \"pd_patrol_region_time\",\n        \"unit\": \"s\",\n        \"description\": \"Duration of region patrol operations by PD checker\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_patrol_region_time\",\n            \"promql\": \"pd_checker_patrol_regions_time{@LABEL}\",\n            \"promMetric\": \"pd_checker_patrol_regions_time\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"database_time\",\n      \"order\": 1,\n      \"displayName\": \"Database Time by SQL Type\",\n      \"name\": \"database_time_by_sql_type\",\n      \"description\": \"Distribution of database time by SQL type\",\n      \"metric\": {\n        \"name\": \"database_time_by_sql_type\",\n        \"unit\": \"s\",\n        \"description\": \"Shows the database time consumed by different types of SQL statements\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_handle_query_duration\",\n            \"promql\": \"sum(rate(tidb_server_handle_query_duration_seconds_sum{ sql_type!=\\\"internal\\\",@LABEL}[1m])) by (sql_type)\",\n            \"promMetric\": \"tidb_server_handle_query_duration_seconds\",\n            \"labels\": [\n              \"sql_type\",\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{sqlType}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_tokens\",\n            \"promql\": \"sum(tidb_server_tokens{@LABEL})\",\n            \"promMetric\": \"tidb_server_tokens\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"database time\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"throughput\",\n      \"order\": 1,\n      \"displayName\": \"QPS\",\n      \"name\": \"qps\",\n      \"description\": \"Overview of queries per second metrics\",\n      \"metric\": {\n        \"name\": \"qps\",\n        \"unit\": \"short\",\n        \"description\": \"Shows total QPS, QPS by type, and failed query rates\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"qps_by_type\",\n            \"promql\": \"sum(rate(tidb_executor_statement_total{@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tidb_executor_statement_total\",\n            \"labels\": [\n              \"instance\",\n              \"type\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"qps\",\n            \"promql\": \"sum(rate(tidb_executor_statement_total{@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_executor_statement\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"Total\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"qps_error\",\n            \"promql\": \"sum(rate(tidb_server_execute_error_total{@LABEL}[1m])) \",\n            \"promMetric\": \"tidb_server_execute_error_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"Failed\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"transaction\",\n      \"order\": 1,\n      \"displayName\": \"Transaction OPS\",\n      \"name\": \"transaction_ops_by_type_and_txn_mode\",\n      \"description\": \"Overview of transaction operations by type and transaction mode\",\n      \"metric\": {\n        \"name\": \"transaction_ops_by_type_and_txn_mode\",\n        \"unit\": \"short\",\n        \"description\": \"Shows transaction rates categorized by type and transaction mode\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tps_by_type_and_txn_mode\",\n            \"promql\": \"sum(rate(tidb_session_transaction_duration_seconds_count{@LABEL}[1m])) by (type, txn_mode)\",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"type\",\n              \"txn_mode\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}-{txnMode}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"raft_log\",\n      \"order\": 1,\n      \"displayName\": \"Append Log Duration\",\n      \"name\": \"append_log_duration\",\n      \"description\": \"Overview of Raft log append duration metrics\",\n      \"metric\": {\n        \"name\": \"append_log_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and 99th percentile durations for appending Raft logs\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_raftstore_append_log_duration\",\n            \"promql\": \"(sum(rate(tikv_raftstore_append_log_duration_seconds_sum{@LABEL}[1m])) / sum(rate(tikv_raftstore_append_log_duration_seconds_count{@LABEL}[1m])))\",\n            \"promMetric\": \"tikv_raftstore_append_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_raftstore_append_log_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_raftstore_append_log_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_raftstore_append_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"database_time\",\n      \"order\": 2,\n      \"displayName\": \"Database Time by SQL Phase\",\n      \"name\": \"database_time_by_sql_phase\",\n      \"description\": \"Distribution of database time by SQL execution phase\",\n      \"metric\": {\n        \"name\": \"database_time_by_sql_phase\",\n        \"unit\": \"s\",\n        \"description\": \"Shows the database time consumed by each phase of SQL execution\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_tokens\",\n            \"promql\": \"sum(tidb_server_tokens{@LABEL})\",\n            \"promMetric\": \"tidb_server_tokens\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"database time\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_session_parse_duration\",\n            \"promql\": \"sum(rate(tidb_session_parse_duration_seconds_sum{sql_type=\\\"general\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_session_parse_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"parse\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_session_compile_duration\",\n            \"promql\": \"sum(rate(tidb_session_compile_duration_seconds_sum{sql_type=\\\"general\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_session_compile_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"compile\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_session_execute_duration\",\n            \"promql\": \"sum(rate(tidb_session_execute_duration_seconds_sum{sql_type=\\\"general\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_session_execute_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"execute\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_server_get_token_duration\",\n            \"promql\": \"sum(rate(tidb_server_get_token_duration_seconds_sum{@LABEL}[1m]))/1000000\",\n            \"promMetric\": \"tidb_server_get_token_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"get token\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"transaction\",\n      \"order\": 2,\n      \"displayName\": \"Duration\",\n      \"name\": \"transaction_duration\",\n      \"description\": \"Overview of transaction duration metrics\",\n      \"metric\": {\n        \"name\": \"transaction_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows transaction duration percentiles by transaction mode\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p99_transaction_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_session_transaction_duration_seconds_bucket{@LABEL}[1m])) by (le, txn_mode))\",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"txn_mode\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"99-{txnMode}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p95_transaction_duration\",\n            \"promql\": \"histogram_quantile(0.95, sum(rate(tidb_session_transaction_duration_seconds_bucket{@LABEL}[1m])) by (le, txn_mode))\",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"txn_mode\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"95-{txnMode}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p80_transaction_duration\",\n            \"promql\": \"histogram_quantile(0.80, sum(rate(tidb_session_transaction_duration_seconds_bucket{@LABEL}[1m])) by (le, txn_mode))\",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"txn_mode\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"80-{txnMode}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"throughput\",\n      \"order\": 2,\n      \"displayName\": \"CPS By Type\",\n      \"name\": \"cps_by_type\",\n      \"description\": \"Overview of commands per second metrics by type\",\n      \"metric\": {\n        \"name\": \"cps_by_type\",\n        \"unit\": \"short\",\n        \"description\": \"Shows the rate of different types of commands being processed\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"cps_by_type\",\n            \"promql\": \"sum(rate(tidb_server_query_total{@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tidb_server_query_total\",\n            \"labels\": [\n              \"instance\",\n              \"type\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"raft_log\",\n      \"order\": 2,\n      \"displayName\": \"Commit Log Duration\",\n      \"name\": \"commit_log_duration\",\n      \"description\": \"Overview of Raft log commit duration metrics\",\n      \"metric\": {\n        \"name\": \"commit_log_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and 99th percentile durations for committing Raft logs\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_raftstore_commit_log_duration\",\n            \"promql\": \"(sum(rate(tikv_raftstore_commit_log_duration_seconds_sum{@LABEL}[1m])) / sum(rate(tikv_raftstore_commit_log_duration_seconds_count{@LABEL}[1m])))\",\n            \"promMetric\": \"tikv_raftstore_commit_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_raftstore_commit_log_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_raftstore_commit_log_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_raftstore_commit_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"database_time\",\n      \"order\": 3,\n      \"displayName\": \"SQL Execute Time Overview\",\n      \"name\": \"sql_execute_time_overview\",\n      \"description\": \"Overview of SQL execution time\",\n      \"metric\": {\n        \"name\": \"sql_execute_time_overview\",\n        \"unit\": \"s\",\n        \"description\": \"Shows the time taken for various SQL execution components\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_tikvclient_request_seconds\",\n            \"promql\": \"sum(rate(tidb_tikvclient_request_seconds_sum{store!=\\\"0\\\",@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tidb_tikvclient_request_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"type\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_client_cmd_handle_cmds_wait_duration\",\n            \"promql\": \"sum(rate(pd_client_cmd_handle_cmds_duration_seconds_sum{type=\\\"wait\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"pd_client_cmd_handle_cmds_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"tso_wait\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_session_execute_duration\",\n            \"promql\": \"sum(rate(tidb_session_execute_duration_seconds_sum{sql_type=\\\"general\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_session_execute_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"execute time\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tiflash_coprocessor_request_duration\",\n            \"promql\": \"sum(rate(tiflash_coprocessor_request_duration_seconds_sum{@LABEL}[1m]))\",\n            \"promMetric\": \"tiflash_coprocessor_request_duration_seconds\",\n            \"labels\": [\n              \"\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"tiflash_mpp\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"raft_log\",\n      \"order\": 3,\n      \"displayName\": \"Apply Log Duration\",\n      \"name\": \"apply_log_duration\",\n      \"description\": \"Overview of Raft log apply duration metrics\",\n      \"metric\": {\n        \"name\": \"apply_log_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and 99th percentile durations for applying Raft logs\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_raftstore_apply_log_duration\",\n            \"promql\": \"(sum(rate(tikv_raftstore_apply_log_duration_seconds_sum{@LABEL}[1m])) / sum(rate(tikv_raftstore_apply_log_duration_seconds_count{@LABEL}[1m])))\",\n            \"promMetric\": \"tikv_raftstore_apply_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_raftstore_apply_log_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_raftstore_apply_log_duration_seconds_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_raftstore_apply_log_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"throughput\",\n      \"order\": 3,\n      \"displayName\": \"Queries Using Plan Cache OPS\",\n      \"name\": \"queries_using_plan_cache_ops\",\n      \"description\": \"Overview of plan cache usage metrics\",\n      \"metric\": {\n        \"name\": \"queries_using_plan_cache_ops\",\n        \"unit\": \"short\",\n        \"description\": \"Shows plan cache hit and miss rates\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"queries_hit_plan_cache\",\n            \"promql\": \"sum(rate(tidb_server_plan_cache_total{@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_server_plan_cache_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg_hit\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"queries_miss_plan_cache\",\n            \"promql\": \"sum(rate(tidb_server_plan_cache_miss_total{@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_server_plan_cache_miss_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg_miss\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"transaction\",\n      \"order\": 3,\n      \"displayName\": \"Commit Token Wait Duration\",\n      \"name\": \"commit_token_wait_duration\",\n      \"description\": \"Overview of commit token wait duration metrics\",\n      \"metric\": {\n        \"name\": \"commit_token_wait_duration\",\n        \"unit\": \"ns\",\n        \"description\": \"Shows percentile measurements of time spent waiting for commit tokens\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p99_commit_token_wait_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_tikvclient_batch_executor_token_wait_duration_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_tikvclient_batch_executor_token_wait_duration\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p95_commit_token_wait_duration\",\n            \"promql\": \"histogram_quantile(0.95, sum(rate(tidb_tikvclient_batch_executor_token_wait_duration_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_tikvclient_batch_executor_token_wait_duration\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"instance\",\n            \"legend\": \"95\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p80_commit_token_wait_duration\",\n            \"promql\": \"histogram_quantile(0.80, sum(rate(tidb_tikvclient_batch_executor_token_wait_duration_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_tikvclient_batch_executor_token_wait_duration\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"instance\",\n            \"legend\": \"80\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"transaction\",\n      \"order\": 4,\n      \"displayName\": \"KV Transaction OPS\",\n      \"name\": \"kv_transaction_ops\",\n      \"description\": \"Overview of Key-Value transaction operations\",\n      \"metric\": {\n        \"name\": \"kv_transaction_ops\",\n        \"unit\": \"short\",\n        \"description\": \"Shows the rate of Key-Value transaction operations by instance\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"kv_transaction_ops\",\n            \"promql\": \"sum(rate(tidb_tikvclient_txn_cmd_duration_seconds_count{type=\\\"commit\\\",@LABEL}[1m])) by (instance)\",\n            \"promMetric\": \"tidb_tikvclient_txn_cmd_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"throughput\",\n      \"order\": 4,\n      \"displayName\": \"KV/TSO Request OPS\",\n      \"name\": \"kv_tso_request_ops\",\n      \"description\": \"Overview of KV and TSO request operation metrics\",\n      \"metric\": {\n        \"name\": \"kv_tso_request_ops\",\n        \"unit\": \"short\",\n        \"description\": \"Shows rates of KV and TSO requests by type\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_tikvclient_request_ops_by_type\",\n            \"promql\": \"sum(rate(tidb_tikvclient_request_seconds_count{@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tidb_tikvclient_request_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_tikvclient_request_ops\",\n            \"promql\": \"sum(rate(tidb_tikvclient_request_seconds_count{@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_tikvclient_request_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"kv request total\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_client_cmd_handle_cmds_tso_duration\",\n            \"promql\": \"sum(rate(pd_client_cmd_handle_cmds_duration_seconds_count{type=\\\"tso\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"pd_client_cmd_handle_cmds_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"tso - cmd\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"kv_tso_request_ops_tso_request\",\n            \"promql\": \"sum(rate(pd_client_request_handle_requests_duration_seconds_count{type=\\\"tso\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"pd_client_request_handle_requests_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"tso - request\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"database_time\",\n      \"order\": 4,\n      \"displayName\": \"Duration\",\n      \"name\": \"duration\",\n      \"description\": \"Overview of query duration metrics\",\n      \"metric\": {\n        \"name\": \"duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and percentile query durations\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_query_duration\",\n            \"promql\": \"sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\\\"internal\\\",@LABEL}[1m])) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\\\"internal\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_server_handle_query_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tidb_query_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket{sql_type!=\\\"internal\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_server_handle_query_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_query_duration_by_sql_type\",\n            \"promql\": \"sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\\\"internal\\\",@LABEL}[1m])) by (sql_type) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\\\"internal\\\",@LABEL}[1m])) by (sql_type)\",\n            \"promMetric\": \"tidb_server_handle_query_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"sql_type\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg-{sqlType}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"database_time\",\n      \"order\": 5,\n      \"displayName\": \"Parse Duration\",\n      \"name\": \"parse_duration\",\n      \"description\": \"Overview of SQL parse duration metrics\",\n      \"metric\": {\n        \"name\": \"parse_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and percentile parse durations\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p95_tidb_session_parse_duration\",\n            \"promql\": \"histogram_quantile(0.95, sum(rate(tidb_session_parse_duration_seconds_bucket{sql_type=\\\"general\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_session_parse_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"95\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_session_parse_duration\",\n            \"promql\": \"sum(rate(tidb_session_parse_duration_seconds_sum{sql_type=\\\"general\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_session_parse_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tidb_session_parse_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_session_parse_duration_seconds_bucket{sql_type=\\\"general\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_session_parse_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"database_time\",\n      \"order\": 6,\n      \"displayName\": \"Compile Duration\",\n      \"name\": \"compile_duration\",\n      \"description\": \"Overview of SQL compile duration metrics\",\n      \"metric\": {\n        \"name\": \"compile_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and percentile compile durations\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"avg_tidb_session_compile_duration\",\n            \"promql\": \"(sum(rate(tidb_session_compile_duration_seconds_sum{sql_type=\\\"general\\\",@LABEL}[1m])) / sum(rate(tidb_session_compile_duration_seconds_count{sql_type=\\\"general\\\",@LABEL}[1m])))\",\n            \"promMetric\": \"tidb_session_compile_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tidb_session_compile_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_session_compile_duration_seconds_bucket{sql_type=\\\"general\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_session_compile_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"database_time\",\n      \"order\": 7,\n      \"displayName\": \"Execute Duration\",\n      \"name\": \"execute_duration\",\n      \"description\": \"Overview of SQL execution duration metrics\",\n      \"metric\": {\n        \"name\": \"execute_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and 95th percentile SQL execution durations\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p95_tidb_session_execute_duration\",\n            \"promql\": \"histogram_quantile(0.95, sum(rate(tidb_session_execute_duration_seconds_bucket{sql_type=\\\"general\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_session_execute_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"95\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"avg_tidb_session_execute_duration\",\n            \"promql\": \"(sum(rate(tidb_session_execute_duration_seconds_sum{sql_type=\\\"general\\\",@LABEL}[1m])) / sum(rate(tidb_session_execute_duration_seconds_count{sql_type=\\\"general\\\",@LABEL}[1m])))\",\n            \"promMetric\": \"tidb_session_execute_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"database_time\",\n      \"order\": 8,\n      \"displayName\": \"Avg TiDB KV Request Duration\",\n      \"name\": \"avg_tidb_kv_request_duration\",\n      \"description\": \"Overview of TiDB KV request duration metrics\",\n      \"metric\": {\n        \"name\": \"avg_tidb_kv_request_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average duration of KV requests by type\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_tikvclient_request_duration\",\n            \"promql\": \"sum(rate(tidb_tikvclient_request_seconds_sum{store!=\\\"0\\\",@LABEL}[1m])) by (type)/ sum(rate(tidb_tikvclient_request_seconds_count{store!=\\\"0\\\",@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tidb_tikvclient_request_seconds\",\n            \"labels\": [\n              \"type\",\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"database_time\",\n      \"order\": 9,\n      \"displayName\": \"Avg TiKV GRPC Duration\",\n      \"name\": \"avg_tikv_grpc_duration\",\n      \"description\": \"Overview of TiKV gRPC request duration metrics\",\n      \"metric\": {\n        \"name\": \"avg_tikv_grpc_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average duration of gRPC requests by type\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"avg_tikv_grpc_duration\",\n            \"promql\": \"sum(rate(tikv_grpc_msg_duration_seconds_sum{store!=\\\"0\\\",@LABEL}[1m])) by (type)/ sum(rate(tikv_grpc_msg_duration_seconds_count{store!=\\\"0\\\",@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tikv_grpc_msg_duration_seconds\",\n            \"labels\": [\n              \"type\",\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"database_time\",\n      \"order\": 10,\n      \"displayName\": \"PD TSO Wait/RPC Duration\",\n      \"name\": \"pd_tso_wait_rpc_duration\",\n      \"description\": \"Overview of PD TSO request processing duration metrics\",\n      \"metric\": {\n        \"name\": \"pd_tso_wait_rpc_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and 99th percentile durations for TSO wait, RPC, and handle times\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_handle_cmds_wait_duration\",\n            \"promql\": \"(sum(rate(pd_client_cmd_handle_cmds_duration_seconds_sum{type=\\\"wait\\\",@LABEL}[1m])) / sum(rate(pd_client_cmd_handle_cmds_duration_seconds_count{type=\\\"wait\\\",@LABEL}[1m])))\",\n            \"promMetric\": \"pd_client_cmd_handle_cmds_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"wait - avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_handle_cmds_rpc_duration\",\n            \"promql\": \"(sum(rate(pd_client_request_handle_requests_duration_seconds_sum{type=\\\"tso,@LABEL\\\"}[1m])) / sum(rate(pd_client_request_handle_requests_duration_seconds_count{type=\\\"tso\\\",@LABEL}[1m])))\",\n            \"promMetric\": \"pd_client_request_handle_requests_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"rpc - avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_handle_cmds_duration\",\n            \"promql\": \"(sum(rate(pd_server_handle_tso_duration_seconds_sum{@LABEL}[1m])) / sum(rate(pd_server_handle_tso_duration_seconds_count{@LABEL}[1m])))\",\n            \"promMetric\": \"pd_server_handle_tso_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"handle - avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_pd_handle_cmds_wait_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(pd_client_request_handle_requests_duration_seconds_bucket{type=\\\"tso\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"pd_client_request_handle_requests_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"wait - 99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_pd_handle_cmds_rpc_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(pd_client_request_handle_requests_duration_seconds_bucket{type=\\\"tso\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"pd_client_request_handle_requests_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"rpc - 99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_pd_handle_cmds_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(pd_server_handle_tso_duration_seconds_bucket{@LABEL}[30s])) by (type, le))\",\n            \"promMetric\": \"pd_server_handle_tso_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"handle - 99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"database_time\",\n      \"order\": 11,\n      \"displayName\": \"Storage Async Write Duration\",\n      \"name\": \"storage_async_write_duration\",\n      \"description\": \"Overview of storage asynchronous write duration metrics\",\n      \"metric\": {\n        \"name\": \"storage_async_write_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and 99th percentile durations for asynchronous write operations\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_storage_engine_async_request_duration\",\n            \"promql\": \"sum(rate(tikv_storage_engine_async_request_duration_seconds_sum{type=\\\"write\\\",@LABEL}[1m])) / sum(rate(tikv_storage_engine_async_request_duration_seconds_count{type=\\\"write\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"tikv_storage_engine_async_request_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_storage_engine_async_request_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_storage_engine_async_request_duration_seconds_bucket{type=\\\"write\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_storage_engine_async_request_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"database_time\",\n      \"order\": 12,\n      \"displayName\": \"Store Duration\",\n      \"name\": \"store_duration\",\n      \"description\": \"Overview of TiKV store operation duration metrics\",\n      \"metric\": {\n        \"name\": \"store_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and 99th percentile durations for store operations\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_raftstore_store_duration\",\n            \"promql\": \"sum(rate(tikv_raftstore_store_duration_secs_sum{@LABEL}[1m])) / sum(rate(tikv_raftstore_store_duration_secs_count{@LABEL}[1m]))\",\n            \"promMetric\": \"tikv_raftstore_store_duration_secs\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_raftstore_store_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_raftstore_store_duration_secs_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_raftstore_store_duration_secs\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"basic\",\n      \"type\": \"database_time\",\n      \"order\": 13,\n      \"displayName\": \"Apply Duration\",\n      \"name\": \"apply_duration\",\n      \"description\": \"Overview of Raft log apply duration metrics\",\n      \"metric\": {\n        \"name\": \"apply_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and 99th percentile durations for applying Raft logs\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_raftstore_apply_duration\",\n            \"promql\": \"(sum(rate(tikv_raftstore_apply_duration_secs_sum{@LABEL}[1m])) / sum(rate(tikv_raftstore_apply_duration_secs_count{@LABEL}[1m])))\",\n            \"promMetric\": \"tikv_raftstore_apply_duration_secs\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tikv_raftstore_apply_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tikv_raftstore_apply_duration_secs_bucket{@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tikv_raftstore_apply_duration_secs\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"overview\",\n      \"type\": \"\",\n      \"order\": 1,\n      \"displayName\": \"Duration\",\n      \"name\": \"duration\",\n      \"description\": \"Overall query execution duration across the cluster\",\n      \"metric\": {\n        \"name\": \"duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows average and percentile query durations\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_query_duration\",\n            \"promql\": \"sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\\\"internal\\\",@LABEL}[1m])) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\\\"internal\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_server_handle_query_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p99_tidb_query_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_server_handle_query_duration_seconds_bucket{sql_type!=\\\"internal\\\",@LABEL}[1m])) by (le))\",\n            \"promMetric\": \"tidb_server_handle_query_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"99\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_query_duration_by_sql_type\",\n            \"promql\": \"sum(rate(tidb_server_handle_query_duration_seconds_sum{sql_type!=\\\"internal\\\",@LABEL}[1m])) by (sql_type) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\\\"internal\\\",@LABEL}[1m])) by (sql_type)\",\n            \"promMetric\": \"tidb_server_handle_query_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"sql_type\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"avg-{sqlType}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"overview\",\n      \"type\": \"\",\n      \"order\": 2,\n      \"displayName\": \"QPS\",\n      \"name\": \"qps\",\n      \"description\": \"Total Queries Per Second across all TiDB instances\",\n      \"metric\": {\n        \"name\": \"qps\",\n        \"unit\": \"short\",\n        \"description\": \"Shows total QPS, QPS by type, and failed query rates\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"qps_by_type\",\n            \"promql\": \"sum(rate(tidb_executor_statement_total{@LABEL}[1m])) by (type)\",\n            \"promMetric\": \"tidb_executor_statement_total\",\n            \"labels\": [\n              \"instance\",\n              \"type\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"qps\",\n            \"promql\": \"sum(rate(tidb_executor_statement_total{@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_executor_statement\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"Total\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"qps_error\",\n            \"promql\": \"sum(rate(tidb_server_execute_error_total{@LABEL}[1m])) \",\n            \"promMetric\": \"tidb_server_execute_error_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"Failed\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"overview\",\n      \"type\": \"\",\n      \"order\": 3,\n      \"displayName\": \"Transaction OPS\",\n      \"name\": \"transaction_ops_by_type_and_txn_mode\",\n      \"description\": \"Transaction operations per second categorized by type and transaction mode\",\n      \"metric\": {\n        \"name\": \"transaction_ops_by_type_and_txn_mode\",\n        \"unit\": \"short\",\n        \"description\": \"Shows transaction rates categorized by type and transaction mode\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tps_by_type_and_txn_mode\",\n            \"promql\": \"sum(rate(tidb_session_transaction_duration_seconds_count{@LABEL}[1m])) by (type, txn_mode)\",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"type\",\n              \"txn_mode\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{type}-{txnMode}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"overview\",\n      \"type\": \"\",\n      \"order\": 4,\n      \"displayName\": \"Transaction Duration\",\n      \"name\": \"transaction_duration\",\n      \"description\": \"Time taken to execute transactions, indicating transaction processing efficiency\",\n      \"metric\": {\n        \"name\": \"transaction_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Shows transaction duration percentiles by transaction mode\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"p99_transaction_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_session_transaction_duration_seconds_bucket{@LABEL}[1m])) by (le, txn_mode))\",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"txn_mode\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"99-{txnMode}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p95_transaction_duration\",\n            \"promql\": \"histogram_quantile(0.95, sum(rate(tidb_session_transaction_duration_seconds_bucket{@LABEL}[1m])) by (le, txn_mode))\",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"txn_mode\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"95-{txnMode}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"p80_transaction_duration\",\n            \"promql\": \"histogram_quantile(0.80, sum(rate(tidb_session_transaction_duration_seconds_bucket{@LABEL}[1m])) by (le, txn_mode))\",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [\n              \"instance\",\n              \"txn_mode\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"80-{txnMode}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"overview\",\n      \"type\": \"\",\n      \"order\": 5,\n      \"displayName\": \"TiKV Increase of Storage Usage\",\n      \"name\": \"tikv_increase_of_storage_usage\",\n      \"description\": \"Rate of storage space consumption increase in TiKV nodes\",\n      \"metric\": {\n        \"name\": \"tikv_increase_of_storage_usage\",\n        \"unit\": \"Bps\",\n        \"description\": \"Shows the rate of storage usage increase in TiKV instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_increase_of_storage_usage\",\n            \"promql\": \"rate(tikv_store_size_bytes{type=\\\"used\\\",@LABEL}[5m])\",\n            \"promMetric\": \"tikv_store_size_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"overview\",\n      \"type\": \"\",\n      \"order\": 6,\n      \"displayName\": \"TiFlash Increase of Storage Usage\",\n      \"name\": \"tiflash_increase_of_storage_usage\",\n      \"description\": \"Rate of storage space consumption increase in TiFlash nodes\",\n      \"metric\": {\n        \"name\": \"tiflash_increase_of_storage_usage\",\n        \"unit\": \"Bps\",\n        \"description\": \"Shows the rate of storage usage increase in TiFlash instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tiflash_increase_of_storage_usage\",\n            \"promql\": \"rate(tiflash_system_current_metric_StoreSizeUsed{@LABEL}[5m])\",\n            \"promMetric\": \"tiflash_system_current_metric_StoreSizeUsed\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"tidb\",\n      \"order\": 1,\n      \"displayName\": \"TiDB CPU\",\n      \"name\": \"tidb_cpu_usage\",\n      \"description\": \"Overview of TiDB CPU usage metrics\",\n      \"metric\": {\n        \"name\": \"tidb_cpu_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Shows CPU usage percentage and MAXPROCS limit for TiDB instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_cpu_usage_percentage\",\n            \"promql\": \"irate(process_cpu_seconds_total{ job=\\\"tidb\\\",@LABEL}[1m])\",\n            \"promMetric\": \"process_cpu_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_server_maxprocs\",\n            \"promql\": \"tidb_server_maxprocs{job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"tidb_server_maxprocs\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"limit-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"tiflash\",\n      \"order\": 1,\n      \"displayName\": \"TiFlash CPU\",\n      \"name\": \"tiflash_cpu_usage\",\n      \"description\": \"Overview of TiFlash CPU usage metrics\",\n      \"metric\": {\n        \"name\": \"tiflash_cpu_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Shows CPU usage percentage and core limits for TiFlash instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tiflash_cpu_usage_percentage\",\n            \"promql\": \"rate(tiflash_proxy_process_cpu_seconds_total{job=\\\"tiflash\\\",@LABEL}[1m])\",\n            \"promMetric\": \"tiflash_proxy_process_cpu_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tiflash_logic_cpu_cores\",\n            \"promql\": \"sum(tiflash_system_current_metric_LogicalCPUCores{@LABEL}) by (instance)\",\n            \"promMetric\": \"tiflash_system_current_metric_LogicalCPUCores\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"limit-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"tikv\",\n      \"order\": 1,\n      \"displayName\": \"TiKV CPU\",\n      \"name\": \"tikv_cpu_usage\",\n      \"description\": \"Overview of TiKV CPU usage metrics\",\n      \"metric\": {\n        \"name\": \"tikv_cpu_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Shows CPU usage percentage for TiKV instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_cpu_usage_percentage\",\n            \"promql\": \"sum(rate(process_cpu_seconds_total{ job=~\\\".*tikv\\\",@LABEL}[1m])) by (instance)\",\n            \"promMetric\": \"process_cpu_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"pd\",\n      \"order\": 1,\n      \"displayName\": \"PD CPU\",\n      \"name\": \"pd_cpu_usage_percentage\",\n      \"description\": \"Overview of PD CPU usage metrics\",\n      \"metric\": {\n        \"name\": \"pd_cpu_usage_percentage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Shows CPU usage percentage for PD instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_cpu_usage_percentage\",\n            \"promql\": \"irate(process_cpu_seconds_total{job=~\\\".*pd.*\\\",@LABEL}[30s])\",\n            \"promMetric\": \"process_cpu_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"pd\",\n      \"order\": 2,\n      \"displayName\": \"PD Memory\",\n      \"name\": \"pd_memory_usage\",\n      \"description\": \"Overview of PD memory usage metrics\",\n      \"metric\": {\n        \"name\": \"pd_memory_usage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Shows detailed memory usage metrics for PD instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"pd_memory_usage\",\n            \"promql\": \"process_resident_memory_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"process_resident_memory_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"process-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_sys_memory_usage\",\n            \"promql\": \"go_memstats_heap_sys_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_sys_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapSys-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_inuse_memory_usage\",\n            \"promql\": \"go_memstats_heap_inuse_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_inuse_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapInuse-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_alloc_memory_usage\",\n            \"promql\": \"go_memstats_heap_alloc_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_alloc_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapAlloc-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_idle_memory_usage\",\n            \"promql\": \"go_memstats_heap_idle_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_idle_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapIdle-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_heap_released_memory_usage\",\n            \"promql\": \"go_memstats_heap_released_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_heap_released_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"HeapReleased-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"pd_gc_trigger_memory_usage\",\n            \"promql\": \"go_memstats_next_gc_bytes{job=~\\\".*pd.*\\\",@LABEL}\",\n            \"promMetric\": \"go_memstats_next_gc_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"pd\",\n            \"legend\": \"GCTrigger-{job}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"tiflash\",\n      \"order\": 2,\n      \"displayName\": \"TiFlash Memory\",\n      \"name\": \"tiflash_memory_usage\",\n      \"description\": \"Overview of TiFlash memory usage metrics\",\n      \"metric\": {\n        \"name\": \"tiflash_memory_usage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Shows memory usage and limits for TiFlash instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tiflash_memory_usage\",\n            \"promql\": \"tiflash_proxy_process_resident_memory_bytes{job=\\\"tiflash\\\",@LABEL}\",\n            \"promMetric\": \"tiflash_proxy_process_resident_memory_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tiflash_memory_limit\",\n            \"promql\": \"sum(tiflash_system_current_metric_MemoryCapacity{@LABEL}) by (instance)\",\n            \"promMetric\": \"tiflash_system_current_metric_MemoryCapacity\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"limit-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"tidb\",\n      \"order\": 2,\n      \"displayName\": \"TiDB Memory\",\n      \"name\": \"tidb_memory_usage\",\n      \"description\": \"Overview of TiDB memory usage metrics\",\n      \"metric\": {\n        \"name\": \"tidb_memory_usage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Shows detailed memory usage metrics for TiDB instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_process_memory_usage\",\n            \"promql\": \"process_resident_memory_bytes{ job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"process_resident_memory_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"process-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_heap_sys_memory_usage\",\n            \"promql\": \"go_memory_classes_heap_objects_bytes{ job=\\\"tidb\\\",@LABEL} + go_memory_classes_heap_unused_bytes{ job=\\\"tidb\\\",@LABEL} + go_memory_classes_heap_released_bytes{ job=\\\"tidb\\\",@LABEL} + go_memory_classes_heap_free_bytes{ job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"go_memory_classes_heap_objects_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"HeapSys-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_heap_inuse_memory_usage\",\n            \"promql\": \"go_memory_classes_heap_objects_bytes{ job=\\\"tidb\\\",@LABEL} + go_memory_classes_heap_unused_bytes{ job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"go_memory_classes_heap_objects_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"HeapInuse-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_heap_alloc_memory_usage\",\n            \"promql\": \"go_memory_classes_heap_objects_bytes{ job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"go_memory_classes_heap_objects_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"HeapAlloc-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_heap_idle_memory_usage\",\n            \"promql\": \"go_memory_classes_heap_released_bytes{job=\\\"tidb\\\",@LABEL} + go_memory_classes_heap_free_bytes{ job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"go_memory_classes_heap_released_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"HeapIdle-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_heap_released_memory_usage\",\n            \"promql\": \"go_memory_classes_heap_released_bytes{job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"go_memory_classes_heap_released_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"HeapReleased-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_gc_trigger_memory_usage\",\n            \"promql\": \"go_gc_heap_goal_bytes{job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"go_gc_heap_goal_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"GCTrigger-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_server_memory_usage\",\n            \"promql\": \"tidb_server_memory_usage{job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"tidb_server_memory_usage\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{module}-{type}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tidb_server_memory_usage_by_module\",\n            \"promql\": \"sum(tidb_server_memory_usage{job=\\\"tidb\\\",@LABEL}) by (module, instance)\",\n            \"promMetric\": \"tidb_server_memory_usage\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{module}-{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"tikv\",\n      \"order\": 2,\n      \"displayName\": \"TiKV Memory\",\n      \"name\": \"tikv_memory_usage\",\n      \"description\": \"Overview of TiKV memory usage metrics\",\n      \"metric\": {\n        \"name\": \"tikv_memory_usage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Shows memory usage for TiKV instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_memory_usage\",\n            \"promql\": \"avg(process_resident_memory_bytes{job=~\\\".*tikv\\\",@LABEL}) by (instance)\",\n            \"promMetric\": \"process_resident_memory_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"tikv\",\n      \"order\": 3,\n      \"displayName\": \"TiKV IO MBps\",\n      \"name\": \"tikv_io_mbps\",\n      \"description\": \"Overview of TiKV IO throughput metrics\",\n      \"metric\": {\n        \"name\": \"tikv_io_mbps\",\n        \"unit\": \"Bps\",\n        \"description\": \"Shows read and write IO throughput for TiKV instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_io_mbps_write\",\n            \"promql\": \"sum(rate(tikv_engine_flow_bytes{db=\\\"kv\\\", type=\\\"wal_file_bytes\\\",@LABEL}[1m])) by (instance)\",\n            \"promMetric\": \"tikv_engine_flow_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"{instance}-write\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tikv_io_mbps_read\",\n            \"promql\": \"sum(rate(tikv_engine_flow_bytes{db=\\\"kv\\\", type=~\\\"bytes_read|iter_bytes_read\\\",@LABEL}[1m])) by (instance)\",\n            \"promMetric\": \"tikv_engine_flow_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"{instance}-read\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"tiflash\",\n      \"order\": 3,\n      \"displayName\": \"TiFlash IO MBps\",\n      \"name\": \"tiflash_io_mbps\",\n      \"description\": \"Overview of TiFlash IO throughput metrics\",\n      \"metric\": {\n        \"name\": \"tiflash_io_mbps\",\n        \"unit\": \"Bps\",\n        \"description\": \"Shows read and write IO throughput for TiFlash instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tiflash_io_mbps_write\",\n            \"promql\": \"sum(rate(tiflash_system_profile_event_WriteBufferFromFileDescriptorWriteBytes{@LABEL}[1m])) by (instance) + sum(rate(tiflash_system_profile_event_PSMWriteBytes{@LABEL}[1m])) by (instance) + sum(rate(tiflash_system_profile_event_WriteBufferAIOWriteBytes{@LABEL}[1m])) by (instance)\",\n            \"promMetric\": \"tiflash_system_profile_event_WriteBufferFromFileDescriptorWriteBytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"{instance}-write\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"tiflash_io_mbps_read\",\n            \"promql\": \"sum(rate(tiflash_system_profile_event_ReadBufferFromFileDescriptorReadBytes{@LABEL}[1m])) by (instance) + sum(rate(tiflash_system_profile_event_PSMReadBytes{@LABEL}[1m])) by (instance) + sum(rate(tiflash_system_profile_event_ReadBufferAIOReadBytes{@LABEL}[1m])) by (instance)\",\n            \"promMetric\": \"tiflash_system_profile_event_ReadBufferFromFileDescriptorReadBytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"{instance}-read\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"tikv\",\n      \"order\": 4,\n      \"displayName\": \"TiKV Available Storage\",\n      \"name\": \"tikv_available_storage\",\n      \"description\": \"Overview of TiKV storage availability metrics\",\n      \"metric\": {\n        \"name\": \"tikv_available_storage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Shows available storage space for TiKV instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_store_size_available\",\n            \"promql\": \"sum(tikv_store_size_bytes{type=\\\"available\\\",@LABEL}) by (instance)\",\n            \"promMetric\": \"tikv_store_size_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"tiflash\",\n      \"order\": 4,\n      \"displayName\": \"TiFlash Available Storage\",\n      \"name\": \"tiflash_available_storage\",\n      \"description\": \"Overview of TiFlash storage availability metrics\",\n      \"metric\": {\n        \"name\": \"tiflash_available_storage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Shows available storage space for TiFlash instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tiflash_available_store_size\",\n            \"promql\": \"sum(tiflash_system_current_metric_StoreSizeAvailable{@LABEL}) by (instance)\",\n            \"promMetric\": \"tiflash_system_current_metric_StoreSizeAvailable\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"tiflash\",\n      \"order\": 5,\n      \"displayName\": \"TiFlash Increase of Storage Usage\",\n      \"name\": \"tiflash_increase_of_storage_usage\",\n      \"description\": \"Overview of TiFlash storage usage growth metrics\",\n      \"metric\": {\n        \"name\": \"tiflash_increase_of_storage_usage\",\n        \"unit\": \"Bps\",\n        \"description\": \"Shows the rate of storage usage increase in TiFlash instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tiflash_increase_of_storage_usage\",\n            \"promql\": \"rate(tiflash_system_current_metric_StoreSizeUsed{@LABEL}[5m])\",\n            \"promMetric\": \"tiflash_system_current_metric_StoreSizeUsed\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tiflash\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"cluster\",\n      \"group\": \"resource\",\n      \"type\": \"tikv\",\n      \"order\": 5,\n      \"displayName\": \"TiKV Increase of Storage Usage\",\n      \"name\": \"tikv_increase_of_storage_usage\",\n      \"description\": \"Overview of TiKV storage usage growth metrics\",\n      \"metric\": {\n        \"name\": \"tikv_increase_of_storage_usage\",\n        \"unit\": \"Bps\",\n        \"description\": \"Shows the rate of storage usage increase in TiKV instances\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_increase_of_storage_usage\",\n            \"promql\": \"rate(tikv_store_size_bytes{type=\\\"used\\\",@LABEL}[5m])\",\n            \"promMetric\": \"tikv_store_size_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    }\n  ]\n}"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/sample-res/metrics-config-host.json",
    "content": "{\n  \"metrics\": [\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"resource\",\n      \"order\": 1,\n      \"displayName\": \"Memory\",\n      \"name\": \"host_memory\",\n      \"description\": \"Total and available memory statistics of the host machine\",\n      \"metric\": {\n        \"name\": \"host_memory\",\n        \"unit\": \"bytes\",\n        \"description\": \"Overview of system memory usage\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_memory_total\",\n            \"promql\": \"node_memory_MemTotal_bytes{@LABEL}\",\n            \"promMetric\": \"node_memory_MemTotal_bytes\",\n            \"labels\": [\n              \"\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"total\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"host_memory_used\",\n            \"promql\": \"node_memory_MemTotal_bytes{@LABEL} - (node_memory_MemAvailable_bytes{@LABEL} or (node_memory_MemFree_bytes{@LABEL} + node_memory_Buffers_bytes{@LABEL} + node_memory_Cached_bytes{@LABEL}))\",\n            \"promMetric\": \"node_memory_MemAvailable_bytes\",\n            \"labels\": [\n              \"\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"used\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"host_memory_available\",\n            \"promql\": \"node_memory_MemAvailable_bytes{@LABEL} or (node_memory_MemFree_bytes{@LABEL} + node_memory_Buffers_bytes{@LABEL} + node_memory_Cached_bytes{@LABEL})\",\n            \"promMetric\": \"node_memory_MemAvailable_bytes\",\n            \"labels\": [\n              \"\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"available\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"performance\",\n      \"order\": 1,\n      \"displayName\": \"Linux System Load\",\n      \"name\": \"host_linux_system_load\",\n      \"description\": \"System load averages for the past 1, 5, and 15 minutes\",\n      \"metric\": {\n        \"name\": \"host_linux_system_load\",\n        \"unit\": \"short\",\n        \"description\": \"System load averages for the past 1, 5, and 15 minutes\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_load_1\",\n            \"promql\": \"node_load1{@LABEL}\",\n            \"promMetric\": \"node_load1\",\n            \"labels\": [\n              \"\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"Load 1m\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"host_load_5\",\n            \"promql\": \"node_load5{@LABEL}\",\n            \"promMetric\": \"node_load5\",\n            \"labels\": [\n              \"\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"Load 5m\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"host_load_15\",\n            \"promql\": \"node_load15{@LABEL}\",\n            \"promMetric\": \"node_load15\",\n            \"labels\": [\n              \"\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"Load 15m\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"process\",\n      \"order\": 1,\n      \"displayName\": \"Process Memory\",\n      \"name\": \"host_process_memory\",\n      \"description\": \"Memory consumption of specific processes running on the host\",\n      \"metric\": {\n        \"name\": \"host_process_memory\",\n        \"unit\": \"bytes\",\n        \"description\": \"Memory consumption of specific processes\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_process_memory\",\n            \"promql\": \"process_resident_memory_bytes{@LABEL}\",\n            \"promMetric\": \"process_resident_memory_bytes\",\n            \"labels\": [\n              \"job\",\n              \"instance\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"{job} {instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"performance\",\n      \"order\": 2,\n      \"displayName\": \"CPU Usage\",\n      \"name\": \"host_cpu_usage\",\n      \"description\": \"Percentage of CPU utilization across all cores of the host machine\",\n      \"metric\": {\n        \"name\": \"host_cpu_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Overall CPU usage of the host machine, expressed as a percentage.\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_cpu_usage\",\n            \"promql\": \"1-(avg by(instance)(irate(node_cpu_seconds_total{mode=\\\"idle\\\",@LABEL}[1m])))\",\n            \"promMetric\": \"node_cpu_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"cpu_usage_percent\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"process\",\n      \"order\": 2,\n      \"displayName\": \"Process CPU Usage\",\n      \"name\": \"host_process_cpu_usage\",\n      \"description\": \"CPU utilization of specific processes running on the host\",\n      \"metric\": {\n        \"name\": \"host_process_cpu_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"CPU utilization percentage for specific processes\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_process_cpu_usage\",\n            \"promql\": \"rate(process_cpu_seconds_total{@LABEL}[1m])\",\n            \"promMetric\": \"process_cpu_seconds_total\",\n            \"labels\": [\n              \"job\",\n              \"instance\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"{job} {instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"resource\",\n      \"order\": 2,\n      \"displayName\": \"Memory Usage\",\n      \"name\": \"host_memory_usage\",\n      \"description\": \"Percentage of total memory currently in use by the system\",\n      \"metric\": {\n        \"name\": \"host_memory_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Total memory usage of the host machine, indicating system-wide RAM utilization\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_memory_usage\",\n            \"promql\": \"(node_memory_MemTotal_bytes{@LABEL}-node_memory_MemFree_bytes{@LABEL}-node_memory_Buffers_bytes{@LABEL}-node_memory_Cached_bytes{@LABEL})/node_memory_MemTotal_bytes{@LABEL}\",\n            \"promMetric\": \"node_memory_MemTotal_bytes\",\n            \"labels\": [],\n            \"type\": \"host\",\n            \"legend\": \"memory_usage_percentage\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"performance\",\n      \"order\": 3,\n      \"displayName\": \"IO Usage\",\n      \"name\": \"host_io_usage\",\n      \"description\": \"Percentage of time the system spent on I/O operations\",\n      \"metric\": {\n        \"name\": \"host_io_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Percentage of time the storage device is busy processing I/O requests\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_io_usage\",\n            \"promql\": \"rate(node_disk_io_time_seconds_total{@LABEL}[30m]) or irate(node_disk_io_time_seconds_total{@LABEL}[1m])\",\n            \"promMetric\": \"node_disk_io_time_seconds_total\",\n            \"labels\": [\n              \"device\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"{device}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"resource\",\n      \"order\": 3,\n      \"displayName\": \"Disk Usage\",\n      \"name\": \"host_disk_usage\",\n      \"description\": \"Percentage of disk space utilized across mounted filesystems\",\n      \"metric\": {\n        \"name\": \"host_disk_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Percentage of used disk space for each filesystem mount point\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_disk_usage\",\n            \"promql\": \"1 - node_filesystem_avail_bytes{ device=~'^/.*',@LABEL} / node_filesystem_size_bytes{device=~'^/.*',@LABEL}\",\n            \"promMetric\": \"node_filesystem_avail_bytes\",\n            \"labels\": [\n              \"device\",\n              \"fstype\",\n              \"mountpoint\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"{device} {fstype} {mountpoint}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"performance\",\n      \"order\": 4,\n      \"displayName\": \"IO Queue Size\",\n      \"name\": \"host_io_queue_size\",\n      \"description\": \"Number of I/O requests waiting in the device queue\",\n      \"metric\": {\n        \"name\": \"host_io_queue_size\",\n        \"unit\": \"short\",\n        \"description\": \"Average number of I/O requests waiting in the device queue\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_io_queue_size\",\n            \"promql\": \"rate(node_disk_io_time_weighted_seconds_total{@LABEL}[5m])\",\n            \"promMetric\": \"node_disk_io_time_weighted_seconds_total\",\n            \"labels\": [\n              \"device\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"{device}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"performance\",\n      \"order\": 5,\n      \"displayName\": \"IOPs\",\n      \"name\": \"host_iops\",\n      \"description\": \"Number of I/O operations (reads and writes) per second\",\n      \"metric\": {\n        \"name\": \"host_iops\",\n        \"unit\": \"short\",\n        \"description\": \"Number of I/O operations (reads and writes) per second\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_iops\",\n            \"promql\": \"sum(rate(node_disk_reads_completed_total{@LABEL}[5m])+rate(node_disk_writes_completed_total{@LABEL}[5m]))by(instance)\",\n            \"promMetric\": \"node_disk_reads_completed_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"iops\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"performance\",\n      \"order\": 6,\n      \"displayName\": \"IO Latency\",\n      \"name\": \"host_io_latency\",\n      \"description\": \"Average time taken to complete I/O operations\",\n      \"metric\": {\n        \"name\": \"host_io_latency\",\n        \"unit\": \"s\",\n        \"description\": \"Average time taken for read and write I/O operations to complete\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_io_write_latency\",\n            \"promql\": \"(rate(node_disk_write_time_seconds_total{@LABEL}[5m])/ rate(node_disk_writes_completed_total{@LABEL}[5m]))\",\n            \"promMetric\": \"node_disk_write_time_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"Write Latency: [{device}]\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"host_io_read_latency\",\n            \"promql\": \"(rate(node_disk_read_time_seconds_total{@LABEL}[5m])/ rate(node_disk_reads_completed_total{@LABEL}[5m]))\",\n            \"promMetric\": \"node_disk_read_time_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"Read Latency: [{device}]\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"performance\",\n      \"order\": 7,\n      \"displayName\": \"IO Throughput\",\n      \"name\": \"host_io_throughput\",\n      \"description\": \"Rate of data transfer for I/O operations\",\n      \"metric\": {\n        \"name\": \"host_io_throughput\",\n        \"unit\": \"bytes\",\n        \"description\": \"Rate of data transfer to and from storage devices\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_io_throughput\",\n            \"promql\": \"irate(node_disk_read_bytes_total{@LABEL}[5m]) + irate(node_disk_written_bytes_total{@LABEL}[5m])\",\n            \"promMetric\": \"node_disk_read_bytes_total\",\n            \"labels\": [\n              \"\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"{device}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"performance\",\n      \"order\": 8,\n      \"displayName\": \"Network Throughput\",\n      \"name\": \"host_network_throughput\",\n      \"description\": \"Rate of data transfer over the network interfaces\",\n      \"metric\": {\n        \"name\": \"host_network_throughput\",\n        \"unit\": \"bytes\",\n        \"description\": \"Network traffic throughput of the host machine, measuring inbound and outbound data transfer\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_network_received\",\n            \"promql\": \"sum(increase(node_network_receive_bytes_total{device!=\\\"lo\\\",@LABEL}[5m]))by(instance)\",\n            \"promMetric\": \"node_network_receive_bytes_total\",\n            \"labels\": [],\n            \"type\": \"host\",\n            \"legend\": \"received\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"host_network_sent\",\n            \"promql\": \"sum(increase(node_network_transmit_bytes_total{device!=\\\"lo\\\",@LABEL}[5m]))by(instance)\",\n            \"promMetric\": \"node_network_transmit_bytes_total\",\n            \"labels\": [],\n            \"type\": \"host\",\n            \"legend\": \"sent\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"performance\",\n      \"order\": 9,\n      \"displayName\": \"Network In/Out Packets\",\n      \"name\": \"host_network_in_out_packets\",\n      \"description\": \"Number of network packets received and transmitted per second\",\n      \"metric\": {\n        \"name\": \"host_network_in_out_packets\",\n        \"unit\": \"pps\",\n        \"description\": \"Number of network packets received (inbound) and transmitted (outbound) per second for each network interface\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_network_in_packets\",\n            \"promql\": \"rate(node_network_receive_packets_total{device!=\\\"lo\\\",@LABEL}[30m]) or irate(node_network_receive_packets_total{device!=\\\"lo\\\",@LABEL}[5m])\",\n            \"promMetric\": \"node_network_receive_packets_total\",\n            \"labels\": [\n              \"device\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"Inbound: {device}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"host_network_out_packets\",\n            \"promql\": \"rate(node_network_transmit_packets_total{device!=\\\"lo\\\",@LABEL}[30m]) or irate(node_network_transmit_packets_total{device!=\\\"lo\\\",@LABEL}[5m])\",\n            \"promMetric\": \"node_network_transmit_packets_total\",\n            \"labels\": [\n              \"device\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"Outbound: {device}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"performance\",\n      \"order\": 10,\n      \"displayName\": \"TCP Retransmission Percentage\",\n      \"name\": \"host_tcp_retrans_percentage\",\n      \"description\": \"Percentage of TCP packets that needed to be retransmitted\",\n      \"metric\": {\n        \"name\": \"host_tcp_retrans_percentage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Percentage of TCP packets that had to be retransmitted\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_tcp_retrans_percentage\",\n            \"promql\": \"rate(node_netstat_Tcp_RetransSegs{@LABEL}[5m]) / rate(node_netstat_Tcp_InSegs{@LABEL}[5m])\",\n            \"promMetric\": \"node_netstat_Tcp_RetransSegs\",\n            \"labels\": [\n              \"\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"tcp_retrans_percent\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"host\",\n      \"group\": \"basic\",\n      \"type\": \"performance\",\n      \"order\": 11,\n      \"displayName\": \"Inode Usage\",\n      \"name\": \"host_inode_usage\",\n      \"description\": \"Percentage of used inodes in the filesystem\",\n      \"metric\": {\n        \"name\": \"host_inode_usage\",\n        \"unit\": \"percent\",\n        \"description\": \"Percentage of used inodes for each filesystem\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_inode_usage\",\n            \"promql\": \"node_filesystem_files_free{@LABEL} / node_filesystem_files{@LABEL}\",\n            \"promMetric\": \"node_filesystem_files_free\",\n            \"labels\": [\n              \"fstype\",\n              \"device\",\n              \"mountpoint\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"{fstype} {device} {mountpoint}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    }\n  ]\n}"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/sample-res/metrics-config-overview.json",
    "content": "{\n  \"metrics\": [\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"instance_top\",\n      \"order\": 1,\n      \"displayName\": \"TiDB CPU\",\n      \"name\": \"overview_tidb_cpu_usage_percentage\",\n      \"description\": \"CPU usage of TiDB instances, showing the percentage of CPU resources being utilized\",\n      \"metric\": {\n        \"name\": \"overview_tidb_cpu_usage_percentage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"CPU usage of TiDB instances, showing the percentage of CPU resources being utilized\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_cpu_usage_percentage\",\n            \"promql\": \"irate(process_cpu_seconds_total{ job=\\\"tidb\\\",@LABEL}[1m])\",\n            \"promMetric\": \"process_cpu_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"cluster_top\",\n      \"order\": 1,\n      \"displayName\": \"Transaction OPS\",\n      \"name\": \"overview_transaction_ops\",\n      \"description\": \"Number of transactions processed per second across the cluster\",\n      \"metric\": {\n        \"name\": \"overview_transaction_ops\",\n        \"unit\": \"short\",\n        \"description\": \"Number of transactions processed per second across the cluster\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tps\",\n            \"promql\": \"sum(rate(tidb_session_transaction_duration_seconds_count{@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"host_top\",\n      \"order\": 1,\n      \"displayName\": \"CPU Usage\",\n      \"name\": \"overview_host_cpu_usage\",\n      \"description\": \"Overall CPU usage of the host machine, showing system-wide CPU utilization\",\n      \"metric\": {\n        \"name\": \"overview_host_cpu_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Overall CPU usage of the host machine, showing system-wide CPU utilization\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_cpu_usage\",\n            \"promql\": \"1-(avg by(instance)(irate(node_cpu_seconds_total{mode=\\\"idle\\\",@LABEL}[1m])))\",\n            \"promMetric\": \"node_cpu_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"cpu_usage_percent\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"instance_top\",\n      \"order\": 2,\n      \"displayName\": \"TiDB Memory\",\n      \"name\": \"overview_tidb_memory_usage\",\n      \"description\": \"Memory usage of TiDB instances, indicating the amount of RAM being consumed\",\n      \"metric\": {\n        \"name\": \"overview_tidb_memory_usage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Memory usage of TiDB instances, indicating the amount of RAM being consumed\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tidb_memory_usage\",\n            \"promql\": \"process_resident_memory_bytes{job=\\\"tidb\\\",@LABEL}\",\n            \"promMetric\": \"process_resident_memory_bytes\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"cluster_top\",\n      \"order\": 2,\n      \"displayName\": \"QPS\",\n      \"name\": \"overview_qps\",\n      \"description\": \"Queries per second processed by the cluster\",\n      \"metric\": {\n        \"name\": \"overview_qps\",\n        \"unit\": \"short\",\n        \"description\": \"Queries per second processed by the cluster\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"qps\",\n            \"promql\": \"sum(rate(tidb_executor_statement_total{@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_executor_statement\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"host_top\",\n      \"order\": 2,\n      \"displayName\": \"Memory Usage\",\n      \"name\": \"overview_host_memory_usage\",\n      \"description\": \"Total memory usage of the host machine, indicating system-wide RAM utilization\",\n      \"metric\": {\n        \"name\": \"overview_host_memory_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Total memory usage of the host machine, indicating system-wide RAM utilization\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_memory_usage\",\n            \"promql\": \"(node_memory_MemTotal_bytes{@LABEL}-node_memory_MemFree_bytes{@LABEL}-node_memory_Buffers_bytes{@LABEL}-node_memory_Cached_bytes{@LABEL})/node_memory_MemTotal_bytes{@LABEL}\",\n            \"promMetric\": \"node_memory_MemTotal_bytes\",\n            \"labels\": [],\n            \"type\": \"host\",\n            \"legend\": \"memory_usage_percent\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"host_top\",\n      \"order\": 3,\n      \"displayName\": \"IO Usage\",\n      \"name\": \"overview_host_avg_io_usage\",\n      \"description\": \"Disk avg I/O usage of the host machine, monitoring disk I/O performance\",\n      \"metric\": {\n        \"name\": \"overview_host_avg_io_usage\",\n        \"unit\": \"percentunit\",\n        \"description\": \"Disk avg I/O usage of the host machine, monitoring disk I/O performance\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_avg_io_usage\",\n            \"promql\": \"avg(rate(node_disk_io_time_seconds_total{@LABEL}[5m])) by (instance)\",\n            \"promMetric\": \"node_disk_io_time_seconds_total\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"host\",\n            \"legend\": \"io_usage\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"instance_top\",\n      \"order\": 3,\n      \"displayName\": \"TiKV Storage\",\n      \"name\": \"overview_tikv_storage_usage\",\n      \"description\": \"Storage space usage of TiKV instances, showing disk space consumption\",\n      \"metric\": {\n        \"name\": \"overview_tikv_storage_usage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Storage space usage of TiKV instances, showing disk space consumption\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tikv_storage_usage\",\n            \"promql\": \"sum(tikv_store_size_bytes{type=\\\"used\\\",@LABEL})by (instance)\",\n            \"promMetric\": \"sum(tikv_store_size_bytes{type=\\\"used\\\",@LABEL})by (instance)\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tikv\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"cluster_top\",\n      \"order\": 3,\n      \"displayName\": \"Duration\",\n      \"name\": \"overview_query_duration\",\n      \"description\": \"Average time taken to execute queries in the cluster\",\n      \"metric\": {\n        \"name\": \"overview_query_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Average time taken to execute queries in the cluster, measured in seconds\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"avg_query_duration\",\n            \"promql\": \"sum(rate(tidb_server_handle_query_duration_seconds_sum{ sql_type!=\\\"internal\\\",@LABEL}[1m])) / sum(rate(tidb_server_handle_query_duration_seconds_count{sql_type!=\\\"internal\\\",@LABEL}[1m]))\",\n            \"promMetric\": \"tidb_server_handle_query_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"host_top\",\n      \"order\": 4,\n      \"displayName\": \"Network Throughput\",\n      \"name\": \"overview_host_network_throughput\",\n      \"description\": \"Network traffic throughput of the host machine, measuring inbound and outbound data transfer\",\n      \"metric\": {\n        \"name\": \"overview_host_network_throughput\",\n        \"unit\": \"bytes\",\n        \"description\": \"Network traffic throughput of the host machine, measuring inbound and outbound data transfer\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"host_network_received\",\n            \"promql\": \"sum(increase(node_network_receive_bytes_total{device!=\\\"lo\\\",@LABEL}[5m]))by(instance)\",\n            \"promMetric\": \"node_network_receive_bytes_total\",\n            \"labels\": [],\n            \"type\": \"host\",\n            \"legend\": \"received\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          },\n          {\n            \"name\": \"host_network_sent\",\n            \"promql\": \"sum(increase(node_network_transmit_bytes_total{device!=\\\"lo\\\",@LABEL}[5m]))by(instance)\",\n            \"promMetric\": \"node_network_transmit_bytes_total\",\n            \"labels\": [],\n            \"type\": \"host\",\n            \"legend\": \"sent\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"instance_top\",\n      \"order\": 4,\n      \"displayName\": \"TiFlash Storage\",\n      \"name\": \"overview_tiflash_storage_usage\",\n      \"description\": \"Storage space usage of TiFlash instances, monitoring disk space utilization\",\n      \"metric\": {\n        \"name\": \"overview_tiflash_storage_usage\",\n        \"unit\": \"bytes\",\n        \"description\": \"Storage space usage of TiFlash instances, monitoring disk space utilization\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"tiflash_storage_usage\",\n            \"promql\": \"sum(tiflash_system_current_metric_StoreSizeUsed{@LABEL}) by (instance)\",\n            \"promMetric\": \"tiflash_system_current_metric_StoreSizeUsed\",\n            \"labels\": [],\n            \"type\": \"tiflash\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"cluster_top\",\n      \"order\": 4,\n      \"displayName\": \"Transaction Duration\",\n      \"name\": \"overview_transaction_duration\",\n      \"description\": \"Average time taken to complete transactions in the cluster\",\n      \"metric\": {\n        \"name\": \"overview_transaction_duration\",\n        \"unit\": \"s\",\n        \"description\": \"Average time taken to complete transactions in the cluster, measured in seconds\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"avg_transaction_duration\",\n            \"promql\": \"sum(rate(tidb_session_transaction_duration_seconds_sum{@LABEL}[1m])) / sum(rate(tidb_session_transaction_duration_seconds_count{@LABEL}[1m])) \",\n            \"promMetric\": \"tidb_session_transaction_duration_seconds\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"cluster_top\",\n      \"order\": 5,\n      \"displayName\": \"Commit Token Wait Duration\",\n      \"name\": \"overview_commit_token_wait_duration\",\n      \"description\": \"Time spent waiting for commit tokens, indicating potential transaction bottlenecks\",\n      \"metric\": {\n        \"name\": \"overview_commit_token_wait_duration\",\n        \"unit\": \"ns\",\n        \"description\": \"Time spent waiting for commit tokens in the cluster\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"commit_token_wait_duration\",\n            \"promql\": \"histogram_quantile(0.99, sum(rate(tidb_tikvclient_batch_executor_token_wait_duration_bucket{@LABEL}[1m]))by(le))\",\n            \"promMetric\": \"tidb_tikvclient_batch_executor_token_wait_duration\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    },\n    {\n      \"class\": \"overview\",\n      \"group\": \"overview\",\n      \"type\": \"cluster_top\",\n      \"order\": 6,\n      \"displayName\": \"Connection Count\",\n      \"name\": \"overview_connection_count\",\n      \"description\": \"Total number of active connections to the cluster\",\n      \"metric\": {\n        \"name\": \"overview_connection_count\",\n        \"unit\": \"short\",\n        \"description\": \"Total number of active connections to the cluster\",\n        \"minTidbVersion\": \"\",\n        \"maxTidbVersion\": \"\",\n        \"isBuiltin\": true,\n        \"expressions\": [\n          {\n            \"name\": \"connection_count_sum\",\n            \"promql\": \"sum(tidb_server_connections{@LABEL})\",\n            \"promMetric\": \"tidb_server_connections\",\n            \"labels\": [\n              \"instance\"\n            ],\n            \"type\": \"tidb\",\n            \"legend\": \"{instance}\",\n            \"minTidbVersion\": \"\",\n            \"maxTidbVersion\": \"\"\n          }\n        ]\n      }\n    }\n  ]\n}"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/sample-res/metrics-data-cpu-usage.json",
    "content": "{\n  \"status\": \"success\",\n  \"data\": [\n    {\n      \"expr\": \"irate(process_cpu_seconds_total{ job=\\\"tidb\\\",}[1m])\",\n      \"legend\": \"{instance}\",\n      \"result\": [\n        {\n          \"metric\": {\n            \"instance\": \"tidb-810cad05/172.18.3.6:10080\",\n            \"sqlType\": \"\",\n            \"type\": \"\",\n            \"result\": \"\",\n            \"txnMode\": \"\",\n            \"job\": \"tidb\",\n            \"device\": \"\",\n            \"fstype\": \"\",\n            \"mountpoint\": \"\",\n            \"module\": \"\",\n            \"kind\": \"\",\n            \"ping\": \"\"\n          },\n          \"values\": [\n            {\n              \"timestamp\": 1734188847,\n              \"value\": \"0.03933333333358557\"\n            },\n            {\n              \"timestamp\": 1734188997,\n              \"value\": \"0.04266666666662786\"\n            },\n            {\n              \"timestamp\": 1734189147,\n              \"value\": \"0.022666666666433837\"\n            },\n            {\n              \"timestamp\": 1734189297,\n              \"value\": \"0.04733333333327513\"\n            },\n            {\n              \"timestamp\": 1734189447,\n              \"value\": \"0.0226666666669189\"\n            },\n            {\n              \"timestamp\": 1734189597,\n              \"value\": \"0.05\"\n            },\n            {\n              \"timestamp\": 1734189747,\n              \"value\": \"0.04333333333343035\"\n            },\n            {\n              \"timestamp\": 1734189897,\n              \"value\": \"0.04600000000015522\"\n            },\n            {\n              \"timestamp\": 1734190047,\n              \"value\": \"0.016666666666666666\"\n            },\n            {\n              \"timestamp\": 1734190197,\n              \"value\": \"0.01933333333339154\"\n            },\n            {\n              \"timestamp\": 1734190347,\n              \"value\": \"0.018666666666589057\"\n            },\n            {\n              \"timestamp\": 1734190497,\n              \"value\": \"0.018666666666589057\"\n            },\n            {\n              \"timestamp\": 1734190647,\n              \"value\": \"0.015999999999864182\"\n            },\n            {\n              \"timestamp\": 1734190797,\n              \"value\": \"0.01933333333339154\"\n            },\n            {\n              \"timestamp\": 1734190947,\n              \"value\": \"0.017333333332984088\"\n            },\n            {\n              \"timestamp\": 1734191097,\n              \"value\": \"0.020000000000194026\"\n            },\n            {\n              \"timestamp\": 1734191247,\n              \"value\": \"0.016666666666666666\"\n            },\n            {\n              \"timestamp\": 1734191397,\n              \"value\": \"0.02133333333331393\"\n            },\n            {\n              \"timestamp\": 1734191547,\n              \"value\": \"0.018666666666589057\"\n            },\n            {\n              \"timestamp\": 1734191697,\n              \"value\": \"0.027333333333566166\"\n            },\n            {\n              \"timestamp\": 1734191847,\n              \"value\": \"0.016666666666666666\"\n            },\n            {\n              \"timestamp\": 1734191997,\n              \"value\": \"0.019999999999708962\"\n            },\n            {\n              \"timestamp\": 1734192147,\n              \"value\": \"0.017335644752284392\"\n            },\n            {\n              \"timestamp\": 1734192297,\n              \"value\": \"0.01933333333339154\"\n            },\n            {\n              \"timestamp\": 1734192447,\n              \"value\": \"0.01733333333346915\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"expr\": \"irate(process_cpu_seconds_total{ job=\\\"tidb\\\",}[1m])\",\n      \"legend\": \"{instance}\",\n      \"result\": [\n        {\n          \"metric\": {\n            \"instance\": \"tidb-810cad05/172.18.3.8:10080\",\n            \"sqlType\": \"\",\n            \"type\": \"\",\n            \"result\": \"\",\n            \"txnMode\": \"\",\n            \"job\": \"tidb\",\n            \"device\": \"\",\n            \"fstype\": \"\",\n            \"mountpoint\": \"\",\n            \"module\": \"\",\n            \"kind\": \"\",\n            \"ping\": \"\"\n          },\n          \"values\": [\n            {\n              \"timestamp\": 1734188847,\n              \"value\": \"0.012666666666336823\"\n            },\n            {\n              \"timestamp\": 1734188997,\n              \"value\": \"0.013999999999941792\"\n            },\n            {\n              \"timestamp\": 1734189147,\n              \"value\": \"0.011333333333216919\"\n            },\n            {\n              \"timestamp\": 1734189297,\n              \"value\": \"0.013333333333139307\"\n            },\n            {\n              \"timestamp\": 1734189447,\n              \"value\": \"0.028666666666686068\"\n            },\n            {\n              \"timestamp\": 1734189597,\n              \"value\": \"0.014666666666744276\"\n            },\n            {\n              \"timestamp\": 1734189747,\n              \"value\": \"0.012000000000019403\"\n            },\n            {\n              \"timestamp\": 1734189897,\n              \"value\": \"0.014666666666744276\"\n            },\n            {\n              \"timestamp\": 1734190047,\n              \"value\": \"0.012666666666821888\"\n            },\n            {\n              \"timestamp\": 1734190197,\n              \"value\": \"0.014666666666259213\"\n            },\n            {\n              \"timestamp\": 1734190347,\n              \"value\": \"0.012000000000019403\"\n            },\n            {\n              \"timestamp\": 1734190497,\n              \"value\": \"0.013333333333624372\"\n            },\n            {\n              \"timestamp\": 1734190647,\n              \"value\": \"0.011333333333216919\"\n            },\n            {\n              \"timestamp\": 1734190797,\n              \"value\": \"0.014000000000426857\"\n            },\n            {\n              \"timestamp\": 1734190947,\n              \"value\": \"0.03999999999990299\"\n            },\n            {\n              \"timestamp\": 1734191097,\n              \"value\": \"0.03400000000013582\"\n            },\n            {\n              \"timestamp\": 1734191247,\n              \"value\": \"0.04066666666622041\"\n            },\n            {\n              \"timestamp\": 1734191397,\n              \"value\": \"0.041333333333022894\"\n            },\n            {\n              \"timestamp\": 1734191547,\n              \"value\": \"0.03866666666678308\"\n            },\n            {\n              \"timestamp\": 1734191697,\n              \"value\": \"0.03666666666686069\"\n            },\n            {\n              \"timestamp\": 1734191847,\n              \"value\": \"0.03866666666678308\"\n            },\n            {\n              \"timestamp\": 1734191997,\n              \"value\": \"0.03600000000005821\"\n            },\n            {\n              \"timestamp\": 1734192147,\n              \"value\": \"0.03400000000013582\"\n            },\n            {\n              \"timestamp\": 1734192297,\n              \"value\": \"0.04733333333376019\"\n            },\n            {\n              \"timestamp\": 1734192447,\n              \"value\": \"0.04800000000007761\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"expr\": \"irate(process_cpu_seconds_total{ job=\\\"tidb\\\",}[1m])\",\n      \"legend\": \"{instance}\",\n      \"result\": [\n        {\n          \"metric\": {\n            \"instance\": \"tidb-c1237e04/172.18.3.4:10080\",\n            \"sqlType\": \"\",\n            \"type\": \"\",\n            \"result\": \"\",\n            \"txnMode\": \"\",\n            \"job\": \"tidb\",\n            \"device\": \"\",\n            \"fstype\": \"\",\n            \"mountpoint\": \"\",\n            \"module\": \"\",\n            \"kind\": \"\",\n            \"ping\": \"\"\n          },\n          \"values\": [\n            {\n              \"timestamp\": 1734188847,\n              \"value\": \"0.02133333333331393\"\n            },\n            {\n              \"timestamp\": 1734188997,\n              \"value\": \"0.031999999999728364\"\n            },\n            {\n              \"timestamp\": 1734189147,\n              \"value\": \"0.020666666666511447\"\n            },\n            {\n              \"timestamp\": 1734189297,\n              \"value\": \"0.02333333333323632\"\n            },\n            {\n              \"timestamp\": 1734189447,\n              \"value\": \"0.022000000000116416\"\n            },\n            {\n              \"timestamp\": 1734189597,\n              \"value\": \"0.030666666666608458\"\n            },\n            {\n              \"timestamp\": 1734189747,\n              \"value\": \"0.021999999999631353\"\n            },\n            {\n              \"timestamp\": 1734189897,\n              \"value\": \"0.02333333333323632\"\n            },\n            {\n              \"timestamp\": 1734190047,\n              \"value\": \"0.021999999999631353\"\n            },\n            {\n              \"timestamp\": 1734190197,\n              \"value\": \"0.029333333333488552\"\n            },\n            {\n              \"timestamp\": 1734190347,\n              \"value\": \"0.02066666666699651\"\n            },\n            {\n              \"timestamp\": 1734190497,\n              \"value\": \"0.022666666666433837\"\n            },\n            {\n              \"timestamp\": 1734190647,\n              \"value\": \"0.02133333333331393\"\n            },\n            {\n              \"timestamp\": 1734190797,\n              \"value\": \"0.03133333333341094\"\n            },\n            {\n              \"timestamp\": 1734190947,\n              \"value\": \"0.02066666666699651\"\n            },\n            {\n              \"timestamp\": 1734191097,\n              \"value\": \"0.0226666666669189\"\n            },\n            {\n              \"timestamp\": 1734191247,\n              \"value\": \"0.02333333333323632\"\n            },\n            {\n              \"timestamp\": 1734191397,\n              \"value\": \"0.030000000000291037\"\n            },\n            {\n              \"timestamp\": 1734191547,\n              \"value\": \"0.01533333333354676\"\n            },\n            {\n              \"timestamp\": 1734191697,\n              \"value\": \"0.016666666666666666\"\n            },\n            {\n              \"timestamp\": 1734191847,\n              \"value\": \"0.015999999999864182\"\n            },\n            {\n              \"timestamp\": 1734191997,\n              \"value\": \"0.02466666666684129\"\n            },\n            {\n              \"timestamp\": 1734192147,\n              \"value\": \"0.015333333333061697\"\n            },\n            {\n              \"timestamp\": 1734192297,\n              \"value\": \"0.018000000000271636\"\n            },\n            {\n              \"timestamp\": 1734192447,\n              \"value\": \"0.015999999999864182\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"expr\": \"irate(process_cpu_seconds_total{ job=\\\"tidb\\\",}[1m])\",\n      \"legend\": \"{instance}\",\n      \"result\": [\n        {\n          \"metric\": {\n            \"instance\": \"tidb-810cad05/172.18.3.7:10080\",\n            \"sqlType\": \"\",\n            \"type\": \"\",\n            \"result\": \"\",\n            \"txnMode\": \"\",\n            \"job\": \"tidb\",\n            \"device\": \"\",\n            \"fstype\": \"\",\n            \"mountpoint\": \"\",\n            \"module\": \"\",\n            \"kind\": \"\",\n            \"ping\": \"\"\n          },\n          \"values\": [\n            {\n              \"timestamp\": 1734188847,\n              \"value\": \"0.012000000000019403\"\n            },\n            {\n              \"timestamp\": 1734188997,\n              \"value\": \"0.01533333333354676\"\n            },\n            {\n              \"timestamp\": 1734189147,\n              \"value\": \"0.012666666666821888\"\n            },\n            {\n              \"timestamp\": 1734189297,\n              \"value\": \"0.013999999999941792\"\n            },\n            {\n              \"timestamp\": 1734189447,\n              \"value\": \"0.011333333333216919\"\n            },\n            {\n              \"timestamp\": 1734189597,\n              \"value\": \"0.014666666666744276\"\n            },\n            {\n              \"timestamp\": 1734189747,\n              \"value\": \"0.012000000000019403\"\n            },\n            {\n              \"timestamp\": 1734189897,\n              \"value\": \"0.013999999999941792\"\n            },\n            {\n              \"timestamp\": 1734190047,\n              \"value\": \"0.025333333333158712\"\n            },\n            {\n              \"timestamp\": 1734190197,\n              \"value\": \"0.029333333333488552\"\n            },\n            {\n              \"timestamp\": 1734190347,\n              \"value\": \"0.011333333333701982\"\n            },\n            {\n              \"timestamp\": 1734190497,\n              \"value\": \"0.013999999999941792\"\n            },\n            {\n              \"timestamp\": 1734190647,\n              \"value\": \"0.012000000000019403\"\n            },\n            {\n              \"timestamp\": 1734190797,\n              \"value\": \"0.01533333333354676\"\n            },\n            {\n              \"timestamp\": 1734190947,\n              \"value\": \"0.012000000000019403\"\n            },\n            {\n              \"timestamp\": 1734191097,\n              \"value\": \"0.014666666666744276\"\n            },\n            {\n              \"timestamp\": 1734191247,\n              \"value\": \"0.012000000000019403\"\n            },\n            {\n              \"timestamp\": 1734191397,\n              \"value\": \"0.03866666666678308\"\n            },\n            {\n              \"timestamp\": 1734191547,\n              \"value\": \"0.03400000000013582\"\n            },\n            {\n              \"timestamp\": 1734191697,\n              \"value\": \"0.04133333333350796\"\n            },\n            {\n              \"timestamp\": 1734191847,\n              \"value\": \"0.0346666666669383\"\n            },\n            {\n              \"timestamp\": 1734191997,\n              \"value\": \"0.03666666666637563\"\n            },\n            {\n              \"timestamp\": 1734192147,\n              \"value\": \"0.029999999999805974\"\n            },\n            {\n              \"timestamp\": 1734192297,\n              \"value\": \"0.03133333333292588\"\n            },\n            {\n              \"timestamp\": 1734192447,\n              \"value\": \"0.03933333333358557\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"expr\": \"irate(process_cpu_seconds_total{ job=\\\"tidb\\\",}[1m])\",\n      \"legend\": \"{instance}\",\n      \"result\": [\n        {\n          \"metric\": {\n            \"instance\": \"tidb-2bd7c6e1/172.18.3.5:10080\",\n            \"sqlType\": \"\",\n            \"type\": \"\",\n            \"result\": \"\",\n            \"txnMode\": \"\",\n            \"job\": \"tidb\",\n            \"device\": \"\",\n            \"fstype\": \"\",\n            \"mountpoint\": \"\",\n            \"module\": \"\",\n            \"kind\": \"\",\n            \"ping\": \"\"\n          },\n          \"values\": [\n            {\n              \"timestamp\": 1734188847,\n              \"value\": \"0.017999999999786572\"\n            },\n            {\n              \"timestamp\": 1734188997,\n              \"value\": \"0.014666666666744276\"\n            },\n            {\n              \"timestamp\": 1734189147,\n              \"value\": \"0.017333333332984088\"\n            },\n            {\n              \"timestamp\": 1734189297,\n              \"value\": \"0.014666666666259213\"\n            },\n            {\n              \"timestamp\": 1734189447,\n              \"value\": \"0.017999999999786572\"\n            },\n            {\n              \"timestamp\": 1734189597,\n              \"value\": \"0.014666666666744276\"\n            },\n            {\n              \"timestamp\": 1734189747,\n              \"value\": \"0.016666666666666666\"\n            },\n            {\n              \"timestamp\": 1734189897,\n              \"value\": \"0.01533333333354676\"\n            },\n            {\n              \"timestamp\": 1734190047,\n              \"value\": \"0.017999999999786572\"\n            },\n            {\n              \"timestamp\": 1734190197,\n              \"value\": \"0.013999999999941792\"\n            },\n            {\n              \"timestamp\": 1734190347,\n              \"value\": \"0.018000000000271636\"\n            },\n            {\n              \"timestamp\": 1734190497,\n              \"value\": \"0.014666666666744276\"\n            },\n            {\n              \"timestamp\": 1734190647,\n              \"value\": \"0.03733333333317811\"\n            },\n            {\n              \"timestamp\": 1734190797,\n              \"value\": \"0.01533333333354676\"\n            },\n            {\n              \"timestamp\": 1734190947,\n              \"value\": \"0.01733333333346915\"\n            },\n            {\n              \"timestamp\": 1734191097,\n              \"value\": \"0.015333333333061697\"\n            },\n            {\n              \"timestamp\": 1734191247,\n              \"value\": \"0.01866666666707412\"\n            },\n            {\n              \"timestamp\": 1734191397,\n              \"value\": \"0.015999999999864182\"\n            },\n            {\n              \"timestamp\": 1734191547,\n              \"value\": \"0.01933333333339154\"\n            },\n            {\n              \"timestamp\": 1734191697,\n              \"value\": \"0.01533333333354676\"\n            },\n            {\n              \"timestamp\": 1734191847,\n              \"value\": \"0.017999999999786572\"\n            },\n            {\n              \"timestamp\": 1734191997,\n              \"value\": \"0.014666666666744276\"\n            },\n            {\n              \"timestamp\": 1734192147,\n              \"value\": \"0.018000000000271636\"\n            },\n            {\n              \"timestamp\": 1734192297,\n              \"value\": \"0.014666666666744276\"\n            },\n            {\n              \"timestamp\": 1734192447,\n              \"value\": \"0.017999999999786572\"\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/sample-res/slow-query-detail.json",
    "content": "{\n  \"digest\": \"877ddf60c6084ae30353cc484f375e5159c231f0d9363213d1d1cc2ffbd272ce\",\n  \"query\": \"SELECT  *,  FIELD(LOWER(A.TYPE), 'tiflash', 'tikv', 'pd', 'tidb') AS _ORDER FROM (  SELECT   TYPE, INSTANCE, DEVICE_TYPE, DEVICE_NAME, JSON_OBJECTAGG(NAME, VALUE) AS JSON_VALUE  FROM   INFORMATION_SCHEMA.CLUSTER_LOAD  WHERE   DEVICE_TYPE IN (?,?)  GROUP BY TYPE, INSTANCE, DEVICE_TYPE, DEVICE_NAME ) AS A ORDER BY  _ORDER DESC, INSTANCE, DEVICE_TYPE, DEVICE_NAME [arguments: (\\\"memory\\\", \\\"cpu\\\")];\",\n  \"instance\": \"127.0.0.1:10080\",\n  \"db\": \"\",\n  \"connection_id\": \"5414958427354957035\",\n  \"success\": 1,\n  \"timestamp\": 1690272708.823209,\n  \"query_time\": 1.51515176,\n  \"parse_time\": 0,\n  \"compile_time\": 0.00103503,\n  \"rewrite_time\": 0.000332248,\n  \"preproc_subqueries_time\": 0,\n  \"optimize_time\": 0.000354698,\n  \"wait_ts\": 0,\n  \"cop_time\": 0,\n  \"lock_keys_time\": 0,\n  \"write_sql_response_total\": 0.000068678,\n  \"exec_retry_time\": 0,\n  \"memory_max\": 220680,\n  \"disk_max\": 0,\n  \"txn_start_ts\": \"0\",\n  \"prev_stmt\": \"\",\n  \"plan\": \"\\tid                     \\ttask\\testRows\\toperator info                                                                                                                                                                                                                                      \\tactRows\\texecution info                                                                                                                                                                                                                                                                                                                                                      \\tmemory  \\tdisk\\n\\tSort_7                 \\troot\\t8000   \\tColumn#8:desc, Column#2, Column#3, Column#4                                                                                                                                                                                                        \\t12     \\ttime:1.51s, loops:2                                                                                                                                                                                                                                                                                                                                                 \\t18.4 KB \\t0 Bytes\\n\\t└─Projection_9         \\troot\\t8000   \\tColumn#1, Column#2, Column#3, Column#4, Column#7, field(lower(Column#1), tiflash, tikv, pd, tidb)-\\u003eColumn#8                                                                                                                                        \\t12     \\ttime:1.51s, loops:6, Concurrency:5                                                                                                                                                                                                                                                                                                                                  \\t33.8 KB \\tN/A\\n\\t  └─HashAgg_10         \\troot\\t8000   \\tgroup by:Column#1, Column#2, Column#3, Column#4, funcs:json_objectagg(Column#5, Column#6)-\\u003eColumn#7, funcs:firstrow(Column#1)-\\u003eColumn#1, funcs:firstrow(Column#2)-\\u003eColumn#2, funcs:firstrow(Column#3)-\\u003eColumn#3, funcs:firstrow(Column#4)-\\u003eColumn#4\\t12     \\ttime:1.51s, loops:6, partial_worker:{wall_time:1.513240352s, concurrency:5, task_num:1, tot_wait:7.565194284s, tot_exec:149.691µs, tot_time:7.56536502s, max:1.513153823s, p95:1.513153823s}, final_worker:{wall_time:1.5133523s, concurrency:5, task_num:5, tot_wait:7.566142195s, tot_exec:223.043µs, tot_time:7.566371614s, max:1.513316486s, p95:1.513316486s}\\t47.4 KB \\tN/A\\n\\t    └─Selection_11     \\troot\\t8000   \\tin(Column#3, \\\"memory\\\", \\\"cpu\\\")                                                                                                                                                                                                                      \\t69     \\ttime:1.51s, loops:2                                                                                                                                                                                                                                                                                                                                                 \\t135.2 KB\\tN/A\\n\\t      └─MemTableScan_12\\troot\\t10000  \\ttable:CLUSTER_LOAD                                                                                                                                                                                                                                 \\t1193   \\ttime:1.51s, loops:3                                                                                                                                                                                                                                                                                                                                                 \\tN/A     \\tN/A\",\n  \"binary_plan\": \"{\\\"discardedDueToTooLong\\\":false,\\\"main\\\":{\\\"accessObjects\\\":[],\\\"actRows\\\":12,\\\"children\\\":[{\\\"accessObjects\\\":[],\\\"actRows\\\":12,\\\"children\\\":[{\\\"accessObjects\\\":[],\\\"actRows\\\":12,\\\"children\\\":[{\\\"accessObjects\\\":[],\\\"actRows\\\":69,\\\"children\\\":[{\\\"accessObjects\\\":[{\\\"scanObject\\\":{\\\"database\\\":\\\"INFORMATION_SCHEMA\\\",\\\"table\\\":\\\"CLUSTER_LOAD\\\"}}],\\\"actRows\\\":1193,\\\"copExecInfo\\\":{},\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":10000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"N/A\\\",\\\"name\\\":\\\"MemTableScan_12\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"3\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":499000,\\\"diagnosis\\\":[\\\"high_est_error\\\"],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":8000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"138400\\\",\\\"name\\\":\\\"Selection_11\\\",\\\"operatorInfo\\\":\\\"in(Column#3, \\\\\\\"memory\\\\\\\", \\\\\\\"cpu\\\\\\\")\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"2\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":1772970.6,\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":8000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"48580\\\",\\\"name\\\":\\\"HashAgg_10\\\",\\\"operatorInfo\\\":\\\"group by:Column#1, Column#2, Column#3, Column#4, funcs:json_objectagg(Column#5, Column#6)-\\\\u003eColumn#7, funcs:firstrow(Column#1)-\\\\u003eColumn#1, funcs:firstrow(Column#2)-\\\\u003eColumn#2, funcs:firstrow(Column#3)-\\\\u003eColumn#3, funcs:firstrow(Column#4)-\\\\u003eColumn#4\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"6\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[{\\\"final_worker\\\":{\\\"concurrency\\\":\\\"5\\\",\\\"max\\\":\\\"1.513316486s\\\",\\\"p95\\\":\\\"1.513316486s\\\",\\\"task_num\\\":\\\"5\\\",\\\"tot_exec\\\":\\\"223.043µs\\\",\\\"tot_time\\\":\\\"7.566371614s\\\",\\\"tot_wait\\\":\\\"7.566142195s\\\",\\\"wall_time\\\":\\\"1.5133523s\\\"},\\\"partial_worker\\\":{\\\"concurrency\\\":\\\"5\\\",\\\"max\\\":\\\"1.513153823s\\\",\\\"p95\\\":\\\"1.513153823s\\\",\\\"task_num\\\":\\\"1\\\",\\\"tot_exec\\\":\\\"149.691µs\\\",\\\"tot_time\\\":\\\"7.56536502s\\\",\\\"tot_wait\\\":\\\"7.565194284s\\\",\\\"wall_time\\\":\\\"1.513240352s\\\"}}],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":1856802.6,\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":8000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"34596\\\",\\\"name\\\":\\\"Projection_9\\\",\\\"operatorInfo\\\":\\\"Column#1, Column#2, Column#3, Column#4, Column#7, field(lower(Column#1), tiflash, tikv, pd, tidb)-\\\\u003eColumn#8\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"6\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[{\\\"Concurrency\\\":\\\"5\\\"}],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":7403943.686437106,\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":8000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"18792\\\",\\\"name\\\":\\\"Sort_7\\\",\\\"operatorInfo\\\":\\\"Column#8:desc, Column#2, Column#3, Column#4\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"2\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"},\\\"withRuntimeStats\\\":true}\",\n  \"binary_plan_text\": \"\\n| id                      | estRows  | estCost    | actRows | task | access object      | execution info                                                                                                                                                                                                                                                                                                                                                     | operator info                                                                                                                                                                                                                                       | memory   | disk     |\\n| Sort_7                  | 8000.00  | 7403943.69 | 12      | root |                    | time:1.51s, loops:2                                                                                                                                                                                                                                                                                                                                                | Column#8:desc, Column#2, Column#3, Column#4                                                                                                                                                                                                         | 18.4 KB  | 0 Bytes  |\\n| └─Projection_9          | 8000.00  | 1856802.60 | 12      | root |                    | time:1.51s, loops:6, Concurrency:5                                                                                                                                                                                                                                                                                                                                 | Column#1, Column#2, Column#3, Column#4, Column#7, field(lower(Column#1), tiflash, tikv, pd, tidb)-\\u003eColumn#8                                                                                                                                         | 33.8 KB  | N/A      |\\n|   └─HashAgg_10          | 8000.00  | 1772970.60 | 12      | root |                    | time:1.51s, loops:6, partial_worker:{wall_time:1.513240352s, concurrency:5, task_num:1, tot_wait:7.565194284s, tot_exec:149.691µs, tot_time:7.56536502s, max:1.513153823s, p95:1.513153823s}, final_worker:{wall_time:1.5133523s, concurrency:5, task_num:5, tot_wait:7.566142195s, tot_exec:223.043µs, tot_time:7.566371614s, max:1.513316486s, p95:1.513316486s} | group by:Column#1, Column#2, Column#3, Column#4, funcs:json_objectagg(Column#5, Column#6)-\\u003eColumn#7, funcs:firstrow(Column#1)-\\u003eColumn#1, funcs:firstrow(Column#2)-\\u003eColumn#2, funcs:firstrow(Column#3)-\\u003eColumn#3, funcs:firstrow(Column#4)-\\u003eColumn#4 | 47.4 KB  | N/A      |\\n|     └─Selection_11      | 8000.00  | 499000.00  | 69      | root |                    | time:1.51s, loops:2                                                                                                                                                                                                                                                                                                                                                | in(Column#3, \\\"memory\\\", \\\"cpu\\\")                                                                                                                                                                                                                       | 135.2 KB | N/A      |\\n|       └─MemTableScan_12 | 10000.00 | 0.00       | 1193    | root | table:CLUSTER_LOAD | time:1.51s, loops:3                                                                                                                                                                                                                                                                                                                                                |                                                                                                                                                                                                                                                     | N/A      | N/A      |\\n\",\n  \"warnings\": [\n    {\n      \"Level\": \"Warning\",\n      \"Message\": \"skip prepared plan-cache: PhysicalMemTable plan is un-cacheable\"\n    }\n  ],\n  \"is_internal\": 0,\n  \"index_names\": \"\",\n  \"stats\": \"\",\n  \"backoff_types\": \"\",\n  \"prepared\": 1,\n  \"plan_from_cache\": 0,\n  \"plan_from_binding\": 0,\n  \"user\": \"root\",\n  \"host\": \"127.0.0.1\",\n  \"process_time\": 0,\n  \"wait_time\": 0,\n  \"backoff_time\": 0,\n  \"get_commit_ts_time\": 0,\n  \"local_latch_wait_time\": 0,\n  \"resolve_lock_time\": 0,\n  \"prewrite_time\": 0,\n  \"wait_prewrite_binlog_time\": 0,\n  \"commit_time\": 0,\n  \"commit_backoff_time\": 0,\n  \"cop_proc_avg\": 0,\n  \"cop_proc_p90\": 0,\n  \"cop_proc_max\": 0,\n  \"cop_wait_avg\": 0,\n  \"cop_wait_p90\": 0,\n  \"cop_wait_max\": 0,\n  \"write_keys\": 0,\n  \"write_size\": 0,\n  \"prewrite_region\": 0,\n  \"txn_retry\": 0,\n  \"request_count\": 0,\n  \"process_keys\": 0,\n  \"total_keys\": 0,\n  \"cop_proc_addr\": \"\",\n  \"cop_wait_addr\": \"\",\n  \"rocksdb_delete_skipped_count\": 0,\n  \"rocksdb_key_skipped_count\": 0,\n  \"rocksdb_block_cache_hit_count\": 0,\n  \"rocksdb_block_read_count\": 0,\n  \"rocksdb_block_read_byte\": 0\n}\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/sample-res/slow-query-list.json",
    "content": "{\n  \"data\": [\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568085\",\n      \"success\": 0,\n      \"timestamp\": 1731395083.834432,\n      \"query_time\": 42.813718709,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567971\",\n      \"success\": 0,\n      \"timestamp\": 1731395676.372656,\n      \"query_time\": 36.06388146,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567961\",\n      \"success\": 0,\n      \"timestamp\": 1731395492.146849,\n      \"query_time\": 31.88501126,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567971\",\n      \"success\": 0,\n      \"timestamp\": 1731395911.519522,\n      \"query_time\": 31.13266307,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567971\",\n      \"success\": 0,\n      \"timestamp\": 1731396090.233227,\n      \"query_time\": 29.793699554,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"36ceb598d0e5d79b1d82c4d5e3033d3180ae92a2c65fb6560a7fb0f7887c7a4f\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`users`.`last_modified_date`) FROM `stackoverflow`.`users`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567965\",\n      \"success\": 0,\n      \"timestamp\": 1731394720.186855,\n      \"query_time\": 29.674765804,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 1335,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567969\",\n      \"success\": 0,\n      \"timestamp\": 1731394886.522594,\n      \"query_time\": 25.564702573,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567969\",\n      \"success\": 0,\n      \"timestamp\": 1731395125.504682,\n      \"query_time\": 25.427689653,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567971\",\n      \"success\": 0,\n      \"timestamp\": 1731394705.139473,\n      \"query_time\": 24.193893241,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n      \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567975\",\n      \"success\": 0,\n      \"timestamp\": 1731395453.978445,\n      \"query_time\": 23.970207449,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"69667039b51ac90afcda120edbbd61cb1f5db69fdde9314d56a9a94c027074c2\",\n      \"query\": \"INSERT INTO `github_events` (`repo_id`,`repo_name`,`language`,`actor_id`,`actor_login`,`additions`,`deletions`,`action`,`commit_id`,`number`,`org_id`,`org_login`,`pr_merged`,`state`,`pr_merged_at`,`closed_at`,`comments`,`pr_or_issue_id`,`pr_changed_files`,`pr_review_comments`,`event_day`,`event_month`,`event_year`,`push_size`,`push_distinct_size`,`id`,`type`,`created_at`,`pr_or_issue_created_at`,`creator_user_id`,`creator_user_login`) VALUES (841416687, 'DigistoreAdmin/backend_v2', '', 120089549, 'Fibin-kummil', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 4, 1, 43726827610, 'PushEvent', '2024-11-12 06:00:00', '1970-01-01 00:00:00', 0, ''), (882610803, 'domi00248/upload2', '', 172067999, 'domi00248', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43726827623, 'PushEvent', '2024-11-12 06:00:00', '1970-01-01 00:00:00', 0, ''), (730591456, 'RekhaAparna01/openpower-vpd-parser', '', 139200172, 'RekhaAparna01', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 0, 0, 43726827625, 'DeleteEvent', '2024-11-12 06:00:00', '1970-01-01 00:00:00', 0, ''), (813161388, 'husnapupita/WedusKripto18', '', 41898282, 'github-actions[bot]', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43726827627, 'PushEvent', '2024-11-12 06:00:00', '1970-01-01 00:00:00', 0, ''), (885612399, 'nutters1000/tsto-mayhemids-5', '', 41898282, 'github-actions[bot]', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43726827648, 'PushEvent', '2024-11-12 06:00:00', '1970-01-01 00:00:00', 0, ''), (718389500, 'yuuk-64-gi-0/unofficial_LiveSchedule_Data', '', 83411350, 'yuuk-64-gi-0', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43726827652, 'PushEvent', '2024-11-12 06:00:00', '1970-01-01 00:00:00', 0, ''), (652340321, 'morgenm/basicgopot', '', 65553080, 'codecov-commenter', 0, 0, 'created', '', 59, 0, '', FALSE, 'open', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 1, 2651133191, 0, 0, '2024-11-12', '2024-11-01', 2024, 0, 0, 43726827662, 'IssueCommentEvent', '2024-11-12 06:00:00', '2024-11-12 05:58:54', 65553080, 'codecov-commenter'), (851017538, 'muazhari/dti-se', '', 39398937, 'muazhari', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43726827690, 'PushEvent', '2024-11-12 06:00:00', '1970-01-01 00:00:00', 0, ''), (818342606, 'wusiuyu/hknews', '', 23726356, 'wusiuyu', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43726827697, 'PushEvent', '2024-11-12 06:00:00', '1970-01-01 00:00:00', 0, ''), (863361087, 'Happy-pixelpulse/remdy', '', 152676310, 'Happy-pixelpulse', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43726827702, 'PushEvent', '2024-11-12 06:00:00', '1970-01-01 00:00:00', 0, ''), (878375686, 'kysports-cn/kytiyuhui', '', 41898282, 'github-actions[bot]', 0, 0, 'opened', '', 87154, 0, '', FALSE, 'open', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 2651134931, 0, 0, '2024-11-12', '2024-11-01', 2024, 0, 0, 43726827711, 'IssuesEvent', '2024-11-12 06:00:00', '2024-11-12 05:59:59', 41898282, 'github-actions[bot]'), (880063275, 'matthewlu2/plot_spatial', '', 135487014, 'matthewlu2', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43726827713, 'PushEvent', '2024-11-12 06:00:00', '1970-01-01 00:00:00', 0, ''), (887073784, 'erwinfauzy/erwinfauzy', '', 148672470, 'erwinfauzy', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-0(len:26915689);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396162.887493,\n      \"query_time\": 23.02075085,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 492977264,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"14409372a64bb05b14c68b644eaf2e7559cde6adee1f0208b8e38cf27d0bc4ac\",\n      \"query\": \"INSERT INTO `github_events` (`repo_id`,`repo_name`,`language`,`actor_id`,`actor_login`,`additions`,`deletions`,`action`,`commit_id`,`number`,`org_id`,`org_login`,`pr_merged`,`state`,`pr_merged_at`,`closed_at`,`comments`,`pr_or_issue_id`,`pr_changed_files`,`pr_review_comments`,`event_day`,`event_month`,`event_year`,`push_size`,`push_distinct_size`,`id`,`type`,`created_at`,`pr_or_issue_created_at`,`creator_user_id`,`creator_user_login`) VALUES (859113880, 'buituandev/FashionWebProject', '', 147406431, 'buituandev', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727388870, 'PushEvent', '2024-11-12 06:24:54', '1970-01-01 00:00:00', 0, ''), (373797896, 'Johnsondhoni07/Johnsondhoni07', '', 41898282, 'github-actions[bot]', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727388882, 'PushEvent', '2024-11-12 06:24:54', '1970-01-01 00:00:00', 0, ''), (575019540, 'cpp-johnny/cpp-johnny', '', 41898282, 'github-actions[bot]', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727388885, 'PushEvent', '2024-11-12 06:24:54', '1970-01-01 00:00:00', 0, ''), (457470784, 'sebast759/sebast759.github.io', '', 41928138, 'sebast759', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727388888, 'PushEvent', '2024-11-12 06:24:54', '1970-01-01 00:00:00', 0, ''), (684869383, '37xxxphillipsce/one_green', '', 41898282, 'github-actions[bot]', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727388895, 'PushEvent', '2024-11-12 06:24:54', '1970-01-01 00:00:00', 0, ''), (677255002, 'dgfdacd/dgfdacd', '', 41898282, 'github-actions[bot]', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727388904, 'PushEvent', '2024-11-12 06:24:54', '1970-01-01 00:00:00', 0, ''), (167107390, 'lets-mica/mica', '', 30398684, 'yu-wade', 0, 0, '', '', 0, 46373530, 'lets-mica', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 0, 0, 43727388909, 'ForkEvent', '2024-11-12 06:24:54', '1970-01-01 00:00:00', 0, ''), (887067611, 'gyu-iin/CreatAi', '', 188141647, 'gyu-iin', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727388917, 'PushEvent', '2024-11-12 06:24:54', '1970-01-01 00:00:00', 0, ''), (381137107, 'The-Forbidden-Trove/character_name_blacklist', '', 75438007, 'Jzir', 0, 0, '', '', 0, 74618880, 'The-Forbidden-Trove', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727388922, 'PushEvent', '2024-11-12 06:24:54', '1970-01-01 00:00:00', 0, ''), (811116430, 'Tunan81/weread2notion-pro', '', 41898282, 'github-actions[bot]', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727388936, 'PushEvent', '2024-11-12 06:24:54', '1970-01-01 00:00:00', 0, ''), (778600850, 'mattapong/lava-rpc', '', 41898282, 'github-actions[bot]', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727388951, 'PushEvent', '2024-11-12 06:24:54', '1970-01-01 00:00:00', 0, ''), (205775816, 'madoodia/Configs', '', 3639157, 'madoodia', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727388953, 'PushEvent', '2024-11-12 06:24:54', '1970-01-01 00:00:00', 0, ''), (795909164, 'odczynflnpm/iste-ad-facilis', '', 41898282, 'github-actions[bot]', 0, 0, '', '', 0, 167699334, 'odczynflnpm', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12',(len:27432718);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396210.970711,\n      \"query_time\": 20.506392327,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 493337264,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568085\",\n      \"success\": 0,\n      \"timestamp\": 1731395300.460164,\n      \"query_time\": 20.321525171,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"cc17b10e96dd94b2ec106c9733b6168ec984890773d5cade120f17f6f09ebb4b\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 12 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), cte AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS current_events_total     FROM github_events ) SELECT     current_max_event_time AS record_time,     current_events_total - COALESCE((SELECT last_events_total FROM last_record), 0) AS events_increment,     current_events_total AS events_total FROM     cte ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568085\",\n      \"success\": 0,\n      \"timestamp\": 1731394820.386959,\n      \"query_time\": 19.428609419,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 1,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n      \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567959\",\n      \"success\": 0,\n      \"timestamp\": 1731395929.105154,\n      \"query_time\": 19.096130682,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567961\",\n      \"success\": 0,\n      \"timestamp\": 1731395714.338041,\n      \"query_time\": 13.976654642,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567971\",\n      \"success\": 0,\n      \"timestamp\": 1731395413.97186,\n      \"query_time\": 13.805915836,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567961\",\n      \"success\": 0,\n      \"timestamp\": 1731394994.788962,\n      \"query_time\": 13.803781269,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567971\",\n      \"success\": 0,\n      \"timestamp\": 1731394934.205035,\n      \"query_time\": 13.245276475,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567969\",\n      \"success\": 0,\n      \"timestamp\": 1731395353.12341,\n      \"query_time\": 12.953681993,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568085\",\n      \"success\": 0,\n      \"timestamp\": 1731395772.752351,\n      \"query_time\": 12.404848074,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567969\",\n      \"success\": 0,\n      \"timestamp\": 1731395832.209703,\n      \"query_time\": 11.699750477,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n      \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567975\",\n      \"success\": 0,\n      \"timestamp\": 1731395321.654226,\n      \"query_time\": 11.645562838,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"02bcfcb6b50a132f26b2722e9b822039ddd7eed8257c9f8cf8c6a3a9b5572fe3\",\n      \"query\": \"WITH repos_with_prs_24h AS (     SELECT         /*+ READ_FROM_STORAGE(tiflash[ge]) */         ge.repo_id,         COUNT(DISTINCT IF(action = 'opened', ge.pr_or_issue_id, NULL))                          AS opened_prs,         COUNT(DISTINCT IF(action = 'closed' AND ge.pr_merged = false, ge.pr_or_issue_id, NULL)) AS closed_prs,         COUNT(DISTINCT IF(action = 'closed' AND ge.pr_merged = true, ge.pr_or_issue_id, NULL))  AS merged_prs,         COUNT(*)                                                                                AS total_pr_events,         COUNT(DISTINCT actor_id)                                                                AS developers     FROM         github_events ge     WHERE         ge.type = 'PullRequestEvent'         AND (ge.action = 'opened' OR ge.action = 'closed')         AND ge.additions \\u003e 10         AND ge.created_at \\u003e DATE_SUB(NOW(), INTERVAL 1 DAY)         AND ge.repo_name NOT IN(             'NixOS/nixpkgs',             'firstcontributions/first-contributions',             'Homebrew/homebrew-cask',             'microsoft/winget-pkgs',             'microsoft/vcpkg'         )         AND ge.actor_login NOT IN(             'robodoo',             'r-ryantm',             'scala-steward'         )         AND LOWER(ge.actor_login) NOT REGEXP '^(bot-.+|.+bot|.+\\\\\\\\[bot\\\\\\\\]|.*-bot-.*|robot-.+|.+-ci-.+|.+-ci|.+-testing|.*clabot.*|.+-gerrit|k8s-.+|.+-machine|.+-automation|github-.+|.+-github|.+-service|.+-builds|codecov-.+|.*teamcity.*|jenkins-.+|.+-jira-.+|witness.+|.+witness|signcla.+|.+signcla|.+-cicd-.+|.*autotester.*)$'     GROUP BY         ge.repo_id     HAVING developers \\u003e 5     ORDER BY total_pr_events DESC     LIMIT 100 ) SELECT     gr.repo_id, gr.repo_name,     developers, total_pr_events,     opened_prs, closed_prs, merged_prs FROM repos_with_prs_24h r JOIN github_repos gr ON r.repo_id = gr.repo_id WHERE gr.stars \\u003e 100 ORDER BY total_pr_events DESC LIMIT 5;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567959\",\n      \"success\": 0,\n      \"timestamp\": 1731394941.60428,\n      \"query_time\": 11.59451965,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 191124,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 100,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n      \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568043\",\n      \"success\": 0,\n      \"timestamp\": 1731396040.969341,\n      \"query_time\": 10.959927937,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"8df59f50fb66c4473c5a484fc95287cd5776718a8ad2f2d22b673a8700277569\",\n      \"query\": \"WITH developers_with_prs_24h AS (     SELECT         /*+ READ_FROM_STORAGE(tiflash[ge]) */         ge.actor_id,         COUNT(DISTINCT CASE WHEN action = 'opened' THEN ge.pr_or_issue_id END) AS opened_prs,         COUNT(DISTINCT CASE WHEN action = 'closed' AND ge.pr_merged = false THEN ge.pr_or_issue_id END) AS closed_prs,         COUNT(DISTINCT CASE WHEN action = 'closed' AND ge.pr_merged = true THEN ge.pr_or_issue_id END) AS merged_prs,         COUNT(*) AS total_pr_events     FROM         github_events ge     WHERE         ge.type = 'PullRequestEvent'         AND (ge.action = 'opened' OR ge.action = 'closed')         AND ge.created_at \\u003e DATE_SUB(NOW(), INTERVAL 1 DAY)         AND ge.actor_login NOT IN (SELECT login FROM blacklist_users)         AND ge.actor_login NOT REGEXP '^(bot-.+|.+bot|.+\\\\\\\\[bot\\\\\\\\]|.+-bot-.+|robot-.+|.+-ci-.+|.+-ci|.+-testing|.+clabot.+|.+-gerrit|k8s-.+|.+-machine|.+-automation|.+-sdk|.+-integration|github-.+|.+-github|.+-service|.+-builds|codecov-.+|.+teamcity.+|jenkins-.+|.+-jira-.+|witness.+|.+witness|signcla.+|.+signcla|.+-cicd-.+|.+autotester.+)$'         AND ge.additions \\u003e 10     GROUP BY         ge.actor_id     ORDER BY total_pr_events DESC ) SELECT     gu.id AS actor_id,     gu.login AS actor_login,     d.total_pr_events,     d.opened_prs,     d.closed_prs,     d.merged_prs FROM developers_with_prs_24h d JOIN github_users gu ON gu.id = d.actor_id WHERE     gu.followers \\u003e 5 ORDER BY total_pr_events DESC LIMIT 5;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568111\",\n      \"success\": 0,\n      \"timestamp\": 1731394880.440971,\n      \"query_time\": 10.411373758,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4608326,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 998,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n      \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568111\",\n      \"success\": 0,\n      \"timestamp\": 1731395560.413131,\n      \"query_time\": 10.404900406,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n      \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567975\",\n      \"success\": 0,\n      \"timestamp\": 1731395679.850997,\n      \"query_time\": 9.842890208,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"fd11911b34dd73778e4ff46ebab8cb132b2f2cc11be66a7296de48055061ac83\",\n      \"query\": \"INSERT INTO `github_events` (`repo_id`,`repo_name`,`language`,`actor_id`,`actor_login`,`additions`,`deletions`,`action`,`commit_id`,`number`,`org_id`,`org_login`,`pr_merged`,`state`,`pr_merged_at`,`closed_at`,`comments`,`pr_or_issue_id`,`pr_changed_files`,`pr_review_comments`,`event_day`,`event_month`,`event_year`,`push_size`,`push_distinct_size`,`id`,`type`,`created_at`,`pr_or_issue_created_at`,`creator_user_id`,`creator_user_login`) VALUES (884180087, 'hthsports-cn/hthtiyuhth', '', 41898282, 'github-actions[bot]', 0, 0, 'opened', '', 33500, 0, '', FALSE, 'open', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 2651222208, 0, 0, '2024-11-12', '2024-11-01', 2024, 0, 0, 43727937790, 'IssuesEvent', '2024-11-12 06:47:50', '2024-11-12 06:47:48', 41898282, 'github-actions[bot]'), (599412601, 'binhle59/playwright-saucedemo.com', '', 92701382, 'binhle59', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727937814, 'PushEvent', '2024-11-12 06:47:50', '1970-01-01 00:00:00', 0, ''), (733377750, 'prepheadrus/A_06', '', 83468174, 'prepheadrus', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727937816, 'PushEvent', '2024-11-12 06:47:50', '1970-01-01 00:00:00', 0, ''), (884235033, 'YogaFerdiansya/Final-Project_MySkill', '', 186820366, 'YogaFerdiansya', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727937817, 'PushEvent', '2024-11-12 06:47:50', '1970-01-01 00:00:00', 0, ''), (884361731, 'jnhsports-cn/jnhtiyu-game', '', 41898282, 'github-actions[bot]', 0, 0, 'opened', '', 31640, 0, '', FALSE, 'open', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 2651222207, 0, 0, '2024-11-12', '2024-11-01', 2024, 0, 0, 43727937823, 'IssuesEvent', '2024-11-12 06:47:50', '2024-11-12 06:47:48', 41898282, 'github-actions[bot]'), (884181201, 'hthsports-cn/hthtiyuj9', '', 41898282, 'github-actions[bot]', 0, 0, 'opened', '', 33408, 0, '', FALSE, 'open', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 2651222210, 0, 0, '2024-11-12', '2024-11-01', 2024, 0, 0, 43727937835, 'IssuesEvent', '2024-11-12 06:47:50', '2024-11-12 06:47:48', 41898282, 'github-actions[bot]'), (886529506, 'teknik-github/nginx-kubernetes', '', 136105599, 'teknik-github', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727937837, 'PushEvent', '2024-11-12 06:47:50', '1970-01-01 00:00:00', 0, ''), (885795523, 'slerman12/BrokenWisdoms-V2-in-progress', '', 9126603, 'slerman12', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727937838, 'PushEvent', '2024-11-12 06:47:50', '1970-01-01 00:00:00', 0, ''), (887068329, 'freeukapp/uk', '', 171124844, 'freeukapp', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 1, 1, 43727937841, 'PushEvent', '2024-11-12 06:47:50', '1970-01-01 00:00:00', 0, ''), (884181357, 'hthsports-cn/hthtiyusports', '', 41898282, 'github-actions[bot]', 0, 0, 'opened', '', 33081, 0, '', FALSE, 'open', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 2651222205, 0, 0, '2024-11-12', '2024-11-01', 2024, 0, 0, 43727937842, 'IssuesEvent', '2024-11-12 06:47:50', '2024-11-12 06:47:48', 41898282, 'github-actions[bot]'), (887096036, 'jiangsp/test-repo', '', 12456337, 'jiangsp', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 0, 0, 43727937855, 'CreateEvent', '2024-11-12 06:47:50', '1970-01-01 00:00:00', 0, ''), (636962086, 'bitomic/cronstruct', '', 29139614, 'renovate[bot]', 0, 0, '', '', 0, 0, '', FALSE, '', '1970-01-01 00:00:00', '1970-01-01 00:00:00', 0, 0, 0, 0, '2024-11-12', '2024-11-01', 2024, 2, 1, 43727937871, 'PushEvent', '2024-11-12 06:47:50', '1970-01-01 00:00:00', 0, ''), (549661928, 'lightsats(len:13163817);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396235.93062,\n      \"query_time\": 9.720509906,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 268427264,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n      \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568043\",\n      \"success\": 0,\n      \"timestamp\": 1731395799.041102,\n      \"query_time\": 9.032250736,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n      \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568043\",\n      \"success\": 0,\n      \"timestamp\": 1731395078.826332,\n      \"query_time\": 8.8163619,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n      \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568111\",\n      \"success\": 0,\n      \"timestamp\": 1731394717.1922,\n      \"query_time\": 7.182756459,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567961\",\n      \"success\": 0,\n      \"timestamp\": 1731395946.467787,\n      \"query_time\": 6.046596884,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567971\",\n      \"success\": 0,\n      \"timestamp\": 1731396005.952455,\n      \"query_time\": 5.467857452,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n      \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568111\",\n      \"success\": 0,\n      \"timestamp\": 1731394955.416653,\n      \"query_time\": 5.407257378,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 1860,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567971\",\n      \"success\": 0,\n      \"timestamp\": 1731396125.741657,\n      \"query_time\": 5.297103595,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567971\",\n      \"success\": 0,\n      \"timestamp\": 1731396185.526266,\n      \"query_time\": 5.07917745,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"36ceb598d0e5d79b1d82c4d5e3033d3180ae92a2c65fb6560a7fb0f7887c7a4f\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`users`.`last_modified_date`) FROM `stackoverflow`.`users`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567965\",\n      \"success\": 0,\n      \"timestamp\": 1731395051.318045,\n      \"query_time\": 4.682126417,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 1331,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"f75457b4116b4eccab69e99d34bf6f1669feb167d818bacfc9f20cb8b642dfde\",\n      \"query\": \"select id,title,status,user_id,query_sql,result,created_at from explorer_questions where unix_timestamp(created_at) between 1731392644 and 1731394451 limit 0, 100;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724570355\",\n      \"success\": 0,\n      \"timestamp\": 1731394756.951821,\n      \"query_time\": 4.425153747,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 381221885,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.readonly\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 37454,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c980164ab6815c14e9af370c01d70c07627c89fef262f8b088bb4c83f15051ac\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`comments`.`creation_date`) FROM `stackoverflow`.`comments`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724569861\",\n      \"success\": 0,\n      \"timestamp\": 1731395642.640544,\n      \"query_time\": 4.318355414,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n      \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568043\",\n      \"success\": 0,\n      \"timestamp\": 1731395194.032638,\n      \"query_time\": 4.022916922,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567969\",\n      \"success\": 0,\n      \"timestamp\": 1731394624.795916,\n      \"query_time\": 3.857108275,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n      \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568111\",\n      \"success\": 0,\n      \"timestamp\": 1731396153.419079,\n      \"query_time\": 3.409466191,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567961\",\n      \"success\": 0,\n      \"timestamp\": 1731395223.288161,\n      \"query_time\": 3.167641698,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567971\",\n      \"success\": 0,\n      \"timestamp\": 1731395163.237968,\n      \"query_time\": 3.14813826,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568085\",\n      \"success\": 0,\n      \"timestamp\": 1731395522.865902,\n      \"query_time\": 2.623152299,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567969\",\n      \"success\": 0,\n      \"timestamp\": 1731395582.691315,\n      \"query_time\": 2.446044816,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"be8d3f0f91956f4da4dd19972a61dd3a7d478ff60c67cfe863e1ab52a73ea48b\",\n      \"query\": \"SELECT actor_login,        COUNT(*) AS events FROM github_events ge WHERE repo_id = 41986369   AND (         (type = 'PullRequestEvent' AND action = 'opened') OR         (type = 'IssuesEvent' AND action = 'opened') OR         (type = 'IssueCommentEvent' AND action = 'created') OR         (type = 'PullRequestReviewEvent' AND action = 'created') OR         (type = 'PullRequestReviewCommentEvent' AND action = 'created') OR         (type = 'PushEvent' AND action = '')     )   AND actor_login NOT LIKE '%bot'   AND actor_login NOT LIKE '%[bot]'   AND actor_login NOT IN (SELECT login FROM blacklist_users bu) GROUP BY actor_login ORDER BY events DESC LIMIT 200;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724570953\",\n      \"success\": 0,\n      \"timestamp\": 1731395253.868425,\n      \"query_time\": 1.7582347390000002,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 14108752,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 715631,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c980164ab6815c14e9af370c01d70c07627c89fef262f8b088bb4c83f15051ac\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`comments`.`creation_date`) FROM `stackoverflow`.`comments`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724569861\",\n      \"success\": 0,\n      \"timestamp\": 1731395241.950735,\n      \"query_time\": 1.585105232,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"28994526549b36b499a9c31f545c6b5d99df9823651bcc69b8080e9d77ef1c8f\",\n      \"query\": \"DELETE FROM `event_logs` WHERE (created_at \\u003c= '2024-11-12 07:16:03.455697') LIMIT 100000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572875\",\n      \"success\": 0,\n      \"timestamp\": 1731396065.084168,\n      \"query_time\": 1.469898559,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 5450866,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 813142,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568085\",\n      \"success\": 0,\n      \"timestamp\": 1731394742.332813,\n      \"query_time\": 1.384022751,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"36ceb598d0e5d79b1d82c4d5e3033d3180ae92a2c65fb6560a7fb0f7887c7a4f\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`users`.`last_modified_date`) FROM `stackoverflow`.`users`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567965\",\n      \"success\": 0,\n      \"timestamp\": 1731394843.499413,\n      \"query_time\": 1.369542649,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 1333,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c980164ab6815c14e9af370c01d70c07627c89fef262f8b088bb4c83f15051ac\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`comments`.`creation_date`) FROM `stackoverflow`.`comments`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724569861\",\n      \"success\": 0,\n      \"timestamp\": 1731395456.173311,\n      \"query_time\": 1.365610137,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"36ceb598d0e5d79b1d82c4d5e3033d3180ae92a2c65fb6560a7fb0f7887c7a4f\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`users`.`last_modified_date`) FROM `stackoverflow`.`users`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567965\",\n      \"success\": 0,\n      \"timestamp\": 1731396028.246033,\n      \"query_time\": 1.309044044,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 1333,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"28994526549b36b499a9c31f545c6b5d99df9823651bcc69b8080e9d77ef1c8f\",\n      \"query\": \"DELETE FROM `event_logs` WHERE (created_at \\u003c= '2024-11-12 07:09:02.923132') LIMIT 100000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572057\",\n      \"success\": 0,\n      \"timestamp\": 1731395644.595067,\n      \"query_time\": 1.293512121,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 5450065,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 788894,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"36ceb598d0e5d79b1d82c4d5e3033d3180ae92a2c65fb6560a7fb0f7887c7a4f\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`users`.`last_modified_date`) FROM `stackoverflow`.`users`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567965\",\n      \"success\": 0,\n      \"timestamp\": 1731394650.302318,\n      \"query_time\": 1.282091282,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 1333,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"28994526549b36b499a9c31f545c6b5d99df9823651bcc69b8080e9d77ef1c8f\",\n      \"query\": \"DELETE FROM `event_logs` WHERE (created_at \\u003c= '2024-11-12 07:02:03.428522') LIMIT 100000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724571329\",\n      \"success\": 0,\n      \"timestamp\": 1731395225.057753,\n      \"query_time\": 1.182898929,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 6385875,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 764696,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"1f1f79dc86b52642303cfba02d397ce6239dc00add3917b979bf1d76f23da47e\",\n      \"query\": \"WITH pr_with_merged_at AS (     SELECT         number, DATE_FORMAT(created_at, '%Y-%m-01') AS t_month, created_at AS merged_at     FROM         github_events ge     WHERE         type = 'PullRequestEvent'         -- Considering that some repositories accept the code of the contributor by closing the PR and push commit directly,         -- here is not distinguished whether it is the merged event.         -- See: https://github.com/mongodb/mongo/pulls?q=is%3Apr+is%3Aclosed         AND action = 'closed'         AND repo_id = 724712 ), pr_with_opened_at AS (     SELECT         number, created_at AS opened_at     FROM         github_events ge     WHERE         type = 'PullRequestEvent'         AND action = 'opened'         -- Exclude Bots         -- AND actor_login NOT LIKE '%bot%'         -- AND actor_login NOT IN (SELECT login FROM blacklist_users bu)         AND repo_id = 724712 ), tdiff AS (     SELECT         t_month,         (UNIX_TIMESTAMP(pwm.merged_at) - UNIX_TIMESTAMP(pwo.opened_at)) AS diff     FROM         pr_with_opened_at pwo         JOIN pr_with_merged_at pwm ON pwo.number = pwm.number AND pwm.merged_at \\u003e pwo.opened_at ), tdiff_with_rank AS (     SELECT         tdiff.t_month,         diff  / 60 / 60 AS diff,         ROW_NUMBER() OVER (PARTITION BY tdiff.t_month ORDER BY diff) AS r,         COUNT(*) OVER (PARTITION BY tdiff.t_month) AS cnt,         FIRST_VALUE(diff / 60 / 60) OVER (PARTITION BY tdiff.t_month ORDER BY diff) AS p0,         FIRST_VALUE(diff / 60 / 60)  OVER (PARTITION BY tdiff.t_month ORDER BY diff DESC) AS p100     FROM tdiff ), tdiff_p25 AS (     SELECT         t_month, diff AS p25     FROM         tdiff_with_rank tr     WHERE         r = ROUND(cnt * 0.25) ), tdiff_p50 AS (     SELECT         t_month, diff AS p50     FROM         tdiff_with_rank tr     WHERE         r = ROUND(cnt * 0.5) ), tdiff_p75 AS (     SELECT         t_month, diff AS p75     FROM         tdiff_with_rank tr     WHERE         r = ROUND(cnt * 0.75) ) SELECT     tr.t_month AS event_month,     p0,     p25,     p50,     p75,     p100 FROM tdiff_with_rank tr LEFT JOIN tdiff_p25 p25 ON tr.t_month = p25.t_month LEFT JOIN tdiff_p50 p50 ON tr.t_month = p50.t_month LEFT JOIN tdiff_p75 p75 ON tr.t_month = p75.t_month WHERE r = 1 ORDER BY event_month ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724569263\",\n      \"success\": 0,\n      \"timestamp\": 1731395414.605844,\n      \"query_time\": 1.098174003,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 39824163,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 147573,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c980164ab6815c14e9af370c01d70c07627c89fef262f8b088bb4c83f15051ac\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`comments`.`creation_date`) FROM `stackoverflow`.`comments`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724569861\",\n      \"success\": 0,\n      \"timestamp\": 1731394844.376842,\n      \"query_time\": 1.045544638,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 1186,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c980164ab6815c14e9af370c01d70c07627c89fef262f8b088bb4c83f15051ac\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`comments`.`creation_date`) FROM `stackoverflow`.`comments`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724569861\",\n      \"success\": 0,\n      \"timestamp\": 1731396223.352117,\n      \"query_time\": 1.028242696,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 1183,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"query\": \"INSERT INTO mv_events_total(record_time, events_increment, events_total) WITH last_record AS (     SELECT         /*+ MERGE() */         record_time AS last_event_time,         events_total AS last_events_total     FROM mv_events_total     WHERE record_time \\u003e (NOW() - INTERVAL 1 HOUR)     ORDER BY record_time DESC     LIMIT 1 ), inc_events AS (     SELECT         MAX(created_at) AS current_max_event_time,         COUNT(1) AS inc_events     FROM github_events ge     WHERE         created_at \\u003e= (SELECT last_event_time FROM last_record) ) SELECT     current_max_event_time AS record_time,     inc_events AS new_events_increment,     inc_events + (SELECT last_events_total FROM last_record) AS new_events_total FROM     inc_events WHERE     EXISTS(SELECT * FROM last_record) ON DUPLICATE KEY UPDATE     events_increment = VALUES(events_increment),     events_total = VALUES(events_total);\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567971\",\n      \"success\": 0,\n      \"timestamp\": 1731394801.965975,\n      \"query_time\": 0.996480012,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 16468,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.pipeline\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 4,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c980164ab6815c14e9af370c01d70c07627c89fef262f8b088bb4c83f15051ac\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`comments`.`creation_date`) FROM `stackoverflow`.`comments`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724569861\",\n      \"success\": 0,\n      \"timestamp\": 1731395826.337301,\n      \"query_time\": 0.993633424,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"36ceb598d0e5d79b1d82c4d5e3033d3180ae92a2c65fb6560a7fb0f7887c7a4f\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`users`.`last_modified_date`) FROM `stackoverflow`.`users`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567965\",\n      \"success\": 0,\n      \"timestamp\": 1731394801.838705,\n      \"query_time\": 0.910571422,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 1326,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"b2f17178f468a7841cdd97cad6f34e6a6304b77a70230c642b58a9db9b98aa38\",\n      \"query\": \"WITH stars_per_company AS (     SELECT         IF(             gu.organization_formatted IS NOT NULL AND LENGTH(gu.organization_formatted) != 0,             gu.organization_formatted,             'Unknown'         ) AS company_name,         COUNT(DISTINCT ge.actor_login) AS stargazers     FROM github_events ge     LEFT JOIN github_users gu ON ge.actor_login = gu.login     WHERE         ge.repo_id IN (724712)         AND ge.type = 'WatchEvent'         AND ge.action = 'started'     GROUP BY company_name ), stars_total AS (     SELECT SUM(stargazers) AS total FROM stars_per_company ) SELECT     spc.company_name,     spc.stargazers,     spc.stargazers / st.total AS proportion FROM stars_per_company spc, stars_total st WHERE spc.company_name != 'Unknown' ORDER BY spc.stargazers DESC LIMIT 50;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568001\",\n      \"success\": 0,\n      \"timestamp\": 1731395393.409098,\n      \"query_time\": 0.864050077,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 18486024,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 208195,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"36ceb598d0e5d79b1d82c4d5e3033d3180ae92a2c65fb6560a7fb0f7887c7a4f\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`users`.`last_modified_date`) FROM `stackoverflow`.`users`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567965\",\n      \"success\": 0,\n      \"timestamp\": 1731395864.962533,\n      \"query_time\": 0.830027895,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 1331,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"bcd7135d761d7237779b21d885710f72f0757e287d7d82d37597f8a924f5cbeb\",\n      \"query\": \"WITH group_by_org AS (   SELECT       CASE         WHEN (TRIM(gu.organization) = '' OR gu.organization IS NULL) THEN 'UNKNOWN'         ELSE LOWER(REPLACE(gu.organization, '@', ''))       END AS org_name,       COUNT(DISTINCT ge.actor_login) AS stargazers   FROM github_events ge   LEFT JOIN github_users gu ON ge.actor_login = gu.login   WHERE       ge.repo_id = (SELECT repo_id FROM github_repos WHERE repo_name = CONCAT(?, '/', ?) LIMIT 1)       AND ge.type = 'WatchEvent'       AND ge.action = 'started'       AND ge.created_at \\u003e= ?       AND ge.created_at \\u003c= ?       AND IF(? = TRUE, gu.organization != '', TRUE)   GROUP BY org_name ), summary AS (   SELECT SUM(stargazers) AS total FROM group_by_org ) SELECT      org_name,     stargazers,     stargazers / total AS percentage FROM group_by_org, summary ORDER BY stargazers DESC [arguments: (\\\"uutils\\\", \\\"coreutils\\\", \\\"2000-01-01\\\", \\\"2099-01-01\\\", 1)];\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724571205\",\n      \"success\": 0,\n      \"timestamp\": 1731395242.070591,\n      \"query_time\": 0.824970564,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 6365124,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.EXLV7STi_APIKEY\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 52081,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"b4d08c7d6845f9df9d89f57d88d4a9bfe0dd4068d1771a08051a2983d0e77ef1\",\n      \"query\": \"WITH group_by_area AS (     SELECT         gu.country_code AS country_or_area,         COUNT(1) as cnt     FROM github_events ge     LEFT JOIN github_users gu ON ge.actor_login = gu.login     WHERE         repo_id IN (724712)         AND ge.type = 'WatchEvent'         AND ge.action = 'started'                  AND gu.country_code NOT IN ('', 'N/A', 'UND')     GROUP BY country_or_area ), summary AS (     SELECT SUM(cnt) AS total FROM group_by_area ) SELECT      country_or_area,     cnt AS count,     cnt / summary.total AS percentage FROM group_by_area, summary ORDER BY cnt DESC ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724569997\",\n      \"success\": 0,\n      \"timestamp\": 1731395393.389929,\n      \"query_time\": 0.824057292,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 6853796,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 208195,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396065.825409,\n      \"query_time\": 0.817365226,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22131613,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 41840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396078.349497,\n      \"query_time\": 0.810693408,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22572520,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 211840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"f45ac600a1a4f54840eb3a5de4b4a4f8d8ba7b50626671878f3d9ccd750b271f\",\n      \"query\": \"WITH pull_requests AS (     SELECT         724712 AS repo_id,         IFNULL(COUNT(DISTINCT number), 0) AS total     FROM github_events     WHERE         type = 'PullRequestEvent'         AND repo_id = 724712 ), pull_request_creators AS (     SELECT         724712 AS repo_id,          IFNULL(COUNT(DISTINCT actor_login), 0) AS total     FROM github_events     WHERE         type = 'PullRequestEvent'         AND repo_id = 724712         AND action = 'opened' ), pull_request_reviews AS (     SELECT         724712 AS repo_id,         IFNULL(COUNT(1), 0) AS total     FROM github_events     WHERE         type = 'PullRequestReviewEvent'         AND repo_id = 724712         AND action = 'created' ), pull_request_reviewers AS (     SELECT         724712 AS repo_id,         IFNULL(COUNT(DISTINCT actor_login), 0) AS total     FROM github_events     WHERE         type = 'PullRequestReviewEvent'         AND repo_id = 724712         AND action = 'created' ) SELECT     724712 AS repo_id,     pr.total AS pull_requests,     prc.total AS pull_request_creators,     prr.total AS pull_request_reviews,     prrc.total AS pull_request_reviewers FROM (     SELECT 724712 AS repo_id ) sub LEFT JOIN pull_requests pr ON sub.repo_id = pr.repo_id LEFT JOIN pull_request_creators prc ON sub.repo_id = prc.repo_id LEFT JOIN pull_request_reviews prr ON sub.repo_id = prr.repo_id LEFT JOIN pull_request_reviewers prrc ON sub.repo_id = prrc.repo_id ;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724569997\",\n      \"success\": 0,\n      \"timestamp\": 1731395413.995461,\n      \"query_time\": 0.787534813,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 8471003,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 411670,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396070.19148,\n      \"query_time\": 0.782179986,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22212212,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 101840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396068.003281,\n      \"query_time\": 0.777597014,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22223182,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 71840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396076.072635,\n      \"query_time\": 0.750265403,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22466287,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 181840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396075.30868,\n      \"query_time\": 0.739032597,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 20989128,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 172898,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396073.087358,\n      \"query_time\": 0.738901277,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22310114,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 141840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c102b8c3be5e5ae3f7591257488aa52e329c3eee03be817d49ac974705d7ef4c\",\n      \"query\": \"SELECT     gr.repo_id,     gr.repo_name FROM github_repos gr WHERE     gr.owner_id = 530995217 LIMIT 9999;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724568131\",\n      \"success\": 0,\n      \"timestamp\": 1731394755.871201,\n      \"query_time\": 0.736586614,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 19520,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.gh_api\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396080.492737,\n      \"query_time\": 0.733412968,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 21960392,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 244682,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396072.334653,\n      \"query_time\": 0.728839053,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22214549,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 131840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396074.555971,\n      \"query_time\": 0.726463072,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22357299,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 161840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396082.593559,\n      \"query_time\": 0.725887943,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 21139139,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 270252,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396064.994371,\n      \"query_time\": 0.719222946,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22118597,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 31840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396076.804077,\n      \"query_time\": 0.717092904,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22402880,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 191840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"36ceb598d0e5d79b1d82c4d5e3033d3180ae92a2c65fb6560a7fb0f7887c7a4f\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`users`.`last_modified_date`) FROM `stackoverflow`.`users`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567965\",\n      \"success\": 0,\n      \"timestamp\": 1731395661.18532,\n      \"query_time\": 0.716139968,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 1332,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"36ceb598d0e5d79b1d82c4d5e3033d3180ae92a2c65fb6560a7fb0f7887c7a4f\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`users`.`last_modified_date`) FROM `stackoverflow`.`users`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567965\",\n      \"success\": 0,\n      \"timestamp\": 1731396231.333459,\n      \"query_time\": 0.71560644,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 1333,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396073.815622,\n      \"query_time\": 0.714527288,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22359513,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 151840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396077.525107,\n      \"query_time\": 0.707241735,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 20769634,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 201428,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c980164ab6815c14e9af370c01d70c07627c89fef262f8b088bb4c83f15051ac\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`comments`.`creation_date`) FROM `stackoverflow`.`comments`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724569861\",\n      \"success\": 0,\n      \"timestamp\": 1731395272.815385,\n      \"query_time\": 0.69981582,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"36ceb598d0e5d79b1d82c4d5e3033d3180ae92a2c65fb6560a7fb0f7887c7a4f\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`users`.`last_modified_date`) FROM `stackoverflow`.`users`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724567965\",\n      \"success\": 0,\n      \"timestamp\": 1731395458.115337,\n      \"query_time\": 0.695422673,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 1333,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396066.530357,\n      \"query_time\": 0.691207091,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22240113,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 51840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396068.707627,\n      \"query_time\": 0.690437198,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22291517,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 81840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396081.197017,\n      \"query_time\": 0.690322873,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22569676,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 251840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c980164ab6815c14e9af370c01d70c07627c89fef262f8b088bb4c83f15051ac\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`comments`.`creation_date`) FROM `stackoverflow`.`comments`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724569861\",\n      \"success\": 0,\n      \"timestamp\": 1731394660.67075,\n      \"query_time\": 0.690175173,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396071.591309,\n      \"query_time\": 0.68949899,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22212310,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 121840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396064.261355,\n      \"query_time\": 0.6893115,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 21930234,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 21840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396079.048383,\n      \"query_time\": 0.685035091,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 21853561,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 224146,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396079.745406,\n      \"query_time\": 0.683370142,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22113114,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 231509,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396070.887834,\n      \"query_time\": 0.682037997,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22309224,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 111840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396069.39572,\n      \"query_time\": 0.674223256,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22384852,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 91840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"2bdfb5a55c4869bfbfe70e16c45611165ca1f635c07372d2c3c8d15ab7e2fdb0\",\n      \"query\": \"DELETE FROM `github_events` WHERE `github_events`.`created_at` BETWEEN '2024-11-12 06:00:00' AND '2024-11-12 06:59:59' LIMIT 10000;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724572873\",\n      \"success\": 0,\n      \"timestamp\": 1731396067.211877,\n      \"query_time\": 0.667787653,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 22182138,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 61840,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    },\n    {\n      \"digest\": \"c980164ab6815c14e9af370c01d70c07627c89fef262f8b088bb4c83f15051ac\",\n      \"query\": \"SELECT MAX(`stackoverflow`.`comments`.`creation_date`) FROM `stackoverflow`.`comments`;\",\n      \"instance\": \"\",\n      \"db\": \"gharchive_dev\",\n      \"connection_id\": \"3483529913724569861\",\n      \"success\": 0,\n      \"timestamp\": 1731396040.397937,\n      \"query_time\": 0.666126679,\n      \"parse_time\": 0,\n      \"compile_time\": 0,\n      \"rewrite_time\": 0,\n      \"preproc_subqueries_time\": 0,\n      \"optimize_time\": 0,\n      \"wait_ts\": 0,\n      \"cop_time\": 0,\n      \"lock_keys_time\": 0,\n      \"write_sql_response_total\": 0,\n      \"exec_retry_time\": 0,\n      \"memory_max\": 4096,\n      \"disk_max\": 0,\n      \"txn_start_ts\": \"\",\n      \"prev_stmt\": \"\",\n      \"plan\": \"\",\n      \"is_internal\": 0,\n      \"index_names\": \"\",\n      \"stats\": \"\",\n      \"backoff_types\": \"\",\n      \"prepared\": 0,\n      \"plan_from_cache\": 0,\n      \"plan_from_binding\": 0,\n      \"user\": \"3EDFHZJX5iSzvfr.etl\",\n      \"host\": \"\",\n      \"process_time\": 0,\n      \"wait_time\": 0,\n      \"backoff_time\": 0,\n      \"get_commit_ts_time\": 0,\n      \"local_latch_wait_time\": 0,\n      \"resolve_lock_time\": 0,\n      \"prewrite_time\": 0,\n      \"wait_prewrite_binlog_time\": 0,\n      \"commit_time\": 0,\n      \"commit_backoff_time\": 0,\n      \"cop_proc_avg\": 0,\n      \"cop_proc_p90\": 0,\n      \"cop_proc_max\": 0,\n      \"cop_wait_avg\": 0,\n      \"cop_wait_p90\": 0,\n      \"cop_wait_max\": 0,\n      \"write_keys\": 0,\n      \"write_size\": 0,\n      \"prewrite_region\": 0,\n      \"txn_retry\": 0,\n      \"request_count\": 0,\n      \"process_keys\": 0,\n      \"total_keys\": 0,\n      \"cop_proc_addr\": \"\",\n      \"cop_wait_addr\": \"\",\n      \"rocksdb_delete_skipped_count\": 0,\n      \"rocksdb_key_skipped_count\": 0,\n      \"rocksdb_block_cache_hit_count\": 0,\n      \"rocksdb_block_read_count\": 0,\n      \"rocksdb_block_read_byte\": 0,\n      \"ru\": 0,\n      \"time_queued_by_rc\": 0,\n      \"resource_group\": \"\"\n    }\n  ],\n  \"nextPageToken\": \"1\",\n  \"totalSize\": 10\n}\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/sample-res/statement-list.json",
    "content": "{\n  \"data\": [\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `mv_events_total` ( `record_time` , `events_increment` , `events_total` ) with `last_record` as ( select `record_time` as `last_event_time` , `events_total` as `last_events_total` from `mv_events_total` where `record_time` \\u003e ( now ( ) - interval ? hour ) order by `record_time` desc limit ? ) , `inc_events` as ( select max ( `created_at` ) as `current_max_event_time` , count ( ? ) as `inc_events` from `github_events` `ge` where `created_at` \\u003e= ( select `last_event_time` from `last_record` ) ) select `current_max_event_time` as `record_time` , `inc_events` as `new_events_increment` , `inc_events` + ( select `last_events_total` from `last_record` ) as `new_events_total` from `inc_events` where exists ( select * from `last_record` ) on duplicate key update `events_increment` = values ( `events_increment` ) , `events_total` = values ( `events_total` )\",\n      \"digest\": \"c33c8e181899149210aaab99716ac1b1d0bb432b6721d57cbf2e5cbf00177356\",\n      \"exec_count\": 25,\n      \"sum_latency\": 398962145114,\n      \"max_latency\": 55391695564,\n      \"min_latency\": 545751314,\n      \"avg_latency\": 15958485804,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 6755.099868774415,\n      \"avg_ru\": 270.2039947509766\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"\",\n      \"digest\": \"993fb73ba8b2e351a7d757e4bf993da779cf584af0fd6857cdbeaeb14cb4268c\",\n      \"exec_count\": 33292,\n      \"sum_latency\": 216292010854,\n      \"max_latency\": 190233380,\n      \"min_latency\": 3125034,\n      \"avg_latency\": 6496816,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 476595.389825428,\n      \"avg_ru\": 14.31561305495098\n    },\n    {\n      \"summary_begin_time\": 1731382199,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with `stars` as ( select `ge` . `repo_id` as `repo_id` , count ( ? ) as `total` , count ( distinct `actor_id` ) as `actors` , sum ( timestampdiff ( second , date_sub ( now ( ) , interval ? hour ) , `ge` . `created_at` ) / timestampdiff ( second , date_sub ( now ( ) , interval ? hour ) , now ( ) ) * ? + ? ) as `score` from `github_events` `ge` where type = ? and `ge` . `created_at` \\u003e= date_sub ( now ( ) , interval ? hour ) group by `ge` . `repo_id` having `actors` \\u003e ? * `total` ) , `forks` as ( select `ge` . `repo_id` as `repo_id` , count ( ? ) as `total` , count ( distinct `actor_id` ) as `actors` , sum ( timestampdiff ( second , date_sub ( now ( ) , interval ? hour ) , `ge` . `created_at` ) / timestampdiff ( second , date_sub ( now ( ) , interval ? hour ) , now ( ) ) * ? + ? ) as `score` from `github_events` `ge` where type = ? and `ge` . `created_at` \\u003e= date_sub ( now ( ) , interval ? hour ) group by `ge` . `repo_id` having `actors` \\u003e ? * `total` ) , `toprepos` as ( select `r` . `...\",\n      \"digest\": \"37603db7d950f437baab77c402ca278dfb81fb201725dda0253d5cf54243f208\",\n      \"exec_count\": 4,\n      \"sum_latency\": 200039457150,\n      \"max_latency\": 75740429058,\n      \"min_latency\": 22151604389,\n      \"avg_latency\": 50009864287,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1466585.291414369,\n      \"avg_ru\": 366646.3228535922\n    },\n    {\n      \"summary_begin_time\": 1731382199,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select count ( distinct `actor_id` ) as `developers` , count ( distinct `repo_id` ) as `repos` , sum ( if ( action = ? and `pr_merged` = true , `additions` , ? ) ) as `additions` , sum ( if ( action = ? and `pr_merged` = true , `deletions` , ? ) ) as `deletions` , count ( distinct if ( action = ? , `pr_or_issue_id` , ? ) ) as `opened_prs` , count ( distinct if ( action = ? and `pr_merged` = false , `pr_or_issue_id` , ? ) ) as `closed_prs` , count ( distinct if ( action = ? and `pr_merged` = true , `pr_or_issue_id` , ? ) ) as `merged_prs` from `github_events` `ge` where type = ? and `created_at` \\u003e date_sub ( now ( ) , interval ? hour ) ;\",\n      \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n      \"exec_count\": 13,\n      \"sum_latency\": 179523117963,\n      \"max_latency\": 50954683596,\n      \"min_latency\": 4227281524,\n      \"avg_latency\": 13809470612,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 9830.1027730306,\n      \"avg_ru\": 756.1617517715846\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select max ( `stackoverflow` . `users` . `last_modified_date` ) from `stackoverflow` . `users`\",\n      \"digest\": \"36ceb598d0e5d79b1d82c4d5e3033d3180ae92a2c65fb6560a7fb0f7887c7a4f\",\n      \"exec_count\": 35,\n      \"sum_latency\": 137883610182,\n      \"max_latency\": 59020613749,\n      \"min_latency\": 256525386,\n      \"avg_latency\": 3939531719,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 267490.8553009033,\n      \"avg_ru\": 7642.595865740095\n    },\n    {\n      \"summary_begin_time\": 1731382440,\n      \"summary_end_time\": 1731383591,\n      \"digest_text\": \"with `stars` as ( select `ge` . `repo_id` as `repo_id` , count ( ? ) as `total` , count ( distinct `actor_id` ) as `actors` , sum ( timestampdiff ( second , date_sub ( now ( ) , interval ? month ) , `ge` . `created_at` ) / timestampdiff ( second , date_sub ( now ( ) , interval ? month ) , now ( ) ) * ? + ? ) as `score` from `github_events` `ge` where type = ? and `ge` . `created_at` \\u003e= date_sub ( now ( ) , interval ? month ) group by `ge` . `repo_id` having `actors` \\u003e ? * `total` ) , `forks` as ( select `ge` . `repo_id` as `repo_id` , count ( ? ) as `total` , count ( distinct `actor_id` ) as `actors` , sum ( timestampdiff ( second , date_sub ( now ( ) , interval ? month ) , `ge` . `created_at` ) / timestampdiff ( second , date_sub ( now ( ) , interval ? month ) , now ( ) ) * ? + ? ) as `score` from `github_events` `ge` where type = ? and `ge` . `created_at` \\u003e= date_sub ( now ( ) , interval ? month ) group by `ge` . `repo_id` having `actors` \\u003e ? * `total` ) , `toprepos` as ( select `...\",\n      \"digest\": \"639d0ae6f3e83e86f46c41d84f99618d2aeebfdb552f5fe42d66b6695e9eba4a\",\n      \"exec_count\": 6,\n      \"sum_latency\": 136248108048,\n      \"max_latency\": 45894677166,\n      \"min_latency\": 8308606188,\n      \"avg_latency\": 22708018008,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 2113696.351765869,\n      \"avg_ru\": 352282.72529431153\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select max ( `stackoverflow` . `comments` . `creation_date` ) from `stackoverflow` . `comments`\",\n      \"digest\": \"c980164ab6815c14e9af370c01d70c07627c89fef262f8b088bb4c83f15051ac\",\n      \"exec_count\": 46,\n      \"sum_latency\": 107660476771,\n      \"max_latency\": 59558360148,\n      \"min_latency\": 109787132,\n      \"avg_latency\": 2340445147,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 543444.5157470703,\n      \"avg_ru\": 11814.011211892834\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `stats_query_summary` ( `query_name` , `digest_text` , `executed_at` ) values ( ... ) , ( ... ) ;\",\n      \"digest\": \"1588d48073c5ecbfa5ca85ed3108156075b3d3868c6541f97e82a27f68b5b46c\",\n      \"exec_count\": 16285,\n      \"sum_latency\": 96927632912,\n      \"max_latency\": 101021767,\n      \"min_latency\": 2938746,\n      \"avg_latency\": 5951957,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 308226.4697265625,\n      \"avg_ru\": 18.927016869914798\n    },\n    {\n      \"summary_begin_time\": 1731382805,\n      \"summary_end_time\": 1731382865,\n      \"digest_text\": \"insert into `mv_repo_participants` ( `repo_id` , `user_login` , `first_engagement_at` , `last_engagement_at` ) select `ge` . `repo_id` , `ge` . `actor_login` as `user_login` , min ( `ge` . `created_at` ) as `new_first_engagement_at` , max ( `ge` . `created_at` ) as `new_last_engagement_at` from `github_events` `ge` where `ge` . `type` != ? and `ge` . `org_id` != ? and `ge` . `created_at` \\u003e= ? and `ge` . `created_at` \\u003c ? group by `ge` . `repo_id` , `ge` . `actor_login` on duplicate key update `first_engagement_at` = `least` ( `first_engagement_at` , `new_first_engagement_at` ) , `last_engagement_at` = `greatest` ( `last_engagement_at` , `new_last_engagement_at` ) ;\",\n      \"digest\": \"0221e529d7e075c5691837ef86c7e03b774f31557e9913d06a825c0a459f3589\",\n      \"exec_count\": 1,\n      \"sum_latency\": 65362002994,\n      \"max_latency\": 65362002994,\n      \"min_latency\": 65362002994,\n      \"avg_latency\": 65362002994,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 9954.152366129345,\n      \"avg_ru\": 9954.152366129345\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select `gr` . `repo_id` , `gr` . `repo_name` from `github_repos` `gr` where `gr` . `owner_id` = ? limit ?\",\n      \"digest\": \"c102b8c3be5e5ae3f7591257488aa52e329c3eee03be817d49ac974705d7ef4c\",\n      \"exec_count\": 31719,\n      \"sum_latency\": 65091639251,\n      \"max_latency\": 313573815,\n      \"min_latency\": 1072644,\n      \"avg_latency\": 2052134,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 17046.923215739298,\n      \"avg_ru\": 0.5374357078009804\n    },\n    {\n      \"summary_begin_time\": 1731382380,\n      \"summary_end_time\": 1731383349,\n      \"digest_text\": \"with `stars` as ( select `ge` . `repo_id` as `repo_id` , count ( ? ) as `total` , count ( distinct `actor_id` ) as `actors` , sum ( timestampdiff ( second , date_sub ( now ( ) , interval ? week ) , `ge` . `created_at` ) / timestampdiff ( second , date_sub ( now ( ) , interval ? week ) , now ( ) ) * ? + ? ) as `score` from `github_events` `ge` where type = ? and `ge` . `created_at` \\u003e= date_sub ( now ( ) , interval ? week ) group by `ge` . `repo_id` having `actors` \\u003e ? * `total` ) , `forks` as ( select `ge` . `repo_id` as `repo_id` , count ( ? ) as `total` , count ( distinct `actor_id` ) as `actors` , sum ( timestampdiff ( second , date_sub ( now ( ) , interval ? week ) , `ge` . `created_at` ) / timestampdiff ( second , date_sub ( now ( ) , interval ? week ) , now ( ) ) * ? + ? ) as `score` from `github_events` `ge` where type = ? and `ge` . `created_at` \\u003e= date_sub ( now ( ) , interval ? week ) group by `ge` . `repo_id` having `actors` \\u003e ? * `total` ) , `toprepos` as ( select `r` . `...\",\n      \"digest\": \"67f2d75158c276205e197776572a8e42914927355e2cb8bb97a64a717de7b034\",\n      \"exec_count\": 3,\n      \"sum_latency\": 58750446299,\n      \"max_latency\": 43291828666,\n      \"min_latency\": 4998831948,\n      \"avg_latency\": 19583482099,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1063171.3158467377,\n      \"avg_ru\": 354390.4386155792\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select * , date_add ( `updated_at` , interval `expires` second ) as `expired_at` from cache where `cache_key` = ? and ( ( `expires` = ? ) or ( date_add ( `updated_at` , interval `expires` second ) \\u003e= now ( ) ) ) limit ? ;\",\n      \"digest\": \"67d06204b8ae62e14dbe085885e7b8abf4bed105f9581577acdb2aade862a61a\",\n      \"exec_count\": 34794,\n      \"sum_latency\": 56248978757,\n      \"max_latency\": 171024758,\n      \"min_latency\": 1033990,\n      \"avg_latency\": 1616628,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 16740.036560059027,\n      \"avg_ru\": 0.4811184847979257\n    },\n    {\n      \"summary_begin_time\": 1731382562,\n      \"summary_end_time\": 1731383591,\n      \"digest_text\": \"with `stars_group_by_repo` as ( select `repo_id` , count ( distinct `actor_login` ) as `prs` from `github_events` where type = ? and `repo_id` in ( select `repo_id` from `collection_items` `ci` where `collection_id` = ? ) group by `repo_id` ) , `stars_group_by_month` as ( select `date_format` ( `created_at` , ? ) as `t_month` , `repo_id` , count ( distinct `actor_login` ) as `stars` from `github_events` where type = ? and action = ? and `repo_id` in ( select `repo_id` from `collection_items` `ci` where `collection_id` = ? ) and `created_at` \\u003c `date_format` ( now ( ) , ? ) and `created_at` \\u003e= `date_format` ( date_sub ( now ( ) , interval ? month ) , ? ) group by `t_month` , `repo_id` ) , `stars_last_month` as ( select `t_month` , `repo_id` , `stars` , `row_number` ( ) `over` ( order by `stars` desc ) as `rank` from `stars_group_by_month` `sgn` where `t_month` = `date_format` ( date_sub ( now ( ) , interval ? month ) , ? ) ) , `stars_last_2nd_month` as ( select `t_month` , `repo_id` ,...\",\n      \"digest\": \"080d1b5f80c312c309d7d3d8993625487427b93347df4af3b29f3e141423b3b4\",\n      \"exec_count\": 89,\n      \"sum_latency\": 37018190973,\n      \"max_latency\": 2570278267,\n      \"min_latency\": 15171765,\n      \"avg_latency\": 415934730,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 3,\n      \"sum_ru\": 122637.8251403808,\n      \"avg_ru\": 1377.9530914649526\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"c9000df9044e406a4686db84d5a7290ec0b9fec880f04f6f42cdc2586f245284\",\n      \"exec_count\": 1655,\n      \"sum_latency\": 32749142277,\n      \"max_latency\": 1037914617,\n      \"min_latency\": 10743096,\n      \"avg_latency\": 19788001,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1533270.2085937527,\n      \"avg_ru\": 926.4472559478868\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"16c8ace7d0b32a585a11551956d1fa6585524fa9eeaf6dd7c144fc7198aedf75\",\n      \"exec_count\": 2740,\n      \"sum_latency\": 31568646171,\n      \"max_latency\": 447417973,\n      \"min_latency\": 1403457,\n      \"avg_latency\": 11521403,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 76161.37849121087,\n      \"avg_ru\": 27.79612353693827\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with `repos` as ( select `gr` . `repo_id` from `github_repos` `gr` where `gr` . `owner_id` = ? and `gr` . `repo_id` in ( ? ) ) select `mrde` . `user_login` as `login` , sum ( `mrde` . `engagements` ) as `engagements` from `mv_repo_daily_engagements` `mrde` where `repo_id` in ( select `repo_id` from `repos` ) and `mrde` . `day` \\u003e ( now ( ) - interval ? day ) and `lower` ( `mrde` . `user_login` ) not like ? and `mrde` . `user_login` not in ( select `login` from `blacklist_users` limit ? ) and `user_login` != ( select `owner_login` from `github_repos` `gr` where `gr` . `owner_id` = ? limit ? ) and not exists ( select ? from `mv_repo_participants` `mrp` where `mrp` . `repo_id` in ( select `repo_id` from `repos` ) and `mrp` . `user_login` = `mrde` . `user_login` and `mrp` . `first_engagement_at` \\u003c ( current_date ( ) - interval ? day ) limit ? ) group by `mrde` . `user_login` order by 2 desc limit ?\",\n      \"digest\": \"abaf689215b927b40fc94c8e0bf26e0b48872d55ba3012a07821aa1d3b6ef11a\",\n      \"exec_count\": 318,\n      \"sum_latency\": 15763251468,\n      \"max_latency\": 199362543,\n      \"min_latency\": 6552286,\n      \"avg_latency\": 49569973,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 15279.601461791992,\n      \"avg_ru\": 48.049061200603745\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731382623,\n      \"digest_text\": \"with `stars_group_by_repo` as ( select `repo_id` , count ( distinct `actor_login` ) as `prs` from `github_events` where type = ? and action = ? and `repo_id` in ( select `repo_id` from `collection_items` `ci` where `collection_id` = ? ) group by `repo_id` ) , `stars_group_by_period` as ( select ( `datediff` ( current_date ( ) , date ( `created_at` ) ) ) div ? as `period` , `repo_id` , count ( distinct `actor_login` ) as `stars` from `github_events` where type = ? and action = ? and `repo_id` in ( select `repo_id` from `collection_items` `ci` where `collection_id` = ? ) and `created_at` \\u003e date_sub ( current_date ( ) , interval ? day ) group by `period` , `repo_id` ) , `stars_last_period` as ( select `repo_id` , `stars` , `row_number` ( ) `over` ( order by `stars` desc ) as `rank` from `stars_group_by_period` `sgp` where `period` = ? ) , `stars_last_2nd_period` as ( select `repo_id` , `stars` , `row_number` ( ) `over` ( order by `stars` desc ) as `rank` from `stars_group_by_period` `s...\",\n      \"digest\": \"d8c59e842412f9c79586352ab7a3e59ae1d684595c0ebe5d64d5a81ac1f5ff32\",\n      \"exec_count\": 42,\n      \"sum_latency\": 15532264833,\n      \"max_latency\": 4060983047,\n      \"min_latency\": 25524066,\n      \"avg_latency\": 369815829,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 3,\n      \"sum_ru\": 40758.88808288573,\n      \"avg_ru\": 970.449716259184\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select count ( ? ) as `cnt` , max ( `created_at` ) as `latest_created_at` , `unix_timestamp` ( max ( `created_at` ) ) as `latest_timestamp` from `github_events` where `created_at` between `from_unixtime` ( ? ) and ( utc_timestamp - interval ? minute ) and `from_unixtime` ( ? ) \\u003e ( utc_timestamp - interval ? hour ) ;\",\n      \"digest\": \"85310c3dcab6bcdd1a6c04a6f254ab47b9ce73165d54fb9d1a75931d6fb7f15e\",\n      \"exec_count\": 1074,\n      \"sum_latency\": 12864785594,\n      \"max_latency\": 240024191,\n      \"min_latency\": 646545,\n      \"avg_latency\": 11978385,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 2,\n      \"sum_ru\": 44520.267921956365,\n      \"avg_ru\": 41.452763428264774\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select `event_logs` . `id` from `event_logs` where `event_logs` . `id` in ( ... )\",\n      \"digest\": \"ce7322d73316c5a7ded2d44f7e1be3444a525185d35475f43cefff8cde04c917\",\n      \"exec_count\": 5503,\n      \"sum_latency\": 11447587755,\n      \"max_latency\": 76657589,\n      \"min_latency\": 1367289,\n      \"avg_latency\": 2080244,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 2994.613453165679,\n      \"avg_ru\": 0.5441783487489876\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with `repos` as ( select `gr` . `repo_id` from `github_repos` `gr` where `gr` . `owner_id` = ? and `gr` . `repo_id` in ( ? ) ) , `current_period_new_participants` as ( select count ( ? ) as `new_participants` from `mv_repo_participants` `mrp` where `mrp` . `repo_id` in ( select `repo_id` from `repos` ) and `mrp` . `first_engagement_at` between ( now ( ) - interval ? day ) and now ( ) and `lower` ( `user_login` ) not like ? and `user_login` not in ( select `login` from `blacklist_users` limit ? ) ) , `past_period_new_participants` as ( select count ( ? ) as `new_participants` from `mv_repo_participants` `mrp` where `mrp` . `repo_id` in ( select `repo_id` from `repos` ) and `mrp` . `first_engagement_at` between ( now ( ) - interval ? day ) and ( now ( ) - interval ? day ) and `lower` ( `user_login` ) not like ? and `user_login` not in ( select `login` from `blacklist_users` limit ? ) ) select `cpnp` . `new_participants` as `current_period_total` , `ppnp` . `new_participants` as `past_...\",\n      \"digest\": \"42f60c15d73afdb89f9cf7e9c9928fae502396a00e4d2e74d902d7622ed82480\",\n      \"exec_count\": 407,\n      \"sum_latency\": 9450502399,\n      \"max_latency\": 86183839,\n      \"min_latency\": 5329637,\n      \"avg_latency\": 23219907,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 2,\n      \"sum_ru\": 28319.535983276364,\n      \"avg_ru\": 69.58116949207952\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `hackernews` . `users` ( `created` , `id` , `karma` , `last_fetch_at` , `about` ) values ( ... ) on duplicate key update `created` = values ( `created` ) , `karma` = values ( `karma` ) , `last_fetch_at` = values ( `last_fetch_at` ) , `about` = values ( `about` )\",\n      \"digest\": \"f9a8dcc813e4cf3e7939137fe52fd6ff641b693a88c079bdd1040b02af654f9f\",\n      \"exec_count\": 905,\n      \"sum_latency\": 6067675674,\n      \"max_latency\": 47886764,\n      \"min_latency\": 1260951,\n      \"avg_latency\": 6704614,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 17000.82401021323,\n      \"avg_ru\": 18.785440895263235\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `hackernews` . `users` ( `about` , `created` , `id` , `karma` , `last_fetch_at` ) values ( ... ) on duplicate key update `about` = values ( `about` ) , `created` = values ( `created` ) , `karma` = values ( `karma` ) , `last_fetch_at` = values ( `last_fetch_at` )\",\n      \"digest\": \"d1f5a9105c5b3c95571e77cb8c7f00daf8bf577090a7923cb53bd5b3339a7260\",\n      \"exec_count\": 806,\n      \"sum_latency\": 5370701263,\n      \"max_latency\": 127385230,\n      \"min_latency\": 1060033,\n      \"avg_latency\": 6663401,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 16681.610009765634,\n      \"avg_ru\": 20.69678661261245\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with `repos` as ( select `gr` . `repo_id` from `github_repos` `gr` where `gr` . `owner_id` = ? and `gr` . `repo_id` in ( ? ) ) , `current_period_active_participants` as ( select count ( distinct `user_login` ) as `active_participants` from `mv_repo_participants` where `repo_id` in ( select `repo_id` from `repos` ) and `last_engagement_at` between ( now ( ) - interval ? day ) and now ( ) and `lower` ( `user_login` ) not like ? and `user_login` not in ( select `login` from `blacklist_users` limit ? ) ) , `past_period_active_participants` as ( select count ( distinct `user_login` ) as `active_participants` from `mv_repo_participants` where `repo_id` in ( select `repo_id` from `repos` ) and `last_engagement_at` between ( now ( ) - interval ? day ) and ( now ( ) - interval ? day ) and `lower` ( `user_login` ) not like ? and `user_login` not in ( select `login` from `blacklist_users` limit ? ) ) select `cpnp` . `active_participants` as `current_period_total` , `ppnp` . `active_participant...\",\n      \"digest\": \"b14988c8d83c97fc1e17b5ff334dfd932c00253ad902b79474f249c0ce1a489f\",\n      \"exec_count\": 372,\n      \"sum_latency\": 4979299666,\n      \"max_latency\": 67781555,\n      \"min_latency\": 5331072,\n      \"avg_latency\": 13385214,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 2,\n      \"sum_ru\": 9547.273452758789,\n      \"avg_ru\": 25.664713582684918\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with `group_by_org` as ( select case when ( trim ( `gu` . `organization` ) = ? or `gu` . `organization` is ? ) then ? else `lower` ( replace ( `gu` . `organization` , ... ) ) end as `org_name` , count ( distinct `ge` . `actor_login` ) as `stargazers` from `github_events` `ge` left join `github_users` `gu` on `ge` . `actor_login` = `gu` . `login` where `ge` . `repo_id` = ( select `repo_id` from `github_repos` where `repo_name` = `concat` ( ... ) limit ? ) and `ge` . `type` = ? and `ge` . `action` = ? and `ge` . `created_at` \\u003e= ? and `ge` . `created_at` \\u003c= ? and if ( ? = true , `gu` . `organization` != ? , true ) group by `org_name` ) , `summary` as ( select sum ( `stargazers` ) as `total` from `group_by_org` ) select `org_name` , `stargazers` , `stargazers` / `total` as `percentage` from `group_by_org` , `summary` order by `stargazers` desc ;\",\n      \"digest\": \"bcd7135d761d7237779b21d885710f72f0757e287d7d82d37597f8a924f5cbeb\",\n      \"exec_count\": 39,\n      \"sum_latency\": 4509966449,\n      \"max_latency\": 1881207176,\n      \"min_latency\": 2863136,\n      \"avg_latency\": 115640165,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 26318.81174926764,\n      \"avg_ru\": 674.8413269042984\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `hackernews` . `items` ( `by` , `id` , `parent` , `text` , `time` , `type` , `last_fetch_at` ) values ( ... ) on duplicate key update `by` = values ( `by` ) , `parent` = values ( `parent` ) , `text` = values ( `text` ) , `time` = values ( `time` ) , `type` = values ( `type` ) , `last_fetch_at` = values ( `last_fetch_at` )\",\n      \"digest\": \"c75db291418be47315642664cf6986785283a0ea166b17802e9915b0431bced1\",\n      \"exec_count\": 569,\n      \"sum_latency\": 4430911656,\n      \"max_latency\": 196957079,\n      \"min_latency\": 3481355,\n      \"avg_latency\": 7787190,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 14463.449578857424,\n      \"avg_ru\": 25.419067801155403\n    },\n    {\n      \"summary_begin_time\": 1731382501,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"delete from `event_logs` where ( `created_at` \\u003c= ? ) limit ?\",\n      \"digest\": \"28994526549b36b499a9c31f545c6b5d99df9823651bcc69b8080e9d77ef1c8f\",\n      \"exec_count\": 4,\n      \"sum_latency\": 4178517412,\n      \"max_latency\": 1187361710,\n      \"min_latency\": 856306315,\n      \"avg_latency\": 1044629353,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 27637.983173624692,\n      \"avg_ru\": 6909.495793406173\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with `repos` as ( select `gr` . `repo_id` , `gr` . `repo_name` from `github_repos` `gr` where `gr` . `owner_id` = ? ) , `stars_per_country` as ( select if ( `gu` . `country_code` in ( ... ) , ? , `gu` . `country_code` ) as `country_code` , count ( ? ) as `stars` from `github_events` `ge` join `github_users` `gu` on `ge` . `actor_login` = `gu` . `login` where `ge` . `repo_id` in ( select `repo_id` from `repos` ) and `ge` . `type` = ? and `ge` . `action` = ? and `ge` . `actor_login` not like ? and `gu` . `country_code` not in ( ... ) and `ge` . `created_at` \\u003e ( now ( ) - interval ? month ) group by `gu` . `country_code` ) , `stars_total` as ( select sum ( `stars` ) as `stars_total` from `stars_per_country` ) select `spc` . `country_code` , `spc` . `stars` , `round` ( `spc` . `stars` / `st` . `stars_total` , ? ) as `percentage` from `stars_per_country` `spc` , `stars_total` `st` order by `spc` . `stars` desc limit ?\",\n      \"digest\": \"189fb2c80ca6b20954afb7c5078914e9a14fce477e78ca7e037bcb84b56ba5fa\",\n      \"exec_count\": 22,\n      \"sum_latency\": 4164196437,\n      \"max_latency\": 241357895,\n      \"min_latency\": 130952710,\n      \"avg_latency\": 189281656,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 11402.894204711929,\n      \"avg_ru\": 518.3133729414513\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `stats_api_requests` ( `client_ip` , `client_origin` , `method` , `path` , query , error , `status_code` , duration , `is_dev` ) values ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ... , false ) , ( ... , false , ....\",\n      \"digest\": \"64494b4e9ec3eaf5cce488d01d74bc8a5cf9ea4374fe4ff3e99809630c5fa002\",\n      \"exec_count\": 254,\n      \"sum_latency\": 3540458226,\n      \"max_latency\": 33768477,\n      \"min_latency\": 10935636,\n      \"avg_latency\": 13938811,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 49261.2087890625,\n      \"avg_ru\": 193.94176688607283\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `hackernews` . `items` ( `by` , `id` , `kids` , `parent` , `text` , `time` , `type` , `last_fetch_at` ) values ( ... ) on duplicate key update `by` = values ( `by` ) , `kids` = values ( `kids` ) , `parent` = values ( `parent` ) , `text` = values ( `text` ) , `time` = values ( `time` ) , `type` = values ( `type` ) , `last_fetch_at` = values ( `last_fetch_at` )\",\n      \"digest\": \"d73c9c9f6f5c69bc0937eaa47c352e516639a4d7188643c2162f5efe04c919f5\",\n      \"exec_count\": 516,\n      \"sum_latency\": 3318069183,\n      \"max_latency\": 19601128,\n      \"min_latency\": 3473463,\n      \"avg_latency\": 6430366,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 8462.864382934567,\n      \"avg_ru\": 16.400899966927454\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `hackernews` . `items` ( `by` , `descendants` , `id` , `kids` , `score` , `time` , `title` , `type` , `url` , `last_fetch_at` ) values ( ... ) on duplicate key update `by` = values ( `by` ) , `descendants` = values ( `descendants` ) , `kids` = values ( `kids` ) , `score` = values ( `score` ) , `time` = values ( `time` ) , `title` = values ( `title` ) , `type` = values ( `type` ) , `url` = values ( `url` ) , `last_fetch_at` = values ( `last_fetch_at` )\",\n      \"digest\": \"341281b2ef38825d8c7bb1c6aa69a1a4b4c95758f12ab9e10158156821bf9869\",\n      \"exec_count\": 464,\n      \"sum_latency\": 3188875808,\n      \"max_latency\": 97469284,\n      \"min_latency\": 3768896,\n      \"avg_latency\": 6872577,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 10564.28226928711,\n      \"avg_ru\": 22.767849718291185\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select * , date_add ( `updated_at` , interval `expires` second ) as `expired_at` from `cached_table_cache` where `cache_key` = ? and ( ( `expires` = ? ) or ( date_add ( `updated_at` , interval `expires` second ) \\u003e= now ( ) ) ) limit ? ;\",\n      \"digest\": \"b1571d293fa687def09da8c650cd11e3acdec6d29e683c84b74f129cfa3f1bcd\",\n      \"exec_count\": 1567,\n      \"sum_latency\": 3012144854,\n      \"max_latency\": 37930523,\n      \"min_latency\": 1136996,\n      \"avg_latency\": 1922236,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1016.0132303873709,\n      \"avg_ru\": 0.6483811297941103\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383591,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"474fb646cd29b8a5c8e94e664245c157df9306f11ad004ce0027d079fdebc2c7\",\n      \"exec_count\": 306,\n      \"sum_latency\": 3008445668,\n      \"max_latency\": 116401232,\n      \"min_latency\": 986926,\n      \"avg_latency\": 9831521,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 4911.326397705077,\n      \"avg_ru\": 16.050086267010055\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select `hackernews` . `items` . * from `hackernews` . `items` where `hackernews` . `items` . `id` = ? order by `hackernews` . `items` . `id` asc limit ?\",\n      \"digest\": \"f91beaf16e268d13d7168f75d001dbea71cda387ee17b8a261cf743bf2a053be\",\n      \"exec_count\": 3293,\n      \"sum_latency\": 2799228713,\n      \"max_latency\": 116118557,\n      \"min_latency\": 572232,\n      \"avg_latency\": 850054,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1594.247341918947,\n      \"avg_ru\": 0.4841322022225773\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with `repos` as ( select `gr` . `repo_id` from `github_repos` `gr` where `gr` . `owner_id` = ? ) , `stars_per_period` as ( select timestampdiff ( month , `created_at` , now ( ) ) div ? as `period` , count ( ? ) as `stars_total` from `github_events` `ge` where `ge` . `repo_id` in ( select `repo_id` from `repos` ) and type = ? and action = ? and `ge` . `actor_login` not like ? and `created_at` \\u003e ( now ( ) - interval ? month ) group by `period` ) , `current_period_stars` as ( select `stars_total` from `stars_per_period` where `period` = ? ) , `past_period_stars` as ( select `stars_total` from `stars_per_period` where `period` = ? ) select `cps` . `stars_total` as `current_period_total` , `pps` . `stars_total` as `past_period_total` , ( `cps` . `stars_total` - `pps` . `stars_total` ) / `pps` . `stars_total` as `growth_percentage` from `current_period_stars` `cps` , `past_period_stars` `pps` ;\",\n      \"digest\": \"3315fb3158e57316f8d3f57c85bb6a28ec0728f078d9669902a0993a7d922794\",\n      \"exec_count\": 48,\n      \"sum_latency\": 2747766820,\n      \"max_latency\": 79271005,\n      \"min_latency\": 9272489,\n      \"avg_latency\": 57245142,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 5546.544455973307,\n      \"avg_ru\": 115.55300949944389\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383591,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) on duplicate key update `repo_id` = `repo_id`\",\n      \"digest\": \"0ba0fe30c41664d40c19a7b47f92ded27c1095e3ea1813ed8be805ab761cde47\",\n      \"exec_count\": 302,\n      \"sum_latency\": 2418791012,\n      \"max_latency\": 17961194,\n      \"min_latency\": 3718574,\n      \"avg_latency\": 8009241,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 19048.573632812517,\n      \"avg_ru\": 63.07474712851827\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `cached_table_cache` ( `cache_key` , `cache_value` , `expires` ) values ( ... ) on duplicate key update `cache_value` = values ( `cache_value` ) , `expires` = values ( `expires` ) ;\",\n      \"digest\": \"a4e2f6f25e3ed687eabbc0ca100b2ada45d6ac827f15c29c8d92bba58e5a9418\",\n      \"exec_count\": 296,\n      \"sum_latency\": 2413357290,\n      \"max_latency\": 62499826,\n      \"min_latency\": 3821836,\n      \"avg_latency\": 8153234,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 12473.881120808917,\n      \"avg_ru\": 42.1414902730031\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select count ( ? ) as `cnt` , max ( `created_at` ) as `latest_created_at` , `unix_timestamp` ( max ( `created_at` ) ) as `latest_timestamp` from `github_events` where `created_at` between ( utc_timestamp - interval ? minute - interval `unix_timestamp` ( utc_timestamp - interval ? minute ) % ? second ) and ( utc_timestamp - interval ? minute - interval `unix_timestamp` ( utc_timestamp - interval ? minute ) % ? second ) group by ( ( `unix_timestamp` ( `created_at` ) - `unix_timestamp` ( utc_timestamp - interval ? minute - interval `unix_timestamp` ( utc_timestamp - interval ? minute ) % ? second ) ) - ( `unix_timestamp` ( `created_at` ) - `unix_timestamp` ( utc_timestamp - interval ? minute - interval `unix_timestamp` ( utc_timestamp - interval ? minute ) % ? second ) ) % ? ) order by `latest_timestamp` ;\",\n      \"digest\": \"289f8404a009ab0bcd011b3cc9ebed8f7df4c5dd3b47fe25e53b111ff7c0b6f9\",\n      \"exec_count\": 217,\n      \"sum_latency\": 2268638819,\n      \"max_latency\": 156854018,\n      \"min_latency\": 5560650,\n      \"avg_latency\": 10454556,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 3546.487251790363,\n      \"avg_ru\": 16.343259224840384\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"73fcf695719959387dddd6336fa4afd913e2c65c0065d224958d8d2a7a03489f\",\n      \"exec_count\": 67,\n      \"sum_latency\": 2011301955,\n      \"max_latency\": 435182144,\n      \"min_latency\": 1333804,\n      \"avg_latency\": 30019432,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1591.6728759765629,\n      \"avg_ru\": 23.756311581739745\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"b3980ccab61a988b0dde58ce44e04270c02aedc948bba60dddcb79138178caff\",\n      \"exec_count\": 53,\n      \"sum_latency\": 2002076920,\n      \"max_latency\": 433845589,\n      \"min_latency\": 1422042,\n      \"avg_latency\": 37775036,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1484.0759216308595,\n      \"avg_ru\": 28.00143248360112\n    },\n    {\n      \"summary_begin_time\": 1731382199,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"3d79ee2a3fc89a14e75219ec1744f5d048d4beb59d107959c8f321e9db37a212\",\n      \"exec_count\": 59,\n      \"sum_latency\": 1862758036,\n      \"max_latency\": 425485660,\n      \"min_latency\": 1342420,\n      \"avg_latency\": 31572170,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1392.667840576172,\n      \"avg_ru\": 23.604539670782575\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with `repos` as ( select `gr` . `repo_id` from `github_repos` `gr` where `gr` . `owner_id` = ? and `gr` . `repo_id` in ( ? ) ) select `user_login` as `login` , sum ( `engagements` ) as `engagements` from `mv_repo_daily_engagements` where `repo_id` in ( select `repo_id` from `repos` ) and day \\u003e ( now ( ) - interval ? day ) and `lower` ( `user_login` ) not like ? and `user_login` not in ( select `login` from `blacklist_users` limit ? ) group by `user_login` order by 2 desc limit ?\",\n      \"digest\": \"4c2414106811c4c9df4766cab0328fd0f3da61540e9e2c5a007189ce97d00bf3\",\n      \"exec_count\": 232,\n      \"sum_latency\": 1847116557,\n      \"max_latency\": 94316921,\n      \"min_latency\": 3842052,\n      \"avg_latency\": 7961709,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 605.0182647705078,\n      \"avg_ru\": 2.6078373481487405\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with recursive `seq` ( `idx` , `current_period_day` , `past_period_day` ) as ( select ? as `idx` , `date_format` ( current_date ( ) , ? ) as `current_period_day` , `date_format` ( date_sub ( current_date ( ) , interval ? day ) , ? ) as `past_period_day` union all select `idx` + ? as `idx` , `date_format` ( date_sub ( current_date ( ) , interval `idx` day ) , ? ) as `current_period_day` , `date_format` ( date_sub ( current_date ( ) , interval `idx` + ? day ) , ? ) as `past_period_day` from `seq` where ? = ? and `idx` \\u003c ? ) , `repos` as ( select `gr` . `repo_id` from `github_repos` `gr` where `gr` . `owner_id` = ? and `gr` . `repo_id` in ( ? ) ) , `group_by_day` as ( select timestampdiff ( day , day , current_date ( ) ) % ? + ? as `idx` , timestampdiff ( day , day , current_date ( ) ) div ? as `period` , day , `participants` from ( select `date_format` ( day , ? ) as day , count ( distinct `user_login` ) as `participants` from `mv_repo_daily_engagements` `mrde` where `repo_id` in ( se...\",\n      \"digest\": \"e0bd74cc8ea679b5d90247541649a83735affae6d3ef2cc0a314c4bb8ed253af\",\n      \"exec_count\": 87,\n      \"sum_latency\": 1698665560,\n      \"max_latency\": 60084232,\n      \"min_latency\": 9893057,\n      \"avg_latency\": 19524891,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 415.3587677001953,\n      \"avg_ru\": 4.774238709197647\n    },\n    {\n      \"summary_begin_time\": 1731383228,\n      \"summary_end_time\": 1731383531,\n      \"digest_text\": \"insert into `user_selected_stocks` ( `symbol` , time , high , low , open , close , `volume` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ...\",\n      \"digest\": \"0ae5f259e23bc838c2142dbf86fccd03f22100a5dd9904b9e7b905057d071193\",\n      \"exec_count\": 2,\n      \"sum_latency\": 1617878877,\n      \"max_latency\": 939491256,\n      \"min_latency\": 678387621,\n      \"avg_latency\": 808939438,\n      \"schema_name\": \"sp500insight\",\n      \"plan_count\": 1,\n      \"sum_ru\": 153.37692260742188,\n      \"avg_ru\": 76.68846130371094\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with recursive `seq` ( `idx` , `current_period_day` , `past_period_day` ) as ( select ? as `idx` , `date_format` ( current_date ( ) , ? ) as `current_period_day` , `date_format` ( date_sub ( current_date ( ) , interval ? day ) , ? ) as `past_period_day` union all select `idx` + ? as `idx` , `date_format` ( date_sub ( current_date ( ) , interval `idx` day ) , ? ) as `current_period_day` , `date_format` ( date_sub ( current_date ( ) , interval `idx` + ? day ) , ? ) as `past_period_day` from `seq` where ? = ? and `idx` \\u003c ? ) , `repos` as ( select `gr` . `repo_id` from `github_repos` `gr` where `gr` . `owner_id` = ? and `gr` . `repo_id` in ( ? ) ) , `group_by_day` as ( select timestampdiff ( day , day , current_date ( ) ) % ? + ? as `idx` , timestampdiff ( day , day , current_date ( ) ) div ? as `period` , day , `participants` from ( select `date_format` ( day , ? ) as day , count ( distinct `user_login` ) as `participants` from `mv_repo_daily_engagements` where `repo_id` in ( select `r...\",\n      \"digest\": \"bcdb049c41409f5eee6e0bab80ffa5983f96e2b72d60430554f7c2002a426f29\",\n      \"exec_count\": 124,\n      \"sum_latency\": 1590989778,\n      \"max_latency\": 33730386,\n      \"min_latency\": 5431784,\n      \"avg_latency\": 12830562,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 513.531391398112,\n      \"avg_ru\": 4.141382188694451\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with recursive `seq` ( `idx` , `current_period_day` , `past_period_day` ) as ( select ? as `idx` , `date_format` ( current_date ( ) , ? ) as `current_period_day` , `date_format` ( date_sub ( current_date ( ) , interval ? month ) , ? ) as `past_period_day` union all select `idx` + ? as `idx` , `date_format` ( date_sub ( current_date ( ) , interval `idx` month ) , ? ) as `current_period_day` , `date_format` ( date_sub ( current_date ( ) , interval `idx` + ? month ) , ? ) as `past_period_day` from `seq` where ? = ? and `idx` \\u003c ? ) , `repos` as ( select `gr` . `repo_id` from `github_repos` `gr` where `gr` . `owner_id` = ? ) , `group_by_day` as ( select timestampdiff ( month , day , current_date ( ) ) % ? + ? as `idx` , timestampdiff ( month , day , current_date ( ) ) div ? as `period` , day , `stars` from ( select `date_format` ( `created_at` , ? ) as day , count ( ? ) as `stars` from `github_events` `ge` where `repo_id` in ( select `repo_id` from `repos` ) and type = ? and action = ? a...\",\n      \"digest\": \"53dd5e9c4f6376b9aadbd5e4633a942d5ae60cee4c7e85cb64da9471a5250f70\",\n      \"exec_count\": 24,\n      \"sum_latency\": 1509381144,\n      \"max_latency\": 93906534,\n      \"min_latency\": 55317062,\n      \"avg_latency\": 62890881,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 2870.995052083333,\n      \"avg_ru\": 119.62479383680555\n    },\n    {\n      \"summary_begin_time\": 1731382320,\n      \"summary_end_time\": 1731383591,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"6c392545d1efbaeed9bde22bf43c89d57f81654cf937a8b1572adb4a15719ffe\",\n      \"exec_count\": 24,\n      \"sum_latency\": 1456644316,\n      \"max_latency\": 1037234554,\n      \"min_latency\": 12779487,\n      \"avg_latency\": 60693513,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 23037.91386718754,\n      \"avg_ru\": 959.9130777994809\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with `group_by_countries` as ( select case when trim ( `gu` . `country_code` ) in ( ... ) or `gu` . `country_code` is ? then ? else `gu` . `country_code` end as `country_code` , count ( ? ) as `stargazers` from `github_events` `ge` left join `github_users` `gu` on `ge` . `actor_login` = `gu` . `login` where `repo_id` = ( select `repo_id` from `github_repos` where `repo_name` = `concat` ( ... ) limit ? ) and `ge` . `type` = ? and `ge` . `action` = ? and `ge` . `created_at` \\u003e= ? and `ge` . `created_at` \\u003c= ? and if ( ? = true , `gu` . `country_code` not in ( ... ) , true ) group by 1 ) , `summary` as ( select sum ( `stargazers` ) as `total` from `group_by_countries` ) select `country_code` , `stargazers` , `stargazers` / `total` as `percentage` from `group_by_countries` , `summary` order by `stargazers` desc ;\",\n      \"digest\": \"e033077dd0a595f1c0f9818bb9678a8c2dcc0699b56c70937b6363bb844b660b\",\n      \"exec_count\": 41,\n      \"sum_latency\": 1313140075,\n      \"max_latency\": 194851089,\n      \"min_latency\": 3030051,\n      \"avg_latency\": 32027806,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 8417.493408203052,\n      \"avg_ru\": 205.3047172732452\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with `repos` as ( select `gr` . `repo_id` , `gr` . `repo_name` from `github_repos` `gr` where `gr` . `owner_id` = ? ) , `repos_with_stars` as ( select `repo_id` , count ( ? ) as `stars` from `github_events` `ge` where `ge` . `repo_id` in ( select `repo_id` from `repos` ) and `ge` . `type` = ? and `ge` . `action` = ? and `ge` . `actor_login` not like ? and `created_at` \\u003e ( now ( ) - interval ? month ) group by `repo_id` order by `stars` desc limit ? ) select `gr` . `repo_id` , `gr` . `repo_name` , `rws` . `stars` from `repos_with_stars` `rws` join `github_repos` `gr` using ( `repo_id` ) order by `stars` desc limit ?\",\n      \"digest\": \"544f8fc71f07c278592c339a9fa0be5b20076c6d1fe64d23d63826caca8bc477\",\n      \"exec_count\": 24,\n      \"sum_latency\": 1262086824,\n      \"max_latency\": 73459416,\n      \"min_latency\": 8896236,\n      \"avg_latency\": 52586951,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 2315.768294270832,\n      \"avg_ru\": 96.490345594618\n    },\n    {\n      \"summary_begin_time\": 1731382199,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"with `stars` as ( select `repo_id` , `created_at` , `row_number` ( ) `over` ( partition by `repo_id` , `actor_login` ) as `row_num` from `github_events` where type = ? and `repo_id` in ( ? ) ) , `stars_per_month` as ( select `repo_id` , `date_format` ( `created_at` , ? ) as `t_month` , count ( ? ) as `total` from `stars` where `row_num` = ? group by `repo_id` , `t_month` ) , `acc` as ( select `repo_id` , `t_month` , sum ( `total` ) `over` ( partition by `repo_id` order by `t_month` ) as `total` , `row_number` ( ) `over` ( partition by `repo_id` , `t_month` ) as `row_num` from `stars_per_month` ) select `repo_id` , `t_month` as `event_month` , `total` from `acc` where `row_num` = ? order by `t_month` ;\",\n      \"digest\": \"261349e506e11aebca564132f9343a765a42f2799fe0a40e5ef34b7134839f61\",\n      \"exec_count\": 10,\n      \"sum_latency\": 1248891884,\n      \"max_latency\": 464036186,\n      \"min_latency\": 3269270,\n      \"avg_latency\": 124889188,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 972.5673889160157,\n      \"avg_ru\": 97.25673889160157\n    },\n    {\n      \"summary_begin_time\": 1731382320,\n      \"summary_end_time\": 1731383651,\n      \"digest_text\": \"with recursive `seq` ( `idx` , `current_period_day` , `last_period_day` ) as ( select ? as `idx` , current_date ( ) as `current_period_day` , date_sub ( current_date ( ) , interval ? day ) as `last_period_day` union all select `idx` + ? as `idx` , date_sub ( current_date ( ) , interval `idx` day ) as `current_period_day` , date_sub ( current_date ( ) , interval `idx` + ? day ) as `last_period_day` from `seq` where `idx` \\u003c ? ) , `group_by_day` as ( select `day_offset` % ? + ? as `idx` , `day_offset` div ? as `period` , day , `contributors` from ( select ( `datediff` ( current_date ( ) , day ) ) as `day_offset` , day , `contributors` from ( select `date_format` ( `created_at` , ? ) as day , count ( distinct `actor_id` ) as `contributors` from `github_events` `ge` where `repo_id` = ? and ( ( type = ? and action = ? ) or ( type = ? and action = ? ) or ( type = ? and action = ? ) or ( type = ? and action = ? ) or ( type = ? and action = ? ) or ( type = ? and action = ? ) ) and `created_a...\",\n      \"digest\": \"1334130de9e7a3df7b13b40f87d2e653e93a0ce59d1be81cc711459f8bfaf8f3\",\n      \"exec_count\": 5,\n      \"sum_latency\": 1101457190,\n      \"max_latency\": 965876538,\n      \"min_latency\": 13541729,\n      \"avg_latency\": 220291438,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 3637.7133768717463,\n      \"avg_ru\": 727.5426753743493\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"ed97b012822bd72ba7415b3dd81de502f5b9de37d3be1a714f2e71e6cd557b67\",\n      \"exec_count\": 46,\n      \"sum_latency\": 1073595351,\n      \"max_latency\": 419024368,\n      \"min_latency\": 1412409,\n      \"avg_latency\": 23339029,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1150.2638916015628,\n      \"avg_ru\": 25.005736773947017\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with `group_by_org` as ( select case when ( trim ( `gu` . `organization` ) in ( ... ) or `gu` . `organization` is ? ) then ? else `lower` ( replace ( `gu` . `organization` , ... ) ) end as `org_name` , count ( distinct `ge` . `actor_login` ) as `pull_request_creators` from `github_events` `ge` left join `github_users` `gu` on `ge` . `actor_login` = `gu` . `login` where `ge` . `repo_id` = ( select `repo_id` from `github_repos` where `repo_name` = `concat` ( ... ) limit ? ) and `ge` . `type` = ? and `ge` . `action` = ? and `ge` . `created_at` \\u003e= ? and `ge` . `created_at` \\u003c= ? and if ( ? = true , `gu` . `organization` != ? , true ) group by `org_name` ) , `summary` as ( select sum ( `pull_request_creators` ) as `total` from `group_by_org` ) select `org_name` , `pull_request_creators` , `pull_request_creators` / `total` as `percentage` from `group_by_org` , `summary` order by `pull_request_creators` desc ;\",\n      \"digest\": \"5ee52db5067308c2055c2e9a315c9b52b4f252adc74af5c8c6d954d575d763bc\",\n      \"exec_count\": 41,\n      \"sum_latency\": 1041289299,\n      \"max_latency\": 455243070,\n      \"min_latency\": 3187253,\n      \"avg_latency\": 25397299,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1497.2536804199224,\n      \"avg_ru\": 36.5183824492664\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select * from ( select `id` , type , action , `actor_id` , `actor_login` , `repo_id` , `repo_name` , `number` , `pr_merged` , `created_at` from `github_events` where `created_at` between ( utc_timestamp - interval ? minute - interval `unix_timestamp` ( utc_timestamp - interval ? minute ) % ? second ) and ( utc_timestamp - interval ? minute - interval `unix_timestamp` ( utc_timestamp - interval ? minute ) % ? second ) and `actor_login` not like ? and `actor_login` not like ? and type in ( ... ) limit ? ) `sub` order by `created_at` desc ;\",\n      \"digest\": \"6130f94bf789102647203af567bfed857c0fbd8205ca89f930c33f74d6160df9\",\n      \"exec_count\": 111,\n      \"sum_latency\": 1025730817,\n      \"max_latency\": 43800121,\n      \"min_latency\": 4215869,\n      \"avg_latency\": 9240818,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1044.7612101236978,\n      \"avg_ru\": 9.412263154267547\n    },\n    {\n      \"summary_begin_time\": 1731382199,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"with recursive `seq` ( `idx` , `current_period_day` , `last_period_day` ) as ( select ? as `idx` , current_date ( ) as `current_period_day` , date_sub ( current_date ( ) , interval ? day ) as `last_period_day` union all select `idx` + ? as `idx` , date_sub ( current_date ( ) , interval `idx` day ) as `current_period_day` , date_sub ( current_date ( ) , interval `idx` + ? day ) as `last_period_day` from `seq` where `idx` \\u003c ? ) , `group_by_day` as ( select `day_offset` % ? + ? as `idx` , `day_offset` div ? as `period` , day , `stars` from ( select ( `datediff` ( current_date ( ) , day ) ) as `day_offset` , day , `stars` from ( select `date_format` ( `created_at` , ? ) as day , count ( ? ) as `stars` from `github_events` `ge` where type = ? and `repo_id` = ? and `created_at` \\u003e date_sub ( current_date ( ) , interval ? day ) group by day order by day ) `sub` ) `sub2` ) , `last_28_days` as ( select `idx` , day , `stars` from `group_by_day` where `period` = ? ) , `last_2nd_28_days` as ( se...\",\n      \"digest\": \"c14f9770ffc525fe5a3b083a3baa2d06786fa8ed444dc5bb487aed4ba2621b08\",\n      \"exec_count\": 15,\n      \"sum_latency\": 1004317221,\n      \"max_latency\": 552761658,\n      \"min_latency\": 4901052,\n      \"avg_latency\": 66954481,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1878.8455454508464,\n      \"avg_ru\": 125.2563696967231\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `stats_query_summary` ( `query_name` , `digest_text` , `executed_at` ) values ( ... ) ;\",\n      \"digest\": \"db2684d569a499b5a91d12abc419988153f2c3abccf80584717c7ab29eafac46\",\n      \"exec_count\": 154,\n      \"sum_latency\": 963053637,\n      \"max_latency\": 14405505,\n      \"min_latency\": 3251030,\n      \"avg_latency\": 6253595,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 2793.921875,\n      \"avg_ru\": 18.142349837662337\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select `hackernews` . `users` . * from `hackernews` . `users` where `hackernews` . `users` . `id` = ? order by `hackernews` . `users` . `id` asc limit ?\",\n      \"digest\": \"297a56834acc252f7e0fbb8c4d1820bb4f2cf1fb9907ab5ad2f19883cb4edb45\",\n      \"exec_count\": 1136,\n      \"sum_latency\": 947391617,\n      \"max_latency\": 21853770,\n      \"min_latency\": 576826,\n      \"avg_latency\": 833971,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 542.692946370443,\n      \"avg_ru\": 0.47772266405848857\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"with `group_by_org` as ( select case when ( trim ( `gu` . `organization` ) = ? or `gu` . `organization` is ? ) then ? else `lower` ( replace ( `gu` . `organization` , ... ) ) end as `org_name` , count ( distinct `ge` . `actor_login` ) as `issue_creators` from `github_events` `ge` left join `github_users` `gu` on `ge` . `actor_login` = `gu` . `login` where `ge` . `repo_id` = ( select `repo_id` from `github_repos` where `repo_name` = `concat` ( ... ) limit ? ) and `ge` . `type` = ? and `ge` . `action` = ? and `ge` . `created_at` \\u003e= ? and `ge` . `created_at` \\u003c= ? and if ( ? = true , `gu` . `organization` != ? , true ) group by `org_name` ) , `summary` as ( select sum ( `issue_creators` ) as `total` from `group_by_org` ) select `org_name` , `issue_creators` , `issue_creators` / `total` as `percentage` from `group_by_org` , `summary` order by `issue_creators` desc ;\",\n      \"digest\": \"a45cef35e7c1a830a64438920470cfdb0ca2a1aeba125619a74664fac0726c8b\",\n      \"exec_count\": 44,\n      \"sum_latency\": 920473488,\n      \"max_latency\": 199480754,\n      \"min_latency\": 3111569,\n      \"avg_latency\": 20919852,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 2952.3678904215476,\n      \"avg_ru\": 67.09927023685336\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"74ea47789ccb49e1223fff49e5a235a6682c187c1d730f58bcb8394834031c1d\",\n      \"exec_count\": 53,\n      \"sum_latency\": 840881453,\n      \"max_latency\": 451761629,\n      \"min_latency\": 1370554,\n      \"avg_latency\": 15865687,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1398.5346191406252,\n      \"avg_ru\": 26.38744564416274\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"b653d71f35b4c383bc75b1e37758069dc24b9cbe49ebd0b600fc58b67d0b72aa\",\n      \"exec_count\": 59,\n      \"sum_latency\": 824670270,\n      \"max_latency\": 431918979,\n      \"min_latency\": 1447396,\n      \"avg_latency\": 13977462,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1415.9999969482424,\n      \"avg_ru\": 23.999999948275295\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"1288a26c5196a52a854ef7e29009bdff403c3b80482adfe428681a9064f3128c\",\n      \"exec_count\": 38,\n      \"sum_latency\": 817616437,\n      \"max_latency\": 243835786,\n      \"min_latency\": 10511410,\n      \"avg_latency\": 21516222,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 28770.45371093756,\n      \"avg_ru\": 757.1172029194095\n    },\n    {\n      \"summary_begin_time\": 1731382199,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"9e97aff0e4cbc4d845f39683f95f21f8e58c104670c2b6e109cf89f620df6840\",\n      \"exec_count\": 41,\n      \"sum_latency\": 808675540,\n      \"max_latency\": 38805032,\n      \"min_latency\": 13343127,\n      \"avg_latency\": 19723793,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 39493.11796875007,\n      \"avg_ru\": 963.2467797256114\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"d55960a69cdb138f874f4f0a7f713b2e79a343f25e4dd9fd845adf677d4a5d18\",\n      \"exec_count\": 51,\n      \"sum_latency\": 778018853,\n      \"max_latency\": 437054877,\n      \"min_latency\": 1468573,\n      \"avg_latency\": 15255271,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1363.7450164794925,\n      \"avg_ru\": 26.74009836234299\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"dec90e4c5c2feb4bf9dcb6a9afee6c6924e2d1be319a6a691674edb138c895e6\",\n      \"exec_count\": 52,\n      \"sum_latency\": 776868288,\n      \"max_latency\": 439421467,\n      \"min_latency\": 1271757,\n      \"avg_latency\": 14939774,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1210.2992309570313,\n      \"avg_ru\": 23.274985210712142\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select max ( `stackoverflow` . `questions` . `last_activity_date` ) from `stackoverflow` . `questions`\",\n      \"digest\": \"e24f6aabf2a3e11712a8e85240556f4568b4b27100cba9b2344a9371d0fd8914\",\n      \"exec_count\": 75,\n      \"sum_latency\": 772006748,\n      \"max_latency\": 16403725,\n      \"min_latency\": 8685480,\n      \"avg_latency\": 10293423,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 243.96473185221353,\n      \"avg_ru\": 3.252863091362847\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"with `group_by_countries` as ( select case when trim ( `gu` . `country_code` ) in ( ... ) or `gu` . `country_code` is ? then ? else `gu` . `country_code` end as `country_code` , count ( distinct `actor_login` ) as `issue_creators` from `github_events` `ge` left join `github_users` `gu` on `ge` . `actor_login` = `gu` . `login` where `repo_id` = ( select `repo_id` from `github_repos` where `repo_name` = `concat` ( ... ) limit ? ) and `ge` . `type` = ? and `ge` . `action` = ? and `ge` . `created_at` \\u003e= ? and `ge` . `created_at` \\u003c= ? and if ( ? = true , `gu` . `country_code` not in ( ... ) , true ) group by 1 ) , `summary` as ( select sum ( `issue_creators` ) as `total` from `group_by_countries` ) select `country_code` , `issue_creators` , `issue_creators` / `total` as `percentage` from `group_by_countries` , `summary` order by `issue_creators` desc ;\",\n      \"digest\": \"1021af16579c112d553fc838b50d38000149abe820866a35a36d5f0237b43cee\",\n      \"exec_count\": 39,\n      \"sum_latency\": 711147084,\n      \"max_latency\": 133383170,\n      \"min_latency\": 5874168,\n      \"avg_latency\": 18234540,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 2012.7801208496226,\n      \"avg_ru\": 51.609746688451864\n    },\n    {\n      \"summary_begin_time\": 1731382501,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"select `table_name` from ( select * from `information_schema` . `tables` where `table_schema` = database ( ) ) `_subquery`\",\n      \"digest\": \"11d1f137ebaedbc58ec1b3af5af1db986edca323b2468ee76e5fb8d436dc5ff2\",\n      \"exec_count\": 4,\n      \"sum_latency\": 680785508,\n      \"max_latency\": 265491809,\n      \"min_latency\": 106820430,\n      \"avg_latency\": 170196377,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"5691fd1a9990db5a3a281408b66425d109559cfb310b1294aec3b4b62f727463\",\n      \"exec_count\": 39,\n      \"sum_latency\": 676958032,\n      \"max_latency\": 25051963,\n      \"min_latency\": 11276691,\n      \"avg_latency\": 17357898,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 34907.15742187507,\n      \"avg_ru\": 895.0553185096171\n    },\n    {\n      \"summary_begin_time\": 1731383410,\n      \"summary_end_time\": 1731383470,\n      \"digest_text\": \"insert into `user_selected_stocks` ( `symbol` , time , high , low , open , close , `volume` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ...\",\n      \"digest\": \"28d0bbdd695d979f5f3ca250eb3a5fd2aa4da49756f4560cabd1e784d943be3c\",\n      \"exec_count\": 1,\n      \"sum_latency\": 675751462,\n      \"max_latency\": 675751462,\n      \"min_latency\": 675751462,\n      \"avg_latency\": 675751462,\n      \"schema_name\": \"sp500insight\",\n      \"plan_count\": 1,\n      \"sum_ru\": 83.95759989420573,\n      \"avg_ru\": 83.95759989420573\n    },\n    {\n      \"summary_begin_time\": 1731382199,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"03ac2dab2f7857321f73bcaa5a63e4ffce264a44122a13715f6ee920bd74dd75\",\n      \"exec_count\": 55,\n      \"sum_latency\": 673594410,\n      \"max_latency\": 329768071,\n      \"min_latency\": 1371432,\n      \"avg_latency\": 12247171,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1244.2515136718753,\n      \"avg_ru\": 22.622754794034098\n    },\n    {\n      \"summary_begin_time\": 1731383711,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `user_selected_stocks` ( `symbol` , time , high , low , open , close , `volume` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ...\",\n      \"digest\": \"403b2fb3af215559f5de35afd158d6a00fc097d803259d8e137723bfa4e89667\",\n      \"exec_count\": 1,\n      \"sum_latency\": 665152498,\n      \"max_latency\": 665152498,\n      \"min_latency\": 665152498,\n      \"avg_latency\": 665152498,\n      \"schema_name\": \"sp500insight\",\n      \"plan_count\": 1,\n      \"sum_ru\": 81.39517110188801,\n      \"avg_ru\": 81.39517110188801\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"6ee72b12462872c788b9debcc8a3de80f52714b4a14dae52278672ba84b2faf3\",\n      \"exec_count\": 35,\n      \"sum_latency\": 662265823,\n      \"max_latency\": 26716443,\n      \"min_latency\": 12770355,\n      \"avg_latency\": 18921880,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 34745.083398437564,\n      \"avg_ru\": 992.7166685267875\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"7e2e65f36669580042af9ffbc0685f1263f569b73d1089ce824efd596fe696d2\",\n      \"exec_count\": 32,\n      \"sum_latency\": 659880103,\n      \"max_latency\": 35671531,\n      \"min_latency\": 13681563,\n      \"avg_latency\": 20621253,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 31788.161914062548,\n      \"avg_ru\": 993.3800598144546\n    },\n    {\n      \"summary_begin_time\": 1731383349,\n      \"summary_end_time\": 1731383410,\n      \"digest_text\": \"insert into `user_selected_stocks` ( `symbol` , time , high , low , open , close , `volume` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ...\",\n      \"digest\": \"e5516bddd5acb7e61caf2f3bfeeefac63fe52ef4af00252a2445b667b6ce41ec\",\n      \"exec_count\": 1,\n      \"sum_latency\": 650773372,\n      \"max_latency\": 650773372,\n      \"min_latency\": 650773372,\n      \"avg_latency\": 650773372,\n      \"schema_name\": \"sp500insight\",\n      \"plan_count\": 1,\n      \"sum_ru\": 77.02444864908854,\n      \"avg_ru\": 77.02444864908854\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with `repos` as ( select `gr` . `repo_id` from `github_repos` `gr` where `gr` . `owner_id` = ? ) , `opened_issues` as ( select `sub` . `repo_id` , `sub` . `number` , `sub` . `opened_by` , `sub` . `opened_at` , timestampdiff ( month , `opened_at` , now ( ) ) div ? as `period` from ( select `ge` . `repo_id` , `ge` . `number` , `ge` . `actor_login` as `opened_by` , `ge` . `created_at` as `opened_at` , `row_number` ( ) `over` ( partition by `ge` . `repo_id` , `ge` . `number` order by `ge` . `created_at` ) as `times` from `github_events` `ge` where `ge` . `repo_id` in ( select `repo_id` from `repos` ) and `ge` . `type` = ? and `ge` . `action` = ? and `ge` . `actor_login` not like ? and `created_at` \\u003e ( now ( ) - interval ? month ) ) `sub` where `sub` . `times` = ? ) , `closed_issues` as ( select `sub` . `repo_id` , `sub` . `number` , `sub` . `closed_by` , `sub` . `closed_at` , if ( `sub` . `closed_by` = `oi` . `opened_by` , ... ) as type , timestampdiff ( month , `closed_at` , now ( ) ) ...\",\n      \"digest\": \"5ce933429ea39c10882e6b1c03b544bf14cba788f343764d3ae387d68a07f300\",\n      \"exec_count\": 25,\n      \"sum_latency\": 649524768,\n      \"max_latency\": 43541222,\n      \"min_latency\": 8889050,\n      \"avg_latency\": 25980990,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1023.7721557617188,\n      \"avg_ru\": 40.95088623046875\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"with `group_by_countries` as ( select case when trim ( `gu` . `country_code` ) in ( ... ) or `gu` . `country_code` is ? then ? else `gu` . `country_code` end as `country_code` , count ( distinct `actor_login` ) as `pull_request_creators` from `github_events` `ge` left join `github_users` `gu` on `ge` . `actor_login` = `gu` . `login` where `repo_id` = ( select `repo_id` from `github_repos` where `repo_name` = `concat` ( ... ) limit ? ) and `ge` . `type` = ? and `ge` . `action` = ? and `ge` . `created_at` \\u003e= ? and `ge` . `created_at` \\u003c= ? and if ( ? = true , `gu` . `country_code` not in ( ... ) , true ) group by 1 ) , `summary` as ( select sum ( `pull_request_creators` ) as `total` from `group_by_countries` ) select `country_code` , `pull_request_creators` , `pull_request_creators` / `total` as `percentage` from `group_by_countries` , `summary` order by `pull_request_creators` desc ;\",\n      \"digest\": \"73a63750840e80dd32470565af2391380ad047b695bc34464102b617418f47ab\",\n      \"exec_count\": 41,\n      \"sum_latency\": 638925138,\n      \"max_latency\": 143988576,\n      \"min_latency\": 2854315,\n      \"avg_latency\": 15583539,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1423.0299194336133,\n      \"avg_ru\": 34.70804681545398\n    },\n    {\n      \"summary_begin_time\": 1731382199,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) on duplicate key update `repo_id` = `repo_id`\",\n      \"digest\": \"72dd244c69f070da35d814162e43a9954c30bb024322947c360da503ebb99fae\",\n      \"exec_count\": 46,\n      \"sum_latency\": 622690816,\n      \"max_latency\": 25715050,\n      \"min_latency\": 8841393,\n      \"avg_latency\": 13536756,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 27895.82441406255,\n      \"avg_ru\": 606.430965523099\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"e280a493a87b4378d6b1fe54376fa3bac12e4cb826100ef240e3195f68927fe7\",\n      \"exec_count\": 45,\n      \"sum_latency\": 618974616,\n      \"max_latency\": 321782376,\n      \"min_latency\": 1232043,\n      \"avg_latency\": 13754991,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 940.5014648437502,\n      \"avg_ru\": 20.90003255208334\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383651,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , true , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , fa...\",\n      \"digest\": \"23827b80d5d7ce1b1c5093336788caa1b2e82eaf6cb7a9bd475eec22459ae8a9\",\n      \"exec_count\": 32,\n      \"sum_latency\": 613243411,\n      \"max_latency\": 34989642,\n      \"min_latency\": 13569842,\n      \"avg_latency\": 19163856,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 31012.989843750056,\n      \"avg_ru\": 969.1559326171893\n    },\n    {\n      \"summary_begin_time\": 1731382320,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"76a88a37a22698d98fd3430d73f4193b05554dd58fb2e20447bcfdbce973d8b9\",\n      \"exec_count\": 44,\n      \"sum_latency\": 608993211,\n      \"max_latency\": 322896418,\n      \"min_latency\": 1334624,\n      \"avg_latency\": 13840754,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1033.7263854980472,\n      \"avg_ru\": 23.49378148859198\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) on duplicate key update `repo_id` =...\",\n      \"digest\": \"7260abf4bcf52f42b81c694c810ff89baf3460166124c2ecac69667ba7e45f9f\",\n      \"exec_count\": 44,\n      \"sum_latency\": 602588507,\n      \"max_latency\": 24112511,\n      \"min_latency\": 9648709,\n      \"avg_latency\": 13695193,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 28618.640625000055,\n      \"avg_ru\": 650.423650568183\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"666ef0230b9e5f335e0796a4ef5a1bd60e49a4888fd2f8f8b7aa478bbeb060d2\",\n      \"exec_count\": 29,\n      \"sum_latency\": 591206423,\n      \"max_latency\": 32135656,\n      \"min_latency\": 13203150,\n      \"avg_latency\": 20386428,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 29734.044140625047,\n      \"avg_ru\": 1025.3118669181051\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , true , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , fa...\",\n      \"digest\": \"5ee302301fc6a0cbd47ad0e5fd712c8112c6a241d12f7b664503b6863c275337\",\n      \"exec_count\": 29,\n      \"sum_latency\": 588285815,\n      \"max_latency\": 37018786,\n      \"min_latency\": 12393782,\n      \"avg_latency\": 20285717,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 28254.164648437545,\n      \"avg_ru\": 974.2815396012946\n    },\n    {\n      \"summary_begin_time\": 1731382199,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , t...\",\n      \"digest\": \"33ab890189ec0d13d3584b416ef91ca83fd8ff6575280cefdddd704343ea6ab7\",\n      \"exec_count\": 32,\n      \"sum_latency\": 585414371,\n      \"max_latency\": 31358650,\n      \"min_latency\": 12147916,\n      \"avg_latency\": 18294199,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 31361.679492187555,\n      \"avg_ru\": 980.0524841308611\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , true , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , fa...\",\n      \"digest\": \"3fe678f57478b0d00cd0474ab610e300b7108401a5752e883d497e6fec0455a8\",\n      \"exec_count\": 26,\n      \"sum_latency\": 571731248,\n      \"max_latency\": 61101402,\n      \"min_latency\": 11581232,\n      \"avg_latency\": 21989663,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 26078.089257812535,\n      \"avg_ru\": 1003.0034329927898\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"549508b9ffd6f3967fca4903e488e53687788f17a63a53e5f6414bf67939de0f\",\n      \"exec_count\": 33,\n      \"sum_latency\": 565226423,\n      \"max_latency\": 30943200,\n      \"min_latency\": 10632057,\n      \"avg_latency\": 17128073,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 27965.964648437555,\n      \"avg_ru\": 847.4534741950774\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , true , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , fa...\",\n      \"digest\": \"e0512fdfea3f6ea04669b371c22ff85bff0f553dbb2aa7f4e752b7769e8711da\",\n      \"exec_count\": 34,\n      \"sum_latency\": 556787924,\n      \"max_latency\": 24783240,\n      \"min_latency\": 11182661,\n      \"avg_latency\": 16376115,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 31707.213281250057,\n      \"avg_ru\": 932.5650965073546\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"fae06a702ea678dc76c3e678635ce99857e8babd7d84774beff5ae57054d4d1b\",\n      \"exec_count\": 32,\n      \"sum_latency\": 552981680,\n      \"max_latency\": 29924133,\n      \"min_latency\": 12782852,\n      \"avg_latency\": 17280677,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 31657.813867187553,\n      \"avg_ru\": 989.306683349611\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"f67fb80d4fb941ffeb34313f10e0b6e3e42ddb925d60f88ebf70908c519e2779\",\n      \"exec_count\": 29,\n      \"sum_latency\": 552313097,\n      \"max_latency\": 35951441,\n      \"min_latency\": 12958538,\n      \"avg_latency\": 19045279,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 28005.19492187505,\n      \"avg_ru\": 965.696376616381\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"f7988c0be94bac0f27e693e28923ac9cfd2e3c6e855934b4b33432c44cb8b293\",\n      \"exec_count\": 25,\n      \"sum_latency\": 546585826,\n      \"max_latency\": 35870135,\n      \"min_latency\": 12771118,\n      \"avg_latency\": 21863433,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 24851.326562500035,\n      \"avg_ru\": 994.0530625000014\n    },\n    {\n      \"summary_begin_time\": 1731382259,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"97d1153ba080a7e7d34cc5d1318df10c86b7e5e9a955e4b7dded37fc853df35d\",\n      \"exec_count\": 31,\n      \"sum_latency\": 544385805,\n      \"max_latency\": 29593928,\n      \"min_latency\": 12313174,\n      \"avg_latency\": 17560832,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 27984.556250000056,\n      \"avg_ru\": 902.7276209677437\n    },\n    {\n      \"summary_begin_time\": 1731382320,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"81fbe0383de24431ff55007851fcfa06e149e156ff7afd5ac021ef640428b7e9\",\n      \"exec_count\": 34,\n      \"sum_latency\": 535964376,\n      \"max_latency\": 25651613,\n      \"min_latency\": 9635154,\n      \"avg_latency\": 15763658,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 25622.10078125005,\n      \"avg_ru\": 753.5911994485309\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"fdc2797c1cc12a82f27ddd78a1191dd372fff64c90dcce94a9ac1cc2c2d8553f\",\n      \"exec_count\": 34,\n      \"sum_latency\": 528420374,\n      \"max_latency\": 26504169,\n      \"min_latency\": 10221376,\n      \"avg_latency\": 15541775,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 27887.14023437506,\n      \"avg_ru\": 820.2100068933842\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) on duplicate key update `repo_id` = `repo_id`\",\n      \"digest\": \"c4fbafaa2a8738267641de2efab102a1271de41ba35b380878817534e3990bb7\",\n      \"exec_count\": 54,\n      \"sum_latency\": 521182484,\n      \"max_latency\": 24719165,\n      \"min_latency\": 5416051,\n      \"avg_latency\": 9651527,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 11947.558789062496,\n      \"avg_ru\": 221.2510886863425\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383711,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , true , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , fa...\",\n      \"digest\": \"c1dedf3fe7c518e9bdf1838710959139b6ffecd3e2dbafef8ebd0ae990cedb8e\",\n      \"exec_count\": 25,\n      \"sum_latency\": 519629636,\n      \"max_latency\": 33692766,\n      \"min_latency\": 12817903,\n      \"avg_latency\": 20785185,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 25254.545312500042,\n      \"avg_ru\": 1010.1818125000017\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"594f84614a0cc70fc88c7162dc3888f08f5051a3468bc25f9031a517f17422be\",\n      \"exec_count\": 35,\n      \"sum_latency\": 519093034,\n      \"max_latency\": 33371741,\n      \"min_latency\": 9878871,\n      \"avg_latency\": 14831229,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 24664.884375000052,\n      \"avg_ru\": 704.7109821428586\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383651,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , f...\",\n      \"digest\": \"8828b349fb62fad122792fb83b7b50f2ccfa98d517996a8633351543b051418e\",\n      \"exec_count\": 31,\n      \"sum_latency\": 517608676,\n      \"max_latency\": 26018229,\n      \"min_latency\": 11901188,\n      \"avg_latency\": 16697054,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 27787.69570312506,\n      \"avg_ru\": 896.3772807459696\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `event_logs` ( `id` , `created_at` ) values ( ... ) , ( ... ) , ( ... ) , ( ... ) , ( ... ) on duplicate key update `id` = `id`\",\n      \"digest\": \"30c40f6a8dd91874ead39aa378f944e8ef401b55d0884c967c8cb813b91ab1fd\",\n      \"exec_count\": 60,\n      \"sum_latency\": 507610013,\n      \"max_latency\": 94767855,\n      \"min_latency\": 1026368,\n      \"avg_latency\": 8460166,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 1034.554217529297,\n      \"avg_ru\": 17.242570292154948\n    },\n    {\n      \"summary_begin_time\": 1731382199,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , true , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , fa...\",\n      \"digest\": \"aca179c8ac10ad90b2d2eb06546990bf7ec57d40f514f198c330154e266e04bd\",\n      \"exec_count\": 26,\n      \"sum_latency\": 506894936,\n      \"max_latency\": 35421934,\n      \"min_latency\": 13233366,\n      \"avg_latency\": 19495959,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 25179.052343750045,\n      \"avg_ru\": 968.4250901442325\n    },\n    {\n      \"summary_begin_time\": 1731382501,\n      \"summary_end_time\": 1731383651,\n      \"digest_text\": \"with `group_by_area` as ( select `gu` . `country_code` as `country_or_area` , count ( ? ) as `cnt` from `github_events` `ge` left join `github_users` `gu` on `ge` . `actor_login` = `gu` . `login` where `repo_id` in ( ? ) and `ge` . `type` = ? and `ge` . `action` = ? and `gu` . `country_code` not in ( ... ) group by `country_or_area` ) , `summary` as ( select sum ( `cnt` ) as `total` from `group_by_area` ) select `country_or_area` , `cnt` as `count` , `cnt` / `summary` . `total` as `percentage` from `group_by_area` , `summary` order by `cnt` desc ;\",\n      \"digest\": \"b4d08c7d6845f9df9d89f57d88d4a9bfe0dd4068d1771a08051a2983d0e77ef1\",\n      \"exec_count\": 4,\n      \"sum_latency\": 505267035,\n      \"max_latency\": 269694489,\n      \"min_latency\": 6887430,\n      \"avg_latency\": 126316758,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 4468.563988240472,\n      \"avg_ru\": 1117.140997060118\n    },\n    {\n      \"summary_begin_time\": 1731382380,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) on duplicat...\",\n      \"digest\": \"beac496de5be37a8d87f52aedc7280b0982b305313c549f1f18c5d83cf03a26c\",\n      \"exec_count\": 34,\n      \"sum_latency\": 502846488,\n      \"max_latency\": 23674978,\n      \"min_latency\": 9650809,\n      \"avg_latency\": 14789602,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 23553.674023437547,\n      \"avg_ru\": 692.7551183363985\n    },\n    {\n      \"summary_begin_time\": 1731382139,\n      \"summary_end_time\": 1731383772,\n      \"digest_text\": \"insert into `github_events` ( `repo_id` , `repo_name` , `language` , `actor_id` , `actor_login` , `additions` , `deletions` , `action` , `commit_id` , `number` , `org_id` , `org_login` , `pr_merged` , `state` , `pr_merged_at` , `closed_at` , `comments` , `pr_or_issue_id` , `pr_changed_files` , `pr_review_comments` , `event_day` , `event_month` , `event_year` , `push_size` , `push_distinct_size` , `id` , `type` , `created_at` , `pr_or_issue_created_at` , `creator_user_id` , `creator_user_login` ) values ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , true , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , false , ... ) , ( ... , fa...\",\n      \"digest\": \"a30fb47209c738ec037bae041bee36a3f916588f9b8706275baa1a083a79e65d\",\n      \"exec_count\": 27,\n      \"sum_latency\": 501862451,\n      \"max_latency\": 31312582,\n      \"min_latency\": 13809735,\n      \"avg_latency\": 18587498,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_count\": 1,\n      \"sum_ru\": 26914.987304687544,\n      \"avg_ru\": 996.8513816550942\n    }\n  ],\n  \"nextPageToken\": \"\",\n  \"totalSize\": 10\n}\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/sample-res/statement-plans-detail.json",
    "content": "{\n  \"summary_begin_time\": 1731395228,\n  \"summary_end_time\": 1731396928,\n  \"digest_text\": \"select count ( ? ) as `cnt` , max ( `created_at` ) as `latest_created_at` , `unix_timestamp` ( max ( `created_at` ) ) as `latest_timestamp` from `github_events` where `created_at` between `from_unixtime` ( ? ) and ( utc_timestamp - interval ? minute ) and `from_unixtime` ( ? ) \\u003e ( utc_timestamp - interval ? hour ) ;\",\n  \"digest\": \"85310c3dcab6bcdd1a6c04a6f254ab47b9ce73165d54fb9d1a75931d6fb7f15e\",\n  \"exec_count\": 875,\n  \"stmt_type\": \"Select\",\n  \"sum_latency\": 5148485571,\n  \"max_latency\": 164537484,\n  \"min_latency\": 619447,\n  \"avg_latency\": 5883983,\n  \"avg_compile_latency\": 968229,\n  \"max_compile_latency\": 2824436,\n  \"sum_cop_task_num\": 18249,\n  \"max_cop_process_time\": 162000000,\n  \"max_cop_wait_time\": 5000000,\n  \"avg_process_time\": 4080000,\n  \"max_process_time\": 305000000,\n  \"avg_wait_time\": 901714,\n  \"max_wait_time\": 28000000,\n  \"avg_backoff_time\": 2285,\n  \"max_backoff_time\": 2000000,\n  \"avg_total_keys\": 11801,\n  \"max_total_keys\": 592472,\n  \"avg_processed_keys\": 11780,\n  \"max_processed_keys\": 592451,\n  \"avg_mem\": 104312,\n  \"max_mem\": 129833,\n  \"first_seen\": 1731395230,\n  \"last_seen\": 1731396926,\n  \"sample_user\": \"3EDFHZJX5iSzvfr.gh_api\",\n  \"query_sample_text\": \"SELECT\\n    /*+ MAX_EXECUTION_TIME(15000) */\\n    COUNT(1) AS cnt,\\n    MAX(created_at) AS latest_created_at,\\n    UNIX_TIMESTAMP(MAX(created_at)) AS latest_timestamp\\nFROM github_events\\nWHERE\\n    created_at BETWEEN FROM_UNIXTIME(1731395039) AND (UTC_TIMESTAMP - INTERVAL 5 MINUTE)\\n    AND FROM_UNIXTIME(1731395039) \\u003e (UTC_TIMESTAMP - INTERVAL 4 HOUR)\",\n  \"schema_name\": \"gharchive_dev\",\n  \"table_names\": \"gharchive_dev.github_events\",\n  \"index_names\": \"github_events:index_github_events_on_created_at\",\n  \"plan_count\": 2,\n  \"plan\": \"\\tid                       \\ttask     \\testRows     \\toperator info                                                                                                                              \\tactRows\\texecution info                                                                                                                                                                                                                                                                                                                                 \\tmemory   \\tdisk\\n\\tProjection_5             \\troot     \\t1           \\tColumn#35, Column#36, unix_timestamp(Column#36)-\\u003eColumn#37                                                                                 \\t1      \\ttime:3.02ms, loops:2, Concurrency:OFF                                                                                                                                                                                                                                                                                                          \\t17.7 KB  \\tN/A\\n\\t└─HashAgg_15             \\troot     \\t1           \\tfuncs:count(Column#39)-\\u003eColumn#35, funcs:max(Column#40)-\\u003eColumn#36                                                                         \\t1      \\ttime:3ms, loops:2, partial_worker:{wall_time:2.986984ms, concurrency:5, task_num:1, tot_wait:14.759342ms, tot_exec:7.86µs, tot_time:14.77375ms, max:2.957061ms, p95:2.957061ms}, final_worker:{wall_time:3.010556ms, concurrency:5, task_num:1, tot_wait:14.851254ms, tot_exec:11.675µs, tot_time:14.86594ms, max:2.982471ms, p95:2.982471ms}\\t99.8 KB  \\tN/A\\n\\t  └─IndexReader_16       \\troot     \\t1           \\tindex:HashAgg_7                                                                                                                            \\t15     \\ttime:2.94ms, loops:2, cop_task: {num: 21, max: 2.89ms, min: 392.8µs, avg: 951.8µs, p95: 1.38ms, max_proc_keys: 4362, p95_proc_keys: 1028, tot_proc: 2ms, rpc_num: 21, rpc_time: 19.6ms, copr_cache_hit_ratio: 0.00, build_task_duration: 115.7µs, max_distsql_concurrency: 15}                                                              \\t600 Bytes\\tN/A\\n\\t    └─HashAgg_7          \\tcop[tikv]\\t1           \\tfuncs:count(1)-\\u003eColumn#39, funcs:max(gharchive_dev.github_events.created_at)-\\u003eColumn#40                                                    \\t15     \\ttikv_task:{proc max:4ms, min:0s, avg: 952.4µs, p80:4ms, p95:4ms, iters:26, tasks:21}, scan_detail: {total_process_keys: 7568, total_process_keys_size: 317856, total_keys: 7589, get_snapshot_time: 202µs, rocksdb: {block: {}}}                                                                                                             \\tN/A      \\tN/A\\n\\t      └─IndexRangeScan_14\\tcop[tikv]\\t210539763.07\\ttable:github_events, index:index_github_events_on_created_at(created_at), range:[2024-11-12 07:03:59,2024-11-12 07:06:14], keep order:false\\t7568   \\ttikv_task:{proc max:4ms, min:0s, avg: 952.4µs, p80:4ms, p95:4ms, iters:26, tasks:21}                                                                                                                                                                                                                                                          \\tN/A      \\tN/A\",\n  \"plan_digest\": \"458c98bf3604ae89a16e932e38e3c9cadb7500646f7fa88795dcb5e0fbf4e5be\",\n  \"binary_plan\": \"{\\\"discardedDueToTooLong\\\":false,\\\"main\\\":{\\\"accessObjects\\\":[],\\\"actRows\\\":1,\\\"children\\\":[{\\\"accessObjects\\\":[],\\\"actRows\\\":1,\\\"children\\\":[{\\\"accessObjects\\\":[{\\\"dynamicPartitionObjects\\\":{\\\"objects\\\":[{\\\"allPartitions\\\":true,\\\"database\\\":\\\"gharchive_dev\\\",\\\"table\\\":\\\"github_events\\\"}]}}],\\\"actRows\\\":15,\\\"children\\\":[{\\\"accessObjects\\\":[],\\\"actRows\\\":15,\\\"children\\\":[{\\\"accessObjects\\\":[{\\\"scanObject\\\":{\\\"database\\\":\\\"gharchive_dev\\\",\\\"indexes\\\":[{\\\"cols\\\":[\\\"created_at\\\"],\\\"name\\\":\\\"index_github_events_on_created_at\\\"}],\\\"table\\\":\\\"github_events\\\"}}],\\\"actRows\\\":7568,\\\"copExecInfo\\\":{\\\"tikv_task\\\":{\\\"avg\\\":\\\"952.4µs\\\",\\\"iters\\\":\\\"26\\\",\\\"min\\\":\\\"0s\\\",\\\"p80\\\":\\\"4ms\\\",\\\"p95\\\":\\\"4ms\\\",\\\"proc max\\\":\\\"4ms\\\",\\\"tasks\\\":\\\"21\\\"}},\\\"cost\\\":34275873428.61,\\\"diagnosis\\\":[\\\"high_est_error\\\"],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"20.0004ms\\\",\\\"estRows\\\":210539763.075,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"N/A\\\",\\\"name\\\":\\\"IndexRangeScan_14\\\",\\\"operatorInfo\\\":\\\"range:[2024-11-12 07:03:59,2024-11-12 07:06:14], keep order:false\\\",\\\"rootBasicExecInfo\\\":{},\\\"rootGroupExecInfo\\\":[],\\\"storeType\\\":\\\"tikv\\\",\\\"taskType\\\":\\\"cop\\\"}],\\\"copExecInfo\\\":{\\\"scan_detail\\\":{\\\"get_snapshot_time\\\":\\\"202µs\\\",\\\"rocksdb\\\":{\\\"block\\\":{}},\\\"total_keys\\\":\\\"7589\\\",\\\"total_process_keys\\\":\\\"7568\\\",\\\"total_process_keys_size\\\":\\\"317856\\\"},\\\"tikv_task\\\":{\\\"avg\\\":\\\"952.4µs\\\",\\\"iters\\\":\\\"26\\\",\\\"min\\\":\\\"0s\\\",\\\"p80\\\":\\\"4ms\\\",\\\"p95\\\":\\\"4ms\\\",\\\"proc max\\\":\\\"4ms\\\",\\\"tasks\\\":\\\"21\\\"}},\\\"cost\\\":40579435442.6955,\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"20.0004ms\\\",\\\"estRows\\\":1,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"N/A\\\",\\\"name\\\":\\\"HashAgg_7\\\",\\\"operatorInfo\\\":\\\"funcs:count(1)-\\\\u003eColumn#39, funcs:max(gharchive_dev.github_events.created_at)-\\\\u003eColumn#40\\\",\\\"rootBasicExecInfo\\\":{},\\\"rootGroupExecInfo\\\":[],\\\"storeType\\\":\\\"tikv\\\",\\\"taskType\\\":\\\"cop\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":2705295700.4037004,\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"20.0004ms\\\",\\\"estRows\\\":1,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"600\\\",\\\"name\\\":\\\"IndexReader_16\\\",\\\"operatorInfo\\\":\\\"index:HashAgg_7\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"2\\\",\\\"time\\\":\\\"2.94ms\\\"},\\\"rootGroupExecInfo\\\":[{\\\"cop_task\\\":{\\\"avg\\\":\\\"951.8µs\\\",\\\"build_task_duration\\\":\\\"115.7µs\\\",\\\"copr_cache_hit_ratio\\\":\\\"0.00\\\",\\\"max\\\":\\\"2.89ms\\\",\\\"max_distsql_concurrency\\\":\\\"15\\\",\\\"max_proc_keys\\\":\\\"4362\\\",\\\"min\\\":\\\"392.8µs\\\",\\\"num\\\":\\\"21\\\",\\\"p95\\\":\\\"1.38ms\\\",\\\"p95_proc_keys\\\":\\\"1028\\\",\\\"rpc_num\\\":\\\"21\\\",\\\"rpc_time\\\":\\\"19.6ms\\\",\\\"tot_proc\\\":\\\"2ms\\\"}}],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":2705297237.9637003,\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"20.0004ms\\\",\\\"estRows\\\":1,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"102180\\\",\\\"name\\\":\\\"HashAgg_15\\\",\\\"operatorInfo\\\":\\\"funcs:count(Column#39)-\\\\u003eColumn#35, funcs:max(Column#40)-\\\\u003eColumn#36\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"2\\\",\\\"time\\\":\\\"3ms\\\"},\\\"rootGroupExecInfo\\\":[{\\\"final_worker\\\":{\\\"concurrency\\\":\\\"5\\\",\\\"max\\\":\\\"2.982471ms\\\",\\\"p95\\\":\\\"2.982471ms\\\",\\\"task_num\\\":\\\"1\\\",\\\"tot_exec\\\":\\\"11.675µs\\\",\\\"tot_time\\\":\\\"14.86594ms\\\",\\\"tot_wait\\\":\\\"14.851254ms\\\",\\\"wall_time\\\":\\\"3.010556ms\\\"},\\\"partial_worker\\\":{\\\"concurrency\\\":\\\"5\\\",\\\"max\\\":\\\"2.957061ms\\\",\\\"p95\\\":\\\"2.957061ms\\\",\\\"task_num\\\":\\\"1\\\",\\\"tot_exec\\\":\\\"7.86µs\\\",\\\"tot_time\\\":\\\"14.77375ms\\\",\\\"tot_wait\\\":\\\"14.759342ms\\\",\\\"wall_time\\\":\\\"2.986984ms\\\"}}],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":2705297248.1433,\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"3.02ms\\\",\\\"estRows\\\":1,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"18160\\\",\\\"name\\\":\\\"Projection_5\\\",\\\"operatorInfo\\\":\\\"Column#35, Column#36, unix_timestamp(Column#36)-\\\\u003eColumn#37\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"2\\\",\\\"time\\\":\\\"3.02ms\\\"},\\\"rootGroupExecInfo\\\":[{\\\"Concurrency\\\":\\\"OFF\\\"}],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"},\\\"withRuntimeStats\\\":true}\",\n  \"plan_hint\": \"hash_agg(@`sel_1`), use_index(@`sel_1` `gharchive_dev`.`github_events` `index_github_events_on_created_at`), no_order_index(@`sel_1` `gharchive_dev`.`github_events` `index_github_events_on_created_at`), agg_to_cop(@`sel_1`), max_execution_time(15000)\",\n  \"sum_ru\": 16599.290484619145,\n  \"sum_rru\": 16599.290484619145,\n  \"avg_ru\": 18.970617696707595,\n  \"avg_rru\": 18.970617696707595\n}\n"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/sample-res/statement-plans-list.json",
    "content": "{\n  \"data\": [\n    {\n      \"summary_begin_time\": 1731395228,\n      \"summary_end_time\": 1731396928,\n      \"digest_text\": \"select count ( ? ) as `cnt` , max ( `created_at` ) as `latest_created_at` , `unix_timestamp` ( max ( `created_at` ) ) as `latest_timestamp` from `github_events` where `created_at` between `from_unixtime` ( ? ) and ( utc_timestamp - interval ? minute ) and `from_unixtime` ( ? ) \\u003e ( utc_timestamp - interval ? hour ) ;\",\n      \"digest\": \"85310c3dcab6bcdd1a6c04a6f254ab47b9ce73165d54fb9d1a75931d6fb7f15e\",\n      \"exec_count\": 869,\n      \"stmt_type\": \"Select\",\n      \"sum_latency\": 5144430781,\n      \"max_latency\": 164537484,\n      \"min_latency\": 2662266,\n      \"avg_latency\": 5919943,\n      \"avg_mem\": 104850,\n      \"max_mem\": 129833,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_digest\": \"458c98bf3604ae89a16e932e38e3c9cadb7500646f7fa88795dcb5e0fbf4e5be\",\n      \"plan_hint\": \"hash_agg(@`sel_1`), use_index(@`sel_1` `gharchive_dev`.`github_events` `index_github_events_on_created_at`), no_order_index(@`sel_1` `gharchive_dev`.`github_events` `index_github_events_on_created_at`), agg_to_cop(@`sel_1`), max_execution_time(15000)\"\n    },\n    {\n      \"summary_begin_time\": 1731395712,\n      \"summary_end_time\": 1731395894,\n      \"digest_text\": \"select count ( ? ) as `cnt` , max ( `created_at` ) as `latest_created_at` , `unix_timestamp` ( max ( `created_at` ) ) as `latest_timestamp` from `github_events` where `created_at` between `from_unixtime` ( ? ) and ( utc_timestamp - interval ? minute ) and `from_unixtime` ( ? ) \\u003e ( utc_timestamp - interval ? hour ) ;\",\n      \"digest\": \"85310c3dcab6bcdd1a6c04a6f254ab47b9ce73165d54fb9d1a75931d6fb7f15e\",\n      \"exec_count\": 6,\n      \"stmt_type\": \"Select\",\n      \"sum_latency\": 4054790,\n      \"max_latency\": 797059,\n      \"min_latency\": 619447,\n      \"avg_latency\": 675798,\n      \"avg_mem\": 26418,\n      \"max_mem\": 27912,\n      \"schema_name\": \"gharchive_dev\",\n      \"plan_digest\": \"8c0a7b1bc1a0894b4dab93844ea1fd5d6a10f5515a4ebd66ca56d3f71bd7b27f\",\n      \"plan_hint\": \"stream_agg(@`sel_1`), max_execution_time(15000)\"\n    }\n  ]\n}"
  },
  {
    "path": "ui-v2/packages/api/server/src/azores/sample-res/statement-slow-query-list.json",
    "content": "[\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724674023\",\n    \"success\": 0,\n    \"timestamp\": 1731484007.364735,\n    \"query_time\": 17.356576616,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 3415,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724674025\",\n    \"success\": 0,\n    \"timestamp\": 1731483875.213136,\n    \"query_time\": 5.20261797,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 3403,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724623537\",\n    \"success\": 0,\n    \"timestamp\": 1731483761.945083,\n    \"query_time\": 11.936930494,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 3413,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724623531\",\n    \"success\": 0,\n    \"timestamp\": 1731483636.588591,\n    \"query_time\": 6.580224996,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 1862,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724623537\",\n    \"success\": 0,\n    \"timestamp\": 1731483510.391377,\n    \"query_time\": 0.382754965,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 1853,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724674023\",\n    \"success\": 0,\n    \"timestamp\": 1731483407.392716,\n    \"query_time\": 17.384262229,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 1864,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724623531\",\n    \"success\": 0,\n    \"timestamp\": 1731483278.527449,\n    \"query_time\": 8.519016228,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 1862,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724623537\",\n    \"success\": 0,\n    \"timestamp\": 1731483171.406427,\n    \"query_time\": 21.397817264,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 1842,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724674025\",\n    \"success\": 0,\n    \"timestamp\": 1731483048.661767,\n    \"query_time\": 18.65083934,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 3415,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724623531\",\n    \"success\": 0,\n    \"timestamp\": 1731482913.415495,\n    \"query_time\": 3.40567882,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 1859,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724674025\",\n    \"success\": 0,\n    \"timestamp\": 1731482800.178462,\n    \"query_time\": 10.167798402,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 1864,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724623537\",\n    \"success\": 0,\n    \"timestamp\": 1731482680.645222,\n    \"query_time\": 10.636440703,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 1864,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724674023\",\n    \"success\": 0,\n    \"timestamp\": 1731482556.314372,\n    \"query_time\": 6.305582706,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 1861,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724674025\",\n    \"success\": 0,\n    \"timestamp\": 1731482442.929289,\n    \"query_time\": 12.918626472,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 1864,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  },\n  {\n    \"digest\": \"cb0ce9437949f181c6f405bcdfe92e9b179e95f307e5b0817aa7c773430511d5\",\n    \"query\": \"SELECT     COUNT(DISTINCT actor_id) AS developers,     COUNT(DISTINCT repo_id) AS repos,     SUM(IF(action = 'closed' AND pr_merged = true, additions, 0)) AS additions,     SUM(IF(action = 'closed' AND pr_merged = true, deletions, 0)) AS deletions,     COUNT(DISTINCT IF(action = 'opened', pr_or_issue_id, NULL)) AS opened_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = false, pr_or_issue_id, NULL)) AS closed_prs,     COUNT(DISTINCT IF(action = 'closed' AND pr_merged = true, pr_or_issue_id, NULL)) AS merged_prs FROM     github_events ge WHERE     type = 'PullRequestEvent'     AND created_at \\u003e DATE_SUB(NOW(), INTERVAL 24 HOUR) ;\",\n    \"instance\": \"\",\n    \"db\": \"\",\n    \"connection_id\": \"3483529913724623537\",\n    \"success\": 0,\n    \"timestamp\": 1731482317.57419,\n    \"query_time\": 7.565186919,\n    \"parse_time\": 0,\n    \"compile_time\": 0,\n    \"rewrite_time\": 0,\n    \"preproc_subqueries_time\": 0,\n    \"optimize_time\": 0,\n    \"wait_ts\": 0,\n    \"cop_time\": 0,\n    \"lock_keys_time\": 0,\n    \"write_sql_response_total\": 0,\n    \"exec_retry_time\": 0,\n    \"memory_max\": 1861,\n    \"disk_max\": 0,\n    \"txn_start_ts\": \"\",\n    \"prev_stmt\": \"\",\n    \"plan\": \"\",\n    \"is_internal\": 0,\n    \"index_names\": \"\",\n    \"stats\": \"\",\n    \"backoff_types\": \"\",\n    \"prepared\": 0,\n    \"plan_from_cache\": 0,\n    \"plan_from_binding\": 0,\n    \"user\": \"\",\n    \"host\": \"\",\n    \"process_time\": 0,\n    \"wait_time\": 0,\n    \"backoff_time\": 0,\n    \"get_commit_ts_time\": 0,\n    \"local_latch_wait_time\": 0,\n    \"resolve_lock_time\": 0,\n    \"prewrite_time\": 0,\n    \"wait_prewrite_binlog_time\": 0,\n    \"commit_time\": 0,\n    \"commit_backoff_time\": 0,\n    \"cop_proc_avg\": 0,\n    \"cop_proc_p90\": 0,\n    \"cop_proc_max\": 0,\n    \"cop_wait_avg\": 0,\n    \"cop_wait_p90\": 0,\n    \"cop_wait_max\": 0,\n    \"write_keys\": 0,\n    \"write_size\": 0,\n    \"prewrite_region\": 0,\n    \"txn_retry\": 0,\n    \"request_count\": 0,\n    \"process_keys\": 0,\n    \"total_keys\": 0,\n    \"cop_proc_addr\": \"\",\n    \"cop_wait_addr\": \"\",\n    \"rocksdb_delete_skipped_count\": 0,\n    \"rocksdb_key_skipped_count\": 0,\n    \"rocksdb_block_cache_hit_count\": 0,\n    \"rocksdb_block_read_count\": 0,\n    \"rocksdb_block_read_byte\": 0,\n    \"ru\": 0,\n    \"time_queued_by_rc\": 0,\n    \"resource_group\": \"\"\n  }\n]\n"
  },
  {
    "path": "ui-v2/packages/api/server/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ESNext\",\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"strict\": true,\n    \"skipLibCheck\": true,\n    \"lib\": [\"ESNext\"],\n    \"types\": [\"@cloudflare/workers-types/2023-07-01\"],\n    \"jsx\": \"react-jsx\",\n    \"jsxImportSource\": \"hono/jsx\"\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/api/server/wrangler.toml",
    "content": "name = \"tidb-dashboard-lib-api-server\"\nmain = \"src/azores/index.ts\"\ncompatibility_date = \"2024-12-14\"\n\n# compatibility_flags = [ \"nodejs_compat\" ]\n\n# [vars]\n# MY_VAR = \"my-variable\"\n\n# [[kv_namespaces]]\n# binding = \"MY_KV_NAMESPACE\"\n# id = \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"\n\n# [[r2_buckets]]\n# binding = \"MY_BUCKET\"\n# bucket_name = \"my-bucket\"\n\n# [[d1_databases]]\n# binding = \"DB\"\n# database_name = \"my-database\"\n# database_id = \"\"\n\n# [ai]\n# binding = \"AI\"\n\n# [observability]\n# enabled = true\n# head_sampling_rate = 1"
  },
  {
    "path": "ui-v2/packages/libs/1-charts/CHANGELOG.md",
    "content": "# @pingcap-incubator/tidb-dashboard-lib-charts\n\n## 0.16.0\n\n### Minor Changes\n\n- bump version\n\n## 0.15.0\n\n### Minor Changes\n\n- refactor: re-exports lib-uitls/lib-charts/lib-primitive-ui/lib-biz-ui from lib-apps\n\n## 0.14.1\n\n### Patch Changes\n\n- upgrade @elastic/charts to latest version\n\n## 0.14.0\n\n### Minor Changes\n\n- upgrade uikit, refine table empty status, refine chart empty legend name case\n\n## 0.13.1\n\n### Patch Changes\n\n- increase data format decimal\n\n## 0.13.0\n\n### Minor Changes\n\n- suport brush to change time range\n\n## 0.12.0\n\n### Minor Changes\n\n- update metrics charts\n\n## 0.11.0\n\n### Minor Changes\n\n- fix time-range-picker, refine cols-multi-select\n\n## 0.10.0\n\n### Minor Changes\n\n- i18n for slow query and statement app\n\n## 0.9.0\n\n### Minor Changes\n\n- upgrade uikit\n\n## 0.8.0\n\n### Minor Changes\n\n- support advanced filters for diagnosis\n\n## 0.7.0\n\n### Minor Changes\n\n- refine pagination and sort url state\n\n## 0.6.0\n\n### Minor Changes\n\n- refine\n\n## 0.5.0\n\n### Minor Changes\n\n- refine slow-query and statement apps\n\n## 0.4.0\n\n### Minor Changes\n\n- update uikit\n\n## 0.3.0\n\n### Minor Changes\n\n- add azores cluster metrics page\n\n## 0.2.0\n\n### Minor Changes\n\n- add metrics azores host page\n\n## 0.1.0\n\n### Minor Changes\n\n- update i18n, format utils\n\n## 0.0.9\n\n### Patch Changes\n\n- add i18n\n\n## 0.0.8\n\n### Patch Changes\n\n- set time formatter\n\n## 0.0.7\n\n### Patch Changes\n\n- support dark mode for lib-charts\n\n## 0.0.6\n\n### Patch Changes\n\n- refine\n\n## 0.0.5\n\n### Patch Changes\n\n- refactor metric\n\n## 0.0.4\n\n### Patch Changes\n\n- upgrade uikit\n\n## 0.0.3\n\n### Patch Changes\n\n- add prom utils functions\n\n## 0.0.2\n\n### Patch Changes\n\n- first release\n"
  },
  {
    "path": "ui-v2/packages/libs/1-charts/README.md",
    "content": "# @pingcap-incubator/tidb-dashboard-lib-charts\n\n## Usage\n\n### Step 1\n\nImport style css\n\n```ts\nimport \"@pingcap-incubator/tidb-dashboard-lib-charts/dist/style.css\"\n```\n\n### Step 2\n\nUse `ChartThemeSwitch` component in the top level (for example `App` component)\n\n```tsx\nfunction Routes() {\n  const theme = useComputedColorScheme()\n\n  return (\n    <Router>\n      <Stack p={16}>\n        {/* ... */}\n        <ChartThemeSwitch value={theme} />\n      </Stack>\n    </Router>\n  )\n}\n```\n\n### Step 3\n\nRender series data by `SeriesChart` component\n\n```tsx\n<SeriesChart\n  unit={config.unit}\n  data={seriesData}\n  timeRange={tr}\n  theme={colorScheme}\n/>\n```\n"
  },
  {
    "path": "ui-v2/packages/libs/1-charts/package.json",
    "content": "{\n  \"name\": \"@pingcap-incubator/tidb-dashboard-lib-charts\",\n  \"version\": \"0.16.0\",\n  \"description\": \"\",\n  \"type\": \"module\",\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"files\": [\n    \"dist\",\n    \"README.md\",\n    \"CHANGELOG.md\"\n  ],\n  \"scripts\": {\n    \"tsc:watch\": \"tsc --watch\",\n    \"rollup:watch\": \"rollup -c --watch\",\n    \"css:watch\": \"sass src/style.scss dist/style.css --watch\",\n    \"dev\": \"concurrently --kill-others \\\"pnpm tsc:watch\\\" \\\"pnpm rollup:watch\\\" \\\"pnpm css:watch\\\"\",\n    \"css:build\": \"sass src/style.scss dist/style.css\",\n    \"build\": \"tsc && rollup -c && pnpm css:build\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@rollup/plugin-typescript\": \"^12.1.1\",\n    \"@types/react\": \"^18.3.12\",\n    \"react\": \"^18.3.1\",\n    \"rollup\": \"^4.24.0\",\n    \"sass\": \"^1.81.0\",\n    \"tslib\": \"^2.8.0\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"^18.3.1\"\n  },\n  \"dependencies\": {\n    \"@baurine/grafana-value-formats\": \"^1.0.5\",\n    \"@elastic/charts\": \"^69.1.1\"\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-charts/rollup.config.js",
    "content": "import typescript from \"@rollup/plugin-typescript\"\n\nexport default {\n  input: \"src/index.ts\",\n  output: {\n    dir: \"dist\",\n    format: \"es\",\n  },\n  plugins: [typescript()],\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-charts/src/chart-theme-switch.tsx",
    "content": "import { useEffect } from \"react\"\n\nconst LIGHT_TOKEN = \"charts-light-theme\"\nconst DARK_TOKEN = \"charts-dark-theme\"\n\nexport function useChartTheme(theme: \"light\" | \"dark\") {\n  useEffect(() => {\n    if (theme === \"light\") {\n      window.document.body.classList.remove(DARK_TOKEN)\n      window.document.body.classList.add(LIGHT_TOKEN)\n    } else {\n      window.document.body.classList.remove(LIGHT_TOKEN)\n      window.document.body.classList.add(DARK_TOKEN)\n    }\n  }, [theme])\n}\n\nexport function ChartThemeSwitch({ value }: { value: \"light\" | \"dark\" }) {\n  useChartTheme(value)\n  return null\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-charts/src/index.ts",
    "content": "export * from \"./series-chart\"\nexport * from \"./chart-theme-switch\"\nexport * from \"./type\"\n"
  },
  {
    "path": "ui-v2/packages/libs/1-charts/src/sample-data.ts",
    "content": "// see https://github.com/elastic/elastic-charts/blob/main/packages/charts/src/utils/data_samples/test_dataset_kibana.ts\n"
  },
  {
    "path": "ui-v2/packages/libs/1-charts/src/series-chart.tsx",
    "content": "import { getValueFormat } from \"@baurine/grafana-value-formats\"\nimport {\n  Axis,\n  BrushEvent,\n  Chart,\n  DARK_THEME,\n  LIGHT_THEME,\n  LegendValue,\n  LineSeries,\n  Position,\n  ScaleType,\n  SeriesIdentifier,\n  Settings,\n  SettingsProps,\n  timeFormatter,\n} from \"@elastic/charts\"\nimport { useCallback, useMemo } from \"react\"\n\nimport { renderSeriesData } from \"./series-render\"\nimport { SeriesData } from \"./type\"\n\nfunction formatNumByUnit(value: number, unit: string) {\n  const formatFn = getValueFormat(unit)\n  if (!formatFn) {\n    return value + \"\"\n  }\n  return formatFn(value, 2)\n}\n\nfunction niceTimeFormat(seconds: number) {\n  // if (max time - min time > 5 days)\n  if (seconds > 5 * 24 * 60 * 60) return \"MM-DD\"\n  // if (max time - min time > 1 day)\n  if (seconds > 1 * 24 * 60 * 60) return \"MM-DD HH:mm\"\n  // if (max time - min time > 5 minutes)\n  if (seconds > 5 * 60) return \"HH:mm\"\n  return \"HH:mm:ss\"\n}\n\n// align the time range according to the minimal interval and minimal range size.\nexport function alignRange(\n  range: TimeRangeValue,\n  minIntervalSec = 30,\n  minRangeSec = 60,\n): TimeRangeValue {\n  let [min, max] = range\n  if (max - min < minRangeSec) {\n    min = max - minRangeSec\n  }\n  min = Math.floor(min / minIntervalSec) * minIntervalSec\n  max = Math.ceil(max / minIntervalSec) * minIntervalSec\n  return [min, max]\n}\n\nconst tooltipHeaderFormatter = timeFormatter(\"YYYY-MM-DD HH:mm:ss\")\n\ntype TimeRangeValue = [number, number]\n\ntype SeriesChartProps = {\n  theme?: \"light\" | \"dark\"\n  data: SeriesData[]\n  unit: string\n  timeRange: TimeRangeValue\n  charSetting?: SettingsProps\n  onBrush?: (range: TimeRangeValue) => void\n}\n\nexport function SeriesChart({\n  theme = \"light\",\n  data,\n  unit,\n  timeRange,\n  charSetting,\n  onBrush,\n}: SeriesChartProps) {\n  const xAxisFormatter = useMemo(\n    () => timeFormatter(niceTimeFormat(timeRange[1] - timeRange[0])),\n    [timeRange],\n  )\n\n  // note: this doesn't work with StrictMode in debug mode (it's fine in production)\n  const handleBrushEnd = useCallback(\n    (ev: BrushEvent) => {\n      if (!ev.x) {\n        return\n      }\n      const timeRange: TimeRangeValue = [\n        Math.floor((ev.x[0] as number) / 1000),\n        Math.floor((ev.x[1] as number) / 1000),\n      ]\n      onBrush?.(alignRange(timeRange))\n    },\n    [onBrush],\n  )\n\n  // @todo: it seems doesn't work, try it later\n  const specIds = useMemo(() => data.map((d) => d.id), [data])\n  const handleLegendSort = useCallback(\n    (a: SeriesIdentifier, b: SeriesIdentifier) => {\n      return specIds.indexOf(a.specId) - specIds.indexOf(b.specId)\n    },\n    [specIds],\n  )\n\n  return (\n    <Chart>\n      <Settings\n        baseTheme={theme === \"light\" ? LIGHT_THEME : DARK_THEME}\n        showLegend\n        legendPosition={Position.Right}\n        legendSize={200}\n        legendValues={[LegendValue.Average]}\n        legendSort={handleLegendSort}\n        xDomain={{ min: timeRange[0] * 1000, max: timeRange[1] * 1000 }}\n        onBrushEnd={onBrush && handleBrushEnd}\n        {...charSetting}\n      />\n\n      <Axis\n        id=\"bottom\"\n        position={Position.Bottom}\n        ticks={6}\n        showOverlappingTicks\n        tickFormat={tooltipHeaderFormatter}\n        labelFormat={xAxisFormatter}\n      />\n      <Axis\n        id=\"left\"\n        position={Position.Left}\n        ticks={5}\n        tickFormat={(v) => formatNumByUnit(v, unit)}\n      />\n\n      {data.map(renderSeriesData)}\n\n      {/* for avoid chart to show \"no data\" when data is empty */}\n      {data.length === 0 && (\n        <LineSeries\n          id=\"_placeholder\"\n          xScaleType={ScaleType.Time}\n          yScaleType={ScaleType.Linear}\n          xAccessor={0}\n          yAccessors={[1]}\n          hideInLegend\n          data={[\n            [timeRange[0] * 1000, null],\n            [timeRange[1] * 1000, null],\n          ]}\n        />\n      )}\n    </Chart>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-charts/src/series-render.tsx",
    "content": "import { AreaSeries, BarSeries, LineSeries, ScaleType } from \"@elastic/charts\"\n\nimport { SeriesData } from \"./type\"\n\nexport function renderSeriesData(sd: SeriesData) {\n  if (sd.type === \"line\") {\n    return renderLine(sd)\n  } else if (sd.type === \"bar_stacked\") {\n    return renderStackedBar(sd)\n  } else if (sd.type === \"area_stack\") {\n    return renderAreaStack(sd)\n  } else if (sd.type === \"area\") {\n    return renderArea(sd)\n  }\n  return renderLine(sd)\n}\n\nfunction renderLine(sd: SeriesData) {\n  return (\n    <LineSeries\n      key={sd.id}\n      id={sd.id}\n      xScaleType={ScaleType.Time}\n      yScaleType={ScaleType.Linear}\n      xAccessor={0}\n      yAccessors={[1]}\n      data={sd.data}\n      name={sd.name}\n      color={typeof sd.color === \"function\" ? sd.color(sd.name) : sd.color}\n      lineSeriesStyle={{\n        line: {\n          strokeWidth: 2,\n        },\n        point: {\n          visible: \"never\",\n        },\n        ...sd.lineSeriesStyle,\n      }}\n    />\n  )\n}\n\nfunction renderStackedBar(sd: SeriesData) {\n  return (\n    <BarSeries\n      key={sd.id}\n      id={sd.id}\n      xScaleType={ScaleType.Time}\n      yScaleType={ScaleType.Linear}\n      xAccessor={0}\n      yAccessors={[1]}\n      stackAccessors={[0]}\n      data={sd.data}\n      name={sd.name}\n      color={typeof sd.color === \"function\" ? sd.color(sd.name) : sd.color}\n    />\n  )\n}\n\nfunction renderAreaStack(sd: SeriesData) {\n  return (\n    <AreaSeries\n      key={sd.id}\n      id={sd.id}\n      xScaleType={ScaleType.Time}\n      yScaleType={ScaleType.Linear}\n      xAccessor={0}\n      yAccessors={[1]}\n      stackAccessors={[0]}\n      data={sd.data}\n      name={sd.name}\n      color={typeof sd.color === \"function\" ? sd.color(sd.name) : sd.color}\n    />\n  )\n}\n\nfunction renderArea(sd: SeriesData) {\n  return (\n    <AreaSeries\n      key={sd.id}\n      id={sd.id}\n      xScaleType={ScaleType.Time}\n      yScaleType={ScaleType.Linear}\n      xAccessor={0}\n      yAccessors={[1]}\n      data={sd.data}\n      name={sd.name}\n      color={typeof sd.color === \"function\" ? sd.color(sd.name) : sd.color}\n    />\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-charts/src/style.scss",
    "content": ".charts-light-theme {\n  @import '../node_modules/@elastic/charts/dist/theme_only_light';\n\n}\n.charts-dark-theme {\n  @import '../node_modules/@elastic/charts/dist/theme_only_dark';\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-charts/src/type.ts",
    "content": "import { LineSeriesStyle, RecursivePartial } from \"@elastic/charts\"\n\nexport type SeriesDataType = \"line\" | \"area\" | \"bar_stacked\" | \"area_stack\"\n\nexport type DataPoint = [msTimestamp: number, value: number | null]\n\nexport type SeriesData = {\n  id: string\n  name: string\n  data: DataPoint[]\n  type?: SeriesDataType\n  color?: string | ((seriesName: string) => string | undefined)\n  lineSeriesStyle?: RecursivePartial<LineSeriesStyle>\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-charts/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.app.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\"\n  },\n  \"include\": [\"./src\"]\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-icons/CHANGELOG.md",
    "content": "# @pingcap-incubator/tidb-dashboard-lib-icons\n\n## 0.14.0\n\n### Minor Changes\n\n- bump version\n\n## 0.13.0\n\n### Minor Changes\n\n- refactor: re-exports lib-uitls/lib-charts/lib-primitive-ui/lib-biz-ui from lib-apps\n\n## 0.12.0\n\n### Minor Changes\n\n- upgrade uikit, refine table empty status, refine chart empty legend name case\n\n## 0.11.0\n\n### Minor Changes\n\n- fix time-range-picker, refine cols-multi-select\n\n## 0.10.0\n\n### Minor Changes\n\n- i18n for slow query and statement app\n\n## 0.9.0\n\n### Minor Changes\n\n- upgrade uikit\n\n## 0.8.0\n\n### Minor Changes\n\n- support advanced filters for diagnosis\n\n## 0.7.0\n\n### Minor Changes\n\n- refine pagination and sort url state\n\n## 0.6.0\n\n### Minor Changes\n\n- refine\n\n## 0.5.0\n\n### Minor Changes\n\n- refine slow-query and statement apps\n\n## 0.4.0\n\n### Minor Changes\n\n- update uikit\n\n## 0.3.0\n\n### Minor Changes\n\n- add azores cluster metrics page\n\n## 0.2.0\n\n### Minor Changes\n\n- add metrics azores host page\n\n## 0.1.0\n\n### Minor Changes\n\n- update i18n, format utils\n\n## 0.0.7\n\n### Patch Changes\n\n- add i18n\n\n## 0.0.6\n\n### Patch Changes\n\n- support dark mode for lib-charts\n\n## 0.0.5\n\n### Patch Changes\n\n- refine\n\n## 0.0.4\n\n### Patch Changes\n\n- refactor metric\n\n## 0.0.3\n\n### Patch Changes\n\n- upgrade uikit\n\n## 0.0.2\n\n### Patch Changes\n\n- first release\n"
  },
  {
    "path": "ui-v2/packages/libs/1-icons/package.json",
    "content": "{\n  \"name\": \"@pingcap-incubator/tidb-dashboard-lib-icons\",\n  \"version\": \"0.14.0\",\n  \"description\": \"\",\n  \"type\": \"module\",\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"files\": [\n    \"dist\",\n    \"README.md\",\n    \"CHANGELOG.md\"\n  ],\n  \"scripts\": {\n    \"tsc:watch\": \"tsc --watch\",\n    \"rollup:watch\": \"rollup -c --watch\",\n    \"dev\": \"concurrently --kill-others \\\"pnpm tsc:watch\\\" \\\"pnpm rollup:watch\\\"\",\n    \"build\": \"tsc && rollup -c\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@rollup/plugin-typescript\": \"^12.1.1\",\n    \"@tidbcloud/uikit\": \"catalog:\",\n    \"@types/react\": \"^18.3.12\",\n    \"react\": \"^18.3.1\",\n    \"rollup\": \"^4.24.0\",\n    \"tslib\": \"^2.8.0\"\n  },\n  \"peerDependencies\": {\n    \"@tidbcloud/uikit\": \"catalog:\",\n    \"react\": \"^18.3.1\"\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-icons/rollup.config.js",
    "content": "import typescript from \"@rollup/plugin-typescript\"\n\nexport default {\n  input: \"src/index.ts\",\n  output: {\n    dir: \"dist\",\n    format: \"es\",\n  },\n  plugins: [typescript()],\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-icons/src/index.ts",
    "content": "// decided not to export\n// export * from \"@tidbcloud/uikit/icons\"\n\nexport function icons() {\n  console.log(\"placeholder\")\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-icons/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.app.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\"\n  },\n  \"include\": [\"./src\"]\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/CHANGELOG.md",
    "content": "# @pingcap-incubator/tidb-dashboard-lib-utils\n\n## 0.15.0\n\n### Minor Changes\n\n- bump version\n\n## 0.14.0\n\n### Minor Changes\n\n- refactor i18n and fitlers state\n\n## 0.13.0\n\n### Minor Changes\n\n- refactor: re-exports lib-uitls/lib-charts/lib-primitive-ui/lib-biz-ui from lib-apps\n\n## 0.12.1\n\n### Patch Changes\n\n- support now-to-future type relative time range\n\n## 0.12.0\n\n### Minor Changes\n\n- upgrade uikit, refine table empty status, refine chart empty legend name case\n\n## 0.11.2\n\n### Patch Changes\n\n- refine chart same as grafana\n\n## 0.11.1\n\n### Patch Changes\n\n- refine chart step\n\n## 0.11.0\n\n### Minor Changes\n\n- fix time-range-picker, refine cols-multi-select\n\n## 0.10.0\n\n### Minor Changes\n\n- i18n for slow query and statement app\n\n## 0.9.0\n\n### Minor Changes\n\n- upgrade uikit\n\n## 0.8.1\n\n### Patch Changes\n\n- renaming fields\n\n## 0.8.0\n\n### Minor Changes\n\n- support advanced filters for diagnosis\n\n## 0.7.0\n\n### Minor Changes\n\n- refine pagination and sort url state\n\n## 0.6.0\n\n### Minor Changes\n\n- refine\n\n## 0.5.0\n\n### Minor Changes\n\n- refine slow-query and statement apps\n\n## 0.4.0\n\n### Minor Changes\n\n- update uikit\n\n## 0.3.0\n\n### Minor Changes\n\n- add azores cluster metrics page\n\n## 0.2.0\n\n### Minor Changes\n\n- add metrics azores host page\n\n## 0.1.0\n\n### Minor Changes\n\n- update i18n, format utils\n\n## 0.0.10\n\n### Patch Changes\n\n- refine i18n\n\n## 0.0.9\n\n### Patch Changes\n\n- test i18n\n\n## 0.0.8\n\n### Patch Changes\n\n- debug i18n\n\n## 0.0.7\n\n### Patch Changes\n\n- add i18n\n\n## 0.0.6\n\n### Patch Changes\n\n- support dark mode for lib-charts\n\n## 0.0.5\n\n### Patch Changes\n\n- refine\n\n## 0.0.4\n\n### Patch Changes\n\n- refactor metric\n\n## 0.0.3\n\n### Patch Changes\n\n- upgrade uikit\n\n## 0.0.2\n\n### Patch Changes\n\n- first release\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/package.json",
    "content": "{\n  \"name\": \"@pingcap-incubator/tidb-dashboard-lib-utils\",\n  \"version\": \"0.15.0\",\n  \"description\": \"\",\n  \"type\": \"module\",\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"files\": [\n    \"dist\",\n    \"README.md\",\n    \"CHANGELOG.md\"\n  ],\n  \"scripts\": {\n    \"tsc:watch\": \"tsc --watch\",\n    \"rollup:watch\": \"rollup -c --watch\",\n    \"dev\": \"concurrently --kill-others \\\"pnpm tsc:watch\\\" \\\"pnpm rollup:watch\\\"\",\n    \"build\": \"tsc && rollup -c\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@rollup/plugin-typescript\": \"^12.1.1\",\n    \"@tidbcloud/uikit\": \"catalog:\",\n    \"@types/react\": \"^18.3.12\",\n    \"i18next\": \"^23.16.2\",\n    \"i18next-browser-languagedetector\": \"^8.0.0\",\n    \"react\": \"^18.3.1\",\n    \"react-i18next\": \"^15.1.0\",\n    \"rollup\": \"^4.24.0\",\n    \"tslib\": \"^2.8.0\",\n    \"zustand\": \"^5.0.2\"\n  },\n  \"peerDependencies\": {\n    \"@tidbcloud/uikit\": \"catalog:\",\n    \"i18next\": \"^23.16.2\",\n    \"i18next-browser-languagedetector\": \"^8.0.0\",\n    \"react\": \"^18.3.1\",\n    \"react-i18next\": \"^15.1.0\",\n    \"zustand\": \"^5.0.2\"\n  },\n  \"dependencies\": {\n    \"@baurine/grafana-value-formats\": \"^1.0.5\",\n    \"@baurine/sql-formatter-plus\": \"^1.5.3\",\n    \"pretty-ms\": \"^9.2.0\",\n    \"string-template\": \"^1.0.0\"\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/rollup.config.js",
    "content": "import typescript from \"@rollup/plugin-typescript\"\n\nexport default {\n  input: \"src/index.ts\",\n  output: {\n    dir: \"dist\",\n    format: \"es\",\n  },\n  plugins: [typescript()],\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/delay.ts",
    "content": "export function delay(ms: number) {\n  return new Promise((resolve) => setTimeout(resolve, ms))\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/env.d.ts",
    "content": "declare module \"@baurine/sql-formatter-plus\"\ndeclare module \"string-template\"\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/format.ts",
    "content": "import { getValueFormat } from \"@baurine/grafana-value-formats\"\nimport { format } from \"@baurine/sql-formatter-plus\"\nimport { dayjs } from \"@tidbcloud/uikit/utils\"\nimport prettyMs from \"pretty-ms\"\n\nexport function formatTime(\n  value: number | Date, // number is unix timestamp, unit is milliseconds\n  format: string = \"YYYY-MM-DD HH:mm:ss\",\n) {\n  return dayjs(value).format(format)\n}\n\nexport function formatDuration(seconds: number, short = false) {\n  if (short) {\n    return prettyMs(seconds * 1000, { compact: true })\n  } else {\n    return prettyMs(seconds * 1000, { verbose: true })\n  }\n}\n\nexport function formatNumByUnit(\n  value: number,\n  unit: string,\n  precision: number = 1,\n) {\n  if (isNaN(value)) {\n    return \"\"\n  }\n  const formatFn = getValueFormat(unit)\n  if (!formatFn) {\n    return value + \"\"\n  }\n  if (unit === \"short\") {\n    return formatFn(value, 0, precision)\n  }\n  return formatFn(value, precision)\n}\n\nexport function formatSql(sql: string, compact: boolean = false): string {\n  let formattedSQL = sql\n  try {\n    formattedSQL = format(sql, { uppercase: true, language: \"tidb\" })\n  } catch (_e) {\n    console.log(sql)\n  }\n  if (compact) {\n    formattedSQL = simpleMinifySql(formattedSQL)\n  }\n  return formattedSQL\n}\n\n// remove extra spaces to make sql more compact\nexport function simpleMinifySql(str: string) {\n  return str\n    .replace(/\\s{1,}/g, \" \")\n    .replace(/\\{\\s{1,}/g, \"{\")\n    .replace(/\\}\\s{1,}/g, \"}\")\n    .replace(/;\\s{1,}/g, \";\")\n    .replace(/\\/\\*\\s{1,}/g, \"/*\")\n    .replace(/\\*\\/\\s{1,}/g, \"*/\")\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/i18n.ts",
    "content": "import { useHotkeys } from \"@tidbcloud/uikit/hooks\"\nimport i18next, { Resource, TOptions } from \"i18next\"\nimport LanguageDetector from \"i18next-browser-languagedetector\"\nimport { useCallback, useMemo } from \"react\"\nimport { initReactI18next, useTranslation } from \"react-i18next\"\n\nexport { Trans } from \"react-i18next\"\n\nconst DEF_DISTRO = {\n  pd: \"PD\",\n  tidb: \"TiDB\",\n  tikv: \"TiKV\",\n  tiflash: \"TiFlash\",\n  ticdc: \"TiCDC\",\n}\n\nexport function initI18n() {\n  i18next\n    .use(initReactI18next)\n    .use(LanguageDetector)\n    .init({\n      resources: {},\n      fallbackLng: \"en\", // fallbackLng won't change the detected language\n      supportedLngs: [\"zh\", \"en\"], // supportedLngs will change the detected language\n      interpolation: {\n        escapeValue: false,\n        defaultVariables: { distro: DEF_DISTRO },\n      },\n    })\n\n  return i18next\n}\n\nexport function changeLang(lang: string) {\n  i18next.changeLanguage(lang)\n}\n\nfunction addResourceBundles(langsLocales: Resource) {\n  Object.keys(langsLocales).forEach((lang) => {\n    const locales = langsLocales[lang]\n    const ns = locales[\"__namespace__\"] as string\n    if (!ns) {\n      throw new Error(`__namespace__ not found in locales`)\n    }\n    i18next.addResourceBundle(lang, ns, locales, true, true)\n  })\n}\n\nexport function addLangsLocales(langsLocales: Resource) {\n  if (i18next.isInitialized) {\n    addResourceBundles(langsLocales)\n  } else {\n    i18next.on(\"initialized\", function (_options) {\n      addResourceBundles(langsLocales)\n    })\n  }\n}\n\nexport function useTn(ns: string) {\n  const { t, i18n } = useTranslation()\n\n  // translate by key\n  // example:\n  // tk('panels.instance_top.title', 'Top 5 Node Utilization')\n  // tk(`panels.${category}.title`)\n  // tk(\"time_range.hour\", \"{{count}} hr\", { count: 1 })\n  // tk(\"time_range.hour\", \"{{count}} hrs\", { count: 24 })\n  // tk(\"time_range.hour\", \"\", {count: n})\n  const tk = useCallback(\n    (i18nKey: string, defVal?: string, options?: TOptions) => {\n      return t(i18nKey, defVal ?? i18nKey, { ns, ...options })\n    },\n    [t, ns],\n  )\n\n  // translate by text\n  // example:\n  // tt(\"how are you?\")\n  // tt(\"Hello.World\")\n  // tt(\"Clear Filters\")\n  // tt(\"hello {{name}}\", { name: \"world\" })\n  const tt = useCallback(\n    (text: string, options?: TOptions) => {\n      return t(text, text, { ns, ...options })\n    },\n    [t, ns],\n  )\n\n  const ret = useMemo(() => {\n    return { tk, tt, i18n, t }\n  }, [tk, tt, i18n, t])\n\n  return ret\n}\n\nexport function useHotkeyChangeLang(hotkey: string = \"mod+L\") {\n  const { i18n } = useTranslation()\n  useHotkeys([\n    [hotkey, () => i18n.changeLanguage(i18n.language === \"en\" ? \"zh\" : \"en\")],\n  ])\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/index.ts",
    "content": "// decided not to export\n// export * from \"@tidbcloud/uikit/hooks\"\n// export * from \"@tidbcloud/uikit/utils\"\n\nexport * from \"./format\"\nexport * from \"./delay\"\nexport * from \"./prom\"\nexport * from \"./time-range\"\nexport * from \"./i18n\"\n\nexport * from \"./url-state\"\nexport * from \"./memory-state\"\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/memory-state/index.ts",
    "content": "export * from \"./reset-filters-state\"\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/memory-state/reset-filters-state.ts",
    "content": "import { create } from \"zustand\"\n\ninterface ResetFiltersState {\n  resetVal: number\n  reset: () => void\n}\n\nexport const useResetFiltersState = create<ResetFiltersState>((set) => ({\n  resetVal: 0,\n  reset: () => set({ resetVal: Date.now() }),\n}))\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/prom.ts",
    "content": "import interpolate from \"string-template\"\n\nexport enum TransformNullValue {\n  NULL = \"null\",\n  AS_ZERO = \"as_zero\",\n}\n\nexport type PromResultItem = {\n  metric: Record<string, string>\n  values: ([number, string] | { timestamp: number; value: string })[]\n}\n\nexport type PromSeriesItem = {\n  name: string\n  data: [number, number | null][]\n}\n\n////////////////////////////////\n\nconst POSITIVE_INFINITY_SAMPLE_VALUE = \"+Inf\"\nconst NEGATIVE_INFINITY_SAMPLE_VALUE = \"-Inf\"\n\nfunction parseStrVal(value: string): number {\n  switch (value) {\n    case POSITIVE_INFINITY_SAMPLE_VALUE:\n      return Number.POSITIVE_INFINITY\n    case NEGATIVE_INFINITY_SAMPLE_VALUE:\n      return Number.NEGATIVE_INFINITY\n    default:\n      return parseFloat(value)\n  }\n}\n\nfunction transformStrVal(value: string, nullValue?: TransformNullValue) {\n  let v: number | null = parseStrVal(value)\n  if (isNaN(v)) {\n    if (nullValue === TransformNullValue.AS_ZERO) {\n      v = 0\n    } else {\n      v = null\n    }\n  }\n  return v\n}\n\nexport function transformPromResultItem(\n  item: PromResultItem,\n  nameTemplate: string,\n  nullValue?: TransformNullValue,\n): PromSeriesItem {\n  let name = interpolate(nameTemplate, item.metric)\n  if (name === \"\") {\n    name = Object.entries(item.metric)\n      .filter(([, v]) => v !== \"\")\n      .map(([k, v]) => `${k}=${v}`)\n      .join(\",\")\n    if (name !== \"\") {\n      name = \"{\" + name + \"}\"\n    }\n  }\n  if (name === \"\") {\n    name = \"value\"\n  }\n  return {\n    name,\n    data: item.values.map((v) => {\n      const [ts, val] = Array.isArray(v) ? v : [v.timestamp, v.value]\n      return [ts * 1000, transformStrVal(val, nullValue)]\n    }),\n  }\n}\n\n////////////////////////////////\n\nexport const DEF_SCRAPE_INTERVAL = 30\n\nexport function resolvePromQLTemplate(\n  promql: string,\n  step: number,\n  scrapeInterval: number = DEF_SCRAPE_INTERVAL,\n): string {\n  return promql.replace(\n    /\\$__rate_interval/g,\n    `${Math.max(step + scrapeInterval, 4 * scrapeInterval)}s`,\n  )\n}\n\nexport function calcPromQueryStep(\n  tr: [number, number],\n  width: number,\n  scrapeInterval: number = DEF_SCRAPE_INTERVAL,\n  minBinWidth: number = 4,\n) {\n  if (width <= 0) {\n    return scrapeInterval\n  }\n  const points = width / minBinWidth\n  const step = (tr[1] - tr[0]) / points\n  const fixedStep = Math.ceil(step / scrapeInterval) * scrapeInterval\n  return fixedStep\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/time-range.ts",
    "content": "import { dayjs } from \"@tidbcloud/uikit/utils\"\n\n////////////////////////////////////////////////////////////\n\nexport type TimeRangeValue = [from: number, to: number]\n\nexport interface RelativeTimeRange {\n  // to be compatible, keep \"relative\", and \"before-now\" has same meaning with \"relative\"\n  type: \"relative\" | \"before-to-now\" | \"now-to-future\"\n  value: number // unit: seconds\n}\n\nexport interface AbsoluteTimeRange {\n  type: \"absolute\"\n  value: TimeRangeValue // unit: seconds\n}\n\nexport type TimeRange = RelativeTimeRange | AbsoluteTimeRange\n\n////////////////////////////////////////////////////////////\n\nexport const toTimeRangeValue = (\n  timeRange: TimeRange,\n  offset = 0,\n): TimeRangeValue => {\n  if (timeRange.type === \"absolute\") {\n    return timeRange.value.map((t) => t + offset) as TimeRangeValue\n  } else {\n    const now = dayjs().unix()\n    if (timeRange.type === \"now-to-future\") {\n      return [now + offset, now + timeRange.value + offset]\n    }\n    return [now - timeRange.value + offset, now + offset]\n  }\n}\n\nexport function fromTimeRangeValue(v: TimeRangeValue): AbsoluteTimeRange {\n  return {\n    type: \"absolute\",\n    value: [...v],\n  }\n}\n\n////////////////////////////////////////////////////////////\n\nexport type URLTimeRange = { from: string; to: string }\n\nexport const toURLTimeRange = (timeRange: TimeRange): URLTimeRange => {\n  if (timeRange.type === \"absolute\") {\n    return { from: timeRange.value[0] + \"\", to: timeRange.value[1] + \"\" }\n  }\n  if (timeRange.type === \"now-to-future\") {\n    return { from: \"now\", to: timeRange.value + \"\" }\n  }\n  return { from: `${timeRange.value}`, to: \"now\" }\n}\n\nexport const urlToTimeRange = (urlTimeRange: URLTimeRange): TimeRange => {\n  if (urlTimeRange.from === \"now\") {\n    return { type: \"now-to-future\", value: Number(urlTimeRange.to) }\n  }\n  if (urlTimeRange.to === \"now\") {\n    return { type: \"relative\", value: Number(urlTimeRange.from) }\n  }\n  return {\n    type: \"absolute\",\n    value: [Number(urlTimeRange.from), Number(urlTimeRange.to)],\n  }\n}\n\nexport const urlToTimeRangeValue = (\n  urlTimeRange: URLTimeRange,\n): TimeRangeValue => {\n  return toTimeRangeValue(urlToTimeRange(urlTimeRange))\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/url-state/advanced-filters-url-state.ts",
    "content": "import { useCallback, useMemo } from \"react\"\n\nimport { PaginationUrlState } from \"./pagination-url-state\"\nimport { useUrlState } from \"./use-url-state\"\n\nexport type AdvancedFilterItem = {\n  name: string\n  operator: string // =, !=, >, >=, <, <=, in, not in\n  values: string[]\n}\n\nexport type AdvancedFiltersUrlState = Partial<Record<\"af\", string>>\n\nexport function useAdvancedFiltersUrlState() {\n  const [queryParams, setQueryParams] = useUrlState<\n    AdvancedFiltersUrlState & PaginationUrlState\n  >()\n\n  const advancedFilters = useMemo<AdvancedFilterItem[]>(() => {\n    const filtersObjArr: AdvancedFilterItem[] = []\n    if (queryParams.af) {\n      const filtersArr = queryParams.af.split(\";\")\n      filtersArr.forEach((filter) => {\n        const [filterName, filterOperator, ...filterValues] = filter.split(\",\")\n        if (filterName && filterOperator) {\n          filtersObjArr.push({\n            name: decodeURIComponent(filterName),\n            operator: decodeURIComponent(filterOperator),\n            values: filterValues.map((v) => decodeURIComponent(v)),\n          })\n        }\n      })\n    }\n    return filtersObjArr\n  }, [queryParams.af])\n\n  const setAdvancedFilters = useCallback(\n    (newAdvancedFilters: AdvancedFilterItem[]) => {\n      const afStr = newAdvancedFilters\n        .map(\n          (f) =>\n            `${encodeURIComponent(f.name)},${encodeURIComponent(\n              f.operator,\n            )},${f.values.map((v) => encodeURIComponent(v)).join(\",\")}`,\n        )\n        .join(\";\")\n      setQueryParams({\n        af: afStr,\n        pageIndex: undefined,\n      })\n    },\n    [setQueryParams],\n  )\n\n  return {\n    advancedFilters,\n    setAdvancedFilters,\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/url-state/index.ts",
    "content": "export * from \"./use-url-state\"\n\nexport * from \"./pagination-url-state\"\nexport * from \"./sort-url-state\"\n\nexport * from \"./timerange-url-state\"\nexport * from \"./search-url-state\"\nexport * from \"./advanced-filters-url-state\"\n\nexport * from \"./pro-table-sort-state\"\nexport * from \"./pro-table-pagination-state\"\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/url-state/pagination-url-state.ts",
    "content": "import { useCallback, useMemo } from \"react\"\n\nimport { useUrlState } from \"./use-url-state\"\n\nexport type Pagination = {\n  pageIndex: number\n  pageSize: number\n}\n\nexport type PaginationUrlState = Partial<\n  Record<\"pageIndex\" | \"pageSize\", string>\n>\n\nexport function usePaginationUrlState(defPageSize: number = 15) {\n  const [queryParams, setQueryParams] = useUrlState<PaginationUrlState>()\n\n  const pagination = useMemo<Pagination>(() => {\n    return {\n      pageIndex: Number(queryParams.pageIndex) || 0,\n      pageSize: Number(queryParams.pageSize) || defPageSize,\n    }\n  }, [queryParams.pageIndex, queryParams.pageSize, defPageSize])\n\n  const setPagination = useCallback(\n    (newPagination: Pagination) => {\n      setQueryParams({\n        pageIndex:\n          newPagination.pageIndex === 0\n            ? undefined\n            : newPagination.pageIndex.toString(),\n        pageSize:\n          newPagination.pageSize === defPageSize\n            ? undefined\n            : newPagination.pageSize.toString(),\n      })\n    },\n    [setQueryParams],\n  )\n\n  return { pagination, setPagination }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/url-state/pro-table-pagination-state.ts",
    "content": "import { MRT_PaginationState, ProTableOptions } from \"@tidbcloud/uikit/biz\"\nimport { useCallback } from \"react\"\n\nimport { Pagination } from \"./pagination-url-state\"\n\ntype onPaginationChangeFn = Required<ProTableOptions>[\"onPaginationChange\"]\n\nexport function useProTablePaginationState(\n  pagination: Pagination,\n  setPagination: (v: Pagination) => void,\n): {\n  paginationState: MRT_PaginationState\n  setPaginationState: onPaginationChangeFn\n} {\n  const paginationState = pagination\n\n  const setPaginationState = useCallback<onPaginationChangeFn>(\n    (updater) => {\n      const newPagination =\n        typeof updater === \"function\" ? updater(paginationState) : updater\n      setPagination(newPagination)\n    },\n    [setPagination, paginationState.pageIndex, paginationState.pageSize],\n  )\n\n  return { paginationState, setPaginationState }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/url-state/pro-table-sort-state.ts",
    "content": "import { MRT_SortingState, ProTableOptions } from \"@tidbcloud/uikit/biz\"\nimport { useCallback, useMemo } from \"react\"\n\nimport { SortRule } from \"./sort-url-state\"\n\ntype onSortChangeFn = Required<ProTableOptions>[\"onSortingChange\"]\n\nexport function useProTableSortState(\n  sortRule: SortRule,\n  setSortRule: (v: SortRule) => void,\n): {\n  sortingState: MRT_SortingState\n  setSortingState: onSortChangeFn\n} {\n  const sortingState = useMemo(() => {\n    if (sortRule.orderBy) {\n      return [{ id: sortRule.orderBy, desc: sortRule.desc }]\n    }\n    return []\n  }, [sortRule.orderBy, sortRule.desc])\n\n  const setSortingState = useCallback<onSortChangeFn>(\n    (updater) => {\n      const newSort =\n        typeof updater === \"function\" ? updater(sortingState) : updater\n      if (newSort === sortingState) {\n        return\n      }\n      if (newSort.length > 0) {\n        setSortRule({ orderBy: newSort[0].id, desc: newSort[0].desc })\n      } else {\n        setSortRule({ orderBy: \"\", desc: true })\n      }\n    },\n    [setSortRule, sortingState],\n  )\n\n  return { sortingState, setSortingState }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/url-state/search-url-state.ts",
    "content": "import { useCallback } from \"react\"\n\nimport { PaginationUrlState } from \"./pagination-url-state\"\nimport { useUrlState } from \"./use-url-state\"\n\nexport type SearchUrlState = Partial<Record<\"term\", string>>\n\nexport function useSearchUrlState() {\n  const [queryParams, setQueryParams] = useUrlState<\n    SearchUrlState & PaginationUrlState\n  >()\n\n  const term = decodeURIComponent(queryParams.term ?? \"\")\n  const setTerm = useCallback(\n    (v?: string) => {\n      setQueryParams({\n        term: v ? encodeURIComponent(v) : v,\n        pageIndex: undefined,\n      })\n    },\n    [setQueryParams],\n  )\n\n  return { term, setTerm }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/url-state/sort-url-state.ts",
    "content": "import { useCallback, useMemo } from \"react\"\n\nimport { PaginationUrlState } from \"./pagination-url-state\"\nimport { useUrlState } from \"./use-url-state\"\n\nexport type SortRule = {\n  orderBy: string\n  desc: boolean\n}\n\nexport type SortUrlState = Partial<Record<\"orderBy\" | \"desc\", string>>\n\nexport function useSortUrlState(defOrderBy: string = \"\") {\n  const [queryParams, setQueryParams] = useUrlState<\n    SortUrlState & PaginationUrlState\n  >()\n\n  const sortRule = useMemo<SortRule>(() => {\n    return {\n      orderBy: queryParams.orderBy ?? defOrderBy,\n      desc: queryParams.desc !== \"false\",\n    }\n  }, [queryParams.orderBy, queryParams.desc, defOrderBy])\n  const setSortRule = useCallback(\n    (newSortRule: SortRule) => {\n      setQueryParams({\n        orderBy: newSortRule.orderBy || undefined,\n        desc: newSortRule.desc ? undefined : \"false\",\n        pageIndex: undefined,\n      })\n    },\n    [setQueryParams],\n  )\n\n  return { sortRule, setSortRule }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/url-state/timerange-url-state.ts",
    "content": "import { useCallback, useMemo } from \"react\"\n\nimport { TimeRange, toURLTimeRange, urlToTimeRange } from \"../time-range\"\n\nimport { PaginationUrlState } from \"./pagination-url-state\"\nimport { useUrlState } from \"./use-url-state\"\n\nexport type TimeRangeUrlState = Partial<Record<\"from\" | \"to\", string>>\n\nconst DEF_TIME_RANGE: TimeRange = {\n  type: \"relative\",\n  value: 30 * 60,\n}\n\nexport function useTimeRangeUrlState(defTimeRange?: TimeRange) {\n  const [queryParams, setQueryParams] = useUrlState<\n    TimeRangeUrlState & PaginationUrlState\n  >()\n\n  const timeRange = useMemo(() => {\n    const { from, to } = queryParams\n    if (from && to) {\n      return urlToTimeRange({ from, to })\n    }\n    return defTimeRange || DEF_TIME_RANGE\n  }, [queryParams.from, queryParams.to, defTimeRange])\n  const setTimeRange = useCallback(\n    (newTimeRange: TimeRange) => {\n      setQueryParams({\n        ...toURLTimeRange(newTimeRange),\n        pageIndex: undefined,\n      })\n    },\n    [setQueryParams],\n  )\n\n  return { timeRange, setTimeRange }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/src/url-state/use-url-state.tsx",
    "content": "import {\n  createContext,\n  useCallback,\n  useContext,\n  useMemo,\n  useState,\n} from \"react\"\n\ntype UrlStateCtxValue = {\n  urlQuery: string\n  setUrlQuery: (v: string) => void\n}\n\nconst UrlStateContext = createContext<UrlStateCtxValue | null>(null)\n\nconst useUrlStateContext = () => {\n  const context = useContext(UrlStateContext)\n\n  if (!context) {\n    throw new Error(\"useUrlStateContext must be used within a UrlStateProvider\")\n  }\n\n  return context\n}\n\nfunction defCtxVal(): UrlStateCtxValue {\n  return {\n    urlQuery: new URL(window.location.href).search,\n    setUrlQuery(p) {\n      const url = new URL(window.location.href)\n      window.history.replaceState({}, \"\", `${url.pathname}?${p}`)\n    },\n  }\n}\n\nexport function UrlStateProvider(props: {\n  children: React.ReactNode\n  value?: UrlStateCtxValue\n}) {\n  const val: UrlStateCtxValue = props.value || defCtxVal()\n\n  const [urlQuery, _setUrlQuery] = useState(val.urlQuery)\n\n  // UrlStateProvider is designed to each page has its own provider instance,\n  // won't share between pages\n  // so we don't need to sync urlQuery from props\n  // -------------------\n  // sync urlQuery from props changes\n  // useEffect(() => {\n  //   _setUrlQuery(val.urlQuery)\n  // }, [val.urlQuery])\n\n  const ctxValue = useMemo(\n    () => ({\n      urlQuery,\n      setUrlQuery: (v: string) => {\n        val.setUrlQuery(v)\n        // trigger re-render\n        _setUrlQuery(v)\n      },\n    }),\n    [urlQuery, val],\n  )\n\n  return (\n    <UrlStateContext.Provider value={ctxValue}>\n      {props.children}\n    </UrlStateContext.Provider>\n  )\n}\n\n//----------------------\n\ntype UrlState = Partial<Record<string, string>>\ntype UrlStateObj<T extends UrlState = UrlState> = {\n  [key in keyof T]: string\n}\n\nexport function useUrlState<T extends UrlState = UrlState>(): [\n  UrlStateObj<T>,\n  (s: UrlStateObj<T>) => void,\n] {\n  const { urlQuery, setUrlQuery } = useUrlStateContext()\n\n  const queryParams = useMemo(() => {\n    console.log(\"1 ---- urlQuery:\", urlQuery)\n    const searchParams = new URLSearchParams(urlQuery)\n    const paramsObj: Record<string, string> = {}\n    searchParams.forEach((v, k) => {\n      paramsObj[k] = v\n    })\n    return paramsObj as UrlStateObj<T>\n  }, [urlQuery])\n\n  const setQueryParams = useCallback(\n    (s: UrlStateObj<T>) => {\n      console.log(\"2 ---- urlQuery:\", urlQuery)\n      console.log(\"3 ---- s:\", s)\n      const searchParams = new URLSearchParams(urlQuery)\n      Object.keys(s).forEach((k) => {\n        if (s[k]) {\n          searchParams.set(k, s[k])\n        } else {\n          searchParams.delete(k)\n        }\n      })\n      // searchParams.toString() will do encodeURIComponent internally\n      setUrlQuery(searchParams.toString())\n    },\n    [setUrlQuery, urlQuery],\n  )\n\n  return [queryParams, setQueryParams] as const\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/1-utils/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.app.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\"\n  },\n  \"include\": [\"./src\"]\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/2-primitive-ui/CHANGELOG.md",
    "content": "# @pingcap-incubator/tidb-dashboard-lib-primitive-ui\n\n## 0.14.0\n\n### Minor Changes\n\n- bump version\n\n## 0.13.0\n\n### Minor Changes\n\n- refactor: re-exports lib-uitls/lib-charts/lib-primitive-ui/lib-biz-ui from lib-apps\n\n## 0.12.0\n\n### Minor Changes\n\n- upgrade uikit, refine table empty status, refine chart empty legend name case\n\n## 0.11.0\n\n### Minor Changes\n\n- fix time-range-picker, refine cols-multi-select\n\n## 0.10.0\n\n### Minor Changes\n\n- i18n for slow query and statement app\n\n## 0.9.0\n\n### Minor Changes\n\n- upgrade uikit\n\n## 0.8.0\n\n### Minor Changes\n\n- support advanced filters for diagnosis\n\n## 0.7.0\n\n### Minor Changes\n\n- refine pagination and sort url state\n\n## 0.6.0\n\n### Minor Changes\n\n- refine\n\n## 0.5.0\n\n### Minor Changes\n\n- refine slow-query and statement apps\n\n## 0.4.0\n\n### Minor Changes\n\n- update uikit\n\n## 0.3.0\n\n### Minor Changes\n\n- add azores cluster metrics page\n\n## 0.2.0\n\n### Minor Changes\n\n- add metrics azores host page\n\n## 0.1.0\n\n### Minor Changes\n\n- update i18n, format utils\n\n## 0.0.7\n\n### Patch Changes\n\n- add i18n\n\n## 0.0.6\n\n### Patch Changes\n\n- support dark mode for lib-charts\n\n## 0.0.5\n\n### Patch Changes\n\n- refine\n\n## 0.0.4\n\n### Patch Changes\n\n- refactor metric\n\n## 0.0.3\n\n### Patch Changes\n\n- upgrade uikit\n\n## 0.0.2\n\n### Patch Changes\n\n- first release\n"
  },
  {
    "path": "ui-v2/packages/libs/2-primitive-ui/package.json",
    "content": "{\n  \"name\": \"@pingcap-incubator/tidb-dashboard-lib-primitive-ui\",\n  \"version\": \"0.14.0\",\n  \"description\": \"\",\n  \"type\": \"module\",\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"files\": [\n    \"dist\",\n    \"README.md\",\n    \"CHANGELOG.md\"\n  ],\n  \"scripts\": {\n    \"tsc:watch\": \"tsc --watch\",\n    \"rollup:watch\": \"rollup -c --watch\",\n    \"dev\": \"concurrently --kill-others \\\"pnpm tsc:watch\\\" \\\"pnpm rollup:watch\\\"\",\n    \"build\": \"tsc && rollup -c\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@rollup/plugin-typescript\": \"^12.1.1\",\n    \"@tidbcloud/uikit\": \"catalog:\",\n    \"@types/react\": \"^18.3.12\",\n    \"react\": \"^18.3.1\",\n    \"rollup\": \"^4.24.0\",\n    \"tslib\": \"^2.8.0\"\n  },\n  \"peerDependencies\": {\n    \"@tidbcloud/uikit\": \"catalog:\",\n    \"react\": \"^18.3.1\"\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/2-primitive-ui/rollup.config.js",
    "content": "import typescript from \"@rollup/plugin-typescript\"\n\nexport default {\n  input: \"src/index.ts\",\n  output: {\n    dir: \"dist\",\n    format: \"es\",\n  },\n  plugins: [typescript()],\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/2-primitive-ui/src/index.ts",
    "content": "// decided not to export\n// export * from \"@tidbcloud/uikit\"\n\nexport * from \"./uikit-theme-provider\"\n"
  },
  {
    "path": "ui-v2/packages/libs/2-primitive-ui/src/uikit-theme-provider.tsx",
    "content": "import { ColorScheme, useColorScheme } from \"@tidbcloud/uikit\"\nimport { useHotkeys } from \"@tidbcloud/uikit/hooks\"\nimport { ThemeProvider } from \"@tidbcloud/uikit/theme\"\n\nexport const UiKitThemeProvider = ({\n  children,\n}: React.PropsWithChildren<unknown>) => {\n  const { colorScheme, setColorScheme } = useColorScheme(\"auto\", {\n    getInitialValueInEffect: false,\n    key: \"mantine-color-scheme\",\n  })\n\n  const toggleColorScheme = (value?: ColorScheme) => {\n    setColorScheme(value || (colorScheme === \"dark\" ? \"light\" : \"dark\"))\n  }\n\n  useHotkeys([[\"mod+J\", () => toggleColorScheme()]])\n\n  return (\n    <ThemeProvider\n      colorScheme={colorScheme}\n      notifications={{\n        position: \"top-center\",\n      }}\n    >\n      {children}\n    </ThemeProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/2-primitive-ui/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.app.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\"\n  },\n  \"include\": [\"./src\"]\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/CHANGELOG.md",
    "content": "# @pingcap-incubator/tidb-dashboard-lib-biz-ui\n\n## 0.17.0\n\n### Minor Changes\n\n- bump version\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.15.0\n\n## 0.16.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.14.0\n\n## 0.16.0\n\n### Minor Changes\n\n- refactor: re-exports lib-uitls/lib-charts/lib-primitive-ui/lib-biz-ui from lib-apps\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.13.0\n\n## 0.15.1\n\n### Patch Changes\n\n- support now-to-future type relative time range\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.12.1\n\n## 0.15.0\n\n### Minor Changes\n\n- upgrade uikit, refine table empty status, refine chart empty legend name case\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.12.0\n\n## 0.14.3\n\n### Patch Changes\n\n- adjust monitoring panel names\n\n## 0.14.2\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.11.2\n\n## 0.14.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.11.1\n\n## 0.14.0\n\n### Minor Changes\n\n- refine\n\n## 0.13.2\n\n### Patch Changes\n\n- enable plan-table columns resize\n\n## 0.13.1\n\n### Patch Changes\n\n- update json view style\n\n## 0.13.0\n\n### Minor Changes\n\n- update metrics charts\n\n## 0.12.0\n\n### Minor Changes\n\n- refine plan table and fix highlight-sql components\n\n## 0.11.3\n\n### Patch Changes\n\n- refine advanced fitlers\n\n## 0.11.2\n\n### Patch Changes\n\n- fix NumberInput cannot input 0 bug\n\n## 0.11.1\n\n### Patch Changes\n\n- fix cols-multi-select style\n\n## 0.11.0\n\n### Minor Changes\n\n- fix time-range-picker, refine cols-multi-select\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.11.0\n\n## 0.10.0\n\n### Minor Changes\n\n- i18n for slow query and statement app\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.10.0\n\n## 0.9.1\n\n### Patch Changes\n\n- refine advanced filters component\n\n## 0.9.0\n\n### Minor Changes\n\n- upgrade uikit\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.9.0\n\n## 0.8.3\n\n### Patch Changes\n\n- show raw json info for slow query and statement detail\n\n## 0.8.2\n\n### Patch Changes\n\n- add columns select, time range alert\n\n## 0.8.1\n\n### Patch Changes\n\n- renaming fields\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.8.1\n\n## 0.8.0\n\n### Minor Changes\n\n- support advanced filters for diagnosis\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.8.0\n\n## 0.7.0\n\n### Minor Changes\n\n- refine pagination and sort url state\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.7.0\n\n## 0.6.1\n\n### Patch Changes\n\n- refine\n\n## 0.6.0\n\n### Minor Changes\n\n- refine\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.6.0\n\n## 0.5.0\n\n### Minor Changes\n\n- refine slow-query and statement apps\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.5.0\n\n## 0.4.0\n\n### Minor Changes\n\n- update uikit\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.4.0\n\n## 0.3.0\n\n### Minor Changes\n\n- add azores cluster metrics page\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.3.0\n\n## 0.2.0\n\n### Minor Changes\n\n- add metrics azores host page\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.2.0\n\n## 0.1.0\n\n### Minor Changes\n\n- update i18n, format utils\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.1.0\n\n## 0.0.10\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.10\n\n## 0.0.9\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.9\n\n## 0.0.8\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.8\n\n## 0.0.7\n\n### Patch Changes\n\n- add i18n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.7\n\n## 0.0.6\n\n### Patch Changes\n\n- support dark mode for lib-charts\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.6\n\n## 0.0.5\n\n### Patch Changes\n\n- refine\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.5\n\n## 0.0.4\n\n### Patch Changes\n\n- refactor metric\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.4\n\n## 0.0.3\n\n### Patch Changes\n\n- upgrade uikit\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.3\n\n## 0.0.2\n\n### Patch Changes\n\n- first release\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.2\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/package.json",
    "content": "{\n  \"name\": \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\",\n  \"version\": \"0.17.0\",\n  \"description\": \"\",\n  \"type\": \"module\",\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"files\": [\n    \"dist\",\n    \"README.md\",\n    \"CHANGELOG.md\"\n  ],\n  \"scripts\": {\n    \"tsc:watch\": \"tsc --watch\",\n    \"rollup:watch\": \"rollup -c --watch\",\n    \"dev\": \"concurrently --kill-others \\\"pnpm tsc:watch\\\" \\\"pnpm rollup:watch\\\"\",\n    \"build\": \"tsc && rollup -c\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@rollup/plugin-typescript\": \"^12.1.1\",\n    \"@tidbcloud/uikit\": \"catalog:\",\n    \"@types/react\": \"^18.3.12\",\n    \"react\": \"^18.3.1\",\n    \"rollup\": \"^4.24.0\",\n    \"tslib\": \"^2.8.0\"\n  },\n  \"peerDependencies\": {\n    \"@tidbcloud/uikit\": \"catalog:\",\n    \"react\": \"^18.3.1\"\n  },\n  \"dependencies\": {\n    \"@pingcap-incubator/tidb-dashboard-lib-utils\": \"workspace:^\",\n    \"ahooks\": \"^3.8.1\",\n    \"react-json-view-lite\": \"^2.0.1\"\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/rollup.config.js",
    "content": "import typescript from \"@rollup/plugin-typescript\"\n\nexport default {\n  input: \"src/index.ts\",\n  output: {\n    dir: \"dist\",\n    format: \"es\",\n  },\n  plugins: [typescript()],\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/action-drawer.tsx",
    "content": "import {\n  Box,\n  BoxProps,\n  Drawer,\n  DrawerProps,\n  Group,\n  GroupProps,\n} from \"@tidbcloud/uikit\"\n\nexport const ActionDrawer = (props: DrawerProps) => {\n  return (\n    <Drawer\n      position=\"right\"\n      size=\"30rem\"\n      styles={(theme) => ({\n        content: {\n          display: \"flex\",\n          flexDirection: \"column\",\n          transitionProperty: \"flex-basis, transform, opacity !important\",\n        },\n        header: {\n          paddingLeft: 24,\n          backgroundColor: theme.colors.carbon[0],\n          borderBottom: `1px solid ${theme.colors.carbon[4]}`,\n        },\n        title: {\n          fontWeight: 700,\n          fontSize: 16,\n          lineHeight: 1.5,\n          color: theme.colors.carbon[9],\n        },\n        body: {\n          flex: 1,\n          display: \"flex\",\n          flexDirection: \"column\",\n          padding: 0,\n          overflowY: \"hidden\",\n        },\n      })}\n      {...props}\n    />\n  )\n}\n\nconst ActionDrawerBody = (props: React.PropsWithChildren<BoxProps>) => {\n  return <Box sx={{ flex: 1, padding: 24, overflowY: \"auto\" }} {...props} />\n}\n\nconst ActionDrawerFooter = (props: GroupProps) => {\n  return (\n    <Group\n      justify=\"flex-end\"\n      px={24}\n      py=\"md\"\n      sx={(theme) => ({\n        borderTop: `1px solid ${theme.colors.carbon[4]}`,\n        backgroundColor: theme.colors.carbon[0],\n        position: \"sticky\",\n        bottom: 0,\n      })}\n      {...props}\n    />\n  )\n}\n\nActionDrawer.Body = ActionDrawerBody\nActionDrawer.Footer = ActionDrawerFooter\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/advanced-filters/filter-setting.tsx",
    "content": "import {\n  AdvancedFilterItem,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  ActionIcon,\n  Box,\n  Group,\n  NumberInput,\n  Select,\n  TextInput,\n  Typography,\n} from \"@tidbcloud/uikit\"\nimport { IconTrash01 } from \"@tidbcloud/uikit/icons\"\nimport { useEffect } from \"react\"\n\nimport { FilterMultiSelect } from \"../filter-multi-select\"\n\n// AdvancedFilterItem represent the filter from url\nexport type AdvancedFilterSettingItem = AdvancedFilterItem & {\n  createdAt: number\n  deleted: boolean\n}\n\n// AdvancedFilterInfo represent the info from backend\nexport type AdvancedFilterInfo = {\n  name: string\n  type: string // string | number | bool\n  unit: string\n  values: string[]\n}\n\nexport function AdvancedFilterSetting({\n  availableFilters,\n  filtersInfo,\n  onReqFilterInfo,\n  filter,\n  onUpdate,\n  showDelete = true,\n  conditionLabel = \"AND\",\n}: {\n  availableFilters: Array<string | { label: string; value: string }>\n  filtersInfo?: AdvancedFilterInfo[]\n  onReqFilterInfo?: (filterName: string) => void\n  filter: AdvancedFilterSettingItem\n  onUpdate?: (item: AdvancedFilterSettingItem) => void\n  showDelete?: boolean\n  conditionLabel?: string\n}) {\n  const { tt } = useTn(\"advanced-filters\")\n\n  const filterInfo = filtersInfo?.find((f) => f.name === filter.name)\n\n  useEffect(() => {\n    if (filter.name) {\n      const fInfo = filtersInfo?.find((f) => f.name === filter.name)\n      if (!fInfo) {\n        onReqFilterInfo?.(filter.name)\n      }\n    }\n  }, [filter.name])\n\n  return (\n    <Group>\n      <Typography w={42}>{conditionLabel}</Typography>\n\n      <Select\n        w={240}\n        searchable\n        placeholder={tt(\"Filter Name\")}\n        data={availableFilters}\n        value={filter.name}\n        onChange={(v) => onUpdate?.({ ...filter, name: v || \"\", values: [] })}\n      />\n\n      <Box w={100}>\n        {filterInfo && filterInfo.values.length > 0 && (\n          <Select\n            data={[\"in\", \"not in\"]}\n            value={filter.operator || \"in\"}\n            onChange={(v) => onUpdate?.({ ...filter, operator: v || \"in\" })}\n          />\n        )}\n        {filterInfo && filterInfo.values.length === 0 && (\n          <Select\n            data={[\"=\", \"!=\", \">\", \">=\", \"<\", \"<=\"]}\n            value={filter.operator || \"=\"}\n            onChange={(v) => onUpdate?.({ ...filter, operator: v || \"=\" })}\n          />\n        )}\n      </Box>\n\n      <Box w={240}>\n        {filterInfo && filterInfo.values.length > 0 && (\n          <FilterMultiSelect\n            kind={filter.name}\n            hideKind\n            fullResultCount={3}\n            data={filterInfo.values}\n            value={filter.values}\n            onChange={(v) =>\n              onUpdate?.({\n                ...filter,\n                values: v,\n                operator: filter.operator || \"in\",\n              })\n            }\n          />\n        )}\n        {filterInfo &&\n          filterInfo.values.length === 0 &&\n          filterInfo.type === \"string\" && (\n            <TextInput\n              value={filter.values[0]}\n              onChange={(e) =>\n                onUpdate?.({\n                  ...filter,\n                  values: [e.target.value],\n                  operator: filter.operator || \"=\",\n                })\n              }\n            />\n          )}\n        {filterInfo &&\n          filterInfo.values.length === 0 &&\n          filterInfo.type !== \"string\" && (\n            <NumberInput\n              rightSection={filterInfo.unit}\n              value={Number(filter.values[0])}\n              allowNegative={false}\n              allowDecimal={filterInfo.type.startsWith(\"float\")}\n              hideControls\n              onChange={(v) =>\n                onUpdate?.({\n                  ...filter,\n                  values: [v + \"\"],\n                  operator: filter.operator || \"=\",\n                })\n              }\n            />\n          )}\n      </Box>\n\n      <Group ml=\"auto\" w={28}>\n        {showDelete && (\n          <ActionIcon onClick={() => onUpdate?.({ ...filter, deleted: true })}>\n            <IconTrash01 size={16} />\n          </ActionIcon>\n        )}\n      </Group>\n    </Group>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/advanced-filters/filters-modal.tsx",
    "content": "import {\n  AdvancedFilterItem,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { ActionIcon, Box, Modal } from \"@tidbcloud/uikit\"\nimport { useDisclosure } from \"@tidbcloud/uikit/hooks\"\nimport { IconFilterFunnel02, IconPlus } from \"@tidbcloud/uikit/icons\"\nimport { useState } from \"react\"\n\nimport { AdvancedFilterInfo, AdvancedFilterSettingItem } from \"./filter-setting\"\nimport { AdvancedFiltersSetting, newFilterSettingItem } from \"./filters-setting\"\nimport { I18nNamespace, updateI18nLocales } from \"./locales\"\n\nexport function AdvancedFiltersModal({\n  availableFilters,\n  advancedFilters,\n  onUpdateFilters,\n  reqFilterInfo,\n}: {\n  availableFilters: Array<string | { label: string; value: string }>\n  advancedFilters: AdvancedFilterItem[]\n  onUpdateFilters?: (items: AdvancedFilterItem[]) => void\n  reqFilterInfo?: (filterName: string) => Promise<AdvancedFilterInfo>\n}) {\n  const { tt } = useTn(\"advanced-filters\")\n\n  const hasFilters = advancedFilters.length > 0\n\n  const [opened, { open, close }] = useDisclosure(false)\n\n  const [settingItems, setSettingItems] = useState<AdvancedFilterSettingItem[]>(\n    [],\n  )\n\n  function handleOpen() {\n    // sync with advancedFilters when open modal\n    if (advancedFilters.length === 0) {\n      setSettingItems([newFilterSettingItem()])\n    } else {\n      const now = Date.now() // milliseconds\n      setSettingItems(\n        advancedFilters.map((f, i) => ({\n          ...f,\n          createdAt: now - i * 100,\n          deleted: false,\n        })),\n      )\n    }\n    open()\n  }\n\n  function handleSubmit() {\n    const items = settingItems.filter(\n      (i) => !i.deleted && !!i.name && !!i.operator && !!i.values[0],\n    )\n    onUpdateFilters?.(items)\n    close()\n  }\n\n  const [filtersInfo, setFiltersInfo] = useState<AdvancedFilterInfo[]>([])\n\n  function handleReqFilterInfo(filterName: string) {\n    reqFilterInfo?.(filterName).then((d) =>\n      setFiltersInfo((prev) => [...prev, d]),\n    )\n  }\n\n  return (\n    <>\n      <ActionIcon\n        variant={\"default\"}\n        w={48}\n        size={40}\n        color={hasFilters ? \"peacock\" : undefined}\n        onClick={handleOpen}\n      >\n        <IconFilterFunnel02 size={16} />\n        {hasFilters ? <Box pl={2}>{advancedFilters.length}</Box> : <IconPlus />}\n      </ActionIcon>\n\n      <Modal\n        size=\"auto\"\n        title={tt(\"Advanced Filters\")}\n        opened={opened}\n        onClose={close}\n      >\n        <AdvancedFiltersSetting\n          availableFilters={availableFilters || []}\n          filtersInfo={filtersInfo}\n          reqFilterInfo={handleReqFilterInfo}\n          filters={settingItems}\n          onUpdateFilters={setSettingItems}\n          onSubmit={handleSubmit}\n          onClose={close}\n        />\n      </Modal>\n    </>\n  )\n}\n\nAdvancedFiltersModal.i18nNamespace = I18nNamespace\nAdvancedFiltersModal.updateI18nLocales = updateI18nLocales\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/advanced-filters/filters-setting.tsx",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Button, Group, Stack } from \"@tidbcloud/uikit\"\n\nimport {\n  AdvancedFilterInfo,\n  AdvancedFilterSetting,\n  AdvancedFilterSettingItem,\n} from \"./filter-setting\"\n\nexport function newFilterSettingItem(): AdvancedFilterSettingItem {\n  return {\n    name: \"\",\n    operator: \"\",\n    values: [],\n    createdAt: Date.now(),\n    deleted: false,\n  }\n}\n\nexport function AdvancedFiltersSetting({\n  availableFilters,\n  filtersInfo,\n  reqFilterInfo,\n  filters,\n  onUpdateFilters,\n  onSubmit,\n  onClose,\n}: {\n  availableFilters: Array<string | { label: string; value: string }>\n  filtersInfo?: AdvancedFilterInfo[]\n  reqFilterInfo?: (filterName: string) => void\n  filters: AdvancedFilterSettingItem[]\n  onUpdateFilters?: (items: AdvancedFilterSettingItem[]) => void\n  onSubmit?: () => void\n  onClose?: () => void\n}) {\n  const { tt } = useTn(\"advanced-filters\")\n\n  const activeItems = filters.filter((i) => !i.deleted)\n\n  function handleAddItem() {\n    onUpdateFilters?.([...filters, newFilterSettingItem()])\n  }\n\n  // update `deleted` to true to act as deleted\n  function handleUpdateItem(item: AdvancedFilterSettingItem) {\n    onUpdateFilters?.(\n      filters.map((i) =>\n        i.createdAt === item.createdAt ? { ...i, ...item } : i,\n      ),\n    )\n  }\n\n  return (\n    <Stack w={720}>\n      {activeItems.map((item, i) => (\n        <AdvancedFilterSetting\n          key={item.createdAt}\n          availableFilters={availableFilters || []}\n          filter={item}\n          filtersInfo={filtersInfo}\n          onReqFilterInfo={reqFilterInfo}\n          onUpdate={handleUpdateItem}\n          // showDelete={activeItems.length > 1}\n          conditionLabel={i === 0 ? tt(\"WHEN\") : tt(\"AND\")}\n        />\n      ))}\n\n      <Group>\n        <Button variant=\"outline\" onClick={handleAddItem}>\n          {tt(\"Add Filter\")}\n        </Button>\n        <Group ml=\"auto\">\n          <Button variant=\"default\" onClick={onClose}>\n            {tt(\"Cancel\")}\n          </Button>\n          <Button onClick={onSubmit}>{tt(\"Save\")}</Button>\n        </Group>\n      </Group>\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/advanced-filters/index.ts",
    "content": "export * from \"./filter-setting\"\nexport * from \"./filters-setting\"\nexport * from \"./filters-modal\"\n\nimport \"./locales\"\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/advanced-filters/locales.ts",
    "content": "import { addLangsLocales } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n\nexport const I18nNamespace = \"advanced-filters\"\ntype I18nLocaleKeys =\n  | \"AND\"\n  | \"Add Filter\"\n  | \"Advanced Filters\"\n  | \"Cancel\"\n  | \"Filter Name\"\n  | \"Save\"\n  | \"WHEN\"\ntype I18nLocale = {\n  [K in I18nLocaleKeys]?: string\n}\nconst en: I18nLocale = {}\nconst zh: I18nLocale = {\n  AND: \"且\",\n  \"Add Filter\": \"添加筛选条件\",\n  \"Advanced Filters\": \"高级筛选\",\n  Cancel: \"取消\",\n  \"Filter Name\": \"筛选条件名称\",\n  Save: \"保存\",\n  WHEN: \"当\",\n}\n\nexport function updateI18nLocales(locales: { [ln: string]: I18nLocale }) {\n  for (const [ln, locale] of Object.entries(locales)) {\n    addLangsLocales({\n      [ln]: {\n        __namespace__: I18nNamespace,\n        ...locale,\n      },\n    })\n  }\n}\n\nupdateI18nLocales({ en, zh })\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/auto-refresh-button/index.tsx",
    "content": "import { formatNumByUnit } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Button, Menu, Text, Tooltip } from \"@tidbcloud/uikit\"\nimport {\n  IconChevronSelectorVertical,\n  IconRefreshCw01,\n} from \"@tidbcloud/uikit/icons\"\nimport { useRafInterval } from \"ahooks\"\nimport { forwardRef, useImperativeHandle, useMemo, useState } from \"react\"\n\nimport { RefreshProgress } from \"./progress\"\n\ninterface AutoRefreshButtonProps {\n  autoRefreshValue?: number\n  autoRefreshOptions?: number[]\n  onAutoRefreshChange?: (v: number) => void\n  onRefresh?: () => void\n  loading?: boolean\n  disabled?: boolean\n}\n\nexport interface AutoRefreshButtonRef {\n  refresh: () => void\n}\n\nconst AUTO_REFRESH_SECONDS_OPTIONS = [\n  30,\n  60,\n  5 * 60,\n  15 * 60,\n  30 * 60,\n  60 * 60,\n  2 * 60 * 60,\n]\n\nexport const DEFAULT_AUTO_REFRESH_SECONDS = 60\n\nconst AutoRefreshButton = forwardRef<\n  AutoRefreshButtonRef,\n  AutoRefreshButtonProps\n>(\n  (\n    {\n      autoRefreshValue = DEFAULT_AUTO_REFRESH_SECONDS,\n      autoRefreshOptions = AUTO_REFRESH_SECONDS_OPTIONS,\n      onAutoRefreshChange,\n      onRefresh,\n      loading,\n      disabled,\n    },\n    ref,\n  ) => {\n    const selectedOption = useMemo(\n      () => autoRefreshOptions.find((op) => op === autoRefreshValue),\n      [autoRefreshValue, autoRefreshOptions],\n    )\n    const [remaining, setRemaining] = useState<number>(autoRefreshValue)\n    const [menuOpened, setMenuOpened] = useState(false)\n\n    // useRafInterval stops running when browser render doesn't work, for example, browser tab is not active, browser window is minimized.\n    useRafInterval(() => {\n      if (disabled || autoRefreshValue === 0) {\n        return\n      }\n      setRemaining((r) => {\n        const newRemaining = r - 1\n        if (newRemaining === 0) {\n          handleRefresh()\n        }\n        return newRemaining\n      })\n    }, 1000)\n\n    const handleRefresh = async () => {\n      if (disabled) {\n        return\n      }\n      setRemaining(autoRefreshValue)\n      onRefresh?.()\n    }\n\n    useImperativeHandle(ref, () => ({\n      refresh: handleRefresh,\n    }))\n\n    const handleAutoRefreshChange = (v: number) => {\n      onAutoRefreshChange?.(v)\n      setRemaining(v)\n    }\n\n    return (\n      <Button.Group>\n        <Button\n          variant=\"default\"\n          px={12}\n          styles={(theme) => ({\n            root: {\n              backgroundColor: theme.colors.carbon[0],\n              borderColor: theme.colors.carbon[4],\n              \"&:hover\": {\n                backgroundColor: theme.colors.carbon[0],\n                borderColor: theme.colors.carbon[5],\n              },\n            },\n            label: { fontWeight: 400 },\n          })}\n          onClick={handleRefresh}\n          leftSection={\n            autoRefreshValue && !disabled ? (\n              <RefreshProgress\n                value={Math.floor(\n                  ((autoRefreshValue - remaining) / autoRefreshValue) * 100,\n                )}\n              />\n            ) : (\n              <IconRefreshCw01 size={16} />\n            )\n          }\n          loading={loading}\n          loaderProps={{ size: 16 }}\n          disabled={disabled}\n        >\n          Refresh\n        </Button>\n\n        <Menu\n          position=\"bottom-end\"\n          offset={4}\n          width={110}\n          opened={menuOpened}\n          onChange={setMenuOpened}\n        >\n          <Menu.Target>\n            <Tooltip disabled={!!autoRefreshValue} label=\"Auto Refresh: Off\">\n              <Button\n                disabled={disabled || loading}\n                variant=\"default\"\n                styles={(theme) => ({\n                  root: {\n                    backgroundColor: theme.colors.carbon[0],\n                    borderColor: menuOpened\n                      ? theme.colors.carbon[9]\n                      : theme.colors.carbon[4],\n                    \"&:hover\": {\n                      backgroundColor: theme.colors.carbon[0],\n                      borderColor: menuOpened\n                        ? theme.colors.carbon[9]\n                        : theme.colors.carbon[5],\n                    },\n                    \"&:active\": { transform: \"none\" },\n                  },\n                  label: { fontWeight: 400 },\n                })}\n                px={12}\n              >\n                {!!autoRefreshValue && (\n                  <Text mr={8}>\n                    {formatNumByUnit(autoRefreshValue, \"s\", 0)}\n                  </Text>\n                )}\n\n                <IconChevronSelectorVertical size={16} />\n              </Button>\n            </Tooltip>\n          </Menu.Target>\n\n          <Menu.Dropdown>\n            <Menu.Label>Auto Refresh</Menu.Label>\n            <Menu.Item\n              sx={(theme) => ({\n                background: !selectedOption\n                  ? theme.colors.carbon[3]\n                  : undefined,\n              })}\n              onClick={() => handleAutoRefreshChange(0)}\n            >\n              Off\n            </Menu.Item>\n\n            <Menu.Divider />\n\n            <>\n              {autoRefreshOptions.map((seconds) => (\n                <Menu.Item\n                  key={seconds}\n                  sx={(theme) => ({\n                    background:\n                      seconds === selectedOption\n                        ? theme.colors.carbon[3]\n                        : undefined,\n                  })}\n                  onClick={() => handleAutoRefreshChange(seconds)}\n                >\n                  {formatNumByUnit(seconds, \"s\", 0)}\n                </Menu.Item>\n              ))}\n            </>\n          </Menu.Dropdown>\n        </Menu>\n      </Button.Group>\n    )\n  },\n)\n\nexport { AutoRefreshButton }\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/auto-refresh-button/progress.tsx",
    "content": "import { RingProgress } from \"@tidbcloud/uikit\"\n\nexport function RefreshProgress({ value }: { value: number }) {\n  return (\n    <RingProgress\n      ml={-4}\n      mr={-4}\n      size={22}\n      thickness={4}\n      rootColor=\"carbon.5\"\n      sections={[{ value, color: \"carbon.8\" }]}\n    />\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/charts-multi-select.tsx",
    "content": "import {\n  addLangsLocales,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  Button,\n  Checkbox,\n  Combobox,\n  Group,\n  UnstyledButton,\n  useCombobox,\n  useMantineTheme,\n} from \"@tidbcloud/uikit\"\nimport { IconChevronDown } from \"@tidbcloud/uikit/icons\"\nimport { useMemo, useState } from \"react\"\n\nexport type ChartsSelectData = {\n  category: string\n  label: string\n  val: string\n}[]\n\nexport type ChartMultiSelectProps = {\n  data: ChartsSelectData\n  value: string[]\n\n  onSelect?: (val: string) => void\n  onUnSelect?: (val: string) => void\n  onReset?: () => void\n}\n\nexport function ChartMultiSelect({\n  data,\n  value,\n\n  onSelect,\n  onUnSelect,\n  onReset,\n}: ChartMultiSelectProps) {\n  const { tt } = useTn(\"charts-multi-select\")\n\n  const theme = useMantineTheme()\n  const [showHidden, setShowHidden] = useState(false)\n  const [search, setSearch] = useState(\"\")\n\n  const combobox = useCombobox({\n    onDropdownClose: () => {\n      combobox.resetSelectedOption()\n      setSearch(\"\")\n    },\n    onDropdownOpen: () => {\n      combobox.focusSearchInput()\n    },\n  })\n\n  const selectedData = data.filter(\n    (item) => value.includes(item.val) || value.includes(\"all\"),\n  )\n\n  const filteredData = useMemo(() => {\n    let d = data.filter(\n      (item) =>\n        !showHidden || (!value.includes(item.val) && !value.includes(\"all\")),\n    )\n    const term = search.toLowerCase().trim()\n    if (term) {\n      d = d.filter(\n        (item) =>\n          item.val.toLowerCase().includes(term) ||\n          item.label.toLowerCase().includes(term),\n      )\n    }\n    return d\n  }, [search, showHidden, data, value])\n\n  const categories = Array.from(\n    new Set(filteredData.map((item) => item.category)),\n  )\n\n  const options = categories.map((c, idx) => (\n    <Combobox.Group label={c} key={c + \"_\" + idx}>\n      {filteredData\n        .filter((item) => item.category === c)\n        .map((item, i) => (\n          <Combobox.Option\n            value={item.val}\n            key={item.val + \"_\" + i}\n            styles={{\n              option: {\n                \"&:hover\": {\n                  backgroundColor: theme.colors.carbon[3],\n                },\n              },\n            }}\n          >\n            <Checkbox\n              checked={value.includes(item.val) || value.includes(\"all\")}\n              onChange={(e) =>\n                handleCheckChange(e.currentTarget.checked, item.val)\n              }\n              label={item.label}\n            />\n          </Combobox.Option>\n        ))}\n    </Combobox.Group>\n  ))\n\n  function handleCheckChange(checked: boolean, v: string) {\n    if (checked) {\n      onSelect?.(v)\n    } else {\n      onUnSelect?.(v)\n    }\n  }\n\n  return (\n    <Combobox\n      store={combobox}\n      shadow=\"sm\"\n      width={260}\n      position=\"bottom-end\"\n      styles={{\n        search: {\n          border: \"none\",\n          borderBottom: `1px solid ${theme.colors.carbon[3]}`,\n          \"&:hover\": {\n            borderBottom: `1px solid ${theme.colors.carbon[3]}`,\n          },\n          \"&:focus\": {\n            borderBottom: `1px solid ${theme.colors.carbon[3]}`,\n          },\n        },\n      }}\n    >\n      <Combobox.Target>\n        <Button\n          variant=\"outline\"\n          rightSection={<IconChevronDown size={16} />}\n          onClick={() => combobox.openDropdown()}\n        >\n          {data.length === selectedData.length\n            ? tt(\"All charts selected\")\n            : tt(\"{{selected}}/{{all}} charts selected\", {\n                selected: selectedData.length,\n                all: data.length,\n              })}\n        </Button>\n      </Combobox.Target>\n\n      <Combobox.Dropdown>\n        <Combobox.Search\n          value={search}\n          onChange={(event) => setSearch(event.currentTarget.value)}\n          placeholder={tt(\"Search\")}\n          styles={{\n            input: {\n              border: \"none\",\n            },\n          }}\n        />\n        <Combobox.Options mah={300} style={{ overflowY: \"auto\" }}>\n          {options.length > 0 ? (\n            options\n          ) : (\n            <Combobox.Empty>{tt(\"Nothing found\")}</Combobox.Empty>\n          )}\n        </Combobox.Options>\n        <Combobox.Footer>\n          <Group>\n            <Group ml=\"auto\" justify=\"flex-end\" gap=\"xs\">\n              <UnstyledButton\n                fz=\"xs\"\n                c=\"peacock.7\"\n                onClick={() => setShowHidden(!showHidden)}\n              >\n                {showHidden ? tt(\"Show All\") : tt(\"Show Hidden\")}\n              </UnstyledButton>\n              <UnstyledButton fz=\"xs\" c=\"peacock.7\" onClick={onReset}>\n                {tt(\"Reset\")}\n              </UnstyledButton>\n            </Group>\n          </Group>\n        </Combobox.Footer>\n      </Combobox.Dropdown>\n    </Combobox>\n  )\n}\n\n//------------------------\n// i18n\n// auto updated by running `pnpm gen:locales`\n\nconst I18nNamespace = \"charts-multi-select\"\ntype I18nLocaleKeys =\n  | \"All charts selected\"\n  | \"Nothing found\"\n  | \"Reset\"\n  | \"Search\"\n  | \"Show All\"\n  | \"Show Hidden\"\n  | \"{{selected}}/{{all}} charts selected\"\ntype I18nLocale = {\n  [K in I18nLocaleKeys]?: string\n}\nconst en: I18nLocale = {}\nconst zh: I18nLocale = {\n  \"All charts selected\": \"所有图表已选\",\n  \"Nothing found\": \"未找到\",\n  Reset: \"重置\",\n  Search: \"搜索\",\n  \"Show All\": \"显示全部\",\n  \"Show Hidden\": \"显示未选\",\n  \"{{selected}}/{{all}} charts selected\": \"{{selected}}/{{all}} 图表已选\",\n}\n\nfunction updateI18nLocales(locales: { [ln: string]: I18nLocale }) {\n  for (const [ln, locale] of Object.entries(locales)) {\n    addLangsLocales({\n      [ln]: {\n        __namespace__: I18nNamespace,\n        ...locale,\n      },\n    })\n  }\n}\n\nupdateI18nLocales({ en, zh })\n\nChartMultiSelect.i18nNamespace = I18nNamespace\nChartMultiSelect.updateI18nLocales = updateI18nLocales\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/cols-multi-select.tsx",
    "content": "import {\n  addLangsLocales,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  ActionIcon,\n  Checkbox,\n  Combobox,\n  Group,\n  Typography,\n  UnstyledButton,\n  useCombobox,\n  useMantineTheme,\n} from \"@tidbcloud/uikit\"\nimport { IconSettings04 } from \"@tidbcloud/uikit/icons\"\nimport { useMemo, useState } from \"react\"\n\nexport type ColumnMultiSelectProps = {\n  data: { label: string; val: string }[]\n  value: string[]\n  onChange: (value: string[]) => void\n  onReset?: () => void\n}\n\nexport function ColumnMultiSelect({\n  data,\n  value,\n  onChange,\n  onReset,\n}: ColumnMultiSelectProps) {\n  const { tt } = useTn(\"cols-multi-select\")\n\n  const theme = useMantineTheme()\n  const [showSelected, setShowSelected] = useState(false)\n  const [search, setSearch] = useState(\"\")\n\n  const combobox = useCombobox({\n    onDropdownClose: () => {\n      combobox.resetSelectedOption()\n      setSearch(\"\")\n    },\n    onDropdownOpen: () => {\n      combobox.focusSearchInput()\n    },\n  })\n\n  const selectedData = data.filter(\n    (item) => value.includes(item.val) || value.includes(\"all\"),\n  )\n\n  const filteredData = useMemo(() => {\n    let d = data.filter(\n      (item) =>\n        !showSelected || value.includes(item.val) || value.includes(\"all\"),\n    )\n    const term = search.toLowerCase().trim()\n    if (term) {\n      d = d.filter(\n        (item) =>\n          item.val.toLowerCase().includes(term) ||\n          item.label.toLowerCase().includes(term),\n      )\n    }\n    return d\n  }, [search, showSelected, data, value])\n\n  const options = filteredData.map((item) => (\n    <Combobox.Option\n      value={item.val}\n      key={item.val}\n      styles={{\n        option: {\n          \"&:hover\": {\n            backgroundColor: theme.colors.carbon[3],\n          },\n        },\n      }}\n    >\n      <Group wrap=\"nowrap\" gap=\"xs\">\n        <Checkbox\n          checked={value.includes(item.val) || value.includes(\"all\")}\n          onChange={() => {}}\n        />\n        <Typography truncate>{item.label}</Typography>\n      </Group>\n    </Combobox.Option>\n  ))\n\n  function handleOptionSelect(val: string) {\n    const selected = selectedData.map((item) => item.val)\n    const newValue = selected.includes(val)\n      ? selected.filter((v) => v !== val)\n      : [...selected, val]\n    onChange(newValue)\n  }\n\n  return (\n    <Combobox\n      store={combobox}\n      onOptionSubmit={handleOptionSelect}\n      shadow=\"sm\"\n      width={260}\n      position=\"bottom-end\"\n      styles={{\n        search: {\n          border: \"none\",\n          borderBottom: `1px solid ${theme.colors.carbon[3]}`,\n          \"&:hover\": {\n            borderBottom: `1px solid ${theme.colors.carbon[3]}`,\n          },\n          \"&:focus\": {\n            borderBottom: `1px solid ${theme.colors.carbon[3]}`,\n          },\n        },\n      }}\n    >\n      <Combobox.Target>\n        <ActionIcon onClick={() => combobox.toggleDropdown()}>\n          <IconSettings04 size={16} />\n        </ActionIcon>\n      </Combobox.Target>\n\n      <Combobox.Dropdown>\n        <Combobox.Search\n          value={search}\n          onChange={(event) => setSearch(event.currentTarget.value)}\n          placeholder={tt(\"Search columns...\")}\n          styles={{\n            input: {\n              border: \"none\",\n            },\n          }}\n        />\n        <Combobox.Options mah={300} style={{ overflowY: \"auto\" }}>\n          {options.length > 0 ? (\n            options\n          ) : (\n            <Combobox.Empty>{tt(\"Nothing found\")}</Combobox.Empty>\n          )}\n        </Combobox.Options>\n        <Combobox.Footer>\n          <Group>\n            <Typography fz=\"xs\" c=\"dimmed\">\n              {tt(\"{{selected}}/{{all}}\", {\n                selected: selectedData.length,\n                all: data.length,\n              })}\n            </Typography>\n            <Group ml=\"auto\" justify=\"flex-end\" gap=\"xs\">\n              <UnstyledButton\n                fz=\"xs\"\n                c=\"peacock.7\"\n                onClick={() => setShowSelected(!showSelected)}\n              >\n                {showSelected ? tt(\"Show All\") : tt(\"Show Selected\")}\n              </UnstyledButton>\n              <UnstyledButton\n                fz=\"xs\"\n                c=\"peacock.7\"\n                onClick={() => onChange([\"all\"])}\n              >\n                {tt(\"Select All\")}\n              </UnstyledButton>\n              <UnstyledButton fz=\"xs\" c=\"peacock.7\" onClick={onReset}>\n                {tt(\"Reset\")}\n              </UnstyledButton>\n            </Group>\n          </Group>\n        </Combobox.Footer>\n      </Combobox.Dropdown>\n    </Combobox>\n  )\n}\n\n//------------------------\n// i18n\n// auto updated by running `pnpm gen:locales`\n\nconst I18nNamespace = \"cols-multi-select\"\ntype I18nLocaleKeys =\n  | \"Nothing found\"\n  | \"Reset\"\n  | \"Search columns...\"\n  | \"Select All\"\n  | \"Show All\"\n  | \"Show Selected\"\n  | \"{{selected}}/{{all}}\"\ntype I18nLocale = {\n  [K in I18nLocaleKeys]?: string\n}\nconst en: I18nLocale = {}\nconst zh: I18nLocale = {\n  \"Nothing found\": \"未找到\",\n  Reset: \"重置\",\n  \"Search columns...\": \"搜索列...\",\n  \"Select All\": \"全选\",\n  \"Show All\": \"显示全部\",\n  \"Show Selected\": \"显示已选\",\n  \"{{selected}}/{{all}}\": \"{{selected}}/{{all}}\",\n}\n\nfunction updateI18nLocales(locales: { [ln: string]: I18nLocale }) {\n  for (const [ln, locale] of Object.entries(locales)) {\n    addLangsLocales({\n      [ln]: {\n        __namespace__: I18nNamespace,\n        ...locale,\n      },\n    })\n  }\n}\n\nupdateI18nLocales({ en, zh })\n\nColumnMultiSelect.i18nNamespace = I18nNamespace\nColumnMultiSelect.updateI18nLocales = updateI18nLocales\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/custom-json-view.tsx",
    "content": "import { useComputedColorScheme } from \"@tidbcloud/uikit\"\nimport { createStyles } from \"@tidbcloud/uikit/emotion\"\nimport { useMemo } from \"react\"\nimport {\n  JsonView,\n  Props as JsonViewProps,\n  allExpanded,\n  darkStyles,\n  defaultStyles,\n} from \"react-json-view-lite\"\n\nimport \"react-json-view-lite/dist/index.css\"\n\nconst useStyles = createStyles(() => ({\n  container: {\n    paddingTop: 8,\n    paddingBottom: 8,\n    background: \"transparent\",\n    lineHeight: 1.2,\n    whiteSpace: \"pre\",\n    // whiteSpace: \"pre-wrap\",\n    // wordWrap: \"break-word\",\n    overflow: \"auto\",\n    fontFamily: \"monospace\",\n    fontSize: 12,\n  },\n  basicChildStyle: {\n    margin: 0,\n    padding: 0,\n  },\n}))\n\nexport function CustomJsonView({ data }: JsonViewProps) {\n  const colorScheme = useComputedColorScheme()\n  const { classes } = useStyles()\n  const style = useMemo(() => {\n    const _style = colorScheme === \"dark\" ? darkStyles : defaultStyles\n    return {\n      ..._style,\n      container: classes.container,\n      basicChildStyle: classes.basicChildStyle,\n    }\n  }, [colorScheme])\n\n  return <JsonView data={data} shouldExpandNode={allExpanded} style={style} />\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/filter-multi-select.tsx",
    "content": "import {\n  CloseButton,\n  Combobox,\n  Group,\n  InputBase,\n  Typography,\n  useCombobox,\n  useMantineTheme,\n} from \"@tidbcloud/uikit\"\nimport { IconCheck } from \"@tidbcloud/uikit/icons\"\nimport { useState } from \"react\"\n\nexport type FilterMultiSelectProps = {\n  kind: string\n  hideKind?: boolean\n  fullResultCount?: number // @todo: choose a better name\n\n  data: string[]\n  value: string[]\n  onChange: (value: string[]) => void\n\n  width?: number\n  disabled?: boolean\n}\n\nexport function FilterMultiSelect({\n  kind,\n  hideKind = false,\n  fullResultCount = 2,\n  data,\n  value,\n  onChange,\n  width,\n  disabled,\n}: FilterMultiSelectProps) {\n  const theme = useMantineTheme()\n\n  const [search, setSearch] = useState(\"\")\n  const combobox = useCombobox({\n    onDropdownClose: () => {\n      combobox.resetSelectedOption()\n      setSearch(\"\")\n    },\n\n    onDropdownOpen: () => {\n      combobox.focusSearchInput()\n    },\n  })\n\n  const options = data\n    .filter((item) => item.toLowerCase().includes(search.toLowerCase().trim()))\n    .map((item) => (\n      <Combobox.Option\n        value={item}\n        key={item}\n        styles={{\n          option: {\n            \"&:hover\": {\n              backgroundColor: theme.colors.carbon[3],\n            },\n          },\n        }}\n      >\n        <Group wrap=\"nowrap\">\n          <Typography truncate>{item}</Typography>\n          <Group ml=\"auto\">\n            {value.includes(item) && (\n              <IconCheck\n                size={16}\n                strokeWidth={2}\n                color={theme.colors.peacock[7]}\n              />\n            )}\n          </Group>\n        </Group>\n      </Combobox.Option>\n    ))\n\n  function handleOptionSelect(val: string) {\n    const newValue = value.includes(val)\n      ? value.filter((v) => v !== val)\n      : [...value, val]\n    onChange(newValue)\n  }\n\n  function selectResult() {\n    if (value.length === 0) {\n      return <Typography c=\"carbon\" truncate>{`Select ${kind}...`}</Typography>\n    }\n    if (value.length < fullResultCount) {\n      return (\n        <Group gap={4} wrap=\"nowrap\">\n          {!hideKind && (\n            <Typography c=\"carbon\" fw={500} truncate>\n              {kind}\n            </Typography>\n          )}\n          <Typography fw={500} truncate>\n            {value.join(\", \")}\n          </Typography>\n        </Group>\n      )\n    }\n    return (\n      <Group gap={4} wrap=\"nowrap\">\n        {!hideKind && (\n          <Typography c=\"carbon\" fw={500} truncate>\n            {kind}\n          </Typography>\n        )}\n        <Typography fw={500} truncate>\n          {value.length} selected\n        </Typography>\n      </Group>\n    )\n  }\n\n  return (\n    <Combobox\n      store={combobox}\n      onOptionSubmit={handleOptionSelect}\n      shadow=\"sm\"\n      styles={{\n        search: {\n          border: \"none\",\n          borderBottom: `1px solid ${theme.colors.carbon[3]}`,\n          \"&:hover\": {\n            borderBottom: `1px solid ${theme.colors.carbon[3]}`,\n          },\n          \"&:focus\": {\n            borderBottom: `1px solid ${theme.colors.carbon[3]}`,\n          },\n        },\n      }}\n    >\n      <Combobox.Target>\n        <InputBase\n          w={width}\n          disabled={disabled}\n          component=\"button\"\n          type=\"button\"\n          pointer\n          rightSection={\n            value.length > 0 ? (\n              <CloseButton\n                size=\"sm\"\n                onMouseDown={(event) => event.preventDefault()}\n                onClick={() => onChange([])}\n                aria-label=\"Clear value\"\n              />\n            ) : (\n              <Combobox.Chevron />\n            )\n          }\n          onClick={() => combobox.toggleDropdown()}\n          rightSectionPointerEvents={value === null ? \"none\" : \"all\"}\n        >\n          {selectResult()}\n        </InputBase>\n      </Combobox.Target>\n\n      <Combobox.Dropdown>\n        <Combobox.Search\n          value={search}\n          onChange={(event) => setSearch(event.currentTarget.value)}\n          placeholder={`Search ${kind.toLowerCase()}`}\n          styles={{\n            input: {\n              border: \"none\",\n            },\n          }}\n        />\n        <Combobox.Options mah={300} style={{ overflowY: \"auto\" }}>\n          {options.length > 0 ? (\n            options\n          ) : (\n            <Combobox.Empty>Nothing found</Combobox.Empty>\n          )}\n        </Combobox.Options>\n      </Combobox.Dropdown>\n    </Combobox>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/highlight-sql.tsx",
    "content": "import { formatSql } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Box, Prism } from \"@tidbcloud/uikit\"\nimport React, { useMemo } from \"react\"\n\nfunction InlineHighlightSQL({ sql }: { sql: string }) {\n  const formattedSql = useMemo(() => {\n    return formatSql(sql, true)\n  }, [sql])\n\n  return (\n    <Prism\n      noCopy\n      language=\"sql\"\n      styles={{\n        scrollArea: {\n          \"& > div > div\": {\n            display: \"block !important\",\n          },\n        },\n        code: {\n          backgroundColor: \"transparent !important\",\n          padding: 0,\n          fontSize: 13,\n        },\n        line: {\n          padding: 0,\n        },\n        lineContent: {\n          overflow: \"hidden\",\n          whiteSpace: \"nowrap\",\n          textOverflow: \"ellipsis\",\n        },\n      }}\n    >\n      {formattedSql}\n    </Prism>\n  )\n}\n\nfunction HighlightSQL({ sql }: { sql: string }) {\n  const formattedSql = useMemo(() => {\n    return formatSql(sql, false)\n  }, [sql])\n\n  return (\n    <Box mah=\"90vh\" maw=\"60vw\" sx={{ overflow: \"auto\" }}>\n      <Prism\n        language=\"sql\"\n        styles={{\n          copy: {\n            top: 0,\n            right: 0,\n          },\n          code: {\n            backgroundColor: \"transparent !important\",\n            padding: 0,\n            paddingRight: 24,\n            paddingTop: 3,\n            fontSize: 12,\n          },\n          line: {\n            padding: 0,\n          },\n        }}\n      >\n        {formattedSql}\n      </Prism>\n    </Box>\n  )\n}\n\nconst _InlineHighlightSQL = React.memo(InlineHighlightSQL)\nconst _HighlightSQL = React.memo(HighlightSQL)\n\nexport {\n  _InlineHighlightSQL as InlineHighlightSQL,\n  _HighlightSQL as HighlightSQL,\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/index.ts",
    "content": "// decided not to export\n// export * from \"@tidbcloud/uikit/biz\"\n\nexport * from \"./highlight-sql\"\nexport * from \"./sql-with-hover\"\n\nexport * from \"./plan-table\"\nexport * from \"./info-table\"\n\nexport * from \"./loading-skeleton\"\nexport * from \"./auto-refresh-button\"\nexport * from \"./status-indicator\"\n\n// @todo: combine to a single component\nexport * from \"./filter-multi-select\"\nexport * from \"./cols-multi-select\"\nexport * from \"./charts-multi-select\"\n\nexport * from \"./advanced-filters\"\n\nexport * from \"./custom-json-view\"\n\nexport * from \"./time-range-picker\"\n\nexport * from \"./action-drawer\"\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/info-table.tsx",
    "content": "import {\n  addLangsLocales,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Tooltip, Typography } from \"@tidbcloud/uikit\"\nimport { MRT_ColumnDef, ProTable } from \"@tidbcloud/uikit/biz\"\n\nexport type InfoModel = {\n  name: string\n  level?: number\n  value: string\n  desc?: string\n}\n\nexport function InfoTable({ data }: { data: InfoModel[] }) {\n  const { tt } = useTn(\"info-table\")\n  const columns: MRT_ColumnDef<InfoModel>[] = [\n    {\n      id: \"name\",\n      header: tt(\"Name\"),\n      accessorFn: (row) => (\n        <Typography\n          truncate\n          fw={row.level === 0 ? \"bold\" : undefined}\n          pl={row.level && row.level * 24}\n        >\n          {row.name}\n        </Typography>\n      ),\n    },\n    {\n      id: \"value\",\n      header: tt(\"Value\"),\n      accessorFn: (row) => <Typography truncate>{row.value}</Typography>,\n    },\n    {\n      id: \"desc\",\n      header: tt(\"Description\"),\n      accessorFn: (row) => (\n        <Tooltip\n          multiline\n          maw={600}\n          label={row.desc}\n          position=\"top-start\"\n          withArrow\n        >\n          <Typography maw={800} truncate>\n            {row.desc}\n          </Typography>\n        </Tooltip>\n      ),\n    },\n  ]\n  return <ProTable columns={columns} data={data} />\n}\n\n//------------------------\n// i18n\n// auto updated by running `pnpm gen:locales`\n\nconst I18nNamespace = \"info-table\"\n\ntype I18nLocaleKeys = \"Description\" | \"Name\" | \"Value\"\ntype I18nLocale = {\n  [K in I18nLocaleKeys]?: string\n}\nconst en: I18nLocale = {}\nconst zh: I18nLocale = {\n  Description: \"描述\",\n  Name: \"名称\",\n  Value: \"值\",\n}\n\nfunction updateI18nLocales(locales: { [ln: string]: I18nLocale }) {\n  for (const [ln, locale] of Object.entries(locales)) {\n    addLangsLocales({\n      [ln]: {\n        __namespace__: I18nNamespace,\n        ...locale,\n      },\n    })\n  }\n}\n\nupdateI18nLocales({ en, zh })\n\nInfoTable.i18nNamespace = I18nNamespace\nInfoTable.updateI18nLocales = updateI18nLocales\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/loading-skeleton.tsx",
    "content": "import { Skeleton, Stack } from \"@tidbcloud/uikit\"\n\nexport function LoadingSkeleton() {\n  return (\n    <Stack gap=\"xs\">\n      <Skeleton height={10} />\n      <Skeleton height={10} />\n      <Skeleton height={10} width=\"70%\" />\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/plan-table/index.tsx",
    "content": "import { Tooltip, Typography } from \"@tidbcloud/uikit\"\nimport { MRT_ColumnDef, ProTable } from \"@tidbcloud/uikit/biz\"\nimport { useMemo } from \"react\"\n\nimport { PlanItem, getPlanTextType, parsePlanTextToArray } from \"./parser\"\n\nconst columns: MRT_ColumnDef<PlanItem>[] = [\n  {\n    id: \"id\",\n    header: \"id\",\n    accessorFn: (row) => (\n      <Tooltip withinPortal label={row.id}>\n        <Typography truncate ff=\"monospace\" sx={{ whiteSpace: \"pre\" }}>\n          {row.id}\n        </Typography>\n      </Tooltip>\n    ),\n  },\n  {\n    id: \"estRows\",\n    header: \"estRows\",\n    size: 120,\n    enableResizing: false,\n    accessorFn: (row) => row.estRows,\n  },\n  {\n    id: \"estCost\",\n    header: \"estCost\",\n    size: 120,\n    enableResizing: false,\n    accessorFn: (row) => row.estCost,\n  },\n  {\n    id: \"actRows\",\n    header: \"actRows\",\n    size: 120,\n    enableResizing: false,\n    accessorFn: (row) => row.actRows,\n  },\n  {\n    id: \"task\",\n    header: \"task\",\n    size: 100,\n    enableResizing: false,\n    accessorFn: (row) => row.task,\n  },\n  {\n    id: \"accessObject\",\n    header: \"access object\",\n    size: 120,\n    accessorFn: (row) => (\n      <Tooltip\n        withinPortal\n        multiline\n        maw={400}\n        label={row.accessObject}\n        style={{ wordBreak: \"break-all\" }}\n      >\n        <Typography maw={200} truncate>\n          {row.accessObject}\n        </Typography>\n      </Tooltip>\n    ),\n  },\n  {\n    id: \"executionInfo\",\n    header: \"execution info\",\n    enableResizing: false,\n    accessorFn: (row) => (\n      <Tooltip\n        withinPortal\n        multiline\n        maw={400}\n        label={row.executionInfo}\n        style={{ wordBreak: \"break-all\" }}\n      >\n        <Typography maw={200} truncate>\n          {row.executionInfo}\n        </Typography>\n      </Tooltip>\n    ),\n  },\n  {\n    id: \"operatorInfo\",\n    header: \"operator info\",\n    enableResizing: false,\n    accessorFn: (row) => {\n      // truncate the string if it's too long\n      // operation info may be super super long\n      const truncateLength = 100\n      let truncatedStr = row.operatorInfo ?? \"\"\n      if (truncatedStr.length > truncateLength) {\n        truncatedStr = row.operatorInfo.slice(0, truncateLength) + \"...\"\n      }\n      const truncateTooltipLen = 2000\n      let truncatedTooltipStr = row.operatorInfo ?? \"\"\n      if (truncatedTooltipStr.length > truncateTooltipLen) {\n        truncatedTooltipStr =\n          row.operatorInfo.slice(0, truncateTooltipLen) +\n          \"...(too long to show, copy or download to analyze)\"\n      }\n      return (\n        <Tooltip\n          withinPortal\n          multiline\n          maw={400}\n          label={truncatedTooltipStr}\n          style={{ wordBreak: \"break-all\" }}\n        >\n          <Typography maw={200} truncate>\n            {truncatedStr}\n          </Typography>\n        </Tooltip>\n      )\n    },\n  },\n  {\n    id: \"memory\",\n    header: \"memory\",\n    size: 100,\n    enableResizing: false,\n    accessorFn: (row) => row.memory,\n  },\n  {\n    id: \"disk\",\n    header: \"disk\",\n    size: 100,\n    enableResizing: false,\n    accessorFn: (row) => row.disk,\n  },\n]\n\nexport function PlanTable({ plan }: { plan: string }) {\n  const planType = getPlanTextType(plan)\n  const planItems = useMemo(() => parsePlanTextToArray(plan), [plan])\n\n  return (\n    <ProTable\n      data={planItems}\n      columns={columns}\n      layoutMode=\"grid\"\n      enableColumnResizing\n      enableColumnPinning\n      initialState={{\n        columnVisibility: {\n          estCost: planType === \"v2\",\n          accessObject: planType === \"v2\",\n        },\n        columnPinning: { left: [\"id\"] },\n      }}\n    />\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/plan-table/parser.ts",
    "content": "const _PLAN_COLUMN_KEYS = [\n  \"id\",\n  \"estRows\",\n  \"estCost\",\n  \"actRows\",\n  \"task\",\n  \"accessObject\",\n  \"executionInfo\",\n  \"operatorInfo\",\n  \"memory\",\n  \"disk\",\n] as const\ntype PLAN_COLUMN_KEYS_UNION = (typeof _PLAN_COLUMN_KEYS)[number]\ntype PlanFiledPosition = Record<\n  PLAN_COLUMN_KEYS_UNION,\n  {\n    start: number\n    len: number\n  }\n>\nexport type PlanItem = Record<PLAN_COLUMN_KEYS_UNION, string>\n\n// in sample data sample-data/slow-query-detail.json, plan field is the plan v1 format\n// it uses '\\t' as separator for each column\n// it has a header line starts with '\\tid'\n// it has \"id, task, estRows, operator info, actRows, execution info, memory, disk\" columns\nexport function parsePlanV1TextToArray(planV1Text: string): PlanItem[] {\n  const result: PlanItem[] = []\n\n  if (!planV1Text.startsWith(\"\\tid\")) {\n    console.error(\"invalid plan text format\")\n    return result\n  }\n\n  const headerEndPos = planV1Text.indexOf(\"\\n\")\n  const headerLine = planV1Text.slice(0, headerEndPos)\n  const headers = headerLine.split(\"\\t\")\n  if (headers.length !== 9) {\n    console.error(\"invalid plan text format\")\n    return result\n  }\n\n  let curPos = headerEndPos + 2\n  while (true) {\n    // id\n    let nextTabPos = planV1Text.indexOf(\"\\t\", curPos)\n    if (nextTabPos === -1) {\n      break\n    }\n    const id = planV1Text.slice(curPos, nextTabPos).trimEnd()\n\n    // task\n    curPos = nextTabPos + 1\n    nextTabPos = planV1Text.indexOf(\"\\t\", curPos)\n    if (nextTabPos === -1) {\n      break\n    }\n    const task = planV1Text.slice(curPos, nextTabPos).trim()\n\n    // estRows\n    curPos = nextTabPos + 1\n    nextTabPos = planV1Text.indexOf(\"\\t\", curPos)\n    if (nextTabPos === -1) {\n      break\n    }\n    const estRows = planV1Text.slice(curPos, nextTabPos).trim()\n\n    // operator info\n    curPos = nextTabPos + 1\n    nextTabPos = planV1Text.indexOf(\"\\t\", curPos)\n    if (nextTabPos === -1) {\n      break\n    }\n    const operatorInfo = planV1Text.slice(curPos, nextTabPos).trim()\n\n    // actRows\n    curPos = nextTabPos + 1\n    nextTabPos = planV1Text.indexOf(\"\\t\", curPos)\n    if (nextTabPos === -1) {\n      break\n    }\n    const actRows = planV1Text.slice(curPos, nextTabPos).trim()\n\n    // execution info\n    curPos = nextTabPos + 1\n    nextTabPos = planV1Text.indexOf(\"\\t\", curPos)\n    if (nextTabPos === -1) {\n      break\n    }\n    const executionInfo = planV1Text.slice(curPos, nextTabPos).trim()\n\n    // memory\n    curPos = nextTabPos + 1\n    nextTabPos = planV1Text.indexOf(\"\\t\", curPos)\n    if (nextTabPos === -1) {\n      break\n    }\n    const memory = planV1Text.slice(curPos, nextTabPos).trim()\n\n    // disk\n    curPos = nextTabPos + 1\n    nextTabPos = planV1Text.indexOf(\"\\t\", curPos)\n    if (nextTabPos === -1) {\n      nextTabPos = planV1Text.length\n    }\n    const disk = planV1Text.slice(curPos, nextTabPos).trim()\n\n    result.push({\n      id,\n      estRows,\n      estCost: \"\",\n      actRows,\n      task,\n      accessObject: \"\",\n      executionInfo,\n      operatorInfo,\n      memory,\n      disk,\n    })\n\n    if (nextTabPos >= planV1Text.length) {\n      break\n    }\n    curPos = nextTabPos + 1\n  }\n\n  return result\n}\n\n// in sample data sample-data/slow-query-detail.json, binary_plan_text field is the plan v2 format\n// it uses '|' as separator for each column\n// it has a header line starts with '\\n| id'\n// it has \"id, estRows, estCost, actRows, task, access object, execution info, operator info, memory, disk\" columns\nexport function parsePlanV2TextToArray(planV2Text: string): PlanItem[] {\n  const result: PlanItem[] = []\n  let positions: PlanFiledPosition | null = null\n\n  // we can't simply split by '\\n', because operator info column may contain '\\n'\n  // for example, execution plan for \"select `tidb_decode_binary_plan` ( ? ) as `binary_plan_text`;\"\n  // const lines = binaryPlanText.split('\\n')\n\n  const headerEndPos = planV2Text.indexOf(\"\\n\", 1)\n  const headerLine = planV2Text.slice(1, headerEndPos)\n  if (!headerLine.startsWith(\"| id\")) {\n    console.error(\"invalid plan text format\")\n    return result\n  }\n  const headerLineLen = headerLine.length\n\n  const headers = headerLine.split(\"|\")\n  // 0: \"\"\n  // 1: \" id                      \"\n  // 2: \" estRows  \"\n  // 3: \" estCost    \"\n  // 4: \" actRows \"\n  // 5: \" task \"\n  // 6: \" access object      \"\n  // 7: \" execution info     \"\n  // 8: \" operator info                                   \"\n  // 9: \" memory   \"\n  // 10: \" disk     \"\n  // 11: \"\"\n  if (headers.length !== 12) {\n    console.error(\"invalid plan text format\")\n    return result\n  }\n  positions = {\n    id: {\n      start: 0,\n      len: headers[1].length,\n    },\n    estRows: {\n      start: 0,\n      len: headers[2].length,\n    },\n    estCost: {\n      start: 0,\n      len: headers[3].length,\n    },\n    actRows: {\n      start: 0,\n      len: headers[4].length,\n    },\n    task: {\n      start: 0,\n      len: headers[5].length,\n    },\n    accessObject: {\n      start: 0,\n      len: headers[6].length,\n    },\n    executionInfo: {\n      start: 0,\n      len: headers[7].length,\n    },\n    operatorInfo: {\n      start: 0,\n      len: headers[8].length,\n    },\n    memory: {\n      start: 0,\n      len: headers[9].length,\n    },\n    disk: {\n      start: 0,\n      len: headers[10].length,\n    },\n  }\n  positions.id.start = 1\n  positions.estRows.start = positions.id.start + positions.id.len + 1\n  positions.estCost.start = positions.estRows.start + positions.estRows.len + 1\n  positions.actRows.start = positions.estCost.start + positions.estCost.len + 1\n  positions.task.start = positions.actRows.start + positions.actRows.len + 1\n  positions.accessObject.start = positions.task.start + positions.task.len + 1\n  positions.executionInfo.start =\n    positions.accessObject.start + positions.accessObject.len + 1\n  positions.operatorInfo.start =\n    positions.executionInfo.start + positions.executionInfo.len + 1\n  positions.memory.start =\n    positions.operatorInfo.start + positions.operatorInfo.len + 1\n  positions.disk.start = positions.memory.start + positions.memory.len + 1\n\n  let lineIdx = 1\n  while (true) {\n    const lineStart = 1 + (headerLineLen + 1) * lineIdx\n    const lineEnd = 1 + (headerLineLen + 1) * (lineIdx + 1)\n    if (lineEnd > planV2Text.length) {\n      break\n    }\n    lineIdx++\n\n    const line = planV2Text.slice(lineStart, lineEnd)\n    const item: PlanItem = {\n      id: line\n        .slice(positions.id.start + 1, positions.id.start + positions.id.len)\n        .trimEnd(), // start+1 for removing the leading white space\n      estRows: line\n        .slice(\n          positions.estRows.start,\n          positions.estRows.start + positions.estRows.len,\n        )\n        .trim(),\n      estCost: line\n        .slice(\n          positions.estCost.start,\n          positions.estCost.start + positions.estCost.len,\n        )\n        .trim(),\n      actRows: line\n        .slice(\n          positions.actRows.start,\n          positions.actRows.start + positions.actRows.len,\n        )\n        .trim(),\n      task: line\n        .slice(positions.task.start, positions.task.start + positions.task.len)\n        .trim(),\n      accessObject: line\n        .slice(\n          positions.accessObject.start,\n          positions.accessObject.start + positions.accessObject.len,\n        )\n        .trim(),\n      executionInfo: line\n        .slice(\n          positions.executionInfo.start,\n          positions.executionInfo.start + positions.executionInfo.len,\n        )\n        .trim(),\n      operatorInfo: line\n        .slice(\n          positions.operatorInfo.start,\n          positions.operatorInfo.start + positions.operatorInfo.len,\n        )\n        .trim(),\n      memory: line\n        .slice(\n          positions.memory.start,\n          positions.memory.start + positions.memory.len,\n        )\n        .trim(),\n      disk: line\n        .slice(positions.disk.start, positions.disk.start + positions.disk.len)\n        .trim(),\n    }\n    result.push(item)\n  }\n\n  return result\n}\n\nexport function parsePlanTextToArray(plan: string): PlanItem[] {\n  const planType = getPlanTextType(plan)\n  if (planType === \"v1\") {\n    return parsePlanV1TextToArray(plan)\n  } else if (planType === \"v2\") {\n    return parsePlanV2TextToArray(plan)\n  }\n  return []\n}\n\nexport function getPlanTextType(plan: string): \"v1\" | \"v2\" | \"unknown\" {\n  if (plan.startsWith(\"\\tid\")) {\n    return \"v1\"\n  } else if (plan.startsWith(\"\\n| id\")) {\n    return \"v2\"\n  }\n  console.error(\"invalid plan text format\")\n  return \"unknown\"\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/plan-table/sample-data/plan-v1-1.txt",
    "content": "\tid                       \ttask     \testRows     \toperator info                                                                                                                              \tactRows\texecution info                                                                                                                                                                                                                                                                                                                                  \tmemory   \tdisk\n\tProjection_5             \troot     \t1           \tColumn#35, Column#36, unix_timestamp(Column#36)->Column#37                                                                                 \t1      \ttime:1.7ms, loops:2, Concurrency:OFF                                                                                                                                                                                                                                                                                                            \t760 Bytes\tN/A\n\t└─HashAgg_15             \troot     \t1           \tfuncs:count(Column#39)->Column#35, funcs:max(Column#40)->Column#36                                                                         \t1      \ttime:1.69ms, loops:2, partial_worker:{wall_time:1.668391ms, concurrency:5, task_num:1, tot_wait:8.174473ms, tot_exec:7.311µs, tot_time:8.187847ms, max:1.647755ms, p95:1.647755ms}, final_worker:{wall_time:1.716398ms, concurrency:5, task_num:1, tot_wait:8.316075ms, tot_exec:12.235µs, tot_time:8.33123ms, max:1.683119ms, p95:1.683119ms}\t13.6 KB  \tN/A\n\t  └─IndexReader_16       \troot     \t1           \tindex:HashAgg_7                                                                                                                            \t15     \ttime:1.6ms, loops:2, cop_task: {num: 21, max: 1.32ms, min: 430.9µs, avg: 738.4µs, p95: 1.28ms, max_proc_keys: 1535, p95_proc_keys: 384, rpc_num: 21, rpc_time: 15.1ms, copr_cache_hit_ratio: 0.00, build_task_duration: 130.5µs, max_distsql_concurrency: 15}                                                                                \t848 Bytes\tN/A\n\t    └─HashAgg_7          \tcop[tikv]\t1           \tfuncs:count(1)->Column#39, funcs:max(gharchive_dev.github_events.created_at)->Column#40                                                    \t15     \ttikv_task:{proc max:0s, min:0s, avg: 0s, p80:0s, p95:0s, iters:22, tasks:21}, scan_detail: {total_process_keys: 2462, total_process_keys_size: 103404, total_keys: 2483, get_snapshot_time: 1.58ms, rocksdb: {block: {}}}                                                                                                                       \tN/A      \tN/A\n\t      └─IndexRangeScan_14\tcop[tikv]\t210302349.40\ttable:github_events, index:index_github_events_on_created_at(created_at), range:[2024-11-10 12:08:00,2024-11-10 12:08:42], keep order:false\t2462   \ttikv_task:{proc max:0s, min:0s, avg: 0s, p80:0s, p95:0s, iters:22, tasks:21}                                                                                                                                                                                                                                                                    \tN/A      \tN/A\n\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/plan-table/sample-data/plan-v1-2.txt",
    "content": "\tid                                \ttask        \testRows     \toperator info                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               \tactRows\texecution info                                                                                         \tmemory \tdisk\n\tTableReader_46                    \troot        \t1           \tMppVersion: 1, data:ExchangeSender_45                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       \t1      \ttime:15.8s, loops:2, cop_task: {num: 2, max: 0s, min: 0s, avg: 0s, p95: 0s, copr_cache_hit_ratio: 0.00}\t1.82 KB\tN/A\n\t└─ExchangeSender_45               \tcop[tiflash]\t1           \tExchangeType: PassThrough                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   \t1      \ttiflash_task:{time:15.8s, loops:1, threads:1}                                                          \tN/A    \tN/A\n\t  └─Projection_41                 \tcop[tiflash]\t1           \tColumn#35, Column#36, Column#37, Column#38, Column#39, Column#40, Column#41                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 \t1      \ttiflash_task:{time:15.8s, loops:1, threads:1}                                                          \tN/A    \tN/A\n\t    └─HashAgg_42                  \tcop[tiflash]\t1           \tfuncs:count(distinct gharchive_dev.github_events.actor_id)->Column#35, funcs:count(distinct gharchive_dev.github_events.repo_id)->Column#36, funcs:sum(Column#49)->Column#37, funcs:sum(Column#50)->Column#38, funcs:count(distinct Column#51)->Column#39, funcs:count(distinct Column#52)->Column#40, funcs:count(distinct Column#53)->Column#41                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           \t1      \ttiflash_task:{time:15.8s, loops:1, threads:1}                                                          \tN/A    \tN/A\n\t      └─ExchangeReceiver_44       \tcop[tiflash]\t1           \t                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            \t185819 \ttiflash_task:{time:15.8s, loops:22, threads:8}                                                         \tN/A    \tN/A\n\t        └─ExchangeSender_43       \tcop[tiflash]\t1           \tExchangeType: PassThrough, Compression: FAST                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                \t185819 \ttiflash_task:{time:15.8s, loops:256, threads:9}                                                        \tN/A    \tN/A\n\t          └─HashAgg_40            \tcop[tiflash]\t1           \tgroup by:Column#56, Column#57, Column#58, Column#59, Column#60, funcs:sum(Column#54)->Column#49, funcs:sum(Column#55)->Column#50                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            \t185819 \ttiflash_task:{time:15.8s, loops:256, threads:9}                                                        \tN/A    \tN/A\n\t            └─Projection_47       \tcop[tiflash]\t228054454.94\tcast(if(and(eq(gharchive_dev.github_events.action, \"closed\"), eq(gharchive_dev.github_events.pr_merged, 1)), gharchive_dev.github_events.additions, 0), decimal(20,0) BINARY)->Column#54, cast(if(and(eq(gharchive_dev.github_events.action, \"closed\"), eq(gharchive_dev.github_events.pr_merged, 1)), gharchive_dev.github_events.deletions, 0), decimal(20,0) BINARY)->Column#55, gharchive_dev.github_events.actor_id, gharchive_dev.github_events.repo_id, if(eq(gharchive_dev.github_events.action, \"opened\"), gharchive_dev.github_events.pr_or_issue_id, NULL)->Column#58, if(and(eq(gharchive_dev.github_events.action, \"closed\"), eq(gharchive_dev.github_events.pr_merged, 0)), gharchive_dev.github_events.pr_or_issue_id, NULL)->Column#59, if(and(eq(gharchive_dev.github_events.action, \"closed\"), eq(gharchive_dev.github_events.pr_merged, 1)), gharchive_dev.github_events.pr_or_issue_id, NULL)->Column#60\t188173 \ttiflash_task:{time:15.8s, loops:40, threads:16}                                                        \tN/A    \tN/A\n\t              └─Selection_22      \tcop[tiflash]\t228054454.94\tgt(gharchive_dev.github_events.created_at, 2024-11-09 10:04:30)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             \t188173 \ttiflash_task:{time:15.8s, loops:40, threads:16}                                                        \tN/A    \tN/A\n\t                └─TableFullScan_21\tcop[tiflash]\t684163364.82\ttable:ge, pushed down filter:eq(gharchive_dev.github_events.type, \"PullRequestEvent\"), keep order:false, PartitionTableScan:true                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            \t190474 \ttiflash_task:{time:15.8s, loops:40, threads:16}                                                        \tN/A    \tN/A\n\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/plan-table/sample-data/plan-v2-1.txt",
    "content": "| id                                 | estRows      | estCost         | actRows | task         | access object                | execution info                                                                                          | operator info                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | memory  | disk  |\n| TableReader_46                     | 1.00         | 48754088733.03  | 1       | root         | partition:pull_request_event | time:39.7s, loops:2, cop_task: {num: 2, max: 0s, min: 0s, avg: 0s, p95: 0s, copr_cache_hit_ratio: 0.00} | MppVersion: 1, data:ExchangeSender_45                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | 1.83 KB | N/A   |\n| └─ExchangeSender_45                | 1.00         | 731311330731.52 | 1       | mpp[tiflash] |                              | tiflash_task:{time:39.7s, loops:1, threads:1}                                                           | ExchangeType: PassThrough                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | N/A     | N/A   |\n|   └─Projection_41                  | 1.00         | 731311330731.49 | 1       | mpp[tiflash] |                              | tiflash_task:{time:39.7s, loops:1, threads:1}                                                           | Column#35, Column#36, Column#37, Column#38, Column#39, Column#40, Column#41                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | N/A     | N/A   |\n|     └─HashAgg_42                   | 1.00         | 731311330731.46 | 1       | mpp[tiflash] |                              | tiflash_task:{time:39.7s, loops:1, threads:1}                                                           | funcs:count(distinct gharchive_dev.github_events.actor_id)->Column#35, funcs:count(distinct gharchive_dev.github_events.repo_id)->Column#36, funcs:sum(Column#49)->Column#37, funcs:sum(Column#50)->Column#38, funcs:count(distinct Column#51)->Column#39, funcs:count(distinct Column#52)->Column#40, funcs:count(distinct Column#53)->Column#41                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | N/A     | N/A   |\n|       └─ExchangeReceiver_44        | 1.00         | 731311330653.94 | 189803  | mpp[tiflash] |                              | tiflash_task:{time:39.6s, loops:21, threads:8}                                                          |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | N/A     | N/A   |\n|         └─ExchangeSender_43        | 1.00         | 731311330533.94 | 189803  | mpp[tiflash] |                              | tiflash_task:{time:39.6s, loops:256, threads:9}                                                         | ExchangeType: PassThrough, Compression: FAST                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | N/A     | N/A   |\n|           └─HashAgg_40             | 1.00         | 731311330533.94 | 189803  | mpp[tiflash] |                              | tiflash_task:{time:39.6s, loops:256, threads:9}                                                         | group by:Column#56, Column#57, Column#58, Column#59, Column#60, funcs:sum(Column#54)->Column#49, funcs:sum(Column#55)->Column#50                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | N/A     | N/A   |\n|             └─Projection_47        | 228028729.03 | 741529297805.73 | 191940  | mpp[tiflash] |                              | tiflash_task:{time:39.6s, loops:26, threads:16}                                                         | cast(if(and(eq(gharchive_dev.github_events.action, \"closed\"), eq(gharchive_dev.github_events.pr_merged, 1)), gharchive_dev.github_events.additions, 0), decimal(20,0) BINARY)->Column#54, cast(if(and(eq(gharchive_dev.github_events.action, \"closed\"), eq(gharchive_dev.github_events.pr_merged, 1)), gharchive_dev.github_events.deletions, 0), decimal(20,0) BINARY)->Column#55, gharchive_dev.github_events.actor_id, gharchive_dev.github_events.repo_id, if(eq(gharchive_dev.github_events.action, \"opened\"), gharchive_dev.github_events.pr_or_issue_id, NULL)->Column#58, if(and(eq(gharchive_dev.github_events.action, \"closed\"), eq(gharchive_dev.github_events.pr_merged, 0)), gharchive_dev.github_events.pr_or_issue_id, NULL)->Column#59, if(and(eq(gharchive_dev.github_events.action, \"closed\"), eq(gharchive_dev.github_events.pr_merged, 1)), gharchive_dev.github_events.pr_or_issue_id, NULL)->Column#60 | N/A     | N/A   |\n|               └─Selection_22       | 228028729.03 | 730105149692.77 | 191940  | mpp[tiflash] |                              | tiflash_task:{time:39.5s, loops:26, threads:16}                                                         | gt(gharchive_dev.github_events.created_at, 2024-11-09 03:54:30)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | N/A     | N/A   |\n|                 └─TableFullScan_21 | 684086187.09 | 689800007857.57 | 199190  | mpp[tiflash] | table:ge                     | tiflash_task:{time:39.4s, loops:26, threads:16}                                                         | pushed down filter:eq(gharchive_dev.github_events.type, \"PullRequestEvent\"), keep order:false, PartitionTableScan:true                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | N/A     | N/A   |\n\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/plan-table/sample-data/slow-query-detail.json",
    "content": "{\n  \"digest\": \"877ddf60c6084ae30353cc484f375e5159c231f0d9363213d1d1cc2ffbd272ce\",\n  \"query\": \"SELECT  *,  FIELD(LOWER(A.TYPE), 'tiflash', 'tikv', 'pd', 'tidb') AS _ORDER FROM (  SELECT   TYPE, INSTANCE, DEVICE_TYPE, DEVICE_NAME, JSON_OBJECTAGG(NAME, VALUE) AS JSON_VALUE  FROM   INFORMATION_SCHEMA.CLUSTER_LOAD  WHERE   DEVICE_TYPE IN (?,?)  GROUP BY TYPE, INSTANCE, DEVICE_TYPE, DEVICE_NAME ) AS A ORDER BY  _ORDER DESC, INSTANCE, DEVICE_TYPE, DEVICE_NAME [arguments: (\\\"memory\\\", \\\"cpu\\\")];\",\n  \"instance\": \"127.0.0.1:10080\",\n  \"db\": \"\",\n  \"connection_id\": \"5414958427354957035\",\n  \"success\": 1,\n  \"timestamp\": 1690272708.823209,\n  \"query_time\": 1.51515176,\n  \"parse_time\": 0,\n  \"compile_time\": 0.00103503,\n  \"rewrite_time\": 0.000332248,\n  \"preproc_subqueries_time\": 0,\n  \"optimize_time\": 0.000354698,\n  \"wait_ts\": 0,\n  \"cop_time\": 0,\n  \"lock_keys_time\": 0,\n  \"write_sql_response_total\": 0.000068678,\n  \"exec_retry_time\": 0,\n  \"memory_max\": 220680,\n  \"disk_max\": 0,\n  \"txn_start_ts\": \"0\",\n  \"prev_stmt\": \"\",\n  \"plan\": \"\\tid                     \\ttask\\testRows\\toperator info                                                                                                                                                                                                                                      \\tactRows\\texecution info                                                                                                                                                                                                                                                                                                                                                      \\tmemory  \\tdisk\\n\\tSort_7                 \\troot\\t8000   \\tColumn#8:desc, Column#2, Column#3, Column#4                                                                                                                                                                                                        \\t12     \\ttime:1.51s, loops:2                                                                                                                                                                                                                                                                                                                                                 \\t18.4 KB \\t0 Bytes\\n\\t└─Projection_9         \\troot\\t8000   \\tColumn#1, Column#2, Column#3, Column#4, Column#7, field(lower(Column#1), tiflash, tikv, pd, tidb)-\\u003eColumn#8                                                                                                                                        \\t12     \\ttime:1.51s, loops:6, Concurrency:5                                                                                                                                                                                                                                                                                                                                  \\t33.8 KB \\tN/A\\n\\t  └─HashAgg_10         \\troot\\t8000   \\tgroup by:Column#1, Column#2, Column#3, Column#4, funcs:json_objectagg(Column#5, Column#6)-\\u003eColumn#7, funcs:firstrow(Column#1)-\\u003eColumn#1, funcs:firstrow(Column#2)-\\u003eColumn#2, funcs:firstrow(Column#3)-\\u003eColumn#3, funcs:firstrow(Column#4)-\\u003eColumn#4\\t12     \\ttime:1.51s, loops:6, partial_worker:{wall_time:1.513240352s, concurrency:5, task_num:1, tot_wait:7.565194284s, tot_exec:149.691µs, tot_time:7.56536502s, max:1.513153823s, p95:1.513153823s}, final_worker:{wall_time:1.5133523s, concurrency:5, task_num:5, tot_wait:7.566142195s, tot_exec:223.043µs, tot_time:7.566371614s, max:1.513316486s, p95:1.513316486s}\\t47.4 KB \\tN/A\\n\\t    └─Selection_11     \\troot\\t8000   \\tin(Column#3, \\\"memory\\\", \\\"cpu\\\")                                                                                                                                                                                                                      \\t69     \\ttime:1.51s, loops:2                                                                                                                                                                                                                                                                                                                                                 \\t135.2 KB\\tN/A\\n\\t      └─MemTableScan_12\\troot\\t10000  \\ttable:CLUSTER_LOAD                                                                                                                                                                                                                                 \\t1193   \\ttime:1.51s, loops:3                                                                                                                                                                                                                                                                                                                                                 \\tN/A     \\tN/A\",\n  \"binary_plan\": \"{\\\"discardedDueToTooLong\\\":false,\\\"main\\\":{\\\"accessObjects\\\":[],\\\"actRows\\\":12,\\\"children\\\":[{\\\"accessObjects\\\":[],\\\"actRows\\\":12,\\\"children\\\":[{\\\"accessObjects\\\":[],\\\"actRows\\\":12,\\\"children\\\":[{\\\"accessObjects\\\":[],\\\"actRows\\\":69,\\\"children\\\":[{\\\"accessObjects\\\":[{\\\"scanObject\\\":{\\\"database\\\":\\\"INFORMATION_SCHEMA\\\",\\\"table\\\":\\\"CLUSTER_LOAD\\\"}}],\\\"actRows\\\":1193,\\\"copExecInfo\\\":{},\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":10000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"N/A\\\",\\\"name\\\":\\\"MemTableScan_12\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"3\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":499000,\\\"diagnosis\\\":[\\\"high_est_error\\\"],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":8000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"138400\\\",\\\"name\\\":\\\"Selection_11\\\",\\\"operatorInfo\\\":\\\"in(Column#3, \\\\\\\"memory\\\\\\\", \\\\\\\"cpu\\\\\\\")\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"2\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":1772970.6,\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":8000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"48580\\\",\\\"name\\\":\\\"HashAgg_10\\\",\\\"operatorInfo\\\":\\\"group by:Column#1, Column#2, Column#3, Column#4, funcs:json_objectagg(Column#5, Column#6)-\\\\u003eColumn#7, funcs:firstrow(Column#1)-\\\\u003eColumn#1, funcs:firstrow(Column#2)-\\\\u003eColumn#2, funcs:firstrow(Column#3)-\\\\u003eColumn#3, funcs:firstrow(Column#4)-\\\\u003eColumn#4\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"6\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[{\\\"final_worker\\\":{\\\"concurrency\\\":\\\"5\\\",\\\"max\\\":\\\"1.513316486s\\\",\\\"p95\\\":\\\"1.513316486s\\\",\\\"task_num\\\":\\\"5\\\",\\\"tot_exec\\\":\\\"223.043µs\\\",\\\"tot_time\\\":\\\"7.566371614s\\\",\\\"tot_wait\\\":\\\"7.566142195s\\\",\\\"wall_time\\\":\\\"1.5133523s\\\"},\\\"partial_worker\\\":{\\\"concurrency\\\":\\\"5\\\",\\\"max\\\":\\\"1.513153823s\\\",\\\"p95\\\":\\\"1.513153823s\\\",\\\"task_num\\\":\\\"1\\\",\\\"tot_exec\\\":\\\"149.691µs\\\",\\\"tot_time\\\":\\\"7.56536502s\\\",\\\"tot_wait\\\":\\\"7.565194284s\\\",\\\"wall_time\\\":\\\"1.513240352s\\\"}}],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":1856802.6,\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":8000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"34596\\\",\\\"name\\\":\\\"Projection_9\\\",\\\"operatorInfo\\\":\\\"Column#1, Column#2, Column#3, Column#4, Column#7, field(lower(Column#1), tiflash, tikv, pd, tidb)-\\\\u003eColumn#8\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"6\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[{\\\"Concurrency\\\":\\\"5\\\"}],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"}],\\\"copExecInfo\\\":{},\\\"cost\\\":7403943.686437106,\\\"diagnosis\\\":[],\\\"diskBytes\\\":\\\"N/A\\\",\\\"duration\\\":\\\"1.51s\\\",\\\"estRows\\\":8000,\\\"labels\\\":[],\\\"memoryBytes\\\":\\\"18792\\\",\\\"name\\\":\\\"Sort_7\\\",\\\"operatorInfo\\\":\\\"Column#8:desc, Column#2, Column#3, Column#4\\\",\\\"rootBasicExecInfo\\\":{\\\"loops\\\":\\\"2\\\",\\\"time\\\":\\\"1.51s\\\"},\\\"rootGroupExecInfo\\\":[],\\\"storeType\\\":\\\"tidb\\\",\\\"taskType\\\":\\\"root\\\"},\\\"withRuntimeStats\\\":true}\",\n  \"binary_plan_text\": \"\\n| id                      | estRows  | estCost    | actRows | task | access object      | execution info                                                                                                                                                                                                                                                                                                                                                     | operator info                                                                                                                                                                                                                                       | memory   | disk     |\\n| Sort_7                  | 8000.00  | 7403943.69 | 12      | root |                    | time:1.51s, loops:2                                                                                                                                                                                                                                                                                                                                                | Column#8:desc, Column#2, Column#3, Column#4                                                                                                                                                                                                         | 18.4 KB  | 0 Bytes  |\\n| └─Projection_9          | 8000.00  | 1856802.60 | 12      | root |                    | time:1.51s, loops:6, Concurrency:5                                                                                                                                                                                                                                                                                                                                 | Column#1, Column#2, Column#3, Column#4, Column#7, field(lower(Column#1), tiflash, tikv, pd, tidb)-\\u003eColumn#8                                                                                                                                         | 33.8 KB  | N/A      |\\n|   └─HashAgg_10          | 8000.00  | 1772970.60 | 12      | root |                    | time:1.51s, loops:6, partial_worker:{wall_time:1.513240352s, concurrency:5, task_num:1, tot_wait:7.565194284s, tot_exec:149.691µs, tot_time:7.56536502s, max:1.513153823s, p95:1.513153823s}, final_worker:{wall_time:1.5133523s, concurrency:5, task_num:5, tot_wait:7.566142195s, tot_exec:223.043µs, tot_time:7.566371614s, max:1.513316486s, p95:1.513316486s} | group by:Column#1, Column#2, Column#3, Column#4, funcs:json_objectagg(Column#5, Column#6)-\\u003eColumn#7, funcs:firstrow(Column#1)-\\u003eColumn#1, funcs:firstrow(Column#2)-\\u003eColumn#2, funcs:firstrow(Column#3)-\\u003eColumn#3, funcs:firstrow(Column#4)-\\u003eColumn#4 | 47.4 KB  | N/A      |\\n|     └─Selection_11      | 8000.00  | 499000.00  | 69      | root |                    | time:1.51s, loops:2                                                                                                                                                                                                                                                                                                                                                | in(Column#3, \\\"memory\\\", \\\"cpu\\\")                                                                                                                                                                                                                       | 135.2 KB | N/A      |\\n|       └─MemTableScan_12 | 10000.00 | 0.00       | 1193    | root | table:CLUSTER_LOAD | time:1.51s, loops:3                                                                                                                                                                                                                                                                                                                                                |                                                                                                                                                                                                                                                     | N/A      | N/A      |\\n\",\n  \"warnings\": [\n    {\n      \"Level\": \"Warning\",\n      \"Message\": \"skip prepared plan-cache: PhysicalMemTable plan is un-cacheable\"\n    }\n  ],\n  \"is_internal\": 0,\n  \"index_names\": \"\",\n  \"stats\": \"\",\n  \"backoff_types\": \"\",\n  \"prepared\": 1,\n  \"plan_from_cache\": 0,\n  \"plan_from_binding\": 0,\n  \"user\": \"root\",\n  \"host\": \"127.0.0.1\",\n  \"process_time\": 0,\n  \"wait_time\": 0,\n  \"backoff_time\": 0,\n  \"get_commit_ts_time\": 0,\n  \"local_latch_wait_time\": 0,\n  \"resolve_lock_time\": 0,\n  \"prewrite_time\": 0,\n  \"wait_prewrite_binlog_time\": 0,\n  \"commit_time\": 0,\n  \"commit_backoff_time\": 0,\n  \"cop_proc_avg\": 0,\n  \"cop_proc_p90\": 0,\n  \"cop_proc_max\": 0,\n  \"cop_wait_avg\": 0,\n  \"cop_wait_p90\": 0,\n  \"cop_wait_max\": 0,\n  \"write_keys\": 0,\n  \"write_size\": 0,\n  \"prewrite_region\": 0,\n  \"txn_retry\": 0,\n  \"request_count\": 0,\n  \"process_keys\": 0,\n  \"total_keys\": 0,\n  \"cop_proc_addr\": \"\",\n  \"cop_wait_addr\": \"\",\n  \"rocksdb_delete_skipped_count\": 0,\n  \"rocksdb_key_skipped_count\": 0,\n  \"rocksdb_block_cache_hit_count\": 0,\n  \"rocksdb_block_read_count\": 0,\n  \"rocksdb_block_read_byte\": 0\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/sql-with-hover.tsx",
    "content": "import { Box, HoverCard, HoverCardProps, Typography } from \"@tidbcloud/uikit\"\nimport { useMemo } from \"react\"\n\nimport { HighlightSQL, InlineHighlightSQL } from \"./highlight-sql\"\n\nexport function SQLWithHover({\n  sql,\n  position,\n}: {\n  sql: string\n  position?: HoverCardProps[\"position\"]\n}) {\n  const truncSQLShort = useMemo(() => {\n    return sql.length <= 200 ? sql : sql.slice(0, 200) + \"...\"\n  }, [sql])\n  const truncSQLLong = useMemo(() => {\n    return sql.length <= 1000 ? sql : sql.slice(0, 1000) + \"...\"\n  }, [sql])\n\n  return (\n    <HoverCard\n      withinPortal\n      withArrow\n      position={position || \"right\"}\n      shadow=\"md\"\n    >\n      <HoverCard.Target>\n        <Box>\n          <InlineHighlightSQL sql={truncSQLShort} />\n        </Box>\n      </HoverCard.Target>\n      <HoverCard.Dropdown onClick={(e) => e.stopPropagation()} p=\"xs\">\n        <HighlightSQL sql={truncSQLLong} />\n      </HoverCard.Dropdown>\n    </HoverCard>\n  )\n}\n\n// the evicted record's digest is empty string\nexport function EvictedSQL() {\n  return (\n    <HoverCard withinPortal withArrow position=\"right\" shadow=\"md\">\n      <HoverCard.Target>\n        <Typography c=\"dimmed\" fs=\"italic\">\n          Others\n        </Typography>\n      </HoverCard.Target>\n      <HoverCard.Dropdown onClick={(e) => e.stopPropagation()}>\n        <Typography>All of other dropped SQL statements</Typography>\n      </HoverCard.Dropdown>\n    </HoverCard>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/status-indicator.tsx",
    "content": "import { Box, Group } from \"@tidbcloud/uikit\"\nimport { LabelTooltip } from \"@tidbcloud/uikit/biz\"\nimport React from \"react\"\n\nexport const StatusIndicator: React.FC<\n  React.PropsWithChildren<{\n    label: string\n    dotColor: string\n    dotFill?: boolean\n    tip?: string\n  }>\n> = ({ label, dotColor, dotFill = false, tip }) => {\n  return (\n    <Group gap={0} p={4} align=\"center\">\n      <Box\n        w={8}\n        h={8}\n        mr={8}\n        sx={{ borderRadius: \"50%\", border: `1px solid ${dotColor}` }}\n        bg={dotFill ? dotColor : \"transparent\"}\n      />\n      {/* must use `span`, can't use `Typography` here, else the selected item can't be highlighted */}\n      <span>{label}</span>\n      {tip && <LabelTooltip label={tip} />}\n    </Group>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/time-range-picker/custom.tsx",
    "content": "import {\n  AbsoluteTimeRange,\n  TimeRangeValue,\n  formatDuration,\n  formatTime,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  Alert,\n  Box,\n  Button,\n  DatePicker,\n  Flex,\n  Group,\n  Input,\n  Text,\n  TimeInput,\n  Typography,\n} from \"@tidbcloud/uikit\"\nimport { IconAlertCircle, IconChevronLeft } from \"@tidbcloud/uikit/icons\"\nimport { dayjs } from \"@tidbcloud/uikit/utils\"\nimport { useMemo, useState } from \"react\"\n\ninterface CustomTimeRangePickerProps {\n  value: TimeRangeValue\n  minDateTime?: Date\n  maxDateTime?: Date\n  maxDuration?: number // unit: seconds\n  onChange?: (v: AbsoluteTimeRange) => void\n  onCancel?: () => void\n  onReturnClick?: () => void\n}\n\nconst CustomTimeRangePicker = ({\n  value,\n  maxDateTime,\n  minDateTime,\n  maxDuration,\n  onChange,\n  onCancel,\n  onReturnClick,\n}: CustomTimeRangePickerProps) => {\n  const { tt } = useTn(\"time-range-picker\")\n\n  const [start, setStart] = useState(() => new Date(value[0] * 1000))\n  const [end, setEnd] = useState(() => new Date(value[1] * 1000))\n  const startTime = dayjs(start).format(\"HH:mm:ss\")\n  const endTime = dayjs(end).format(\"HH:mm:ss\")\n\n  const startAfterEnd = useMemo(() => {\n    return start.valueOf() > end.valueOf()\n  }, [start, end])\n  const beyondMin = useMemo(() => {\n    return minDateTime && start.valueOf() < minDateTime.valueOf()\n  }, [minDateTime, start])\n  const beyondMax = useMemo(() => {\n    return maxDateTime && end.valueOf() > maxDateTime.valueOf()\n  }, [maxDateTime, end])\n  const beyondDuration = useMemo(() => {\n    if (maxDuration !== undefined) {\n      return end.valueOf() - start.valueOf() > maxDuration * 1000\n    }\n    return false\n  }, [maxDuration, start, end])\n\n  const [displayRangeDate, setDisplayRangeDate] = useState<\n    [Date | null, Date | null]\n  >([start, end])\n\n  const updateRangeDate = (dates: [Date | null, Date | null]) => {\n    setDisplayRangeDate(dates)\n\n    if (dates[0]) {\n      const newStart = new Date(dates[0])\n      newStart.setHours(start.getHours())\n      newStart.setMinutes(start.getMinutes())\n      newStart.setSeconds(start.getSeconds())\n      setStart(newStart)\n\n      // to support to select the same day for start and end\n      let newEnd = new Date(dates[0])\n      if (dates[1]) {\n        newEnd = new Date(dates[1])\n      }\n      newEnd.setHours(end.getHours())\n      newEnd.setMinutes(end.getMinutes())\n      newEnd.setSeconds(end.getSeconds())\n      setEnd(newEnd)\n    }\n  }\n\n  const updateTime = (v: string, k: \"start\" | \"end\") => {\n    let setter = setStart\n    if (k === \"end\") {\n      setter = setEnd\n    }\n    setter((old) => {\n      const d = dayjs(`2025-01-01 ${v}`, \"YYYY-MM-DD HH:mm:ss\").toDate()\n      const newD = new Date(old!)\n      newD.setHours(d.getHours())\n      newD.setMinutes(d.getMinutes())\n      newD.setSeconds(d.getSeconds())\n      return newD\n    })\n  }\n  const apply = () =>\n    onChange?.({\n      type: \"absolute\",\n      value: [dayjs(start).unix(), dayjs(end).unix()],\n    })\n\n  return (\n    <Box p={16} w={280} m={-4}>\n      <Group onClick={onReturnClick} sx={{ cursor: \"pointer\" }}>\n        <IconChevronLeft size={16} />\n        <Typography variant=\"body-lg\">{tt(\"Back\")}</Typography>\n      </Group>\n\n      <Group gap={0} pt={8} justify=\"space-between\">\n        <Typography variant=\"label-sm\">{tt(\"Start\")}</Typography>\n        <Group gap={8}>\n          <Input\n            w={116}\n            value={dayjs(start).format(\"MMM D, YYYY\")}\n            onChange={() => {}}\n            error={beyondMin || startAfterEnd || beyondDuration}\n          />\n          <TimeInput\n            w={90}\n            withSeconds\n            value={startTime}\n            onChange={(d) => updateTime(d.currentTarget.value, \"start\")}\n            error={beyondMin || startAfterEnd || beyondDuration}\n          />\n        </Group>\n      </Group>\n\n      <Group gap={0} pt={8} justify=\"space-between\">\n        <Typography variant=\"label-sm\">{tt(\"End\")}</Typography>\n        <Group gap={8}>\n          <Input\n            w={116}\n            value={dayjs(end).format(\"MMM D, YYYY\")}\n            onChange={() => {}}\n            error={beyondMax || startAfterEnd || beyondDuration}\n          />\n          <TimeInput\n            w={90}\n            withSeconds\n            value={endTime}\n            onChange={(d) => updateTime(d.currentTarget.value, \"end\")}\n            error={beyondMax || startAfterEnd || beyondDuration}\n          />\n        </Group>\n      </Group>\n\n      <Flex justify=\"center\" pt={8}>\n        <DatePicker\n          type=\"range\"\n          value={displayRangeDate}\n          onChange={updateRangeDate}\n          maxDate={maxDateTime}\n          minDate={minDateTime}\n        />\n      </Flex>\n\n      {(startAfterEnd || beyondMin || beyondMax || beyondDuration) && (\n        <Alert icon={<IconAlertCircle size={16} />} color=\"red\" pt={8}>\n          {startAfterEnd && (\n            <Text c=\"red\">\n              {tt(\"Please select an end time after the start time.\")}\n            </Text>\n          )}\n          {beyondMin && (\n            <Text c=\"red\">\n              {tt(\"Please select a start time after {{time}}.\", {\n                time: formatTime(minDateTime!, \"MMM D, YYYY HH:mm:ss\"),\n              })}\n            </Text>\n          )}\n          {beyondMax && (\n            <Text c=\"red\">\n              {tt(\"Please select an end time before {{time}}.\", {\n                time: formatTime(maxDateTime!, \"MMM D, YYYY HH:mm:ss\"),\n              })}\n            </Text>\n          )}\n          {beyondDuration && (\n            <Text c=\"red\">\n              {tt(\n                \"The selection exceeds the {{duration}} limit, please select a shorter time range.\",\n                { duration: formatDuration(maxDuration!) },\n              )}\n            </Text>\n          )}\n        </Alert>\n      )}\n\n      <Flex\n        pt={8}\n        gap=\"xs\"\n        justify=\"flex-end\"\n        align=\"flex-start\"\n        direction=\"row\"\n        wrap=\"wrap\"\n      >\n        <Button size=\"xs\" variant=\"default\" onClick={onCancel}>\n          {tt(\"Cancel\")}\n        </Button>\n        <Button\n          size=\"xs\"\n          onClick={apply}\n          disabled={startAfterEnd || beyondMin || beyondMax || beyondDuration}\n        >\n          {tt(\"Apply\")}\n        </Button>\n      </Flex>\n    </Box>\n  )\n}\n\nexport default CustomTimeRangePicker\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/src/time-range-picker/index.tsx",
    "content": "import {\n  RelativeTimeRange,\n  TimeRange,\n  addLangsLocales,\n  formatDuration,\n  formatTime,\n  toTimeRangeValue,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  Box,\n  Button,\n  ButtonProps,\n  Group,\n  Menu,\n  Text,\n  Tooltip,\n  Typography,\n} from \"@tidbcloud/uikit\"\nimport { IconChevronRight } from \"@tidbcloud/uikit/icons\"\nimport { useMemo, useState } from \"react\"\n\nimport CustomTimeRangePicker from \"./custom\"\n\nconst DEFAULT_QUICK_RANGES = [\n  5 * 60,\n  15 * 60,\n  30 * 60,\n  60 * 60,\n  3 * 60 * 60,\n  12 * 60 * 60,\n  24 * 60 * 60,\n  2 * 24 * 60 * 60,\n  3 * 24 * 60 * 60,\n]\n\nexport interface TimeRangePickerProps extends ButtonProps {\n  value: TimeRange\n  onChange?: (value: TimeRange) => void\n\n  loading?: boolean\n\n  minDateTime?: () => Date\n  maxDateTime?: () => Date\n  maxDuration?: number // unit: seconds\n\n  // quick range selection items, Last x mins, Last x hours...\n  // unit: seconds.\n  quickRanges?: number[]\n  quickRangesType?: RelativeTimeRange[\"type\"]\n  disableAbsoluteRanges?: boolean\n}\n\nexport const TimeRangePicker = ({\n  value,\n  minDateTime,\n  maxDateTime,\n  maxDuration,\n  disableAbsoluteRanges = false,\n  onChange,\n  quickRanges = DEFAULT_QUICK_RANGES,\n  quickRangesType = \"relative\",\n  loading,\n  sx,\n}: React.PropsWithChildren<TimeRangePickerProps>) => {\n  const { tt } = useTn(\"time-range-picker\")\n  const [opened, setOpened] = useState(false)\n  const [customMode, setCustomMode] = useState(false)\n  const isRelativeRange = value.type !== \"absolute\"\n  const relativeTimePrefix =\n    value.type === \"now-to-future\" ? tt(\"Next\") : tt(\"Past\")\n\n  const timeRangeValue = toTimeRangeValue(value)\n  const duration = timeRangeValue[1] - timeRangeValue[0]\n  const selectedRelativeItem = useMemo(() => {\n    if (value.type === \"absolute\") {\n      return\n    }\n    return quickRanges.find(\n      (it) => it === value.value && value.type === quickRangesType,\n    )\n  }, [quickRanges, value, quickRangesType])\n\n  const formattedAbsDateTime = useMemo(() => {\n    return `${formatTime(timeRangeValue[0] * 1000, \"MMM D, YYYY HH:mm\")} - ${formatTime(\n      timeRangeValue[1] * 1000,\n      \"MMM D, YYYY HH:mm\",\n    )}`\n  }, [timeRangeValue])\n\n  return (\n    <Menu\n      shadow=\"md\"\n      width={customMode ? \"auto\" : disableAbsoluteRanges ? 200 : 280}\n      position=\"bottom-end\"\n      opened={opened}\n      onOpen={() => {\n        setOpened(true)\n        setCustomMode(false)\n      }}\n      onClose={() => setOpened(false)}\n    >\n      <Menu.Target>\n        <Tooltip\n          label={formattedAbsDateTime}\n          disabled={isRelativeRange}\n          withArrow\n        >\n          <Button\n            variant=\"default\"\n            bg=\"carbon.0\"\n            styles={(theme) => ({\n              root: {\n                paddingLeft: \"12px\",\n                paddingRight: \"12px\",\n                borderColor: opened\n                  ? theme.colors.carbon[9]\n                  : theme.colors.carbon[4],\n                \"&:hover\": {\n                  backgroundColor: theme.colors.carbon[0],\n                  borderColor: opened\n                    ? theme.colors.carbon[9]\n                    : theme.colors.carbon[5],\n                },\n                \"&:active\": { transform: \"none\" },\n              },\n              inner: {\n                width: \"100%\",\n              },\n              label: {\n                display: \"flex\",\n                justifyContent: \"space-between\",\n                width: \"100%\",\n                fontWeight: 400,\n              },\n            })}\n            w={disableAbsoluteRanges ? 200 : 280}\n            sx={sx}\n            loading={loading}\n          >\n            <Group w=\"100%\" gap={0}>\n              <Box sx={{ flex: \"none\" }}>\n                <DurationBadge>{formatDuration(duration, true)}</DurationBadge>\n              </Box>\n              <Text\n                px={8}\n                sx={{\n                  flex: \"1 1\",\n                  overflow: \"hidden\",\n                  whiteSpace: \"nowrap\",\n                  textOverflow: \"ellipsis\",\n                  textAlign: \"left\",\n                }}\n              >\n                {isRelativeRange\n                  ? `${relativeTimePrefix} ${formatDuration(duration)}`\n                  : formattedAbsDateTime}\n              </Text>\n            </Group>\n          </Button>\n        </Tooltip>\n      </Menu.Target>\n\n      <Menu.Dropdown>\n        {customMode ? (\n          <CustomTimeRangePicker\n            value={timeRangeValue}\n            minDateTime={minDateTime?.()}\n            maxDateTime={maxDateTime?.()}\n            maxDuration={maxDuration}\n            onChange={(v) => {\n              onChange?.(v)\n              setOpened(false)\n            }}\n            onCancel={() => setOpened(false)}\n            onReturnClick={() => setCustomMode(false)}\n          />\n        ) : (\n          <>\n            {!disableAbsoluteRanges && (\n              <>\n                <Menu.Item\n                  rightSection={<IconChevronRight size={16} />}\n                  closeMenuOnClick={false}\n                  onClick={() => setCustomMode(true)}\n                >\n                  <Typography variant=\"body-lg\">{tt(\"Custom\")}</Typography>\n                </Menu.Item>\n\n                <Menu.Divider />\n              </>\n            )}\n\n            <>\n              {quickRanges.map((seconds) => (\n                <Menu.Item\n                  key={seconds}\n                  sx={(theme) => ({\n                    background:\n                      seconds === selectedRelativeItem\n                        ? theme.colors.carbon[3]\n                        : \"\",\n                  })}\n                  onClick={() =>\n                    onChange?.({ type: quickRangesType, value: seconds })\n                  }\n                >\n                  <Text>\n                    {relativeTimePrefix} {formatDuration(seconds)}\n                  </Text>\n                </Menu.Item>\n              ))}\n            </>\n          </>\n        )}\n      </Menu.Dropdown>\n    </Menu>\n  )\n}\n\nconst DurationBadge = ({ children }: { children: React.ReactNode }) => {\n  return (\n    <Box\n      display=\"inline-block\"\n      w={35}\n      py={3}\n      bg=\"carbon.3\"\n      c=\"carbon.8\"\n      fz={10}\n      lh=\"14px\"\n      ta=\"center\"\n      sx={{ borderRadius: 8 }}\n    >\n      {children}\n    </Box>\n  )\n}\n\n//------------------------\n// i18n\n// auto updated by running `pnpm gen:locales`\n\nconst I18nNamespace = \"time-range-picker\"\ntype I18nLocaleKeys =\n  | \"Apply\"\n  | \"Back\"\n  | \"Cancel\"\n  | \"Custom\"\n  | \"End\"\n  | \"Next\"\n  | \"Past\"\n  | \"Please select a start time after {{time}}.\"\n  | \"Please select an end time after the start time.\"\n  | \"Please select an end time before {{time}}.\"\n  | \"Start\"\n  | \"The selection exceeds the {{duration}} limit, please select a shorter time range.\"\ntype I18nLocale = {\n  [K in I18nLocaleKeys]?: string\n}\nconst en: I18nLocale = {}\nconst zh: I18nLocale = {\n  Apply: \"应用\",\n  Back: \"返回\",\n  Cancel: \"取消\",\n  Custom: \"自定义\",\n  End: \"结束\",\n  Next: \"未来\",\n  Past: \"过去\",\n  \"Please select a start time after {{time}}.\":\n    \"请选择 {{time}} 之后的开始时间。\",\n  \"Please select an end time after the start time.\":\n    \"请确保结束时间在开始时间之后。\",\n  \"Please select an end time before {{time}}.\":\n    \"请选择 {{time}} 之前的结束时间。\",\n  Start: \"开始\",\n  \"The selection exceeds the {{duration}} limit, please select a shorter time range.\":\n    \"选择超出了 {{duration}} 的限制，请选择更短的时间范围。\",\n}\n\nfunction updateI18nLocales(locales: { [ln: string]: I18nLocale }) {\n  for (const [ln, locale] of Object.entries(locales)) {\n    addLangsLocales({\n      [ln]: {\n        __namespace__: I18nNamespace,\n        ...locale,\n      },\n    })\n  }\n}\n\nupdateI18nLocales({ en, zh })\n\nTimeRangePicker.i18nNamespace = I18nNamespace\nTimeRangePicker.updateI18nLocales = updateI18nLocales\n"
  },
  {
    "path": "ui-v2/packages/libs/3-biz-ui/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.app.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\"\n  },\n  \"include\": [\"./src\"]\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/CHANGELOG.md",
    "content": "# @pingcap-incubator/tidb-dashboard-lib-apps\n\n## 0.20.2\n\n### Patch Changes\n\n- fix diagnosis url state\n\n## 0.20.1\n\n### Patch Changes\n\n- update monitoring ui\n\n## 0.20.0\n\n### Minor Changes\n\n- bump version\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.16.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.14.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.15.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.14.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.17.0\n\n## 0.19.7\n\n### Patch Changes\n\n- fix get prom addr when metric data is empty\n\n## 0.19.2\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.14.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.16.1\n\n## 0.19.1\n\n### Patch Changes\n\n- temporary disable sql-statement setting entry\n\n## 0.19.0\n\n### Minor Changes\n\n- refactor: re-exports lib-uitls/lib-charts/lib-primitive-ui/lib-biz-ui from lib-apps\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.15.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.13.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.13.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.13.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.16.0\n\n## 0.18.2\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.14.1\n\n## 0.18.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.12.1\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.15.1\n\n## 0.18.0\n\n### Minor Changes\n\n- upgrade uikit, refine table empty status, refine chart empty legend name case\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.14.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.12.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.12.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.12.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.15.0\n\n## 0.17.6\n\n### Patch Changes\n\n- show prometheus address for monitoring chart\n\n## 0.17.5\n\n### Patch Changes\n\n- add troubleshooting links and prom addr for charts\n\n## 0.17.4\n\n### Patch Changes\n\n- adjust monitoring panel names\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.14.3\n\n## 0.17.3\n\n### Patch Changes\n\n- refine chart same as grafana\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.11.2\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.14.2\n\n## 0.17.2\n\n### Patch Changes\n\n- refine chart step\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.11.1\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.14.1\n\n## 0.17.1\n\n### Patch Changes\n\n- refine related statement button\n\n## 0.17.0\n\n### Minor Changes\n\n- refine\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.14.0\n\n## 0.16.2\n\n### Patch Changes\n\n- address feedback\n\n## 0.16.1\n\n### Patch Changes\n\n- add statement setting\n\n## 0.16.0\n\n### Minor Changes\n\n- add sql-history feature\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.13.2\n\n## 0.15.1\n\n### Patch Changes\n\n- fix i18n issue\n- Updated dependencies\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.13.1\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.13.1\n\n## 0.15.0\n\n### Minor Changes\n\n- update sql-limit ux\n\n## 0.14.0\n\n### Minor Changes\n\n- add drill down drawer for metrics\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.13.0\n\n## 0.13.0\n\n### Minor Changes\n\n- update metrics charts\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.12.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.13.0\n\n## 0.12.0\n\n### Minor Changes\n\n- fix related slow queries link for statement detail page\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.12.0\n\n## 0.11.4\n\n### Patch Changes\n\n- 1. add refresh button for monitoring single chart\n  2. add tip for open detail page in new tab for slow-query and statement\n  3. highlight selected slow-query and statement row in list table for slow-query and statement\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.11.3\n\n## 0.11.3\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.11.2\n\n## 0.11.2\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.11.1\n\n## 0.11.1\n\n### Patch Changes\n\n- refine monitoring\n\n## 0.11.0\n\n### Minor Changes\n\n- fix time-range-picker, refine cols-multi-select\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.11.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.11.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.11.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.11.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.11.0\n\n## 0.10.0\n\n### Minor Changes\n\n- i18n for slow query and statement app\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.10.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.10.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.10.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.10.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.10.0\n\n## 0.9.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.9.1\n\n## 0.9.0\n\n### Minor Changes\n\n- upgrade uikit\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.9.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.9.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.9.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.9.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.9.0\n\n## 0.8.5\n\n### Patch Changes\n\n- show raw json info for slow query and statement detail\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.8.3\n\n## 0.8.4\n\n### Patch Changes\n\n- support open statement detail page from slow query detail page\n\n## 0.8.3\n\n### Patch Changes\n\n- refine plans list\n\n## 0.8.2\n\n### Patch Changes\n\n- add columns select, time range alert\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.8.2\n\n## 0.8.1\n\n### Patch Changes\n\n- renaming fields\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.8.1\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.8.1\n\n## 0.8.0\n\n### Minor Changes\n\n- support advanced filters for diagnosis\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.8.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.8.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.8.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.8.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.8.0\n\n## 0.7.1\n\n### Patch Changes\n\n- add showDetailBack config for diagnosis apps\n\n## 0.7.0\n\n### Minor Changes\n\n- refine pagination and sort url state\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.7.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.7.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.7.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.7.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.7.0\n\n## 0.6.1\n\n### Patch Changes\n\n- refine\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.6.1\n\n## 0.6.0\n\n### Minor Changes\n\n- refine\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.6.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.6.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.6.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.6.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.6.0\n\n## 0.5.4\n\n### Patch Changes\n\n- add sql limit for slow query\n\n## 0.5.3\n\n### Patch Changes\n\n- add sql limit feature\n\n## 0.5.2\n\n### Patch Changes\n\n- add sql plan bind feature\n\n## 0.5.1\n\n### Patch Changes\n\n- refine slow-query and statement detail page back button\n\n## 0.5.0\n\n### Minor Changes\n\n- refine slow-query and statement apps\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.5.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.5.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.5.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.5.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.5.0\n\n## 0.4.2\n\n### Patch Changes\n\n- make metric config kind as union type\n\n## 0.4.1\n\n### Patch Changes\n\n- add drill down modal\n\n## 0.4.0\n\n### Minor Changes\n\n- update uikit\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.4.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.4.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.4.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.4.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.4.0\n\n## 0.3.3\n\n### Patch Changes\n\n- fix build\n\n## 0.3.2\n\n### Patch Changes\n\n- update wording\n\n## 0.3.1\n\n### Patch Changes\n\n- update i18n\n\n## 0.3.0\n\n### Minor Changes\n\n- add azores cluster metrics page\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.3.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.3.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.3.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.3.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.3.0\n\n## 0.2.1\n\n### Patch Changes\n\n- refine metrics\n\n## 0.2.0\n\n### Minor Changes\n\n- add metrics azores host page\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.2.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.2.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.2.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.2.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.2.0\n\n## 0.1.0\n\n### Minor Changes\n\n- update i18n, format utils\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.1.0\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.1.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.1.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.1.0\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.1.0\n\n## 0.0.13\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.10\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.0.10\n\n## 0.0.12\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.9\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.0.9\n\n## 0.0.11\n\n### Patch Changes\n\n- debug i18n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.8\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.0.8\n\n## 0.0.10\n\n### Patch Changes\n\n- add i18n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.7\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.0.7\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.0.7\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.0.7\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.0.9\n\n## 0.0.9\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.0.8\n\n## 0.0.8\n\n### Patch Changes\n\n- support dark mode for lib-charts\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.6\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.0.6\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.0.6\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.0.6\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.0.7\n\n## 0.0.7\n\n### Patch Changes\n\n- add loading for panel, remove segement control items border\n\n## 0.0.6\n\n### Patch Changes\n\n- refine\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.5\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.0.5\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.0.5\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.0.5\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.0.6\n\n## 0.0.5\n\n### Patch Changes\n\n- refactor metric\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.4\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.0.4\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.0.4\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.0.4\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.0.5\n\n## 0.0.4\n\n### Patch Changes\n\n- upgrade uikit\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.3\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.0.3\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.0.3\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.0.3\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.0.4\n\n## 0.0.3\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.0.3\n\n## 0.0.2\n\n### Patch Changes\n\n- first release\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.2\n  - @pingcap-incubator/tidb-dashboard-lib-icons@0.0.2\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.0.2\n  - @pingcap-incubator/tidb-dashboard-lib-biz-ui@0.0.2\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.0.2\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/package.json",
    "content": "{\n  \"name\": \"@pingcap-incubator/tidb-dashboard-lib-apps\",\n  \"version\": \"0.20.2\",\n  \"description\": \"\",\n  \"type\": \"module\",\n  \"main\": \"dist/index.js\",\n  \"module\": \"dist/index.js\",\n  \"types\": \"dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./dist/index.js\"\n    },\n    \"./slow-query\": {\n      \"import\": \"./dist/slow-query/index.js\"\n    },\n    \"./statement\": {\n      \"import\": \"./dist/statement/index.js\"\n    },\n    \"./metric\": {\n      \"import\": \"./dist/metric/index.js\"\n    },\n    \"./utils\": {\n      \"import\": \"./dist/_re-exports/utils.js\"\n    },\n    \"./charts\": {\n      \"import\": \"./dist/_re-exports/charts.js\"\n    },\n    \"./charts-css\": {\n      \"import\": \"./dist/_re-exports/charts-css.js\"\n    },\n    \"./primitive-ui\": {\n      \"import\": \"./dist/_re-exports/primitive-ui.js\"\n    },\n    \"./biz-ui\": {\n      \"import\": \"./dist/_re-exports/biz-ui.js\"\n    }\n  },\n  \"typesVersions\": {\n    \"*\": {\n      \".\": [\n        \"./dist/index.d.ts\"\n      ],\n      \"slow-query\": [\n        \"./dist/slow-query/index.d.ts\"\n      ],\n      \"statement\": [\n        \"./dist/statement/index.d.ts\"\n      ],\n      \"metric\": [\n        \"./dist/metric/index.d.ts\"\n      ],\n      \"utils\": [\n        \"./dist/_re-exports/utils.d.ts\"\n      ],\n      \"charts\": [\n        \"./dist/_re-exports/charts.d.ts\"\n      ],\n      \"charts-css\": [\n        \"./dist/_re-exports/charts-css.d.ts\"\n      ],\n      \"primitive-ui\": [\n        \"./dist/_re-exports/primitive-ui.d.ts\"\n      ],\n      \"biz-ui\": [\n        \"./dist/_re-exports/biz-ui.d.ts\"\n      ]\n    }\n  },\n  \"files\": [\n    \"dist\",\n    \"README.md\",\n    \"CHANGELOG.md\"\n  ],\n  \"scripts\": {\n    \"tsc:watch\": \"tsc --watch\",\n    \"rollup:watch\": \"rollup -c --watch\",\n    \"dev\": \"concurrently --kill-others \\\"pnpm tsc:watch\\\" \\\"pnpm rollup:watch\\\"\",\n    \"build\": \"tsc && rollup -c\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"MIT\",\n  \"devDependencies\": {\n    \"@rollup/plugin-json\": \"^6.1.0\",\n    \"@rollup/plugin-typescript\": \"^12.1.1\",\n    \"@tanstack/react-query\": \"^5.59.16\",\n    \"@tidbcloud/uikit\": \"catalog:\",\n    \"@types/lodash-es\": \"^4.17.12\",\n    \"@types/react\": \"^18.3.12\",\n    \"react\": \"^18.3.1\",\n    \"rollup\": \"^4.24.0\",\n    \"tslib\": \"^2.8.0\",\n    \"zustand\": \"^5.0.2\"\n  },\n  \"peerDependencies\": {\n    \"@tanstack/react-query\": \"^5.59.16\",\n    \"@tidbcloud/uikit\": \"catalog:\",\n    \"react\": \"^18.3.1\",\n    \"zustand\": \"^5.0.2\"\n  },\n  \"dependencies\": {\n    \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\": \"workspace:^\",\n    \"@pingcap-incubator/tidb-dashboard-lib-charts\": \"workspace:^\",\n    \"@pingcap-incubator/tidb-dashboard-lib-icons\": \"workspace:^\",\n    \"@pingcap-incubator/tidb-dashboard-lib-primitive-ui\": \"workspace:^\",\n    \"@pingcap-incubator/tidb-dashboard-lib-utils\": \"workspace:^\",\n    \"lodash-es\": \"^4.17.21\"\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/rollup.config.js",
    "content": "import typescript from \"@rollup/plugin-typescript\"\nimport json from \"@rollup/plugin-json\"\n\nexport default {\n  input: {\n    index: \"src/index.ts\",\n    \"slow-query/index\": \"src/slow-query/index.ts\",\n    \"statement/index\": \"src/statement/index.ts\",\n    \"metric/index\": \"src/metric/index.ts\",\n    // _re-exports\n    \"_re-exports/utils\": \"src/_re-exports/utils.ts\",\n    \"_re-exports/charts\": \"src/_re-exports/charts.ts\",\n    \"_re-exports/charts-css\": \"src/_re-exports/charts-css.ts\",\n    \"_re-exports/primitive-ui\": \"src/_re-exports/primitive-ui.ts\",\n    \"_re-exports/biz-ui\": \"src/_re-exports/biz-ui.ts\",\n  },\n  output: {\n    dir: \"dist\",\n    format: \"es\",\n  },\n  plugins: [typescript(), json()],\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_re-exports/biz-ui.ts",
    "content": "export * from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_re-exports/charts-css.ts",
    "content": "import \"@pingcap-incubator/tidb-dashboard-lib-charts/dist/style.css\"\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_re-exports/charts.ts",
    "content": "export * from \"@pingcap-incubator/tidb-dashboard-lib-charts\"\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_re-exports/primitive-ui.ts",
    "content": "export * from \"@pingcap-incubator/tidb-dashboard-lib-primitive-ui\"\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_re-exports/utils.ts",
    "content": "export * from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/cols-factory.tsx",
    "content": "import {\n  formatNumByUnit,\n  formatTime,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Tooltip, Typography } from \"@tidbcloud/uikit\"\nimport { MRT_ColumnDef, MRT_RowData } from \"@tidbcloud/uikit/biz\"\n\nexport class ColConfig<T extends MRT_RowData> {\n  constructor(private col: MRT_ColumnDef<T>) {}\n\n  getConfig(): MRT_ColumnDef<T> {\n    return this.col\n  }\n  setConfig(config: MRT_ColumnDef<T>) {\n    this.col = config\n    return this\n  }\n  patchConfig(config: Partial<MRT_ColumnDef<T>>) {\n    this.col = { ...this.col, ...config }\n    return this\n  }\n}\n\nexport class TableColsFactory<T extends MRT_RowData> {\n  constructor(private tk: (key: string) => string) {}\n\n  columns(colConfigs: ColConfig<T>[]): MRT_ColumnDef<T>[] {\n    return colConfigs.map((c) => c.getConfig())\n  }\n\n  defCol(filedName: keyof T): ColConfig<T> {\n    return new ColConfig({\n      id: String(filedName),\n      header: this.tk(`fields.${String(filedName)}`),\n      enableSorting: true,\n      enableResizing: false,\n      accessorFn: (row) => row[filedName],\n    })\n  }\n\n  number(filedName: keyof T, unit: string): ColConfig<T> {\n    return this.defCol(filedName).patchConfig({\n      accessorFn: (row) => formatNumByUnit(row[filedName]!, unit) || \"-\",\n    })\n  }\n\n  timestamp(filedName: keyof T): ColConfig<T> {\n    return this.defCol(filedName).patchConfig({\n      accessorFn: (row) => formatTime(row[filedName]! * 1000),\n    })\n  }\n\n  text(filedName: keyof T): ColConfig<T> {\n    return this.defCol(filedName).patchConfig({\n      enableSorting: false,\n      enableResizing: true,\n      accessorFn: (row) => (\n        <Typography truncate>{row[filedName] || \"-\"}</Typography>\n      ),\n    })\n  }\n\n  textWithTooltip(filedName: keyof T): ColConfig<T> {\n    return this.defCol(filedName).patchConfig({\n      enableSorting: false,\n      enableResizing: true,\n      accessorFn: (row) => (\n        <Tooltip label={row[filedName] || \"\"}>\n          <Typography truncate>{row[filedName] || \"-\"}</Typography>\n        </Tooltip>\n      ),\n    })\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-history/components/chart.tsx",
    "content": "import {\n  SeriesChart,\n  SeriesData,\n} from \"@pingcap-incubator/tidb-dashboard-lib-charts\"\nimport {\n  TimeRangeValue,\n  toTimeRangeValue,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Box, Flex, Loader, useComputedColorScheme } from \"@tidbcloud/uikit\"\nimport { useMemo } from \"react\"\n\nimport { useSqlHistoryState } from \"../shared-state/memory-state\"\nimport { useSqlHistoryMetricData } from \"../utils/use-data\"\n\nexport function SqlHistoryChart() {\n  const colorScheme = useComputedColorScheme()\n  const { data: metricData, isLoading } = useSqlHistoryMetricData()\n  const metric = useSqlHistoryState((state) => state.metric)\n  const timeRange = useSqlHistoryState((state) => state.timeRange)\n  const setTimeRange = useSqlHistoryState((state) => state.setTimeRange)\n  const tr = useMemo<TimeRangeValue>(\n    () => (timeRange ? toTimeRangeValue(timeRange) : [0, 0]),\n    [timeRange],\n  )\n\n  const seriesData = useMemo<SeriesData[]>(() => {\n    if (!metricData) return []\n    return [\n      {\n        id: metric?.name || \"\",\n        name: metric?.name || \"\",\n        data: metricData || [],\n      },\n    ]\n  }, [metricData, metric])\n\n  function handleTimeRangeChange(v: TimeRangeValue) {\n    setTimeRange({ type: \"absolute\", value: v })\n  }\n\n  return (\n    <Box h={160}>\n      {seriesData.length > 0 || !isLoading ? (\n        <SeriesChart\n          // key is needed to force re-render when switching metric, but why\n          key={metric?.name}\n          unit={metric?.unit || \"short\"}\n          data={seriesData}\n          timeRange={tr}\n          theme={colorScheme}\n          onBrush={handleTimeRangeChange}\n        />\n      ) : (\n        <Flex h=\"100%\" align=\"center\" justify=\"center\">\n          <Loader size=\"xs\" />\n        </Flex>\n      )}\n    </Box>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-history/components/filters.tsx",
    "content": "import { TimeRangePicker } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Group, Select } from \"@tidbcloud/uikit\"\nimport { dayjs } from \"@tidbcloud/uikit/utils\"\nimport { useEffect, useMemo } from \"react\"\n\nimport { useAppContext } from \"../ctx\"\nimport { useSqlHistoryState } from \"../shared-state/memory-state\"\nimport { useSqlHistoryMetricNamesData } from \"../utils/use-data\"\n\nconst QUICK_RANGES: number[] = [\n  5 * 60, // 5 mins\n  15 * 60,\n  30 * 60,\n  60 * 60,\n  6 * 60 * 60,\n  12 * 60 * 60,\n  24 * 60 * 60,\n  2 * 24 * 60 * 60,\n  3 * 24 * 60 * 60, // 3 days\n  7 * 24 * 60 * 60, // 7 days\n]\n\nfunction MetricSelect() {\n  const ctx = useAppContext()\n  const { t } = useTn(\"sql-history\")\n  const { data: metrics } = useSqlHistoryMetricNamesData()\n  const metric = useSqlHistoryState((state) => state.metric)\n  const setMetric = useSqlHistoryState((state) => state.setMetric)\n  useEffect(() => {\n    if (metrics && !metric) {\n      setMetric(metrics[0])\n    }\n  }, [metrics])\n\n  const selectData = useMemo(\n    () =>\n      metrics?.map((m) => ({\n        label: t(`fields.${m.name}`, m.name, { ns: ctx.cfg.parentAppName }),\n        value: m.name,\n      })),\n    [metrics, t],\n  )\n\n  function handleMetricChange(v: string | null) {\n    const metric = metrics?.find((m) => m.name === v)\n    setMetric(metric)\n  }\n\n  return (\n    <Select\n      data={selectData}\n      value={metric && metric.name}\n      onChange={handleMetricChange}\n    />\n  )\n}\n\nfunction TimeRangeSelect() {\n  const ctx = useAppContext()\n  const timeRange = useSqlHistoryState((state) => state.timeRange)\n  const setTimeRange = useSqlHistoryState((state) => state.setTimeRange)\n  useEffect(() => {\n    if (!timeRange) {\n      setTimeRange(ctx.cfg.initialTimeRange)\n    }\n  }, [timeRange])\n\n  return (\n    <TimeRangePicker\n      value={timeRange || ctx.cfg.initialTimeRange}\n      onChange={setTimeRange}\n      quickRanges={QUICK_RANGES}\n      minDateTime={() =>\n        dayjs()\n          .subtract(QUICK_RANGES[QUICK_RANGES.length - 1], \"seconds\")\n          .startOf(\"d\")\n          .toDate()\n      }\n      maxDateTime={() => dayjs().endOf(\"d\").toDate()}\n    />\n  )\n}\n\nexport function Filters() {\n  return (\n    <Group>\n      <MetricSelect />\n      <TimeRangeSelect />\n    </Group>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-history/components/index.tsx",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Card, Group, Stack, Title } from \"@tidbcloud/uikit\"\nimport { useEffect } from \"react\"\n\nimport { useSqlHistoryState } from \"../shared-state/memory-state\"\n\nimport { SqlHistoryChart } from \"./chart\"\nimport { Filters } from \"./filters\"\nimport { TimeRangeClipAlert } from \"./time-range-clip-alert\"\n\nexport function SqlHistoryCard() {\n  const { tt } = useTn(\"sql-history\")\n  const reset = useSqlHistoryState((s) => s.reset)\n\n  // reset state on unmount\n  useEffect(() => {\n    return () => {\n      reset()\n    }\n  }, [])\n\n  return (\n    <Card shadow=\"xs\" p=\"md\">\n      <Stack gap=\"xs\">\n        <Group justify=\"space-between\">\n          <Title order={5}>{tt(\"SQL History\")}</Title>\n          <Filters />\n        </Group>\n        <TimeRangeClipAlert />\n        <SqlHistoryChart />\n      </Stack>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-history/components/time-range-clip-alert.tsx",
    "content": "import { formatTime, useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Alert } from \"@tidbcloud/uikit\"\n\nimport { useTimeRangeValueState } from \"../shared-state/memory-state\"\n\nexport function TimeRangeClipAlert() {\n  const { tt } = useTn(\"sql-history\")\n  const trv = useTimeRangeValueState((s) => s.trv)\n  const beyondMax = useTimeRangeValueState((s) => s.beyondMax)\n\n  if (!beyondMax) {\n    return null\n  }\n\n  return (\n    <Alert>\n      {tt(\n        \"Due to the limitation, currently only support to query max 24 hours data, so the actual time range is {{begin}} to {{end}}\",\n        {\n          begin: formatTime(trv[0] * 1000),\n          end: formatTime(trv[1] * 1000),\n        },\n      )}\n    </Alert>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-history/ctx/index.tsx",
    "content": "import { TimeRange } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { createContext, useContext } from \"react\"\n\n////////////////////////////////\n\nexport type HistoryMetricItem = {\n  name: string\n  unit: string\n}\n\nexport type AppApi = {\n  getHistoryMetricNames(): Promise<HistoryMetricItem[]>\n  getHistoryMetricData(params: {\n    sqlDigest: string\n    metricName: string\n    beginTime: number\n    endTime: number\n  }): Promise<[number, number][]>\n}\n\nexport type AppConfig = {\n  parentAppName: string // 'slow-query' | 'statement'\n  sqlDigest: string\n  initialTimeRange: TimeRange\n  timeRangeMaxDuration?: number // unit: seconds\n}\n\nexport type AppCtxValue = {\n  // we use ctxId to be a part of queryKey for react-query,\n  // to differ same requests from different clusters, so this value can be clusterId, or other unique value\n  ctxId: string\n  cfg: AppConfig\n  api: AppApi\n}\n\nexport const AppContext = createContext<AppCtxValue | null>(null)\n\nexport const useAppContext = () => {\n  const context = useContext(AppContext)\n\n  if (!context) {\n    throw new Error(\"SqlHistory AppContext must be used within a provider\")\n  }\n\n  return context\n}\n\n////////////////////////////////\n\nexport function AppProvider(props: {\n  children: React.ReactNode\n  ctxValue: AppCtxValue\n}) {\n  return (\n    <AppContext.Provider value={props.ctxValue}>\n      {props.children}\n    </AppContext.Provider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-history/index.ts",
    "content": "// Note: this is a shared components for slow-query app and statement app\n\nexport * from \"./ctx\"\nexport * from \"./components\"\n\n// i18n\nimport \"./locales\"\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-history/locales/en.json",
    "content": "{\n  \"__namespace__\": \"sql-history\",\n  \"__comment__\": \"this file can be updated by running `pnpm gen:locales` command\"\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-history/locales/index.ts",
    "content": "import { addLangsLocales } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n\nimport en from \"./en.json\"\nimport zh from \"./zh.json\"\n\naddLangsLocales({ en, zh })\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-history/locales/zh.json",
    "content": "{\n  \"__namespace__\": \"sql-history\",\n  \"__comment__\": \"this file can be updated by running `pnpm gen:locales` command\",\n  \"Due to the limitation, currently only support to query max 24 hours data, so the actual time range is {{begin}} to {{end}}\": \"由于限制，目前仅支持查询最大 24 小时的数据，实际时间范围是 {{begin}} ~ {{end}}\",\n  \"SQL History\": \"SQL 历史\"\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-history/shared-state/memory-state.ts",
    "content": "import {\n  TimeRange,\n  TimeRangeValue,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { create } from \"zustand\"\n\nimport { HistoryMetricItem } from \"../ctx\"\n\ninterface SqlHistoryState {\n  timeRange: TimeRange | undefined\n  setTimeRange: (timeRange: TimeRange | undefined) => void\n\n  metric: HistoryMetricItem | undefined\n  setMetric: (metric: HistoryMetricItem | undefined) => void\n\n  reset: () => void\n}\n\nexport const useSqlHistoryState = create<SqlHistoryState>((set) => ({\n  timeRange: undefined,\n  setTimeRange: (timeRange: TimeRange | undefined) => set({ timeRange }),\n\n  metric: undefined,\n  setMetric: (metric: HistoryMetricItem | undefined) => set({ metric }),\n\n  reset: () => set({ timeRange: undefined, metric: undefined }),\n}))\n\n//----------------------------------------------\n\n// when get slow query list data, the time range can't beyond max 24 hours\n// if that, we should clip the time range and show a alert\ninterface TimeRangeValueState {\n  trv: TimeRangeValue\n  beyondMax: boolean\n  setTRV: (newTRV: TimeRangeValue, beyondMax?: boolean) => void\n}\n\nexport const useTimeRangeValueState = create<TimeRangeValueState>((set) => ({\n  trv: [0, 0],\n  beyondMax: false,\n  setTRV: (newTRV, beyondMax) => set({ trv: newTRV, beyondMax }),\n}))\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-history/utils/use-data.ts",
    "content": "import { toTimeRangeValue } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useQuery } from \"@tanstack/react-query\"\n\nimport { useAppContext } from \"../ctx\"\nimport {\n  useSqlHistoryState,\n  useTimeRangeValueState,\n} from \"../shared-state/memory-state\"\n\nexport function useSqlHistoryMetricNamesData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"sql-history\", \"metric-names\"],\n    queryFn: () => ctx.api.getHistoryMetricNames(),\n  })\n}\n\nexport function useSqlHistoryMetricData() {\n  const ctx = useAppContext()\n  const metric = useSqlHistoryState((state) => state.metric)\n  const timeRange = useSqlHistoryState((state) => state.timeRange)\n\n  const setTRV = useTimeRangeValueState((s) => s.setTRV)\n\n  return useQuery({\n    queryKey: [\n      ctx.ctxId,\n      \"sql-history\",\n      \"metric-data\",\n      ctx.cfg.sqlDigest,\n      metric,\n      timeRange,\n    ],\n    queryFn: () => {\n      const tr = toTimeRangeValue(timeRange!)\n      const beginTime = tr[0]\n      let endTime = tr[1]\n      if (ctx.cfg.timeRangeMaxDuration) {\n        const beyondMax = endTime - beginTime > ctx.cfg.timeRangeMaxDuration\n        if (beyondMax) {\n          endTime = beginTime + ctx.cfg.timeRangeMaxDuration\n        }\n        setTRV([beginTime, endTime], beyondMax)\n      }\n      return ctx.api.getHistoryMetricData({\n        sqlDigest: ctx.cfg.sqlDigest,\n        metricName: metric!.name,\n        beginTime,\n        endTime,\n      })\n    },\n    enabled: !!metric && !!timeRange,\n  })\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-limit/components/index.tsx",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Card, Group, Stack, Title, Typography } from \"@tidbcloud/uikit\"\n\nimport { useSqlLimitSupportData } from \"../utils/use-data\"\n\nimport { SqlLimitSettingModal } from \"./setting\"\nimport { SqlLimitTable } from \"./table\"\n\nexport function SqlLimitCard() {\n  const { tt } = useTn(\"sql-limit\")\n  const { data: sqlLimitSupport } = useSqlLimitSupportData()\n\n  return (\n    <Card shadow=\"xs\" p=\"md\">\n      <Stack gap=\"xs\">\n        <Group justify=\"space-between\">\n          <Title order={5}>{tt(\"SQL Limit\")}</Title>\n          {sqlLimitSupport?.is_support && <SqlLimitSettingModal />}\n        </Group>\n        {sqlLimitSupport && !sqlLimitSupport.is_support && (\n          <Typography c=\"gray\">\n            {tt(\n              \"SQL limit feature is only available in and above {{distro.tidb}} 7.5.0\",\n            )}\n          </Typography>\n        )}\n        {sqlLimitSupport && sqlLimitSupport.is_support && <SqlLimitTable />}\n      </Stack>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-limit/components/setting.tsx",
    "content": "import { LoadingSkeleton } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  Button,\n  Modal,\n  Select,\n  Stack,\n  Typography,\n  notifier,\n} from \"@tidbcloud/uikit\"\nimport { useState } from \"react\"\n\nimport { useSettingModalState } from \"../shared-state/memory-state\"\nimport { useCreateSqlLimitData, useRuGroupsData } from \"../utils/use-data\"\n\nfunction SqlLimitSettingBody() {\n  const { data: ruGroups, isLoading } = useRuGroupsData()\n  const setLimitMut = useCreateSqlLimitData()\n\n  const setModalVisible = useSettingModalState((s) => s.setVisible)\n\n  const [resourceGroup, setResourceGroup] = useState(\"\")\n  const [action, setAction] = useState(\"\")\n\n  const { tt } = useTn(\"sql-limit\")\n\n  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {\n    e.preventDefault()\n    if (!resourceGroup || !action) {\n      return\n    }\n    try {\n      await setLimitMut.mutateAsync({ ruGroup: resourceGroup, action })\n      notifier.success(tt(\"Set SQL limit successfully\"))\n      setModalVisible(false)\n    } catch (_err) {\n      notifier.error(tt(\"Set SQL limit failed\"))\n    }\n  }\n\n  if (isLoading) {\n    return <LoadingSkeleton />\n  }\n\n  if (!ruGroups) {\n    return null\n  }\n\n  if (ruGroups.length === 0) {\n    return (\n      <Typography>\n        {tt(\n          \"There is no resource groups, please create a resource group manually first\",\n        )}\n      </Typography>\n    )\n  }\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <Stack>\n        <Select\n          placeholder={tt(\"Resource Group\")}\n          data={ruGroups}\n          value={resourceGroup}\n          onChange={(v) => setResourceGroup(v || \"\")}\n        />\n        <Select\n          placeholder={tt(\"Action\")}\n          data={[\"DRYRUN\", \"COOLDOWN\", \"KILL\"]}\n          value={action}\n          onChange={(v) => setAction(v || \"\")}\n        />\n        <Button ml=\"auto\" type=\"submit\" disabled={!resourceGroup || !action}>\n          {tt(\"Set Limit\")}\n        </Button>\n      </Stack>\n    </form>\n  )\n}\n\nexport function SqlLimitSettingModal() {\n  const modalVisible = useSettingModalState((s) => s.visible)\n  const setModalVisible = useSettingModalState((s) => s.setVisible)\n  const { tt } = useTn(\"sql-limit\")\n\n  return (\n    <>\n      <Button variant=\"default\" size=\"xs\" onClick={() => setModalVisible(true)}>\n        {tt(\"Add or Update\")}\n      </Button>\n\n      <Modal\n        title={tt(\"SQL Limit Setting\")}\n        opened={modalVisible}\n        onClose={() => {\n          setModalVisible(false)\n        }}\n      >\n        <SqlLimitSettingBody />\n      </Modal>\n    </>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-limit/components/table.tsx",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Button, notifier, openConfirmModal } from \"@tidbcloud/uikit\"\nimport { MRT_ColumnDef, ProTable } from \"@tidbcloud/uikit/biz\"\nimport { useMemo } from \"react\"\n\nimport { SqlLimitStatusItem } from \"../ctx\"\nimport { useDeleteSqlLimitData, useSqlLimitStatusData } from \"../utils/use-data\"\n\nexport function SqlLimitTable() {\n  const { data: sqlLimitStatus } = useSqlLimitStatusData()\n  const deleteSqlLimitMut = useDeleteSqlLimitData()\n  const { tt } = useTn(\"sql-limit\")\n\n  async function handleDelete(id: string) {\n    try {\n      await deleteSqlLimitMut.mutateAsync(id)\n      notifier.success(tt(\"Delete SQL limit successfully\"))\n    } catch (_err) {\n      notifier.error(tt(\"Delete SQL limit failed\"))\n    }\n  }\n\n  function confirmDelete(item: SqlLimitStatusItem) {\n    openConfirmModal({\n      title: tt(\"Delete Limit\"),\n      children: tt(\n        \"Are you sure to delete this limit (Resource Group - {{ruGroup}}, Action - {{action}})?\",\n        { ruGroup: item.ru_group, action: item.action },\n      ),\n      confirmProps: { color: \"red\", variant: \"outline\" },\n      labels: { confirm: tt(\"Delete\"), cancel: tt(\"Cancel\") },\n      onConfirm: () => handleDelete(item.id),\n    })\n  }\n\n  const columns = useMemo<MRT_ColumnDef<SqlLimitStatusItem>[]>(\n    () => [\n      {\n        id: \"ru_group\",\n        header: tt(\"Resource Group\"),\n        accessorFn: (row) => row.ru_group,\n      },\n      { id: \"action\", header: tt(\"Action\"), accessorFn: (row) => row.action },\n      {\n        id: \"start_time\",\n        header: tt(\"Start Time\"),\n        accessorFn: (row) => row.start_time,\n      },\n      {\n        id: \"operation\",\n        header: \"\",\n        size: 120,\n        mantineTableBodyCellProps: {\n          align: \"right\",\n        },\n        // accessorFn can't update the text when changing the language\n        Cell: (d) => (\n          <Button\n            c=\"red.7\"\n            variant=\"transparent\"\n            onClick={() => confirmDelete(d.row.original)}\n          >\n            {tt(\"Delete Limit\")}\n          </Button>\n        ),\n      },\n    ],\n    [tt],\n  )\n\n  return (\n    <ProTable\n      data={sqlLimitStatus || []}\n      columns={columns}\n      enableColumnPinning\n      initialState={{\n        columnPinning: { right: [\"operation\"] },\n      }}\n      emptyMessage={tt(\"No Data\")}\n    />\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-limit/ctx/index.tsx",
    "content": "import { createContext, useContext } from \"react\"\n\n////////////////////////////////\nexport type SqlLimitStatusItem = {\n  id: string\n  ru_group: string\n  action: string\n  start_time: string\n}\n\nexport type AppApi = {\n  // sql limit\n  getRuGroups(): Promise<string[]>\n  checkSqlLimitSupport(): Promise<{ is_support: boolean }>\n  getSqlLimitStatus(params: {\n    watchText: string\n  }): Promise<SqlLimitStatusItem[]>\n  createSqlLimit(params: {\n    watchText: string\n    ruGroup: string\n    action: string\n  }): Promise<void>\n  deleteSqlLimit(params: { watchText: string; id: string }): Promise<void>\n}\n\nexport type AppCtxValue = {\n  // we use ctxId to be a part of queryKey for react-query,\n  // to differ same requests from different clusters, so this value can be clusterId, or other unique value\n  ctxId: string\n  sqlDigest: string\n\n  api: AppApi\n}\n\nexport const AppContext = createContext<AppCtxValue | null>(null)\n\nexport const useAppContext = () => {\n  const context = useContext(AppContext)\n\n  if (!context) {\n    throw new Error(\"SqlLimit AppContext must be used within a provider\")\n  }\n\n  return context\n}\n\n////////////////////////////////\n\nexport function AppProvider(props: {\n  children: React.ReactNode\n  ctxValue: AppCtxValue\n}) {\n  return (\n    <AppContext.Provider value={props.ctxValue}>\n      {props.children}\n    </AppContext.Provider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-limit/index.ts",
    "content": "// Note: this is a shared components for slow-query app and statement app\n\nexport * from \"./ctx\"\nexport * from \"./components\"\n\n// i18n\nimport \"./locales\"\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-limit/locales/en.json",
    "content": "{\n  \"__namespace__\": \"sql-limit\",\n  \"__comment__\": \"this file can be updated by running `pnpm gen:locales` command\"\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-limit/locales/index.ts",
    "content": "import { addLangsLocales } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n\nimport en from \"./en.json\"\nimport zh from \"./zh.json\"\n\naddLangsLocales({ en, zh })\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-limit/locales/zh.json",
    "content": "{\n  \"__namespace__\": \"sql-limit\",\n  \"__comment__\": \"this file can be updated by running `pnpm gen:locales` command\",\n  \"Action\": \"操作\",\n  \"Add or Update\": \"添加或更新\",\n  \"Are you sure to delete this limit (Resource Group - {{ruGroup}}, Action - {{action}})?\": \"你确定要删除此限流 (资源组 - {{ruGroup}}, 操作 - {{action}}) 吗？\",\n  \"Cancel\": \"取消\",\n  \"Delete Limit\": \"删除限流\",\n  \"Delete SQL limit failed\": \"删除 SQL 限流失败\",\n  \"Delete SQL limit successfully\": \"删除 SQL 限流成功\",\n  \"Delete\": \"删除\",\n  \"No Data\": \"无结果\",\n  \"Resource Group\": \"资源组\",\n  \"SQL Limit Setting\": \"SQL 限流设置\",\n  \"SQL Limit\": \"SQL 限流\",\n  \"SQL limit feature is only available in and above {{distro.tidb}} 7.5.0\": \"SQL 限流功能仅在 {{distro.tidb}} 7.5.0 及以上版本可用\",\n  \"Set Limit\": \"设置限流\",\n  \"Set SQL limit failed\": \"设置 SQL 限流失败\",\n  \"Set SQL limit successfully\": \"设置 SQL 限流成功\",\n  \"Start Time\": \"开始时间\",\n  \"There is no resource groups, please create a resource group manually first\": \"暂无资源组，请先手动创建资源组\"\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-limit/shared-state/memory-state.ts",
    "content": "import { create } from \"zustand\"\n\ninterface SettingModalState {\n  visible: boolean\n  setVisible: (visible: boolean) => void\n}\n\nexport const useSettingModalState = create<SettingModalState>((set) => ({\n  visible: false,\n  setVisible: (visible: boolean) => set({ visible }),\n}))\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/sql-limit/utils/use-data.ts",
    "content": "import { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\"\n\nimport { useAppContext } from \"../ctx\"\n\nexport function useRuGroupsData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"sql-limit\", \"ru-groups\"],\n    queryFn: () => ctx.api.getRuGroups(),\n  })\n}\n\nexport function useSqlLimitSupportData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"sql-limit-support\"],\n    queryFn: () => ctx.api.checkSqlLimitSupport(),\n  })\n}\n\nexport function useSqlLimitStatusData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"sql-limit-status\", ctx.sqlDigest],\n    queryFn: () => ctx.api.getSqlLimitStatus({ watchText: ctx.sqlDigest }),\n  })\n}\n\nexport function useCreateSqlLimitData() {\n  const ctx = useAppContext()\n  const queryClient = useQueryClient()\n  return useMutation({\n    mutationFn: (params: { ruGroup: string; action: string }) => {\n      return ctx.api.createSqlLimit({ watchText: ctx.sqlDigest, ...params })\n    },\n    onSuccess: () => {\n      queryClient.invalidateQueries({\n        queryKey: [ctx.ctxId, \"sql-limit-status\", ctx.sqlDigest],\n      })\n    },\n  })\n}\n\nexport function useDeleteSqlLimitData() {\n  const ctx = useAppContext()\n  const queryClient = useQueryClient()\n  return useMutation({\n    mutationFn: (id: string) => {\n      return ctx.api.deleteSqlLimit({ watchText: ctx.sqlDigest, id })\n    },\n    onSuccess: () => {\n      queryClient.invalidateQueries({\n        queryKey: [ctx.ctxId, \"sql-limit-status\", ctx.sqlDigest],\n      })\n    },\n  })\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/_shared/state-filters.tsx",
    "content": "import { TimeRangePicker } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport {\n  useResetFiltersState,\n  useSearchUrlState,\n  useTimeRangeUrlState,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Button, CloseButton, TextInput } from \"@tidbcloud/uikit\"\nimport { IconCornerDownLeft, IconSearchSm } from \"@tidbcloud/uikit/icons\"\nimport { dayjs } from \"@tidbcloud/uikit/utils\"\nimport { useEffect, useState } from \"react\"\n\n//////////////////////////////////////////////\n// UrlStateTimeRangePicker\nconst QUICK_RANGES: number[] = [\n  5 * 60, // 5 mins\n  15 * 60,\n  30 * 60,\n  60 * 60,\n  6 * 60 * 60,\n  12 * 60 * 60,\n  24 * 60 * 60,\n  2 * 24 * 60 * 60,\n  3 * 24 * 60 * 60, // 3 days\n  7 * 24 * 60 * 60, // 7 days\n]\n\nexport function UrlStateTimeRangePicker() {\n  const { timeRange, setTimeRange } = useTimeRangeUrlState()\n\n  return (\n    <TimeRangePicker\n      value={timeRange}\n      onChange={(v) => {\n        setTimeRange(v)\n      }}\n      quickRanges={QUICK_RANGES}\n      minDateTime={() =>\n        dayjs()\n          .subtract(QUICK_RANGES[QUICK_RANGES.length - 1], \"seconds\")\n          .startOf(\"d\")\n          .toDate()\n      }\n      maxDateTime={() => dayjs().endOf(\"d\").toDate()}\n    />\n  )\n}\n\n//////////////////////////////////////////////\n// UrlStateSearchInput\nexport function UrlStateSearchInput({\n  placeholder = \"\",\n}: {\n  placeholder: string\n}) {\n  const { term, setTerm } = useSearchUrlState()\n  console.log(\"term:\", term)\n\n  const resetVal = useResetFiltersState((s) => s.resetVal)\n  const [text, setText] = useState(term)\n\n  // reset text when clicking `reset filters` button\n  useEffect(() => {\n    if (resetVal > 0) {\n      setText(\"\")\n    }\n  }, [resetVal])\n\n  const handleSearchSubmit = (e: React.FormEvent<HTMLFormElement>) => {\n    e.preventDefault()\n    setTerm(text)\n  }\n\n  return (\n    <form onSubmit={handleSearchSubmit}>\n      <TextInput\n        w={280}\n        value={text}\n        onChange={(e) => setText(e.target.value)}\n        placeholder={placeholder}\n        leftSection={<IconSearchSm />}\n        rightSection={\n          text ? (\n            <CloseButton\n              size=\"sm\"\n              onMouseDown={(e) => e.preventDefault()} // to prevent the input lose focus\n              onClick={() => {\n                setText(\"\")\n                setTerm(undefined)\n              }}\n            />\n          ) : (\n            <IconCornerDownLeft />\n          )\n        }\n      />\n    </form>\n  )\n}\n\n//////////////////////////////////////////////\n// MemoryStateResetButton\nexport function MemoryStateResetButton({ text }: { text: string }) {\n  const reset = useResetFiltersState((s) => s.reset)\n  return (\n    <Button variant=\"subtle\" onClick={reset}>\n      {text}\n    </Button>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/index.ts",
    "content": "// keep a dummy export funtion to prevent build error\nexport function hello() {\n  console.log(\"hello tidb-dashboard-lib\")\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/components/chart-actions-menu.tsx",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { ActionIcon, Menu, Stack, Typography } from \"@tidbcloud/uikit\"\nimport {\n  IconDotsHorizontal,\n  IconEyeOff,\n  IconRefreshCw02,\n} from \"@tidbcloud/uikit/icons\"\n\nexport function ChartActionsMenu({\n  promAddr,\n  showHide,\n  onHide,\n  onRefresh,\n}: {\n  promAddr?: string\n  showHide?: boolean\n  onHide: () => void\n  onRefresh: () => void\n}) {\n  const { tt } = useTn(\"metric\")\n\n  return (\n    <Menu>\n      <Menu.Target>\n        <ActionIcon variant=\"transparent\" aria-label=\"metrics chart actions\">\n          <IconDotsHorizontal size={16} />\n        </ActionIcon>\n      </Menu.Target>\n\n      <Menu.Dropdown>\n        {promAddr && (\n          <>\n            <Stack gap={2} py={8} px={12}>\n              <Typography c=\"carbon.6\" fw={500} fz={12}>\n                {tt(\"Prometheus Address\")}\n              </Typography>\n              <Typography>{promAddr}</Typography>\n            </Stack>\n            <Menu.Divider />\n          </>\n        )}\n        <Menu.Item\n          leftSection={<IconRefreshCw02 size={16} strokeWidth={2} />}\n          onClick={onRefresh}\n        >\n          {tt(\"Refresh\")}\n        </Menu.Item>\n        {showHide && (\n          <Menu.Item\n            leftSection={<IconEyeOff size={16} strokeWidth={2} />}\n            onClick={onHide}\n          >\n            {tt(\"Hide\")}\n          </Menu.Item>\n        )}\n      </Menu.Dropdown>\n    </Menu>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/components/chart-body.tsx",
    "content": "import {\n  SeriesChart,\n  SeriesData,\n} from \"@pingcap-incubator/tidb-dashboard-lib-charts\"\nimport {\n  PromResultItem,\n  TimeRange,\n  TimeRangeValue,\n  TransformNullValue,\n  calcPromQueryStep,\n  toTimeRangeValue,\n  transformPromResultItem,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  Alert,\n  Box,\n  Flex,\n  Loader,\n  useComputedColorScheme,\n} from \"@tidbcloud/uikit\"\nimport { useEffect, useMemo, useRef, useState } from \"react\"\n\nimport { useAppContext } from \"../ctx\"\nimport { useChartState } from \"../shared-state/memory-state\"\nimport { useMetricsUrlState } from \"../shared-state/url-state\"\nimport { SingleChartConfig } from \"../utils/type\"\nimport { useMetricDataByMetricName } from \"../utils/use-data\"\n\nexport function transformData(\n  items: PromResultItem[],\n  qIdx: number,\n  // query: SingleQueryConfig,\n  legendName: string,\n  nullValue?: TransformNullValue,\n): SeriesData[] {\n  return items.map((d, dIdx) => ({\n    ...transformPromResultItem(d, legendName, nullValue),\n    id: `${qIdx}-${dIdx}`,\n    // type: query.type,\n    // color: query.color,\n    // lineSeriesStyle: query.lineSeriesStyle,\n  }))\n}\n\nexport function ChartBody({\n  config,\n  timeRange,\n  onTimeRangeChange,\n  labelValue,\n}: {\n  config: SingleChartConfig\n  timeRange: TimeRange\n  onTimeRangeChange?: (timeRange: TimeRangeValue) => void\n  labelValue?: string\n}) {\n  const setMetricPromAddrs = useChartState((s) => s.setMetricPromAddrs)\n\n  const ctx = useAppContext()\n  const { refresh } = useMetricsUrlState()\n  const colorScheme = useComputedColorScheme()\n\n  const chartRef = useRef<HTMLDivElement | null>(null)\n  const isVisible = useRef(false)\n  const [isFetched, setIsFetched] = useState(false)\n\n  // a function can always get the latest value\n  function getStep() {\n    if (!chartRef.current) return 0\n\n    return calcPromQueryStep(\n      toTimeRangeValue(timeRange),\n      chartRef.current.offsetWidth - 200,\n      ctx.cfg.scrapeInterval,\n    )\n  }\n\n  const {\n    data: metricData,\n    isLoading,\n    refetch,\n    error,\n  } = useMetricDataByMetricName(\n    config.metricName,\n    timeRange,\n    getStep,\n    labelValue,\n  )\n\n  // @todo: https://mantine.dev/hooks/use-intersection/ use this hook to simplify\n  // only fetch data when the chart is visible in the viewport\n  useEffect(() => {\n    const observer = new IntersectionObserver(\n      ([entry]) => {\n        isVisible.current = entry.isIntersecting\n        if (entry.isIntersecting && !isFetched) {\n          refetch()\n          setIsFetched(true)\n        }\n      },\n      { threshold: 0.1 },\n    )\n\n    if (chartRef.current) {\n      observer.observe(chartRef.current)\n    }\n\n    return () => {\n      observer.disconnect()\n    }\n  }, [refetch, isFetched])\n\n  useEffect(() => {\n    if (isVisible.current) {\n      refetch()\n      setIsFetched(true)\n    } else {\n      setIsFetched(false)\n    }\n  }, [timeRange, labelValue])\n\n  useEffect(() => {\n    // when click refresh button in the chart self, set refresh value to `_${metricName}{Date.now()}`\n    if (refresh.startsWith(\"_\")) {\n      if (!refresh.startsWith(`_${config.metricName}`)) {\n        return\n      }\n      if (isVisible.current) {\n        refetch()\n      }\n      return\n    }\n\n    // when click auto-refresh-button outside the chart, set refresh value to Date.now()\n    if (isVisible.current) {\n      refetch()\n      setIsFetched(true)\n    } else {\n      setIsFetched(false)\n    }\n  }, [refresh])\n\n  const seriesData = useMemo(\n    () =>\n      (metricData?.metrics ?? [])\n        .map((d, idx) =>\n          transformData(d.result, idx, d.legend, config.nullValue),\n        )\n        .flat(),\n    [metricData],\n  )\n\n  useEffect(() => {\n    if (metricData?.promAddr) {\n      setMetricPromAddrs(config.metricName, metricData.promAddr)\n    }\n  }, [metricData?.promAddr, config.metricName])\n\n  return (\n    <Box\n      h={160}\n      ref={chartRef}\n      // add `data-metric` attribute to identify the metric name for easy debugging\n      data-metric={config.metricName}\n    >\n      {error ? (\n        <Alert color=\"red\">{error.message}</Alert>\n      ) : isLoading ? (\n        <Flex h=\"100%\" align=\"center\" justify=\"center\">\n          <Loader size=\"xs\" />\n        </Flex>\n      ) : (\n        <SeriesChart\n          unit={config.unit}\n          data={seriesData}\n          timeRange={metricData?.tr ?? toTimeRangeValue(timeRange)}\n          theme={colorScheme}\n          onBrush={onTimeRangeChange}\n        />\n      )}\n    </Box>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/components/chart-header.tsx",
    "content": "import { TimeRange, useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { ActionIcon, Box, Group, Tooltip, Typography } from \"@tidbcloud/uikit\"\nimport { IconLayersThree02, IconRefreshCw02 } from \"@tidbcloud/uikit/icons\"\n\nimport {\n  useChartState,\n  useChartsSelectState,\n} from \"../shared-state/memory-state\"\nimport { useMetricsUrlState } from \"../shared-state/url-state\"\nimport { SingleChartConfig } from \"../utils/type\"\n\nimport { ChartActionsMenu } from \"./chart-actions-menu\"\n\nexport function ChartHeader({\n  title,\n  enableDrillDown = false,\n  showMoreActions = false,\n  showHide = false,\n  config,\n  timeRange,\n  children,\n}: {\n  title?: string\n  enableDrillDown?: boolean\n  showMoreActions?: boolean\n  showHide?: boolean\n  config: SingleChartConfig\n  timeRange?: TimeRange\n  children?: React.ReactNode\n}) {\n  const { tt } = useTn(\"metric\")\n  const { setRefresh } = useMetricsUrlState()\n  const setSelectedChart = useChartState((s) => s.setSelectedChart)\n  const setTimeRange = useChartState((s) => s.setTimeRange)\n  const metricPromAddrs = useChartState((s) => s.metricPromAddrs)\n  const curPromAddr = metricPromAddrs[config.metricName]\n\n  const hiddenCharts = useChartsSelectState((s) => s.hiddenCharts)\n  const setHiddenCharts = useChartsSelectState((s) => s.setHiddenCharts)\n\n  function handleHide() {\n    setHiddenCharts([...hiddenCharts, config.metricName])\n  }\n\n  return (\n    <Group gap={2} mb={8}>\n      <Typography variant=\"label-lg\">{title}</Typography>\n      <Box sx={{ flexGrow: 1 }} />\n      {!showMoreActions && (\n        <ActionIcon\n          variant=\"transparent\"\n          onClick={() => setRefresh(\"_\" + config.metricName + \"_\")}\n        >\n          <IconRefreshCw02 size={14} strokeWidth={2} />\n        </ActionIcon>\n      )}\n      {enableDrillDown && (\n        <Tooltip label={tt(\"Drill down analysis\")}>\n          <ActionIcon\n            mx={-4}\n            variant=\"transparent\"\n            onClick={() => {\n              setTimeRange(timeRange!)\n              setSelectedChart(config)\n            }}\n          >\n            <IconLayersThree02 size={16} strokeWidth={2} />\n          </ActionIcon>\n        </Tooltip>\n      )}\n      {showMoreActions && (\n        <ChartActionsMenu\n          onHide={handleHide}\n          onRefresh={() => setRefresh(\"_\" + config.metricName + \"_\")}\n          promAddr={curPromAddr}\n          showHide={showHide}\n        />\n      )}\n      {children}\n    </Group>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/components/charts-select.tsx",
    "content": "import {\n  ChartMultiSelect,\n  ChartsSelectData,\n} from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMemo } from \"react\"\n\nimport { useChartsSelectState } from \"../shared-state/memory-state\"\nimport { useCurPanelConfigsData } from \"../utils/use-data\"\n\nexport function ChartsSelect() {\n  const { tk } = useTn(\"metric\")\n\n  const { panelConfigData } = useCurPanelConfigsData()\n  const chartsSelectData = useMemo(() => {\n    const d: ChartsSelectData = []\n    for (const panel of panelConfigData || []) {\n      const category = tk(`panels.${panel.category}`, panel.category)\n      for (const chart of panel.charts) {\n        d.push({\n          category,\n          label: chart.title,\n          val: chart.metricName,\n        })\n      }\n    }\n    return d\n  }, [panelConfigData])\n\n  const hiddenCharts = useChartsSelectState((s) => s.hiddenCharts)\n  const setHiddenCharts = useChartsSelectState((s) => s.setHiddenCharts)\n\n  const chartsSelectValue = useMemo(() => {\n    return chartsSelectData\n      .map((item) => item.val)\n      .filter((v) => !hiddenCharts.includes(v))\n  }, [chartsSelectData, hiddenCharts])\n\n  function onReset() {\n    const allData = chartsSelectData.map((item) => item.val)\n    setHiddenCharts(hiddenCharts.filter((v) => !allData.includes(v)))\n  }\n\n  function onSelect(val: string) {\n    setHiddenCharts(hiddenCharts.filter((v) => v !== val))\n  }\n\n  function onUnSelect(val: string) {\n    setHiddenCharts([...hiddenCharts, val])\n  }\n\n  return (\n    <ChartMultiSelect\n      data={chartsSelectData}\n      value={chartsSelectValue}\n      onSelect={onSelect}\n      onUnSelect={onUnSelect}\n      onReset={onReset}\n    />\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/components/loading-card.tsx",
    "content": "import { Card, Skeleton } from \"@tidbcloud/uikit\"\n\nexport function LoadingCard() {\n  return (\n    <Card p={16} bg=\"carbon.0\">\n      <Skeleton visible={true} h={290} />\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/ctx/index.tsx",
    "content": "import { PromResultItem } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { createContext, useContext } from \"react\"\n\nimport { MetricConfigKind, SinglePanelConfig } from \"../utils/type\"\n\n////////////////////////////////\n\nexport type MetricDataByNameResultItem = {\n  expr: string\n  legend: string\n  result: PromResultItem[]\n  promAddr?: string\n}\n\ntype AppApi = {\n  getMetricQueriesConfig(kind: MetricConfigKind): Promise<SinglePanelConfig[]>\n\n  getMetricConfig(): Promise<{ delaySec: number }>\n\n  getMetricLabelValues(params: {\n    metricName: string\n    labelName: string\n    beginTime: number\n    endTime: number\n  }): Promise<string[]>\n\n  getMetricDataByPromQL(params: {\n    promQL: string\n    beginTime: number\n    endTime: number\n    step: number\n  }): Promise<PromResultItem[]>\n\n  getMetricDataByMetricName(params: {\n    metricName: string\n    beginTime: number\n    endTime: number\n    step: number\n    label?: string\n  }): Promise<MetricDataByNameResultItem[]>\n}\n\ntype AppConfig = {\n  title?: string\n  scrapeInterval?: number\n}\n\ntype AppActions = {\n  openDiagnosis(id: string): void\n  openHostMonitoring(id: string): void\n}\n\nexport type AppCtxValue = {\n  // we use ctxId to be a part of queryKey for react-query,\n  // to differ same requests from different clusters, so this value can be clusterId, or other unique value\n  ctxId: string\n  api: AppApi\n  cfg: AppConfig\n  actions: AppActions\n}\n\nexport const AppContext = createContext<AppCtxValue | null>(null)\n\nexport const useAppContext = () => {\n  const context = useContext(AppContext)\n\n  if (!context) {\n    throw new Error(\"Metric AppContext must be used within a provider\")\n  }\n\n  return context\n}\n\n////////////////////////////////\n\nexport function AppProvider(props: {\n  children: React.ReactNode\n  ctxValue: AppCtxValue\n}) {\n  return (\n    <AppContext.Provider value={props.ctxValue}>\n      {props.children}\n    </AppContext.Provider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/index.ts",
    "content": "export * from \"./ctx\"\nexport * from \"./utils/type\"\n\nexport * from \"./pages/normal\"\nexport * from \"./pages/azores-overview\"\nexport * from \"./pages/azores-host\"\nexport * from \"./pages/azores-cluster-overview\"\nexport * from \"./pages/azores-cluster\"\n\n// i18n\nimport \"./locales\"\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/locales/en.json",
    "content": "{\n  \"__namespace__\": \"metric\",\n  \"__comment__\": \"this file can be updated by running `pnpm gen:locales` command\",\n  \"groups.advanced\": \"Advanced\",\n  \"groups.basic\": \"Basic\",\n  \"panels.analyze_statistics\": \"Analyze Statistics\",\n  \"panels.cluster_top\": \"Top 5 SQL Performance\",\n  \"panels.connections\": \"Connections\",\n  \"panels.database_time\": \"Database Time\",\n  \"panels.high_disk_io_usage\": \"High Disk I/O Usage\",\n  \"panels.host\": \"Host\",\n  \"panels.host_top\": \"Top 5 Host Performance\",\n  \"panels.hosts_resource\": \"Hosts Resource\",\n  \"panels.hotspot\": \"Hotspot\",\n  \"panels.increase_of_rw_latency\": \"Increased Read and Write Latency\",\n  \"panels.instance_top\": \"Top 5 Node Utilization\",\n  \"panels.io_env\": \"IO & Env\",\n  \"panels.load_analysis\": \"Load Analysis\",\n  \"panels.lock_conflicts\": \"Lock Conflicts\",\n  \"panels.optimizer_behavior\": \"Optimizer Behavior\",\n  \"panels.pd\": \"PD\",\n  \"panels.pd_leader\": \"PD Leader\",\n  \"panels.performance\": \"Performance\",\n  \"panels.process\": \"Process\",\n  \"panels.raft_log\": \"Raft Log\",\n  \"panels.resource\": \"Resource\",\n  \"panels.sql_load_profile\": \"SQL Load Profile\",\n  \"panels.sql_tuning\": \"SQL Tuning\",\n  \"panels.throughput\": \"Throughput\",\n  \"panels.tidb\": \"TiDB\",\n  \"panels.tidb_components_resource\": \"TiDB Componets Resource\",\n  \"panels.tidb_oom\": \"TiDB OOM\",\n  \"panels.tiflash\": \"TiFlash\",\n  \"panels.tiflash_related\": \"TiFlash Related\",\n  \"panels.tikv\": \"TiKV\",\n  \"panels.top_down_duration\": \"Top-down Duration\",\n  \"panels.transaction\": \"Transaction\",\n  \"panels.write_conflicts\": \"Write Conflicts\",\n  \"time_range.day_other\": \"{{count}} days\",\n  \"time_range.hour_one\": \"{{count}} hr\",\n  \"time_range.hour_other\": \"{{count}} hrs\"\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/locales/index.ts",
    "content": "import { addLangsLocales } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n\nimport en from \"./en.json\"\nimport zh from \"./zh.json\"\n\naddLangsLocales({ en, zh })\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/locales/zh.json",
    "content": "{\n  \"__namespace__\": \"metric\",\n  \"__comment__\": \"this file can be updated by running `pnpm gen:locales` command\",\n  \"groups.advanced\": \"高级\",\n  \"groups.basic\": \"基础\",\n  \"panels.analyze_statistics\": \"分析统计\",\n  \"panels.cluster_top\": \"前 5 SQL 性能\",\n  \"panels.connections\": \"应用链接\",\n  \"panels.database_time\": \"数据库时间\",\n  \"panels.high_disk_io_usage\": \"磁盘 IO 过高\",\n  \"panels.host\": \"主机\",\n  \"panels.host_top\": \"前 5 主机性能\",\n  \"panels.hosts_resource\": \"集群主机资源\",\n  \"panels.hotspot\": \"热点\",\n  \"panels.increase_of_rw_latency\": \"读写延时增加\",\n  \"panels.instance_top\": \"前 5 节点利用率\",\n  \"panels.io_env\": \"IO & Env\",\n  \"panels.load_analysis\": \"负载分析\",\n  \"panels.lock_conflicts\": \"锁冲突\",\n  \"panels.optimizer_behavior\": \"优化器行为\",\n  \"panels.pd\": \"PD\",\n  \"panels.pd_leader\": \"PD Leader\",\n  \"panels.performance\": \"性能\",\n  \"panels.process\": \"进程\",\n  \"panels.raft_log\": \"Raft Log\",\n  \"panels.resource\": \"资源\",\n  \"panels.sql_load_profile\": \"SQL 负载\",\n  \"panels.sql_tuning\": \"SQL 调优\",\n  \"panels.throughput\": \"吞吐量\",\n  \"panels.tidb\": \"TiDB\",\n  \"panels.tidb_components_resource\": \"TiDB 组件资源\",\n  \"panels.tidb_oom\": \"TiDB OOM\",\n  \"panels.tiflash\": \"TiFlash\",\n  \"panels.tiflash_related\": \"TiFlash 相关\",\n  \"panels.tikv\": \"TiKV\",\n  \"panels.top_down_duration\": \"耗时分解\",\n  \"panels.transaction\": \"事务\",\n  \"panels.write_conflicts\": \"写冲突\",\n  \"time_range.day_other\": \"{{count}} 天\",\n  \"time_range.hour_one\": \"{{count}} 小时\",\n  \"time_range.hour_other\": \"{{count}} 小时\",\n  \"All Instances\": \"所有实例\",\n  \"Core Metrics\": \"核心指标\",\n  \"Drill Down Analysis\": \"下钻分析\",\n  \"Drill down analysis\": \"下钻分析\",\n  \"Hide\": \"隐藏\",\n  \"Host Monitoring\": \"主机监控\",\n  \"Prometheus Address\": \"Prometheus 地址\",\n  \"Refresh\": \"刷新\",\n  \"SQL Diagnosis\": \"SQL 诊断\",\n  \"Select Instance\": \"选择实例\",\n  \"The rank may have a delay of up to {{n}} minutes\": \"排名可能会有 {{n}} 分钟的延迟\",\n  \"Troubleshoot High Disk I/O Usage in TiDB\": \"TiDB 磁盘 I/O 过高的处理办法\",\n  \"Troubleshoot Hotspot Issues\": \"TiDB 热点问题处理\",\n  \"Troubleshoot Increased Read and Write Latency\": \"读写延迟增加问题排查\",\n  \"Troubleshoot Lock Conflicts\": \"TiDB 锁冲突问题处理\",\n  \"Troubleshoot TiDB OOM Issues\": \"TiDB OOM 故障排查\",\n  \"Troubleshoot Write Conflicts in Optimistic Transactions\": \"乐观事务模型下写写冲突问题排查\",\n  \"Troubleshooting guide\": \"故障排查指南\"\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-cluster/filters.tsx",
    "content": "import {\n  AutoRefreshButton,\n  AutoRefreshButtonRef,\n  DEFAULT_AUTO_REFRESH_SECONDS,\n  TimeRangePicker,\n} from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport {\n  toURLTimeRange,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Anchor, Box, Group, SegmentedControl } from \"@tidbcloud/uikit\"\nimport { dayjs } from \"@tidbcloud/uikit/utils\"\nimport { capitalize } from \"lodash-es\"\nimport { useMemo, useRef, useState } from \"react\"\n\nimport { ChartsSelect } from \"../../components/charts-select\"\nimport { useAppContext } from \"../../ctx\"\nimport { useMetricsUrlState } from \"../../shared-state/url-state\"\nimport { QUICK_RANGES } from \"../../utils/constants\"\n\nconst GROUPS = [\"basic\", \"advanced\"]\n\n// @ts-expect-error @typescript-eslint/no-unused-vars\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction useLocales() {\n  const { tk } = useTn(\"metric\")\n  // for gogocode to scan and generate en.json before build\n  tk(\"groups.basic\", \"Basic\")\n  tk(\"groups.advanced\", \"Advanced\")\n}\n\n// @todo: maybe it should be named Toolbar instead of Filters\nexport function Filters() {\n  const { tk, tt } = useTn(\"metric\")\n  const ctx = useAppContext()\n  const { panel, setQueryParams, timeRange, setTimeRange, setRefresh } =\n    useMetricsUrlState()\n  const tabs = GROUPS?.map((p) => ({\n    label: tk(`groups.${p}`, capitalize(p)),\n    value: p,\n  }))\n\n  const [autoRefreshValue, setAutoRefreshValue] = useState<number>(\n    DEFAULT_AUTO_REFRESH_SECONDS,\n  )\n  const autoRefreshRef = useRef<AutoRefreshButtonRef>(null)\n  const [loading, setLoading] = useState(false)\n\n  const diagnosisLinkId = useMemo(() => {\n    const { from, to } = toURLTimeRange(timeRange)\n    return `${from},${to}`\n  }, [timeRange])\n\n  function handlePanelChange(newPanel: string) {\n    autoRefreshRef.current?.refresh()\n    setQueryParams({\n      panel: newPanel || undefined,\n      refresh: new Date().valueOf().toString(),\n    })\n  }\n\n  function handleRefresh() {\n    setLoading(true)\n    setTimeout(() => {\n      setLoading(false)\n    }, 1000)\n    setRefresh()\n  }\n\n  const panelSwitch = tabs && tabs.length > 0 && (\n    <SegmentedControl\n      withItemsBorders={false}\n      data={tabs}\n      value={panel || tabs[0].value}\n      onChange={handlePanelChange}\n    />\n  )\n\n  const diagnosisLink = (\n    <Anchor\n      onClick={() => {\n        ctx.actions.openDiagnosis(diagnosisLinkId)\n      }}\n    >\n      {tt(\"SQL Diagnosis\")}\n    </Anchor>\n  )\n\n  const chartsSelect = <ChartsSelect />\n\n  const timeRangePicker = (\n    <TimeRangePicker\n      value={timeRange}\n      onChange={(v) => {\n        setTimeRange(v)\n      }}\n      quickRanges={QUICK_RANGES}\n      minDateTime={() =>\n        dayjs()\n          .subtract(QUICK_RANGES[QUICK_RANGES.length - 1], \"seconds\")\n          .startOf(\"d\")\n          .toDate()\n      }\n      maxDateTime={() => dayjs().endOf(\"d\").toDate()}\n    />\n  )\n\n  const autoRefreshButton = (\n    <AutoRefreshButton\n      ref={autoRefreshRef}\n      autoRefreshValue={autoRefreshValue}\n      onAutoRefreshChange={setAutoRefreshValue}\n      onRefresh={handleRefresh}\n      loading={loading}\n    />\n  )\n\n  return (\n    <Group>\n      {panelSwitch}\n\n      <Box sx={{ flexGrow: 1 }} />\n\n      {diagnosisLink}\n      {chartsSelect}\n      {timeRangePicker}\n      {autoRefreshButton}\n    </Group>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-cluster/index.tsx",
    "content": "import { Stack } from \"@tidbcloud/uikit\"\n\nimport { LoadingCard } from \"../../components/loading-card\"\nimport { useCurPanelConfigsData } from \"../../utils/use-data\"\nimport { AzoresMetricDetailDrawer } from \"../azores-metric-detail/drawer\"\n\nimport { Filters } from \"./filters\"\nimport { AzoresClusterMetricsPanel } from \"./panel\"\n// import { AzoresMetricDetailModal } from \"../azores-metric-detail/modal\"\n\nexport function AzoresClusterMetricsPage() {\n  const { panelConfigData, isLoading } = useCurPanelConfigsData()\n\n  if (isLoading) {\n    return <LoadingCard />\n  }\n\n  return (\n    <Stack>\n      <Filters />\n\n      {panelConfigData\n        ?.filter((p) => p.charts.length > 0)\n        .map((panel) => {\n          return (\n            <AzoresClusterMetricsPanel key={panel.category} config={panel} />\n          )\n        })}\n\n      {/* notice: don't put `AzoresMetricModal` in the panel component, all panels should share one modal */}\n      {/* <AzoresMetricDetailModal /> */}\n      <AzoresMetricDetailDrawer />\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-cluster/panel.tsx",
    "content": "import {\n  TimeRangeValue,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Anchor, Box, Card, Group, Stack, Typography } from \"@tidbcloud/uikit\"\nimport { IconLinkExternal01 } from \"@tidbcloud/uikit/icons\"\n\nimport { ChartBody } from \"../../components/chart-body\"\nimport { ChartHeader } from \"../../components/chart-header\"\nimport { useChartsSelectState } from \"../../shared-state/memory-state\"\nimport { useMetricsUrlState } from \"../../shared-state/url-state\"\nimport { SinglePanelConfig } from \"../../utils/type\"\n\n// @ts-expect-error @typescript-eslint/no-unused-vars\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction useLocales() {\n  const { tk } = useTn(\"metric\")\n  // used for gogocode to scan and generate en.json before build\n  // basic\n  tk(\"panels.database_time\", \"Database Time\")\n  tk(\"panels.connections\", \"Connections\")\n  tk(\"panels.sql_load_profile\", \"SQL Load Profile\")\n  tk(\"panels.top_down_duration\", \"Top-down Duration\")\n  tk(\"panels.transaction\", \"Transaction\")\n  tk(\"panels.tidb_components_resource\", \"TiDB Componets Resource\")\n  tk(\"panels.hosts_resource\", \"Hosts Resource\")\n\n  // advanced - deprecated\n  tk(\"panels.load_analysis\", \"Load Analysis\")\n  tk(\"panels.sql_tuning\", \"SQL Tuning\")\n  tk(\"panels.optimizer_behavior\", \"Optimizer Behavior\")\n  tk(\"panels.pd_leader\", \"PD Leader\")\n  tk(\"panels.io_env\", \"IO & Env\")\n  tk(\"panels.analyze_statistics\", \"Analyze Statistics\")\n  tk(\"panels.tiflash_related\", \"TiFlash Related\")\n  tk(\"panels.raft_log\", \"Raft Log\")\n\n  // advanced - new\n  tk(\"panels.high_disk_io_usage\", \"High Disk I/O Usage\")\n  tk(\"panels.hotspot\", \"Hotspot\")\n  tk(\"panels.increase_of_rw_latency\", \"Increased Read and Write Latency\")\n  tk(\"panels.lock_conflicts\", \"Lock Conflicts\")\n  tk(\"panels.tidb_oom\", \"TiDB OOM\")\n  tk(\"panels.write_conflicts\", \"Write Conflicts\")\n\n  // deprecated\n  tk(\"panels.throughput\", \"Throughput\")\n  tk(\"panels.host\", \"Host\")\n  tk(\"panels.tidb\", \"TiDB\")\n  tk(\"panels.tikv\", \"TiKV\")\n  tk(\"panels.pd\", \"PD\")\n  tk(\"panels.tiflash\", \"TiFlash\")\n}\n\nfunction useTroubleShootingLinks(): {\n  [key: string]: { label: string; link: string }[]\n} {\n  const { tt } = useTn(\"metric\")\n  return {\n    high_disk_io_usage: [\n      {\n        label: tt(\"Troubleshoot High Disk I/O Usage in TiDB\"),\n        link: \"https://docs.pingcap.com/tidb/stable/troubleshoot-high-disk-io\",\n      },\n    ],\n    hotspot: [\n      {\n        label: tt(\"Troubleshoot Hotspot Issues\"),\n        link: \"https://docs.pingcap.com/tidb/stable/troubleshoot-hot-spot-issues\",\n      },\n    ],\n    increase_of_rw_latency: [\n      {\n        label: tt(\"Troubleshoot Increased Read and Write Latency\"),\n        link: \"https://docs.pingcap.com/tidb/stable/troubleshoot-cpu-issues\",\n      },\n    ],\n    lock_conflicts: [\n      {\n        label: tt(\"Troubleshoot Lock Conflicts\"),\n        link: \"https://docs.pingcap.com/tidb/stable/troubleshoot-lock-conflicts\",\n      },\n      {\n        label: tt(\"Troubleshoot Write Conflicts in Optimistic Transactions\"),\n        link: \"https://docs.pingcap.com/tidb/stable/troubleshoot-write-conflicts\",\n      },\n    ],\n    tidb_oom: [\n      {\n        label: tt(\"Troubleshoot TiDB OOM Issues\"),\n        link: \"https://docs.pingcap.com/tidb/stable/troubleshoot-tidb-oom\",\n      },\n    ],\n    write_conflicts: [\n      {\n        label: tt(\"Troubleshoot Lock Conflicts\"),\n        link: \"https://docs.pingcap.com/tidb/stable/troubleshoot-lock-conflicts\",\n      },\n      {\n        label: tt(\"Troubleshoot Write Conflicts in Optimistic Transactions\"),\n        link: \"https://docs.pingcap.com/tidb/stable/troubleshoot-write-conflicts\",\n      },\n    ],\n  }\n}\n\nexport function AzoresClusterMetricsPanel({\n  config,\n}: {\n  config: SinglePanelConfig\n}) {\n  const { tk, tt } = useTn(\"metric\")\n  const { timeRange, setTimeRange } = useMetricsUrlState()\n  const hiddenCharts = useChartsSelectState((s) => s.hiddenCharts)\n\n  const visibleCharts = config.charts.filter((c) => {\n    return !hiddenCharts.includes(c.metricName)\n  })\n\n  function handleTimeRangeChange(v: TimeRangeValue) {\n    setTimeRange({ type: \"absolute\", value: v })\n  }\n\n  const manuals = useTroubleShootingLinks()[config.category]\n\n  if (visibleCharts.length === 0) {\n    return null\n  }\n\n  return (\n    <Box>\n      <Stack gap={0}>\n        <Typography fw={300} fz={24}>\n          {tk(`panels.${config.category}`, config.category)}\n        </Typography>\n        {manuals && (\n          <Group mb={4} gap={4}>\n            <Typography>\n              {tt(\"Troubleshooting guide\")}\n              {\":\"}\n            </Typography>\n            {manuals.map((m, idx) => (\n              <Group key={m.label} gap={2}>\n                {idx > 0 && \" , \"}\n                <Anchor\n                  href={m.link}\n                  target=\"_blank\"\n                  sx={{ display: \"flex\", alignItems: \"center\", gap: 2 }}\n                >\n                  {m.label}\n                  <IconLinkExternal01 />\n                </Anchor>\n              </Group>\n            ))}\n          </Group>\n        )}\n      </Stack>\n\n      <Box\n        style={{\n          display: \"grid\",\n          gap: \"1rem\",\n          gridTemplateColumns: \"repeat(auto-fit, minmax(450px, 1fr))\",\n        }}\n      >\n        {visibleCharts.map((c, idx) => (\n          <Card key={c.title + idx} p={16} pb={10} bg=\"carbon.0\" shadow=\"none\">\n            <ChartHeader\n              title={c.title}\n              enableDrillDown={true}\n              showMoreActions={true}\n              showHide={true}\n              config={c}\n              timeRange={timeRange}\n            />\n            <ChartBody\n              config={c}\n              timeRange={timeRange}\n              onTimeRangeChange={handleTimeRangeChange}\n            />\n          </Card>\n        ))}\n      </Box>\n    </Box>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-cluster-overview/index.tsx",
    "content": "import { Stack } from \"@tidbcloud/uikit\"\n\nimport { useMetricQueriesConfigData } from \"../../utils/use-data\"\n\nimport { AzoresClusterOverviewMetricsPanel } from \"./panel\"\nimport { LoadingCard } from \"../../components/loading-card\"\n\nexport function AzoresClusterOverviewMetricsPage() {\n  const { data: panelConfigData, isLoading } = useMetricQueriesConfigData(\n    \"azores-cluster-overview\",\n  )\n\n  if (isLoading) {\n    return (\n      <LoadingCard />\n    )\n  }\n\n  return (\n    <Stack>\n      {panelConfigData\n        ?.filter((p) => p.charts.length > 0)\n        .map((panel) => {\n          return (\n            <AzoresClusterOverviewMetricsPanel\n              key={panel.category}\n              config={panel}\n            />\n          )\n        })}\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-cluster-overview/panel.tsx",
    "content": "import {\n  RelativeTimeRange,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  Box,\n  Card,\n  Group,\n  SegmentedControl,\n  Stack,\n  Typography,\n} from \"@tidbcloud/uikit\"\nimport { useMemo, useState } from \"react\"\n\nimport { ChartBody } from \"../../components/chart-body\"\nimport { ChartHeader } from \"../../components/chart-header\"\nimport { SinglePanelConfig } from \"../../utils/type\"\n\nexport function AzoresClusterOverviewMetricsPanel({\n  config,\n}: {\n  config: SinglePanelConfig\n}) {\n  const { tk, tt } = useTn(\"metric\")\n\n  const timeRangeOptions = useMemo(() => {\n    return [\n      {\n        label: tk(\"time_range.hour\", \"{{count}} hr\", { count: 1 }),\n        value: 60 * 60 + \"\",\n      },\n      {\n        label: tk(\"time_range.hour\", \"{{count}} hrs\", { count: 24 }),\n        value: 24 * 60 * 60 + \"\",\n      },\n      {\n        label: tk(\"time_range.day\", \"{{count}} days\", { count: 7 }),\n        value: 7 * 24 * 60 * 60 + \"\",\n      },\n    ]\n  }, [tk])\n  const [timeRange, setTimeRange] = useState<RelativeTimeRange>({\n    type: \"relative\",\n    value: parseInt(timeRangeOptions[0].value),\n  })\n\n  return (\n    <Stack gap={8}>\n      <Group>\n        <Typography variant=\"title-lg\">{tt(\"Core Metrics\")}</Typography>\n        <Group ml=\"auto\">\n          <SegmentedControl\n            size=\"xs\"\n            withItemsBorders={false}\n            data={timeRangeOptions}\n            onChange={(v) => {\n              setTimeRange({ type: \"relative\", value: parseInt(v) })\n            }}\n          />\n        </Group>\n      </Group>\n\n      <Box\n        style={{\n          display: \"grid\",\n          gap: \"1rem\",\n          gridTemplateColumns: \"repeat(auto-fit, minmax(450px, 1fr))\",\n        }}\n      >\n        {config.charts.map((c, idx) => (\n          <Card key={c.title + idx} p={16} pb={10} bg=\"carbon.0\" shadow=\"none\">\n            <ChartHeader title={c.title} config={c} showMoreActions={true} />\n            <ChartBody config={c} timeRange={timeRange} />\n          </Card>\n        ))}\n      </Box>\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-host/filters.tsx",
    "content": "import {\n  AutoRefreshButton,\n  AutoRefreshButtonRef,\n  DEFAULT_AUTO_REFRESH_SECONDS,\n  TimeRangePicker,\n} from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { Group } from \"@tidbcloud/uikit\"\nimport { dayjs } from \"@tidbcloud/uikit/utils\"\nimport { useRef, useState } from \"react\"\n\nimport { useMetricsUrlState } from \"../../shared-state/url-state\"\nimport { QUICK_RANGES } from \"../../utils/constants\"\n\n// @todo: maybe it should be named Toolbar instead of Filters\nexport function Filters() {\n  const { timeRange, setTimeRange, setRefresh } = useMetricsUrlState()\n  const [autoRefreshValue, setAutoRefreshValue] = useState<number>(\n    DEFAULT_AUTO_REFRESH_SECONDS,\n  )\n  const autoRefreshRef = useRef<AutoRefreshButtonRef>(null)\n  const [loading, setLoading] = useState(false)\n\n  function handleRefresh() {\n    setLoading(true)\n    setTimeout(() => {\n      setLoading(false)\n    }, 1000)\n    setRefresh()\n  }\n\n  const timeRangePicker = (\n    <TimeRangePicker\n      value={timeRange}\n      onChange={(v) => {\n        setTimeRange(v)\n      }}\n      quickRanges={QUICK_RANGES}\n      minDateTime={() =>\n        dayjs()\n          .subtract(QUICK_RANGES[QUICK_RANGES.length - 1], \"seconds\")\n          .startOf(\"d\")\n          .toDate()\n      }\n      maxDateTime={() => dayjs().endOf(\"d\").toDate()}\n    />\n  )\n\n  const autoRefreshButton = (\n    <AutoRefreshButton\n      ref={autoRefreshRef}\n      autoRefreshValue={autoRefreshValue}\n      onAutoRefreshChange={setAutoRefreshValue}\n      onRefresh={handleRefresh}\n      loading={loading}\n    />\n  )\n\n  return (\n    <Group ml=\"auto\">\n      {timeRangePicker}\n      {autoRefreshButton}\n    </Group>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-host/index.tsx",
    "content": "import { Stack } from \"@tidbcloud/uikit\"\n\nimport { LoadingCard } from \"../../components/loading-card\"\nimport { useMetricQueriesConfigData } from \"../../utils/use-data\"\n\nimport { Filters } from \"./filters\"\nimport { AzoresHostMetricsPanel } from \"./panel\"\n\nexport function AzoresHostMetricsPage() {\n  const { data: panelConfigData, isLoading } =\n    useMetricQueriesConfigData(\"azores-host\")\n\n  if (isLoading) {\n    return <LoadingCard />\n  }\n\n  return (\n    <Stack>\n      <Filters />\n\n      {panelConfigData\n        ?.filter((p) => p.charts.length > 0)\n        .map((panel) => {\n          return <AzoresHostMetricsPanel key={panel.category} config={panel} />\n        })}\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-host/panel.tsx",
    "content": "import {\n  TimeRangeValue,\n  useTimeRangeUrlState,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Box, Card, Group, Stack, Typography } from \"@tidbcloud/uikit\"\n\nimport { ChartBody } from \"../../components/chart-body\"\nimport { ChartHeader } from \"../../components/chart-header\"\nimport { SinglePanelConfig } from \"../../utils/type\"\n\n// @ts-expect-error @typescript-eslint/no-unused-vars\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction useLocales() {\n  const { tk } = useTn(\"metric\")\n  // used for gogocode to scan and generate en.json before build\n  tk(\"panels.performance\", \"Performance\")\n  tk(\"panels.resource\", \"Resource\")\n  tk(\"panels.process\", \"Process\")\n}\n\nexport function AzoresHostMetricsPanel({\n  config,\n}: {\n  config: SinglePanelConfig\n}) {\n  const { tk } = useTn(\"metric\")\n  const { timeRange, setTimeRange } = useTimeRangeUrlState()\n\n  function handleTimeRangeChange(v: TimeRangeValue) {\n    setTimeRange({ type: \"absolute\", value: v })\n  }\n\n  return (\n    <Stack gap={8}>\n      <Group>\n        <Typography variant=\"title-lg\">\n          {tk(`panels.${config.category}`)}\n        </Typography>\n      </Group>\n\n      <Box\n        style={{\n          display: \"grid\",\n          gap: \"1rem\",\n          gridTemplateColumns: \"repeat(auto-fit, minmax(450px, 1fr))\",\n        }}\n      >\n        {config.charts.map((c, idx) => (\n          <Card key={c.title + idx} p={16} pb={10} bg=\"carbon.0\" shadow=\"none\">\n            <ChartHeader title={c.title} config={c} />\n            <ChartBody\n              config={c}\n              timeRange={timeRange}\n              onTimeRangeChange={handleTimeRangeChange}\n            />\n          </Card>\n        ))}\n      </Box>\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-metric-detail/body.tsx",
    "content": "import {\n  TimeRangeValue,\n  toURLTimeRange,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Anchor, Box, Card, Stack } from \"@tidbcloud/uikit\"\nimport { useMemo } from \"react\"\n\nimport { ChartBody } from \"../../components/chart-body\"\nimport { ChartHeader } from \"../../components/chart-header\"\nimport { useAppContext } from \"../../ctx\"\nimport { useChartState } from \"../../shared-state/memory-state\"\n\nimport { Filters } from \"./filters\"\n\nexport function AzoresMetricDetailBody() {\n  const ctx = useAppContext()\n  const selectedChart = useChartState((state) => state.selectedChart)\n  const timeRange = useChartState((state) => state.timeRange)\n  const setTimeRange = useChartState((state) => state.setTimeRange)\n  const selectedInstance = useChartState((state) => state.selectedInstance)\n  const { tt } = useTn(\"metric\")\n\n  const diagnosisLinkId = useMemo(() => {\n    const { from, to } = toURLTimeRange(timeRange)\n    return `${from},${to}`\n  }, [timeRange])\n\n  function handleTimeRangeChange(v: TimeRangeValue) {\n    setTimeRange({ type: \"absolute\", value: v })\n  }\n\n  if (!selectedChart) {\n    return null\n  }\n\n  return (\n    <Stack gap={\"xl\"}>\n      <Filters />\n\n      <Box miw={800}>\n        <Stack>\n          <Card p={16} pb={10} bg=\"carbon.0\" shadow=\"none\">\n            <ChartHeader title={tt(\"All Instances\")} config={selectedChart}>\n              <Anchor\n                onClick={() => ctx.actions.openDiagnosis(diagnosisLinkId)}\n              >\n                {tt(\"SQL Diagnosis\")}\n              </Anchor>\n            </ChartHeader>\n            <ChartBody\n              config={selectedChart}\n              timeRange={timeRange}\n              onTimeRangeChange={handleTimeRangeChange}\n            />\n          </Card>\n\n          {selectedInstance && (\n            <Card p={16} pb={10} bg=\"carbon.0\" shadow=\"none\">\n              <ChartHeader title={selectedInstance} config={selectedChart}>\n                <Anchor\n                  onClick={() =>\n                    ctx.actions.openHostMonitoring(selectedInstance)\n                  }\n                >\n                  {tt(\"Host Monitoring\")}\n                </Anchor>\n              </ChartHeader>\n              <ChartBody\n                config={selectedChart}\n                timeRange={timeRange}\n                onTimeRangeChange={handleTimeRangeChange}\n                labelValue={`instance=\"${selectedInstance}\"`}\n              />\n            </Card>\n          )}\n        </Stack>\n      </Box>\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-metric-detail/drawer.tsx",
    "content": "import { ActionDrawer } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n\nimport { useChartState } from \"../../shared-state/memory-state\"\n\nimport { AzoresMetricDetailBody } from \"./body\"\n\nexport function AzoresMetricDetailDrawer() {\n  const selectedChart = useChartState((state) => state.selectedChart)\n\n  const reset = useChartState((state) => state.reset)\n  const { tt } = useTn(\"metric\")\n\n  if (!selectedChart) {\n    return null\n  }\n\n  return (\n    <ActionDrawer\n      position=\"right\"\n      withinPortal\n      size=\"auto\"\n      title={`${selectedChart.title} ${tt(\"Drill Down Analysis\")}`}\n      opened={true}\n      onClose={reset}\n    >\n      <ActionDrawer.Body>\n        <AzoresMetricDetailBody />\n      </ActionDrawer.Body>\n    </ActionDrawer>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-metric-detail/filters.tsx",
    "content": "import { TimeRangePicker } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Box, Group, Select } from \"@tidbcloud/uikit\"\nimport { dayjs } from \"@tidbcloud/uikit/utils\"\n\nimport { useChartState } from \"../../shared-state/memory-state\"\nimport { QUICK_RANGES } from \"../../utils/constants\"\nimport { useMetricLabelValuesData } from \"../../utils/use-data\"\n\nexport function Filters() {\n  const { tt } = useTn(\"metric\")\n  const timeRange = useChartState((state) => state.timeRange)\n  const setTimeRange = useChartState((state) => state.setTimeRange)\n  const selectedChart = useChartState((state) => state.selectedChart)\n  const setSelectedInstance = useChartState(\n    (state) => state.setSelectedInstance,\n  )\n\n  const { data: instancesData } = useMetricLabelValuesData(\n    selectedChart?.metricName || \"\",\n    \"instance\",\n    timeRange,\n  )\n\n  const instanceSelect = (\n    <Select\n      w={280}\n      comboboxProps={{ shadow: \"md\" }}\n      placeholder={tt(\"Select Instance\")}\n      data={instancesData || []}\n      clearable\n      onChange={(v) => {\n        setSelectedInstance(v ? v : undefined)\n      }}\n    />\n  )\n\n  const timeRangePicker = (\n    <TimeRangePicker\n      value={timeRange}\n      onChange={(v) => {\n        setTimeRange(v)\n      }}\n      quickRanges={QUICK_RANGES}\n      minDateTime={() =>\n        dayjs()\n          .subtract(QUICK_RANGES[QUICK_RANGES.length - 1], \"seconds\")\n          .startOf(\"d\")\n          .toDate()\n      }\n      maxDateTime={() => dayjs().endOf(\"d\").toDate()}\n    />\n  )\n\n  return (\n    <Group>\n      {instanceSelect}\n\n      <Box sx={{ flexGrow: 1 }} />\n\n      {timeRangePicker}\n    </Group>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-metric-detail/modal.tsx",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Modal } from \"@tidbcloud/uikit\"\n\nimport { useChartState } from \"../../shared-state/memory-state\"\n\nimport { AzoresMetricDetailBody } from \"./body\"\n\nexport function AzoresMetricDetailModal() {\n  const selectedChart = useChartState((state) => state.selectedChart)\n\n  const reset = useChartState((state) => state.reset)\n  const { tt } = useTn(\"metric\")\n\n  if (!selectedChart) {\n    return null\n  }\n\n  return (\n    <Modal\n      centered={false}\n      withinPortal\n      overlayProps={{ backgroundOpacity: 0.3 }}\n      size=\"auto\"\n      title={`${selectedChart.title} ${tt(\"Drill Down Analysis\")}`}\n      opened={true}\n      onClose={reset}\n    >\n      <AzoresMetricDetailBody />\n    </Modal>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-overview/index.tsx",
    "content": "import { Stack } from \"@tidbcloud/uikit\"\n\nimport { useMetricQueriesConfigData } from \"../../utils/use-data\"\n\nimport { AzoresOverviewMetricsPanel } from \"./panel\"\nimport { LoadingCard } from \"../../components/loading-card\"\n\nexport function AzoresOverviewMetricsPage() {\n  const { data: panelConfigData, isLoading } =\n    useMetricQueriesConfigData(\"azores-overview\")\n\n  if (isLoading) {\n    return (\n      <LoadingCard />\n    )\n  }\n\n  return (\n    <Stack>\n      {panelConfigData\n        ?.filter((p) => p.charts.length > 0)\n        .map((panel) => {\n          return (\n            <AzoresOverviewMetricsPanel key={panel.category} config={panel} />\n          )\n        })}\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/azores-overview/panel.tsx",
    "content": "import {\n  RelativeTimeRange,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  Box,\n  Card,\n  Group,\n  SegmentedControl,\n  Stack,\n  Tooltip,\n  Typography,\n} from \"@tidbcloud/uikit\"\nimport { IconInfoCircle } from \"@tidbcloud/uikit/icons\"\nimport { useMemo, useState } from \"react\"\n\nimport { ChartBody } from \"../../components/chart-body\"\nimport { ChartHeader } from \"../../components/chart-header\"\nimport { SinglePanelConfig } from \"../../utils/type\"\nimport { useMetricConfigData } from \"../../utils/use-data\"\n\n// @ts-expect-error @typescript-eslint/no-unused-vars\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction useLocales() {\n  const { tk } = useTn(\"metric\")\n  // used for gogocode to scan and generate en.json before build\n  tk(\"panels.instance_top\", \"Top 5 Node Utilization\")\n  tk(\"panels.host_top\", \"Top 5 Host Performance\")\n  tk(\"panels.cluster_top\", \"Top 5 SQL Performance\")\n}\n\nexport function AzoresOverviewMetricsPanel({\n  config,\n}: {\n  config: SinglePanelConfig\n}) {\n  const { tk, tt } = useTn(\"metric\")\n  const timeRangeOptions = useMemo(() => {\n    return [\n      {\n        label: tk(\"time_range.hour\", \"{{count}} hr\", { count: 1 }),\n        value: 60 * 60 + \"\",\n      },\n      {\n        label: tk(\"time_range.hour\", \"{{count}} hrs\", { count: 24 }),\n        value: 24 * 60 * 60 + \"\",\n      },\n      {\n        label: tk(\"time_range.day\", \"{{count}} days\", { count: 7 }),\n        value: 7 * 24 * 60 * 60 + \"\",\n      },\n    ]\n  }, [tk])\n  const [timeRange, setTimeRange] = useState<RelativeTimeRange>({\n    type: \"relative\",\n    value: parseInt(timeRangeOptions[0].value),\n  })\n  const { data: metricConfigData } = useMetricConfigData()\n\n  return (\n    <Stack gap={8}>\n      <Group gap=\"xs\">\n        <Typography variant=\"title-lg\">\n          {tk(`panels.${config.category}`)}\n        </Typography>\n        {!!metricConfigData?.delaySec && (\n          <Tooltip\n            label={tt(\"The rank may have a delay of up to {{n}} minutes\", {\n              n: Math.ceil((metricConfigData?.delaySec || 0) / 60),\n            })}\n          >\n            <IconInfoCircle />\n          </Tooltip>\n        )}\n        <Group ml=\"auto\">\n          <SegmentedControl\n            size=\"xs\"\n            withItemsBorders={false}\n            data={timeRangeOptions}\n            onChange={(v) => {\n              setTimeRange({ type: \"relative\", value: parseInt(v) })\n            }}\n          />\n        </Group>\n      </Group>\n\n      <Box\n        style={{\n          display: \"grid\",\n          gap: \"1rem\",\n          gridTemplateColumns: \"repeat(auto-fit, minmax(450px, 1fr))\",\n        }}\n      >\n        {config.charts.map((c, idx) => (\n          <Card key={c.title + idx} p={16} pb={10} bg=\"carbon.0\" shadow=\"none\">\n            <ChartHeader title={c.title} config={c} />\n            <ChartBody config={c} timeRange={timeRange} />\n          </Card>\n        ))}\n      </Box>\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/normal/chart-card.tsx",
    "content": "import {\n  SeriesChart,\n  SeriesData,\n} from \"@pingcap-incubator/tidb-dashboard-lib-charts\"\nimport {\n  PromResultItem,\n  TransformNullValue,\n  calcPromQueryStep,\n  toTimeRangeValue,\n  transformPromResultItem,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  Box,\n  Card,\n  Flex,\n  Group,\n  Loader,\n  Typography,\n  useComputedColorScheme,\n} from \"@tidbcloud/uikit\"\nimport { useEffect, useMemo, useRef } from \"react\"\n\nimport { useAppContext } from \"../../ctx\"\nimport { useMetricsUrlState } from \"../../shared-state/url-state\"\nimport { SingleChartConfig, SingleQueryConfig } from \"../../utils/type\"\nimport { useMetricDataByPromQLs } from \"../../utils/use-data\"\n\nexport function transformData(\n  items: PromResultItem[],\n  qIdx: number,\n  query: SingleQueryConfig,\n  nullValue?: TransformNullValue,\n): SeriesData[] {\n  return items.map((d, dIdx) => ({\n    ...transformPromResultItem(d, query.legendName, nullValue),\n    id: `${qIdx}-${dIdx}`,\n    type: query.type,\n    color: query.color,\n    // lineSeriesStyle: query.lineSeriesStyle,\n  }))\n}\n\nexport function ChartCard({ config }: { config: SingleChartConfig }) {\n  const colorScheme = useComputedColorScheme()\n  const ctx = useAppContext()\n  const { timeRange, refresh } = useMetricsUrlState()\n  const tr = useMemo(() => toTimeRangeValue(timeRange), [timeRange, refresh])\n  const chartRef = useRef<HTMLDivElement | null>(null)\n\n  // a function can always get the latest value\n  function getStep() {\n    if (!chartRef.current) return 0\n    return calcPromQueryStep(\n      tr,\n      chartRef.current.offsetWidth - 140,\n      ctx.cfg.scrapeInterval,\n    )\n  }\n\n  const {\n    data: metricData,\n    isLoading,\n    refetch,\n  } = useMetricDataByPromQLs(\n    config.queries.map((q) => q.promql),\n    timeRange,\n    getStep,\n  )\n  const seriesData = useMemo(\n    () =>\n      (metricData ?? [])\n        .map((d, idx) =>\n          transformData(d, idx, config.queries[idx], config.nullValue),\n        )\n        .flat(),\n    [metricData],\n  )\n\n  useEffect(() => {\n    refetch()\n  }, [timeRange, refresh])\n\n  return (\n    <Card p={16} bg=\"carbon.0\" shadow=\"none\">\n      <Group mb=\"xs\" gap={0} sx={{ justifyContent: \"center\" }}>\n        <Typography variant=\"title-md\">{config.title}</Typography>\n      </Group>\n\n      <Box h={200} ref={chartRef}>\n        {seriesData.length > 0 || !isLoading ? (\n          <SeriesChart\n            unit={config.unit}\n            data={seriesData}\n            timeRange={tr}\n            theme={colorScheme}\n            charSetting={{ xDomain: undefined }}\n          />\n        ) : (\n          <Flex h={200} align=\"center\" justify=\"center\">\n            <Loader size=\"xs\" />\n          </Flex>\n        )}\n      </Box>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/normal/filters.tsx",
    "content": "import {\n  AutoRefreshButton,\n  AutoRefreshButtonRef,\n  DEFAULT_AUTO_REFRESH_SECONDS,\n  TimeRangePicker,\n} from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { Group, SegmentedControl } from \"@tidbcloud/uikit\"\nimport { dayjs } from \"@tidbcloud/uikit/utils\"\nimport { useRef, useState } from \"react\"\n\nimport { useMetricsUrlState } from \"../../shared-state/url-state\"\nimport { QUICK_RANGES } from \"../../utils/constants\"\nimport { useMetricQueriesConfigData } from \"../../utils/use-data\"\n\nexport function Filters() {\n  const { panel, timeRange, setTimeRange, setRefresh, setQueryParams } =\n    useMetricsUrlState()\n  const { data: panelConfigData } = useMetricQueriesConfigData(\"normal\")\n  const tabs = panelConfigData?.map((p) => ({\n    label: p.displayName,\n    value: p.category,\n  }))\n\n  const [autoRefreshValue, setAutoRefreshValue] = useState<number>(\n    DEFAULT_AUTO_REFRESH_SECONDS,\n  )\n  const autoRefreshRef = useRef<AutoRefreshButtonRef>(null)\n  const [loading, setLoading] = useState(false)\n\n  function handlePanelChange(newPanel: string) {\n    autoRefreshRef.current?.refresh()\n    setQueryParams({\n      panel: newPanel || undefined,\n      refresh: new Date().valueOf().toString(),\n    })\n  }\n\n  function handleRefresh() {\n    setLoading(true)\n    setTimeout(() => {\n      setLoading(false)\n    }, 1000)\n    setRefresh()\n  }\n\n  const panelSwitch = tabs && tabs.length > 0 && (\n    <SegmentedControl\n      withItemsBorders={false}\n      data={tabs}\n      value={panel || tabs[0].value}\n      onChange={handlePanelChange}\n    />\n  )\n\n  const timeRangePicker = (\n    <TimeRangePicker\n      value={timeRange}\n      onChange={(v) => {\n        setTimeRange(v)\n      }}\n      quickRanges={QUICK_RANGES}\n      minDateTime={() =>\n        dayjs()\n          .subtract(QUICK_RANGES[QUICK_RANGES.length - 1], \"seconds\")\n          .startOf(\"d\")\n          .toDate()\n      }\n      maxDateTime={() => dayjs().endOf(\"d\").toDate()}\n    />\n  )\n\n  const autoRefreshButton = (\n    <AutoRefreshButton\n      ref={autoRefreshRef}\n      autoRefreshValue={autoRefreshValue}\n      onAutoRefreshChange={setAutoRefreshValue}\n      onRefresh={handleRefresh}\n      loading={loading}\n    />\n  )\n\n  return (\n    <Group>\n      {panelSwitch}\n\n      <Group ml=\"auto\">\n        {timeRangePicker}\n        {autoRefreshButton}\n      </Group>\n    </Group>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/normal/index.tsx",
    "content": "import { Stack } from \"@tidbcloud/uikit\"\n\nimport { Filters } from \"./filters\"\nimport { Panel } from \"./panel\"\n\nexport function NormalMetricsPage() {\n  return (\n    <Stack>\n      <Filters />\n      <Panel />\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/pages/normal/panel.tsx",
    "content": "import { LoadingSkeleton } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { SimpleGrid } from \"@tidbcloud/uikit\"\n\nimport { useMetricsUrlState } from \"../../shared-state/url-state\"\nimport { useMetricQueriesConfigData } from \"../../utils/use-data\"\n\nimport { ChartCard } from \"./chart-card\"\n\nexport function Panel() {\n  const { panel } = useMetricsUrlState()\n  const { data: panelConfigData, isLoading } =\n    useMetricQueriesConfigData(\"normal\")\n  const panelConfig =\n    panelConfigData?.find((p) => p.category === panel) || panelConfigData?.[0]\n\n  if (isLoading) {\n    return <LoadingSkeleton />\n  }\n\n  return (\n    <SimpleGrid type=\"container\" cols={{ base: 1, \"900px\": 2 }} spacing=\"xl\">\n      {panelConfig?.charts.map((c) => <ChartCard key={c.title} config={c} />)}\n    </SimpleGrid>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/shared-state/memory-state.ts",
    "content": "import { TimeRange } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { create } from \"zustand\"\n\nimport { SingleChartConfig } from \"../utils/type\"\n\nconst DEF_TIME_RANGE: TimeRange = { type: \"relative\", value: 30 * 60 }\n\ninterface ChartState {\n  selectedChart: SingleChartConfig | undefined\n  setSelectedChart: (newChart: SingleChartConfig | undefined) => void\n\n  timeRange: TimeRange\n  setTimeRange: (newTimeRange: TimeRange) => void\n\n  selectedInstance: string | undefined\n  setSelectedInstance: (newValue: string | undefined) => void\n\n  metricPromAddrs: { [key: string]: string }\n  setMetricPromAddrs: (metricName: string, promAddr: string) => void\n\n  reset: () => void\n}\n\nexport const useChartState = create<ChartState>((set, get) => ({\n  selectedChart: undefined,\n  setSelectedChart: (newChart) => set({ selectedChart: newChart }),\n\n  timeRange: DEF_TIME_RANGE,\n  setTimeRange: (newTimeRange) => set({ timeRange: newTimeRange }),\n\n  selectedInstance: undefined,\n  setSelectedInstance: (newValue) => set({ selectedInstance: newValue }),\n\n  metricPromAddrs: {},\n  setMetricPromAddrs: (metricName: string, promAddr: string) =>\n    set({\n      metricPromAddrs: { ...get().metricPromAddrs, [metricName]: promAddr },\n    }),\n\n  reset: () =>\n    set({\n      selectedChart: undefined,\n      timeRange: DEF_TIME_RANGE,\n      selectedInstance: undefined,\n      metricPromAddrs: {},\n    }),\n}))\n\n//---------------------------------\n\nconst LOCAL_STORAGE_KEY = \"metrics.hide.charts\"\n\ninterface ChartsSelectState {\n  hiddenCharts: string[]\n  setHiddenCharts: (newHiddenCharts: string[]) => void\n  reset: () => void\n}\n\nexport const useChartsSelectState = create<ChartsSelectState>((set) => ({\n  hiddenCharts: localStorage.getItem(LOCAL_STORAGE_KEY)?.split(\",\") || [],\n  setHiddenCharts: (newHiddenCharts) => {\n    localStorage.setItem(LOCAL_STORAGE_KEY, newHiddenCharts.join(\",\"))\n    set({ hiddenCharts: newHiddenCharts })\n  },\n  reset: () => {\n    localStorage.removeItem(LOCAL_STORAGE_KEY)\n    set({ hiddenCharts: [] })\n  },\n}))\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/shared-state/url-state.ts",
    "content": "import {\n  TimeRangeUrlState,\n  useTimeRangeUrlState,\n  useUrlState,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useCallback } from \"react\"\n\ntype MetricsUrlState = Partial<Record<\"panel\" | \"refresh\", string>> &\n  TimeRangeUrlState\n\nexport function useMetricsUrlState() {\n  const [queryParams, setQueryParams] = useUrlState<MetricsUrlState>()\n  const { timeRange, setTimeRange } = useTimeRangeUrlState()\n\n  const panel = queryParams.panel ?? \"\"\n  const setPanel = useCallback(\n    (newPanel: string) => {\n      setQueryParams({ panel: newPanel || undefined })\n    },\n    [setQueryParams],\n  )\n\n  const refresh = queryParams.refresh ?? \"\"\n  const setRefresh = useCallback(\n    (v?: string) => {\n      const now = new Date().valueOf().toString()\n      setQueryParams({ refresh: `${v || \"\"}${now}` })\n    },\n    [setQueryParams],\n  )\n\n  return {\n    panel,\n    setPanel,\n\n    timeRange,\n    setTimeRange,\n\n    refresh,\n    setRefresh,\n\n    queryParams,\n    setQueryParams,\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/utils/common.ts",
    "content": "import {\n  TimeRange,\n  toTimeRangeValue,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n\nexport function fixTimeRange(\n  timeRange: TimeRange,\n  step: number = 30,\n): [number, number] {\n  const tr = toTimeRangeValue(timeRange)\n  const end = tr[1] - (tr[1] % step)\n  const begin = tr[0] - (tr[0] % step)\n  return [begin, end]\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/utils/constants.ts",
    "content": "export const QUICK_RANGES: number[] = [\n  5 * 60, // 5 mins\n  15 * 60,\n  30 * 60,\n  60 * 60,\n  6 * 60 * 60,\n  12 * 60 * 60,\n  24 * 60 * 60,\n  2 * 24 * 60 * 60,\n  3 * 24 * 60 * 60, // 3 days\n  7 * 24 * 60 * 60, // 7 days\n]\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/utils/type.ts",
    "content": "import { SeriesDataType } from \"@pingcap-incubator/tidb-dashboard-lib-charts\"\nimport { TransformNullValue } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n\nexport type MetricConfigKind =\n  | \"normal\"\n  | \"azores-overview\"\n  | \"azores-host\"\n  | \"azores-cluster-overview\"\n  | \"azores-cluster\"\n\nexport interface SingleQueryConfig {\n  promql: string\n  legendName: string\n  type: SeriesDataType\n  color?: string | ((seriesName: string) => string | undefined)\n}\n\nexport interface SingleChartConfig {\n  metricName: string\n  title: string\n  label?: string\n  queries: SingleQueryConfig[]\n  nullValue?: TransformNullValue\n  unit: string\n}\n\n// one group has many categories\n// one category has many charts\n// one chart has many queries (aka promqls)\nexport interface SinglePanelConfig {\n  group: string\n  category: string\n  displayName?: string\n  charts: SingleChartConfig[]\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/metric/utils/use-data.ts",
    "content": "import {\n  TimeRange,\n  resolvePromQLTemplate,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { keepPreviousData, useQuery } from \"@tanstack/react-query\"\n\nimport { useAppContext } from \"../ctx\"\nimport { useMetricsUrlState } from \"../shared-state/url-state\"\nimport { MetricConfigKind } from \"../utils/type\"\n\nimport { fixTimeRange } from \"./common\"\n\nexport function useMetricQueriesConfigData(kind: MetricConfigKind) {\n  const ctx = useAppContext()\n\n  return useQuery({\n    queryKey: [ctx.ctxId, \"metric-queries-config\", kind],\n    queryFn: () => ctx.api.getMetricQueriesConfig(kind),\n  })\n}\n\nexport function useMetricConfigData() {\n  const ctx = useAppContext()\n\n  return useQuery({\n    queryKey: [ctx.ctxId, \"metric-config\"],\n    queryFn: () => ctx.api.getMetricConfig(),\n  })\n}\n\nexport function useMetricLabelValuesData(\n  metricName: string,\n  labelName: string,\n  timeRange: TimeRange,\n) {\n  const ctx = useAppContext()\n\n  return useQuery({\n    queryKey: [\n      ctx.ctxId,\n      \"metric-label-values\",\n      metricName,\n      labelName,\n      timeRange,\n    ],\n    queryFn: () => {\n      const tr = fixTimeRange(timeRange)\n      return ctx.api.getMetricLabelValues({\n        metricName,\n        labelName,\n        beginTime: tr[0],\n        endTime: tr[1],\n      })\n    },\n    enabled: !!metricName,\n  })\n}\n\nexport function useMetricDataByMetricName(\n  metricName: string,\n  timeRange: TimeRange,\n  stepFn: () => number,\n  labelValue?: string,\n) {\n  const ctx = useAppContext()\n\n  return useQuery({\n    queryKey: [\n      ctx.ctxId,\n      \"metric-data-by-metric-name\",\n      metricName,\n      timeRange,\n      // step is not the query key, it is expected\n      labelValue,\n    ],\n    queryFn: () => {\n      const step = stepFn()\n      const tr = fixTimeRange(timeRange, step)\n      return ctx.api\n        .getMetricDataByMetricName({\n          metricName,\n          beginTime: tr[0],\n          endTime: tr[1],\n          step,\n          label: labelValue,\n        })\n        .then((d) => ({ metrics: d, tr, promAddr: d[0]?.promAddr }))\n    },\n    placeholderData: keepPreviousData,\n    // set `enabled: false`, so queryFn can only be manually triggered by calling `refetch()`\n    enabled: false,\n    retry: 1,\n  })\n}\n\n// @todo: return tr\nexport function useMetricDataByPromQLs(\n  promQLs: string[],\n  timeRange: TimeRange,\n  stepFn: () => number,\n) {\n  const ctx = useAppContext()\n\n  return useQuery({\n    queryKey: [ctx.ctxId, \"metric-data-by-promqls\", promQLs, timeRange],\n    queryFn: () => {\n      const step = stepFn()\n      const tr = fixTimeRange(timeRange, step)\n      return Promise.all(\n        promQLs.map((pq) =>\n          ctx.api.getMetricDataByPromQL({\n            promQL: resolvePromQLTemplate(pq, step, ctx.cfg.scrapeInterval),\n            beginTime: tr[0],\n            endTime: tr[1],\n            step,\n          }),\n        ),\n      )\n    },\n    placeholderData: keepPreviousData,\n    // set `enabled: false`, so queryFn can only be manually triggered by calling `refetch()`\n    enabled: false,\n  })\n}\n\n//---------------------------\n\nexport function useCurPanelConfigsData() {\n  const { panel } = useMetricsUrlState()\n  const { data: panelConfigData, isLoading } =\n    useMetricQueriesConfigData(\"azores-cluster\")\n\n  const filteredPanelConfigData = panelConfigData?.filter(\n    (p) => p.group === (panel || \"basic\"),\n  )\n\n  return {\n    panelConfigData: filteredPanelConfigData,\n    isLoading,\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/ctx/index.tsx",
    "content": "import { AdvancedFilterItem } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { createContext, useContext } from \"react\"\n\nimport { AppApi as SqlHistoryAppApi } from \"../../_shared/sql-history\"\nimport { AppApi as SqlLimitAppApi } from \"../../_shared/sql-limit\"\nimport { AdvancedFilterInfoModel, SlowqueryModel } from \"../models\"\n\n////////////////////////////////\n\ntype AppApi = SqlLimitAppApi &\n  SqlHistoryAppApi & {\n    // filters\n    getDbs(): Promise<string[]>\n    // advanced filters\n    getAdvancedFilterNames(): Promise<string[]>\n    getAdvancedFilterInfo(params: {\n      name: string\n    }): Promise<AdvancedFilterInfoModel>\n    // available fields\n    getAvailableFields(): Promise<string[]>\n\n    // list & detail\n    getSlowQueries(params: {\n      beginTime: number\n      endTime: number\n      dbs: string[]\n      ruGroups: string[]\n      sqlDigest: string\n      term: string\n      limit: number\n      advancedFilters: AdvancedFilterItem[]\n      fields: string[]\n      orderBy: string\n      desc: boolean\n      pageIndex: number\n      pageSize: number\n    }): Promise<{ total: number; items: SlowqueryModel[] }>\n\n    getSlowQuery(params: { id: string }): Promise<SlowqueryModel>\n  }\n\ntype AppConfig = {\n  title?: string\n  // whether to show back to list page button in the detail page\n  // if set to false, the back button will be hidden\n  // and you need to handle the back action outside of the app by yourself\n  // default is true\n  showDetailBack?: boolean\n}\n\ntype AppActions = {\n  openDetail(id: string, newTab: boolean): void\n  backToList(): void\n  openStatement(id: string): void\n}\n\nexport type AppCtxValue = {\n  // we use ctxId to be a part of queryKey for react-query,\n  // to differ same requests from different clusters, so this value can be clusterId, or other unique value\n  ctxId: string\n  api: AppApi\n  cfg: AppConfig\n  actions: AppActions\n}\n\nexport const AppContext = createContext<AppCtxValue | null>(null)\n\nexport const useAppContext = () => {\n  const context = useContext(AppContext)\n\n  if (!context) {\n    throw new Error(\"SlowQuery AppContext must be used within a provider\")\n  }\n\n  return context\n}\n\n////////////////////////////////\n\nexport function AppProvider(props: {\n  children: React.ReactNode\n  ctxValue: AppCtxValue\n}) {\n  return (\n    <AppContext.Provider value={props.ctxValue}>\n      {props.children}\n    </AppContext.Provider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/index.ts",
    "content": "export * from \"./ctx\"\n\nexport * from \"./pages/list\"\nexport * from \"./pages/detail\"\n\n// i18n\nimport \"./locales\"\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/locales/en.json",
    "content": "{\n  \"__namespace__\": \"slow-query\",\n  \"__comment__\": \"this file can be updated by running `pnpm gen:locales` command\",\n  \"fields.backoff_detail\": \"Backoff Detail\",\n  \"fields.backoff_time\": \"Execution Backoff Time\",\n  \"fields.backoff_time.desc\": \"The total backoff waiting time before retry when a query encounters errors (note: there may be multiple backoffs in parallel so that this may not be a wall time)\",\n  \"fields.backoff_total\": \"Backoff Total\",\n  \"fields.backoff_types\": \"Backoff Types\",\n  \"fields.binary_plan\": \"Binary Plan\",\n  \"fields.commit_backoff_time\": \"Commit Backoff Time\",\n  \"fields.commit_backoff_time.desc\": \"The total backoff waiting time when 2PC commit encounters errors (note: there may be multiple backoffs in parallel so that this may not be a wall time)\",\n  \"fields.commit_time\": \"Commit Time\",\n  \"fields.commit_time.desc\": \"Time consumed in 2PC commit phase when transaction commits\",\n  \"fields.compile_time\": \"Generate Plan Time\",\n  \"fields.compile_time.desc\": \"Time consumed when generating the plan\",\n  \"fields.connection_id\": \"Connection ID\",\n  \"fields.connection_id.desc\": \"Unique connection ID of the query\",\n  \"fields.cop_proc_addr\": \"Copr Address (Process)\",\n  \"fields.cop_proc_addr.desc\": \"The address of the {{distro.tikv}} that takes most time process the Coprocessor request\",\n  \"fields.cop_proc_avg\": \"Mean Cop Proc\",\n  \"fields.cop_proc_max\": \"Max Cop Proc\",\n  \"fields.cop_proc_p90\": \"P90 Cop Proc\",\n  \"fields.cop_time\": \"Coprocessor Executor Time\",\n  \"fields.cop_time.desc\": \"The elapsed wall time when {{distro.tidb}} Coprocessor executor waiting all Coprocessor requests to finish (note: when there are JOIN in SQL statement, multiple {{distro.tidb}} Coprocessor executors may be running in parallel, which may cause this time not being a wall time)\",\n  \"fields.cop_wait_addr\": \"Copr Address (Wait)\",\n  \"fields.cop_wait_addr.desc\": \"The address of the {{distro.tikv}} that takes most time wait the Coprocessor request\",\n  \"fields.cop_wait_avg\": \"Mean Cop Wait\",\n  \"fields.cop_wait_max\": \"Max Cop Wait\",\n  \"fields.cop_wait_p90\": \"P90 Cop Wait\",\n  \"fields.db\": \"Execution Database\",\n  \"fields.db.desc\": \"The database used to execute the query\",\n  \"fields.digest\": \"Query Template ID\",\n  \"fields.digest.desc\": \"a.k.a. Query digest\",\n  \"fields.disk_max\": \"Max Disk\",\n  \"fields.disk_max.desc\": \"Maximum disk usage of the query\",\n  \"fields.exec_retry_count\": \"Retried Execution Count\",\n  \"fields.exec_retry_time\": \"Retried Execution Time\",\n  \"fields.exec_retry_time.desc\": \"Wall time consumed when SQL statement is retried and executed again, except for the last execution\",\n  \"fields.get_commit_ts_time\": \"Get Commit Ts Time\",\n  \"fields.get_commit_ts_time.desc\": \"Time consumed when getting a commit timestamp for 2PC commit phase when transaction commits\",\n  \"fields.has_more_results\": \"Has More Results?\",\n  \"fields.host\": \"Client Address\",\n  \"fields.host.desc\": \"The address of the client that sends the query\",\n  \"fields.index_names\": \"Index Names\",\n  \"fields.index_names.desc\": \"The name of the used index\",\n  \"fields.instance\": \"{{distro.tidb}} Instance\",\n  \"fields.instance.desc\": \"The {{distro.tidb}} address that handles the query\",\n  \"fields.is_explicit_txn\": \"Is Explicit Transaction?\",\n  \"fields.is_internal\": \"Is Internal?\",\n  \"fields.is_internal.desc\": \"Whether this is an internal query\",\n  \"fields.kv_total\": \"KV Total\",\n  \"fields.local_latch_wait_time\": \"Local Latch Wait Time\",\n  \"fields.local_latch_wait_time.desc\": \"Time consumed when {{distro.tidb}} waits for the lock in the current {{distro.tidb}} instance before 2PC commit phase when transaction commits\",\n  \"fields.lock_keys_time\": \"Lock Keys Time\",\n  \"fields.lock_keys_time.desc\": \"Time consumed when locking keys in pessimistic transaction\",\n  \"fields.memory_max\": \"Max Memory\",\n  \"fields.memory_max.desc\": \"Maximum memory usage of the query\",\n  \"fields.optimize_time\": \"Optimize Plan Time\",\n  \"fields.optimize_time.desc\": \"Time consumed when optimizing the plan\",\n  \"fields.parse_time\": \"Parse Time\",\n  \"fields.parse_time.desc\": \"Time consumed when parsing the query\",\n  \"fields.pd_total\": \"PD Total\",\n  \"fields.plan\": \"Execution Plan\",\n  \"fields.plan_digest\": \"Plan Digest\",\n  \"fields.plan_from_binding\": \"Is Plan from Binding?\",\n  \"fields.plan_from_cache\": \"Is Plan from Cache?\",\n  \"fields.prepared\": \"Is Prepared?\",\n  \"fields.prepared.desc\": \"Is Generated by the prepare statement\",\n  \"fields.preproc_subqueries\": \"Preprocess Sub-Query\",\n  \"fields.preproc_subqueries_time\": \"Preprocess Sub-Query Time\",\n  \"fields.preproc_subqueries_time.desc\": \"Time consumed when pre-processing the subquery during the rewrite plan phase\",\n  \"fields.prev_stmt\": \"Previous Query\",\n  \"fields.prewrite_region\": \"Prewrite Regions\",\n  \"fields.prewrite_time\": \"Prewrite Time\",\n  \"fields.prewrite_time.desc\": \"Time consumed in 2PC prewrite phase when transaction commits\",\n  \"fields.process_keys\": \"Process Keys\",\n  \"fields.process_time\": \"Coprocessor Process Time\",\n  \"fields.process_time.desc\": \"The total time of Coprocessor request being executed in {{distro.tikv}} (note: {{distro.tikv}} executes requests in parallel so that this is not a wall time)\",\n  \"fields.query\": \"Query\",\n  \"fields.query_time\": \"Latency\",\n  \"fields.query_time.desc\": \"Execution time of the query\",\n  \"fields.query_time_2\": \"Query Time\",\n  \"fields.query_time_2.desc\": \"The elapsed wall time when execution the query\",\n  \"fields.request_count\": \"Request Count\",\n  \"fields.request_unit_read\": \"Request Unit Read\",\n  \"fields.request_unit_write\": \"Request Unit Write\",\n  \"fields.resolve_lock_time\": \"Resolve Lock Time\",\n  \"fields.resolve_lock_time.desc\": \"Time consumed when {{distro.tidb}} resolves locks from other transactions in 2PC prewrite phase when transaction commits\",\n  \"fields.resource_group\": \"Resource Group\",\n  \"fields.resource_group.desc\": \"The resource group that the query belongs to\",\n  \"fields.result\": \"Result\",\n  \"fields.result.desc\": \"Whether query is executed successfully\",\n  \"fields.result_rows\": \"Result Rows\",\n  \"fields.rewrite_time\": \"Rewrite Plan Time\",\n  \"fields.rewrite_time.desc\": \"Time consumed when rewriting the plan\",\n  \"fields.rocksdb_block_cache_hit_count\": \"RocksDB Block Cache Hits\",\n  \"fields.rocksdb_block_cache_hit_count.desc\": \"Total number of hits from the block cache (RocksDB block_cache_hit_count)\",\n  \"fields.rocksdb_block_read_byte\": \"RocksDB Read Size\",\n  \"fields.rocksdb_block_read_byte.desc\": \"Total number of bytes RocksDB read from file (RocksDB block_read_byte)\",\n  \"fields.rocksdb_block_read_count\": \"RocksDB Block Reads\",\n  \"fields.rocksdb_block_read_count.desc\": \"Total number of blocks RocksDB read from file (RocksDB block_read_count)\",\n  \"fields.rocksdb_delete_skipped_count\": \"RocksDB Skipped Deletions\",\n  \"fields.rocksdb_delete_skipped_count.desc\": \"Total number of deleted (a.k.a. tombstone) key versions that are skipped during iteration (RocksDB delete_skipped_count)\",\n  \"fields.rocksdb_key_skipped_count\": \"RocksDB Skipped Keys\",\n  \"fields.rocksdb_key_skipped_count.desc\": \"Total number of keys skipped during iteration (RocksDB key_skipped_count)\",\n  \"fields.ru\": \"RU\",\n  \"fields.ru.desc\": \"Request units\",\n  \"fields.session_alias\": \"Session Alias\",\n  \"fields.sql\": \"Query\",\n  \"fields.stats\": \"Used Statistics\",\n  \"fields.success\": \"Is Success?\",\n  \"fields.success.desc\": \"Whether query is executed successfully\",\n  \"fields.tidb_cpu_time\": \"{{distro.tidb}} CPU Time\",\n  \"fields.tikv_cpu_time\": \"{{distro.tikv}} CPU Time\",\n  \"fields.time_queued_by_rc\": \"Total Time Queued by RC\",\n  \"fields.time_queued_by_rc.desc\": \"The total wait time spent in the resource queue (note: {{distro.tikv}} executes requests in parallel so that this is not a wall time)\",\n  \"fields.timestamp\": \"Finish Time\",\n  \"fields.timestamp.desc\": \"The time this query finished execution\",\n  \"fields.total_keys\": \"Total Keys\",\n  \"fields.total_keys.desc\": \"Total keys of the query\",\n  \"fields.txn_retry\": \"Transaction Retries\",\n  \"fields.txn_start_ts\": \"Start Timestamp\",\n  \"fields.txn_start_ts.desc\": \"Transaction start timestamp, a.k.a. Transaction ID\",\n  \"fields.user\": \"Execution User\",\n  \"fields.user.desc\": \"The user that executes the query\",\n  \"fields.wait_prewrite_binlog_time\": \"Wait Binlog Prewrite Time\",\n  \"fields.wait_prewrite_binlog_time.desc\": \"Time consumed when waiting Binlog prewrite to finish\",\n  \"fields.wait_time\": \"Coprocessor Wait Time\",\n  \"fields.wait_time.desc\": \"The total time of Coprocessor request is prepared and wait to execute in {{distro.tikv}}, which may happen when retrieving a snapshot though Raft concensus protocol (note: {{distro.tikv}} waits requests in parallel so that this is not a wall time)\",\n  \"fields.wait_ts\": \"Get Start Ts Time\",\n  \"fields.wait_ts.desc\": \"Time consumed when getting a start timestamp when transaction begins\",\n  \"fields.warnings\": \"Warnings\",\n  \"fields.write_keys\": \"Write Keys\",\n  \"fields.write_size\": \"Write Size\",\n  \"fields.write_sql_response_total\": \"Send Response Time\",\n  \"fields.write_sql_response_total.desc\": \"Time consumed when sending response to the SQL client\"\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/locales/index.ts",
    "content": "import { addLangsLocales } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n\nimport en from \"./en.json\"\nimport zh from \"./zh.json\"\n\naddLangsLocales({ en, zh })\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/locales/preset.ts",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n\n// @ts-expect-error @typescript-eslint/no-unused-vars\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction useLocales() {\n  const { tk } = useTn(\"slow-query\")\n  // used for gogocode to scan and generate en.json before build\n  tk(\"fields.instance\", \"{{distro.tidb}} Instance\")\n  tk(\n    \"fields.instance.desc\",\n    \"The {{distro.tidb}} address that handles the query\",\n  )\n  tk(\"fields.connection_id\", \"Connection ID\")\n  tk(\"fields.connection_id.desc\", \"Unique connection ID of the query\")\n  tk(\"fields.sql\", \"Query\")\n  tk(\"fields.query\", \"Query\")\n  tk(\"fields.timestamp\", \"Finish Time\")\n  tk(\"fields.timestamp.desc\", \"The time this query finished execution\")\n  tk(\"fields.query_time\", \"Latency\")\n  tk(\"fields.query_time.desc\", \"Execution time of the query\")\n  tk(\"fields.memory_max\", \"Max Memory\")\n  tk(\"fields.memory_max.desc\", \"Maximum memory usage of the query\")\n  tk(\"fields.disk_max\", \"Max Disk\")\n  tk(\"fields.disk_max.desc\", \"Maximum disk usage of the query\")\n  tk(\"fields.digest\", \"Query Template ID\")\n  tk(\"fields.digest.desc\", \"a.k.a. Query digest\")\n  tk(\"fields.is_internal\", \"Is Internal?\")\n  tk(\"fields.is_internal.desc\", \"Whether this is an internal query\")\n  tk(\"fields.success\", \"Is Success?\")\n  tk(\"fields.success.desc\", \"Whether query is executed successfully\")\n  tk(\"fields.prepared\", \"Is Prepared?\")\n  tk(\"fields.prepared.desc\", \"Is Generated by the prepare statement\")\n  tk(\"fields.plan_from_cache\", \"Is Plan from Cache?\")\n  tk(\"fields.plan_from_binding\", \"Is Plan from Binding?\")\n  tk(\"fields.result\", \"Result\")\n  tk(\"fields.result.desc\", \"Whether query is executed successfully\")\n  tk(\"fields.index_names\", \"Index Names\")\n  tk(\"fields.index_names.desc\", \"The name of the used index\")\n  tk(\"fields.stats\", \"Used Statistics\")\n  tk(\"fields.backoff_types\", \"Backoff Types\")\n  tk(\"fields.user\", \"Execution User\")\n  tk(\"fields.user.desc\", \"The user that executes the query\")\n  tk(\"fields.host\", \"Client Address\")\n  tk(\"fields.host.desc\", \"The address of the client that sends the query\")\n  tk(\"fields.db\", \"Execution Database\")\n  tk(\"fields.db.desc\", \"The database used to execute the query\")\n  tk(\"fields.query_time_2\", \"Query Time\")\n  tk(\n    \"fields.query_time_2.desc\",\n    \"The elapsed wall time when execution the query\",\n  )\n  tk(\"fields.parse_time\", \"Parse Time\")\n  tk(\"fields.parse_time.desc\", \"Time consumed when parsing the query\")\n  tk(\"fields.compile_time\", \"Generate Plan Time\")\n  tk(\"fields.compile_time.desc\", \"Time consumed when generating the plan\")\n  tk(\"fields.rewrite_time\", \"Rewrite Plan Time\")\n  tk(\"fields.rewrite_time.desc\", \"Time consumed when rewriting the plan\")\n  tk(\"fields.preproc_subqueries_time\", \"Preprocess Sub-Query Time\")\n  tk(\n    \"fields.preproc_subqueries_time.desc\",\n    \"Time consumed when pre-processing the subquery during the rewrite plan phase\",\n  )\n  tk(\"fields.optimize_time\", \"Optimize Plan Time\")\n  tk(\"fields.optimize_time.desc\", \"Time consumed when optimizing the plan\")\n  tk(\"fields.wait_ts\", \"Get Start Ts Time\")\n  tk(\n    \"fields.wait_ts.desc\",\n    \"Time consumed when getting a start timestamp when transaction begins\",\n  )\n  tk(\"fields.cop_time\", \"Coprocessor Executor Time\")\n  tk(\n    \"fields.cop_time.desc\",\n    \"The elapsed wall time when {{distro.tidb}} Coprocessor executor waiting all Coprocessor requests to finish (note: when there are JOIN in SQL statement, multiple {{distro.tidb}} Coprocessor executors may be running in parallel, which may cause this time not being a wall time)\",\n  )\n  tk(\"fields.wait_time\", \"Coprocessor Wait Time\")\n  tk(\n    \"fields.wait_time.desc\",\n    \"The total time of Coprocessor request is prepared and wait to execute in {{distro.tikv}}, which may happen when retrieving a snapshot though Raft concensus protocol (note: {{distro.tikv}} waits requests in parallel so that this is not a wall time)\",\n  )\n  tk(\"fields.process_time\", \"Coprocessor Process Time\")\n  tk(\n    \"fields.process_time.desc\",\n    \"The total time of Coprocessor request being executed in {{distro.tikv}} (note: {{distro.tikv}} executes requests in parallel so that this is not a wall time)\",\n  )\n  tk(\"fields.backoff_time\", \"Execution Backoff Time\")\n  tk(\n    \"fields.backoff_time.desc\",\n    \"The total backoff waiting time before retry when a query encounters errors (note: there may be multiple backoffs in parallel so that this may not be a wall time)\",\n  )\n  tk(\"fields.lock_keys_time\", \"Lock Keys Time\")\n  tk(\n    \"fields.lock_keys_time.desc\",\n    \"Time consumed when locking keys in pessimistic transaction\",\n  )\n  tk(\"fields.get_commit_ts_time\", \"Get Commit Ts Time\")\n  tk(\n    \"fields.get_commit_ts_time.desc\",\n    \"Time consumed when getting a commit timestamp for 2PC commit phase when transaction commits\",\n  )\n  tk(\"fields.local_latch_wait_time\", \"Local Latch Wait Time\")\n  tk(\n    \"fields.local_latch_wait_time.desc\",\n    \"Time consumed when {{distro.tidb}} waits for the lock in the current {{distro.tidb}} instance before 2PC commit phase when transaction commits\",\n  )\n  tk(\"fields.resolve_lock_time\", \"Resolve Lock Time\")\n  tk(\n    \"fields.resolve_lock_time.desc\",\n    \"Time consumed when {{distro.tidb}} resolves locks from other transactions in 2PC prewrite phase when transaction commits\",\n  )\n  tk(\"fields.prewrite_time\", \"Prewrite Time\")\n  tk(\n    \"fields.prewrite_time.desc\",\n    \"Time consumed in 2PC prewrite phase when transaction commits\",\n  )\n  tk(\"fields.wait_prewrite_binlog_time\", \"Wait Binlog Prewrite Time\")\n  tk(\n    \"fields.wait_prewrite_binlog_time.desc\",\n    \"Time consumed when waiting Binlog prewrite to finish\",\n  )\n  tk(\"fields.commit_time\", \"Commit Time\")\n  tk(\n    \"fields.commit_time.desc\",\n    \"Time consumed in 2PC commit phase when transaction commits\",\n  )\n  tk(\"fields.commit_backoff_time\", \"Commit Backoff Time\")\n  tk(\n    \"fields.commit_backoff_time.desc\",\n    \"The total backoff waiting time when 2PC commit encounters errors (note: there may be multiple backoffs in parallel so that this may not be a wall time)\",\n  )\n  tk(\"fields.write_sql_response_total\", \"Send Response Time\")\n  tk(\n    \"fields.write_sql_response_total.desc\",\n    \"Time consumed when sending response to the SQL client\",\n  )\n  tk(\"fields.exec_retry_time\", \"Retried Execution Time\")\n  tk(\n    \"fields.exec_retry_time.desc\",\n    \"Wall time consumed when SQL statement is retried and executed again, except for the last execution\",\n  )\n  tk(\"fields.request_count\", \"Request Count\")\n  tk(\"fields.process_keys\", \"Process Keys\")\n  tk(\"fields.total_keys\", \"Total Keys\")\n  tk(\"fields.total_keys.desc\", \"Total keys of the query\")\n  tk(\"fields.cop_proc_addr\", \"Copr Address (Process)\")\n  tk(\n    \"fields.cop_proc_addr.desc\",\n    \"The address of the {{distro.tikv}} that takes most time process the Coprocessor request\",\n  )\n  tk(\"fields.cop_wait_addr\", \"Copr Address (Wait)\")\n  tk(\n    \"fields.cop_wait_addr.desc\",\n    \"The address of the {{distro.tikv}} that takes most time wait the Coprocessor request\",\n  )\n  tk(\"fields.txn_start_ts\", \"Start Timestamp\")\n  tk(\n    \"fields.txn_start_ts.desc\",\n    \"Transaction start timestamp, a.k.a. Transaction ID\",\n  )\n  tk(\"fields.write_keys\", \"Write Keys\")\n  tk(\"fields.write_size\", \"Write Size\")\n  tk(\"fields.prewrite_region\", \"Prewrite Regions\")\n  tk(\"fields.txn_retry\", \"Transaction Retries\")\n  tk(\"fields.prev_stmt\", \"Previous Query\")\n  tk(\"fields.plan\", \"Execution Plan\")\n  tk(\"fields.cop_proc_avg\", \"Mean Cop Proc\")\n  tk(\"fields.cop_wait_avg\", \"Mean Cop Wait\")\n  tk(\"fields.rocksdb_delete_skipped_count\", \"RocksDB Skipped Deletions\")\n  tk(\n    \"fields.rocksdb_delete_skipped_count.desc\",\n    \"Total number of deleted (a.k.a. tombstone) key versions that are skipped during iteration (RocksDB delete_skipped_count)\",\n  )\n  tk(\"fields.rocksdb_key_skipped_count\", \"RocksDB Skipped Keys\")\n  tk(\n    \"fields.rocksdb_key_skipped_count.desc\",\n    \"Total number of keys skipped during iteration (RocksDB key_skipped_count)\",\n  )\n  tk(\"fields.rocksdb_block_cache_hit_count\", \"RocksDB Block Cache Hits\")\n  tk(\n    \"fields.rocksdb_block_cache_hit_count.desc\",\n    \"Total number of hits from the block cache (RocksDB block_cache_hit_count)\",\n  )\n  tk(\"fields.rocksdb_block_read_count\", \"RocksDB Block Reads\")\n  tk(\n    \"fields.rocksdb_block_read_count.desc\",\n    \"Total number of blocks RocksDB read from file (RocksDB block_read_count)\",\n  )\n  tk(\"fields.rocksdb_block_read_byte\", \"RocksDB Read Size\")\n  tk(\n    \"fields.rocksdb_block_read_byte.desc\",\n    \"Total number of bytes RocksDB read from file (RocksDB block_read_byte)\",\n  )\n  tk(\"fields.ru\", \"RU\")\n  tk(\"fields.ru.desc\", \"Request units\")\n  tk(\"fields.resource_group\", \"Resource Group\")\n  tk(\n    \"fields.resource_group.desc\",\n    \"The resource group that the query belongs to\",\n  )\n  tk(\"fields.time_queued_by_rc\", \"Total Time Queued by RC\")\n  tk(\n    \"fields.time_queued_by_rc.desc\",\n    \"The total wait time spent in the resource queue (note: {{distro.tikv}} executes requests in parallel so that this is not a wall time)\",\n  )\n  tk(\"fields.ia_remote_read_segment_size\", \"IA Remote Read Segment Size\")\n  tk(\n    \"fields.ia_remote_read_segment_size.desc\",\n    \"Total number of bytes read from IA remote segments\",\n  )\n  tk(\"fields.ia_remote_read_segment_wait_time\", \"IA Remote Read Segment Wait Time\")\n  tk(\n    \"fields.ia_remote_read_segment_wait_time.desc\",\n    \"The wait time spent reading IA remote segments\",\n  )\n\n  // additional fields\n  tk(\"fields.backoff_detail\", \"Backoff Detail\")\n  tk(\"fields.backoff_total\", \"Backoff Total\")\n  tk(\"fields.binary_plan\", \"Binary Plan\")\n  tk(\"fields.cop_proc_max\", \"Max Cop Proc\")\n  tk(\"fields.cop_proc_p90\", \"P90 Cop Proc\")\n  tk(\"fields.cop_wait_max\", \"Max Cop Wait\")\n  tk(\"fields.cop_wait_p90\", \"P90 Cop Wait\")\n  tk(\"fields.exec_retry_count\", \"Retried Execution Count\")\n  tk(\"fields.has_more_results\", \"Has More Results?\")\n  tk(\"fields.is_explicit_txn\", \"Is Explicit Transaction?\")\n  tk(\"fields.pd_total\", \"PD Total\")\n  tk(\"fields.kv_total\", \"KV Total\")\n  tk(\"fields.preproc_subqueries\", \"Preprocess Sub-Query\")\n  tk(\"fields.request_unit_read\", \"Request Unit Read\")\n  tk(\"fields.request_unit_write\", \"Request Unit Write\")\n  tk(\"fields.session_alias\", \"Session Alias\")\n  tk(\"fields.result_rows\", \"Result Rows\")\n  tk(\"fields.warnings\", \"Warnings\")\n  tk(\"fields.plan_digest\", \"Plan Digest\")\n\n  tk(\"fields.tidb_cpu_time\", \"{{distro.tidb}} CPU Time\")\n  tk(\"fields.tikv_cpu_time\", \"{{distro.tikv}} CPU Time\")\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/locales/zh.json",
    "content": "{\n  \"__namespace__\": \"slow-query\",\n  \"__comment__\": \"this file can be updated by running `pnpm gen:locales` command\",\n  \"fields.backoff_detail\": \"Backoff 详情\",\n  \"fields.backoff_time\": \"执行阶段累计 Backoff 耗时\",\n  \"fields.backoff_time.desc\": \"在执行失败时，Backoff 机制等待一段时间再重试时的 Backoff 累计耗时（注：可能同时存在多个 Backoff，因此该时间可能不是自然流逝时间）\",\n  \"fields.backoff_total\": \"Backoff 总数\",\n  \"fields.backoff_types\": \"重试类型\",\n  \"fields.binary_plan\": \"Binary 执行计划\",\n  \"fields.commit_backoff_time\": \"Commit 阶段累计 Backoff 耗时\",\n  \"fields.commit_backoff_time.desc\": \"事务递交失败时，Backoff 机制等待一段时间再重试时的 Backoff 累计耗时（注：可能同时存在多个 Backoff，因此该时间可能不是自然流逝时间）\",\n  \"fields.commit_time\": \"Commit 阶段耗时\",\n  \"fields.commit_time.desc\": \"事务两阶段提交中第二阶段（commit 阶段）的耗时\",\n  \"fields.compile_time\": \"生成执行计划耗时\",\n  \"fields.compile_time.desc\": \"Time consumed when generating the plan\",\n  \"fields.connection_id\": \"连接号\",\n  \"fields.connection_id.desc\": \"SQL 查询客户端连接 ID\",\n  \"fields.cop_proc_addr\": \"最长处理时间实例\",\n  \"fields.cop_proc_addr.desc\": \"The address of the {{distro.tikv}} that takes most time process the Coprocessor request\",\n  \"fields.cop_proc_avg\": \"平均处理时间\",\n  \"fields.cop_proc_max\": \"最长处理时间\",\n  \"fields.cop_proc_p90\": \"P90 处理时间\",\n  \"fields.cop_time\": \"Coprocessor 执行耗时\",\n  \"fields.cop_time.desc\": \"{{distro.tidb}} Coprocessor 算子等待所有任务在 {{distro.tikv}} 上并行执行完毕耗费的自然时间（注：当 SQL 语句中包含 JOIN 时，多个 {{distro.tidb}} Coprocessor 算子可能会并行执行，此时不再等同于自然时间）\",\n  \"fields.cop_wait_addr\": \"最长等待时间实例\",\n  \"fields.cop_wait_addr.desc\": \"The address of the {{distro.tikv}} that takes most time wait the Coprocessor request\",\n  \"fields.cop_wait_avg\": \"平均等待时间\",\n  \"fields.cop_wait_max\": \"最长等待时间\",\n  \"fields.cop_wait_p90\": \"P90 等待时间\",\n  \"fields.db\": \"执行数据库\",\n  \"fields.db.desc\": \"执行该 SQL 查询时使用的数据库名称\",\n  \"fields.digest\": \"SQL 模板 ID\",\n  \"fields.digest.desc\": \"SQL 模板的唯一标识（SQL 指纹）\",\n  \"fields.disk_max\": \"最大磁盘空间\",\n  \"fields.disk_max.desc\": \"该 SQL 查询执行时占用的最大磁盘空间\",\n  \"fields.exec_retry_count\": \"前序执行重试次数\",\n  \"fields.exec_retry_time\": \"前序执行重试耗时\",\n  \"fields.exec_retry_time.desc\": \"由于锁冲突或错误，计划可能会执行失败并重试执行多次，该时间是不包含最后一次执行的前序执行自然时间（注：执行计划中的时间不含该前序时间）\",\n  \"fields.get_commit_ts_time\": \"取事务 Commit Ts 耗时\",\n  \"fields.get_commit_ts_time.desc\": \"从 {{distro.pd}} 取提交时间戳（事务号）步骤的耗时\",\n  \"fields.has_more_results\": \"是否有更多结果?\",\n  \"fields.host\": \"客户端地址\",\n  \"fields.host.desc\": \"发送 SQL 查询的客户端地址\",\n  \"fields.index_names\": \"索引名\",\n  \"fields.index_names.desc\": \"SQL 查询执行时使用的索引名称\",\n  \"fields.instance\": \"{{distro.tidb}} 实例\",\n  \"fields.instance.desc\": \"处理该 SQL 查询的 {{distro.tidb}} 实例地址\",\n  \"fields.is_explicit_txn\": \"是否为显式事务?\",\n  \"fields.is_internal\": \"是否为内部 SQL 查询\",\n  \"fields.is_internal.desc\": \"Whether this is an internal query\",\n  \"fields.kv_total\": \"KV 总数\",\n  \"fields.local_latch_wait_time\": \"{{distro.tidb}} 本地等锁耗时\",\n  \"fields.local_latch_wait_time.desc\": \"事务在 {{distro.tidb}} 本地与其他事务产生了锁冲突并等待的耗时\",\n  \"fields.lock_keys_time\": \"上锁耗时\",\n  \"fields.lock_keys_time.desc\": \"悲观事务中对相关行数据进行上锁的耗时\",\n  \"fields.memory_max\": \"最大内存\",\n  \"fields.memory_max.desc\": \"该 SQL 查询执行时占用的最大内存空间\",\n  \"fields.optimize_time\": \"优化执行计划耗时\",\n  \"fields.optimize_time.desc\": \"Time consumed when optimizing the plan\",\n  \"fields.parse_time\": \"解析耗时\",\n  \"fields.parse_time.desc\": \"解析该 SQL 查询的耗时\",\n  \"fields.pd_total\": \"PD 总数\",\n  \"fields.plan\": \"执行计划\",\n  \"fields.plan_digest\": \"Plan ID\",\n  \"fields.plan_from_binding\": \"查询计划是否来自绑定\",\n  \"fields.plan_from_cache\": \"查询计划是否来自缓存\",\n  \"fields.prepared\": \"是否由 prepare 语句生成\",\n  \"fields.prepared.desc\": \"Is Generated by the prepare statement\",\n  \"fields.preproc_subqueries\": \"子查询预处理\",\n  \"fields.preproc_subqueries_time\": \"子查询预处理耗时\",\n  \"fields.preproc_subqueries_time.desc\": \"Time consumed when pre-processing the subquery during the rewrite plan phase\",\n  \"fields.prev_stmt\": \"前一条 SQL 查询\",\n  \"fields.prewrite_region\": \"Prewrite 涉及 Regions 个数\",\n  \"fields.prewrite_time\": \"Prewrite 阶段耗时\",\n  \"fields.prewrite_time.desc\": \"事务两阶段提交中第一阶段（prewrite 阶段）的耗时\",\n  \"fields.process_keys\": \"可见版本数\",\n  \"fields.process_time\": \"Coprocessor 累计执行耗时\",\n  \"fields.process_time.desc\": \"{{distro.tikv}} 执行 Coprocessor 任务的累计处理时间（注：{{distro.tikv}} 会并行处理任务，因此该时间不是自然流逝时间）\",\n  \"fields.query\": \"SQL\",\n  \"fields.query_time\": \"总执行时间\",\n  \"fields.query_time.desc\": \"该 SQL 查询总的执行时间\",\n  \"fields.query_time_2\": \"SQL 执行时间\",\n  \"fields.query_time_2.desc\": \"执行 SQL 耗费的自然时间\",\n  \"fields.request_count\": \"Coprocessor 请求数\",\n  \"fields.request_unit_read\": \"读取资源单位\",\n  \"fields.request_unit_write\": \"写入资源单位\",\n  \"fields.resolve_lock_time\": \"解锁耗时\",\n  \"fields.resolve_lock_time.desc\": \"事务在提交过程中与其他事务产生了锁冲突并处理锁冲突的耗时\",\n  \"fields.resource_group\": \"资源组\",\n  \"fields.resource_group.desc\": \"SQL 语句所属的资源组\",\n  \"fields.result\": \"执行结果\",\n  \"fields.result.desc\": \"SQL 查询是否执行成功\",\n  \"fields.result_rows\": \"返回行数\",\n  \"fields.rewrite_time\": \"重写执行计划耗时\",\n  \"fields.rewrite_time.desc\": \"Time consumed when rewriting the plan\",\n  \"fields.rocksdb_block_cache_hit_count\": \"RocksDB 缓存读次数\",\n  \"fields.rocksdb_block_cache_hit_count.desc\": \"RocksDB 从 Block Cache 缓存中读数据的次数 (block_cache_hit_count)\",\n  \"fields.rocksdb_block_read_byte\": \"RocksDB 文件系统读数据量\",\n  \"fields.rocksdb_block_read_byte.desc\": \"RocksDB 从文件系统中读数据的数据量 (block_read_byte)\",\n  \"fields.rocksdb_block_read_count\": \"RocksDB 文件系统读次数\",\n  \"fields.rocksdb_block_read_count.desc\": \"RocksDB 从文件系统中读数据的次数 (block_read_count)\",\n  \"fields.rocksdb_delete_skipped_count\": \"RocksDB 已删除 Key 扫描数\",\n  \"fields.rocksdb_delete_skipped_count.desc\": \"RocksDB 扫数据时遇到的已删除 (tombstone) Key 数量 (delete_skipped_count)\",\n  \"fields.rocksdb_key_skipped_count\": \"RocksDB Key 扫描数\",\n  \"fields.rocksdb_key_skipped_count.desc\": \"RocksDB 扫数据时所有遇到的 Key 数量 (key_skipped_count)\",\n  \"fields.ru\": \"RU\",\n  \"fields.ru.desc\": \"资源单位(RU)\",\n  \"fields.session_alias\": \"会话别名\",\n  \"fields.sql\": \"SQL\",\n  \"fields.stats\": \"使用的统计信息\",\n  \"fields.success\": \"是否执行成功\",\n  \"fields.success.desc\": \"SQL 查询是否执行成功\",\n  \"fields.tidb_cpu_time\": \"{{distro.tidb}} CPU 时间\",\n  \"fields.tikv_cpu_time\": \"{{distro.tikv}} CPU 时间\",\n  \"fields.time_queued_by_rc\": \"RC 等待累积耗时\",\n  \"fields.time_queued_by_rc.desc\": \"SQL 语句在资源组队列中等待的累积时间（注：{{distro.tikv}} 会并行等待任务，因此该时间不是自然流逝时间）\",\n  \"fields.timestamp\": \"结束运行时间\",\n  \"fields.timestamp.desc\": \"该 SQL 查询结束运行时的时间\",\n  \"fields.total_keys\": \"遇到版本数\",\n  \"fields.total_keys.desc\": \"Total keys of the query\",\n  \"fields.txn_retry\": \"事务重试次数\",\n  \"fields.txn_start_ts\": \"事务号\",\n  \"fields.txn_start_ts.desc\": \"事务开始的时间戳，也即是事务号\",\n  \"fields.user\": \"执行用户名\",\n  \"fields.user.desc\": \"执行该 SQL 查询的用户名，可能存在多个执行用户，仅显示其中某一个\",\n  \"fields.wait_prewrite_binlog_time\": \"Binlog Prewrite 等待耗时\",\n  \"fields.wait_prewrite_binlog_time.desc\": \"等待 Binlog Prewrite 完成的耗时\",\n  \"fields.wait_time\": \"Coprocessor 累计等待耗时\",\n  \"fields.wait_time.desc\": \"{{distro.tikv}} 准备并等待 Coprocessor 任务执行的累计时间，等待过程中包括通过 Raft 一致性协议取快照等（注：{{distro.tikv}} 会并行等待任务，因此该时间不是自然流逝时间）\",\n  \"fields.wait_ts\": \"取事务 Start Ts 耗时\",\n  \"fields.wait_ts.desc\": \"从 {{distro.pd}} 取事务开始时间戳步骤的耗时\",\n  \"fields.warnings\": \"警告信息\",\n  \"fields.write_keys\": \"写入 Key 个数\",\n  \"fields.write_size\": \"写入数据量\",\n  \"fields.write_sql_response_total\": \"发送结果耗时\",\n  \"fields.write_sql_response_total.desc\": \"发送 SQL 语句执行结果给客户端的耗时\",\n  \"All (Raw JSON)\": \"全部 (原始 JSON)\",\n  \"Basic\": \"基础信息\",\n  \"Clear Filters\": \"清空筛选条件\",\n  \"Coprocessor\": \"Coprocessor\",\n  \"Detail\": \"详情\",\n  \"Due to the limitation, currently only support to query max 24 hours data, so the actual time range is {{begin}} to {{end}}\": \"由于限制，目前仅支持查询最大 24 小时的数据，实际时间范围是 {{begin}} ~ {{end}}\",\n  \"Find SQL text\": \"查找 SQL\",\n  \"I got it\": \"我知道了\",\n  \"No Data\": \"无结果\",\n  \"Plan\": \"执行计划\",\n  \"Query\": \"SQL 查询\",\n  \"SQL digest\": \"SQL digest\",\n  \"Slow Query Detail\": \"慢查询详情\",\n  \"Table\": \"表格\",\n  \"Tell me again next time\": \"下次还告诉我\",\n  \"Text\": \"文本\",\n  \"Time\": \"执行时间\",\n  \"Tips\": \"提示\",\n  \"Transaction\": \"事务\",\n  \"View related statement in statement page (but it may be evicted already)\": \"在 SQL 语句页面查看相关的 SQL 语句（但有可能已经被淘汰）\",\n  \"View related statement in statement page, but it may be evicted already, so the result maybe empty\": \"在 SQL 语句页面查看相关的 SQL 语句，但该 SQL 语句有可能已经被淘汰，所以结果可能为空\",\n  \"View related statement\": \"查看相关的 SQL 语句\",\n  \"Warnings\": \"警告\",\n  \"When opening the detail page, you can press <kbd>Ctrl</kbd> or <kbd>⌘</kbd> to view it in a new tab, or <kbd>Shift</kbd> to view it in a new window.\": \"在打开详情页时，你可以按住 <kbd>Ctrl</kbd> 或 <kbd>⌘</kbd> 在新标签页打开，或按住 <kbd>Shift</kbd> 在新窗口中打开。\",\n  \"page\": \"页\",\n  \"total\": \"总计\"\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/models/advanced-filter-info-model.ts",
    "content": "import { AdvancedFilterInfo } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\n\nexport type AdvancedFilterInfoModel = AdvancedFilterInfo\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/models/index.ts",
    "content": "export * from \"./slowquery-model\"\nexport * from \"./advanced-filter-info-model\"\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/models/slowquery-model.ts",
    "content": "export interface SlowqueryModel {\n  backoff_time?: number\n  backoff_types?: string\n  binary_plan?: string\n  binary_plan_json?: string\n  binary_plan_text?: string\n  commit_backoff_time?: number\n  commit_time?: number\n  compile_time?: number\n  connection_id?: string\n  cop_proc_addr?: string\n  cop_proc_avg?: number\n  cop_proc_max?: number\n  cop_proc_p90?: number\n  cop_time?: number\n  cop_wait_addr?: string\n  cop_wait_avg?: number\n  cop_wait_max?: number\n  cop_wait_p90?: number\n  db?: string\n  digest?: string\n  disk_max?: number\n  exec_retry_time?: number\n  get_commit_ts_time?: number\n  host?: string\n  ia_remote_read_segment_size?: number\n  ia_remote_read_segment_wait_time?: number\n  index_names?: string\n  instance?: string\n  is_internal?: number\n  local_latch_wait_time?: number\n  lock_keys_time?: number\n  memory_max?: number\n  optimize_time?: number\n  parse_time?: number\n  plan?: string\n  plan_from_binding?: number\n  plan_from_cache?: number\n  prepared?: number\n  preproc_subqueries_time?: number\n  prev_stmt?: string\n  prewrite_region?: number\n  prewrite_time?: number\n  process_keys?: number\n  process_time?: number\n  query?: string\n  query_time?: number\n  request_count?: number\n  resolve_lock_time?: number\n  resource_group?: string\n  rewrite_time?: number\n  rocksdb_block_cache_hit_count?: number\n  rocksdb_block_read_byte?: number\n  rocksdb_block_read_count?: number\n  rocksdb_delete_skipped_count?: number\n  rocksdb_key_skipped_count?: number\n  ru?: number\n  stats?: string\n  success?: number\n  time_queued_by_rc?: number\n  timestamp?: number\n  total_keys?: number\n  txn_retry?: number\n  txn_start_ts?: string\n  user?: string\n  wait_prewrite_binlog_time?: number\n  wait_time?: number\n  wait_ts?: number\n  warnings?: string | object[]\n  write_keys?: number\n  write_size?: number\n  write_sql_response_total?: number\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/detail/detail-basic.tsx",
    "content": "import {\n  InfoModel,\n  InfoTable,\n} from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport {\n  formatNumByUnit,\n  formatTime,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMemo } from \"react\"\n\nimport { SlowqueryModel } from \"../../models\"\n\nfunction getData(\n  data: SlowqueryModel,\n  tk: (key: string) => string,\n): InfoModel[] {\n  return [\n    {\n      name: tk(\"fields.timestamp\"),\n      value: formatTime(data.timestamp! * 1000),\n      desc: tk(\"fields.timestamp.desc\"),\n    },\n    {\n      name: tk(\"fields.digest\"),\n      value: data.digest!,\n      desc: tk(\"fields.digest.desc\"),\n    },\n    {\n      name: tk(\"fields.is_internal\"),\n      value: data.is_internal === 1 ? \"Yes\" : \"No\",\n      desc: tk(\"fields.is_internal.desc\"),\n    },\n    {\n      name: tk(\"fields.success\"),\n      value: data.success === 1 ? \"Yes\" : \"No\",\n      desc: tk(\"fields.success.desc\"),\n    },\n    {\n      name: tk(\"fields.prepared\"),\n      value: data.prepared === 1 ? \"Yes\" : \"No\",\n      desc: tk(\"fields.prepared.desc\"),\n    },\n    {\n      name: tk(\"fields.plan_from_cache\"),\n      value: data.plan_from_cache === 1 ? \"Yes\" : \"No\",\n    },\n    {\n      name: tk(\"fields.plan_from_binding\"),\n      value: data.plan_from_binding === 1 ? \"Yes\" : \"No\",\n    },\n    {\n      name: tk(\"fields.db\"),\n      value: data.db || \"-\",\n      desc: tk(\"fields.db.desc\"),\n    },\n    {\n      name: tk(\"fields.index_names\"),\n      value: data.index_names || \"-\",\n      desc: tk(\"fields.index_names.desc\"),\n    },\n    {\n      name: tk(\"fields.stats\"),\n      value: data.stats || \"-\",\n    },\n    {\n      name: tk(\"fields.backoff_types\"),\n      value: data.backoff_types || \"-\",\n    },\n    {\n      name: tk(\"fields.memory_max\"),\n      value: formatNumByUnit(data.memory_max || 0, \"bytes\"),\n      desc: tk(\"fields.memory_max.desc\"),\n    },\n    {\n      name: tk(\"fields.disk_max\"),\n      value: formatNumByUnit(data.disk_max || 0, \"bytes\"),\n      desc: tk(\"fields.disk_max.desc\"),\n    },\n    {\n      name: tk(\"fields.instance\"),\n      value: data.instance || \"-\",\n      desc: tk(\"fields.instance.desc\"),\n    },\n    {\n      name: tk(\"fields.connection_id\"),\n      value: data.connection_id || \"-\",\n      desc: tk(\"fields.connection_id.desc\"),\n    },\n    {\n      name: tk(\"fields.user\"),\n      value: data.user || \"-\",\n      desc: tk(\"fields.user.desc\"),\n    },\n    {\n      name: tk(\"fields.host\"),\n      value: data.host || \"-\",\n      desc: tk(\"fields.host.desc\"),\n    },\n  ]\n}\n\nexport function DetailBasic({ data }: { data: SlowqueryModel }) {\n  const { tk } = useTn(\"slow-query\")\n  const infoData = useMemo(() => getData(data, tk), [data, tk])\n  return <InfoTable data={infoData} />\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/detail/detail-copr.tsx",
    "content": "import {\n  InfoModel,\n  InfoTable,\n} from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport {\n  formatNumByUnit,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMemo } from \"react\"\n\nimport { SlowqueryModel } from \"../../models\"\n\nfunction getData(\n  data: SlowqueryModel,\n  tk: (key: string) => string,\n): InfoModel[] {\n  return [\n    {\n      name: tk(\"fields.request_count\"),\n      value: formatNumByUnit(data.request_count || 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.process_keys\"),\n      value: formatNumByUnit(data.process_keys || 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.total_keys\"),\n      value: formatNumByUnit(data.total_keys || 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.cop_proc_addr\"),\n      value: data.cop_proc_addr || \"-\",\n      desc: tk(\"fields.cop_proc_addr.desc\"),\n    },\n    {\n      name: tk(\"fields.cop_wait_addr\"),\n      value: data.cop_wait_addr || \"-\",\n      desc: tk(\"fields.cop_wait_addr.desc\"),\n    },\n    {\n      name: tk(\"fields.rocksdb_block_cache_hit_count\"),\n      value: formatNumByUnit(data.rocksdb_block_cache_hit_count || 0, \"short\"),\n      desc: tk(\"fields.rocksdb_block_cache_hit_count.desc\"),\n    },\n    {\n      name: tk(\"fields.rocksdb_block_read_byte\"),\n      value: formatNumByUnit(data.rocksdb_block_read_byte || 0, \"bytes\"),\n      desc: tk(\"fields.rocksdb_block_read_byte.desc\"),\n    },\n    {\n      name: tk(\"fields.rocksdb_block_read_count\"),\n      value: formatNumByUnit(data.rocksdb_block_read_count || 0, \"short\"),\n      desc: tk(\"fields.rocksdb_block_read_count.desc\"),\n    },\n    {\n      name: tk(\"fields.rocksdb_delete_skipped_count\"),\n      value: formatNumByUnit(data.rocksdb_delete_skipped_count || 0, \"short\"),\n      desc: tk(\"fields.rocksdb_delete_skipped_count.desc\"),\n    },\n    {\n      name: tk(\"fields.rocksdb_key_skipped_count\"),\n      value: formatNumByUnit(data.rocksdb_key_skipped_count || 0, \"short\"),\n      desc: tk(\"fields.rocksdb_key_skipped_count.desc\"),\n    },\n    {\n      name: tk(\"fields.ia_remote_read_segment_size\"),\n      value: formatNumByUnit(data.ia_remote_read_segment_size || 0, \"bytes\"),\n      desc: tk(\"fields.ia_remote_read_segment_size.desc\"),\n    },\n    {\n      name: tk(\"fields.ia_remote_read_segment_wait_time\"),\n      value: formatNumByUnit(\n        data.ia_remote_read_segment_wait_time || 0,\n        \"s\",\n      ),\n      desc: tk(\"fields.ia_remote_read_segment_wait_time.desc\"),\n    },\n  ]\n}\n\nexport function DetailCopr({ data }: { data: SlowqueryModel }) {\n  const { tk } = useTn(\"slow-query\")\n  const infoData = useMemo(() => getData(data, tk), [data, tk])\n  return <InfoTable data={infoData} />\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/detail/detail-tabs.tsx",
    "content": "import { CustomJsonView } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  ActionIcon,\n  Card,\n  CopyButton,\n  Stack,\n  Tabs,\n  Title,\n  Tooltip,\n} from \"@tidbcloud/uikit\"\nimport { IconCheck, IconCopy02 } from \"@tidbcloud/uikit/icons\"\nimport { useMemo } from \"react\"\n\nimport { SlowqueryModel } from \"../../models\"\n\nimport { DetailBasic } from \"./detail-basic\"\nimport { DetailCopr } from \"./detail-copr\"\nimport { DetailTime } from \"./detail-time\"\nimport { DetailTxn } from \"./detail-txn\"\n\nfunction DetailAll({ data }: { data: SlowqueryModel }) {\n  return (\n    <Stack gap={0}>\n      <CopyButton value={JSON.stringify(data, null, 2)} timeout={2000}>\n        {({ copied, copy }) => (\n          <Tooltip\n            label={copied ? \"Copied\" : \"Copy\"}\n            withArrow\n            position=\"right\"\n          >\n            <ActionIcon variant=\"subtle\" onClick={copy}>\n              {copied ? <IconCheck size={16} /> : <IconCopy02 size={16} />}\n            </ActionIcon>\n          </Tooltip>\n        )}\n      </CopyButton>\n      <CustomJsonView data={data} />\n    </Stack>\n  )\n}\n\nexport function DetailTabs({ data }: { data: SlowqueryModel }) {\n  const { tt } = useTn(\"slow-query\")\n  const tabs = useMemo(() => {\n    const _tabs = [\n      {\n        label: tt(\"Basic\"),\n        value: \"basic\",\n        component: <DetailBasic data={data} />,\n      },\n      {\n        label: tt(\"Time\"),\n        value: \"time\",\n        component: <DetailTime data={data} />,\n      },\n      {\n        label: tt(\"Coprocessor\"),\n        value: \"copr\",\n        component: <DetailCopr data={data} />,\n      },\n      {\n        label: tt(\"Transaction\"),\n        value: \"txn\",\n        component: <DetailTxn data={data} />,\n      },\n    ]\n    if (data.warnings) {\n      let jsonData = {}\n      if (typeof data.warnings === \"string\") {\n        // data.warnings maybe \"null\", after JSON.parse, it will be null\n        jsonData = JSON.parse(data.warnings)\n      } else if (typeof data.warnings === \"object\") {\n        jsonData = data.warnings\n      }\n      if (jsonData) {\n        _tabs.push({\n          label: tt(\"Warnings\"),\n          value: \"warnings\",\n          component: <CustomJsonView data={jsonData} />,\n        })\n      }\n    }\n    // all\n    _tabs.push({\n      label: tt(\"All (Raw JSON)\"),\n      value: \"all\",\n      component: <DetailAll data={data} />,\n    })\n\n    return _tabs\n  }, [data, tt])\n\n  return (\n    <Card shadow=\"xs\" p=\"md\">\n      <Stack gap=\"xs\">\n        <Title order={5}>{tt(\"Detail\")}</Title>\n        <Tabs defaultValue={tabs[0].value}>\n          <Tabs.List mb=\"md\">\n            {tabs.map((tab) => (\n              <Tabs.Tab key={tab.value} value={tab.value}>\n                {tab.label}\n              </Tabs.Tab>\n            ))}\n          </Tabs.List>\n          {tabs.map((tab) => (\n            <Tabs.Panel key={tab.value} value={tab.value}>\n              {tab.component}\n            </Tabs.Panel>\n          ))}\n        </Tabs>\n      </Stack>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/detail/detail-time.tsx",
    "content": "import {\n  InfoModel,\n  InfoTable,\n} from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport {\n  formatNumByUnit,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMemo } from \"react\"\n\nimport { SlowqueryModel } from \"../../models\"\n\nfunction getData(\n  data: SlowqueryModel,\n  tk: (key: string) => string,\n): InfoModel[] {\n  return [\n    {\n      name: tk(\"fields.query_time_2\"),\n      value: formatNumByUnit(data.query_time! * 10e8, \"ns\"),\n      level: 0,\n      desc: tk(\"fields.query_time_2.desc\"),\n    },\n    {\n      name: tk(\"fields.parse_time\"),\n      value: formatNumByUnit(data.parse_time! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.parse_time.desc\"),\n    },\n    {\n      name: tk(\"fields.compile_time\"),\n      value: formatNumByUnit(data.compile_time! * 10e8, \"ns\"),\n      level: 1,\n      desc: \"\",\n    },\n    {\n      name: tk(\"fields.rewrite_time\"),\n      value: formatNumByUnit(data.rewrite_time! * 10e8, \"ns\"),\n      level: 2,\n      desc: \"\",\n    },\n    {\n      name: tk(\"fields.preproc_subqueries_time\"),\n      value: formatNumByUnit(data.preproc_subqueries_time! * 10e8, \"ns\"),\n      level: 3,\n      desc: tk(\"fields.preproc_subqueries_time.desc\"),\n    },\n    {\n      name: tk(\"fields.optimize_time\"),\n      value: formatNumByUnit(data.optimize_time! * 10e8, \"ns\"),\n      level: 2,\n      desc: tk(\"fields.optimize_time.desc\"),\n    },\n    {\n      name: tk(\"fields.cop_time\"),\n      value: formatNumByUnit(data.cop_time! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.cop_time.desc\"),\n    },\n    {\n      name: tk(\"fields.wait_time\"),\n      value: formatNumByUnit(data.wait_time! * 10e8, \"ns\"),\n      level: 2,\n      desc: tk(\"fields.wait_time.desc\"),\n    },\n    {\n      name: tk(\"fields.process_time\"),\n      value: formatNumByUnit(data.process_time! * 10e8, \"ns\"),\n      level: 2,\n      desc: tk(\"fields.process_time.desc\"),\n    },\n    {\n      name: tk(\"fields.local_latch_wait_time\"),\n      value: formatNumByUnit(data.local_latch_wait_time! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.local_latch_wait_time.desc\"),\n    },\n    {\n      name: tk(\"fields.lock_keys_time\"),\n      value: formatNumByUnit(data.lock_keys_time! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.lock_keys_time.desc\"),\n    },\n    {\n      name: tk(\"fields.resolve_lock_time\"),\n      value: formatNumByUnit(data.resolve_lock_time! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.resolve_lock_time.desc\"),\n    },\n    {\n      name: tk(\"fields.wait_ts\"),\n      value: formatNumByUnit(data.wait_ts! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.wait_ts.desc\"),\n    },\n    {\n      name: tk(\"fields.get_commit_ts_time\"),\n      value: formatNumByUnit(data.get_commit_ts_time! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.get_commit_ts_time.desc\"),\n    },\n    {\n      name: tk(\"fields.prewrite_time\"),\n      value: formatNumByUnit(data.prewrite_time! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.prewrite_time.desc\"),\n    },\n    {\n      name: tk(\"fields.commit_time\"),\n      value: formatNumByUnit(data.commit_time! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.commit_time.desc\"),\n    },\n    {\n      name: tk(\"fields.backoff_time\"),\n      value: formatNumByUnit(data.backoff_time! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.backoff_time.desc\"),\n    },\n    {\n      name: tk(\"fields.commit_backoff_time\"),\n      value: formatNumByUnit(data.commit_backoff_time! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.commit_backoff_time.desc\"),\n    },\n    {\n      name: tk(\"fields.exec_retry_time\"),\n      value: formatNumByUnit(data.exec_retry_time! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.exec_retry_time.desc\"),\n    },\n    {\n      name: tk(\"fields.write_sql_response_total\"),\n      value: formatNumByUnit(data.write_sql_response_total! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.write_sql_response_total.desc\"),\n    },\n    {\n      name: tk(\"fields.wait_prewrite_binlog_time\"),\n      value: formatNumByUnit(data.wait_prewrite_binlog_time! * 10e8, \"ns\"),\n      level: 1,\n      desc: tk(\"fields.wait_prewrite_binlog_time.desc\"),\n    },\n  ]\n}\n\nexport function DetailTime({ data }: { data: SlowqueryModel }) {\n  const { tk } = useTn(\"slow-query\")\n  const infoData = useMemo(() => getData(data, tk), [data, tk])\n  return <InfoTable data={infoData} />\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/detail/detail-txn.tsx",
    "content": "import {\n  InfoModel,\n  InfoTable,\n} from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport {\n  formatNumByUnit,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMemo } from \"react\"\n\nimport { SlowqueryModel } from \"../../models\"\n\nfunction getData(\n  data: SlowqueryModel,\n  tk: (key: string) => string,\n): InfoModel[] {\n  return [\n    {\n      name: tk(\"fields.txn_start_ts\"),\n      value: data.txn_start_ts!,\n      desc: tk(\"fields.txn_start_ts.desc\"),\n    },\n    {\n      name: tk(\"fields.write_keys\"),\n      value: formatNumByUnit(data.write_keys || 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.write_size\"),\n      value: formatNumByUnit(data.write_size || 0, \"bytes\"),\n    },\n    {\n      name: tk(\"fields.prewrite_region\"),\n      value: formatNumByUnit(data.prewrite_region || 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.txn_retry\"),\n      value: formatNumByUnit(data.txn_retry || 0, \"short\"),\n    },\n  ]\n}\n\nexport function DetailTxn({ data }: { data: SlowqueryModel }) {\n  const { tk } = useTn(\"slow-query\")\n  const infoData = useMemo(() => getData(data, tk), [data, tk])\n  return <InfoTable data={infoData} />\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/detail/index.tsx",
    "content": "import { LoadingSkeleton } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { ActionIcon, Group, Stack, Typography } from \"@tidbcloud/uikit\"\nimport { IconChevronLeft } from \"@tidbcloud/uikit/icons\"\n\nimport { useAppContext } from \"../../ctx\"\nimport { useDetailData } from \"../../utils/use-data\"\n\nimport { DetailTabs } from \"./detail-tabs\"\nimport { DetailPlan } from \"./plan\"\nimport { DetailQuery } from \"./query\"\nimport { RelatedStatementButton } from \"./related-statement-button\"\nimport { SqlHistory } from \"./sql-history\"\nimport { SqlLimit } from \"./sql-limit\"\n\nexport function Detail() {\n  const ctx = useAppContext()\n\n  const { data: detailData, isLoading } = useDetailData()\n  const { tt } = useTn(\"slow-query\")\n\n  return (\n    <Stack>\n      {ctx.cfg.showDetailBack !== false && (\n        <Group wrap=\"nowrap\">\n          <ActionIcon\n            aria-label=\"Navigate Back\"\n            variant=\"default\"\n            onClick={ctx.actions.backToList}\n          >\n            <IconChevronLeft size={20} />\n          </ActionIcon>\n          <Typography variant=\"title-lg\">{tt(\"Slow Query Detail\")}</Typography>\n          <Group ml=\"auto\">\n            <RelatedStatementButton />\n          </Group>\n        </Group>\n      )}\n\n      {isLoading && <LoadingSkeleton />}\n\n      {detailData && (\n        <Stack>\n          <DetailQuery sql={detailData.query || \"\"} />\n\n          {/* <RelatedStatementLink dbName={detailData.db!} /> */}\n\n          <SqlHistory sqlDigest={detailData.digest!} />\n          <SqlLimit sqlDigest={detailData.digest!} />\n\n          {detailData.plan && <DetailPlan plan={detailData.plan} />}\n\n          <DetailTabs data={detailData} />\n        </Stack>\n      )}\n    </Stack>\n  )\n}\n\nexport { RelatedStatementButton }\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/detail/plan.tsx",
    "content": "import { PlanTable } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Card, Stack, Tabs, Title } from \"@tidbcloud/uikit\"\nimport { CodeBlock } from \"@tidbcloud/uikit/biz\"\nimport { useMemo } from \"react\"\n\nexport function DetailPlan({ plan }: { plan: string }) {\n  const { tt } = useTn(\"slow-query\")\n  const tabs = useMemo(() => {\n    return [\n      {\n        label: tt(\"Text\"),\n        value: \"text\",\n        component: (\n          <CodeBlock\n            codeRender={(content) => <pre>{content}</pre>}\n            foldProps={{\n              persistenceKey: \"slow-query.detail.plan\",\n              iconVisible: true,\n            }}\n          >\n            {plan}\n          </CodeBlock>\n        ),\n      },\n      {\n        label: tt(\"Table\"),\n        value: \"table\",\n        component: <PlanTable plan={plan} />,\n      },\n    ]\n  }, [plan, tt])\n\n  return (\n    <Card shadow=\"xs\" p=\"md\">\n      <Stack gap=\"xs\">\n        <Title order={5}>{tt(\"Plan\")}</Title>\n\n        <Tabs defaultValue={tabs[0].value}>\n          <Tabs.List mb=\"md\">\n            {tabs.map((tab) => (\n              <Tabs.Tab key={tab.value} value={tab.value}>\n                {tab.label}\n              </Tabs.Tab>\n            ))}\n          </Tabs.List>\n          {tabs.map((tab) => (\n            <Tabs.Panel key={tab.value} value={tab.value}>\n              {tab.component}\n            </Tabs.Panel>\n          ))}\n        </Tabs>\n      </Stack>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/detail/query.tsx",
    "content": "import { formatSql, useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Card, Stack, Title } from \"@tidbcloud/uikit\"\nimport { CodeBlock } from \"@tidbcloud/uikit/biz\"\nimport { useMemo } from \"react\"\n\nexport function DetailQuery({ sql }: { sql: string }) {\n  const formattedSQL = useMemo(() => formatSql(sql), [sql])\n\n  const { tt } = useTn(\"slow-query\")\n\n  return (\n    <Card shadow=\"xs\" p=\"md\">\n      <Stack gap=\"xs\">\n        <Title order={5}>{tt(\"Query\")}</Title>\n\n        <CodeBlock\n          language=\"sql\"\n          foldProps={{\n            persistenceKey: \"slowquery.detail.query\",\n            iconVisible: true,\n          }}\n        >\n          {formattedSQL}\n        </CodeBlock>\n      </Stack>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/detail/related-statement-button.tsx",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Button, Tooltip } from \"@tidbcloud/uikit\"\nimport { useMemo } from \"react\"\n\nimport { useAppContext } from \"../../ctx\"\nimport { useDetailUrlState } from \"../../shared-state/detail-url-state\"\nimport { useDetailData } from \"../../utils/use-data\"\n\nexport function RelatedStatementButton() {\n  const ctx = useAppContext()\n  const { id } = useDetailUrlState()\n  const { data: detailData } = useDetailData()\n  const statementId = useMemo(() => {\n    const [sqlDigest, _connectionId, _timestamp, from, to] = id.split(\",\")\n    return [from, to, sqlDigest, detailData?.db].join(\",\")\n  }, [id, detailData?.db])\n  const { tt } = useTn(\"slow-query\")\n\n  if (!detailData) {\n    return null\n  }\n\n  return (\n    <Tooltip\n      label={tt(\n        \"View related statement in statement page, but it may be evicted already, so the result maybe empty\",\n      )}\n    >\n      <Button\n        variant=\"default\"\n        onClick={() => ctx.actions.openStatement(statementId)}\n      >\n        {tt(\"View related statement\")}\n      </Button>\n    </Tooltip>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/detail/related-statement-link.tsx",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Anchor, Card, Group } from \"@tidbcloud/uikit\"\nimport { IconLinkExternal01 } from \"@tidbcloud/uikit/icons\"\nimport { useMemo } from \"react\"\n\nimport { useAppContext } from \"../../ctx\"\nimport { useDetailUrlState } from \"../../shared-state/detail-url-state\"\n\nexport function RelatedStatementLink({ dbName }: { dbName: string }) {\n  const { id } = useDetailUrlState()\n  const ctx = useAppContext()\n  const statementId = useMemo(() => {\n    const [sqlDigest, _connectionId, _timestamp, from, to] = id.split(\",\")\n    return [from, to, sqlDigest, dbName].join(\",\")\n  }, [id, dbName])\n  const { tt } = useTn(\"slow-query\")\n\n  return (\n    <Card shadow=\"xs\" p=\"md\">\n      <Anchor\n        onClick={() => {\n          ctx.actions.openStatement(statementId)\n        }}\n        w=\"fit-content\"\n      >\n        <Group gap={4}>\n          {tt(\n            \"View related statement in statement page (but it may be evicted already)\",\n          )}\n          <IconLinkExternal01 />\n        </Group>\n      </Anchor>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/detail/sql-history.tsx",
    "content": "import { TimeRange } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMemo } from \"react\"\n\nimport { AppProvider, SqlHistoryCard } from \"../../../_shared/sql-history\"\nimport { useAppContext } from \"../../ctx\"\nimport { useDetailUrlState } from \"../../shared-state/detail-url-state\"\n\nexport function SqlHistory({ sqlDigest }: { sqlDigest: string }) {\n  const ctx = useAppContext()\n\n  const { id } = useDetailUrlState()\n  const initialTimeRange = useMemo<TimeRange>(() => {\n    const [_sqlDigest, _connectionId, _timestamp, from, to] = id.split(\",\")\n    return { type: \"absolute\", value: [Number(from), Number(to)] }\n  }, [id])\n\n  const ctxValue = useMemo(\n    () => ({\n      ...ctx,\n      cfg: {\n        parentAppName: \"slow-query\",\n        sqlDigest,\n        initialTimeRange,\n        timeRangeMaxDuration: 24 * 60 * 60,\n      },\n    }),\n    [ctx, sqlDigest, initialTimeRange],\n  )\n\n  return (\n    <AppProvider ctxValue={ctxValue}>\n      <SqlHistoryCard />\n    </AppProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/detail/sql-limit.tsx",
    "content": "import { useMemo } from \"react\"\n\nimport { AppProvider, SqlLimitCard } from \"../../../_shared/sql-limit\"\nimport { useAppContext } from \"../../ctx\"\n\nexport function SqlLimit({ sqlDigest }: { sqlDigest: string }) {\n  const ctx = useAppContext()\n\n  const ctxValue = useMemo(\n    () => ({\n      ...ctx,\n      sqlDigest,\n    }),\n    [ctx, sqlDigest],\n  )\n\n  return (\n    <AppProvider ctxValue={ctxValue}>\n      <SqlLimitCard />\n    </AppProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/list/advanced-filters-modal.tsx",
    "content": "import { AdvancedFiltersModal as AFModal } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMemo } from \"react\"\n\nimport { useAppContext } from \"../../ctx\"\nimport { useListUrlState } from \"../../shared-state/list-url-state\"\nimport { useAdvancedFilterNamesData } from \"../../utils/use-data\"\n\nexport function AdvancedFiltersModal() {\n  const ctx = useAppContext()\n  const { data: availableFiltersData } = useAdvancedFilterNamesData()\n  const { advancedFilters, setAdvancedFilters } = useListUrlState()\n  const { tk } = useTn(\"slow-query\")\n\n  const availableFilters = useMemo(\n    () =>\n      (availableFiltersData || []).map((f) => ({\n        label: tk(`fields.${f}`),\n        value: f,\n      })),\n    [availableFiltersData, tk],\n  )\n\n  function handleReqFilterInfo(name: string) {\n    return ctx.api.getAdvancedFilterInfo({ name })\n  }\n\n  return (\n    <AFModal\n      availableFilters={availableFilters}\n      advancedFilters={advancedFilters}\n      onUpdateFilters={setAdvancedFilters}\n      reqFilterInfo={handleReqFilterInfo}\n    />\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/list/cols-select.tsx",
    "content": "import { ColumnMultiSelect } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useMemo } from \"react\"\n\nimport { useListUrlState } from \"../../shared-state/list-url-state\"\nimport { useAvailableFieldsData } from \"../../utils/use-data\"\n\nimport { useListTableColumns } from \"./cols\"\n\nexport function ColsSelect() {\n  const { cols, setCols } = useListUrlState()\n  const { data: availableFields } = useAvailableFieldsData()\n  const tableColumns = useListTableColumns()\n\n  const colsData = useMemo(() => {\n    return tableColumns\n      .filter((f) => f.id !== undefined)\n      .filter((f) => availableFields?.includes(f.id!))\n      .map((f) => ({ label: f.header, val: f.id! }))\n  }, [availableFields, tableColumns])\n\n  function handleColsChange(newCols: string[]) {\n    // to avoid conflict with the default value (\"query,timestamp,query_time,memory_max\") when cols is no value\n    if (newCols.length === 0) {\n      setCols([\"empty\"])\n    } else {\n      setCols(newCols)\n    }\n  }\n\n  return (\n    <ColumnMultiSelect\n      data={colsData}\n      value={cols}\n      onChange={handleColsChange}\n      onReset={() => setCols([])}\n    />\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/list/cols.tsx",
    "content": "import { SQLWithHover } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { Trans, useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Box, Kbd, Typography, openConfirmModal } from \"@tidbcloud/uikit\"\nimport { useMemo } from \"react\"\n\nimport { TableColsFactory } from \"../../../_shared/cols-factory\"\nimport { useAppContext } from \"../../ctx\"\nimport { SlowqueryModel } from \"../../models\"\nimport {\n  useSelectedSlowQueryState,\n  useTimeRangeValueState,\n} from \"../../shared-state/memory-state\"\n\nconst REMEMBER_KEY = \"slow-query.press_ctrl_to_open_in_new_tab.tip.remember\"\n\n// @ts-expect-error @typescript-eslint/no-unused-vars\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction useLocales() {\n  const { tt } = useTn(\"slow-query\")\n  // used for gogocode to scan and generate en.json before build\n  tt(\n    \"When opening the detail page, you can press <kbd>Ctrl</kbd> or <kbd>⌘</kbd> to view it in a new tab, or <kbd>Shift</kbd> to view it in a new window.\",\n  )\n}\n\nfunction SqlCell({ row }: { row: SlowqueryModel }) {\n  const { tt } = useTn(\"slow-query\")\n  const ctx = useAppContext()\n\n  const trv = useTimeRangeValueState((s) => s.trv)\n  const setSelectedSlowQuery = useSelectedSlowQueryState(\n    (s) => s.setSelectedSlowQuery,\n  )\n\n  function handleClick(ev: React.MouseEvent) {\n    const { digest, connection_id, timestamp } = row\n    const slowQueryId = [digest, connection_id, timestamp].join(\",\")\n    setSelectedSlowQuery(slowQueryId)\n\n    const detailId = [slowQueryId, trv[0], trv[1]].join(\",\")\n\n    const newTab = ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey\n    // if the user don't press the ctrl/cmd/shift/alt and don't know this operation before\n    // we should show a confirm dialog to tell the user this tip\n    // after he know it, we won't show it again\n    if (!newTab) {\n      const remember = localStorage.getItem(REMEMBER_KEY)\n      if (remember !== \"true\") {\n        openConfirmModal({\n          title: tt(\"Tips\"),\n          children: (\n            <Typography>\n              <Trans\n                ns=\"slow-query\"\n                i18nKey={\n                  \"When opening the detail page, you can press <kbd>Ctrl</kbd> or <kbd>⌘</kbd> to view it in a new tab, or <kbd>Shift</kbd> to view it in a new window.\"\n                }\n                components={{ kbd: <Kbd /> }}\n              />\n            </Typography>\n          ),\n          labels: {\n            confirm: tt(\"I got it\"),\n            cancel: tt(\"Tell me again next time\"),\n          },\n          onConfirm: () => {\n            localStorage.setItem(REMEMBER_KEY, \"true\")\n          },\n        })\n      }\n    }\n\n    ctx.actions.openDetail(detailId, newTab)\n  }\n\n  return (\n    <Box sx={{ cursor: \"pointer\" }} onClick={handleClick} w=\"100%\">\n      <SQLWithHover sql={row.query!} />\n    </Box>\n  )\n}\n\nexport function useListTableColumns() {\n  const { tk } = useTn(\"slow-query\")\n  const columns = useMemo(() => {\n    const tcf = new TableColsFactory<SlowqueryModel>(tk)\n    return tcf.columns([\n      // basic\n      tcf.text(\"query\").patchConfig({\n        minSize: 600,\n        accessorFn: (row) => <SqlCell row={row} />,\n      }),\n      tcf.text(\"digest\"),\n      tcf.text(\"instance\"),\n      tcf.text(\"db\"),\n      tcf.text(\"connection_id\"),\n      tcf.timestamp(\"timestamp\"),\n      tcf.number(\"query_time\", \"s\"),\n      tcf.number(\"parse_time\", \"s\"),\n      tcf.number(\"compile_time\", \"s\"),\n      tcf.number(\"process_time\", \"s\"),\n      tcf.number(\"memory_max\", \"bytes\"),\n      tcf.number(\"disk_max\", \"bytes\"),\n      tcf.text(\"txn_start_ts\"),\n      tcf.text(\"success\").patchConfig({\n        accessorFn: (row) => (row.success === 1 ? \"Yes\" : \"No\"),\n      }),\n      tcf.text(\"is_internal\").patchConfig({\n        accessorFn: (row) => (row.is_internal === 1 ? \"Yes\" : \"No\"),\n      }),\n      tcf.text(\"prepared\").patchConfig({\n        accessorFn: (row) => (row.prepared === 1 ? \"Yes\" : \"No\"),\n      }),\n      tcf.text(\"index_names\"),\n      tcf.text(\"stats\"),\n      tcf.text(\"backoff_types\"),\n      // connection\n      tcf.text(\"user\"),\n      tcf.text(\"host\"),\n      // time\n      tcf.number(\"wait_time\", \"s\"),\n      tcf.number(\"backoff_time\", \"s\"),\n      tcf.number(\"get_commit_ts_time\", \"s\"),\n      tcf.number(\"local_latch_wait_time\", \"s\"),\n      tcf.number(\"prewrite_time\", \"s\"),\n      tcf.number(\"commit_time\", \"s\"),\n      tcf.number(\"commit_backoff_time\", \"s\"),\n      tcf.number(\"resolve_lock_time\", \"s\"),\n      // cop\n      tcf.number(\"cop_proc_avg\", \"s\"),\n      tcf.number(\"cop_proc_max\", \"s\"),\n      tcf.number(\"cop_proc_p90\", \"s\"),\n      tcf.number(\"cop_wait_avg\", \"s\"),\n      tcf.number(\"cop_wait_max\", \"s\"),\n      tcf.number(\"cop_wait_p90\", \"s\"),\n      // tcf.number(\"cop_time\", \"s\"),\n      tcf.number(\"request_count\", \"short\"),\n      tcf.number(\"process_keys\", \"short\"),\n      tcf.number(\"total_keys\", \"short\"),\n      tcf.text(\"cop_proc_addr\"),\n      tcf.text(\"cop_wait_addr\"),\n      // transaction\n      tcf.number(\"write_keys\", \"short\"),\n      tcf.number(\"write_size\", \"bytes\"),\n      tcf.number(\"prewrite_region\", \"short\"),\n      tcf.number(\"txn_retry\", \"short\"),\n      // rocksdb\n      tcf.number(\"rocksdb_delete_skipped_count\", \"short\"),\n      tcf.number(\"rocksdb_key_skipped_count\", \"short\"),\n      tcf.number(\"rocksdb_block_cache_hit_count\", \"short\"),\n      tcf.number(\"rocksdb_block_read_count\", \"short\"),\n      tcf.number(\"rocksdb_block_read_byte\", \"bytes\"),\n      // resource control\n      tcf.number(\"ru\", \"short\"), // @todo: fix\n      tcf.text(\"resource_group\"),\n      tcf.number(\"time_queued_by_rc\", \"s\"),\n      tcf.number(\"ia_remote_read_segment_size\", \"bytes\"),\n      tcf.number(\"ia_remote_read_segment_wait_time\", \"s\"),\n    ])\n  }, [tk])\n\n  return columns\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/list/filters-with-advanced.tsx",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Group } from \"@tidbcloud/uikit\"\n\nimport {\n  MemoryStateResetButton,\n  UrlStateSearchInput,\n  UrlStateTimeRangePicker,\n} from \"../../../_shared/state-filters\"\n\nimport { AdvancedFiltersModal } from \"./advanced-filters-modal\"\n\nexport function FiltersWithAdvanced() {\n  const { tt } = useTn(\"slow-query\")\n\n  return (\n    <Group>\n      <UrlStateTimeRangePicker />\n      <UrlStateSearchInput placeholder={tt(\"Find SQL text\")} />\n      <AdvancedFiltersModal />\n      <MemoryStateResetButton text={tt(\"Clear Filters\")} />\n    </Group>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/list/filters.tsx",
    "content": "import { FilterMultiSelect } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Group, Select, TextInput } from \"@tidbcloud/uikit\"\n\nimport {\n  MemoryStateResetButton,\n  UrlStateSearchInput,\n  UrlStateTimeRangePicker,\n} from \"../../../_shared/state-filters\"\nimport { useListUrlState } from \"../../shared-state/list-url-state\"\nimport { useDbsData, useRuGroupsData } from \"../../utils/use-data\"\n\nconst SLOW_QUERY_LIMIT = [100, 200, 500, 1000].map((l) => ({\n  value: `${l}`,\n  label: `Limit ${l}`,\n}))\n\nfunction LimitSelect() {\n  const { limit, setLimit } = useListUrlState()\n\n  return (\n    <Select\n      w={160}\n      value={String(limit)}\n      onChange={(v) => setLimit(v!)}\n      data={SLOW_QUERY_LIMIT}\n    />\n  )\n}\n\nfunction DBsSelect() {\n  const { dbs, setDbs } = useListUrlState()\n  const { data: dbsData } = useDbsData()\n\n  return (\n    dbsData &&\n    dbsData.length > 0 && (\n      <FilterMultiSelect\n        kind=\"Databases\"\n        data={dbsData}\n        value={dbs}\n        onChange={setDbs}\n        width={200}\n      />\n    )\n  )\n}\n\nfunction RuGroupsSelect() {\n  const { ruGroups, setRuGroups } = useListUrlState()\n  const { data: ruGroupsData } = useRuGroupsData()\n\n  // ignore `default` resource group\n  return (\n    ruGroupsData &&\n    ruGroupsData.length > 1 && (\n      <FilterMultiSelect\n        kind=\"Resource Groups\"\n        data={ruGroupsData}\n        value={ruGroups}\n        onChange={setRuGroups}\n        width={240}\n      />\n    )\n  )\n}\n\nfunction SqlDigestInput() {\n  const { tt } = useTn(\"slow-query\")\n  const { sqlDigest } = useListUrlState()\n\n  return (\n    sqlDigest && (\n      <TextInput\n        w={200}\n        defaultValue={sqlDigest}\n        placeholder={tt(\"SQL digest\")}\n        disabled={true}\n      />\n    )\n  )\n}\n\nexport function Filters() {\n  const { tt } = useTn(\"slow-query\")\n\n  return (\n    <Group>\n      <UrlStateTimeRangePicker />\n      <LimitSelect />\n      <DBsSelect />\n      <RuGroupsSelect />\n      <SqlDigestInput />\n      <UrlStateSearchInput placeholder={tt(\"Find SQL text\")} />\n      <MemoryStateResetButton text={tt(\"Clear Filters\")} />\n    </Group>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/list/index.tsx",
    "content": "import { Group, Stack, Title } from \"@tidbcloud/uikit\"\n\nimport { useAppContext } from \"../../ctx\"\n\nimport { ColsSelect } from \"./cols-select\"\nimport { FiltersWithAdvanced } from \"./filters-with-advanced\"\nimport { RefreshButton } from \"./refresh-button\"\nimport { ListTable } from \"./table\"\nimport { TimeRangeClipAlert } from \"./time-range-clip-alert\"\n\nexport function List() {\n  const ctx = useAppContext()\n\n  return (\n    <Stack>\n      {ctx.cfg.title && (\n        <Title order={1} mb=\"md\">\n          {ctx.cfg.title}\n        </Title>\n      )}\n\n      <Group>\n        <FiltersWithAdvanced />\n        <Group ml=\"auto\">\n          <ColsSelect />\n          <RefreshButton />\n        </Group>\n      </Group>\n\n      <TimeRangeClipAlert />\n\n      <ListTable />\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/list/refresh-button.tsx",
    "content": "import { ActionIcon } from \"@tidbcloud/uikit\"\nimport { IconRefreshCw02 } from \"@tidbcloud/uikit/icons\"\n\nimport { useListData } from \"../../utils/use-data\"\n\nexport function RefreshButton() {\n  const { refetch: reloadList } = useListData()\n\n  return (\n    <ActionIcon\n      onClick={() => {\n        reloadList()\n      }}\n    >\n      <IconRefreshCw02 size={16} />\n    </ActionIcon>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/list/table.tsx",
    "content": "import {\n  useProTablePaginationState,\n  useProTableSortState,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { ProTable } from \"@tidbcloud/uikit/biz\"\nimport { useMemo } from \"react\"\n\nimport { useListUrlState } from \"../../shared-state/list-url-state\"\nimport { useSelectedSlowQueryState } from \"../../shared-state/memory-state\"\nimport { useListData } from \"../../utils/use-data\"\n\nimport { useListTableColumns } from \"./cols\"\n\n// @todo: make it resuable, resolve locales issue\nconst usePaginationConfigs = () => {\n  const { tt } = useTn(\"slow-query\")\n\n  return {\n    showTotal: true,\n    showRowsPerPage: true,\n    rowsPerPageOptions: [10, 15, 20, 30].map((value) => ({\n      value: String(value),\n      label: `${value} / ${tt(\"page\")}`,\n    })),\n    localization: {\n      total: `${tt(\"total\")}: `,\n    },\n  }\n}\n\nexport function ListTable() {\n  const { data, isLoading } = useListData()\n  const tableColumns = useListTableColumns()\n  const {\n    sortRule,\n    setSortRule,\n    pagination,\n    setPagination,\n    cols: visibleCols,\n  } = useListUrlState()\n  const { sortingState, setSortingState } = useProTableSortState(\n    sortRule,\n    setSortRule,\n  )\n  const { paginationState, setPaginationState } = useProTablePaginationState(\n    pagination,\n    setPagination,\n  )\n  const selectedSlowQueryId = useSelectedSlowQueryState((s) => s.slowQueryId)\n\n  const columnVisibility = useMemo(() => {\n    return tableColumns\n      .map((c) => c.id)\n      .filter((f) => f !== undefined)\n      .reduce(\n        (acc, col) => {\n          acc[col] = visibleCols.includes(col) || visibleCols.includes(\"all\")\n          return acc\n        },\n        {} as Record<string, boolean>,\n      )\n  }, [tableColumns, visibleCols])\n\n  const { tt } = useTn(\"slow-query\")\n\n  const paginationConfig = usePaginationConfigs()\n\n  return (\n    <ProTable\n      layoutMode=\"grid\"\n      enableColumnResizing\n      enableColumnPinning\n      enableSorting\n      manualSorting\n      sortDescFirst\n      onSortingChange={setSortingState}\n      manualPagination\n      onPaginationChange={setPaginationState}\n      pagination={paginationConfig}\n      rowCount={data?.total ?? 0}\n      state={{\n        isLoading,\n        sorting: sortingState,\n        pagination: paginationState,\n        columnVisibility,\n      }}\n      initialState={{ columnPinning: { left: [\"query\"] } }}\n      mantineTableBodyRowProps={({ row }) => {\n        const { digest, connection_id, timestamp } = row.original\n        const id = `${digest},${connection_id},${timestamp}`\n        return selectedSlowQueryId === id\n          ? {\n              style(theme) {\n                return {\n                  borderWidth: 1,\n                  borderStyle: \"solid\",\n                  borderColor: theme.colors.carbon[7],\n                }\n              },\n            }\n          : {}\n      }}\n      columns={tableColumns}\n      data={data?.items ?? []}\n      emptyMessage={tt(\"No Data\")}\n    />\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/pages/list/time-range-clip-alert.tsx",
    "content": "import { formatTime, useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Alert } from \"@tidbcloud/uikit\"\n\nimport { useTimeRangeValueState } from \"../../shared-state/memory-state\"\n\nexport function TimeRangeClipAlert() {\n  const { tt } = useTn(\"slow-query\")\n  const trv = useTimeRangeValueState((s) => s.trv)\n  const beyondMax = useTimeRangeValueState((s) => s.beyondMax)\n\n  if (!beyondMax) {\n    return null\n  }\n\n  return (\n    <Alert p={8}>\n      {tt(\n        \"Due to the limitation, currently only support to query max 24 hours data, so the actual time range is {{begin}} to {{end}}\",\n        {\n          begin: formatTime(trv[0] * 1000),\n          end: formatTime(trv[1] * 1000),\n        },\n      )}\n    </Alert>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/shared-state/detail-url-state.ts",
    "content": "import { useUrlState } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n\ntype DetailUrlState = Partial<Record<\"id\", string>>\n\nexport function useDetailUrlState() {\n  const [queryParams, _] = useUrlState<DetailUrlState>()\n\n  const id = queryParams.id ?? \"\"\n\n  return {\n    id,\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/shared-state/list-url-state.ts",
    "content": "import {\n  AdvancedFiltersUrlState,\n  PaginationUrlState,\n  SearchUrlState,\n  SortUrlState,\n  TimeRangeUrlState,\n  useAdvancedFiltersUrlState,\n  usePaginationUrlState,\n  useResetFiltersState,\n  useSearchUrlState,\n  useSortUrlState,\n  useTimeRangeUrlState,\n  useUrlState,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useCallback, useEffect, useMemo } from \"react\"\n\ntype ListUrlState = Partial<\n  Record<\"dbs\" | \"ruGroups\" | \"sqlDigest\" | \"limit\" | \"cols\", string>\n> &\n  SortUrlState &\n  PaginationUrlState &\n  TimeRangeUrlState &\n  SearchUrlState &\n  AdvancedFiltersUrlState\n\nexport function useListUrlState() {\n  const [queryParams, setQueryParams] = useUrlState<ListUrlState>()\n  const { sortRule, setSortRule } = useSortUrlState(\"query_time\")\n  const { pagination, setPagination } = usePaginationUrlState()\n  const { timeRange, setTimeRange } = useTimeRangeUrlState()\n  const { term, setTerm } = useSearchUrlState()\n  const { advancedFilters, setAdvancedFilters } = useAdvancedFiltersUrlState()\n\n  // dbs\n  const dbs = useMemo<string[]>(() => {\n    const _dbs = queryParams.dbs\n    return _dbs ? _dbs.split(\",\") : []\n  }, [queryParams.dbs])\n  const setDbs = useCallback(\n    (v: string[]) => {\n      setQueryParams({ dbs: v.join(\",\"), pageIndex: undefined })\n    },\n    [setQueryParams],\n  )\n\n  // ruGroups\n  const ruGroups = useMemo(() => {\n    const _ruGroups = queryParams.ruGroups\n    return _ruGroups ? _ruGroups.split(\",\") : []\n  }, [queryParams.ruGroups])\n  const setRuGroups = useCallback(\n    (v: string[]) => {\n      setQueryParams({ ruGroups: v.join(\",\"), pageIndex: undefined })\n    },\n    [setQueryParams],\n  )\n\n  // sqlDigest\n  const sqlDigest = queryParams.sqlDigest ?? \"\"\n  const setSqlDigest = useCallback(\n    (v?: string) => {\n      setQueryParams({ sqlDigest: v, pageIndex: undefined })\n    },\n    [setQueryParams],\n  )\n\n  // limit\n  const limit = useMemo(() => {\n    const s = queryParams.limit ?? \"100\"\n    const v = parseInt(s)\n    if (isNaN(v)) {\n      return 100\n    }\n    return v\n  }, [queryParams.limit])\n  const setLimit = useCallback(\n    (v: string) => {\n      setQueryParams({ limit: v, pageIndex: undefined })\n    },\n    [setQueryParams],\n  )\n\n  // cols\n  const cols = useMemo<string[]>(() => {\n    const _cols = queryParams.cols || \"query,timestamp,query_time,memory_max\"\n    return _cols ? _cols.split(\",\") : []\n  }, [queryParams.cols])\n  const setCols = useCallback(\n    (v: string[]) => {\n      setQueryParams({ cols: v.join(\",\") })\n    },\n    [setQueryParams],\n  )\n\n  // reset filters, not include sort\n  const resetFilters = useCallback(() => {\n    setQueryParams({\n      from: undefined,\n      to: undefined,\n      dbs: undefined,\n      ruGroups: undefined,\n      sqlDigest: undefined,\n      af: undefined,\n      limit: undefined,\n      term: undefined,\n      pageIndex: undefined,\n    })\n  }, [setQueryParams])\n  const resetVal = useResetFiltersState((s) => s.resetVal)\n  useEffect(() => {\n    if (resetVal > 0) {\n      resetFilters()\n    }\n  }, [resetVal])\n\n  return {\n    timeRange,\n    setTimeRange,\n\n    dbs,\n    setDbs,\n\n    ruGroups,\n    setRuGroups,\n\n    sqlDigest,\n    setSqlDigest,\n\n    limit,\n    setLimit,\n\n    term,\n    setTerm,\n\n    advancedFilters,\n    setAdvancedFilters,\n\n    sortRule,\n    setSortRule,\n    pagination,\n    setPagination,\n\n    cols,\n    setCols,\n\n    queryParams,\n    setQueryParams,\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/shared-state/memory-state.ts",
    "content": "import { TimeRangeValue } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { create } from \"zustand\"\n\n// in slow query list page, when user select a time range that beyond max 24 hours\n// we need to clip the time range and show a alert\ninterface TimeRangeValueState {\n  trv: TimeRangeValue\n  beyondMax: boolean\n  setTRV: (newTRV: TimeRangeValue, beyondMax?: boolean) => void\n}\n\nexport const useTimeRangeValueState = create<TimeRangeValueState>((set) => ({\n  trv: [0, 0],\n  beyondMax: false,\n  setTRV: (newTRV, beyondMax) => set({ trv: newTRV, beyondMax }),\n}))\n\n//-------------------------------------------------------------\n\ninterface SelectedSlowQueryState {\n  slowQueryId: string\n  setSelectedSlowQuery: (slowQueryId: string) => void\n}\n\nexport const useSelectedSlowQueryState = create<SelectedSlowQueryState>(\n  (set) => ({\n    slowQueryId: \"\",\n    setSelectedSlowQuery: (slowQueryId: string) => set({ slowQueryId }),\n  }),\n)\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/utils/constants.ts",
    "content": "export const QUICK_RANGES: number[] = [\n  5 * 60, // 5 mins\n  15 * 60,\n  30 * 60,\n  60 * 60,\n  6 * 60 * 60,\n  12 * 60 * 60,\n  24 * 60 * 60,\n  2 * 24 * 60 * 60,\n  3 * 24 * 60 * 60, // 3 days\n  7 * 24 * 60 * 60, // 7 days\n]\n\nexport const MAX_TIME_RANGE_DURATION_SECONDS = 24 * 60 * 60 // 24 hours\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/slow-query/utils/use-data.ts",
    "content": "import { toTimeRangeValue } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useQuery } from \"@tanstack/react-query\"\n\nimport { useAppContext } from \"../ctx\"\nimport { useDetailUrlState } from \"../shared-state/detail-url-state\"\nimport { useListUrlState } from \"../shared-state/list-url-state\"\nimport { useTimeRangeValueState } from \"../shared-state/memory-state\"\n\nimport { MAX_TIME_RANGE_DURATION_SECONDS } from \"./constants\"\n\nexport function useDbsData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"slow-query\", \"dbs\"],\n    queryFn: () => ctx.api.getDbs(),\n  })\n}\n\nexport function useRuGroupsData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"slow-query\", \"ru-groups\"],\n    queryFn: () => ctx.api.getRuGroups(),\n  })\n}\n\nexport function useListData() {\n  const ctx = useAppContext()\n  const {\n    timeRange,\n    dbs,\n    ruGroups,\n    sqlDigest,\n    limit,\n    term,\n    sortRule,\n    advancedFilters,\n    cols,\n    pagination,\n  } = useListUrlState()\n  const setTRV = useTimeRangeValueState((s) => s.setTRV)\n\n  const query = useQuery({\n    queryKey: [\n      ctx.ctxId,\n      \"slow-query\",\n      \"list\",\n      timeRange,\n      dbs,\n      ruGroups,\n      sqlDigest,\n      term,\n      advancedFilters,\n      cols,\n      limit,\n      sortRule,\n      pagination,\n    ],\n    queryFn: () => {\n      const tr = toTimeRangeValue(timeRange)\n      const beginTime = tr[0]\n      let endTime = tr[1]\n      const beyondMax = endTime - beginTime > MAX_TIME_RANGE_DURATION_SECONDS\n      if (beyondMax) {\n        endTime = beginTime + MAX_TIME_RANGE_DURATION_SECONDS\n      }\n      setTRV([beginTime, endTime], beyondMax)\n      return ctx.api.getSlowQueries({\n        beginTime,\n        endTime,\n        dbs,\n        ruGroups,\n        sqlDigest,\n        limit,\n        term,\n        advancedFilters,\n        fields: cols.filter((c) => c !== \"empty\"),\n        ...sortRule,\n        ...pagination,\n      })\n    },\n  })\n\n  return query\n}\n\nexport function useDetailData() {\n  const ctx = useAppContext()\n  const { id } = useDetailUrlState()\n\n  const query = useQuery({\n    queryKey: [ctx.ctxId, \"slow-query\", \"detail\", id],\n    queryFn: () => {\n      return ctx.api.getSlowQuery({ id })\n    },\n  })\n  return query\n}\n\n// advanced filters\nexport function useAdvancedFilterNamesData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"slow-query\", \"advanced-filter-names\"],\n    queryFn: () => ctx.api.getAdvancedFilterNames(),\n  })\n}\n\nexport function useAdvancedFilterInfoData(name: string) {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"slow-query\", \"advanced-filter-info\", name],\n    queryFn: () => ctx.api.getAdvancedFilterInfo({ name }),\n    enabled: !!name,\n  })\n}\n\n// available fields\nexport function useAvailableFieldsData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"slow-query\", \"available-fields\"],\n    queryFn: () => ctx.api.getAvailableFields(),\n  })\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/ctx/index.tsx",
    "content": "import { AdvancedFilterItem } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { createContext, useContext } from \"react\"\n\nimport { AppApi as SqlHistoryAppApi } from \"../../_shared/sql-history\"\nimport { AppApi as SqlLimitAppApi } from \"../../_shared/sql-limit\"\nimport {\n  AdvancedFilterInfoModel,\n  StatementConfigModel,\n  StatementModel,\n} from \"../models\"\n\n////////////////////////////////\n\ntype AppApi = SqlLimitAppApi &\n  SqlHistoryAppApi & {\n    // config\n    getStmtConfig(): Promise<StatementConfigModel>\n    updateStmtConfig(params: StatementConfigModel): Promise<void>\n\n    // filters\n    getDbs(): Promise<string[]>\n    getStmtKinds(): Promise<string[]>\n    // advanced filters\n    getAdvancedFilterNames(): Promise<string[]>\n    getAdvancedFilterInfo(params: {\n      name: string\n    }): Promise<AdvancedFilterInfoModel>\n    // available fields\n    getAvailableFields(): Promise<string[]>\n\n    // list & detail\n    getStmtList(params: {\n      beginTime: number\n      endTime: number\n      dbs: string[]\n      ruGroups: string[]\n      stmtKinds: string[]\n      term: string\n      advancedFilters: AdvancedFilterItem[]\n      fields: string[]\n      orderBy: string\n      desc: boolean\n      pageSize: number\n      pageIndex: number\n    }): Promise<{ total: number; items: StatementModel[] }>\n    getStmtPlans(params: { id: string }): Promise<StatementModel[]>\n    getStmtPlansDetail(params: {\n      id: string\n      plans: string[]\n    }): Promise<StatementModel>\n\n    // sql plan bind\n    checkPlanBindSupport(): Promise<{ is_support: boolean }>\n    getPlanBindStatus(params: {\n      sqlDigest: string\n      beginTime: number\n      endTime: number\n    }): Promise<string[]>\n    createPlanBind(params: { planDigest: string }): Promise<void>\n    deletePlanBind(params: { sqlDigest: string }): Promise<void>\n  }\n\ntype AppConfig = {\n  title?: string\n  // whether to show back to list page button in the detail page\n  // if set to false, the back button will be hidden\n  // and you need to handle the back action outside of the app by yourself\n  // default is true\n  showDetailBack?: boolean\n}\n\ntype AppActions = {\n  openDetail(id: string, newTab: boolean): void\n  backToList(): void\n  openSlowQueryList(id: string): void\n}\n\nexport type AppCtxValue = {\n  // we use ctxId to be a part of queryKey for react-query,\n  // to differ same requests from different clusters, so this value can be clusterId, or other unique value\n  ctxId: string\n  api: AppApi\n  cfg: AppConfig\n  actions: AppActions\n}\n\nexport const AppContext = createContext<AppCtxValue | null>(null)\n\nexport const useAppContext = () => {\n  const context = useContext(AppContext)\n\n  if (!context) {\n    throw new Error(\"Statement AppContext must be used within a provider\")\n  }\n\n  return context\n}\n\n////////////////////////////////\n\nexport function AppProvider(props: {\n  children: React.ReactNode\n  ctxValue: AppCtxValue\n}) {\n  return (\n    <AppContext.Provider value={props.ctxValue}>\n      {props.children}\n    </AppContext.Provider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/index.ts",
    "content": "export * from \"./models\"\nexport * from \"./ctx\"\n\nexport * from \"./pages/list\"\nexport * from \"./pages/detail\"\n\n// i18n\nimport \"./locales\"\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/locales/en.json",
    "content": "{\n  \"__namespace__\": \"statement\",\n  \"__comment__\": \"this file can be updated by running `pnpm gen:locales` command\",\n  \"fields.avg_affected_rows\": \"Mean Affected Rows\",\n  \"fields.avg_backoff_time\": \"Mean Backoff Time\",\n  \"fields.avg_commit_backoff_time\": \"Mean Commit Backoff Time\",\n  \"fields.avg_commit_time\": \"Mean Commit Time\",\n  \"fields.avg_compile_latency\": \"Mean Compile Latency\",\n  \"fields.avg_disk\": \"Mean Disk\",\n  \"fields.avg_disk.desc\": \"Disk usage of single query\",\n  \"fields.avg_get_commit_ts_time\": \"Mean Get Commit Ts Time\",\n  \"fields.avg_latency\": \"Mean Latency\",\n  \"fields.avg_latency.desc\": \"Execution time of single query\",\n  \"fields.avg_local_latch_wait_time\": \"Mean Local Latch Wait Time\",\n  \"fields.avg_mem\": \"Mean Memory\",\n  \"fields.avg_mem.desc\": \"Memory usage of single query\",\n  \"fields.avg_parse_latency\": \"Mean Parse Latency\",\n  \"fields.avg_prewrite_regions\": \"Mean Prewrite Regions\",\n  \"fields.avg_prewrite_time\": \"Mean Prewrite Time\",\n  \"fields.avg_process_time\": \"Mean Process Time\",\n  \"fields.avg_processed_keys\": \"Mean Visible Versions Per Query\",\n  \"fields.avg_resolve_lock_time\": \"Mean Resolve Lock Time\",\n  \"fields.avg_rocksdb_block_cache_hit_count\": \"Mean RocksDB Block Cache Hits\",\n  \"fields.avg_rocksdb_block_cache_hit_count.desc\": \"Total number of hits from the block cache (RocksDB block_cache_hit_count)\",\n  \"fields.avg_rocksdb_block_read_byte\": \"Mean RocksDB FS Read Size\",\n  \"fields.avg_rocksdb_block_read_byte.desc\": \"Total number of bytes RocksDB read from file (RocksDB block_read_byte)\",\n  \"fields.avg_rocksdb_block_read_count\": \"Mean RocksDB Block Reads\",\n  \"fields.avg_rocksdb_block_read_count.desc\": \"Total number of blocks RocksDB read from file (RocksDB block_read_count)\",\n  \"fields.avg_rocksdb_delete_skipped_count\": \"Mean RocksDB Skipped Deletions\",\n  \"fields.avg_rocksdb_delete_skipped_count.desc\": \"Total number of deleted (a.k.a. tombstone) key versions that are skipped during iteration (RocksDB delete_skipped_count)\",\n  \"fields.avg_rocksdb_key_skipped_count\": \"Mean RocksDB Skipped Keys\",\n  \"fields.avg_rocksdb_key_skipped_count.desc\": \"Total number of keys skipped during iteration (RocksDB key_skipped_count)\",\n  \"fields.avg_ru\": \"Mean RU\",\n  \"fields.avg_ru.desc\": \"The average number of request units (RU) consumed by the statement\",\n  \"fields.avg_time_queued_by_rc\": \"Mean RC Wait Time in Queue\",\n  \"fields.avg_time_queued_by_rc.desc\": \"The average time that the query waits in the resource control's queue (not a wall time)\",\n  \"fields.avg_total_keys\": \"Mean Meet Versions Per Query\",\n  \"fields.avg_total_keys.desc\": \"Meet versions contains overwritten or deleted versions\",\n  \"fields.avg_txn_retry\": \"Mean Transaction Retries\",\n  \"fields.avg_wait_time\": \"Mean Wait Time\",\n  \"fields.avg_write_keys\": \"Mean Written Keys\",\n  \"fields.avg_write_size\": \"Mean Written Data Size\",\n  \"fields.backoff_time\": \"Backoff Retry Time\",\n  \"fields.backoff_time.desc\": \"The waiting time before retry when a query encounters errors that require a retry\",\n  \"fields.binary_plan\": \"Binary Plan\",\n  \"fields.commit_backoff_time\": \"Commit Backoff Retry Time\",\n  \"fields.commit_time\": \"Commit Time\",\n  \"fields.compile_latency\": \"Compile\",\n  \"fields.compile_latency.desc\": \"Time consumed when optimizing the query\",\n  \"fields.digest\": \"Query Template ID\",\n  \"fields.digest.desc\": \"a.k.a. Query digest\",\n  \"fields.digest_text\": \"Statement Template\",\n  \"fields.digest_text.desc\": \"Similar queries have same statement template even for different query parameters\",\n  \"fields.errors_warnings\": \"Errors / Warnings\",\n  \"fields.errors_warnings.desc\": \"Total Errors and Total Warnings\",\n  \"fields.exec_count\": \"Execution Count\",\n  \"fields.exec_count.desc\": \"Total execution count for this kind of statement\",\n  \"fields.first_seen\": \"First Seen\",\n  \"fields.get_commit_ts_time\": \"Get Commit Ts Time\",\n  \"fields.get_commit_ts_time.desc\": \" \",\n  \"fields.index_names\": \"Index Name\",\n  \"fields.index_names.desc\": \"The name of the used index\",\n  \"fields.last_seen\": \"Last Seen\",\n  \"fields.latency\": \"Query\",\n  \"fields.local_latch_wait_time\": \"Local Latch Wait Time\",\n  \"fields.local_latch_wait_time.desc\": \" \",\n  \"fields.max_backoff_time\": \"Max Backoff Time\",\n  \"fields.max_commit_backoff_time\": \"Max Commit Backoff Time\",\n  \"fields.max_commit_time\": \"Max Commit Time\",\n  \"fields.max_compile_latency\": \"Max Compile Latency\",\n  \"fields.max_cop_process_time\": \"Max Coprocess Time\",\n  \"fields.max_cop_wait_time\": \"Max Coprocess Wait Time\",\n  \"fields.max_disk\": \"Max Disk\",\n  \"fields.max_disk.desc\": \"Maximum disk usage of single query\",\n  \"fields.max_get_commit_ts_time\": \"Max Get Commit Ts Time\",\n  \"fields.max_latency\": \"Max Latency\",\n  \"fields.max_local_latch_wait_time\": \"Max Local Latch Wait Time\",\n  \"fields.max_mem\": \"Max Memory\",\n  \"fields.max_mem.desc\": \"Maximum memory usage of single query\",\n  \"fields.max_parse_latency\": \"Max Parse Latency\",\n  \"fields.max_prewrite_regions\": \"Max Prewrite Regions\",\n  \"fields.max_prewrite_time\": \"Max Prewrite Time\",\n  \"fields.max_process_time\": \"Max Process Time\",\n  \"fields.max_processed_keys\": \"Max Visible Versions Per Query\",\n  \"fields.max_resolve_lock_time\": \"Max Resolve Lock Time\",\n  \"fields.max_rocksdb_block_cache_hit_count\": \"Max RocksDB Block Cache Hits\",\n  \"fields.max_rocksdb_block_read_byte\": \"Max RocksDB FS Read Size\",\n  \"fields.max_rocksdb_block_read_count\": \"Max RocksDB Block Reads\",\n  \"fields.max_rocksdb_delete_skipped_count\": \"Max RocksDB Skipped Deletions\",\n  \"fields.max_rocksdb_key_skipped_count\": \"Max RocksDB Skipped Keys\",\n  \"fields.max_ru\": \"Max RU\",\n  \"fields.max_ru.desc\": \"The maximum number of request units (RU) consumed by the statement\",\n  \"fields.max_time_queued_by_rc\": \"Max RC Wait Time in Queue\",\n  \"fields.max_time_queued_by_rc.desc\": \"The maximum time that the query waits in the resource control's queue (not a wall time)\",\n  \"fields.max_total_keys\": \"Max Meet Versions Per Query\",\n  \"fields.max_txn_retry\": \"Max Transaction Retries\",\n  \"fields.max_wait_time\": \"Max Wait Time\",\n  \"fields.max_write_keys\": \"Max Written Keys\",\n  \"fields.max_write_size\": \"Max Written Data Size\",\n  \"fields.min_latency\": \"Min Latency\",\n  \"fields.parse_latency\": \"Parse Time\",\n  \"fields.parse_latency.desc\": \"Time consumed when parsing the query\",\n  \"fields.plan\": \"Execution Plan\",\n  \"fields.plan_cache_hits\": \"Plan Cache Hits Count\",\n  \"fields.plan_cache_hits.desc\": \"Number of times the execution plan cache is hit\",\n  \"fields.plan_count\": \"Plans Count\",\n  \"fields.plan_count.desc\": \"Number of distinct execution plans of this statement in current time range\",\n  \"fields.plan_digest\": \"Plan ID\",\n  \"fields.plan_digest.desc\": \"Different execution plans have different plan ID\",\n  \"fields.plan_hint\": \"Plan Hint\",\n  \"fields.prev_sample_text\": \"Previous Query Sample\",\n  \"fields.prev_sample_text.desc\": \" \",\n  \"fields.prewrite_time\": \"Prewrite Time\",\n  \"fields.process_time\": \"Coprocessor Execution Time\",\n  \"fields.process_time.desc\": \" \",\n  \"fields.query_sample_text\": \"Query Sample\",\n  \"fields.query_time_2\": \"Query Time\",\n  \"fields.query_time_2.desc\": \"The execution time of a query (due to the parallel execution, it may be significantly smaller than the above time)\",\n  \"fields.rc_wait_time\": \"RC Wait Time\",\n  \"fields.rc_wait_time.desc\": \"The total wait time spent in the resource queue (note: {{distro.tikv}} executes requests in parallel so that this is not a wall time)\",\n  \"fields.related_schemas\": \"Database\",\n  \"fields.related_schemas.desc\": \"Related databases of the statement\",\n  \"fields.resolve_lock_time\": \"Resolve Lock Time\",\n  \"fields.resolve_lock_time.desc\": \" \",\n  \"fields.resource_group\": \"Resource Group\",\n  \"fields.resource_group.desc\": \"The resource group that the query belongs to\",\n  \"fields.sample_user\": \"Execution User\",\n  \"fields.sample_user.desc\": \"The user that executes the query (sampled)\",\n  \"fields.schema_name\": \"Execution Database\",\n  \"fields.schema_name.desc\": \"The database used to execute the query\",\n  \"fields.stmt_type\": \"Statement Type\",\n  \"fields.sum_backoff_times\": \"Total Backoff Count\",\n  \"fields.sum_backoff_times.desc\": \" \",\n  \"fields.sum_cop_task_num\": \"Total Coprocessor Tasks\",\n  \"fields.sum_cop_task_num.desc\": \" \",\n  \"fields.sum_errors\": \"Total Errors\",\n  \"fields.sum_latency\": \"Total Latency\",\n  \"fields.sum_latency.desc\": \"Total execution time for this kind of statement\",\n  \"fields.sum_ru\": \"Total RU\",\n  \"fields.sum_ru.desc\": \"The total number of request units (RU) consumed by the statement\",\n  \"fields.sum_warnings\": \"Total Warnings\",\n  \"fields.table_names\": \"Table Names\",\n  \"fields.tidb_cpu_time\": \"{{distro.tidb}} CPU Time\",\n  \"fields.tikv_cpu_time\": \"{{distro.tikv}} CPU Time\",\n  \"fields.total_process_time\": \"Total Execution Time\",\n  \"fields.total_wait_time\": \"Total Wait Time\",\n  \"fields.wait_time\": \"Coprocessor Wait Time\",\n  \"fields.wait_time.desc\": \" \"\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/locales/index.ts",
    "content": "import { addLangsLocales } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n\nimport en from \"./en.json\"\nimport zh from \"./zh.json\"\n\naddLangsLocales({ en, zh })\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/locales/preset.ts",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n\n// @ts-expect-error @typescript-eslint/no-unused-vars\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction useLocales() {\n  const { tk } = useTn(\"statement\")\n  // used for gogocode to scan and generate en.json before build\n  tk(\"fields.table_names\", \"Table Names\")\n  tk(\"fields.related_schemas\", \"Database\")\n  tk(\"fields.related_schemas.desc\", \"Related databases of the statement\")\n  tk(\"fields.plan_digest\", \"Plan ID\")\n  tk(\n    \"fields.plan_digest.desc\",\n    \"Different execution plans have different plan ID\",\n  )\n  tk(\"fields.digest_text\", \"Statement Template\")\n  tk(\n    \"fields.digest_text.desc\",\n    \"Similar queries have same statement template even for different query parameters\",\n  )\n  tk(\"fields.sum_latency\", \"Total Latency\")\n  tk(\n    \"fields.sum_latency.desc\",\n    \"Total execution time for this kind of statement\",\n  )\n  tk(\"fields.exec_count\", \"Execution Count\")\n  tk(\n    \"fields.exec_count.desc\",\n    \"Total execution count for this kind of statement\",\n  )\n  tk(\"fields.plan_count\", \"Plans Count\")\n  tk(\n    \"fields.plan_count.desc\",\n    \"Number of distinct execution plans of this statement in current time range\",\n  )\n  tk(\"fields.plan_cache_hits\", \"Plan Cache Hits Count\")\n  tk(\n    \"fields.plan_cache_hits.desc\",\n    \"Number of times the execution plan cache is hit\",\n  )\n  tk(\"fields.avg_latency\", \"Mean Latency\")\n  tk(\"fields.avg_latency.desc\", \"Execution time of single query\")\n  tk(\"fields.avg_mem\", \"Mean Memory\")\n  tk(\"fields.avg_mem.desc\", \"Memory usage of single query\")\n  tk(\"fields.max_mem\", \"Max Memory\")\n  tk(\"fields.max_mem.desc\", \"Maximum memory usage of single query\")\n  tk(\"fields.avg_disk\", \"Mean Disk\")\n  tk(\"fields.avg_disk.desc\", \"Disk usage of single query\")\n  tk(\"fields.max_disk\", \"Max Disk\")\n  tk(\"fields.max_disk.desc\", \"Maximum disk usage of single query\")\n  tk(\"fields.index_names\", \"Index Name\")\n  tk(\"fields.index_names.desc\", \"The name of the used index\")\n  tk(\"fields.first_seen\", \"First Seen\")\n  tk(\"fields.last_seen\", \"Last Seen\")\n  tk(\"fields.sample_user\", \"Execution User\")\n  tk(\"fields.sample_user.desc\", \"The user that executes the query (sampled)\")\n  tk(\"fields.sum_errors\", \"Total Errors\")\n  tk(\"fields.sum_warnings\", \"Total Warnings\")\n  tk(\"fields.errors_warnings\", \"Errors / Warnings\")\n  tk(\"fields.errors_warnings.desc\", \"Total Errors and Total Warnings\")\n  tk(\"fields.parse_latency\", \"Parse Time\")\n  tk(\"fields.parse_latency.desc\", \"Time consumed when parsing the query\")\n  tk(\"fields.compile_latency\", \"Compile\")\n  tk(\"fields.compile_latency.desc\", \"Time consumed when optimizing the query\")\n  tk(\"fields.wait_time\", \"Coprocessor Wait Time\")\n  tk(\"fields.wait_time.desc\", \" \") // @todo\n  tk(\"fields.process_time\", \"Coprocessor Execution Time\")\n  tk(\"fields.process_time.desc\", \" \") // @todo\n  tk(\"fields.total_process_time\", \"Total Execution Time\")\n  tk(\"fields.total_wait_time\", \"Total Wait Time\")\n  tk(\"fields.backoff_time\", \"Backoff Retry Time\")\n  tk(\n    \"fields.backoff_time.desc\",\n    \"The waiting time before retry when a query encounters errors that require a retry\",\n  )\n  tk(\"fields.get_commit_ts_time\", \"Get Commit Ts Time\")\n  tk(\"fields.get_commit_ts_time.desc\", \" \") // @todo\n  tk(\"fields.local_latch_wait_time\", \"Local Latch Wait Time\")\n  tk(\"fields.local_latch_wait_time.desc\", \" \") // @todo\n  tk(\"fields.resolve_lock_time\", \"Resolve Lock Time\")\n  tk(\"fields.resolve_lock_time.desc\", \" \") // @todo\n  tk(\"fields.prewrite_time\", \"Prewrite Time\")\n  tk(\"fields.commit_time\", \"Commit Time\")\n  tk(\"fields.commit_backoff_time\", \"Commit Backoff Retry Time\")\n  tk(\"fields.latency\", \"Query\")\n  tk(\"fields.query_time_2\", \"Query Time\")\n  tk(\n    \"fields.query_time_2.desc\",\n    \"The execution time of a query (due to the parallel execution, it may be significantly smaller than the above time)\",\n  )\n  tk(\"fields.sum_cop_task_num\", \"Total Coprocessor Tasks\")\n  tk(\"fields.sum_cop_task_num.desc\", \" \") // @todo\n  tk(\"fields.avg_processed_keys\", \"Mean Visible Versions Per Query\")\n  tk(\"fields.max_processed_keys\", \"Max Visible Versions Per Query\")\n  tk(\"fields.avg_total_keys\", \"Mean Meet Versions Per Query\")\n  tk(\n    \"fields.avg_total_keys.desc\",\n    \"Meet versions contains overwritten or deleted versions\",\n  )\n  tk(\"fields.max_total_keys\", \"Max Meet Versions Per Query\")\n  tk(\"fields.avg_affected_rows\", \"Mean Affected Rows\")\n  tk(\"fields.sum_backoff_times\", \"Total Backoff Count\")\n  tk(\"fields.sum_backoff_times.desc\", \" \") // @todo\n  tk(\"fields.avg_write_keys\", \"Mean Written Keys\")\n  tk(\"fields.max_write_keys\", \"Max Written Keys\")\n  tk(\"fields.avg_write_size\", \"Mean Written Data Size\")\n  tk(\"fields.max_write_size\", \"Max Written Data Size\")\n  tk(\"fields.avg_prewrite_regions\", \"Mean Prewrite Regions\")\n  tk(\"fields.max_prewrite_regions\", \"Max Prewrite Regions\")\n  tk(\"fields.avg_txn_retry\", \"Mean Transaction Retries\")\n  tk(\"fields.max_txn_retry\", \"Max Transaction Retries\")\n  tk(\"fields.digest\", \"Query Template ID\")\n  tk(\"fields.digest.desc\", \"a.k.a. Query digest\")\n  tk(\"fields.schema_name\", \"Execution Database\")\n  tk(\"fields.schema_name.desc\", \"The database used to execute the query\")\n  tk(\"fields.query_sample_text\", \"Query Sample\")\n  tk(\"fields.prev_sample_text\", \"Previous Query Sample\")\n  tk(\"fields.prev_sample_text.desc\", \" \") // @todo\n  tk(\"fields.plan\", \"Execution Plan\")\n  tk(\n    \"fields.avg_rocksdb_delete_skipped_count\",\n    \"Mean RocksDB Skipped Deletions\",\n  )\n  tk(\n    \"fields.avg_rocksdb_delete_skipped_count.desc\",\n    \"Total number of deleted (a.k.a. tombstone) key versions that are skipped during iteration (RocksDB delete_skipped_count)\",\n  )\n  tk(\"fields.max_rocksdb_delete_skipped_count\", \"Max RocksDB Skipped Deletions\")\n  tk(\"fields.avg_rocksdb_key_skipped_count\", \"Mean RocksDB Skipped Keys\")\n  tk(\n    \"fields.avg_rocksdb_key_skipped_count.desc\",\n    \"Total number of keys skipped during iteration (RocksDB key_skipped_count)\",\n  )\n  tk(\"fields.max_rocksdb_key_skipped_count\", \"Max RocksDB Skipped Keys\")\n  tk(\n    \"fields.avg_rocksdb_block_cache_hit_count\",\n    \"Mean RocksDB Block Cache Hits\",\n  )\n  tk(\n    \"fields.avg_rocksdb_block_cache_hit_count.desc\",\n    \"Total number of hits from the block cache (RocksDB block_cache_hit_count)\",\n  )\n  tk(\"fields.max_rocksdb_block_cache_hit_count\", \"Max RocksDB Block Cache Hits\")\n  tk(\"fields.avg_rocksdb_block_read_count\", \"Mean RocksDB Block Reads\")\n  tk(\n    \"fields.avg_rocksdb_block_read_count.desc\",\n    \"Total number of blocks RocksDB read from file (RocksDB block_read_count)\",\n  )\n  tk(\"fields.max_rocksdb_block_read_count\", \"Max RocksDB Block Reads\")\n  tk(\"fields.avg_rocksdb_block_read_byte\", \"Mean RocksDB FS Read Size\")\n  tk(\n    \"fields.avg_rocksdb_block_read_byte.desc\",\n    \"Total number of bytes RocksDB read from file (RocksDB block_read_byte)\",\n  )\n  tk(\"fields.max_rocksdb_block_read_byte\", \"Max RocksDB FS Read Size\")\n  tk(\"fields.resource_group\", \"Resource Group\")\n  tk(\n    \"fields.resource_group.desc\",\n    \"The resource group that the query belongs to\",\n  )\n  tk(\"fields.avg_ru\", \"Mean RU\")\n  tk(\n    \"fields.avg_ru.desc\",\n    \"The average number of request units (RU) consumed by the statement\",\n  )\n  tk(\"fields.max_ru\", \"Max RU\")\n  tk(\n    \"fields.max_ru.desc\",\n    \"The maximum number of request units (RU) consumed by the statement\",\n  )\n  tk(\"fields.sum_ru\", \"Total RU\")\n  tk(\n    \"fields.sum_ru.desc\",\n    \"The total number of request units (RU) consumed by the statement\",\n  )\n  tk(\"fields.avg_time_queued_by_rc\", \"Mean RC Wait Time in Queue\")\n  tk(\n    \"fields.avg_time_queued_by_rc.desc\",\n    \"The average time that the query waits in the resource control's queue (not a wall time)\",\n  )\n  tk(\"fields.max_time_queued_by_rc\", \"Max RC Wait Time in Queue\")\n  tk(\n    \"fields.max_time_queued_by_rc.desc\",\n    \"The maximum time that the query waits in the resource control's queue (not a wall time)\",\n  )\n  tk(\"fields.rc_wait_time\", \"RC Wait Time\")\n  tk(\n    \"fields.rc_wait_time.desc\",\n    \"The total wait time spent in the resource queue (note: {{distro.tikv}} executes requests in parallel so that this is not a wall time)\",\n  )\n\n  // additional fields\n  // @todo: refine translation\n  tk(\"fields.max_latency\", \"Max Latency\")\n  tk(\"fields.min_latency\", \"Min Latency\")\n  tk(\"fields.avg_parse_latency\", \"Mean Parse Latency\")\n  tk(\"fields.max_parse_latency\", \"Max Parse Latency\")\n  tk(\"fields.avg_compile_latency\", \"Mean Compile Latency\")\n  tk(\"fields.max_compile_latency\", \"Max Compile Latency\")\n  tk(\"fields.max_cop_process_time\", \"Max Coprocess Time\")\n  tk(\"fields.max_cop_wait_time\", \"Max Coprocess Wait Time\")\n  tk(\"fields.avg_process_time\", \"Mean Process Time\")\n  tk(\"fields.max_process_time\", \"Max Process Time\")\n  tk(\"fields.avg_wait_time\", \"Mean Wait Time\")\n  tk(\"fields.max_wait_time\", \"Max Wait Time\")\n  tk(\"fields.avg_backoff_time\", \"Mean Backoff Time\")\n  tk(\"fields.max_backoff_time\", \"Max Backoff Time\")\n  tk(\"fields.avg_prewrite_time\", \"Mean Prewrite Time\")\n  tk(\"fields.max_prewrite_time\", \"Max Prewrite Time\")\n  tk(\"fields.avg_commit_time\", \"Mean Commit Time\")\n  tk(\"fields.max_commit_time\", \"Max Commit Time\")\n  tk(\"fields.avg_get_commit_ts_time\", \"Mean Get Commit Ts Time\")\n  tk(\"fields.max_get_commit_ts_time\", \"Max Get Commit Ts Time\")\n  tk(\"fields.avg_commit_backoff_time\", \"Mean Commit Backoff Time\")\n  tk(\"fields.max_commit_backoff_time\", \"Max Commit Backoff Time\")\n  tk(\"fields.avg_resolve_lock_time\", \"Mean Resolve Lock Time\")\n  tk(\"fields.max_resolve_lock_time\", \"Max Resolve Lock Time\")\n  tk(\"fields.avg_local_latch_wait_time\", \"Mean Local Latch Wait Time\")\n  tk(\"fields.max_local_latch_wait_time\", \"Max Local Latch Wait Time\")\n  tk(\"fields.stmt_type\", \"Statement Type\")\n  tk(\"fields.plan_hint\", \"Plan Hint\")\n  tk(\"fields.binary_plan\", \"Binary Plan\")\n\n  tk(\"fields.tidb_cpu_time\", \"{{distro.tidb}} CPU Time\")\n  tk(\"fields.tikv_cpu_time\", \"{{distro.tikv}} CPU Time\")\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/locales/zh.json",
    "content": "{\n  \"__namespace__\": \"statement\",\n  \"__comment__\": \"this file can be updated by running `pnpm gen:locales` command\",\n  \"fields.avg_affected_rows\": \"平均影响行数\",\n  \"fields.avg_backoff_time\": \"Mean Backoff Time\",\n  \"fields.avg_commit_backoff_time\": \"Mean Commit Backoff Time\",\n  \"fields.avg_commit_time\": \"Mean Commit Time\",\n  \"fields.avg_compile_latency\": \"Mean Compile Latency\",\n  \"fields.avg_disk\": \"平均磁盘空间\",\n  \"fields.avg_disk.desc\": \"单条 SQL 查询占用的磁盘空间大小\",\n  \"fields.avg_get_commit_ts_time\": \"Mean Get Commit Ts Time\",\n  \"fields.avg_latency\": \"平均耗时\",\n  \"fields.avg_latency.desc\": \"单条 SQL 查询的执行时间\",\n  \"fields.avg_local_latch_wait_time\": \"Mean Local Latch Wait Time\",\n  \"fields.avg_mem\": \"平均内存\",\n  \"fields.avg_mem.desc\": \"单条 SQL 查询的消耗内存大小\",\n  \"fields.avg_parse_latency\": \"Mean Parse Latency\",\n  \"fields.avg_prewrite_regions\": \"Prewrite 平均涉及 Region 个数\",\n  \"fields.avg_prewrite_time\": \"Mean Prewrite Time\",\n  \"fields.avg_process_time\": \"Mean Process Time\",\n  \"fields.avg_processed_keys\": \"单 SQL 查询平均可见版本数\",\n  \"fields.avg_resolve_lock_time\": \"Mean Resolve Lock Time\",\n  \"fields.avg_rocksdb_block_cache_hit_count\": \"RocksDB 缓存平均读次数\",\n  \"fields.avg_rocksdb_block_cache_hit_count.desc\": \"RocksDB 从 Block Cache 缓存中读数据的次数 (block_cache_hit_count)\",\n  \"fields.avg_rocksdb_block_read_byte\": \"RocksDB 文件系统平均读数据量\",\n  \"fields.avg_rocksdb_block_read_byte.desc\": \"RocksDB 从文件系统中读数据的数据量 (block_read_byte)\",\n  \"fields.avg_rocksdb_block_read_count\": \"RocksDB 文件系统平均读次数\",\n  \"fields.avg_rocksdb_block_read_count.desc\": \"RocksDB 从文件系统中读数据的次数 (block_read_count)\",\n  \"fields.avg_rocksdb_delete_skipped_count\": \"RocksDB 已删除 Key 平均扫描数\",\n  \"fields.avg_rocksdb_delete_skipped_count.desc\": \"RocksDB 扫数据时遇到的已删除 (tombstone) Key 数量 (delete_skipped_count)\",\n  \"fields.avg_rocksdb_key_skipped_count\": \"RocksDB Key 平均扫描数\",\n  \"fields.avg_rocksdb_key_skipped_count.desc\": \"RocksDB 扫数据时所有遇到的 Key 数量 (key_skipped_count)\",\n  \"fields.avg_ru\": \"平均 RU\",\n  \"fields.avg_ru.desc\": \"Statement 语句的平均 RU\",\n  \"fields.avg_time_queued_by_rc\": \"RC 平均等待耗时\",\n  \"fields.avg_time_queued_by_rc.desc\": \"SQL 语句在资源组控制队列中平均等待的时间 (Resource Control)（注：{{distro.tikv}} 会并行处理任务，因此该时间不是自然流逝时间）\",\n  \"fields.avg_total_keys\": \"单 SQL 查询平均遇到版本数\",\n  \"fields.avg_total_keys.desc\": \"含已删除或覆盖但未 GC 的版本\",\n  \"fields.avg_txn_retry\": \"事务平均重试次数\",\n  \"fields.avg_wait_time\": \"Mean Wait Time\",\n  \"fields.avg_write_keys\": \"平均写入 Key 个数\",\n  \"fields.avg_write_size\": \"平均写入数据量\",\n  \"fields.backoff_time\": \"重试等待耗时\",\n  \"fields.backoff_time.desc\": \"单个 SQL 查询所有重试累计后计算\",\n  \"fields.binary_plan\": \"Binary Plan\",\n  \"fields.commit_backoff_time\": \"Commit 重试等待耗时\",\n  \"fields.commit_time\": \"Commit 阶段耗时\",\n  \"fields.compile_latency\": \"优化耗时\",\n  \"fields.compile_latency.desc\": \"编译并优化 SQL 查询的耗时\",\n  \"fields.digest\": \"SQL 模板 ID\",\n  \"fields.digest.desc\": \"SQL 模板的唯一标识（SQL 指纹）\",\n  \"fields.digest_text\": \"SQL 模板\",\n  \"fields.digest_text.desc\": \"相似的 SQL 查询即使查询参数不一样也具有相同的 SQL 模板\",\n  \"fields.errors_warnings\": \"错误 / 警告\",\n  \"fields.errors_warnings.desc\": \"累计错误和警告个数\",\n  \"fields.exec_count\": \"执行次数\",\n  \"fields.exec_count.desc\": \"该类 SQL 语句在时间段内被执行的总次数\",\n  \"fields.first_seen\": \"首次出现时间\",\n  \"fields.get_commit_ts_time\": \"取 Commit Ts 耗时\",\n  \"fields.get_commit_ts_time.desc\": \"从 {{distro.pd}} 取递交时间戳（事务号）步骤的耗时\",\n  \"fields.index_names\": \"索引名\",\n  \"fields.index_names.desc\": \"SQL 执行时使用的索引名称\",\n  \"fields.last_seen\": \"最后出现时间\",\n  \"fields.latency\": \"执行耗时\",\n  \"fields.local_latch_wait_time\": \"Local Latch Wait 耗时\",\n  \"fields.local_latch_wait_time.desc\": \"事务在 {{distro.tidb}} 本地与其他事务产生了锁冲突并等待的耗时\",\n  \"fields.max_backoff_time\": \"Max Backoff Time\",\n  \"fields.max_commit_backoff_time\": \"Max Commit Backoff Time\",\n  \"fields.max_commit_time\": \"Max Commit Time\",\n  \"fields.max_compile_latency\": \"Max Compile Latency\",\n  \"fields.max_cop_process_time\": \"Max Coprocess Time\",\n  \"fields.max_cop_wait_time\": \"Max Coprocess Wait Time\",\n  \"fields.max_disk\": \"最大磁盘空间\",\n  \"fields.max_disk.desc\": \"最大单条 SQL 查询占用的磁盘空间大小\",\n  \"fields.max_get_commit_ts_time\": \"Max Get Commit Ts Time\",\n  \"fields.max_latency\": \"Max Latency\",\n  \"fields.max_local_latch_wait_time\": \"Max Local Latch Wait Time\",\n  \"fields.max_mem\": \"最大内存\",\n  \"fields.max_mem.desc\": \"最大单条 SQL 查询消耗内存大小\",\n  \"fields.max_parse_latency\": \"Max Parse Latency\",\n  \"fields.max_prewrite_regions\": \"Prewrite 最大涉及 Region 个数\",\n  \"fields.max_prewrite_time\": \"Max Prewrite Time\",\n  \"fields.max_process_time\": \"Max Process Time\",\n  \"fields.max_processed_keys\": \"单 SQL 查询最大可见版本数\",\n  \"fields.max_resolve_lock_time\": \"Max Resolve Lock Time\",\n  \"fields.max_rocksdb_block_cache_hit_count\": \"RocksDB 缓存最大读次数\",\n  \"fields.max_rocksdb_block_read_byte\": \"RocksDB 文件系统最大读数据量\",\n  \"fields.max_rocksdb_block_read_count\": \"RocksDB 文件系统最大读次数\",\n  \"fields.max_rocksdb_delete_skipped_count\": \"RocksDB 已删除 Key 最大扫描数\",\n  \"fields.max_rocksdb_key_skipped_count\": \"RocksDB Key 最大扫描数\",\n  \"fields.max_ru\": \"最大 RU\",\n  \"fields.max_ru.desc\": \"该 Statement 执行中使用过的最大 RU\",\n  \"fields.max_time_queued_by_rc\": \"RC 最大等待耗时\",\n  \"fields.max_time_queued_by_rc.desc\": \"SQL 语句在资源组控制队列中最大等待的时间 (Resource Control)（注：{{distro.tikv}} 会并行处理任务，因此该时间不是自然流逝时间）\",\n  \"fields.max_total_keys\": \"单 SQL 查询最大遇到版本数\",\n  \"fields.max_txn_retry\": \"事务最大重试次数\",\n  \"fields.max_wait_time\": \"Max Wait Time\",\n  \"fields.max_write_keys\": \"最大写入 Key 个数\",\n  \"fields.max_write_size\": \"最大写入数据量\",\n  \"fields.min_latency\": \"Min Latency\",\n  \"fields.parse_latency\": \"解析耗时\",\n  \"fields.parse_latency.desc\": \"解析 SQL 查询的耗时\",\n  \"fields.plan\": \"执行计划\",\n  \"fields.plan_cache_hits\": \"计划缓存命中次数\",\n  \"fields.plan_cache_hits.desc\": \"该类 SQL 语句在时间段内的计划缓存命中次数\",\n  \"fields.plan_count\": \"计划数\",\n  \"fields.plan_count.desc\": \"该类 SQL 语句在时间段内的不同执行计划数量\",\n  \"fields.plan_digest\": \"执行计划 ID\",\n  \"fields.plan_digest.desc\": \"不同的执行计划有不同的 ID\",\n  \"fields.plan_hint\": \"Plan Hint\",\n  \"fields.prev_sample_text\": \"前一条 SQL 查询样例\",\n  \"fields.prev_sample_text.desc\": \"一般来说你可能只需要看 COMMIT 语句的前一条 SQL 查询\",\n  \"fields.prewrite_time\": \"Prewrite 阶段耗时\",\n  \"fields.process_time\": \"Coprocessor 执行耗时\",\n  \"fields.process_time.desc\": \"SQL 查询在 {{distro.tikv}} Coprocessor 上的执行耗时，单个 SQL 查询所有 Coprocessor 任务累计后计算\",\n  \"fields.query_sample_text\": \"SQL 查询样例\",\n  \"fields.query_time_2\": \"SQL 执行时间\",\n  \"fields.query_time_2.desc\": \"由于存在并行执行，因此 SQL 执行时间可能远小于上述各项时间\",\n  \"fields.rc_wait_time\": \"RC 资源控制等待累积耗时\",\n  \"fields.rc_wait_time.desc\": \"SQL 语句在资源组队列中等待的累积时间（注：{{distro.tikv}} 会并行等待任务，因此该时间不是自然流逝时间）\",\n  \"fields.related_schemas\": \"数据库\",\n  \"fields.related_schemas.desc\": \"SQL 语句涉及的数据库\",\n  \"fields.resolve_lock_time\": \"Resolve Lock 耗时\",\n  \"fields.resolve_lock_time.desc\": \"事务在 {{distro.tikv}} 与其他事务产生了锁冲突并处理锁冲突的耗时\",\n  \"fields.resource_group\": \"资源组\",\n  \"fields.resource_group.desc\": \"SQL 语句所属的资源组\",\n  \"fields.sample_user\": \"执行用户名\",\n  \"fields.sample_user.desc\": \"执行该类 SQL 的用户名，可能存在多个执行用户，仅显示其中某一个\",\n  \"fields.schema_name\": \"执行数据库\",\n  \"fields.schema_name.desc\": \"执行该 SQL 查询时使用的数据库名称\",\n  \"fields.stmt_type\": \"SQL 语句类型\",\n  \"fields.sum_backoff_times\": \"累计重试次数\",\n  \"fields.sum_backoff_times.desc\": \"这类 SQL 语句遇到需要重试的错误后的总重试次数\",\n  \"fields.sum_cop_task_num\": \"累计 Coprocessor 请求数\",\n  \"fields.sum_cop_task_num.desc\": \"时间段内该类 SQL 语句累计发送的 Coprocessor 请求数\",\n  \"fields.sum_errors\": \"累计 Error 个数\",\n  \"fields.sum_latency\": \"累计耗时\",\n  \"fields.sum_latency.desc\": \"该类 SQL 语句在时间段内的累计执行时间\",\n  \"fields.sum_ru\": \"累积 RU\",\n  \"fields.sum_ru.desc\": \"该 SQL 语句的 RU 累积值\",\n  \"fields.sum_warnings\": \"累计 Warning 个数\",\n  \"fields.table_names\": \"表名\",\n  \"fields.tidb_cpu_time\": \"{{distro.tidb}} CPU 时间\",\n  \"fields.tikv_cpu_time\": \"{{distro.tikv}} CPU 时间\",\n  \"fields.total_process_time\": \"所有执行耗时\",\n  \"fields.total_wait_time\": \"所有等待耗时\",\n  \"fields.wait_time\": \"Coprocessor 等待耗时\",\n  \"fields.wait_time.desc\": \"SQL 查询在 {{distro.tikv}} Coprocessor 上被等待执行的耗时，单个 SQL 查询所有 Coprocessor 任务累计后计算\",\n  \"After enabled, TiDB internal queries will be collected as well.\": \"开启后 TiDB 内部执行的 SQL 语句信息也将被收集。\",\n  \"All (Raw JSON)\": \"全部 (原始 JSON)\",\n  \"Are you sure to bind SQL <code>{{sqlDigest}}</code> with the plan <code>{{planDigest}}</code>?\": \"确定将 SQL <code>{{sqlDigest}}</code> 绑定到计划 <code>{{planDigest}}</code> 吗？\",\n  \"Are you sure to unbind SQL <code>{{sqlDigest}}</code> with <strong>all bound plans</strong>?\": \"确定将 SQL <code>{{sqlDigest}}</code> 与 <strong>所有绑定计划</strong> 解绑吗？\",\n  \"Are you sure want to disable this feature? Current statement history will be cleared.\": \"确认要关闭该功能吗？关闭后现有历史记录也将被清空！\",\n  \"Basic\": \"基本信息\",\n  \"Bind Plan\": \"绑定计划\",\n  \"Bind plan feature is only available in and above {{distro.tidb}} 6.6.0\": \"SQL 执行计划绑定功能仅在 {{distro.tidb}} 6.6.0 及以上版本可用\",\n  \"Bind plan {{planDigest}} failed, reason: {{reason}}\": \"绑定计划 {{planDigest}} 失败，原因：{{reason}}\",\n  \"Bind plan {{planDigest}} successfully!\": \"绑定计划 {{planDigest}} 成功！\",\n  \"Bind\": \"绑定\",\n  \"By enlarging this setting more statement history will be preserved, with larger memory cost.\": \"扩大时间窗个数可以保留更长时间的执行历史，但也会引入更大的内存开销。\",\n  \"By reducing this setting you can select time range more precisely.\": \"缩小时间窗大小可以使得选择的时间范围更精细。\",\n  \"Cancel\": \"取消\",\n  \"Clear Filters\": \"清空筛选条件\",\n  \"Collect Internal Queries\": \"收集内部查询\",\n  \"Coprocessor Read\": \"Coprocessor 读取\",\n  \"Detail\": \"详情\",\n  \"Disable Statement Feature\": \"关闭 SQL 语句分析功能\",\n  \"Disable\": \"确认\",\n  \"Due to time window and expiration configurations, currently displaying data in time range is {{begin}} ~ {{end}}\": \"基于设置的时间窗及过期时间，当前显示数据的时间范围是 {{begin}} ~ {{end}}\",\n  \"Execution Database\": \"执行数据库\",\n  \"Execution Detail of All Plans\": \"所有计划的执行详情\",\n  \"Execution Detail of {{plan}}\": \"计划 {{plan}} 的执行详情\",\n  \"Execution Detail\": \"执行详情\",\n  \"Execution Plan\": \"执行计划\",\n  \"Execution Plans\": \"执行计划列表\",\n  \"Feature Enable\": \"启用功能\",\n  \"Feature Not Enabled\": \"该功能未启用\",\n  \"Find SQL text\": \"查找 SQL\",\n  \"I got it\": \"我知道了\",\n  \"Max Number of Statements\": \"最大收集 SQL 语句个数\",\n  \"Max number of statement to collect. After exceeding, old statement information will be dropped. You may enlarge this setting when memory is sufficient and you discovered that data displayed in UI is incomplete.\": \"收集的 SQL 语句个数上限，当实际执行的 SQL 语句种类超过设定个数后最早执行的 SQL 语句信息将被丢弃。若您发现界面上呈现的 SQL 语句信息不完整，建议在内存允许的情况下调大本参数。\",\n  \"No Data\": \"无结果\",\n  \"Open Setting\": \"打开设置\",\n  \"Plans Count\": \"计划数\",\n  \"Query Sample\": \"SQL 查询样例\",\n  \"Query Template ID\": \"SQL 模板 ID\",\n  \"SQL Statement Total History Size\": \"SQL 语句历史保留总时长\",\n  \"Save\": \"保存\",\n  \"Slow Queries\": \"慢查询\",\n  \"Statement Detail\": \"SQL 语句详情\",\n  \"Statement Setting\": \"SQL 语句设置\",\n  \"Statement Template\": \"SQL 模板\",\n  \"Statement feature is not enabled so that statement history cannot be viewed. You can modify settings to enable the feature and wait for new data being collected.\": \"SQL 语句分析功能未启用，因此无法查看历史记录。 您可以修改设置打开该功能后等待新数据收集。\",\n  \"Table\": \"表格\",\n  \"Tell me again next time\": \"下次还告诉我\",\n  \"Text\": \"文本\",\n  \"This plan can not be bound\": \"该计划无法绑定\",\n  \"Time Range\": \"时间范围\",\n  \"Time\": \"执行时间\",\n  \"Tips\": \"提示\",\n  \"Transaction\": \"事务\",\n  \"Unbind Plans\": \"解绑计划\",\n  \"Unbind plans failed, reason: {{reason}}\": \"解绑计划失败，原因：{{reason}}\",\n  \"Unbind plans successfully!\": \"解绑计划成功！\",\n  \"Unbind\": \"解绑\",\n  \"Update statement config failed!\": \"更新 SQL 语句设置失败！\",\n  \"Update statement config successfully!\": \"更新 SQL 语句设置成功！\",\n  \"When opening the detail page, you can press <kbd>Ctrl</kbd> or <kbd>⌘</kbd> to view it in a new tab, or <kbd>Shift</kbd> to view it in a new window.\": \"在打开详情页时，你可以按住 <kbd>Ctrl</kbd> 或 <kbd>⌘</kbd> 在新标签页打开，或按住 <kbd>Shift</kbd> 在新窗口中打开。\",\n  \"Whether Statement feature is enabled. When enabled, there will be a small SQL statement execution overhead.\": \"是否启用 SQL 语句分析功能，关闭后将不能使用 SQL 语句分析功能，但能提升少量 TiDB 性能。\",\n  \"Window Size (min)\": \"时间窗大小 (分钟)\",\n  \"Window Size x Windows Number\": \"时间窗大小 x 时间窗数量\",\n  \"Windows Number\": \"时间窗数量\",\n  \"page\": \"page\",\n  \"total\": \"total\"\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/models/advanced-filter-info-model.ts",
    "content": "import { AdvancedFilterInfo } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\n\nexport type AdvancedFilterInfoModel = AdvancedFilterInfo\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/models/index.ts",
    "content": "export * from \"./statement-model\"\nexport * from \"./advanced-filter-info-model\"\nexport * from \"./statement-config-model\"\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/models/statement-config-model.ts",
    "content": "export interface StatementConfigModel {\n  enable: boolean\n  max_size: number\n  refresh_interval: number\n  history_size: number\n  internal_query: boolean\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/models/statement-model.ts",
    "content": "export interface StatementModel {\n  avg_affected_rows?: number\n  avg_backoff_time?: number\n  avg_commit_backoff_time?: number\n  avg_commit_time?: number\n  avg_compile_latency?: number\n  avg_cop_process_time?: number\n  avg_cop_wait_time?: number\n  avg_disk?: number\n  avg_get_commit_ts_time?: number\n  avg_latency?: number\n  avg_local_latch_wait_time?: number\n  avg_mem?: number\n  avg_parse_latency?: number\n  avg_prewrite_regions?: number\n  avg_prewrite_time?: number\n  avg_process_time?: number\n  avg_processed_keys?: number\n  avg_resolve_lock_time?: number\n  avg_rocksdb_block_cache_hit_count?: number\n  avg_rocksdb_block_read_byte?: number\n  avg_rocksdb_block_read_count?: number\n  avg_rocksdb_delete_skipped_count?: number\n  avg_rocksdb_key_skipped_count?: number\n  avg_ru?: number\n  avg_time_queued_by_rc?: number\n  avg_total_keys?: number\n  avg_txn_retry?: number\n  avg_wait_time?: number\n  avg_write_keys?: number\n  avg_write_size?: number\n  binary_plan?: string\n  binary_plan_json?: string\n  binary_plan_text?: string\n  digest?: string\n  digest_text?: string\n  exec_count?: number\n  first_seen?: number\n  index_names?: string\n  last_seen?: number\n  max_backoff_time?: number\n  max_commit_backoff_time?: number\n  max_commit_time?: number\n  max_compile_latency?: number\n  max_cop_process_time?: number\n  max_cop_wait_time?: number\n  max_disk?: number\n  max_get_commit_ts_time?: number\n  max_latency?: number\n  max_local_latch_wait_time?: number\n  max_mem?: number\n  max_parse_latency?: number\n  max_prewrite_regions?: number\n  max_prewrite_time?: number\n  max_process_time?: number\n  max_processed_keys?: number\n  max_resolve_lock_time?: number\n  max_rocksdb_block_cache_hit_count?: number\n  max_rocksdb_block_read_byte?: number\n  max_rocksdb_block_read_count?: number\n  max_rocksdb_delete_skipped_count?: number\n  max_rocksdb_key_skipped_count?: number\n  max_ru?: number\n  max_time_queued_by_rc?: number\n  max_total_keys?: number\n  max_txn_retry?: number\n  max_wait_time?: number\n  max_write_keys?: number\n  max_write_size?: number\n  min_latency?: number\n  plan?: string\n  plan_cache_hits?: number\n  plan_can_be_bound?: boolean\n  plan_count?: number\n  plan_digest?: string\n  plan_hint?: string\n  prev_sample_text?: string\n  query_sample_text?: string\n  related_schemas?: string\n  resource_group?: string\n  sample_user?: string\n  schema_name?: string\n  stmt_type?: string\n  sum_backoff_times?: number\n  sum_cop_task_num?: number\n  sum_errors?: number\n  sum_latency?: number\n  sum_ru?: number\n  sum_warnings?: number\n  summary_begin_time?: number\n  summary_end_time?: number\n  table_names?: string\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/index.tsx",
    "content": "import { LoadingSkeleton } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { ActionIcon, Group, Stack, Typography } from \"@tidbcloud/uikit\"\nimport { IconChevronLeft } from \"@tidbcloud/uikit/icons\"\n\nimport { useAppContext } from \"../../ctx\"\nimport { usePlanDetailData } from \"../../utils/use-data\"\n\nimport { PlanDetail } from \"./plan-detail\"\nimport { PlansList } from \"./plans-list\"\nimport { SqlHistory } from \"./sql-history\"\nimport { SqlLimit } from \"./sql-limit\"\nimport { StmtBasic } from \"./stmt-basic\"\nimport { StmtSQL } from \"./stmt-sql\"\n\nexport function Detail() {\n  const { tt } = useTn(\"statement\")\n  const ctx = useAppContext()\n  const { data: planData, isLoading } = usePlanDetailData(\"\")\n\n  return (\n    <Stack>\n      {ctx.cfg.showDetailBack !== false && (\n        <Group wrap=\"nowrap\">\n          <ActionIcon\n            aria-label=\"Navigate Back\"\n            variant=\"default\"\n            onClick={ctx.actions.backToList}\n          >\n            <IconChevronLeft size={20} />\n          </ActionIcon>\n          <Typography variant=\"title-lg\">{tt(\"Statement Detail\")}</Typography>\n        </Group>\n      )}\n\n      {isLoading && <LoadingSkeleton />}\n\n      {planData && (\n        <Stack>\n          <StmtSQL\n            title={tt(\"Statement Template\")}\n            sql={planData.digest_text!}\n          />\n          <StmtBasic stmt={planData} />\n\n          <SqlHistory sqlDigest={planData.digest!} />\n          <SqlLimit sqlDigest={planData.digest!} />\n\n          <PlansList detailData={planData} />\n          <PlanDetail />\n        </Stack>\n      )}\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/plan-detail/detail-basic.tsx",
    "content": "import {\n  InfoModel,\n  InfoTable,\n} from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport {\n  formatNumByUnit,\n  formatTime,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMemo } from \"react\"\n\nimport { StatementModel } from \"../../../models\"\n\nfunction getData(\n  data: StatementModel,\n  tk: (key: string) => string,\n): InfoModel[] {\n  return [\n    {\n      name: tk(\"fields.table_names\"),\n      value: data.table_names || \"-\",\n    },\n    {\n      name: tk(\"fields.index_names\"),\n      value: data.index_names || \"-\",\n      desc: tk(\"fields.index_names.desc\"),\n    },\n    {\n      name: tk(\"fields.first_seen\"),\n      value: data.first_seen ? formatTime(data.first_seen * 1000) : \"-\",\n    },\n    {\n      name: tk(\"fields.last_seen\"),\n      value: data.last_seen ? formatTime(data.last_seen * 1000) : \"-\",\n    },\n    {\n      name: tk(\"fields.exec_count\"),\n      value: formatNumByUnit(data.exec_count ?? 0, \"short\"),\n      desc: tk(\"fields.exec_count.desc\"),\n    },\n    {\n      name: tk(\"fields.sum_latency\"),\n      value: formatNumByUnit(data.sum_latency ?? 0, \"ns\"),\n      desc: tk(\"fields.sum_latency.desc\"),\n    },\n    {\n      name: tk(\"fields.avg_latency\"),\n      value: formatNumByUnit(data.avg_latency ?? 0, \"ns\"),\n      desc: tk(\"fields.avg_latency.desc\"),\n    },\n    {\n      name: tk(\"fields.sample_user\"),\n      value: data.sample_user || \"-\",\n      desc: tk(\"fields.sample_user.desc\"),\n    },\n    {\n      name: tk(\"fields.sum_errors\"),\n      value: formatNumByUnit(data.sum_errors ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.sum_warnings\"),\n      value: formatNumByUnit(data.sum_warnings ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.avg_mem\"),\n      value: formatNumByUnit(data.avg_mem ?? 0, \"bytes\"),\n      desc: tk(\"fields.avg_mem.desc\"),\n    },\n    {\n      name: tk(\"fields.max_mem\"),\n      value: formatNumByUnit(data.max_mem ?? 0, \"bytes\"),\n      desc: tk(\"fields.max_mem.desc\"),\n    },\n    {\n      name: tk(\"fields.avg_disk\"),\n      value: formatNumByUnit(data.avg_disk ?? 0, \"bytes\"),\n      desc: tk(\"fields.avg_disk.desc\"),\n    },\n    {\n      name: tk(\"fields.max_disk\"),\n      value: formatNumByUnit(data.max_disk ?? 0, \"bytes\"),\n      desc: tk(\"fields.max_disk.desc\"),\n    },\n  ]\n}\n\nexport function DetailBasic({ data }: { data: StatementModel }) {\n  const { tk } = useTn(\"statement\")\n  const infoData = useMemo(() => getData(data, tk), [data, tk])\n  return <InfoTable data={infoData} />\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/plan-detail/detail-copr.tsx",
    "content": "import {\n  InfoModel,\n  InfoTable,\n} from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport {\n  formatNumByUnit,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMemo } from \"react\"\n\nimport { StatementModel } from \"../../../models\"\n\nfunction getData(\n  data: StatementModel,\n  tk: (key: string) => string,\n): InfoModel[] {\n  return [\n    {\n      name: tk(\"fields.sum_cop_task_num\"),\n      value: formatNumByUnit(data.sum_cop_task_num ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.avg_processed_keys\"),\n      value: formatNumByUnit(data.avg_processed_keys ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.max_processed_keys\"),\n      value: formatNumByUnit(data.max_processed_keys ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.avg_total_keys\"),\n      value: formatNumByUnit(data.avg_total_keys ?? 0, \"short\"),\n      desc: tk(\"fields.avg_total_keys.desc\"),\n    },\n    {\n      name: tk(\"fields.max_total_keys\"),\n      value: formatNumByUnit(data.max_total_keys ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.avg_rocksdb_block_cache_hit_count\"),\n      value: formatNumByUnit(\n        data.avg_rocksdb_block_cache_hit_count ?? 0,\n        \"short\",\n      ),\n      desc: tk(\"fields.avg_rocksdb_block_cache_hit_count.desc\"),\n    },\n    {\n      name: tk(\"fields.max_rocksdb_block_cache_hit_count\"),\n      value: formatNumByUnit(\n        data.max_rocksdb_block_cache_hit_count ?? 0,\n        \"short\",\n      ),\n    },\n    {\n      name: tk(\"fields.avg_rocksdb_block_read_byte\"),\n      value: formatNumByUnit(data.avg_rocksdb_block_read_byte ?? 0, \"short\"),\n      desc: tk(\"fields.avg_rocksdb_block_read_byte.desc\"),\n    },\n    {\n      name: tk(\"fields.max_rocksdb_block_read_byte\"),\n      value: formatNumByUnit(data.max_rocksdb_block_read_byte ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.avg_rocksdb_block_read_count\"),\n      value: formatNumByUnit(data.avg_rocksdb_block_read_count ?? 0, \"short\"),\n      desc: tk(\"fields.avg_rocksdb_block_read_count.desc\"),\n    },\n    {\n      name: tk(\"fields.max_rocksdb_block_read_count\"),\n      value: formatNumByUnit(data.max_rocksdb_block_read_count ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.avg_rocksdb_delete_skipped_count\"),\n      value: formatNumByUnit(\n        data.avg_rocksdb_delete_skipped_count ?? 0,\n        \"short\",\n      ),\n      desc: tk(\"fields.avg_rocksdb_delete_skipped_count.desc\"),\n    },\n    {\n      name: tk(\"fields.max_rocksdb_delete_skipped_count\"),\n      value: formatNumByUnit(\n        data.max_rocksdb_delete_skipped_count ?? 0,\n        \"short\",\n      ),\n    },\n    {\n      name: tk(\"fields.avg_rocksdb_key_skipped_count\"),\n      value: formatNumByUnit(data.avg_rocksdb_key_skipped_count ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.max_rocksdb_key_skipped_count\"),\n      value: formatNumByUnit(data.max_rocksdb_key_skipped_count ?? 0, \"short\"),\n    },\n  ]\n}\n\nexport function DetailCopr({ data }: { data: StatementModel }) {\n  const { tk } = useTn(\"statement\")\n  const infoData = useMemo(() => getData(data, tk), [data, tk])\n  return <InfoTable data={infoData} />\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/plan-detail/detail-tabs.tsx",
    "content": "import { CustomJsonView } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  ActionIcon,\n  Card,\n  CopyButton,\n  Stack,\n  Tabs,\n  Title,\n  Tooltip,\n} from \"@tidbcloud/uikit\"\nimport { IconCheck, IconCopy02 } from \"@tidbcloud/uikit/icons\"\nimport { useMemo } from \"react\"\n\nimport { StatementModel } from \"../../../models\"\n\nimport { DetailBasic } from \"./detail-basic\"\nimport { DetailCopr } from \"./detail-copr\"\nimport { DetailTime } from \"./detail-time\"\nimport { DetailTxn } from \"./detail-txn\"\n\nfunction DetailAll({ data }: { data: StatementModel }) {\n  return (\n    <Stack gap={0}>\n      <CopyButton value={JSON.stringify(data, null, 2)} timeout={2000}>\n        {({ copied, copy }) => (\n          <Tooltip\n            label={copied ? \"Copied\" : \"Copy\"}\n            withArrow\n            position=\"right\"\n          >\n            <ActionIcon variant=\"subtle\" onClick={copy}>\n              {copied ? <IconCheck size={16} /> : <IconCopy02 size={16} />}\n            </ActionIcon>\n          </Tooltip>\n        )}\n      </CopyButton>\n      <CustomJsonView data={data} />\n    </Stack>\n  )\n}\n\nexport function DetailTabs({ data }: { data: StatementModel }) {\n  const { tt } = useTn(\"statement\")\n  const tabs = useMemo(() => {\n    const _tabs = [\n      {\n        label: tt(\"Basic\"),\n        value: \"basic\",\n        component: <DetailBasic data={data} />,\n      },\n      {\n        label: tt(\"Time\"),\n        value: \"time\",\n        component: <DetailTime data={data} />,\n      },\n      {\n        label: tt(\"Coprocessor Read\"),\n        value: \"copr\",\n        component: <DetailCopr data={data} />,\n      },\n      {\n        label: tt(\"Transaction\"),\n        value: \"txn\",\n        component: <DetailTxn data={data} />,\n      },\n      {\n        label: tt(\"All (Raw JSON)\"),\n        value: \"all\",\n        component: <DetailAll data={data} />,\n      },\n    ]\n    return _tabs\n  }, [data, tt])\n\n  return (\n    <Card shadow=\"xs\" p=\"md\">\n      <Stack gap=\"xs\">\n        <Title order={5}>{tt(\"Detail\")}</Title>\n        <Tabs defaultValue={tabs[0].value}>\n          <Tabs.List mb=\"md\">\n            {tabs.map((tab) => (\n              <Tabs.Tab key={tab.value} value={tab.value}>\n                {tab.label}\n              </Tabs.Tab>\n            ))}\n          </Tabs.List>\n          {tabs.map((tab) => (\n            <Tabs.Panel key={tab.value} value={tab.value}>\n              {tab.component}\n            </Tabs.Panel>\n          ))}\n        </Tabs>\n      </Stack>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/plan-detail/detail-time.tsx",
    "content": "import {\n  InfoModel,\n  InfoTable,\n} from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport {\n  formatNumByUnit,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMemo } from \"react\"\n\nimport { StatementModel } from \"../../../models\"\n\nfunction getData(\n  data: StatementModel,\n  tk: (key: string) => string,\n): InfoModel[] {\n  return [\n    {\n      name: tk(\"fields.query_time_2\"),\n      value: formatNumByUnit(data.avg_latency ?? 0, \"ns\"),\n      level: 0,\n      desc: tk(\"fields.query_time_2.desc\"),\n    },\n    {\n      name: tk(\"fields.avg_parse_latency\"),\n      value: formatNumByUnit(data.avg_parse_latency ?? 0, \"ns\"),\n      level: 1,\n      // desc: tk(\"fields.avg_parse_latency.desc\"),\n    },\n    {\n      name: tk(\"fields.avg_compile_latency\"),\n      value: formatNumByUnit(data.avg_compile_latency ?? 0, \"ns\"),\n      level: 1,\n      // desc: tk(\"fields.avg_compile_latency.desc\"),\n    },\n    {\n      name: tk(\"fields.avg_wait_time\"),\n      value: formatNumByUnit(data.avg_wait_time ?? 0, \"ns\"),\n      level: 1,\n    },\n    {\n      name: tk(\"fields.avg_process_time\"),\n      value: formatNumByUnit(data.avg_process_time ?? 0, \"ns\"),\n      level: 1,\n    },\n    {\n      name: tk(\"fields.avg_backoff_time\"),\n      value: formatNumByUnit(data.avg_backoff_time ?? 0, \"ns\"),\n      level: 1,\n      // desc: tk(\"fields.avg_backoff_time.desc\"),\n    },\n    {\n      name: tk(\"fields.avg_get_commit_ts_time\"),\n      value: formatNumByUnit(data.avg_get_commit_ts_time ?? 0, \"ns\"),\n      level: 1,\n    },\n    {\n      name: tk(\"fields.avg_local_latch_wait_time\"),\n      value: formatNumByUnit(data.avg_local_latch_wait_time ?? 0, \"ns\"),\n      level: 1,\n    },\n    {\n      name: tk(\"fields.avg_resolve_lock_time\"),\n      value: formatNumByUnit(data.avg_resolve_lock_time ?? 0, \"ns\"),\n      level: 1,\n    },\n    {\n      name: tk(\"fields.avg_prewrite_time\"),\n      value: formatNumByUnit(data.avg_prewrite_time ?? 0, \"ns\"),\n      level: 1,\n    },\n    {\n      name: tk(\"fields.avg_commit_time\"),\n      value: formatNumByUnit(data.avg_commit_time ?? 0, \"ns\"),\n      level: 1,\n    },\n    {\n      name: tk(\"fields.avg_commit_backoff_time\"),\n      value: formatNumByUnit(data.avg_commit_backoff_time ?? 0, \"ns\"),\n      level: 1,\n    },\n  ]\n}\n\nexport function DetailTime({ data }: { data: StatementModel }) {\n  const { tk } = useTn(\"statement\")\n  const infoData = useMemo(() => getData(data, tk), [data, tk])\n  return <InfoTable data={infoData} />\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/plan-detail/detail-txn.tsx",
    "content": "import {\n  InfoModel,\n  InfoTable,\n} from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport {\n  formatNumByUnit,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMemo } from \"react\"\n\nimport { StatementModel } from \"../../../models\"\n\nfunction getData(\n  data: StatementModel,\n  tk: (key: string) => string,\n): InfoModel[] {\n  return [\n    {\n      name: tk(\"fields.avg_affected_rows\"),\n      value: formatNumByUnit(data.avg_affected_rows ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.sum_backoff_times\"),\n      value: formatNumByUnit(data.sum_backoff_times ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.avg_write_keys\"),\n      value: formatNumByUnit(data.avg_write_keys ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.max_write_keys\"),\n      value: formatNumByUnit(data.max_write_keys ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.avg_write_size\"),\n      value: formatNumByUnit(data.avg_write_size ?? 0, \"bytes\"),\n    },\n    {\n      name: tk(\"fields.max_write_size\"),\n      value: formatNumByUnit(data.max_write_size ?? 0, \"bytes\"),\n    },\n    {\n      name: tk(\"fields.avg_prewrite_regions\"),\n      value: formatNumByUnit(data.avg_prewrite_regions ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.max_prewrite_regions\"),\n      value: formatNumByUnit(data.max_prewrite_regions ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.avg_txn_retry\"),\n      value: formatNumByUnit(data.avg_txn_retry ?? 0, \"short\"),\n    },\n    {\n      name: tk(\"fields.max_txn_retry\"),\n      value: formatNumByUnit(data.max_txn_retry ?? 0, \"short\"),\n    },\n  ]\n}\n\nexport function DetailTxn({ data }: { data: StatementModel }) {\n  const { tk } = useTn(\"statement\")\n  const infoData = useMemo(() => getData(data, tk), [data, tk])\n  return <InfoTable data={infoData} />\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/plan-detail/index.tsx",
    "content": "import { LoadingSkeleton } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Stack, Title } from \"@tidbcloud/uikit\"\nimport { useMemo } from \"react\"\n\nimport { useDetailUrlState } from \"../../../shared-state/detail-url-state\"\nimport { usePlanDetailData } from \"../../../utils/use-data\"\nimport { StmtSQL } from \"../stmt-sql\"\n\nimport { DetailTabs } from \"./detail-tabs\"\nimport { Plan } from \"./plan\"\n\nexport function PlanDetail() {\n  const { tt } = useTn(\"statement\")\n  const { plan } = useDetailUrlState()\n  const realPlan = plan && plan !== \"all\" ? plan : \"\"\n  const { data: planDetailData, isLoading } = usePlanDetailData(realPlan)\n\n  const title = useMemo(() => {\n    if (plan === \"all\") {\n      return tt(\"Execution Detail of All Plans\")\n    } else if (plan) {\n      return tt(\"Execution Detail of {{plan}}\", { plan })\n    }\n    return tt(\"Execution Detail\")\n  }, [plan, tt])\n\n  return (\n    <Stack>\n      <Title order={4}>{title}</Title>\n\n      {isLoading && <LoadingSkeleton />}\n\n      {planDetailData && (\n        <>\n          {planDetailData.query_sample_text && (\n            <StmtSQL\n              title={tt(\"Query Sample\")}\n              sql={planDetailData.query_sample_text!}\n            />\n          )}\n          {planDetailData.plan && plan !== \"all\" && (\n            <Plan plan={planDetailData.plan!} />\n          )}\n          <DetailTabs data={planDetailData} />\n        </>\n      )}\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/plan-detail/plan.tsx",
    "content": "import { PlanTable } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Card, Stack, Tabs, Title } from \"@tidbcloud/uikit\"\nimport { CodeBlock } from \"@tidbcloud/uikit/biz\"\nimport { useMemo } from \"react\"\n\nexport function Plan({ plan }: { plan: string }) {\n  const { tt } = useTn(\"statement\")\n\n  const tabs = useMemo(() => {\n    return [\n      {\n        label: tt(\"Text\"),\n        value: \"text\",\n        component: (\n          <CodeBlock\n            codeRender={(content) => <pre>{content}</pre>}\n            foldProps={{\n              persistenceKey: \"statement.detail.plan\",\n              iconVisible: true,\n            }}\n          >\n            {plan}\n          </CodeBlock>\n        ),\n      },\n      {\n        label: tt(\"Table\"),\n        value: \"table\",\n        component: <PlanTable plan={plan} />,\n      },\n    ]\n  }, [plan, tt])\n\n  return (\n    <Card shadow=\"xs\" p=\"md\">\n      <Stack gap=\"xs\">\n        <Title order={5}>{tt(\"Execution Plan\")}</Title>\n\n        <Tabs defaultValue={tabs[0].value}>\n          <Tabs.List mb=\"md\">\n            {tabs.map((tab) => (\n              <Tabs.Tab key={tab.value} value={tab.value}>\n                {tab.label}\n              </Tabs.Tab>\n            ))}\n          </Tabs.List>\n          {tabs.map((tab) => (\n            <Tabs.Panel key={tab.value} value={tab.value}>\n              {tab.component}\n            </Tabs.Panel>\n          ))}\n        </Tabs>\n      </Stack>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/plans-list/bind-sql-cell.tsx",
    "content": "import { Trans, useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  Button,\n  Code,\n  Tooltip,\n  Typography,\n  notifier,\n  openConfirmModal,\n} from \"@tidbcloud/uikit\"\n\nimport {\n  useCreatePlanBindData,\n  useDeletePlanBindData,\n} from \"../../../utils/use-data\"\n\n// @ts-expect-error @typescript-eslint/no-unused-vars\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction useLocales() {\n  const { tt } = useTn(\"statement\")\n  // used for gogocode to scan and generate en.json before build\n  tt(\n    \"Are you sure to bind SQL <code>{{sqlDigest}}</code> with the plan <code>{{planDigest}}</code>?\",\n  )\n  tt(\n    \"Are you sure to unbind SQL <code>{{sqlDigest}}</code> with <strong>all bound plans</strong>?\",\n  )\n}\n\nexport function SqlPlanBindActionCell({\n  isSupport,\n  canBind,\n  sqlDigest,\n  bindPlanDigests,\n  curPlanDigest,\n}: {\n  isSupport: boolean\n  canBind: boolean\n  sqlDigest: string\n  bindPlanDigests: string[]\n  curPlanDigest: string\n}) {\n  const { tt } = useTn(\"statement\")\n  const createBindPlanMut = useCreatePlanBindData(sqlDigest, curPlanDigest)\n  const deleteBindPlanMut = useDeletePlanBindData(sqlDigest)\n\n  async function bindPlan() {\n    try {\n      await createBindPlanMut.mutateAsync()\n      notifier.success(\n        tt(\"Bind plan {{planDigest}} successfully!\", {\n          planDigest: curPlanDigest.slice(0, 8) + \"...\",\n        }),\n      )\n    } catch (e) {\n      notifier.error(\n        tt(\"Bind plan {{planDigest}} failed, reason: {{reason}}\", {\n          planDigest: curPlanDigest.slice(0, 8) + \"...\",\n          reason: e instanceof Error ? e.message : String(e),\n        }),\n      )\n    }\n  }\n\n  async function unbindPlan() {\n    try {\n      await deleteBindPlanMut.mutateAsync()\n      notifier.success(tt(\"Unbind plans successfully!\"))\n    } catch (e) {\n      notifier.error(\n        tt(\"Unbind plans failed, reason: {{reason}}\", {\n          reason: e instanceof Error ? e.message : String(e),\n        }),\n      )\n    }\n  }\n\n  function confirmBindPlan() {\n    openConfirmModal({\n      title: tt(\"Bind Plan\"),\n      children: (\n        <Typography>\n          <Trans\n            ns=\"statement\"\n            i18nKey={\n              \"Are you sure to bind SQL <code>{{sqlDigest}}</code> with the plan <code>{{planDigest}}</code>?\"\n            }\n            values={{\n              sqlDigest: sqlDigest.slice(0, 8) + \"...\",\n              planDigest: curPlanDigest.slice(0, 8) + \"...\",\n            }}\n            components={{ code: <Code /> }}\n          />\n        </Typography>\n      ),\n      labels: { confirm: tt(\"Bind\"), cancel: tt(\"Cancel\") },\n      onConfirm: bindPlan,\n    })\n  }\n\n  function confirmUnbindPlan() {\n    openConfirmModal({\n      title: tt(\"Unbind Plans\"),\n      children: (\n        <Typography>\n          <Trans\n            ns=\"statement\"\n            i18nKey={\n              \"Are you sure to unbind SQL <code>{{sqlDigest}}</code> with <strong>all bound plans</strong>?\"\n            }\n            values={{ sqlDigest: sqlDigest.slice(0, 8) + \"...\" }}\n            components={{ code: <Code />, strong: <strong /> }}\n          />\n        </Typography>\n      ),\n      confirmProps: { color: \"red\", variant: \"outline\" },\n      labels: { confirm: tt(\"Unbind\"), cancel: tt(\"Cancel\") },\n      onConfirm: unbindPlan,\n    })\n  }\n\n  if (!curPlanDigest || curPlanDigest === \"all\") {\n    return null\n  }\n  if (!isSupport) {\n    return (\n      <Tooltip\n        label={tt(\n          \"Bind plan feature is only available in and above {{distro.tidb}} 6.6.0\",\n        )}\n      >\n        <Button disabled size=\"xs\">\n          {tt(\"Bind\")}\n        </Button>\n      </Tooltip>\n    )\n  }\n  if (!canBind) {\n    return (\n      <Tooltip label={tt(\"This plan can not be bound\")}>\n        <Button disabled size=\"xs\">\n          {tt(\"Bind\")}\n        </Button>\n      </Tooltip>\n    )\n  }\n  if (bindPlanDigests.length > 0) {\n    if (bindPlanDigests.includes(curPlanDigest)) {\n      return (\n        <Button variant=\"transparent\" c=\"red.7\" onClick={confirmUnbindPlan}>\n          {tt(\"Unbind\")}\n        </Button>\n      )\n    }\n    return null\n  }\n  return (\n    <Button variant=\"transparent\" c=\"peacock.7\" onClick={confirmBindPlan}>\n      {tt(\"Bind\")}\n    </Button>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/plans-list/cols.tsx",
    "content": "import {\n  formatNumByUnit,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Group, Typography } from \"@tidbcloud/uikit\"\nimport { MRT_ColumnDef } from \"@tidbcloud/uikit/biz\"\nimport { useMemo } from \"react\"\n\nimport { StatementModel } from \"../../../models\"\n\nimport { SqlPlanBindActionCell } from \"./bind-sql-cell\"\nimport { PlanCheckCell } from \"./plan-check-cell\"\nimport { SlowQueryCell } from \"./slow-query-cell\"\n\nexport function useStatementColumns(\n  supportBindPlan: boolean,\n  bindPlanDigests: string[],\n) {\n  const { tk } = useTn(\"statement\")\n  const columns = useMemo<MRT_ColumnDef<StatementModel>[]>(() => {\n    return [\n      {\n        id: \"check\",\n        header: \"\",\n        size: 20,\n        enableSorting: false,\n        enableResizing: false,\n        accessorFn: (row) => <PlanCheckCell planDigest={row.plan_digest!} />,\n      },\n      {\n        id: \"plan_digest\",\n        header: tk(\"fields.plan_digest\"),\n        enableSorting: false,\n        minSize: 100,\n        accessorFn: (row) => (\n          <Typography\n            truncate\n            fw={row.plan_digest === \"all\" ? \"bold\" : \"normal\"}\n          >\n            {row.plan_digest || \"-\"}\n          </Typography>\n        ),\n      },\n      {\n        id: \"sum_latency\",\n        header: tk(\"fields.sum_latency\"),\n        enableResizing: false,\n        accessorFn: (row) => (\n          <Typography\n            truncate\n            fw={row.plan_digest === \"all\" ? \"bold\" : \"normal\"}\n          >\n            {formatNumByUnit(row.sum_latency!, \"ns\")}\n          </Typography>\n        ),\n      },\n      {\n        id: \"avg_latency\",\n        header: tk(\"fields.avg_latency\"),\n        enableResizing: false,\n        accessorFn: (row) => (\n          <Typography\n            truncate\n            fw={row.plan_digest === \"all\" ? \"bold\" : \"normal\"}\n          >\n            {formatNumByUnit(row.avg_latency!, \"ns\")}\n          </Typography>\n        ),\n      },\n      {\n        id: \"exec_count\",\n        header: tk(\"fields.exec_count\"),\n        enableResizing: false,\n        accessorFn: (row) => (\n          <Typography\n            truncate\n            fw={row.plan_digest === \"all\" ? \"bold\" : \"normal\"}\n          >\n            {formatNumByUnit(row.exec_count!, \"short\")}\n          </Typography>\n        ),\n      },\n      {\n        id: \"avg_mem\",\n        header: tk(\"fields.avg_mem\"),\n        enableResizing: false,\n        accessorFn: (row) => (\n          <Typography\n            truncate\n            fw={row.plan_digest === \"all\" ? \"bold\" : \"normal\"}\n          >\n            {formatNumByUnit(row.avg_mem!, \"bytes\")}\n          </Typography>\n        ),\n      },\n      {\n        id: \"action\",\n        header: \"\",\n        size: 180,\n        enableSorting: false,\n        enableResizing: false,\n        mantineTableHeadCellProps: {\n          align: \"right\",\n        },\n        mantineTableBodyCellProps: {\n          align: \"right\",\n        },\n        Cell: ({ row }) => (\n          <Group gap=\"xs\">\n            <SqlPlanBindActionCell\n              isSupport={supportBindPlan}\n              canBind={row.original.plan_can_be_bound!}\n              sqlDigest={row.original.digest!}\n              bindPlanDigests={bindPlanDigests}\n              curPlanDigest={row.original.plan_digest!}\n            />\n            <SlowQueryCell planDigest={row.original.plan_digest!} />\n          </Group>\n        ),\n        // here accessorFn doesn't work as expected\n        // need to use Cell\n        // accessorFn: (row) => <SqlPlanBindActionCell\n        //   isSupport={supportBindPlan}\n        //   canBind={row.plan_can_be_bound!}\n        //   sqlDigest={row.digest!}\n        //   bindPlanDigest={bindPlanDigest}\n        //   curPlanDigest={row.plan_digest!}\n        // />\n      },\n    ]\n  }, [supportBindPlan, bindPlanDigests, tk])\n\n  return columns\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/plans-list/index.tsx",
    "content": "import { LoadingSkeleton } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Card, Stack, Title } from \"@tidbcloud/uikit\"\n\nimport { StatementModel } from \"../../../models\"\nimport { usePlansListData } from \"../../../utils/use-data\"\n\nimport { PlansListTable } from \"./table\"\n\nexport function PlansList({ detailData }: { detailData: StatementModel }) {\n  const { tt } = useTn(\"statement\")\n  const { data: plansListData, isLoading } = usePlansListData()\n\n  return (\n    <Card shadow=\"xs\" p=\"md\">\n      <Stack gap=\"xs\">\n        <Title order={5}>{tt(\"Execution Plans\")}</Title>\n\n        {isLoading && <LoadingSkeleton />}\n\n        <PlansListTable data={plansListData || []} detailData={detailData} />\n      </Stack>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/plans-list/plan-check-cell.tsx",
    "content": "import { Radio } from \"@tidbcloud/uikit\"\n\nimport { useDetailUrlState } from \"../../../shared-state/detail-url-state\"\n\nexport function PlanCheckCell({ planDigest }: { planDigest: string }) {\n  const { plan, setPlan } = useDetailUrlState()\n\n  const handleCheckChange = (\n    e: React.ChangeEvent<HTMLInputElement>,\n    planDigest: string,\n  ) => {\n    const checked = e.target.checked\n    if (checked) {\n      setPlan(planDigest)\n    }\n  }\n\n  return (\n    <Radio\n      size=\"xs\"\n      checked={plan === planDigest}\n      onChange={(e) => handleCheckChange(e, planDigest)}\n    />\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/plans-list/slow-query-cell.tsx",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Anchor } from \"@tidbcloud/uikit\"\n\nimport { useAppContext } from \"../../../ctx\"\nimport { useDetailUrlState } from \"../../../shared-state/detail-url-state\"\n\nexport function SlowQueryCell({ planDigest }: { planDigest: string }) {\n  const { tt } = useTn(\"statement\")\n  const ctx = useAppContext()\n  const { id } = useDetailUrlState()\n  const newId = `${id},${planDigest === \"all\" ? \"\" : planDigest}`\n\n  return (\n    <Anchor\n      onClick={() => {\n        ctx.actions.openSlowQueryList(newId)\n      }}\n    >\n      {tt(\"Slow Queries\")}\n    </Anchor>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/plans-list/table.tsx",
    "content": "import { ProTable } from \"@tidbcloud/uikit/biz\"\nimport { useEffect, useMemo, useState } from \"react\"\n\nimport { StatementModel } from \"../../../models\"\nimport { useDetailUrlState } from \"../../../shared-state/detail-url-state\"\nimport {\n  usePlanBindStatusData,\n  usePlanBindSupportData,\n} from \"../../../utils/use-data\"\n\nimport { useStatementColumns } from \"./cols\"\n\nexport function PlansListTable({\n  data,\n  detailData,\n}: {\n  data: StatementModel[]\n  detailData: StatementModel\n}) {\n  const { data: planBindSupport } = usePlanBindSupportData()\n  const { data: planBindStatus } = usePlanBindStatusData(\n    detailData.digest!,\n    detailData.summary_begin_time!,\n    detailData.summary_end_time!,\n  )\n  const columns = useStatementColumns(\n    planBindSupport?.is_support ?? false,\n    planBindStatus ?? [],\n  )\n  const [sorting, setSorting] = useState([{ id: \"exec_count\", desc: true }])\n\n  // do sorting in local\n  const sortedData = useMemo(() => {\n    if (!data) {\n      return []\n    }\n    if (!sorting[0]) {\n      return data\n    }\n    const [{ id, desc }] = sorting\n    const sorted = [...data]\n    sorted.sort((a, b) => {\n      const aVal = a[id as keyof StatementModel] ?? 0\n      const bVal = b[id as keyof StatementModel] ?? 0\n      if (desc) {\n        return Number(aVal) > Number(bVal) ? -1 : 1\n      } else {\n        return Number(aVal) > Number(bVal) ? 1 : -1\n      }\n    })\n    return sorted\n  }, [data, sorting])\n\n  // combine sortedData and detailData\n  // make always show detailData in the footer\n  // detailData is the total value of all plans\n  const finalData = useMemo(() => {\n    if (sortedData && sortedData.length > 1) {\n      return [...sortedData, { ...detailData, plan_digest: \"all\" }]\n    }\n    return sortedData\n  }, [sortedData, detailData])\n\n  // select first plan default\n  const { plan, setPlan } = useDetailUrlState()\n  useEffect(() => {\n    if (!plan && sortedData && sortedData.length > 0) {\n      setPlan(sortedData[0].plan_digest!)\n    }\n  }, [plan, setPlan, sortedData])\n\n  return (\n    <ProTable\n      layoutMode=\"grid\"\n      enableColumnResizing\n      enableColumnPinning\n      enableSorting\n      manualSorting\n      sortDescFirst\n      onSortingChange={setSorting}\n      initialState={{\n        columnPinning: { left: [\"check\", \"plan_digest\"], right: [\"action\"] },\n      }}\n      state={{\n        sorting,\n        columnVisibility: {\n          check: data.length > 1,\n          action: !!planBindSupport && !!planBindStatus,\n        },\n      }}\n      columns={columns}\n      data={finalData}\n    />\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/sql-history.tsx",
    "content": "import { TimeRange } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMemo } from \"react\"\n\nimport { AppProvider, SqlHistoryCard } from \"../../../_shared/sql-history\"\nimport { useAppContext } from \"../../ctx\"\nimport { useDetailUrlState } from \"../../shared-state/detail-url-state\"\n\nexport function SqlHistory({ sqlDigest }: { sqlDigest: string }) {\n  const ctx = useAppContext()\n\n  const { id } = useDetailUrlState()\n  const initialTimeRange = useMemo<TimeRange>(() => {\n    const [summary_begin_time, summary_end_time, _digest, _schema_name] =\n      id.split(\",\")\n    return {\n      type: \"absolute\",\n      value: [Number(summary_begin_time), Number(summary_end_time)],\n    }\n  }, [id])\n\n  const ctxValue = useMemo(\n    () => ({\n      ...ctx,\n      cfg: {\n        parentAppName: \"statement\",\n        sqlDigest,\n        initialTimeRange,\n      },\n    }),\n    [ctx, sqlDigest, initialTimeRange],\n  )\n\n  return (\n    <AppProvider ctxValue={ctxValue}>\n      <SqlHistoryCard />\n    </AppProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/sql-limit.tsx",
    "content": "import { useMemo } from \"react\"\n\nimport { AppProvider, SqlLimitCard } from \"../../../_shared/sql-limit\"\nimport { useAppContext } from \"../../ctx\"\n\nexport function SqlLimit({ sqlDigest }: { sqlDigest: string }) {\n  const ctx = useAppContext()\n\n  const ctxValue = useMemo(\n    () => ({\n      ...ctx,\n      sqlDigest,\n    }),\n    [ctx, sqlDigest],\n  )\n\n  return (\n    <AppProvider ctxValue={ctxValue}>\n      <SqlLimitCard />\n    </AppProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/stmt-basic.tsx",
    "content": "import { formatTime, useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Box, Card, SimpleGrid, Typography } from \"@tidbcloud/uikit\"\n\nimport { StatementModel } from \"../../models\"\n\nexport function StmtBasic({ stmt }: { stmt: StatementModel }) {\n  const { tt } = useTn(\"statement\")\n  return (\n    <Card shadow=\"xs\" p=\"md\">\n      <SimpleGrid cols={2} spacing=\"xs\">\n        <Box>\n          <Typography variant=\"body-lg\" c=\"carbon.7\">\n            {tt(\"Query Template ID\")}\n          </Typography>\n          <Typography style={{ wordBreak: \"break-all\" }}>\n            {stmt.digest ?? \"\"}\n          </Typography>\n        </Box>\n        <Box>\n          <Typography variant=\"body-lg\" c=\"carbon.7\">\n            {tt(\"Time Range\")}\n          </Typography>\n          <Typography>\n            {formatTime(stmt.summary_begin_time! * 1000)} ~{\" \"}\n            {formatTime(stmt.summary_end_time! * 1000)}\n          </Typography>\n        </Box>\n        <Box>\n          <Typography variant=\"body-lg\" c=\"carbon.7\">\n            {tt(\"Plans Count\")}\n          </Typography>\n          <Typography>{stmt.plan_count!}</Typography>\n        </Box>\n        <Box>\n          <Typography variant=\"body-lg\" c=\"carbon.7\">\n            {tt(\"Execution Database\")}\n          </Typography>\n          <Typography>{stmt.schema_name || \"-\"}</Typography>\n        </Box>\n      </SimpleGrid>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/detail/stmt-sql.tsx",
    "content": "import { formatSql } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Card, Stack, Title } from \"@tidbcloud/uikit\"\nimport { CodeBlock } from \"@tidbcloud/uikit/biz\"\nimport { useMemo } from \"react\"\n\nexport function StmtSQL({ title, sql }: { title: string; sql: string }) {\n  const formattedSQL = useMemo(() => formatSql(sql), [sql])\n\n  return (\n    <Card shadow=\"xs\" p=\"md\">\n      <Stack gap=\"xs\">\n        <Title order={5}>{title}</Title>\n\n        <CodeBlock\n          language=\"sql\"\n          foldProps={{\n            persistenceKey: `statement.detail.${title}`,\n            iconVisible: true,\n          }}\n        >\n          {formattedSQL}\n        </CodeBlock>\n      </Stack>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/list/advanced-filters-modal.tsx",
    "content": "import { AdvancedFiltersModal as AFModal } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMemo } from \"react\"\n\nimport { useAppContext } from \"../../ctx\"\nimport { useListUrlState } from \"../../shared-state/list-url-state\"\nimport { useAdvancedFilterNamesData } from \"../../utils/use-data\"\n\nexport function AdvancedFiltersModal() {\n  const ctx = useAppContext()\n  const { data: availableFiltersData } = useAdvancedFilterNamesData()\n  const { advancedFilters, setAdvancedFilters } = useListUrlState()\n  const { tk } = useTn(\"statement\")\n\n  const availableFilters = useMemo(\n    () =>\n      (availableFiltersData || []).map((f) => ({\n        label: tk(`fields.${f}`),\n        value: f,\n      })),\n    [availableFiltersData, tk],\n  )\n\n  function handleReqFilterInfo(name: string) {\n    return ctx.api.getAdvancedFilterInfo({ name })\n  }\n\n  return (\n    <AFModal\n      availableFilters={availableFilters}\n      advancedFilters={advancedFilters}\n      onUpdateFilters={setAdvancedFilters}\n      reqFilterInfo={handleReqFilterInfo}\n    />\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/list/cols-select.tsx",
    "content": "import { ColumnMultiSelect } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useMemo } from \"react\"\n\nimport { useListUrlState } from \"../../shared-state/list-url-state\"\nimport { useAvailableFieldsData } from \"../../utils/use-data\"\n\nimport { useListTableColumns } from \"./cols\"\n\nexport function ColsSelect() {\n  const { cols, setCols } = useListUrlState()\n  const { data: availableFields } = useAvailableFieldsData()\n  const tableColumns = useListTableColumns()\n\n  const colsData = useMemo(() => {\n    return tableColumns\n      .filter((f) => f.id !== undefined)\n      .filter((f) => availableFields?.includes(f.id!))\n      .map((f) => ({ label: f.header, val: f.id! }))\n  }, [availableFields, tableColumns])\n\n  function handleColsChange(newCols: string[]) {\n    // to avoid conflict with the default value (\"digest_text,sum_latency,avg_latency,exec_count,plan_count\") when cols is no value\n    if (newCols.length === 0) {\n      setCols([\"empty\"])\n    } else {\n      setCols(newCols)\n    }\n  }\n\n  return (\n    <ColumnMultiSelect\n      data={colsData}\n      value={cols}\n      onChange={handleColsChange}\n      onReset={() => setCols([])}\n    />\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/list/cols.tsx",
    "content": "import {\n  EvictedSQL,\n  SQLWithHover,\n} from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { Trans, useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Box, Kbd, Typography, openConfirmModal } from \"@tidbcloud/uikit\"\nimport { useMemo } from \"react\"\n\nimport { TableColsFactory } from \"../../../_shared/cols-factory\"\nimport { useAppContext } from \"../../ctx\"\nimport { StatementModel } from \"../../models\"\nimport { useSelectedStatementState } from \"../../shared-state/memory-state\"\n\nconst REMEMBER_KEY = \"statement.press_ctrl_to_open_in_new_tab.tip.remember\"\n\n// @ts-expect-error @typescript-eslint/no-unused-vars\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\nfunction useLocales() {\n  const { tt } = useTn(\"statement\")\n  // used for gogocode to scan and generate en.json before build\n  tt(\n    \"When opening the detail page, you can press <kbd>Ctrl</kbd> or <kbd>⌘</kbd> to view it in a new tab, or <kbd>Shift</kbd> to view it in a new window.\",\n  )\n}\n\nfunction SqlCell({ row }: { row: StatementModel }) {\n  const { tt } = useTn(\"statement\")\n  const ctx = useAppContext()\n  const setSelectedStatement = useSelectedStatementState(\n    (s) => s.setSelectedStatement,\n  )\n\n  function handleClick(ev: React.MouseEvent) {\n    const { digest, schema_name, summary_begin_time, summary_end_time } = row\n    const statementId = [digest, schema_name].join(\",\")\n    setSelectedStatement(statementId)\n\n    const newTab = ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey\n    // if the user don't press the ctrl/cmd/shift/alt and don't know this operation before\n    // we should show a confirm dialog to tell the user this tip\n    // after he know it, we won't show it again\n    if (!newTab) {\n      const remember = localStorage.getItem(REMEMBER_KEY)\n      if (remember !== \"true\") {\n        openConfirmModal({\n          title: tt(\"Tips\"),\n          children: (\n            <Typography>\n              <Trans\n                ns=\"statement\"\n                i18nKey={\n                  \"When opening the detail page, you can press <kbd>Ctrl</kbd> or <kbd>⌘</kbd> to view it in a new tab, or <kbd>Shift</kbd> to view it in a new window.\"\n                }\n                components={{ kbd: <Kbd /> }}\n              />\n            </Typography>\n          ),\n          labels: {\n            confirm: tt(\"I got it\"),\n            cancel: tt(\"Tell me again next time\"),\n          },\n          onConfirm: () => {\n            localStorage.setItem(REMEMBER_KEY, \"true\")\n          },\n        })\n      }\n    }\n\n    const id = [summary_begin_time, summary_end_time, digest, schema_name].join(\n      \",\",\n    )\n    ctx.actions.openDetail(id, newTab)\n  }\n\n  return row.digest_text ? (\n    <Box sx={{ cursor: \"pointer\" }} onClick={handleClick} w=\"100%\">\n      <SQLWithHover sql={row.digest_text} />\n    </Box>\n  ) : (\n    <EvictedSQL />\n  )\n}\n\nexport function useListTableColumns() {\n  const { tk } = useTn(\"statement\")\n  const columns = useMemo(() => {\n    const tcf = new TableColsFactory<StatementModel>(tk)\n    return tcf.columns([\n      tcf.text(\"digest_text\").patchConfig({\n        minSize: 600,\n        accessorFn: (row) => <SqlCell row={row} />,\n      }),\n      tcf.text(\"digest\"),\n\n      tcf.number(\"sum_latency\", \"ns\"),\n      tcf.number(\"avg_latency\", \"ns\"),\n      tcf.number(\"max_latency\", \"ns\"),\n      tcf.number(\"min_latency\", \"ns\"),\n      tcf.number(\"exec_count\", \"short\"),\n\n      tcf.number(\"plan_count\", \"short\"),\n      tcf.number(\"plan_cache_hits\", \"short\"),\n\n      tcf.number(\"avg_mem\", \"bytes\"),\n      tcf.number(\"max_mem\", \"bytes\"),\n      tcf.number(\"avg_disk\", \"bytes\"),\n      tcf.number(\"max_disk\", \"bytes\"),\n\n      tcf.number(\"sum_errors\", \"short\"),\n      tcf.number(\"sum_warnings\", \"short\"),\n\n      tcf.number(\"avg_parse_latency\", \"ns\"),\n      tcf.number(\"max_parse_latency\", \"ns\"),\n      tcf.number(\"avg_compile_latency\", \"ns\"),\n      tcf.number(\"max_compile_latency\", \"ns\"),\n      tcf.number(\"sum_cop_task_num\", \"short\"),\n\n      tcf.number(\"avg_cop_process_time\", \"ns\"),\n      tcf.number(\"max_cop_process_time\", \"ns\"),\n      tcf.number(\"avg_cop_wait_time\", \"ns\"),\n      tcf.number(\"max_cop_wait_time\", \"ns\"),\n      tcf.number(\"avg_process_time\", \"ns\"),\n      tcf.number(\"max_process_time\", \"ns\"),\n      tcf.number(\"avg_wait_time\", \"ns\"),\n      tcf.number(\"max_wait_time\", \"ns\"),\n      tcf.number(\"avg_backoff_time\", \"ns\"),\n      tcf.number(\"max_backoff_time\", \"ns\"),\n      tcf.number(\"avg_write_keys\", \"short\"),\n      tcf.number(\"max_write_keys\", \"short\"),\n      tcf.number(\"avg_processed_keys\", \"short\"),\n      tcf.number(\"max_processed_keys\", \"short\"),\n      tcf.number(\"avg_total_keys\", \"short\"),\n      tcf.number(\"max_total_keys\", \"short\"),\n      tcf.number(\"avg_prewrite_time\", \"ns\"),\n      tcf.number(\"max_prewrite_time\", \"ns\"),\n      tcf.number(\"avg_commit_time\", \"ns\"),\n      tcf.number(\"max_commit_time\", \"ns\"),\n      tcf.number(\"avg_get_commit_ts_time\", \"ns\"),\n      tcf.number(\"max_get_commit_ts_time\", \"ns\"),\n      tcf.number(\"avg_commit_backoff_time\", \"ns\"),\n      tcf.number(\"max_commit_backoff_time\", \"ns\"),\n      tcf.number(\"avg_resolve_lock_time\", \"ns\"),\n      tcf.number(\"max_resolve_lock_time\", \"ns\"),\n      tcf.number(\"avg_local_latch_wait_time\", \"ns\"),\n      tcf.number(\"max_local_latch_wait_time\", \"ns\"),\n      tcf.number(\"avg_write_size\", \"bytes\"),\n      tcf.number(\"max_write_size\", \"bytes\"),\n      tcf.number(\"avg_prewrite_regions\", \"short\"),\n      tcf.number(\"max_prewrite_regions\", \"short\"),\n      tcf.number(\"avg_txn_retry\", \"short\"),\n      tcf.number(\"max_txn_retry\", \"short\"),\n\n      tcf.number(\"sum_backoff_times\", \"short\"),\n      tcf.number(\"avg_affected_rows\", \"short\"),\n\n      tcf.timestamp(\"first_seen\"),\n      tcf.timestamp(\"last_seen\"),\n      tcf.text(\"sample_user\"),\n      tcf.text(\"schema_name\"),\n      tcf.text(\"table_names\"),\n      tcf.text(\"index_names\"),\n      tcf.text(\"plan_digest\"),\n      tcf.text(\"related_schemas\"),\n\n      tcf.number(\"avg_rocksdb_delete_skipped_count\", \"short\"),\n      tcf.number(\"max_rocksdb_delete_skipped_count\", \"short\"),\n      tcf.number(\"avg_rocksdb_key_skipped_count\", \"short\"),\n      tcf.number(\"max_rocksdb_key_skipped_count\", \"short\"),\n      tcf.number(\"avg_rocksdb_block_cache_hit_count\", \"short\"),\n      tcf.number(\"max_rocksdb_block_cache_hit_count\", \"short\"),\n      tcf.number(\"avg_rocksdb_block_read_count\", \"short\"),\n      tcf.number(\"max_rocksdb_block_read_count\", \"short\"),\n      tcf.number(\"avg_rocksdb_block_read_byte\", \"bytes\"),\n      tcf.number(\"max_rocksdb_block_read_byte\", \"bytes\"),\n\n      tcf.text(\"resource_group\"),\n      tcf.number(\"avg_ru\", \"short\"),\n      tcf.number(\"max_ru\", \"short\"),\n      tcf.number(\"avg_time_queued_by_rc\", \"ns\"),\n      tcf.number(\"max_time_queued_by_rc\", \"ns\"),\n    ])\n  }, [tk])\n\n  return columns\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/list/disabled-status.tsx",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Button, Card, Stack, Title, Typography } from \"@tidbcloud/uikit\"\n\nimport { useSettingDrawerState } from \"../../shared-state/memory-state\"\n\nexport function DisabledStatusCard() {\n  const { tt } = useTn(\"statement\")\n  const setSettingDrawerVisible = useSettingDrawerState((s) => s.setVisible)\n\n  return (\n    <Card shadow=\"none\" h={200}>\n      <Stack justify=\"center\" align=\"center\" h=\"100%\">\n        <Title order={4}>{tt(\"Feature Not Enabled\")}</Title>\n        <Typography c=\"dimmed\">\n          {tt(\n            \"Statement feature is not enabled so that statement history cannot be viewed. You can modify settings to enable the feature and wait for new data being collected.\",\n          )}\n        </Typography>\n        <Button onClick={() => setSettingDrawerVisible(true)}>\n          {tt(\"Open Setting\")}\n        </Button>\n      </Stack>\n    </Card>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/list/filters-with-advanced.tsx",
    "content": "import { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Group } from \"@tidbcloud/uikit\"\n\nimport {\n  MemoryStateResetButton,\n  UrlStateSearchInput,\n  UrlStateTimeRangePicker,\n} from \"../../../_shared/state-filters\"\n\nimport { AdvancedFiltersModal } from \"./advanced-filters-modal\"\n\nexport function FiltersWithAdvanced() {\n  const { tt } = useTn(\"statement\")\n\n  return (\n    <Group>\n      <UrlStateTimeRangePicker />\n      <UrlStateSearchInput placeholder={tt(\"Find SQL text\")} />\n      <AdvancedFiltersModal />\n      <MemoryStateResetButton text={tt(\"Clear Filters\")} />\n    </Group>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/list/filters.tsx",
    "content": "import { FilterMultiSelect } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport { useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Group } from \"@tidbcloud/uikit\"\n\nimport {\n  MemoryStateResetButton,\n  UrlStateSearchInput,\n  UrlStateTimeRangePicker,\n} from \"../../../_shared/state-filters\"\nimport { useListUrlState } from \"../../shared-state/list-url-state\"\nimport {\n  useDbsData,\n  useRuGroupsData,\n  useStmtKindsData,\n} from \"../../utils/use-data\"\n\nfunction DBsSelect() {\n  const { dbs, setDbs } = useListUrlState()\n  const { data: dbsData } = useDbsData()\n\n  return (\n    dbsData &&\n    dbsData.length > 0 && (\n      <FilterMultiSelect\n        kind=\"Databases\"\n        data={dbsData}\n        value={dbs}\n        onChange={setDbs}\n        width={200}\n      />\n    )\n  )\n}\n\nfunction RuGroupsSelect() {\n  const { ruGroups, setRuGroups } = useListUrlState()\n  const { data: ruGroupsData } = useRuGroupsData()\n\n  // ignore `default` resource group\n  return (\n    ruGroupsData &&\n    ruGroupsData.length > 1 && (\n      <FilterMultiSelect\n        kind=\"Resource Groups\"\n        data={ruGroupsData}\n        value={ruGroups}\n        onChange={setRuGroups}\n        width={240}\n      />\n    )\n  )\n}\n\nfunction StmtKindsSelect() {\n  const { kinds, setKinds } = useListUrlState()\n  const { data: stmtKindsData } = useStmtKindsData()\n\n  return (\n    stmtKindsData &&\n    stmtKindsData.length > 0 && (\n      <FilterMultiSelect\n        kind=\"Statement Kinds\"\n        data={stmtKindsData}\n        value={kinds}\n        onChange={setKinds}\n        width={240}\n      />\n    )\n  )\n}\n\nexport function Filters() {\n  const { tt } = useTn(\"statement\")\n\n  return (\n    <Group>\n      <UrlStateTimeRangePicker />\n      <DBsSelect />\n      <RuGroupsSelect />\n      <StmtKindsSelect />\n      <UrlStateSearchInput placeholder={tt(\"Find SQL text\")} />\n      <MemoryStateResetButton text={tt(\"Clear Filters\")} />\n    </Group>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/list/index.tsx",
    "content": "import { Box, Group, Stack, Title } from \"@tidbcloud/uikit\"\n\nimport { useAppContext } from \"../../ctx\"\n\nimport { ColsSelect } from \"./cols-select\"\nimport { FiltersWithAdvanced } from \"./filters-with-advanced\"\nimport { RefreshButton } from \"./refresh-button\"\nimport { ListTable } from \"./table\"\nimport { TimeRangeFixAlert } from \"./time-range-fix-alert\"\n\nexport function List() {\n  const ctx = useAppContext()\n  return (\n    <Stack>\n      {ctx.cfg.title && (\n        <Title order={1} mb=\"md\">\n          {ctx.cfg.title}\n        </Title>\n      )}\n\n      <Group>\n        <FiltersWithAdvanced />\n\n        <Box sx={{ flexGrow: 1 }} />\n\n        <ColsSelect />\n        <RefreshButton />\n      </Group>\n\n      <TimeRangeFixAlert />\n\n      <ListTable />\n    </Stack>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/list/refresh-button.tsx",
    "content": "import { ActionIcon } from \"@tidbcloud/uikit\"\nimport { IconRefreshCw02 } from \"@tidbcloud/uikit/icons\"\n\nimport { useListData } from \"../../utils/use-data\"\n\nexport function RefreshButton() {\n  const { refetch: reloadList } = useListData()\n\n  return (\n    <ActionIcon\n      onClick={() => {\n        reloadList()\n      }}\n    >\n      <IconRefreshCw02 size={16} />\n    </ActionIcon>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/list/stmt-setting-button.tsx",
    "content": "import { ActionIcon } from \"@tidbcloud/uikit\"\nimport { IconSettings01 } from \"@tidbcloud/uikit/icons\"\n\nimport { useSettingDrawerState } from \"../../shared-state/memory-state\"\nimport { StatementSettingDrawer } from \"../setting\"\n\nexport function StatementSettingButton() {\n  const visible = useSettingDrawerState((s) => s.visible)\n  const setVisible = useSettingDrawerState((s) => s.setVisible)\n\n  return (\n    <>\n      <ActionIcon onClick={() => setVisible(true)}>\n        <IconSettings01 size={16} />\n      </ActionIcon>\n\n      <StatementSettingDrawer\n        visible={visible}\n        onClose={() => setVisible(false)}\n      />\n    </>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/list/table.tsx",
    "content": "import {\n  useProTablePaginationState,\n  useProTableSortState,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { ProTable } from \"@tidbcloud/uikit/biz\"\nimport { useMemo } from \"react\"\n\nimport { useListUrlState } from \"../../shared-state/list-url-state\"\nimport { useSelectedStatementState } from \"../../shared-state/memory-state\"\nimport { useListData } from \"../../utils/use-data\"\n\nimport { useListTableColumns } from \"./cols\"\n\n// @todo: make it resuable, resolve locales issue\nconst usePaginationConfigs = () => {\n  const { tt } = useTn(\"statement\")\n\n  return {\n    showTotal: true,\n    showRowsPerPage: true,\n    rowsPerPageOptions: [10, 15, 20, 30].map((value) => ({\n      value: String(value),\n      label: `${value} / ${tt(\"page\")}`,\n    })),\n    localization: {\n      total: `${tt(\"total\")}: `,\n    },\n  }\n}\n\nexport function ListTable() {\n  const { data, isLoading } = useListData()\n  const tableColumns = useListTableColumns()\n  const {\n    sortRule,\n    setSortRule,\n    pagination,\n    setPagination,\n    cols: visibleCols,\n  } = useListUrlState()\n  const { sortingState, setSortingState } = useProTableSortState(\n    sortRule,\n    setSortRule,\n  )\n  const { paginationState, setPaginationState } = useProTablePaginationState(\n    pagination,\n    setPagination,\n  )\n  const columnVisibility = useMemo(() => {\n    return tableColumns\n      .map((c) => c.id)\n      .filter((f) => f !== undefined)\n      .reduce(\n        (acc, col) => {\n          acc[col] = visibleCols.includes(col) || visibleCols.includes(\"all\")\n          return acc\n        },\n        {} as Record<string, boolean>,\n      )\n  }, [tableColumns, visibleCols])\n\n  const selectedStatementId = useSelectedStatementState((s) => s.statementId)\n\n  const { tt } = useTn(\"statement\")\n\n  const paginationConfig = usePaginationConfigs()\n\n  return (\n    <ProTable\n      layoutMode=\"grid\"\n      enableColumnResizing\n      enableColumnPinning\n      enableSorting\n      manualSorting\n      sortDescFirst\n      onSortingChange={setSortingState}\n      manualPagination\n      onPaginationChange={setPaginationState}\n      pagination={paginationConfig}\n      rowCount={data?.total ?? 0}\n      state={{\n        isLoading,\n        sorting: sortingState,\n        pagination: paginationState,\n        columnVisibility,\n      }}\n      initialState={{ columnPinning: { left: [\"digest_text\"] } }}\n      mantineTableBodyRowProps={({ row }) => {\n        const { digest, schema_name } = row.original\n        const id = `${digest},${schema_name}`\n        return selectedStatementId === id\n          ? {\n              style(theme) {\n                return {\n                  borderWidth: 1,\n                  borderStyle: \"solid\",\n                  borderColor: theme.colors.carbon[7],\n                }\n              },\n            }\n          : {}\n      }}\n      columns={tableColumns}\n      data={data?.items ?? []}\n      emptyMessage={tt(\"No Data\")}\n    />\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/list/time-range-fix-alert.tsx",
    "content": "import { formatTime, useTn } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { Alert } from \"@tidbcloud/uikit\"\nimport { useMemo } from \"react\"\n\nimport { useListData } from \"../../utils/use-data\"\n\nexport function TimeRangeFixAlert() {\n  const { data } = useListData()\n  const { tt } = useTn(\"statement\")\n\n  const maxTime = useMemo(\n    () =>\n      (data?.items || []).reduce(\n        (max, d) => (d.summary_end_time! > max ? d.summary_end_time! : max),\n        0,\n      ),\n    [data],\n  )\n  const minTime = useMemo(\n    () =>\n      (data?.items || []).reduce(\n        (min, d) => (d.summary_begin_time! < min ? d.summary_begin_time! : min),\n        maxTime,\n      ),\n    [data, maxTime],\n  )\n\n  if (minTime !== maxTime) {\n    return (\n      <Alert p={8}>\n        {tt(\n          \"Due to time window and expiration configurations, currently displaying data in time range is {{begin}} ~ {{end}}\",\n          {\n            begin: formatTime(minTime * 1000),\n            end: formatTime(maxTime * 1000),\n          },\n        )}\n      </Alert>\n    )\n  }\n\n  return null\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/pages/setting/index.tsx",
    "content": "import { LoadingSkeleton } from \"@pingcap-incubator/tidb-dashboard-lib-biz-ui\"\nimport {\n  formatDuration,\n  useTn,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport {\n  Button,\n  Card,\n  Divider,\n  Drawer,\n  Group,\n  Select,\n  Stack,\n  Switch,\n  Typography,\n  notifier,\n  openConfirmModal,\n} from \"@tidbcloud/uikit\"\nimport { useMemo, useState } from \"react\"\n\nimport { StatementConfigModel } from \"../../models\"\nimport {\n  useStmtConfigData,\n  useUpdateStmtConfigData,\n} from \"../../utils/use-data\"\n\nconst MAX_SIZE_OPTIONS = Array.from(\n  { length: 49 },\n  (_, i) => (i + 2) * 100,\n).map((i) => ({\n  value: i + \"\",\n  label: i + \"\",\n}))\n\nconst WINDOW_SIZE_OPTIONS = [1, 5, 15, 30, 60].map((i) => ({\n  value: i * 60 + \"\",\n  label: i + \"\",\n}))\n\nconst WINDOWS_NUMBER_OPTIONS = Array.from({ length: 255 }, (_, i) => i + 1).map(\n  (i) => ({\n    value: i + \"\",\n    label: i + \"\",\n  }),\n)\n\nfunction StatementSettingBody({\n  config,\n  onClose,\n}: {\n  config: StatementConfigModel\n  onClose: () => void\n}) {\n  const { tt } = useTn(\"statement\")\n  const [enable, setEnable] = useState(config.enable)\n  const [maxSize, setMaxSize] = useState(config.max_size)\n  const [windowSize, setWindowSize] = useState(config.refresh_interval)\n  const [windowsNumber, setWindowsNumber] = useState(config.history_size)\n  const [internalQuery, setInternalQuery] = useState(config.internal_query)\n\n  const total = useMemo(\n    () => formatDuration(windowSize * windowsNumber),\n    [windowSize, windowsNumber],\n  )\n\n  const updateMut = useUpdateStmtConfigData()\n\n  async function handleUpdate() {\n    try {\n      await updateMut.mutateAsync({\n        enable,\n        max_size: maxSize,\n        refresh_interval: windowSize,\n        history_size: windowsNumber,\n        internal_query: internalQuery,\n      })\n      notifier.success(tt(\"Update statement config successfully!\"))\n      onClose()\n    } catch (_err) {\n      notifier.error(tt(\"Update statement config failed!\"))\n    }\n  }\n\n  function handleSave() {\n    if (!enable && config.enable) {\n      openConfirmModal({\n        title: tt(\"Disable Statement Feature\"),\n        children: tt(\n          \"Are you sure want to disable this feature? Current statement history will be cleared.\",\n        ),\n        confirmProps: { color: \"red\", variant: \"outline\" },\n        labels: { confirm: tt(\"Disable\"), cancel: tt(\"Cancel\") },\n        onConfirm: handleUpdate,\n      })\n    } else {\n      handleUpdate()\n    }\n  }\n\n  return (\n    <Stack gap=\"lg\" pt={16}>\n      <Switch\n        checked={enable}\n        onChange={(event) => setEnable(event.currentTarget.checked)}\n        label={tt(\"Feature Enable\")}\n        description={tt(\n          \"Whether Statement feature is enabled. When enabled, there will be a small SQL statement execution overhead.\",\n        )}\n      />\n      {enable && (\n        <>\n          <Select\n            label={tt(\"Max Number of Statements\")}\n            description={tt(\n              \"Max number of statement to collect. After exceeding, old statement information will be dropped. You may enlarge this setting when memory is sufficient and you discovered that data displayed in UI is incomplete.\",\n            )}\n            value={maxSize + \"\"}\n            onChange={(v) => setMaxSize(Number(v))}\n            data={MAX_SIZE_OPTIONS}\n          />\n          <Select\n            label={tt(\"Window Size (min)\")}\n            description={tt(\n              \"By reducing this setting you can select time range more precisely.\",\n            )}\n            value={windowSize + \"\"}\n            onChange={(v) => setWindowSize(Number(v))}\n            data={WINDOW_SIZE_OPTIONS}\n          />\n          <Select\n            label={tt(\"Windows Number\")}\n            description={tt(\n              \"By enlarging this setting more statement history will be preserved, with larger memory cost.\",\n            )}\n            value={windowsNumber + \"\"}\n            onChange={(v) => setWindowsNumber(Number(v))}\n            data={WINDOWS_NUMBER_OPTIONS}\n          />\n          <Card shadow=\"none\" p=\"xs\">\n            <Typography>{tt(\"SQL Statement Total History Size\")}</Typography>\n            <Typography c=\"dimmed\" fz={12}>\n              {tt(\"Window Size x Windows Number\")}\n            </Typography>\n            <Typography variant=\"label-lg\" fz={16}>\n              {total}\n            </Typography>\n          </Card>\n          <Switch\n            checked={internalQuery}\n            onChange={(event) => setInternalQuery(event.currentTarget.checked)}\n            label={tt(\"Collect Internal Queries\")}\n            description={tt(\n              \"After enabled, TiDB internal queries will be collected as well.\",\n            )}\n          />\n        </>\n      )}\n      <Divider />\n      <Group justify=\"flex-end\">\n        <Button variant=\"default\" onClick={onClose}>\n          {tt(\"Cancel\")}\n        </Button>\n        <Button onClick={handleSave}>{tt(\"Save\")}</Button>\n      </Group>\n    </Stack>\n  )\n}\n\nexport function StatementSettingDrawer({\n  visible,\n  onClose,\n}: {\n  visible: boolean\n  onClose: () => void\n}) {\n  const { tt } = useTn(\"statement\")\n  const { data: configData, isLoading } = useStmtConfigData()\n\n  return (\n    <Drawer\n      title={tt(\"Statement Setting\")}\n      position=\"right\"\n      opened={visible}\n      onClose={onClose}\n    >\n      {isLoading && <LoadingSkeleton />}\n      {!isLoading && configData && (\n        <StatementSettingBody config={configData} onClose={onClose} />\n      )}\n    </Drawer>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/shared-state/detail-url-state.ts",
    "content": "import { useUrlState } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useCallback } from \"react\"\n\ntype DetailUrlState = Partial<Record<\"id\" | \"plan\", string>>\n\nexport function useDetailUrlState() {\n  const [queryParams, setQueryParams] = useUrlState<DetailUrlState>()\n\n  const id = queryParams.id ?? \"\"\n\n  const plan = queryParams.plan ?? \"\"\n  const setPlan = useCallback(\n    (newPlan: string) => {\n      setQueryParams({ plan: newPlan || undefined })\n    },\n    [setQueryParams],\n  )\n\n  return {\n    id,\n\n    plan,\n    setPlan,\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/shared-state/list-url-state.ts",
    "content": "import {\n  AdvancedFiltersUrlState,\n  PaginationUrlState,\n  SortUrlState,\n  TimeRangeUrlState,\n  useAdvancedFiltersUrlState,\n  usePaginationUrlState,\n  useResetFiltersState,\n  useSortUrlState,\n  useTimeRangeUrlState,\n  useUrlState,\n} from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useCallback, useEffect, useMemo } from \"react\"\n\ntype ListUrlState = Partial<\n  Record<\"dbs\" | \"ruGroups\" | \"kinds\" | \"term\" | \"cols\", string>\n> &\n  SortUrlState &\n  PaginationUrlState &\n  TimeRangeUrlState &\n  AdvancedFiltersUrlState\n\nexport function useListUrlState() {\n  const [queryParams, setQueryParams] = useUrlState<ListUrlState>()\n  const { sortRule, setSortRule } = useSortUrlState(\"sum_latency\")\n  const { pagination, setPagination } = usePaginationUrlState()\n  const { timeRange, setTimeRange } = useTimeRangeUrlState()\n  const { advancedFilters, setAdvancedFilters } = useAdvancedFiltersUrlState()\n\n  // dbs\n  const dbs = useMemo<string[]>(() => {\n    const _dbs = queryParams.dbs\n    return _dbs ? _dbs.split(\",\") : []\n  }, [queryParams.dbs])\n  const setDbs = useCallback(\n    (v: string[]) => {\n      setQueryParams({ dbs: v.join(\",\"), pageIndex: undefined })\n    },\n    [setQueryParams],\n  )\n\n  // ruGroups\n  const ruGroups = useMemo(() => {\n    const _ruGroups = queryParams.ruGroups\n    return _ruGroups ? _ruGroups.split(\",\") : []\n  }, [queryParams.ruGroups])\n  const setRuGroups = useCallback(\n    (v: string[]) => {\n      setQueryParams({ ruGroups: v.join(\",\"), pageIndex: undefined })\n    },\n    [setQueryParams],\n  )\n\n  // kinds\n  const kinds = useMemo(() => {\n    const _kinds = queryParams.kinds\n    return _kinds ? _kinds.split(\",\") : []\n  }, [queryParams.kinds])\n  const setKinds = useCallback(\n    (newKinds: string[]) => {\n      setQueryParams({ kinds: newKinds.join(\",\"), pageIndex: undefined })\n    },\n    [setQueryParams],\n  )\n\n  // term\n  const term = decodeURIComponent(queryParams.term ?? \"\")\n  const setTerm = useCallback(\n    (v?: string) => {\n      setQueryParams({\n        term: v ? encodeURIComponent(v) : v,\n        pageIndex: undefined,\n      })\n    },\n    [setQueryParams],\n  )\n\n  // reset filters, not include sort\n  const resetFilters = useCallback(() => {\n    setQueryParams({\n      from: undefined,\n      to: undefined,\n      dbs: undefined,\n      ruGroups: undefined,\n      kinds: undefined,\n      term: undefined,\n      af: undefined,\n      pageIndex: undefined,\n    })\n  }, [setQueryParams])\n  const resetVal = useResetFiltersState((s) => s.resetVal)\n  useEffect(() => {\n    if (resetVal > 0) {\n      resetFilters()\n    }\n  }, [resetVal])\n\n  // cols\n  const cols = useMemo<string[]>(() => {\n    const _cols =\n      queryParams.cols ||\n      \"digest_text,sum_latency,avg_latency,exec_count,plan_count\"\n    return _cols ? _cols.split(\",\") : []\n  }, [queryParams.cols])\n  const setCols = useCallback(\n    (v: string[]) => {\n      setQueryParams({ cols: v.join(\",\") })\n    },\n    [setQueryParams],\n  )\n\n  return {\n    timeRange,\n    setTimeRange,\n\n    dbs,\n    setDbs,\n\n    ruGroups,\n    setRuGroups,\n\n    kinds,\n    setKinds,\n\n    term,\n    setTerm,\n\n    advancedFilters,\n    setAdvancedFilters,\n\n    sortRule,\n    setSortRule,\n    pagination,\n    setPagination,\n\n    cols,\n    setCols,\n\n    queryParams,\n    setQueryParams,\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/shared-state/memory-state.ts",
    "content": "import { create } from \"zustand\"\n\ninterface SelectedStatementState {\n  statementId: string\n  setSelectedStatement: (statementId: string) => void\n}\n\nexport const useSelectedStatementState = create<SelectedStatementState>(\n  (set) => ({\n    statementId: \"\",\n    setSelectedStatement: (statementId: string) => set({ statementId }),\n  }),\n)\n\n//------------------------------------------------------------------\n\ninterface SettingDrawerState {\n  visible: boolean\n  setVisible: (visible: boolean) => void\n}\n\nexport const useSettingDrawerState = create<SettingDrawerState>((set) => ({\n  visible: false,\n  setVisible: (visible: boolean) => set({ visible }),\n}))\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/utils/constants.ts",
    "content": "export const QUICK_RANGES: number[] = [\n  5 * 60, // 5 mins\n  15 * 60,\n  30 * 60,\n  60 * 60,\n  6 * 60 * 60,\n  12 * 60 * 60,\n  24 * 60 * 60,\n  2 * 24 * 60 * 60,\n  3 * 24 * 60 * 60, // 3 days\n  7 * 24 * 60 * 60, // 7 days\n]\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/src/statement/utils/use-data.ts",
    "content": "import { toTimeRangeValue } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\"\n\nimport { useAppContext } from \"../ctx\"\nimport { StatementConfigModel } from \"../models\"\nimport { useDetailUrlState } from \"../shared-state/detail-url-state\"\nimport { useListUrlState } from \"../shared-state/list-url-state\"\n\nexport function useStmtConfigData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"statement\", \"config\"],\n    queryFn: () => ctx.api.getStmtConfig(),\n  })\n}\n\nexport function useUpdateStmtConfigData() {\n  const ctx = useAppContext()\n  const queryClient = useQueryClient()\n\n  return useMutation({\n    mutationFn: (params: StatementConfigModel) =>\n      ctx.api.updateStmtConfig(params),\n    onSuccess: () => {\n      queryClient.invalidateQueries({\n        queryKey: [ctx.ctxId, \"statement\", \"config\"],\n      })\n    },\n  })\n}\n\nexport function useDbsData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"statement\", \"dbs\"],\n    queryFn: () => ctx.api.getDbs(),\n  })\n}\n\nexport function useRuGroupsData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"statement\", \"ru-groups\"],\n    queryFn: () => ctx.api.getRuGroups(),\n  })\n}\n\nexport function useStmtKindsData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"statement\", \"stmt-kinds\"],\n    queryFn: () => ctx.api.getStmtKinds(),\n  })\n}\n\nexport function useListData() {\n  const ctx = useAppContext()\n  const {\n    timeRange,\n    dbs,\n    ruGroups,\n    kinds,\n    term,\n    advancedFilters,\n    cols,\n    sortRule,\n    pagination,\n  } = useListUrlState()\n\n  const query = useQuery({\n    queryKey: [\n      ctx.ctxId,\n      \"statement\",\n      \"list\",\n      timeRange,\n      dbs,\n      ruGroups,\n      kinds,\n      term,\n      advancedFilters,\n      cols,\n      sortRule,\n      pagination,\n    ],\n    queryFn: () => {\n      const tr = toTimeRangeValue(timeRange)\n      return ctx.api.getStmtList({\n        beginTime: tr[0],\n        endTime: tr[1],\n        dbs,\n        ruGroups,\n        stmtKinds: kinds,\n        term,\n        advancedFilters,\n        fields: cols.filter((c) => c !== \"empty\"),\n        ...sortRule,\n        ...pagination,\n      })\n    },\n  })\n\n  return query\n}\n\nexport function usePlansListData() {\n  const ctx = useAppContext()\n  const { id } = useDetailUrlState()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"statement\", \"plans-list\", id],\n    queryFn: () => ctx.api.getStmtPlans({ id }),\n  })\n}\n\nexport function usePlanDetailData(plan: string) {\n  const ctx = useAppContext()\n  const { id } = useDetailUrlState()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"statement\", \"plan-detail\", id, plan],\n    queryFn: () =>\n      ctx.api.getStmtPlansDetail({\n        id,\n        plans: [plan],\n      }),\n  })\n}\n\n// sql plan bind\nexport function usePlanBindSupportData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"statement\", \"plan-bind-support\"],\n    queryFn: () => ctx.api.checkPlanBindSupport(),\n  })\n}\n\nexport function usePlanBindStatusData(\n  sqlDigest: string,\n  beginTime: number,\n  endTime: number,\n) {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [\n      ctx.ctxId,\n      \"statement\",\n      \"plan-bind-status\",\n      sqlDigest,\n      beginTime,\n      endTime,\n    ],\n    queryFn: () => ctx.api.getPlanBindStatus({ sqlDigest, beginTime, endTime }),\n  })\n}\n\nexport function useCreatePlanBindData(sqlDigest: string, planDigest: string) {\n  const ctx = useAppContext()\n  const queryClient = useQueryClient()\n\n  return useMutation({\n    mutationFn: () => {\n      return ctx.api.createPlanBind({ planDigest })\n    },\n    onSuccess: () => {\n      queryClient.invalidateQueries({\n        queryKey: [ctx.ctxId, \"statement\", \"plan-bind-status\", sqlDigest],\n      })\n    },\n  })\n}\n\nexport function useDeletePlanBindData(sqlDigest: string) {\n  const ctx = useAppContext()\n  const queryClient = useQueryClient()\n  return useMutation({\n    mutationFn: () => {\n      return ctx.api.deletePlanBind({ sqlDigest })\n    },\n    onSuccess: () => {\n      queryClient.invalidateQueries({\n        queryKey: [ctx.ctxId, \"statement\", \"plan-bind-status\", sqlDigest],\n      })\n    },\n  })\n}\n\n// advanced filters\nexport function useAdvancedFilterNamesData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"statement\", \"advanced-filter-names\"],\n    queryFn: () => ctx.api.getAdvancedFilterNames(),\n  })\n}\n\nexport function useAdvancedFilterInfoData(name: string) {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"statement\", \"advanced-filter-info\", name],\n    queryFn: () => ctx.api.getAdvancedFilterInfo({ name }),\n    enabled: !!name,\n  })\n}\n\n// available fields\nexport function useAvailableFieldsData() {\n  const ctx = useAppContext()\n  return useQuery({\n    queryKey: [ctx.ctxId, \"statement\", \"available-fields\"],\n    queryFn: () => ctx.api.getAvailableFields(),\n  })\n}\n"
  },
  {
    "path": "ui-v2/packages/libs/4-apps/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.app.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\"\n  },\n  \"include\": [\"./src\"]\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/CHANGELOG.md",
    "content": "# test-tidb-dashboard-ui-lib\n\n## 0.13.2\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.20.2\n\n## 0.13.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.20.1\n\n## 0.13.0\n\n### Minor Changes\n\n- bump version\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-api-client@0.13.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.20.0\n\n## 0.12.7\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.19.7\n\n## 0.12.2\n\n### Patch Changes\n\n- @pingcap-incubator/tidb-dashboard-lib-apps@0.19.2\n\n## 0.12.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.19.1\n\n## 0.12.0\n\n### Minor Changes\n\n- refactor: re-exports lib-uitls/lib-charts/lib-primitive-ui/lib-biz-ui from lib-apps\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-api-client@0.12.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.19.0\n\n## 0.11.22\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.14.1\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.18.2\n\n## 0.11.21\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.12.1\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.18.1\n\n## 0.11.20\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.14.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.12.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.18.0\n\n## 0.11.19\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.17.6\n\n## 0.11.18\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.17.5\n\n## 0.11.17\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.17.4\n\n## 0.11.16\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.11.2\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.17.3\n\n## 0.11.15\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.11.1\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.17.2\n\n## 0.11.14\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.17.1\n\n## 0.11.13\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.17.0\n\n## 0.11.12\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.16.2\n\n## 0.11.11\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.16.1\n\n## 0.11.10\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.16.0\n\n## 0.11.9\n\n### Patch Changes\n\n- Updated dependencies\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.15.1\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.13.1\n\n## 0.11.8\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.15.0\n\n## 0.11.7\n\n### Patch Changes\n\n- Updated dependencies\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.13.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.14.0\n\n## 0.11.6\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.12.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.13.0\n\n## 0.11.5\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.12.0\n\n## 0.11.4\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.11.4\n\n## 0.11.3\n\n### Patch Changes\n\n- @pingcap-incubator/tidb-dashboard-lib-apps@0.11.3\n\n## 0.11.2\n\n### Patch Changes\n\n- @pingcap-incubator/tidb-dashboard-lib-apps@0.11.2\n\n## 0.11.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.11.1\n\n## 0.11.0\n\n### Minor Changes\n\n- fix time-range-picker, refine cols-multi-select\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-api-client@0.11.0\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.11.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.11.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.11.0\n\n## 0.10.0\n\n### Minor Changes\n\n- i18n for slow query and statement app\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-api-client@0.10.0\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.10.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.10.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.10.0\n\n## 0.9.1\n\n### Patch Changes\n\n- @pingcap-incubator/tidb-dashboard-lib-apps@0.9.1\n\n## 0.9.0\n\n### Minor Changes\n\n- upgrade uikit\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-api-client@0.9.0\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.9.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.9.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.9.0\n\n## 0.8.5\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.8.5\n\n## 0.8.4\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.8.4\n\n## 0.8.3\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.8.3\n\n## 0.8.2\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.8.2\n\n## 0.8.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.8.1\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.8.1\n\n## 0.8.0\n\n### Minor Changes\n\n- support advanced filters for diagnosis\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-api-client@0.8.0\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.8.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.8.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.8.0\n\n## 0.7.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.7.1\n\n## 0.7.0\n\n### Minor Changes\n\n- refine pagination and sort url state\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-api-client@0.7.0\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.7.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.7.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.7.0\n\n## 0.6.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.6.1\n\n## 0.6.0\n\n### Minor Changes\n\n- refine\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-api-client@0.6.0\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.6.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.6.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.6.0\n\n## 0.5.4\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.5.4\n\n## 0.5.3\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.5.3\n\n## 0.5.2\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.5.2\n\n## 0.5.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.5.1\n\n## 0.5.0\n\n### Minor Changes\n\n- refine slow-query and statement apps\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-api-client@0.5.0\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.5.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.5.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.5.0\n\n## 0.4.2\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.4.2\n\n## 0.4.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.4.1\n\n## 0.4.0\n\n### Minor Changes\n\n- update uikit\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.4.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.4.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.4.0\n\n## 0.3.3\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.3.3\n\n## 0.3.2\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.3.2\n\n## 0.3.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.3.1\n\n## 0.3.0\n\n### Minor Changes\n\n- add azores cluster metrics page\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.3.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.3.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.3.0\n\n## 0.2.1\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.2.1\n\n## 0.2.0\n\n### Minor Changes\n\n- add metrics azores host page\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.2.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.2.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.2.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.2.0\n\n## 0.1.0\n\n### Minor Changes\n\n- update i18n, format utils\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-charts@0.1.0\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.1.0\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.1.0\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.1.0\n\n## 0.0.12\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.10\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.0.13\n\n## 0.0.11\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.9\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.0.12\n\n## 0.0.10\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.8\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.0.11\n\n## 0.0.9\n\n### Patch Changes\n\n- add i18n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-utils@0.0.7\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.0.7\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.0.10\n\n## 0.0.8\n\n### Patch Changes\n\n- @pingcap-incubator/tidb-dashboard-lib-apps@0.0.9\n\n## 0.0.7\n\n### Patch Changes\n\n- support dark mode for lib-charts\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.0.6\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.0.8\n\n## 0.0.6\n\n### Patch Changes\n\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.0.7\n\n## 0.0.5\n\n### Patch Changes\n\n- refine\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.0.5\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.0.6\n\n## 0.0.4\n\n### Patch Changes\n\n- refactor metric\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.0.4\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.0.5\n\n## 0.0.3\n\n### Patch Changes\n\n- upgrade uikit\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.0.3\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.0.4\n\n## 0.0.2\n\n### Patch Changes\n\n- @pingcap-incubator/tidb-dashboard-lib-apps@0.0.3\n\n## 0.0.1\n\n### Patch Changes\n\n- first release\n- Updated dependencies\n  - @pingcap-incubator/tidb-dashboard-lib-primitive-ui@0.0.2\n  - @pingcap-incubator/tidb-dashboard-lib-apps@0.0.2\n"
  },
  {
    "path": "ui-v2/packages/portals/test/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/vite.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Vite + React + TS</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "ui-v2/packages/portals/test/package.json",
    "content": "{\n  \"name\": \"test-tidb-dashboard-ui-lib\",\n  \"private\": true,\n  \"version\": \"0.13.2\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"vite build && tsc -b\",\n    \"lint\": \"eslint .\",\n    \"preview\": \"vite preview\"\n  },\n  \"dependencies\": {\n    \"@pingcap-incubator/tidb-dashboard-lib-api-client\": \"workspace:^\",\n    \"@pingcap-incubator/tidb-dashboard-lib-apps\": \"workspace:^\",\n    \"@tanstack/react-query\": \"^5.59.16\",\n    \"@tanstack/react-router\": \"^1.85.0\",\n    \"@tidbcloud/uikit\": \"catalog:\",\n    \"react\": \"^18.3.1\",\n    \"react-dom\": \"^18.3.1\"\n  },\n  \"devDependencies\": {\n    \"@eslint/js\": \"^9.13.0\",\n    \"@tanstack/router-devtools\": \"^1.85.0\",\n    \"@tanstack/router-plugin\": \"^1.84.4\",\n    \"@types/node\": \"^22.10.1\",\n    \"@types/react\": \"^18.3.11\",\n    \"@types/react-dom\": \"^18.3.1\",\n    \"@vitejs/plugin-react\": \"^4.3.3\",\n    \"vite\": \"^5.4.9\"\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/public/swagger.index-advisor.json",
    "content": "{\n  \"swagger\": \"2.0\",\n  \"info\": {\n    \"title\": \"Cluster APIs for TiDB Cloud\",\n    \"version\": \"alpha\"\n  },\n  \"tags\": [\n    {\n      \"name\": \"ClusterService\"\n    }\n  ],\n  \"schemes\": [\"https\"],\n  \"consumes\": [\"application/json\"],\n  \"produces\": [\"application/json\"],\n  \"paths\": {\n    \"/api/v1/serverless/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/advise_indexes\": {\n      \"post\": {\n        \"summary\": \"Advise indexes on the specified cluster.\",\n        \"operationId\": \"AdviseIndexes\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/serverlessClusterAdviseIndexesResp\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/googlerpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"org_id\",\n            \"description\": \"The ID of the org.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"project_id\",\n            \"description\": \"The ID of the project.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"cluster_id\",\n            \"description\": \"The ID of the cluster.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"payload\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/serverlessClusterAdviseIndexesReqPayload\"\n            }\n          },\n          {\n            \"name\": \"with_cloud_admin\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v1/serverless/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/apply_advice\": {\n      \"post\": {\n        \"summary\": \"Apply an index advisor record.\",\n        \"operationId\": \"ApplyAdvice\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/serverlessApplyIndexAdviceResp\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/googlerpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"org_id\",\n            \"description\": \"The ID of the org.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"project_id\",\n            \"description\": \"The ID of the project.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"cluster_id\",\n            \"description\": \"The ID of the cluster.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"payload\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/serverlessApplyIndexAdviceReqPayload\"\n            }\n          },\n          {\n            \"name\": \"with_cloud_admin\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v1/serverless/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/close_advice\": {\n      \"post\": {\n        \"summary\": \"Close an index advisor record.\",\n        \"operationId\": \"CloseAdvice\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/serverlessCloseIndexAdviceResp\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/googlerpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"org_id\",\n            \"description\": \"The ID of the org.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"project_id\",\n            \"description\": \"The ID of the project.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"cluster_id\",\n            \"description\": \"The ID of the cluster.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"payload\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/serverlessCloseIndexAdviceReqPayload\"\n            }\n          },\n          {\n            \"name\": \"with_cloud_admin\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v1/serverless/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/databases\": {\n      \"get\": {\n        \"summary\": \"List databases of a cluster.\",\n        \"operationId\": \"ListDatabases\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/serverlessClusterListDatabasesResp\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/googlerpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"org_id\",\n            \"description\": \"The ID of the org.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"project_id\",\n            \"description\": \"The ID of the project.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"cluster_id\",\n            \"description\": \"The ID of the cluster.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      },\n      \"post\": {\n        \"summary\": \"Create a database of a cluster.\",\n        \"operationId\": \"CreateDatabase\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/serverlessClusterCreateDatabaseResp\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/googlerpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"org_id\",\n            \"description\": \"The ID of the org.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"project_id\",\n            \"description\": \"The ID of the project.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"cluster_id\",\n            \"description\": \"The ID of the cluster.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"payload\",\n            \"in\": \"body\",\n            \"required\": true,\n            \"schema\": {\n              \"$ref\": \"#/definitions/serverlessClusterCreateDatabaseReqPayload\"\n            }\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v1/serverless/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/index_advices\": {\n      \"get\": {\n        \"summary\": \"List index advisor results of a cluster.\",\n        \"operationId\": \"ListIndexAdvices\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/serverlessListIndexAdvicesResp\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/googlerpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"org_id\",\n            \"description\": \"The ID of the org.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"project_id\",\n            \"description\": \"The ID of the project.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"cluster_id\",\n            \"description\": \"The ID of the cluster.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"page_token\",\n            \"description\": \"The number of pages.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int64\",\n            \"default\": 1\n          },\n          {\n            \"name\": \"page_size\",\n            \"description\": \"The size of a page.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"integer\",\n            \"format\": \"int64\",\n            \"default\": 10\n          },\n          {\n            \"name\": \"state_filter\",\n            \"description\": \"The state to filter result.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"name_filter\",\n            \"description\": \"The name of database or table to filter result.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"order_by\",\n            \"description\": \"The column used to order result.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"string\"\n          },\n          {\n            \"name\": \"desc\",\n            \"description\": \"If ordered result should be in descending order.\",\n            \"in\": \"query\",\n            \"required\": false,\n            \"type\": \"boolean\",\n            \"default\": \"false\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v1/serverless/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/index_advices/{advice_id}\": {\n      \"get\": {\n        \"summary\": \"Get detail of a index advice.\",\n        \"operationId\": \"GetIndexAdvice\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/serverlessGetIndexAdviceResp\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/googlerpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"org_id\",\n            \"description\": \"The ID of the org.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"project_id\",\n            \"description\": \"The ID of the project.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"cluster_id\",\n            \"description\": \"The ID of the cluster.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"advice_id\",\n            \"description\": \"The ID of the advice.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    },\n    \"/api/v1/serverless/orgs/{org_id}/projects/{project_id}/clusters/{cluster_id}/index_advices_summary\": {\n      \"get\": {\n        \"summary\": \"Get summary of open index advices.\",\n        \"operationId\": \"GetIndexAdviceSummary\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"A successful response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/serverlessGetIndexAdviceSummaryResp\"\n            }\n          },\n          \"default\": {\n            \"description\": \"An unexpected error response.\",\n            \"schema\": {\n              \"$ref\": \"#/definitions/googlerpcStatus\"\n            }\n          }\n        },\n        \"parameters\": [\n          {\n            \"name\": \"org_id\",\n            \"description\": \"The ID of the org.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"project_id\",\n            \"description\": \"The ID of the project.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          },\n          {\n            \"name\": \"cluster_id\",\n            \"description\": \"The ID of the cluster.\",\n            \"in\": \"path\",\n            \"required\": true,\n            \"type\": \"string\",\n            \"format\": \"uint64\"\n          }\n        ],\n        \"tags\": [\"ClusterService\"]\n      }\n    }\n  },\n  \"definitions\": {\n    \"baseBaseResp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"tags\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          },\n          \"title\": \"such as: request_id, trace_id\"\n        },\n        \"err_code\": {\n          \"type\": \"string\",\n          \"format\": \"int64\"\n        },\n        \"err_msg\": {\n          \"type\": \"string\"\n        }\n      }\n    },\n    \"googlerpcStatus\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"code\": {\n          \"type\": \"integer\",\n          \"format\": \"int32\"\n        },\n        \"message\": {\n          \"type\": \"string\"\n        },\n        \"details\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/protobufAny\"\n          }\n        }\n      }\n    },\n    \"protobufAny\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"@type\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": {}\n    },\n    \"serverlessApplyIndexAdviceReqPayload\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"advice_id\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"example\": 1,\n          \"description\": \"The ID of the advice record.\"\n        }\n      },\n      \"required\": [\"advice_id\"]\n    },\n    \"serverlessApplyIndexAdviceResp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"base_resp\": {\n          \"$ref\": \"#/definitions/baseBaseResp\"\n        }\n      },\n      \"required\": [\"base_resp\"]\n    },\n    \"serverlessCloseIndexAdviceReqPayload\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"advice_id\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\",\n          \"example\": 1,\n          \"description\": \"The ID of the advice record.\"\n        }\n      },\n      \"required\": [\"advice_id\"]\n    },\n    \"serverlessCloseIndexAdviceResp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"base_resp\": {\n          \"$ref\": \"#/definitions/baseBaseResp\"\n        }\n      },\n      \"required\": [\"base_resp\"]\n    },\n    \"serverlessClusterAdviseIndexesReqPayload\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database_name\": {\n          \"type\": \"string\",\n          \"example\": \"helloworld\",\n          \"description\": \"The name of the database.\"\n        },\n        \"queries\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"Queries\"\n        }\n      },\n      \"required\": [\"database_name\", \"queries\"]\n    },\n    \"serverlessClusterAdviseIndexesResp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"text\": {\n          \"type\": \"string\",\n          \"description\": \"The items of databases in the cluster.\"\n        },\n        \"base_resp\": {\n          \"$ref\": \"#/definitions/baseBaseResp\"\n        }\n      },\n      \"required\": [\"text\", \"base_resp\"]\n    },\n    \"serverlessClusterCreateDatabaseReqPayload\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"database_name\": {\n          \"type\": \"string\",\n          \"example\": \"helloworld\",\n          \"description\": \"The name of the database.\"\n        }\n      },\n      \"required\": [\"database_name\"]\n    },\n    \"serverlessClusterCreateDatabaseResp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"base_resp\": {\n          \"$ref\": \"#/definitions/baseBaseResp\"\n        }\n      },\n      \"required\": [\"base_resp\"]\n    },\n    \"serverlessClusterListDatabasesResp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"databases\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"description\": \"The items of databases in the cluster.\"\n        },\n        \"base_resp\": {\n          \"$ref\": \"#/definitions/baseBaseResp\"\n        }\n      },\n      \"required\": [\"databases\", \"base_resp\"]\n    },\n    \"serverlessGetIndexAdviceResp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"advice\": {\n          \"$ref\": \"#/definitions/serverlessIndexAdvice\"\n        },\n        \"base_resp\": {\n          \"$ref\": \"#/definitions/baseBaseResp\"\n        }\n      }\n    },\n    \"serverlessGetIndexAdviceSummaryResp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"open_count\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\"\n        },\n        \"improvement\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"cost_saving_monthly\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"base_resp\": {\n          \"$ref\": \"#/definitions/baseBaseResp\"\n        }\n      }\n    },\n    \"serverlessIndexAdvice\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"id\": {\n          \"type\": \"string\",\n          \"format\": \"uint64\"\n        },\n        \"name\": {\n          \"type\": \"string\"\n        },\n        \"database\": {\n          \"type\": \"string\"\n        },\n        \"table\": {\n          \"type\": \"string\"\n        },\n        \"last_recommend_time\": {\n          \"type\": \"string\",\n          \"format\": \"date-time\"\n        },\n        \"state\": {\n          \"type\": \"string\"\n        },\n        \"index_statement\": {\n          \"type\": \"string\"\n        },\n        \"improvement\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"index_size\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"reason\": {\n          \"type\": \"string\"\n        },\n        \"top_impacted_queries\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/serverlessIndexAdviceImpact\"\n          }\n        },\n        \"state_reason\": {\n          \"type\": \"string\"\n        },\n        \"cost_saving_monthly\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        },\n        \"cost_saving_per_query\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      }\n    },\n    \"serverlessIndexAdviceImpact\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"query\": {\n          \"type\": \"string\"\n        },\n        \"improvement\": {\n          \"type\": \"number\",\n          \"format\": \"double\"\n        }\n      }\n    },\n    \"serverlessListIndexAdvicesResp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"advices\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"object\",\n            \"$ref\": \"#/definitions/serverlessIndexAdvice\"\n          },\n          \"description\": \"The items of index advisor results.\"\n        },\n        \"total\": {\n          \"type\": \"integer\",\n          \"format\": \"int64\",\n          \"description\": \"total items count considering filter\"\n        },\n        \"base_resp\": {\n          \"$ref\": \"#/definitions/baseBaseResp\"\n        }\n      },\n      \"required\": [\"advices\", \"base_resp\"]\n    }\n  },\n  \"securityDefinitions\": {\n    \"bearer\": {\n      \"type\": \"apiKey\",\n      \"description\": \"Authentication token, prefixed by Bearer: 'Bearer token'\",\n      \"name\": \"Authorization\",\n      \"in\": \"header\"\n    }\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/App.css",
    "content": ""
  },
  {
    "path": "ui-v2/packages/portals/test/src/App.tsx",
    "content": "import { UiKitThemeProvider } from \"@pingcap-incubator/tidb-dashboard-lib-apps/primitive-ui\"\n\nimport { ReactQueryProvider } from \"./providers/react-query-provider\"\nimport { RouterProvider } from \"./router/provider\"\n\nimport \"@tidbcloud/uikit/style.css\"\nimport \"@pingcap-incubator/tidb-dashboard-lib-apps/charts-css\"\nimport \"./App.css\"\n\nfunction App() {\n  return (\n    <UiKitThemeProvider>\n      <ReactQueryProvider>\n        <RouterProvider />\n      </ReactQueryProvider>\n    </UiKitThemeProvider>\n  )\n}\n\nexport default App\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/apps/metric/mock-api-app-provider.tsx",
    "content": "import {\n  V2Metrics,\n  metricsServiceGetClusterMetricData,\n  metricsServiceGetClusterMetricInstance,\n  metricsServiceGetHostMetricData,\n  metricsServiceGetMetrics,\n  metricsServiceGetTopMetricConfig,\n  metricsServiceGetTopMetricData,\n} from \"@pingcap-incubator/tidb-dashboard-lib-api-client\"\nimport {\n  AppCtxValue,\n  SinglePanelConfig,\n} from \"@pingcap-incubator/tidb-dashboard-lib-apps/metric\"\nimport {\n  PromResultItem,\n  TransformNullValue,\n  delay,\n} from \"@pingcap-incubator/tidb-dashboard-lib-apps/utils\"\nimport { useMemo } from \"react\"\n\nimport { normalQueryConfig } from \"./sample-data/normal-configs\"\nimport qpsType from \"./sample-data/qps-type.json\"\n\nconst testHostId = import.meta.env.VITE_TEST_HOST_ID\nconst testClusterId = import.meta.env.VITE_TEST_CLUSTER_ID\n\nfunction transformConfigs(metrics: V2Metrics[\"metrics\"]): SinglePanelConfig[] {\n  const configs: SinglePanelConfig[] = []\n\n  const groups = [...new Set((metrics || []).map((m) => m.group || \"\"))]\n  groups.forEach((group) => {\n    const categories = [\n      ...new Set(\n        (metrics?.filter((m) => m.group === group) || []).map(\n          (m) => m.type || \"\",\n        ),\n      ),\n    ]\n    categories.forEach((category) => {\n      const charts = metrics?.filter(\n        (m) => m.type === category && m.group === group && m.name,\n      )\n      configs.push({\n        group,\n        category,\n        displayName: category,\n        charts:\n          charts?.map((metric) => ({\n            metricName: metric.name!,\n            title: metric.displayName!,\n            label: metric.description,\n            queries: [],\n            nullValue: TransformNullValue.AS_ZERO,\n            unit: metric.metric?.unit ?? \"short\",\n          })) ?? [],\n      })\n    })\n  })\n\n  return configs\n}\n\nexport function useCtxValue(): AppCtxValue {\n  let lastKind = \"azores-overview\"\n\n  return useMemo(\n    () => ({\n      ctxId: \"metric\",\n      api: {\n        getMetricQueriesConfig: async (kind) => {\n          lastKind = kind\n          if (kind === \"normal\") {\n            return normalQueryConfig\n          }\n          let metrics\n          if (kind === \"azores-overview\") {\n            metrics = await metricsServiceGetMetrics({\n              class: \"overview\",\n              group: \"overview\",\n            }).then((res) => res.metrics)\n          } else if (kind === \"azores-host\") {\n            metrics = await metricsServiceGetMetrics({\n              class: \"host\",\n            }).then((res) => res.metrics)\n          } else if (kind === \"azores-cluster-overview\") {\n            metrics = await metricsServiceGetMetrics({\n              class: \"cluster\",\n              group: \"overview\",\n            }).then((res) => res.metrics)\n          } else {\n            // kind === 'azores-cluster'\n            metrics = await metricsServiceGetMetrics({\n              class: \"cluster\",\n            }).then((res) => res.metrics)\n          }\n          return transformConfigs(metrics)\n        },\n\n        getMetricConfig() {\n          return metricsServiceGetTopMetricConfig().then((res) => ({\n            delaySec: (res.cacheFlushIntervalInMinutes || 0) * 60,\n          }))\n        },\n\n        getMetricLabelValues(params) {\n          return metricsServiceGetClusterMetricInstance(\n            testClusterId,\n            params.metricName,\n          ).then((res) => res.instanceList ?? [])\n        },\n\n        getMetricDataByPromQL() {\n          return delay(1000).then(\n            () => qpsType.data.result as unknown as PromResultItem[],\n          )\n        },\n\n        getMetricDataByMetricName: async ({\n          metricName,\n          beginTime,\n          endTime,\n          step,\n          label,\n        }) => {\n          let queryData\n          if (lastKind === \"azores-overview\") {\n            queryData = await metricsServiceGetTopMetricData(metricName, {\n              startTime: beginTime.toString(),\n              endTime: endTime.toString(),\n              step: step.toString(),\n            }).then((res) => res.data)\n          } else if (lastKind === \"azores-host\") {\n            queryData = await metricsServiceGetHostMetricData(\n              testHostId,\n              metricName,\n              {\n                startTime: beginTime.toString(),\n                endTime: endTime.toString(),\n                step: step.toString(),\n              },\n            ).then((res) => res.data)\n          } else {\n            // lastKind === 'azores-cluster-overview' || lastKind === 'azores-cluster'\n            queryData = await metricsServiceGetClusterMetricData(\n              testClusterId,\n              metricName,\n              {\n                startTime: beginTime.toString(),\n                endTime: endTime.toString(),\n                step: step.toString(),\n                label,\n              },\n            ).then((res) => res.data)\n          }\n\n          const ret = queryData?.map((d) => ({\n            expr: d.expr ?? \"\",\n            legend: d.legend ?? \"\",\n            result: (d.result as PromResultItem[]) ?? [],\n            promAddr: d.prometheusAddress ?? \"\",\n          }))\n\n          return ret ?? []\n        },\n      },\n      cfg: {\n        title: \"\",\n        scrapeInterval: 30,\n      },\n      actions: {\n        openDiagnosis(id) {\n          const [from, to] = id.split(\",\")\n          window.open(`/statement?from=${from}&to=${to}`, \"_blank\")\n        },\n        openHostMonitoring(id) {\n          window.open(`/metrics/azores-host?host_id=${id}`, \"_blank\")\n        },\n      },\n    }),\n    [],\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/apps/metric/sample-data/normal-configs.ts",
    "content": "import { SinglePanelConfig } from \"@pingcap-incubator/tidb-dashboard-lib-apps/metric\"\nimport { TransformNullValue } from \"@pingcap-incubator/tidb-dashboard-lib-apps/utils\"\n\nexport const normalQueryConfig: SinglePanelConfig[] = [\n  {\n    group: \"\",\n    category: \"cluster_status\",\n    displayName: \"Cluster Status\",\n    charts: [\n      {\n        title: \"Query Per Second\",\n        label:\n          \"The number of SQL statements executed per second, which are collected by SQL types, such as `SELECT`, `INSERT`, and `UPDATE`.\",\n        metricName: \"tidb_executor_statement_total\",\n        queries: [\n          {\n            promql: `sum(rate(tidb_executor_statement_total{db!=\"\"}[$__rate_interval])) or vector(0)`,\n            legendName: \"All\",\n            type: \"line\",\n          },\n          {\n            promql: `sum(rate(tidb_executor_statement_total{db!=\"\"}[$__rate_interval])) by (type)`,\n            legendName: \"{type}\",\n            type: \"line\",\n          },\n        ],\n        nullValue: TransformNullValue.AS_ZERO,\n        unit: \"short\",\n      },\n      {\n        title: \"Used Storage Size\",\n        label: \"The size of the row store and the size of the column store.\",\n        metricName: \"tikv_store_size_bytes\",\n        queries: [\n          {\n            promql:\n              'quantile_over_time(0.5, sum(avg by(keyspace_id, region_id) (tikv_store_size_bytes{type=\"used\"}))[$__rate_interval]) or vector(0)',\n            legendName: \"Row-based storage\",\n            type: \"line\",\n          },\n          {\n            promql:\n              'quantile_over_time(0.5, sum(avg by(keyspace_id, region_id) (tikv_store_size_bytes{type=\"tiflash_used\"}))[$__rate_interval]) or vector(0)',\n            legendName: \"Columnar storage\",\n            type: \"line\",\n          },\n        ],\n        nullValue: TransformNullValue.AS_ZERO,\n        unit: \"bytes\",\n      },\n    ],\n  },\n  {\n    group: \"\",\n    category: \"database_status\",\n    displayName: \"Database Status\",\n    charts: [\n      {\n        title: \"QPS Per DB\",\n        label:\n          \"The number of SQL statements executed per second on every Database, which are collected by SQL types, such as `SELECT`, `INSERT`, and `UPDATE`.\",\n        metricName: \"tidb_executor_statement_total\",\n        queries: [\n          {\n            promql: `sum(rate(tidb_executor_statement_total{db!=\"\"}[$__rate_interval])) default 0`,\n            legendName: \"All\",\n            type: \"line\",\n          },\n          {\n            promql: `sum(rate(tidb_executor_statement_total{db!=\"\"}[$__rate_interval])) by (db) >0 and on(db) (sum(rate(tidb_server_handle_query_duration_seconds_count{db!=\"\"}[$__rate_interval])) by (db) >0)`,\n            legendName: \"{db}\",\n            type: \"line\",\n          },\n        ],\n        nullValue: TransformNullValue.AS_ZERO,\n        unit: \"short\",\n      },\n      {\n        title: \"Average Query Duration Per DB\",\n        label:\n          \"The duration from receiving a request from the client to a database until the database executes the request and returns the result to the client.\",\n        metricName: \"tidb_server_handle_query_duration_seconds\",\n        queries: [\n          {\n            promql:\n              'sum(rate(tidb_server_handle_query_duration_seconds_sum{db!=\"\",sql_type!=\"internal\"}[$__rate_interval])) / sum(rate(tidb_server_handle_query_duration_seconds_count{db!=\"\",sql_type!=\"internal\"}[$__rate_interval])) default 0',\n            legendName: \"All\",\n            type: \"line\",\n          },\n          {\n            promql:\n              '(sum(rate(tidb_server_handle_query_duration_seconds_sum{db!=\"\",sql_type!=\"internal\"}[$__rate_interval])) by (db) / sum(rate(tidb_server_handle_query_duration_seconds_count{db!=\"\",sql_type!=\"internal\"}[$__rate_interval])) by (db) > 0) and on (db) (sum(rate(tidb_executor_statement_total{db!=\"\",sql_type!=\"internal\"}[$__rate_interval])) by (db) >0)',\n            legendName: \"{db}\",\n            type: \"line\",\n          },\n        ],\n        nullValue: TransformNullValue.AS_ZERO,\n        unit: \"s\",\n      },\n    ],\n  },\n]\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/apps/metric/sample-data/qps-type.json",
    "content": "{\n  \"status\": \"success\",\n  \"isPartial\": false,\n  \"data\": {\n    \"resultType\": \"matrix\",\n    \"result\": [\n      {\n        \"metric\": {\n          \"type\": \"Delete\"\n        },\n        \"values\": [\n          [1731595980, \"0\"],\n          [1731596010, \"0\"],\n          [1731596040, \"0\"],\n          [1731596070, \"0\"],\n          [1731596100, \"0\"],\n          [1731596130, \"0\"],\n          [1731596160, \"0\"],\n          [1731596190, \"0\"],\n          [1731596220, \"0.008333333333333333\"],\n          [1731596250, \"0.008333333333333333\"],\n          [1731596280, \"0.008333333333333333\"],\n          [1731596310, \"0.008333333333333333\"],\n          [1731596340, \"0\"],\n          [1731596370, \"0\"],\n          [1731596400, \"0\"],\n          [1731596430, \"0\"],\n          [1731596460, \"0.008333333333333333\"],\n          [1731596490, \"0.008333333333333333\"],\n          [1731596520, \"0.008333333333333333\"],\n          [1731596550, \"0.008333333333333333\"],\n          [1731596580, \"0\"],\n          [1731596610, \"0\"],\n          [1731596640, \"0\"],\n          [1731596670, \"0\"],\n          [1731596700, \"0\"],\n          [1731596730, \"0\"],\n          [1731596760, \"0\"],\n          [1731596790, \"0\"],\n          [1731596820, \"0\"],\n          [1731596850, \"0\"],\n          [1731596880, \"0.008333333333333333\"],\n          [1731596910, \"0.008333333333333333\"],\n          [1731596940, \"0.008333333333333333\"],\n          [1731596970, \"0.008333333333333333\"],\n          [1731597000, \"0\"],\n          [1731597030, \"0\"],\n          [1731597060, \"0\"],\n          [1731597090, \"0\"],\n          [1731597120, \"0\"],\n          [1731597150, \"0\"],\n          [1731597180, \"0\"],\n          [1731597210, \"0\"],\n          [1731597240, \"0\"],\n          [1731597270, \"0\"],\n          [1731597300, \"0.008333333333333333\"],\n          [1731597330, \"0.008333333333333333\"],\n          [1731597360, \"0.008333333333333333\"],\n          [1731597390, \"0.008333333333333333\"],\n          [1731597420, \"0\"],\n          [1731597450, \"0\"],\n          [1731597480, \"0\"],\n          [1731597510, \"0\"],\n          [1731597540, \"0\"],\n          [1731597570, \"0\"],\n          [1731597600, \"0\"],\n          [1731597630, \"0\"],\n          [1731597660, \"0\"],\n          [1731597690, \"0\"],\n          [1731597720, \"0.2916666666666667\"],\n          [1731597750, \"0.2916666666666667\"],\n          [1731597780, \"0.2916666666666667\"],\n          [1731597810, \"0.2916666666666667\"],\n          [1731597840, \"0\"],\n          [1731597870, \"0\"],\n          [1731597900, \"0\"],\n          [1731597930, \"0\"],\n          [1731597960, \"0\"],\n          [1731597990, \"0\"],\n          [1731598020, \"0\"],\n          [1731598050, \"0\"],\n          [1731598080, \"0\"],\n          [1731598110, \"0\"],\n          [1731598140, \"0.008333333333333333\"],\n          [1731598170, \"0.008333333333333333\"],\n          [1731598200, \"0.008333333333333333\"],\n          [1731598230, \"0.008333333333333333\"],\n          [1731598260, \"0\"],\n          [1731598290, \"0\"],\n          [1731598320, \"0\"],\n          [1731598350, \"0\"],\n          [1731598380, \"0\"],\n          [1731598410, \"0\"],\n          [1731598440, \"0\"],\n          [1731598470, \"0\"],\n          [1731598500, \"0\"],\n          [1731598530, \"0\"],\n          [1731598560, \"0.008333333333333333\"],\n          [1731598590, \"0.008333333333333333\"],\n          [1731598620, \"0.008333333333333333\"],\n          [1731598650, \"0.008333333333333333\"],\n          [1731598680, \"0\"],\n          [1731598710, \"0\"],\n          [1731598740, \"0\"],\n          [1731598770, \"0\"],\n          [1731598800, \"0\"],\n          [1731598830, \"0\"],\n          [1731598860, \"0\"],\n          [1731598890, \"0\"],\n          [1731598920, \"0\"],\n          [1731598950, \"0\"],\n          [1731598980, \"0.008333333333333333\"],\n          [1731599010, \"0.008333333333333333\"],\n          [1731599040, \"0.008333333333333333\"],\n          [1731599070, \"0.008333333333333333\"],\n          [1731599100, \"0\"],\n          [1731599130, \"0\"],\n          [1731599160, \"0\"],\n          [1731599190, \"0\"],\n          [1731599220, \"0\"],\n          [1731599250, \"0\"],\n          [1731599280, \"0\"],\n          [1731599310, \"0\"],\n          [1731599340, \"0\"],\n          [1731599370, \"0\"],\n          [1731599400, \"0.008333333333333333\"],\n          [1731599430, \"0.008333333333333333\"],\n          [1731599460, \"0.008333333333333333\"],\n          [1731599490, \"0.008333333333333333\"],\n          [1731599520, \"0\"],\n          [1731599550, \"0\"],\n          [1731599580, \"0\"],\n          [1731599610, \"0\"]\n        ]\n      },\n      {\n        \"metric\": {\n          \"type\": \"Insert\"\n        },\n        \"values\": [\n          [1731595980, \"17.508333333333333\"],\n          [1731596010, \"17.508333333333333\"],\n          [1731596040, \"16.641666666666666\"],\n          [1731596070, \"16.641666666666666\"],\n          [1731596100, \"15.674999999999999\"],\n          [1731596130, \"15.674999999999999\"],\n          [1731596160, \"16.483333333333334\"],\n          [1731596190, \"16.483333333333334\"],\n          [1731596220, \"17.741666666666667\"],\n          [1731596250, \"17.741666666666667\"],\n          [1731596280, \"16.616666666666667\"],\n          [1731596310, \"16.616666666666667\"],\n          [1731596340, \"15.9\"],\n          [1731596370, \"15.9\"],\n          [1731596400, \"16.116666666666667\"],\n          [1731596430, \"16.116666666666667\"],\n          [1731596460, \"16.075\"],\n          [1731596490, \"16.075\"],\n          [1731596520, \"16.566666666666666\"],\n          [1731596550, \"16.566666666666666\"],\n          [1731596580, \"16.791666666666664\"],\n          [1731596610, \"16.791666666666664\"],\n          [1731596640, \"16.2\"],\n          [1731596670, \"16.2\"],\n          [1731596700, \"17.183333333333334\"],\n          [1731596730, \"17.183333333333334\"],\n          [1731596760, \"17.158333333333335\"],\n          [1731596790, \"17.158333333333335\"],\n          [1731596820, \"16.333333333333332\"],\n          [1731596850, \"16.333333333333332\"],\n          [1731596880, \"16.466666666666665\"],\n          [1731596910, \"16.466666666666665\"],\n          [1731596940, \"16.966666666666665\"],\n          [1731596970, \"16.966666666666665\"],\n          [1731597000, \"17.416666666666664\"],\n          [1731597030, \"17.416666666666664\"],\n          [1731597060, \"16.741666666666667\"],\n          [1731597090, \"16.741666666666667\"],\n          [1731597120, \"17.016666666666666\"],\n          [1731597150, \"17.016666666666666\"],\n          [1731597180, \"17.95\"],\n          [1731597210, \"17.95\"],\n          [1731597240, \"18.141666666666666\"],\n          [1731597270, \"18.141666666666666\"],\n          [1731597300, \"18.224999999999998\"],\n          [1731597330, \"18.224999999999998\"],\n          [1731597360, \"18.599999999999998\"],\n          [1731597390, \"18.599999999999998\"],\n          [1731597420, \"18.633333333333333\"],\n          [1731597450, \"18.633333333333333\"],\n          [1731597480, \"18.25\"],\n          [1731597510, \"18.25\"],\n          [1731597540, \"18.108333333333334\"],\n          [1731597570, \"18.108333333333334\"],\n          [1731597600, \"17.883333333333333\"],\n          [1731597630, \"17.883333333333333\"],\n          [1731597660, \"17.391666666666666\"],\n          [1731597690, \"17.391666666666666\"],\n          [1731597720, \"17.766666666666666\"],\n          [1731597750, \"17.766666666666666\"],\n          [1731597780, \"18.266666666666666\"],\n          [1731597810, \"18.266666666666666\"],\n          [1731597840, \"18.091666666666665\"],\n          [1731597870, \"18.091666666666665\"],\n          [1731597900, \"18.091666666666665\"],\n          [1731597930, \"18.091666666666665\"],\n          [1731597960, \"17.666666666666668\"],\n          [1731597990, \"17.666666666666668\"],\n          [1731598020, \"18.133333333333333\"],\n          [1731598050, \"18.133333333333333\"],\n          [1731598080, \"17.833333333333332\"],\n          [1731598110, \"17.833333333333332\"],\n          [1731598140, \"16.966666666666665\"],\n          [1731598170, \"16.966666666666665\"],\n          [1731598200, \"18.241666666666667\"],\n          [1731598230, \"18.241666666666667\"],\n          [1731598260, \"18.09166666666667\"],\n          [1731598290, \"18.09166666666667\"],\n          [1731598320, \"17.383333333333333\"],\n          [1731598350, \"17.383333333333333\"],\n          [1731598380, \"16.84166666666667\"],\n          [1731598410, \"16.84166666666667\"],\n          [1731598440, \"16.583333333333336\"],\n          [1731598470, \"16.583333333333336\"],\n          [1731598500, \"17.208333333333332\"],\n          [1731598530, \"17.208333333333332\"],\n          [1731598560, \"17.183333333333334\"],\n          [1731598590, \"17.183333333333334\"],\n          [1731598620, \"15.991666666666667\"],\n          [1731598650, \"15.991666666666667\"],\n          [1731598680, \"16.28333333333333\"],\n          [1731598710, \"16.28333333333333\"],\n          [1731598740, \"18.216666666666665\"],\n          [1731598770, \"18.216666666666665\"],\n          [1731598800, \"20.116666666666667\"],\n          [1731598830, \"20.116666666666667\"],\n          [1731598860, \"20.208333333333332\"],\n          [1731598890, \"20.208333333333332\"],\n          [1731598920, \"19.258333333333333\"],\n          [1731598950, \"19.258333333333333\"],\n          [1731598980, \"19.4\"],\n          [1731599010, \"19.4\"],\n          [1731599040, \"19.608333333333334\"],\n          [1731599070, \"19.608333333333334\"],\n          [1731599100, \"18.933333333333334\"],\n          [1731599130, \"18.933333333333334\"],\n          [1731599160, \"18.491666666666667\"],\n          [1731599190, \"18.491666666666667\"],\n          [1731599220, \"18.425\"],\n          [1731599250, \"18.425\"],\n          [1731599280, \"17.883333333333333\"],\n          [1731599310, \"17.883333333333333\"],\n          [1731599340, \"17.616666666666667\"],\n          [1731599370, \"17.616666666666667\"],\n          [1731599400, \"17.758333333333333\"],\n          [1731599430, \"17.758333333333333\"],\n          [1731599460, \"18.941666666666666\"],\n          [1731599490, \"18.941666666666666\"],\n          [1731599520, \"19.041666666666668\"],\n          [1731599550, \"19.041666666666668\"],\n          [1731599580, \"19.041666666666668\"],\n          [1731599610, \"19.041666666666668\"]\n        ]\n      },\n      {\n        \"metric\": {\n          \"type\": \"Select\"\n        },\n        \"values\": [\n          [1731595980, \"12.216666666666667\"],\n          [1731596010, \"12.216666666666667\"],\n          [1731596040, \"11.416666666666666\"],\n          [1731596070, \"11.416666666666666\"],\n          [1731596100, \"12.433333333333334\"],\n          [1731596130, \"12.433333333333334\"],\n          [1731596160, \"12.791666666666668\"],\n          [1731596190, \"12.791666666666668\"],\n          [1731596220, \"11.833333333333334\"],\n          [1731596250, \"11.833333333333334\"],\n          [1731596280, \"11.741666666666669\"],\n          [1731596310, \"11.741666666666669\"],\n          [1731596340, \"12.066666666666666\"],\n          [1731596370, \"12.066666666666666\"],\n          [1731596400, \"11.725\"],\n          [1731596430, \"11.725\"],\n          [1731596460, \"11.591666666666667\"],\n          [1731596490, \"11.591666666666667\"],\n          [1731596520, \"12.174999999999999\"],\n          [1731596550, \"12.174999999999999\"],\n          [1731596580, \"13.008333333333333\"],\n          [1731596610, \"13.008333333333333\"],\n          [1731596640, \"13.433333333333332\"],\n          [1731596670, \"13.433333333333332\"],\n          [1731596700, \"13.166666666666666\"],\n          [1731596730, \"13.166666666666666\"],\n          [1731596760, \"11.666666666666666\"],\n          [1731596790, \"11.666666666666666\"],\n          [1731596820, \"11.333333333333332\"],\n          [1731596850, \"11.333333333333332\"],\n          [1731596880, \"13.325\"],\n          [1731596910, \"13.325\"],\n          [1731596940, \"14.033333333333333\"],\n          [1731596970, \"14.033333333333333\"],\n          [1731597000, \"14.241666666666667\"],\n          [1731597030, \"14.241666666666667\"],\n          [1731597060, \"15.124999999999998\"],\n          [1731597090, \"15.124999999999998\"],\n          [1731597120, \"15.758333333333335\"],\n          [1731597150, \"15.758333333333335\"],\n          [1731597180, \"20.291666666666664\"],\n          [1731597210, \"20.291666666666664\"],\n          [1731597240, \"20.299999999999997\"],\n          [1731597270, \"20.299999999999997\"],\n          [1731597300, \"15.983333333333334\"],\n          [1731597330, \"15.983333333333334\"],\n          [1731597360, \"15.75\"],\n          [1731597390, \"15.75\"],\n          [1731597420, \"14.416666666666666\"],\n          [1731597450, \"14.416666666666666\"],\n          [1731597480, \"13.616666666666667\"],\n          [1731597510, \"13.616666666666667\"],\n          [1731597540, \"14.2\"],\n          [1731597570, \"14.2\"],\n          [1731597600, \"15.424999999999999\"],\n          [1731597630, \"15.424999999999999\"],\n          [1731597660, \"14.6\"],\n          [1731597690, \"14.6\"],\n          [1731597720, \"13.825\"],\n          [1731597750, \"13.825\"],\n          [1731597780, \"14.75\"],\n          [1731597810, \"14.75\"],\n          [1731597840, \"14.208333333333332\"],\n          [1731597870, \"14.208333333333332\"],\n          [1731597900, \"14.133333333333333\"],\n          [1731597930, \"14.133333333333333\"],\n          [1731597960, \"15.191666666666668\"],\n          [1731597990, \"15.191666666666668\"],\n          [1731598020, \"14.933333333333334\"],\n          [1731598050, \"14.933333333333334\"],\n          [1731598080, \"14.541666666666668\"],\n          [1731598110, \"14.541666666666668\"],\n          [1731598140, \"13.858333333333334\"],\n          [1731598170, \"13.858333333333334\"],\n          [1731598200, \"13.466666666666667\"],\n          [1731598230, \"13.466666666666667\"],\n          [1731598260, \"13.241666666666667\"],\n          [1731598290, \"13.241666666666667\"],\n          [1731598320, \"13.316666666666666\"],\n          [1731598350, \"13.316666666666666\"],\n          [1731598380, \"12.716666666666667\"],\n          [1731598410, \"12.716666666666667\"],\n          [1731598440, \"12.899999999999999\"],\n          [1731598470, \"12.899999999999999\"],\n          [1731598500, \"14.216666666666669\"],\n          [1731598530, \"14.216666666666669\"],\n          [1731598560, \"13.966666666666665\"],\n          [1731598590, \"13.966666666666665\"],\n          [1731598620, \"13.316666666666666\"],\n          [1731598650, \"13.316666666666666\"],\n          [1731598680, \"13.558333333333334\"],\n          [1731598710, \"13.558333333333334\"],\n          [1731598740, \"13.808333333333334\"],\n          [1731598770, \"13.808333333333334\"],\n          [1731598800, \"13.833333333333332\"],\n          [1731598830, \"13.833333333333332\"],\n          [1731598860, \"12.95\"],\n          [1731598890, \"12.95\"],\n          [1731598920, \"12.375\"],\n          [1731598950, \"12.375\"],\n          [1731598980, \"11.991666666666667\"],\n          [1731599010, \"11.991666666666667\"],\n          [1731599040, \"12.891666666666667\"],\n          [1731599070, \"12.891666666666667\"],\n          [1731599100, \"13.508333333333335\"],\n          [1731599130, \"13.508333333333335\"],\n          [1731599160, \"13.008333333333333\"],\n          [1731599190, \"13.008333333333333\"],\n          [1731599220, \"13.691666666666666\"],\n          [1731599250, \"13.691666666666666\"],\n          [1731599280, \"13.8\"],\n          [1731599310, \"13.8\"],\n          [1731599340, \"13.208333333333334\"],\n          [1731599370, \"13.208333333333334\"],\n          [1731599400, \"12.983333333333334\"],\n          [1731599430, \"12.983333333333334\"],\n          [1731599460, \"13.233333333333333\"],\n          [1731599490, \"13.233333333333333\"],\n          [1731599520, \"13.216666666666667\"],\n          [1731599550, \"13.216666666666667\"],\n          [1731599580, \"13.216666666666667\"],\n          [1731599610, \"13.216666666666667\"]\n        ]\n      },\n      {\n        \"metric\": {\n          \"type\": \"Show\"\n        },\n        \"values\": [\n          [1731597060, \"0\"],\n          [1731597090, \"0\"],\n          [1731597120, \"0\"],\n          [1731597150, \"0\"],\n          [1731597180, \"0\"],\n          [1731597210, \"0\"],\n          [1731597240, \"0\"],\n          [1731597270, \"0\"],\n          [1731597300, \"0\"],\n          [1731597330, \"0\"],\n          [1731597360, \"0\"],\n          [1731597390, \"0\"],\n          [1731597420, \"0\"],\n          [1731597450, \"0\"],\n          [1731597480, \"0\"],\n          [1731597510, \"0\"],\n          [1731597540, \"0\"],\n          [1731597570, \"0\"],\n          [1731597600, \"0\"],\n          [1731597630, \"0\"],\n          [1731597660, \"0\"],\n          [1731597690, \"0\"],\n          [1731597720, \"0\"],\n          [1731597750, \"0\"],\n          [1731597780, \"0\"],\n          [1731597810, \"0\"],\n          [1731597840, \"0\"],\n          [1731597870, \"0\"],\n          [1731597900, \"0\"],\n          [1731597930, \"0\"],\n          [1731597960, \"0.05\"],\n          [1731597990, \"0.05\"],\n          [1731598020, \"0.025\"],\n          [1731598050, \"0.025\"],\n          [1731598080, \"0\"],\n          [1731598110, \"0\"],\n          [1731598140, \"0\"],\n          [1731598170, \"0\"],\n          [1731598200, \"0\"],\n          [1731598230, \"0\"],\n          [1731598260, \"0\"],\n          [1731598290, \"0\"],\n          [1731598320, \"0\"],\n          [1731598350, \"0\"],\n          [1731598380, \"0\"],\n          [1731598410, \"0\"],\n          [1731598440, \"0\"],\n          [1731598470, \"0\"],\n          [1731598500, \"0\"],\n          [1731598530, \"0\"],\n          [1731598560, \"0\"],\n          [1731598590, \"0\"],\n          [1731598620, \"0\"],\n          [1731598650, \"0\"],\n          [1731598680, \"0\"],\n          [1731598710, \"0\"],\n          [1731598740, \"0\"],\n          [1731598770, \"0\"],\n          [1731598800, \"0\"],\n          [1731598830, \"0\"],\n          [1731598860, \"0\"],\n          [1731598890, \"0\"],\n          [1731598920, \"0\"],\n          [1731598950, \"0\"],\n          [1731598980, \"0\"],\n          [1731599010, \"0\"],\n          [1731599040, \"0\"],\n          [1731599070, \"0\"],\n          [1731599100, \"0\"],\n          [1731599130, \"0\"],\n          [1731599160, \"0\"],\n          [1731599190, \"0\"],\n          [1731599220, \"0\"],\n          [1731599250, \"0\"],\n          [1731599280, \"0\"],\n          [1731599310, \"0\"],\n          [1731599340, \"0\"],\n          [1731599370, \"0\"],\n          [1731599400, \"0\"],\n          [1731599430, \"0\"],\n          [1731599460, \"0\"],\n          [1731599490, \"0\"],\n          [1731599520, \"0\"],\n          [1731599550, \"0\"],\n          [1731599580, \"0\"],\n          [1731599610, \"0\"]\n        ]\n      },\n      {\n        \"metric\": {\n          \"type\": \"Update\"\n        },\n        \"values\": [\n          [1731595980, \"0.03333333333333333\"],\n          [1731596010, \"0.03333333333333333\"],\n          [1731596040, \"0.03333333333333333\"],\n          [1731596070, \"0.03333333333333333\"],\n          [1731596100, \"0.03333333333333333\"],\n          [1731596130, \"0.03333333333333333\"],\n          [1731596160, \"0.03333333333333333\"],\n          [1731596190, \"0.03333333333333333\"],\n          [1731596220, \"0.03333333333333333\"],\n          [1731596250, \"0.03333333333333333\"],\n          [1731596280, \"0.025\"],\n          [1731596310, \"0.025\"],\n          [1731596340, \"0.03333333333333333\"],\n          [1731596370, \"0.03333333333333333\"],\n          [1731596400, \"0.04166666666666667\"],\n          [1731596430, \"0.04166666666666667\"],\n          [1731596460, \"0.06666666666666667\"],\n          [1731596490, \"0.06666666666666667\"],\n          [1731596520, \"0.125\"],\n          [1731596550, \"0.125\"],\n          [1731596580, \"0.13333333333333333\"],\n          [1731596610, \"0.13333333333333333\"],\n          [1731596640, \"0.125\"],\n          [1731596670, \"0.125\"],\n          [1731596700, \"0.125\"],\n          [1731596730, \"0.125\"],\n          [1731596760, \"0.125\"],\n          [1731596790, \"0.125\"],\n          [1731596820, \"0.09999999999999999\"],\n          [1731596850, \"0.09999999999999999\"],\n          [1731596880, \"0.10833333333333332\"],\n          [1731596910, \"0.10833333333333332\"],\n          [1731596940, \"0.13333333333333333\"],\n          [1731596970, \"0.13333333333333333\"],\n          [1731597000, \"0.10833333333333332\"],\n          [1731597030, \"0.10833333333333332\"],\n          [1731597060, \"0.11666666666666667\"],\n          [1731597090, \"0.11666666666666667\"],\n          [1731597120, \"0.15833333333333333\"],\n          [1731597150, \"0.15833333333333333\"],\n          [1731597180, \"0.15\"],\n          [1731597210, \"0.15\"],\n          [1731597240, \"0.09999999999999999\"],\n          [1731597270, \"0.09999999999999999\"],\n          [1731597300, \"0.09999999999999999\"],\n          [1731597330, \"0.09999999999999999\"],\n          [1731597360, \"0.11666666666666667\"],\n          [1731597390, \"0.11666666666666667\"],\n          [1731597420, \"0.09166666666666666\"],\n          [1731597450, \"0.09166666666666666\"],\n          [1731597480, \"0.075\"],\n          [1731597510, \"0.075\"],\n          [1731597540, \"0.09999999999999999\"],\n          [1731597570, \"0.09999999999999999\"],\n          [1731597600, \"0.125\"],\n          [1731597630, \"0.125\"],\n          [1731597660, \"0.125\"],\n          [1731597690, \"0.125\"],\n          [1731597720, \"0.14166666666666666\"],\n          [1731597750, \"0.14166666666666666\"],\n          [1731597780, \"0.15833333333333333\"],\n          [1731597810, \"0.15833333333333333\"],\n          [1731597840, \"0.08333333333333333\"],\n          [1731597870, \"0.08333333333333333\"],\n          [1731597900, \"0.03333333333333333\"],\n          [1731597930, \"0.03333333333333333\"],\n          [1731597960, \"0.08333333333333333\"],\n          [1731597990, \"0.08333333333333333\"],\n          [1731598020, \"0.06666666666666667\"],\n          [1731598050, \"0.06666666666666667\"],\n          [1731598080, \"0.03333333333333333\"],\n          [1731598110, \"0.03333333333333333\"],\n          [1731598140, \"0.03333333333333333\"],\n          [1731598170, \"0.03333333333333333\"],\n          [1731598200, \"0.04166666666666667\"],\n          [1731598230, \"0.04166666666666667\"],\n          [1731598260, \"0.025\"],\n          [1731598290, \"0.025\"],\n          [1731598320, \"0.03333333333333333\"],\n          [1731598350, \"0.03333333333333333\"],\n          [1731598380, \"0.04166666666666667\"],\n          [1731598410, \"0.04166666666666667\"],\n          [1731598440, \"0.025\"],\n          [1731598470, \"0.025\"],\n          [1731598500, \"0.03333333333333333\"],\n          [1731598530, \"0.03333333333333333\"],\n          [1731598560, \"0.04166666666666667\"],\n          [1731598590, \"0.04166666666666667\"],\n          [1731598620, \"0.11666666666666667\"],\n          [1731598650, \"0.11666666666666667\"],\n          [1731598680, \"0.13333333333333333\"],\n          [1731598710, \"0.13333333333333333\"],\n          [1731598740, \"0.05\"],\n          [1731598770, \"0.05\"],\n          [1731598800, \"0.03333333333333333\"],\n          [1731598830, \"0.03333333333333333\"],\n          [1731598860, \"0.03333333333333333\"],\n          [1731598890, \"0.03333333333333333\"],\n          [1731598920, \"0.03333333333333333\"],\n          [1731598950, \"0.03333333333333333\"],\n          [1731598980, \"0.04166666666666667\"],\n          [1731599010, \"0.04166666666666667\"],\n          [1731599040, \"0.03333333333333333\"],\n          [1731599070, \"0.03333333333333333\"],\n          [1731599100, \"0.03333333333333333\"],\n          [1731599130, \"0.03333333333333333\"],\n          [1731599160, \"0.04166666666666667\"],\n          [1731599190, \"0.04166666666666667\"],\n          [1731599220, \"0.025\"],\n          [1731599250, \"0.025\"],\n          [1731599280, \"0.03333333333333333\"],\n          [1731599310, \"0.03333333333333333\"],\n          [1731599340, \"0.04166666666666667\"],\n          [1731599370, \"0.04166666666666667\"],\n          [1731599400, \"0.03333333333333333\"],\n          [1731599430, \"0.03333333333333333\"],\n          [1731599460, \"0.03333333333333333\"],\n          [1731599490, \"0.03333333333333333\"],\n          [1731599520, \"0.03333333333333333\"],\n          [1731599550, \"0.03333333333333333\"],\n          [1731599580, \"0.03333333333333333\"],\n          [1731599610, \"0.03333333333333333\"]\n        ]\n      },\n      {\n        \"metric\": {\n          \"type\": \"Use\"\n        },\n        \"values\": [\n          [1731595980, \"1.125\"],\n          [1731596010, \"1.125\"],\n          [1731596040, \"1.2583333333333333\"],\n          [1731596070, \"1.2583333333333333\"],\n          [1731596100, \"1.2833333333333332\"],\n          [1731596130, \"1.2833333333333332\"],\n          [1731596160, \"1.0833333333333333\"],\n          [1731596190, \"1.0833333333333333\"],\n          [1731596220, \"1.0916666666666666\"],\n          [1731596250, \"1.0916666666666666\"],\n          [1731596280, \"1.1333333333333333\"],\n          [1731596310, \"1.1333333333333333\"],\n          [1731596340, \"1.1583333333333332\"],\n          [1731596370, \"1.1583333333333332\"],\n          [1731596400, \"1.125\"],\n          [1731596430, \"1.125\"],\n          [1731596460, \"1.3583333333333332\"],\n          [1731596490, \"1.3583333333333332\"],\n          [1731596520, \"1.5\"],\n          [1731596550, \"1.5\"],\n          [1731596580, \"1.4166666666666665\"],\n          [1731596610, \"1.4166666666666665\"],\n          [1731596640, \"1.2916666666666665\"],\n          [1731596670, \"1.2916666666666665\"],\n          [1731596700, \"1.0833333333333333\"],\n          [1731596730, \"1.0833333333333333\"],\n          [1731596760, \"1.1166666666666667\"],\n          [1731596790, \"1.1166666666666667\"],\n          [1731596820, \"1.275\"],\n          [1731596850, \"1.275\"],\n          [1731596880, \"1.375\"],\n          [1731596910, \"1.375\"],\n          [1731596940, \"1.4416666666666667\"],\n          [1731596970, \"1.4416666666666667\"],\n          [1731597000, \"1.4583333333333333\"],\n          [1731597030, \"1.4583333333333333\"],\n          [1731597060, \"1.4333333333333333\"],\n          [1731597090, \"1.4333333333333333\"],\n          [1731597120, \"1.5916666666666668\"],\n          [1731597150, \"1.5916666666666668\"],\n          [1731597180, \"1.7916666666666665\"],\n          [1731597210, \"1.7916666666666665\"],\n          [1731597240, \"1.5583333333333333\"],\n          [1731597270, \"1.5583333333333333\"],\n          [1731597300, \"1.4416666666666667\"],\n          [1731597330, \"1.4416666666666667\"],\n          [1731597360, \"1.4833333333333332\"],\n          [1731597390, \"1.4833333333333332\"],\n          [1731597420, \"1.3166666666666667\"],\n          [1731597450, \"1.3166666666666667\"],\n          [1731597480, \"1.0333333333333332\"],\n          [1731597510, \"1.0333333333333332\"],\n          [1731597540, \"1.2333333333333332\"],\n          [1731597570, \"1.2333333333333332\"],\n          [1731597600, \"1.7166666666666666\"],\n          [1731597630, \"1.7166666666666666\"],\n          [1731597660, \"1.5499999999999998\"],\n          [1731597690, \"1.5499999999999998\"],\n          [1731597720, \"1.4083333333333332\"],\n          [1731597750, \"1.4083333333333332\"],\n          [1731597780, \"1.5416666666666665\"],\n          [1731597810, \"1.5416666666666665\"],\n          [1731597840, \"1.5333333333333332\"],\n          [1731597870, \"1.5333333333333332\"],\n          [1731597900, \"1.4\"],\n          [1731597930, \"1.4\"],\n          [1731597960, \"1.425\"],\n          [1731597990, \"1.425\"],\n          [1731598020, \"1.6\"],\n          [1731598050, \"1.6\"],\n          [1731598080, \"1.5999999999999999\"],\n          [1731598110, \"1.5999999999999999\"],\n          [1731598140, \"1.5666666666666667\"],\n          [1731598170, \"1.5666666666666667\"],\n          [1731598200, \"1.375\"],\n          [1731598230, \"1.375\"],\n          [1731598260, \"1.1083333333333332\"],\n          [1731598290, \"1.1083333333333332\"],\n          [1731598320, \"1.1416666666666666\"],\n          [1731598350, \"1.1416666666666666\"],\n          [1731598380, \"1.2916666666666665\"],\n          [1731598410, \"1.2916666666666665\"],\n          [1731598440, \"1.275\"],\n          [1731598470, \"1.275\"],\n          [1731598500, \"1.0583333333333333\"],\n          [1731598530, \"1.0583333333333333\"],\n          [1731598560, \"1.0333333333333332\"],\n          [1731598590, \"1.0333333333333332\"],\n          [1731598620, \"1.125\"],\n          [1731598650, \"1.125\"],\n          [1731598680, \"1.1416666666666666\"],\n          [1731598710, \"1.1416666666666666\"],\n          [1731598740, \"1.1916666666666667\"],\n          [1731598770, \"1.1916666666666667\"],\n          [1731598800, \"1.2666666666666666\"],\n          [1731598830, \"1.2666666666666666\"],\n          [1731598860, \"1.5\"],\n          [1731598890, \"1.5\"],\n          [1731598920, \"1.4916666666666667\"],\n          [1731598950, \"1.4916666666666667\"],\n          [1731598980, \"1.15\"],\n          [1731599010, \"1.15\"],\n          [1731599040, \"0.9500000000000001\"],\n          [1731599070, \"0.9500000000000001\"],\n          [1731599100, \"0.9916666666666667\"],\n          [1731599130, \"0.9916666666666667\"],\n          [1731599160, \"0.9666666666666667\"],\n          [1731599190, \"0.9666666666666667\"],\n          [1731599220, \"1.025\"],\n          [1731599250, \"1.025\"],\n          [1731599280, \"1.3166666666666667\"],\n          [1731599310, \"1.3166666666666667\"],\n          [1731599340, \"1.275\"],\n          [1731599370, \"1.275\"],\n          [1731599400, \"1.0583333333333333\"],\n          [1731599430, \"1.0583333333333333\"],\n          [1731599460, \"1.0583333333333333\"],\n          [1731599490, \"1.0583333333333333\"],\n          [1731599520, \"1.0583333333333333\"],\n          [1731599550, \"1.0583333333333333\"],\n          [1731599580, \"1.0583333333333333\"],\n          [1731599610, \"1.0583333333333333\"]\n        ]\n      }\n    ]\n  },\n  \"stats\": {\n    \"seriesFetched\": \"39\",\n    \"executionTimeMsec\": 14\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/apps/slow-query/mock-api-app-provider.tsx",
    "content": "import {\n  DiagnosisServiceAddSqlLimitBodyAction,\n  diagnosisServiceAddSqlLimit,\n  diagnosisServiceCheckSqlLimitSupport,\n  diagnosisServiceGetResourceGroupList,\n  diagnosisServiceGetSlowQueryAvailableAdvancedFilterInfo,\n  diagnosisServiceGetSlowQueryAvailableAdvancedFilters,\n  diagnosisServiceGetSlowQueryAvailableFields,\n  diagnosisServiceGetSlowQueryDetail,\n  diagnosisServiceGetSlowQueryList,\n  diagnosisServiceGetSqlLimitList,\n  diagnosisServiceRemoveSqlLimit,\n} from \"@pingcap-incubator/tidb-dashboard-lib-api-client\"\nimport { AppCtxValue } from \"@pingcap-incubator/tidb-dashboard-lib-apps/slow-query\"\nimport { useNavigate } from \"@tanstack/react-router\"\nimport { useMemo } from \"react\"\n\ndeclare global {\n  interface Window {\n    preUrl?: string[]\n  }\n}\n\nconst clusterId = import.meta.env.VITE_TEST_CLUSTER_ID\n\nexport function useCtxValue(): AppCtxValue {\n  const navigate = useNavigate()\n\n  return useMemo(\n    () => ({\n      ctxId: `slow-query-${clusterId}`,\n      api: {\n        getDbs() {\n          return diagnosisServiceGetSlowQueryAvailableAdvancedFilterInfo(\n            clusterId,\n            \"db\",\n            {\n              skipGlobalErrorHandling: true,\n            },\n          ).then((res) => res.valueList ?? [])\n        },\n        getRuGroups() {\n          return diagnosisServiceGetResourceGroupList(clusterId, {\n            skipGlobalErrorHandling: true,\n          }).then((res) => (res.resourceGroups ?? []).map((r) => r.name || \"\"))\n        },\n        getAdvancedFilterNames() {\n          return diagnosisServiceGetSlowQueryAvailableAdvancedFilters(\n            clusterId,\n          ).then((res) => res.filters ?? [])\n        },\n        getAdvancedFilterInfo(params) {\n          return diagnosisServiceGetSlowQueryAvailableAdvancedFilterInfo(\n            clusterId,\n            params.name,\n          ).then((res) => ({\n            name: res.name ?? \"\",\n            type: res.type ?? \"string\",\n            unit: res.unit ?? \"\",\n            values: res.valueList ?? [],\n          }))\n        },\n        getAvailableFields() {\n          return diagnosisServiceGetSlowQueryAvailableFields(clusterId).then(\n            (res) => res.fields ?? [],\n          )\n        },\n\n        getSlowQueries(params) {\n          const advancedFiltersStrArr: string[] = []\n          for (const filter of params.advancedFilters) {\n            const filterValue = filter.values\n              .map((v) => encodeURIComponent(v))\n              .join(\",\")\n            if (filterValue !== \"\") {\n              advancedFiltersStrArr.push(\n                `${filter.name} ${filter.operator} ${filterValue}`,\n              )\n            }\n          }\n          const fieldsStr = params.fields.includes(\"all\")\n            ? \"*\"\n            : params.fields.join(\",\")\n          return diagnosisServiceGetSlowQueryList(clusterId, {\n            beginTime: params.beginTime + \"\",\n            endTime: params.endTime + \"\",\n            db: params.dbs,\n            text: params.term,\n            orderBy: params.orderBy,\n            isDesc: params.desc,\n            advancedFilter: advancedFiltersStrArr,\n            fields: fieldsStr,\n            pageSize: params.pageSize,\n            skip: params.pageSize * params.pageIndex,\n          }).then((res) => ({\n            total: res.totalSize ?? 0,\n            items: res.data ?? [],\n          }))\n        },\n        getSlowQuery(params: { id: string }) {\n          const [digest, connectionId, timestamp] = params.id.split(\",\")\n          return diagnosisServiceGetSlowQueryDetail(clusterId, digest, {\n            connectionId,\n            timestamp: Number(timestamp),\n          }).then((d) => {\n            if (d.binary_plan_text) {\n              d.plan = d.binary_plan_text\n              delete d.binary_plan_text\n            }\n            return d\n          })\n        },\n\n        // sql limit\n        checkSqlLimitSupport() {\n          return diagnosisServiceCheckSqlLimitSupport(clusterId).then(\n            (res) => ({ is_support: res.isSupport! }),\n          )\n        },\n        getSqlLimitStatus(params) {\n          return diagnosisServiceGetSqlLimitList(clusterId, {\n            watchText: params.watchText,\n          }).then((res) =>\n            (res.data || []).map((d) => ({\n              id: d.id ?? \"\",\n              ru_group: d.resourceGroupName ?? \"\",\n              action: d.action ?? \"\",\n              start_time: d.startTime ?? \"\",\n            })),\n          )\n        },\n        createSqlLimit(params) {\n          return diagnosisServiceAddSqlLimit(clusterId, {\n            watchText: params.watchText,\n            resourceGroup: params.ruGroup,\n            action: params.action as DiagnosisServiceAddSqlLimitBodyAction,\n          }).then(() => {})\n        },\n        deleteSqlLimit(params) {\n          return diagnosisServiceRemoveSqlLimit(clusterId, params).then(\n            () => {},\n          )\n        },\n\n        // sql history\n        getHistoryMetricNames() {\n          return Promise.resolve([\n            { name: \"query_time\", unit: \"s\" },\n            { name: \"memory_max\", unit: \"bytes\" },\n            { name: \"disk_max\", unit: \"bytes\" },\n            { name: \"parse_time\", unit: \"s\" },\n            { name: \"compile_time\", unit: \"s\" },\n            { name: \"rewrite_time\", unit: \"s\" },\n            { name: \"preproc_subqueries_time\", unit: \"s\" },\n            { name: \"optimize_time\", unit: \"s\" },\n            { name: \"cop_time\", unit: \"s\" },\n            { name: \"wait_time\", unit: \"s\" },\n            { name: \"process_time\", unit: \"s\" },\n            { name: \"local_latch_wait_time\", unit: \"s\" },\n            { name: \"lock_keys_time\", unit: \"s\" },\n            { name: \"resolve_lock_time\", unit: \"s\" },\n            { name: \"wait_ts\", unit: \"s\" },\n            { name: \"get_commit_ts_time\", unit: \"s\" },\n            { name: \"prewrite_time\", unit: \"s\" },\n            { name: \"commit_time\", unit: \"s\" },\n            { name: \"backoff_time\", unit: \"s\" },\n            { name: \"commit_backoff_time\", unit: \"s\" },\n            { name: \"exec_retry_time\", unit: \"s\" },\n            { name: \"write_sql_response_total\", unit: \"s\" },\n            { name: \"wait_prewrite_binlog_time\", unit: \"s\" },\n          ])\n        },\n        getHistoryMetricData(params) {\n          return diagnosisServiceGetSlowQueryList(clusterId, {\n            beginTime: params.beginTime + \"\",\n            endTime: params.endTime + \"\",\n            orderBy: \"timestamp\",\n            isDesc: false,\n            pageSize: 1000,\n            fields: [\"timestamp\", params.metricName].join(\",\"),\n            advancedFilter: [`digest = ${params.sqlDigest}`],\n          }).then((res) =>\n            (res.data ?? []).map((d) => [\n              d.timestamp! * 1000,\n              d[params.metricName as keyof typeof d]! as number,\n            ]),\n          )\n        },\n      },\n      cfg: {\n        title: \"\",\n      },\n      actions: {\n        openDetail: (id: string, newTab: boolean) => {\n          window.preUrl = [window.location.pathname + window.location.search]\n          if (newTab) {\n            window.open(`/slow-query/detail?id=${id}`, \"_blank\")\n          } else {\n            navigate({ to: `/slow-query/detail?id=${id}` })\n          }\n        },\n        backToList: () => {\n          const preUrl = window.preUrl?.pop()\n          navigate({ to: preUrl || \"/slow-query\" })\n        },\n        openStatement(id) {\n          const [from, to, sqlDigest, dbName] = id.split(\",\")\n          const fullUrl = `/statement?from=${from}&to=${to}&af=digest,${encodeURIComponent(\"=\")},${sqlDigest};schema_name,in,${dbName}`\n          window.open(fullUrl, \"_blank\")\n        },\n      },\n    }),\n    [navigate],\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/apps/statement/mock-api-app-provider.tsx",
    "content": "import {\n  DiagnosisServiceAddSqlLimitBodyAction,\n  diagnosisServiceAddSqlLimit,\n  diagnosisServiceBindSqlPlan,\n  diagnosisServiceCheckSqlLimitSupport,\n  diagnosisServiceCheckSqlPlanSupport,\n  diagnosisServiceGetResourceGroupList,\n  diagnosisServiceGetSqlLimitList,\n  diagnosisServiceGetSqlPlanBindingList,\n  diagnosisServiceGetSqlPlanList,\n  diagnosisServiceGetTopSqlAvailableAdvancedFilterInfo,\n  diagnosisServiceGetTopSqlAvailableAdvancedFilters,\n  diagnosisServiceGetTopSqlAvailableFields,\n  diagnosisServiceGetTopSqlConfigs,\n  diagnosisServiceGetTopSqlDetail,\n  diagnosisServiceGetTopSqlList,\n  diagnosisServiceRemoveSqlLimit,\n  diagnosisServiceUnbindSqlPlan,\n  diagnosisServiceUpdateTopSqlConfigs,\n} from \"@pingcap-incubator/tidb-dashboard-lib-api-client\"\nimport { AppCtxValue } from \"@pingcap-incubator/tidb-dashboard-lib-apps/statement\"\nimport { useNavigate } from \"@tanstack/react-router\"\nimport { useMemo } from \"react\"\n\nimport { STMT_TYPES } from \"./stmt-types\"\n\ndeclare global {\n  interface Window {\n    preUrl?: string[]\n  }\n}\n\nconst clusterId = import.meta.env.VITE_TEST_CLUSTER_ID\n\nexport function useCtxValue(): AppCtxValue {\n  const navigate = useNavigate()\n\n  return useMemo(\n    () => ({\n      ctxId: `statement-${clusterId}`,\n      api: {\n        getStmtKinds() {\n          return Promise.resolve(STMT_TYPES)\n        },\n        getDbs() {\n          return diagnosisServiceGetTopSqlAvailableAdvancedFilterInfo(\n            clusterId,\n            \"schema_name\",\n            {\n              skipGlobalErrorHandling: true,\n            },\n          ).then((res) => res.valueList ?? [])\n        },\n        getRuGroups() {\n          return diagnosisServiceGetResourceGroupList(clusterId, {\n            skipGlobalErrorHandling: true,\n          }).then((res) => (res.resourceGroups ?? []).map((r) => r.name || \"\"))\n        },\n        getAdvancedFilterNames() {\n          return diagnosisServiceGetTopSqlAvailableAdvancedFilters(\n            clusterId,\n          ).then((res) => res.filters ?? [])\n        },\n        getAdvancedFilterInfo(params) {\n          if (params.name === \"stmt_type\") {\n            return Promise.resolve({\n              name: \"stmt_type\",\n              type: \"string\",\n              unit: \"\",\n              values: STMT_TYPES,\n            })\n          }\n          return diagnosisServiceGetTopSqlAvailableAdvancedFilterInfo(\n            clusterId,\n            params.name,\n          ).then((res) => ({\n            name: res.name ?? \"\",\n            type: res.type ?? \"string\",\n            unit: res.unit ?? \"\",\n            values: res.valueList ?? [],\n          }))\n        },\n        getAvailableFields() {\n          return diagnosisServiceGetTopSqlAvailableFields(clusterId).then(\n            (res) => res.fields ?? [],\n          )\n        },\n\n        // config\n        getStmtConfig() {\n          return diagnosisServiceGetTopSqlConfigs(clusterId).then((res) => ({\n            enable: res.enable!,\n            max_size: res.maxSize!,\n            refresh_interval: res.refreshInterval!,\n            history_size: res.historySize!,\n            internal_query: res.internalQuery!,\n          }))\n        },\n        updateStmtConfig(params) {\n          return diagnosisServiceUpdateTopSqlConfigs(clusterId, {\n            enable: params.enable,\n            refreshInterval: params.refresh_interval,\n            historySize: params.history_size,\n            maxSize: params.max_size,\n            internalQuery: params.internal_query,\n          }).then(() => {})\n        },\n\n        getStmtList(params) {\n          const advancedFiltersStrArr: string[] = []\n          for (const filter of params.advancedFilters) {\n            const filterValue = filter.values\n              .map((v) => encodeURIComponent(v))\n              .join(\",\")\n            if (filterValue !== \"\") {\n              advancedFiltersStrArr.push(\n                `${filter.name} ${filter.operator} ${filterValue}`,\n              )\n            }\n          }\n          const fieldsStr = params.fields.includes(\"all\")\n            ? \"*\"\n            : params.fields.join(\",\")\n          return diagnosisServiceGetTopSqlList(clusterId, {\n            beginTime: params.beginTime + \"\",\n            endTime: params.endTime + \"\",\n            db: params.dbs,\n            text: params.term,\n            orderBy: params.orderBy,\n            isDesc: params.desc,\n            advancedFilter: advancedFiltersStrArr,\n            fields: fieldsStr,\n            pageSize: params.pageSize,\n            skip: params.pageSize * params.pageIndex,\n          }).then((res) => ({\n            total: res.totalSize ?? 0,\n            items: res.data ?? [],\n          }))\n        },\n        getStmtPlans(params) {\n          const [beginTime, endTime, digest, schemaName] = params.id.split(\",\")\n          return diagnosisServiceGetSqlPlanList(clusterId, {\n            beginTime: beginTime + \"\",\n            endTime: endTime + \"\",\n            digest,\n            schemaName,\n          }).then((res) => res.data ?? [])\n        },\n        getStmtPlansDetail(params) {\n          const [beginTime, endTime, digest, _schemaName] = params.id.split(\",\")\n          return diagnosisServiceGetTopSqlDetail(clusterId, digest, {\n            beginTime: beginTime + \"\",\n            endTime: endTime + \"\",\n            planDigest: params.plans.filter(Boolean),\n          }).then((d) => {\n            if (d.binary_plan_text) {\n              d.plan = d.binary_plan_text\n              delete d.binary_plan_text\n            }\n            return d\n          })\n        },\n\n        // sql plan bind\n        checkPlanBindSupport() {\n          return diagnosisServiceCheckSqlPlanSupport(clusterId).then((res) => ({\n            is_support: res.isSupport!,\n          }))\n        },\n        getPlanBindStatus(params) {\n          return diagnosisServiceGetSqlPlanBindingList(clusterId, {\n            beginTime: params.beginTime + \"\",\n            endTime: params.endTime + \"\",\n            digest: params.sqlDigest,\n          }).then((res) => (res.data ?? []).map((d) => d.planDigest!))\n        },\n        createPlanBind(params) {\n          return diagnosisServiceBindSqlPlan(clusterId, params.planDigest).then(\n            () => {},\n          )\n        },\n        deletePlanBind(params) {\n          return diagnosisServiceUnbindSqlPlan(clusterId, {\n            digest: params.sqlDigest,\n          }).then(() => {})\n        },\n\n        // sql limit\n        checkSqlLimitSupport() {\n          return diagnosisServiceCheckSqlLimitSupport(clusterId).then(\n            (res) => ({ is_support: res.isSupport! }),\n          )\n        },\n        getSqlLimitStatus(params) {\n          return diagnosisServiceGetSqlLimitList(clusterId, {\n            watchText: params.watchText,\n          }).then((res) =>\n            (res.data || []).map((d) => ({\n              id: d.id ?? \"\",\n              ru_group: d.resourceGroupName ?? \"\",\n              action: d.action ?? \"\",\n              start_time: d.startTime ?? \"\",\n            })),\n          )\n        },\n        createSqlLimit(params) {\n          return diagnosisServiceAddSqlLimit(clusterId, {\n            watchText: params.watchText,\n            resourceGroup: params.ruGroup,\n            action: params.action as DiagnosisServiceAddSqlLimitBodyAction,\n          }).then(() => {})\n        },\n        deleteSqlLimit(params) {\n          return diagnosisServiceRemoveSqlLimit(clusterId, params).then(\n            () => {},\n          )\n        },\n\n        // sql history\n        getHistoryMetricNames() {\n          return Promise.resolve([\n            { name: \"sum_latency\", unit: \"ns\" },\n            { name: \"avg_latency\", unit: \"ns\" },\n            { name: \"max_latency\", unit: \"ns\" },\n            { name: \"min_latency\", unit: \"ns\" },\n            { name: \"avg_disk\", unit: \"bytes\" },\n            { name: \"max_disk\", unit: \"bytes\" },\n            { name: \"exec_count\", unit: \"short\" },\n            { name: \"plan_count\", unit: \"short\" },\n            { name: \"avg_parse_latency\", unit: \"ns\" },\n            { name: \"avg_compile_latency\", unit: \"ns\" },\n            { name: \"avg_wait_time\", unit: \"ns\" },\n            { name: \"avg_process_time\", unit: \"ns\" },\n            { name: \"avg_backoff_time\", unit: \"ns\" },\n            { name: \"avg_get_commit_ts_time\", unit: \"ns\" },\n            { name: \"avg_local_latch_wait_time\", unit: \"ns\" },\n            { name: \"avg_resolve_lock_time\", unit: \"ns\" },\n            { name: \"avg_prewrite_time\", unit: \"ns\" },\n            { name: \"avg_commit_time\", unit: \"ns\" },\n            { name: \"avg_commit_backoff_time\", unit: \"ns\" },\n          ])\n        },\n        getHistoryMetricData(params) {\n          return diagnosisServiceGetTopSqlList(clusterId, {\n            beginTime: params.beginTime + \"\",\n            endTime: params.endTime + \"\",\n            orderBy: \"summary_begin_time\",\n            isDesc: false,\n            pageSize: 1000,\n            fields: [\"summary_begin_time\", params.metricName].join(\",\"),\n            advancedFilter: [`digest = ${params.sqlDigest}`],\n            isGroupByTime: true,\n          }).then((res) =>\n            (res.data ?? []).map((d) => [\n              d.summary_begin_time! * 1000,\n              d[params.metricName as keyof typeof d]! as number,\n            ]),\n          )\n        },\n      },\n      cfg: {\n        title: \"\",\n      },\n      actions: {\n        openDetail: (id: string, newTab) => {\n          window.preUrl = [window.location.pathname + window.location.search]\n          if (newTab) {\n            window.open(`/statement/detail?id=${id}`, \"_blank\")\n          } else {\n            navigate({ to: `/statement/detail?id=${id}` })\n          }\n        },\n        backToList: () => {\n          const preUrl = window.preUrl?.pop()\n          navigate({ to: preUrl || \"/statement\" })\n        },\n        openSlowQueryList(id) {\n          const [from, to, digest, _schema, plan] = id.split(\",\")\n          const fullUrl = `/slow-query?from=${from}&to=${to}&af=digest,${encodeURIComponent(\"=\")},${digest};plan_digest,${encodeURIComponent(\"=\")},${plan || \"\"}`\n\n          // open in a new tab\n          window.open(fullUrl, \"_blank\")\n        },\n      },\n    }),\n    [navigate],\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/apps/statement/stmt-types.ts",
    "content": "export const STMT_TYPES = [\n  \"AlterTable\",\n  \"AnalyzeTable\",\n  \"Begin\",\n  \"Change\",\n  \"Commit\",\n  \"CompactTable\",\n  \"CreateDatabase\",\n  \"CreateIndex\",\n  \"CreateTable\",\n  \"CreateView\",\n  \"CreateUser\",\n  \"Delete\",\n  \"DropDatabase\",\n  \"DropIndex\",\n  \"DropView\",\n  \"DropTable\",\n  \"DescTable\",\n  \"ExplainAnalyzeSQL\",\n  \"ExplainSQL\",\n  \"Replace\",\n  \"Insert\",\n  \"LoadData\",\n  \"Rollback\",\n  \"Select\",\n  \"Set\",\n  \"Show\",\n  \"TruncateTable\",\n  \"Update\",\n  \"Grant\",\n  \"Revoke\",\n  \"Deallocate\",\n  \"Execute\",\n  \"Prepare\",\n  \"Use\",\n  \"CreateBinding\",\n  \"IndexAdvise\",\n  \"DropBinding\",\n  \"Trace\",\n  \"Shutdown\",\n  \"Savepoint\",\n  \"other\",\n]\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/index.css",
    "content": ""
  },
  {
    "path": "ui-v2/packages/portals/test/src/main.tsx",
    "content": "import { axiosClient } from \"@pingcap-incubator/tidb-dashboard-lib-api-client\"\nimport { initI18n } from \"@pingcap-incubator/tidb-dashboard-lib-apps/utils\"\nimport { StrictMode } from \"react\"\nimport { createRoot } from \"react-dom/client\"\n\nimport App from \"./App.tsx\"\n\nimport \"./index.css\"\n\ninitI18n()\n\naxiosClient.interceptors.request.use((config) => {\n  // env: ''\n  // prod: 'https://tidb-dashboard-lib-api-server.2008-hbl-cf.workers.dev'\n  config.baseURL = import.meta.env.VITE_API_BASE_URL\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  config.headers = { \"Ti-Env\": \"dev\" } as any\n  return config\n})\n\n// handle error\naxiosClient.interceptors.response.use(\n  (response) => response,\n  (error) => {\n    console.log(error)\n    return Promise.reject(error)\n  },\n)\n\ncreateRoot(document.getElementById(\"root\")!).render(\n  <StrictMode>\n    <App />\n  </StrictMode>,\n)\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/providers/react-query-provider.tsx",
    "content": "import { QueryClient, QueryClientProvider } from \"@tanstack/react-query\"\n\n// Create a react query client\nconst queryClient = new QueryClient({\n  defaultOptions: {\n    queries: {\n      refetchOnWindowFocus: false,\n      // retry: 1\n      // refetchOnMount: false,\n      // refetchOnReconnect: false,\n    },\n  },\n})\n\nexport function ReactQueryProvider({\n  children,\n}: {\n  children: React.ReactNode\n}) {\n  return (\n    // Provide the client to your App\n    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/providers/url-state-provider.tsx",
    "content": "import { UrlStateProvider } from \"@pingcap-incubator/tidb-dashboard-lib-apps/utils\"\nimport { useLocation, useNavigate } from \"@tanstack/react-router\"\nimport { useMemo } from \"react\"\n\nexport function TanStackRouterUrlStateProvider(props: {\n  children: React.ReactNode\n}) {\n  const loc = useLocation()\n  const navigate = useNavigate()\n\n  const ctxValue = useMemo(() => {\n    return {\n      urlQuery: loc.searchStr,\n      setUrlQuery(v: string) {\n        navigate({ to: `${loc.pathname}?${v}` })\n      },\n    }\n  }, [loc.searchStr, loc.pathname, navigate])\n\n  return <UrlStateProvider value={ctxValue}>{props.children}</UrlStateProvider>\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/router/devtools.tsx",
    "content": "import React from \"react\"\n\nexport const RouterDevtools =\n  process.env.NODE_ENV === \"production\" ||\n  import.meta.env.VITE_TANSTACK_ROUTER_DEVTOOLS_ENABLED !== \"true\"\n    ? () => null\n    : React.lazy(() =>\n        import(\"@tanstack/router-devtools\").then((res) => ({\n          default: res.TanStackRouterDevtools,\n        })),\n      )\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/router/provider.tsx",
    "content": "import { RouterProvider as TanstackRouterProvider } from \"@tanstack/react-router\"\n\nimport { router } from \"./router\"\n\nexport function RouterProvider() {\n  return <TanstackRouterProvider router={router} />\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/router/router.ts",
    "content": "import { createRouter } from \"@tanstack/react-router\"\n\nimport { routeTree } from \"./routeTree.gen\"\n\nexport const router = createRouter({\n  routeTree,\n})\n\n// Register the router instance for type safety\ndeclare module \"@tanstack/react-router\" {\n  interface Register {\n    router: typeof router\n  }\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/__root.tsx",
    "content": "import { ChartThemeSwitch } from \"@pingcap-incubator/tidb-dashboard-lib-apps/charts\"\nimport { useHotkeyChangeLang } from \"@pingcap-incubator/tidb-dashboard-lib-apps/utils\"\nimport { Outlet, createRootRoute } from \"@tanstack/react-router\"\nimport { useComputedColorScheme } from \"@tidbcloud/uikit\"\n\nimport { RouterDevtools } from \"../router/devtools\"\n\nexport const Route = createRootRoute({\n  component: RootComponent,\n})\n\nfunction RootComponent() {\n  useHotkeyChangeLang()\n  const theme = useComputedColorScheme()\n\n  return (\n    <>\n      <Outlet />\n      <RouterDevtools position=\"bottom-right\" />\n      <ChartThemeSwitch value={theme} />\n    </>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout/metrics/azores-cluster-overview.lazy.tsx",
    "content": "import { AzoresClusterOverviewMetricsPage } from \"@pingcap-incubator/tidb-dashboard-lib-apps/metric\"\nimport { createLazyFileRoute } from \"@tanstack/react-router\"\n\nimport { TanStackRouterUrlStateProvider } from \"../../../providers/url-state-provider\"\n\nexport const Route = createLazyFileRoute(\n  \"/_apps-layout/metrics/azores-cluster-overview\",\n)({\n  component: RouteComponent,\n})\n\nfunction RouteComponent() {\n  return (\n    <TanStackRouterUrlStateProvider>\n      <AzoresClusterOverviewMetricsPage />\n    </TanStackRouterUrlStateProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout/metrics/azores-cluster.lazy.tsx",
    "content": "import { AzoresClusterMetricsPage } from \"@pingcap-incubator/tidb-dashboard-lib-apps/metric\"\nimport { createLazyFileRoute } from \"@tanstack/react-router\"\n\nimport { TanStackRouterUrlStateProvider } from \"../../../providers/url-state-provider\"\n\nexport const Route = createLazyFileRoute(\n  \"/_apps-layout/metrics/azores-cluster\",\n)({\n  component: RouteComponent,\n})\n\nfunction RouteComponent() {\n  return (\n    <TanStackRouterUrlStateProvider>\n      <AzoresClusterMetricsPage />\n    </TanStackRouterUrlStateProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout/metrics/azores-host.lazy.tsx",
    "content": "import { AzoresHostMetricsPage } from \"@pingcap-incubator/tidb-dashboard-lib-apps/metric\"\nimport { createLazyFileRoute } from \"@tanstack/react-router\"\n\nimport { TanStackRouterUrlStateProvider } from \"../../../providers/url-state-provider\"\n\nexport const Route = createLazyFileRoute(\"/_apps-layout/metrics/azores-host\")({\n  component: RouteComponent,\n})\n\nfunction RouteComponent() {\n  return (\n    <TanStackRouterUrlStateProvider>\n      <AzoresHostMetricsPage />\n    </TanStackRouterUrlStateProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout/metrics/azores-overview.lazy.tsx",
    "content": "import { AzoresOverviewMetricsPage } from \"@pingcap-incubator/tidb-dashboard-lib-apps/metric\"\nimport { createLazyFileRoute } from \"@tanstack/react-router\"\n\nimport { TanStackRouterUrlStateProvider } from \"../../../providers/url-state-provider\"\n\nexport const Route = createLazyFileRoute(\n  \"/_apps-layout/metrics/azores-overview\",\n)({\n  component: RouteComponent,\n})\n\nfunction RouteComponent() {\n  return (\n    <TanStackRouterUrlStateProvider>\n      <AzoresOverviewMetricsPage />\n    </TanStackRouterUrlStateProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout/metrics/index.tsx",
    "content": "import { createFileRoute, redirect } from \"@tanstack/react-router\"\n\nexport const Route = createFileRoute(\"/_apps-layout/metrics/\")({\n  beforeLoad: () => {\n    throw redirect({ to: \"/metrics/normal\" })\n  },\n})\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout/metrics/normal.lazy.tsx",
    "content": "import { NormalMetricsPage } from \"@pingcap-incubator/tidb-dashboard-lib-apps/metric\"\nimport { createLazyFileRoute } from \"@tanstack/react-router\"\n\nimport { TanStackRouterUrlStateProvider } from \"../../../providers/url-state-provider\"\n\nexport const Route = createLazyFileRoute(\"/_apps-layout/metrics/normal\")({\n  component: RouteComponent,\n})\n\nfunction RouteComponent() {\n  return (\n    <TanStackRouterUrlStateProvider>\n      <NormalMetricsPage />\n    </TanStackRouterUrlStateProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout/metrics.lazy.tsx",
    "content": "import { AppProvider } from \"@pingcap-incubator/tidb-dashboard-lib-apps/metric\"\nimport { Outlet, createLazyFileRoute } from \"@tanstack/react-router\"\n\nimport { useCtxValue } from \"../../apps/metric/mock-api-app-provider\"\n\nexport const Route = createLazyFileRoute(\"/_apps-layout/metrics\")({\n  component: RouteComponent,\n})\n\nfunction RouteComponent() {\n  const ctxValue = useCtxValue()\n  return (\n    <AppProvider ctxValue={ctxValue}>\n      <Outlet />\n    </AppProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout/slow-query/detail.lazy.tsx",
    "content": "import { Detail as SlowQueryDetailPage } from \"@pingcap-incubator/tidb-dashboard-lib-apps/slow-query\"\nimport { createLazyFileRoute } from \"@tanstack/react-router\"\n\nimport { TanStackRouterUrlStateProvider } from \"../../../providers/url-state-provider\"\n\nexport const Route = createLazyFileRoute(\"/_apps-layout/slow-query/detail\")({\n  component: RouteComponent,\n})\n\nfunction RouteComponent() {\n  return (\n    <TanStackRouterUrlStateProvider>\n      <SlowQueryDetailPage />\n    </TanStackRouterUrlStateProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout/slow-query/index.lazy.tsx",
    "content": "import { List as SlowQueryListPage } from \"@pingcap-incubator/tidb-dashboard-lib-apps/slow-query\"\nimport { createLazyFileRoute } from \"@tanstack/react-router\"\n\nimport { TanStackRouterUrlStateProvider } from \"../../../providers/url-state-provider\"\n\nexport const Route = createLazyFileRoute(\"/_apps-layout/slow-query/\")({\n  component: RouteComponent,\n})\n\nfunction RouteComponent() {\n  return (\n    <TanStackRouterUrlStateProvider>\n      <SlowQueryListPage />\n    </TanStackRouterUrlStateProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout/slow-query.lazy.tsx",
    "content": "import { AppProvider } from \"@pingcap-incubator/tidb-dashboard-lib-apps/slow-query\"\nimport { Outlet, createLazyFileRoute } from \"@tanstack/react-router\"\n\nimport { useCtxValue } from \"../../apps/slow-query/mock-api-app-provider\"\n\nexport const Route = createLazyFileRoute(\"/_apps-layout/slow-query\")({\n  component: RouteComponent,\n})\n\nfunction RouteComponent() {\n  const ctxValue = useCtxValue()\n  return (\n    <AppProvider ctxValue={ctxValue}>\n      <Outlet />\n    </AppProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout/statement/detail.lazy.tsx",
    "content": "import { Detail as StatementDetailPage } from \"@pingcap-incubator/tidb-dashboard-lib-apps/statement\"\nimport { createLazyFileRoute } from \"@tanstack/react-router\"\n\nimport { TanStackRouterUrlStateProvider } from \"../../../providers/url-state-provider\"\n\nexport const Route = createLazyFileRoute(\"/_apps-layout/statement/detail\")({\n  component: RouteComponent,\n})\n\nfunction RouteComponent() {\n  return (\n    <TanStackRouterUrlStateProvider>\n      <StatementDetailPage />\n    </TanStackRouterUrlStateProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout/statement/index.lazy.tsx",
    "content": "import { List as StatementListPage } from \"@pingcap-incubator/tidb-dashboard-lib-apps/statement\"\nimport { createLazyFileRoute } from \"@tanstack/react-router\"\n\nimport { TanStackRouterUrlStateProvider } from \"../../../providers/url-state-provider\"\n\nexport const Route = createLazyFileRoute(\"/_apps-layout/statement/\")({\n  component: RouteComponent,\n})\n\nfunction RouteComponent() {\n  return (\n    <TanStackRouterUrlStateProvider>\n      <StatementListPage />\n    </TanStackRouterUrlStateProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout/statement.lazy.tsx",
    "content": "import { AppProvider } from \"@pingcap-incubator/tidb-dashboard-lib-apps/statement\"\nimport { Outlet, createLazyFileRoute } from \"@tanstack/react-router\"\n\nimport { useCtxValue } from \"../../apps/statement/mock-api-app-provider\"\n\nexport const Route = createLazyFileRoute(\"/_apps-layout/statement\")({\n  component: RouteComponent,\n})\n\nfunction RouteComponent() {\n  const ctxValue = useCtxValue()\n  return (\n    <AppProvider ctxValue={ctxValue}>\n      <Outlet />\n    </AppProvider>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/_apps-layout.tsx",
    "content": "import { Link, Outlet, createFileRoute } from \"@tanstack/react-router\"\nimport { AppShell, NavLink, ScrollArea, Stack } from \"@tidbcloud/uikit\"\nimport { createStyles } from \"@tidbcloud/uikit/emotion\"\n\nexport const Route = createFileRoute(\"/_apps-layout\")({\n  component: RouteComponent,\n})\n\nexport const useStyles = createStyles(() => {\n  return {\n    navItems: {\n      a: {\n        textDecoration: \"none\",\n      },\n    },\n  }\n})\n\nfunction RouteComponent() {\n  const { classes } = useStyles()\n\n  return (\n    <AppShell navbar={{ width: 200, breakpoint: 0 }} padding=\"md\">\n      <AppShell.Navbar p=\"xs\" bg=\"carbon.2\">\n        <AppShell.Section p={8}>TiDB Dashboard Lib</AppShell.Section>\n        <AppShell.Section grow component={ScrollArea}>\n          <Stack gap={8} className={classes.navItems}>\n            <NavLink label=\"Metrics\">\n              <Stack gap={8}>\n                <Link to=\"/metrics/normal\">\n                  {({ isActive }) => (\n                    <NavLink active={isActive} label=\"Normal\" />\n                  )}\n                </Link>\n                <Link to=\"/metrics/azores-overview\">\n                  {({ isActive }) => (\n                    <NavLink active={isActive} label=\"Azores Overview\" />\n                  )}\n                </Link>\n                <Link to=\"/metrics/azores-host\">\n                  {({ isActive }) => (\n                    <NavLink active={isActive} label=\"Azores Host\" />\n                  )}\n                </Link>\n                <Link to=\"/metrics/azores-cluster-overview\">\n                  {({ isActive }) => (\n                    <NavLink\n                      active={isActive}\n                      label=\"Azores Cluster Overview\"\n                    />\n                  )}\n                </Link>\n                <Link to=\"/metrics/azores-cluster\">\n                  {({ isActive }) => (\n                    <NavLink active={isActive} label=\"Azores Cluster\" />\n                  )}\n                </Link>\n              </Stack>\n            </NavLink>\n            <Link to=\"/slow-query\">\n              {({ isActive }) => (\n                <NavLink active={isActive} label=\"Slow Query\" />\n              )}\n            </Link>\n            <Link to=\"/statement\">\n              {({ isActive }) => (\n                <NavLink active={isActive} label=\"Statement\" />\n              )}\n            </Link>\n          </Stack>\n        </AppShell.Section>\n        <AppShell.Section p={8}>Theme / Language</AppShell.Section>\n      </AppShell.Navbar>\n\n      <AppShell.Main>\n        <Outlet />\n      </AppShell.Main>\n    </AppShell>\n  )\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/index.ts",
    "content": "import { createFileRoute, redirect } from \"@tanstack/react-router\"\n\nexport const Route = createFileRoute(\"/\")({\n  beforeLoad: () => {\n    throw redirect({ to: \"/metrics\" })\n  },\n})\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/routes/login.lazy.tsx",
    "content": "import { createLazyFileRoute } from \"@tanstack/react-router\"\n\nexport const Route = createLazyFileRoute(\"/login\")({\n  component: LoginPage,\n})\n\nfunction LoginPage() {\n  return <>Login</>\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "ui-v2/packages/portals/test/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig.app.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\"\n  },\n  \"include\": [\"./src\"]\n}\n"
  },
  {
    "path": "ui-v2/packages/portals/test/vite.config.ts",
    "content": "import { TanStackRouterVite } from \"@tanstack/router-plugin/vite\"\nimport react from \"@vitejs/plugin-react\"\nimport { defineConfig, loadEnv } from \"vite\"\n\nexport default defineConfig(({ mode }) => {\n  const env = loadEnv(mode, process.cwd())\n\n  return {\n    plugins: [\n      TanStackRouterVite({\n        generatedRouteTree: \"./src/router/routeTree.gen.ts\",\n      }),\n      react(),\n    ],\n    server: {\n      proxy: {\n        \"/api\": {\n          target: env.VITE_API_SERVER,\n          changeOrigin: true,\n        },\n      },\n    },\n  }\n})\n"
  },
  {
    "path": "ui-v2/packages/tsconfig.app.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* declaration */\n    \"declaration\": true,\n    \"emitDeclarationOnly\": true,\n    // \"noEmit\": false,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"isolatedModules\": true,\n    \"moduleDetection\": \"force\",\n    \"resolveJsonModule\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  }\n}\n"
  },
  {
    "path": "ui-v2/pnpm-workspace.yaml",
    "content": "packages:\n  # all packages in subdirs of packages/\n  - \"packages/**\"\n\ncatalog:\n  \"@tidbcloud/uikit\": \"^2.1.7\"\n"
  },
  {
    "path": "ui-v2/scripts/gen-locales.ts",
    "content": "import * as fs from \"fs\"\nimport * as path from \"path\"\n\nimport { glob } from \"glob\"\nimport $ from \"gogocode\"\n\ninterface LocaleData {\n  [app: string]: {\n    keys: Record<string, string>\n    texts: Record<string, string>\n  }\n}\n\nfunction sortLocaleData(localeData: LocaleData) {\n  Object.keys(localeData).forEach((appName) => {\n    localeData[appName].keys = Object.fromEntries(\n      Object.entries(localeData[appName].keys).sort(),\n    )\n    localeData[appName].texts = Object.fromEntries(\n      Object.entries(localeData[appName].texts).sort(),\n    )\n  })\n}\n\nfunction handleAppFiles(appFolder: string, localeData: LocaleData) {\n  const outputDir = `${appFolder}/locales`\n  fs.mkdirSync(outputDir, { recursive: true })\n\n  const appName = appFolder.split(\"/\").pop() || \"\"\n  let outputData = {\n    __namespace__: appName,\n    __comment__:\n      \"this file can be updated by running `pnpm gen:locales` command\",\n    ...localeData[appFolder].keys,\n    // ...localeData[appFolder].texts, // texts doesn't need to write in the en.json, to save space\n  }\n\n  // Write en.json\n  fs.writeFileSync(\n    path.join(outputDir, \"en.json\"),\n    JSON.stringify(outputData, null, 2) + \"\\n\",\n  )\n\n  // Write zh.json\n  outputData = {\n    ...outputData,\n    ...localeData[appFolder].texts,\n  }\n  // Update zh.json\n  // Check if zh.json exists and merge with existing translations\n  const zhPath = path.join(outputDir, \"zh.json\")\n  if (fs.existsSync(zhPath)) {\n    const existedZh = JSON.parse(fs.readFileSync(zhPath, \"utf-8\"))\n\n    // replace outputData with existedZh\n    for (const key in existedZh) {\n      outputData[key] = existedZh[key]\n    }\n  }\n  fs.writeFileSync(zhPath, JSON.stringify(outputData, null, 2) + \"\\n\")\n\n  // write index.ts\n  const indexPath = path.join(outputDir, \"index.ts\")\n  fs.writeFileSync(\n    indexPath,\n    `import { addLangsLocales } from \"@pingcap-incubator/tidb-dashboard-lib-utils\"\n\nimport en from \"./en.json\"\nimport zh from \"./zh.json\"\n\naddLangsLocales({ en, zh })\n`,\n  )\n}\n\nconst bizUiNsPathMap: Map<string, string> = new Map()\nfunction handleBizUIFiles(appName: string, localeData: LocaleData) {\n  const filePath = bizUiNsPathMap.get(appName)\n  if (!filePath) {\n    return\n  }\n\n  const code = fs.readFileSync(filePath, \"utf-8\")\n  const ast = $(code)\n\n  const allKeys = Object.keys(localeData[appName].keys).concat(\n    Object.keys(localeData[appName].texts),\n  )\n  ast.replace(\n    `type I18nLocaleKeys = $_$`,\n    `type I18nLocaleKeys = ${allKeys.map((k) => `\\n  | \"${k}\"`).join(\"\")}`,\n  )\n\n  // update en\n  const keyPartKeys = Object.keys(localeData[appName].keys)\n  ast.replace(\n    `const en: I18nLocale = { $$$0 }`,\n    `const en: I18nLocale = { ${keyPartKeys.map((k) => `\"${k}\": \"${localeData[appName].keys[k]}\"`).join(\", \")} }`,\n  )\n\n  // get existed zh\n  const existedZh = ast.find(`const zh: I18nLocale = { $$$0 }`).match[\"$$$0\"]\n  const existedZhLocales = existedZh.reduce(\n    (acc, kv) => {\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      const _kv = kv as any\n      acc[_kv.key.name] = _kv.value.value\n      return acc\n    },\n    {} as Record<string, string>,\n  )\n  const zhLocales = {\n    ...localeData[appName].keys,\n    ...localeData[appName].texts,\n  }\n  // merge\n  Object.keys(existedZhLocales).forEach((k) => {\n    zhLocales[k] = existedZhLocales[k]\n  })\n  // update zh\n  ast.replace(\n    `const zh: I18nLocale = { $$$0 }`,\n    `const zh: I18nLocale = { ${allKeys.map((k) => `\\n  \"${k}\": \"${zhLocales[k]}\",`).join(\"\")} }`,\n  )\n\n  fs.writeFileSync(filePath, ast.generate())\n}\n\nasync function generateLocales() {\n  // Initialize locale data structure\n  const localeData: LocaleData = {}\n\n  // Scan all TypeScript/JavaScript files in the apps folder\n  const files = await glob([\n    \"packages/libs/3-biz-ui/src/**/*.{ts,tsx,js,jsx}\",\n    \"packages/libs/4-apps/src/**/*.{ts,tsx,js,jsx}\",\n  ])\n\n  // Parse and extract locale data\n  for (const filePath of files) {\n    const code = fs.readFileSync(filePath, \"utf-8\")\n    const ast = $(code)\n\n    // get biz-ui locales path\n    const ns = ast.find(\"const I18nNamespace = $_$0\")\n    if (ns.length >= 1) {\n      const nsVal = ns.match[0][0].value\n      bizUiNsPathMap.set(nsVal, filePath)\n    }\n\n    // check app name\n    // app name in the `useTn` call should be the same as the file path\n    let hasTn = false\n    let appFolder = \"\"\n    ast\n      .find(\"const { $$$0 } = useTn($_$0)\")\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      .each((item: any) => {\n        // $$$0 --> item.match['$$$0']\n        // $_$0 --> item.match[0][0].value\n        const appName = item.match[0][0].value\n        const appFolderPos = filePath.indexOf(`/${appName}`)\n        if (appFolderPos === -1) {\n          console.error(filePath)\n          console.error(`app name mismatch: ${appName}`)\n          return\n        } else {\n          if (filePath.indexOf(\"/3-biz-ui/\") !== -1) {\n            appFolder = appName\n          } else {\n            appFolder = filePath.slice(0, appFolderPos) + \"/\" + appName\n          }\n        }\n        hasTn = true\n      })\n    if (!hasTn) {\n      continue\n    }\n\n    // init app data\n    if (!localeData[appFolder]) {\n      localeData[appFolder] = {\n        keys: {},\n        texts: {},\n      }\n    }\n\n    // Handle `tt` calls, likes:\n    // tt('Clear Filters')\n    // tt(\"hello {{name}}\", { name: \"world\" })\n    ast\n      .find(\"tt($_$)\") // same as `find(\"tt($_$0)\")`, only match the first argument\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      .each((tnItem: any) => {\n        const text = tnItem.match[0][0].value\n        localeData[appFolder].texts[text] = text\n      })\n\n    // Handle `tk` calls, likes:\n    // tk(`panels.${props.config.category}`)\n    // tk(\"panels.instance_top\", \"Top 5 Node Utilization\")\n    // tk(\"time_range.hour\", \"{{count}} hr\", { count: 1 })\n    // tk(\"time_range.hour\", \"{{count}} hrs\", { count: 24 })\n    // tk(\"time_range.hour\", \"\", {count: n})\n    ast\n      .find(\"tk($$$0)\") // match all arguments\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      .each((tkItem: any) => {\n        const match = tkItem.match[\"$$$0\"]\n        if (match.length === 1 || match[0].value === undefined) {\n          // ignore this kind of case, likes:\n          // tk(`panels.${props.config.category}`)\n          // tk(`panels.${props.config.category}`, props.config.category)\n        } else {\n          let key = match[0].value\n          const value = match[1].value\n          if (!value) {\n            // continue\n            return\n          }\n          if (match.length === 3) {\n            // handle plural case\n            // eslint-disable-next-line @typescript-eslint/no-explicit-any\n            const options: any = {}\n            for (const option of match[2].properties) {\n              options[option.key.name] = option.value.value\n            }\n            // {count: n}  --> {count: undefined}\n            // {count: 1}  --> {count: 1}\n            // {count: 24} --> {count: 24}\n            if (options.count === 0) {\n              key = `${key}_zero`\n            } else if (options.count === 1) {\n              key = `${key}_one`\n            } else if (options.count > 1) {\n              key = `${key}_other`\n            }\n          }\n\n          // check whether value is existed\n          const existedVal = localeData[appFolder].keys[key]\n          if (existedVal !== undefined && existedVal !== value) {\n            console.error(\n              `same keys but have different values, key: ${key}, values: ${existedVal}, ${value}`,\n            )\n            // break\n            return false\n          }\n          localeData[appFolder].keys[key] = value\n        }\n      })\n  }\n\n  // Sort\n  sortLocaleData(localeData)\n\n  // Output\n  for (const appFolder of Object.keys(localeData)) {\n    if (appFolder.indexOf(\"/4-apps/\") !== -1) {\n      handleAppFiles(appFolder, localeData)\n    } else {\n      handleBizUIFiles(appFolder, localeData)\n    }\n  }\n}\n\ngenerateLocales().catch(console.error)\n"
  },
  {
    "path": "ui-v2/scripts/gogocode-test/1.js",
    "content": "// https://github.com/thx/gogocode/blob/main/docs/specification/basic.zh.md\n\nimport $ from \"gogocode\"\n\n// const source = `\n// function log(a) {\n//   console.log(a);\n// }\n\n// function alert(a) {\n//   alert(a);\n// }\n// `\n\n// const ast = $(source);\n// const fns = ast.find(`function $_$() {}`);  // length = 2\n// console.log(fns.match)  // 只返回第一个 match，其值相当于 fns[0].match\n// console.log(fns.length)\n// const names = [];\n// fns.each((fnNode) => {\n//   const fnName = fnNode.match[0][0].value;\n//   names.push(fnName);\n// }); // names = ['log', 'alert']\n// console.log(names)\n\nconst source = `\nconsole.log(a);\nconsole.log(b, c);\nconsole.log(d, e, f);\n`\n\nconst ast = $(source)\nconst logs = ast.find(`console.log($_$1, $_$2)`)\n// console.log(logs.match)\nconsole.log(logs[0].match)\nconsole.log(logs[1].match)\nconsole.log(logs.length)\n\n// const res = ast.find('console.log($$$0)');\n// console.log(res.length)\n// const params = res[2].match['$$$0'];\n// const paramNames = params.map((p) => p.name);\n// console.log(paramNames); // ['d', 'e', 'f']\n"
  },
  {
    "path": "ui-v2/scripts/gogocode-test/2.js",
    "content": "// https://github.com/thx/gogocode/blob/main/docs/specification/basic.zh.md\n\nimport $ from \"gogocode\"\nimport { inspect } from \"util\"\n\nconst source = `\nexport const I18nNamespace = \"advanced-filters\"\ntype I18nLocaleKeys =\n  | \"Advanced Filters\"\n  | \"Add Filter\"\n  | \"Filter Name\"\n  | \"WHEN\"\n  | \"AND\"\n  | \"Cancel\"\n  | \"Save\"\ntype I18nLocale = {\n  [K in I18nLocaleKeys]?: string\n}\nconst en: I18nLocale = {}\nconst zh: I18nLocale = {\n  \"Advanced Filters\": \"高级筛选\",\n  \"Add Filter\": \"添加筛选条件\",\n  \"Filter Name\": \"筛选条件名称\",\n  WHEN: \"当\",\n  AND: \"且\",\n  Cancel: \"取消\",\n  Save: \"保存\",\n}\n`\n\nconst ast = $(source)\n\nconst namespace = ast.find(`const I18nNamespace = $_$`).match[0][0].value\nconsole.log(namespace)\n\nconst res = ast.find(`const zh: I18nLocale = { $$$0 }`)\nconst kvs = res.match[\"$$$0\"]\nconsole.log(inspect(kvs))\nconsole.log(kvs.map((kv) => `${kv.key.name}:${kv.value.value}`))\n\nast.replace(`type I18nLocaleKeys = $_$`, `type I18nLocaleKeys = \"aa\" | \"bb\"`)\nconsole.log(ast.generate())\n\nast.replace(\n  `const zh: I18nLocale = { $$$0 }`,\n  `const zh: I18nLocale = { \"aa\": \"aa\", \"bb\": \"bb\" }`,\n)\nconsole.log(ast.generate())\n\n// result:\n//\n// const I18nNamespace = \"advanced-filters\"\n// type I18nLocaleKeys = \"aa\" | \"bb\"\n// type I18nLocale = {\n//   [K in I18nLocaleKeys]?: string\n// }\n// const en: I18nLocale = {}\n// const zh: I18nLocale = { \"aa\": \"aa\", \"bb\": \"bb\" }\n"
  },
  {
    "path": "util/assertutil/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage assertutil\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/assertutil/json.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.\n\npackage assertutil\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc JSONContains(t assert.TestingT, src string, contains string, msgAndArgs ...interface{}) bool {\n\tvar srcJSONMap, containedJSONMap map[string]interface{}\n\n\tif err := json.Unmarshal([]byte(src), &srcJSONMap); err != nil {\n\t\treturn assert.Fail(t, fmt.Sprintf(\"Src value ('%s') is not a valid json object string.\\nJSON parsing error: '%s'\", src, err.Error()), msgAndArgs...)\n\t}\n\n\tif err := json.Unmarshal([]byte(contains), &containedJSONMap); err != nil {\n\t\treturn assert.Fail(t, fmt.Sprintf(\"Contained value ('%s') is not a valid json object string.\\nJSON parsing error: '%s'\", contains, err.Error()), msgAndArgs...)\n\t}\n\n\tfor key, value := range containedJSONMap {\n\t\tsrcValue, ok := srcJSONMap[key]\n\t\tif !ok || !assert.ObjectsAreEqual(srcValue, value) {\n\t\t\treturn assert.Fail(t, fmt.Sprintf(\"Src ('%s') does not contain '%s'\", src, contains), msgAndArgs...)\n\t\t}\n\t}\n\n\treturn true\n}\n\nfunc RequireJSONContains(t require.TestingT, src string, contains string, msgAndArgs ...interface{}) {\n\tif JSONContains(t, src, contains, msgAndArgs...) {\n\t\treturn\n\t}\n\tt.FailNow()\n}\n"
  },
  {
    "path": "util/assertutil/json_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage assertutil\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestJSONContains(t *testing.T) {\n\tmockT := new(mockTestingT)\n\trequire.False(t, JSONContains(mockT, `null`, `{\"a\":1}`))\n\trequire.Contains(t, mockT.errorString(), `Src ('null') does not contain '{\"a\":1}'`)\n\n\tmockT = new(mockTestingT)\n\trequire.True(t, JSONContains(mockT, `null`, `null`))\n\n\tmockT = new(mockTestingT)\n\trequire.True(t, JSONContains(mockT, `null`, `{}`))\n\n\tmockT = new(mockTestingT)\n\trequire.True(t, JSONContains(mockT, `{\"a\":1}`, `{\"a\":1}`))\n\n\tmockT = new(mockTestingT)\n\trequire.True(t, JSONContains(mockT, `{\"a\":1}`, `null`))\n\n\tmockT = new(mockTestingT)\n\trequire.False(t, JSONContains(mockT, `nullx`, `{\"a\":1}`))\n\trequire.Contains(t, mockT.errorString(), `Src value ('nullx') is not a valid json object string`)\n\n\tmockT = new(mockTestingT)\n\trequire.False(t, JSONContains(mockT, `{\"a\":1}`, `nullx`))\n\trequire.Contains(t, mockT.errorString(), `Contained value ('nullx') is not a valid json object string`)\n\n\tmockT = new(mockTestingT)\n\trequire.False(t, JSONContains(mockT, `{\"b\":1}`, `{\"a\":1}`))\n\trequire.Contains(t, mockT.errorString(), `Src ('{\"b\":1}') does not contain '{\"a\":1}'`)\n\n\tmockT = new(mockTestingT)\n\trequire.False(t, JSONContains(mockT, `{\"a\":1}`, `{\"b\":1}`))\n\trequire.Contains(t, mockT.errorString(), `Src ('{\"a\":1}') does not contain '{\"b\":1}'`)\n\n\tmockT = new(mockTestingT)\n\trequire.True(t, JSONContains(mockT, `{\"a\":1,\"b\":2}`, `{\"a\":1}`))\n\n\tmockT = new(mockTestingT)\n\trequire.True(t, JSONContains(mockT, `{\"b\":2,\"a\":1}`, `{\"a\":1}`))\n\n\tmockT = new(mockTestingT)\n\trequire.True(t, JSONContains(mockT, `{\"a\":1,\"b\":2}`, `{\"b\":2,\"a\":1}`))\n\n\tmockT = new(mockTestingT)\n\trequire.False(t, JSONContains(mockT, `{\"a\":1}`, `{\"a\":1,\"b\":2}`))\n\trequire.Contains(t, mockT.errorString(), `Src ('{\"a\":1}') does not contain '{\"a\":1,\"b\":2}'`)\n\n\tmockT = new(mockTestingT)\n\trequire.False(t, JSONContains(mockT, `{\"a\":2,\"b\":2}`, `{\"a\":1}`))\n\trequire.Contains(t, mockT.errorString(), `Src ('{\"a\":2,\"b\":2}') does not contain '{\"a\":1}'`)\n\n\tmockT = new(mockTestingT)\n\trequire.False(t, JSONContains(mockT, `{\"a\":1}`, `{\"A\":1}`))\n\trequire.Contains(t, mockT.errorString(), `Src ('{\"a\":1}') does not contain '{\"A\":1}'`)\n\n\tmockT = new(mockTestingT)\n\trequire.True(t, JSONContains(mockT, `{\"a\":{\"foo\":\"bar\"},\"b\":2}`, `{\"a\":{\"foo\":\"bar\"}}`))\n\n\tmockT = new(mockTestingT)\n\trequire.False(t, JSONContains(mockT, `{\"a\":{\"foo\":\"bar\"},\"b\":2}`, `{\"a\":1}`))\n\trequire.Contains(t, mockT.errorString(), `Src ('{\"a\":{\"foo\":\"bar\"},\"b\":2}') does not contain '{\"a\":1}'`)\n\n\tmockT = new(mockTestingT)\n\trequire.False(t, JSONContains(mockT, `{\"a\":{\"foo\":\"bar\"},\"b\":2}`, `{\"a\":{\"foo\":\"box\"}}`))\n\trequire.Contains(t, mockT.errorString(), `Src ('{\"a\":{\"foo\":\"bar\"},\"b\":2}') does not contain '{\"a\":{\"foo\":\"box\"}}'`)\n\n\tmockT = new(mockTestingT)\n\trequire.False(t, JSONContains(mockT, `{\"a\":{\"foo\":\"bar\"},\"b\":2}`, `{\"a\":{\"FOO\":\"bar\"}}`))\n\trequire.Contains(t, mockT.errorString(), `Src ('{\"a\":{\"foo\":\"bar\"},\"b\":2}') does not contain '{\"a\":{\"FOO\":\"bar\"}}'`)\n}\n"
  },
  {
    "path": "util/assertutil/mock.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Copyright (c) 2012-2020 Mat Ryer, Tyler Bunnell and contributors.\n\npackage assertutil\n\nimport \"fmt\"\n\ntype mockTestingT struct {\n\terrorFmt string\n\targs     []interface{}\n}\n\nfunc (m *mockTestingT) errorString() string {\n\treturn fmt.Sprintf(m.errorFmt, m.args...)\n}\n\nfunc (m *mockTestingT) Errorf(format string, args ...interface{}) {\n\tm.errorFmt = format\n\tm.args = args\n}\n"
  },
  {
    "path": "util/client/httpclient/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage httpclient\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/client/httpclient/client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.\n// resty source code and usage is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage httpclient\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"net\"\n\t\"net/http\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/joomcode/errorx\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/nocopy\"\n)\n\nvar (\n\tErrNS            = errorx.NewNamespace(\"http_client\")\n\tErrRequestFailed = ErrNS.NewType(\"request_failed\")\n)\n\n// Client caches connections for future re-use and should be reused instead of\n// created as needed.\ntype Client struct {\n\tnocopy.NoCopy\n\n\tkindTag        string\n\ttransport      http.RoundTripper\n\tdefaultCtx     context.Context\n\tdefaultBaseURL string\n}\n\nfunc newTransport(tlsConfig *tls.Config) *http.Transport {\n\tdialer := &net.Dialer{\n\t\tTimeout:   30 * time.Second,\n\t\tKeepAlive: 30 * time.Second,\n\t}\n\treturn &http.Transport{\n\t\tProxy:                 http.ProxyFromEnvironment,\n\t\tDialContext:           dialer.DialContext,\n\t\tForceAttemptHTTP2:     true,\n\t\tMaxIdleConns:          100,\n\t\tIdleConnTimeout:       90 * time.Second,\n\t\tTLSHandshakeTimeout:   10 * time.Second,\n\t\tExpectContinueTimeout: 1 * time.Second,\n\t\tMaxIdleConnsPerHost:   runtime.GOMAXPROCS(0) + 1,\n\t\tTLSClientConfig:       tlsConfig,\n\t}\n}\n\nfunc New(config Config) *Client {\n\treturn &Client{\n\t\tkindTag:        config.KindTag,\n\t\ttransport:      newTransport(config.TLSConfig),\n\t\tdefaultCtx:     config.DefaultCtx,\n\t\tdefaultBaseURL: config.DefaultBaseURL,\n\t}\n}\n\n// Clone creates a new client with the same configuration. Subsequent SetXxx() calls will not\n// affect the current client. The transport will be shared unless it is changed to something else later.\nfunc (c *Client) Clone() *Client {\n\treturn &Client{\n\t\tkindTag:        c.kindTag,\n\t\ttransport:      c.transport,\n\t\tdefaultCtx:     c.defaultCtx,\n\t\tdefaultBaseURL: c.defaultBaseURL,\n\t}\n}\n\n// SetDefaultTransport sets the default HTTP transport for subsequent new requests.\n// This function should be used only when you want to mock the request.\n// In other cases, there is usually no need to use a customized HTTP transport.\nfunc (c *Client) SetDefaultTransport(transport http.RoundTripper) *Client {\n\tc.transport = transport\n\treturn c\n}\n\n// SetDefaultCtx sets the default context for subsequent new requests.\nfunc (c *Client) SetDefaultCtx(ctx context.Context) *Client {\n\tc.defaultCtx = ctx\n\treturn c\n}\n\n// SetDefaultBaseURL sets the default base URL for subsequent new requests.\nfunc (c *Client) SetDefaultBaseURL(baseURL string) *Client {\n\tc.defaultBaseURL = baseURL\n\treturn c\n}\n\nfunc (c *Client) LR() *LazyRequest {\n\tlReq := newRequest(c.kindTag, c.transport)\n\tif c.defaultCtx != nil {\n\t\tlReq.SetContext(c.defaultCtx)\n\t}\n\tif len(c.defaultBaseURL) > 0 {\n\t\tlReq.SetTLSAwareBaseURL(c.defaultBaseURL)\n\t}\n\treturn lReq\n}\n"
  },
  {
    "path": "util/client/httpclient/config.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage httpclient\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n)\n\ntype Config struct {\n\tKindTag        string\n\tTLSConfig      *tls.Config\n\tDefaultCtx     context.Context\n\tDefaultBaseURL string\n}\n"
  },
  {
    "path": "util/client/httpclient/info.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage httpclient\n\nimport (\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n)\n\n// execInfo is a copy of necessary information during the execution.\n// It can be used to print logs when something happens.\ntype execInfo struct {\n\tkindTag    string\n\treqURL     string\n\treqMethod  string\n\trespStatus string\n\trespBody   string\n}\n\nfunc (e *execInfo) Warn(msg string, err error) {\n\tfields := []zap.Field{\n\t\tzap.String(\"kindTag\", e.kindTag),\n\t\tzap.String(\"url\", e.reqURL),\n\t\tzap.String(\"method\", e.reqMethod),\n\t}\n\tif e.respStatus != \"\" {\n\t\tfields = append(fields, zap.String(\"responseStatus\", e.respStatus))\n\t}\n\tif e.respBody != \"\" {\n\t\tfields = append(fields, zap.String(\"responseBody\", e.respBody))\n\t}\n\tfields = append(fields, zap.Error(err))\n\tlog.Warn(msg, fields...)\n}\n"
  },
  {
    "path": "util/client/httpclient/request.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.\n// resty source code and usage is governed by a MIT style\n// license that can be found in the LICENSE file.\n\npackage httpclient\n\nimport (\n\t\"context\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-resty/resty/v2\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/nocopy\"\n)\n\n// LazyRequest can be used to compose and fire individual request from the client.\n// The request will not be actually sent until reading from LazyResponse.\ntype LazyRequest struct {\n\t// Note: this is a lazy struct.\n\tnocopy.NoCopy\n\n\tkindTag   string\n\tdebugTag  string\n\ttransport http.RoundTripper\n\topsR      []requestUpdateFn\n\topsC      []clientUpdateFn\n}\n\nfunc newRequest(kindTag string, transport http.RoundTripper) *LazyRequest {\n\treturn &LazyRequest{\n\t\tkindTag:   kindTag,\n\t\ttransport: transport,\n\t}\n}\n\n// Clone creates a new request with all settings cloned.\nfunc (lReq *LazyRequest) Clone() *LazyRequest {\n\tlReqCloned := &LazyRequest{\n\t\tkindTag:   lReq.kindTag,\n\t\tdebugTag:  lReq.debugTag,\n\t\ttransport: lReq.transport, // transport will never change after creation, so this is concurrent-safe\n\t\topsR:      make([]requestUpdateFn, len(lReq.opsR)),\n\t\topsC:      make([]clientUpdateFn, len(lReq.opsC)),\n\t}\n\tcopy(lReqCloned.opsR, lReq.opsR)\n\tcopy(lReqCloned.opsC, lReq.opsC)\n\treturn lReqCloned\n}\n\n// SetDebugTag enables the debugging log if tag is not empty, or disables it otherwise.\n// The debugging log will be printed with log level INFO.\nfunc (lReq *LazyRequest) SetDebugTag(debugTag string) *LazyRequest {\n\tlReq.debugTag = debugTag\n\treturn lReq\n}\n\n// SetContext sets the context.Context for current request. It allows\n// to interrupt the request execution if ctx.Done() channel is closed.\n// See https://blog.golang.org/context article and the \"context\" package\n// documentation.\nfunc (lReq *LazyRequest) SetContext(ctx context.Context) *LazyRequest {\n\tif ctx == nil {\n\t\treturn lReq\n\t}\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetContext(ctx)\n\t})\n\treturn lReq\n}\n\n// SetTimeout sets the total timeout for sending the request and reading the response.\nfunc (lReq *LazyRequest) SetTimeout(timeout time.Duration) *LazyRequest {\n\tlReq.opsC = append(lReq.opsC, func(c *resty.Client) {\n\t\tc.SetTimeout(timeout)\n\t})\n\treturn lReq\n}\n\n// SetURL sets the URL for current request. This URL will be used when calling Send().\n//\n//\t \tresp := client.LR().\n//\t \t\tSetMethod(\"GET\").\n//\t\t\t\tSetURL(\"http://httpbin.org/get\").\n//\t\t\t\tSend()\nfunc (lReq *LazyRequest) SetURL(url string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.URL = url\n\t})\n\treturn lReq\n}\n\n// SetMethod sets the method of the request. This method will be used when calling Send().\n//\n//\t \tresp := client.LR().\n//\t \t\tSetMethod(\"GET\").\n//\t\t\t\tSetURL(\"http://httpbin.org/get\").\n//\t\t\t\tSend()\nfunc (lReq *LazyRequest) SetMethod(method string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.Method = method\n\t})\n\treturn lReq\n}\n\nfunc isMTLSConfigured(r http.RoundTripper) bool {\n\ttransport, ok := r.(*http.Transport)\n\tif !ok {\n\t\treturn false\n\t}\n\tif transport.TLSClientConfig == nil {\n\t\treturn false\n\t}\n\tif len(transport.TLSClientConfig.Certificates) > 0 || transport.TLSClientConfig.RootCAs != nil {\n\t\treturn true\n\t}\n\t// It may be possible that transport.TLSClientConfig is &tls.Config{}. In this case\n\t// we still treat it as mTLS not configured.\n\treturn false\n}\n\n// SetTLSAwareBaseURL sets the base URL for current request. Relative URLs will be based on this base URL.\n// If the client is built with TLS certs, http scheme will be changed to https automatically.\n//\n//\tresp := client.LR().\n//\t\tSetTLSAwareBaseURL(\"http://myjeeva.com\").\n//\t\tGet(\"/foo\")\nfunc (lReq *LazyRequest) SetTLSAwareBaseURL(baseURL string) *LazyRequest {\n\t// Rewrite http URL to https if TLS certificate is specified.\n\tif isMTLSConfigured(lReq.transport) && strings.HasPrefix(baseURL, \"http://\") {\n\t\tbaseURL = \"https://\" + baseURL[len(\"http://\"):]\n\t}\n\tlReq.opsC = append(lReq.opsC, func(c *resty.Client) {\n\t\tc.SetHostURL(baseURL)\n\t})\n\treturn lReq\n}\n\n// Send method lazily send the HTTP request using the method and URL already defined\n// for current LazyRequest.\n//\n//\t \tresp := client.LR().\n//\t \t\tSetMethod(\"GET\").\n//\t\t\t\tSetURL(\"http://httpbin.org/get\").\n//\t\t\t\tSend()\nfunc (lReq *LazyRequest) Send() *LazyResponse {\n\treturn newResponse(lReq.Clone())\n}\n\n// Execute lazily sends the HTTP request with given HTTP method and URL\n// for current LazyRequest.\n//\n//\tresp := client.LR().\n//\t\tExecute(\"GET\", \"http://httpbin.org/get\")\nfunc (lReq *LazyRequest) Execute(method, url string) *LazyResponse {\n\tcloned := lReq.Clone()\n\tcloned.opsR = append(cloned.opsR, func(r *resty.Request) {\n\t\tr.Method = method\n\t\tr.URL = url\n\t})\n\treturn newResponse(cloned)\n}\n\n// Get lazily sends a GET request with the specified URL for current LazyRequest.\nfunc (lReq *LazyRequest) Get(url string) *LazyResponse {\n\treturn lReq.Execute(resty.MethodGet, url)\n}\n\n// Post lazily sends a POST request with the specified URL for current LazyRequest.\nfunc (lReq *LazyRequest) Post(url string) *LazyResponse {\n\treturn lReq.Execute(resty.MethodPost, url)\n}\n\n// Put lazily sends a PUT request with the specified URL for current LazyRequest.\nfunc (lReq *LazyRequest) Put(url string) *LazyResponse {\n\treturn lReq.Execute(resty.MethodPut, url)\n}\n\n// Delete lazily sends a DELETE request with the specified URL for current LazyRequest.\nfunc (lReq *LazyRequest) Delete(url string) *LazyResponse {\n\treturn lReq.Execute(resty.MethodDelete, url)\n}\n"
  },
  {
    "path": "util/client/httpclient/request_resty.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Copyright (c) 2015-2021 Jeevanandam M (jeeva@myjeeva.com), All rights reserved.\n// resty source code and usage is governed by a MIT style\n// license that can be found in the LICENSE file.\n\n// This file only contains encapsulated functions implemented over resty.Request\n\npackage httpclient\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/go-resty/resty/v2\"\n)\n\n// SetHeader method is to set a single header field and its value in the current request.\n//\n// For Example: To set `Content-Type` and `Accept` as `application/json`.\n//\n//\tclient.LR().\n//\t\tSetHeader(\"Content-Type\", \"application/json\").\n//\t\tSetHeader(\"Accept\", \"application/json\")\n//\n// Also you can override header value, which was set at client instance level.\nfunc (lReq *LazyRequest) SetHeader(header, value string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetHeader(header, value)\n\t})\n\treturn lReq\n}\n\n// SetHeaders method sets multiple headers field and its values at one go in the current request.\n//\n// For Example: To set `Content-Type` and `Accept` as `application/json`\n//\n//\tclient.LR().\n//\t\tSetHeaders(map[string]string{\n//\t\t\t\"Content-Type\": \"application/json\",\n//\t\t\t\"Accept\": \"application/json\",\n//\t\t})\n//\n// Also you can override header value, which was set at client instance level.\nfunc (lReq *LazyRequest) SetHeaders(headers map[string]string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetHeaders(headers)\n\t})\n\treturn lReq\n}\n\n// SetHeaderVerbatim method is to set a single header field and its value verbatim in the current request.\n//\n// For Example: To set `all_lowercase` and `UPPERCASE` as `available`.\n//\n//\tclient.LR().\n//\t\tSetHeaderVerbatim(\"all_lowercase\", \"available\").\n//\t\tSetHeaderVerbatim(\"UPPERCASE\", \"available\")\n//\n// Also you can override header value, which was set at client instance level.\nfunc (lReq *LazyRequest) SetHeaderVerbatim(header, value string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetHeaderVerbatim(header, value)\n\t})\n\treturn lReq\n}\n\n// SetQueryParam method sets single parameter and its value in the current request.\n// It will be formed as query string for the request.\n//\n// For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.\n//\n//\tclient.LR().\n//\t\tSetQueryParam(\"search\", \"kitchen papers\").\n//\t\tSetQueryParam(\"size\", \"large\")\n//\n// Also you can override query params value, which was set at client instance level.\nfunc (lReq *LazyRequest) SetQueryParam(param, value string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetQueryParam(param, value)\n\t})\n\treturn lReq\n}\n\n// SetQueryParams method sets multiple parameters and its values at one go in the current request.\n// It will be formed as query string for the request.\n//\n// For Example: `search=kitchen%20papers&size=large` in the URL after `?` mark.\n//\n//\tclient.LR().\n//\t\tSetQueryParams(map[string]string{\n//\t\t\t\"search\": \"kitchen papers\",\n//\t\t\t\"size\": \"large\",\n//\t\t})\n//\n// Also you can override query params value, which was set at client instance level.\nfunc (lReq *LazyRequest) SetQueryParams(params map[string]string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetQueryParams(params)\n\t})\n\treturn lReq\n}\n\n// SetQueryParamsFromValues method appends multiple parameters with multi-value\n// (`url.Values`) at one go in the current request. It will be formed as\n// query string for the request.\n//\n// For Example: `status=pending&status=approved&status=open` in the URL after `?` mark.\n//\n//\tclient.LR().\n//\t\tSetQueryParamsFromValues(url.Values{\n//\t\t\t\"status\": []string{\"pending\", \"approved\", \"open\"},\n//\t\t})\n//\n// Also you can override query params value, which was set at client instance level.\nfunc (lReq *LazyRequest) SetQueryParamsFromValues(params url.Values) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetQueryParamsFromValues(params)\n\t})\n\treturn lReq\n}\n\n// SetQueryString method provides ability to use string as an input to set URL query string for the request.\n//\n// Using String as an input\n//\n//\tclient.LR().\n//\t\tSetQueryString(\"productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more\")\nfunc (lReq *LazyRequest) SetQueryString(query string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetQueryString(query)\n\t})\n\treturn lReq\n}\n\n// SetFormData method sets Form parameters and their values in the current request.\n// It's applicable only HTTP method `POST` and `PUT` and requests content type would be set as\n// `application/x-www-form-urlencoded`.\n//\n//\tclient.LR().\n//\t\tSetFormData(map[string]string{\n//\t\t\t\"access_token\": \"BC594900-518B-4F7E-AC75-BD37F019E08F\",\n//\t\t\t\"user_id\": \"3455454545\",\n//\t\t})\n//\n// Also you can override form data value, which was set at client instance level.\nfunc (lReq *LazyRequest) SetFormData(data map[string]string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetFormData(data)\n\t})\n\treturn lReq\n}\n\n// SetFormDataFromValues method appends multiple form parameters with multi-value\n// (`url.Values`) at one go in the current request.\n//\n//\tclient.LR().\n//\t\tSetFormDataFromValues(url.Values{\n//\t\t\t\"search_criteria\": []string{\"book\", \"glass\", \"pencil\"},\n//\t\t})\n//\n// Also you can override form data value, which was set at client instance level.\nfunc (lReq *LazyRequest) SetFormDataFromValues(data url.Values) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetFormDataFromValues(data)\n\t})\n\treturn lReq\n}\n\n// SetBody method sets the request body for the request. It supports various realtime needs as easy.\n// We can say its quite handy or powerful. Supported request body data types is `string`,\n// `[]byte`, `struct`, `map`, `slice` and `io.Reader`. Body value can be pointer or non-pointer.\n// Automatic marshalling for JSON and XML content type, if it is `struct`, `map`, or `slice`.\n//\n// Note: `io.Reader` is processed as bufferless mode while sending request.\n//\n// For Example: Struct as a body input, based on content type, it will be marshalled.\n//\n//\tclient.LR().\n//\t\tSetBody(User{\n//\t\t\tUsername: \"jeeva@myjeeva.com\",\n//\t\t\tPassword: \"welcome2resty\",\n//\t\t})\n//\n// Map as a body input, based on content type, it will be marshalled.\n//\n//\tclient.LR().\n//\t\tSetBody(map[string]interface{}{\n//\t\t\t\"username\": \"jeeva@myjeeva.com\",\n//\t\t\t\"password\": \"welcome2resty\",\n//\t\t\t\"address\": &Address{\n//\t\t\t\tAddress1: \"1111 This is my street\",\n//\t\t\t\tAddress2: \"Apt 201\",\n//\t\t\t\tCity: \"My City\",\n//\t\t\t\tState: \"My State\",\n//\t\t\t\tZipCode: 00000,\n//\t\t\t},\n//\t\t})\n//\n// String as a body input. Suitable for any need as a string input.\n//\n//\tclient.LR().\n//\t\tSetBody(`{\n//\t\t\t\"username\": \"jeeva@getrightcare.com\",\n//\t\t\t\"password\": \"admin\"\n//\t\t}`)\n//\n// []byte as a body input. Suitable for raw request such as file upload, serialize & deserialize, etc.\n//\n//\tclient.LR().\n//\t\tSetBody([]byte(\"This is my raw request, sent as-is\"))\nfunc (lReq *LazyRequest) SetBody(body interface{}) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetBody(body)\n\t})\n\treturn lReq\n}\n\n// Deprecated: This usage is intentionally not supported.\nfunc (lReq *LazyRequest) SetResult() {\n\tpanic(\"do not use this in LazyRequest\")\n}\n\n// Deprecated: This usage is intentionally not supported.\nfunc (lReq *LazyRequest) SetError() {\n\tpanic(\"do not use this in LazyRequest\")\n}\n\n// SetFile method is to set single file field name and its path for multipart upload.\n//\n//\tclient.LR().\n//\t\tSetFile(\"my_file\", \"/Users/jeeva/Gas Bill - Sep.pdf\")\nfunc (lReq *LazyRequest) SetFile(param, filePath string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetFile(param, filePath)\n\t})\n\treturn lReq\n}\n\n// SetFiles method is to set multiple file field name and its path for multipart upload.\n//\n//\tclient.LR().\n//\t\tSetFiles(map[string]string{\n//\t\t\t\t\"my_file1\": \"/Users/jeeva/Gas Bill - Sep.pdf\",\n//\t\t\t\t\"my_file2\": \"/Users/jeeva/Electricity Bill - Sep.pdf\",\n//\t\t\t\t\"my_file3\": \"/Users/jeeva/Water Bill - Sep.pdf\",\n//\t\t\t})\nfunc (lReq *LazyRequest) SetFiles(files map[string]string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetFiles(files)\n\t})\n\treturn lReq\n}\n\n// SetFileReader method is to set single file using io.Reader for multipart upload.\n//\n//\tclient.LR().\n//\t\tSetFileReader(\"profile_img\", \"my-profile-img.png\", bytes.NewReader(profileImgBytes)).\n//\t\tSetFileReader(\"notes\", \"user-notes.txt\", bytes.NewReader(notesBytes))\nfunc (lReq *LazyRequest) SetFileReader(param, fileName string, reader io.Reader) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetFileReader(param, fileName, reader)\n\t})\n\treturn lReq\n}\n\n// SetMultipartFormData method allows simple form data to be attached to the request as `multipart:form-data`.\nfunc (lReq *LazyRequest) SetMultipartFormData(data map[string]string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetMultipartFormData(data)\n\t})\n\treturn lReq\n}\n\n// SetMultipartField method is to set custom data using io.Reader for multipart upload.\nfunc (lReq *LazyRequest) SetMultipartField(param, fileName, contentType string, reader io.Reader) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetMultipartField(param, fileName, contentType, reader)\n\t})\n\treturn lReq\n}\n\n// SetMultipartFields method is to set multiple data fields using io.Reader for multipart upload.\n//\n// For Example:\n//\n//\tclient.LR().SetMultipartFields(\n//\t\t&resty.MultipartField{\n//\t\t\tParam:       \"uploadManifest1\",\n//\t\t\tFileName:    \"upload-file-1.json\",\n//\t\t\tContentType: \"application/json\",\n//\t\t\tReader:      strings.NewReader(`{\"input\": {\"name\": \"Uploaded document 1\", \"_filename\" : [\"file1.txt\"]}}`),\n//\t\t},\n//\t\t&resty.MultipartField{\n//\t\t\tParam:       \"uploadManifest2\",\n//\t\t\tFileName:    \"upload-file-2.json\",\n//\t\t\tContentType: \"application/json\",\n//\t\t\tReader:      strings.NewReader(`{\"input\": {\"name\": \"Uploaded document 2\", \"_filename\" : [\"file2.txt\"]}}`),\n//\t\t})\n//\n// If you have slice already, then simply call-\n//\n//\tclient.LR().SetMultipartFields(fields...)\nfunc (lReq *LazyRequest) SetMultipartFields(fields ...*resty.MultipartField) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetMultipartFields(fields...)\n\t})\n\treturn lReq\n}\n\n// SetContentLength method sets the HTTP header `Content-Length` value for current request.\n// By default Resty won't set `Content-Length`. Also you have an option to enable for every\n// request.\nfunc (lReq *LazyRequest) SetContentLength(l bool) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetContentLength(l)\n\t})\n\treturn lReq\n}\n\n// SetBasicAuth method sets the basic authentication header in the current HTTP request.\n//\n// For Example:\n//\n//\tAuthorization: Basic <base64-encoded-value>\n//\n// To set the header for username \"go-resty\" and password \"welcome\"\n//\n//\tclient.LR().SetBasicAuth(\"go-resty\", \"welcome\")\n//\n// This method overrides the credentials set by method `Client.SetBasicAuth`.\nfunc (lReq *LazyRequest) SetBasicAuth(username, password string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetBasicAuth(username, password)\n\t})\n\treturn lReq\n}\n\n// SetAuthToken method sets the auth token header(Default Scheme: Bearer) in the current HTTP request. Header example:\n//\n//\tAuthorization: Bearer <auth-token-value-comes-here>\n//\n// For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F\n//\n//\tclient.LR().SetAuthToken(\"BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F\")\n//\n// This method overrides the Auth token set by method `Client.SetAuthToken`.\nfunc (lReq *LazyRequest) SetAuthToken(token string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetAuthToken(token)\n\t})\n\treturn lReq\n}\n\n// SetAuthScheme method sets the auth token scheme type in the HTTP request. For Example:\n//\n//\tAuthorization: <auth-scheme-value-set-here> <auth-token-value>\n//\n// For Example: To set the scheme to use OAuth\n//\n//\tclient.LR().SetAuthScheme(\"OAuth\")\n//\n// This auth header scheme gets added to all the request rasied from this client instance.\n// Also it can be overridden or set one at the request level is supported.\n//\n// Information about Auth schemes can be found in RFC7235 which is linked to below along with the page containing\n// the currently defined official authentication schemes:\n//\n//\thttps://tools.ietf.org/html/rfc7235\n//\thttps://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes\n//\n// This method overrides the Authorization scheme set by method `Client.SetAuthScheme`.\nfunc (lReq *LazyRequest) SetAuthScheme(scheme string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetAuthScheme(scheme)\n\t})\n\treturn lReq\n}\n\n// Deprecated: This usage is intentionally not supported.\nfunc (lReq *LazyRequest) SetOutput() {\n\tpanic(\"do not use this in LazyRequest\")\n}\n\n// SetSRV method sets the details to query the service SRV record and execute the\n// request.\n//\n//\tclient.LR().\n//\t\tSetSRV(SRVRecord{\"web\", \"testservice.com\"}).\n//\t\tGet(\"/get\")\nfunc (lReq *LazyRequest) SetSRV(srv *resty.SRVRecord) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetSRV(srv)\n\t})\n\treturn lReq\n}\n\n// Deprecated: This usage is intentionally not supported.\nfunc (lReq *LazyRequest) SetDoNotParseResponse() {\n\tpanic(\"do not use this in LazyRequest\")\n}\n\n// SetPathParam method sets single URL path key-value pair in the\n// Resty current request instance.\n//\n//\tclient.LR().SetPathParam(\"userId\", \"sample@sample.com\")\n//\n//\tResult:\n//\t   URL - /v1/users/{userId}/details\n//\t   Composed URL - /v1/users/sample@sample.com/details\n//\n// It replaces the value of the key while composing the request URL. Also you can\n// override Path Params value, which was set at client instance level.\nfunc (lReq *LazyRequest) SetPathParam(param, value string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetPathParam(param, value)\n\t})\n\treturn lReq\n}\n\n// SetPathParams method sets multiple URL path key-value pairs at one go in the\n// Resty current request instance.\n//\n//\tclient.LR().SetPathParams(map[string]string{\n//\t   \"userId\": \"sample@sample.com\",\n//\t   \"subAccountId\": \"100002\",\n//\t})\n//\n//\tResult:\n//\t   URL - /v1/users/{userId}/{subAccountId}/details\n//\t   Composed URL - /v1/users/sample@sample.com/100002/details\n//\n// It replaces the value of the key while composing request URL. Also you can\n// override Path Params value, which was set at client instance level.\nfunc (lReq *LazyRequest) SetPathParams(params map[string]string) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetPathParams(params)\n\t})\n\treturn lReq\n}\n\n// Deprecated: This usage is intentionally not supported.\nfunc (lReq *LazyRequest) ExpectContentType() {\n\tpanic(\"do not use this in LazyRequest\")\n}\n\n// Deprecated: This usage is intentionally not supported.\nfunc (lReq *LazyRequest) ForceContentType() {\n\tpanic(\"do not use this in LazyRequest\")\n}\n\n// SetJSONEscapeHTML method is to enable/disable the HTML escape on JSON marshal.\n//\n// Note: This option only applicable to standard JSON Marshaller.\nfunc (lReq *LazyRequest) SetJSONEscapeHTML(b bool) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetJSONEscapeHTML(b)\n\t})\n\treturn lReq\n}\n\n// SetCookie method appends a single cookie in the current request instance.\n//\n//\tclient.LR().SetCookie(&http.Cookie{\n//\t\tName:\"go-resty\",\n//\t\tValue:\"This is cookie value\",\n//\t})\n//\n// Note: Method appends the Cookie value into existing Cookie if already existing.\nfunc (lReq *LazyRequest) SetCookie(hc *http.Cookie) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetCookie(hc)\n\t})\n\treturn lReq\n}\n\n// SetCookies method sets an array of cookies in the current request instance.\n//\n//\tcookies := []*http.Cookie{\n//\t\t&http.Cookie{\n//\t\t\tName:\"go-resty-1\",\n//\t\t\tValue:\"This is cookie 1 value\",\n//\t\t},\n//\t\t&http.Cookie{\n//\t\t\tName:\"go-resty-2\",\n//\t\t\tValue:\"This is cookie 2 value\",\n//\t\t},\n//\t}\n//\n//\t// Setting a cookies into resty's current request\n//\tclient.LR().SetCookies(cookies)\n//\n// Note: Method appends the Cookie value into existing Cookie if already existing.\nfunc (lReq *LazyRequest) SetCookies(rs []*http.Cookie) *LazyRequest {\n\tlReq.opsR = append(lReq.opsR, func(r *resty.Request) {\n\t\tr.SetCookies(rs)\n\t})\n\treturn lReq\n}\n"
  },
  {
    "path": "util/client/httpclient/response.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage httpclient\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"runtime\"\n\t\"runtime/debug\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/go-resty/resty/v2\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/israce\"\n\t\"github.com/pingcap/tidb-dashboard/util/nocopy\"\n)\n\nconst (\n\tdefaultTimeout = time.Minute * 5 // Just a default long enough timeout.\n)\n\ntype requestUpdateFn func(r *resty.Request)\n\ntype clientUpdateFn func(c *resty.Client)\n\n// LazyResponse provides access to the response body and response headers in convenient ways.\n// No request is actually sent until LazyResponse is read.\ntype LazyResponse struct {\n\tnocopy.NoCopy\n\n\t// The source request object to execute. It is a clone of the original request object\n\t// to allow concurrent executions.\n\trequestSnapshot *LazyRequest\n\n\t// stackAtNew is the stack when Response is created. It is only available when Golang race mode is enabled.\n\t// This is used to report the missing `Close()` calls.\n\tstackAtNew []byte\n\n\t// Fields below are set only after the request is actually sent.\n\tisExecuted                  bool\n\texecutedResponseWithoutBody *http.Response\n\texecutedResponseBody        io.ReadCloser\n\texecutedError               error\n\texecuteInfo                 *execInfo // Contains some execution information. Will be logged when error happens.\n}\n\nfunc newResponse(sourceSnapshot *LazyRequest) *LazyResponse {\n\ter := &LazyResponse{\n\t\trequestSnapshot: sourceSnapshot,\n\t}\n\truntime.SetFinalizer(er, (*LazyResponse).finalize)\n\tif israce.Enabled {\n\t\ter.stackAtNew = debug.Stack()\n\t}\n\treturn er\n}\n\nfunc (lResp *LazyResponse) doExecutionOnce() {\n\tif lResp.isExecuted {\n\t\treturn\n\t}\n\n\tclient := resty.NewWithClient(&http.Client{\n\t\tTransport: lResp.requestSnapshot.transport,\n\t})\n\tclient.SetRedirectPolicy(resty.FlexibleRedirectPolicy(10))\n\tclient.SetTimeout(defaultTimeout)\n\tif lResp.requestSnapshot.debugTag != \"\" {\n\t\tclient.SetPreRequestHook(func(_ *resty.Client, rr *http.Request) error {\n\t\t\tlog.Info(\"Send request\",\n\t\t\t\tzap.String(\"kindTag\", lResp.requestSnapshot.kindTag),\n\t\t\t\tzap.String(\"debugTag\", lResp.requestSnapshot.debugTag),\n\t\t\t\tzap.String(\"method\", rr.Method),\n\t\t\t\tzap.String(\"url\", rr.URL.String()))\n\t\t\treturn nil\n\t\t})\n\t}\n\n\tfor _, op := range lResp.requestSnapshot.opsC {\n\t\top(client)\n\t}\n\trestyReq := client.R()\n\trestyReq.SetDoNotParseResponse(true)\n\tfor _, op := range lResp.requestSnapshot.opsR {\n\t\top(restyReq)\n\t}\n\n\tinfo := &execInfo{kindTag: lResp.requestSnapshot.kindTag}\n\tinfo.reqURL = restyReq.URL\n\tinfo.reqMethod = restyReq.Method\n\n\trestyResp, err := restyReq.Send()\n\tif err != nil {\n\t\t// Turn all errors into ErrRequestFailed.\n\t\terr = ErrRequestFailed.WrapWithNoMessage(err)\n\t}\n\n\tif (restyResp == nil || restyResp.RawResponse == nil) && err == nil {\n\t\t// Response and error come from 3rd-party libraries, we are not sure about this.\n\t\t// Let's try out best to catch it.\n\t\terr = ErrRequestFailed.New(\"%s %s (%s): internal error, no response\",\n\t\t\trestyResp.Request.Method,\n\t\t\trestyResp.Request.URL,\n\t\t\tlResp.requestSnapshot.kindTag)\n\t\trestyResp = nil\n\t}\n\n\tif !restyResp.IsSuccess() && err == nil {\n\t\t// Turn all non success responses to an error, like 301 Moved Permanently and 404 Not Found.\n\t\t// Note: IsError != !IsSuccess.\n\t\terr = ErrRequestFailed.New(\"%s %s (%s): Response status %d\",\n\t\t\trestyResp.Request.Method,\n\t\t\trestyResp.Request.URL,\n\t\t\tlResp.requestSnapshot.kindTag,\n\t\t\trestyResp.StatusCode())\n\t}\n\n\tif err != nil {\n\t\t// Turn response into nil when there is an error.\n\t\tif restyResp != nil && restyResp.RawResponse != nil {\n\t\t\tdata, _ := io.ReadAll(restyResp.RawResponse.Body)\n\t\t\t_ = restyResp.RawResponse.Body.Close()\n\t\t\tinfo.respStatus = restyResp.Status()\n\t\t\tinfo.respBody = string(data)\n\t\t\trestyResp = nil\n\t\t}\n\t\tinfo.Warn(\"Request failed\", err)\n\t}\n\n\tif restyResp != nil && restyResp.RawResponse != nil {\n\t\tlResp.executedResponseBody = restyResp.RawResponse.Body\n\t\tlResp.executedResponseWithoutBody = restyResp.RawResponse\n\t\trestyResp.RawResponse.Body = nil\n\t} else {\n\t\tlResp.executedResponseBody = nil\n\t\tlResp.executedResponseWithoutBody = nil\n\t}\n\tlResp.executedError = err\n\tlResp.executeInfo = info\n\tlResp.isExecuted = true\n\n\t// The request is executed, no need to schedule a check for the execution any more.\n\truntime.SetFinalizer(lResp, nil)\n}\n\nfunc (lResp *LazyResponse) close() {\n\t_ = lResp.executedResponseBody.Close()\n}\n\n// Finish closes the response and discard any unreaded response body. Read is not possible after that.\n// The returned raw response does not have a body. To read the body, call PipeBody, ReadBodyAsBytes,\n// ReadBodyAsString or ReadBodyAsJSON.\n// If the response status code is not a success status, an error will be returned.\nfunc (lResp *LazyResponse) Finish() (respNoBody *http.Response, err error) {\n\tlResp.doExecutionOnce()\n\tif lResp.executedError != nil {\n\t\treturn nil, lResp.executedError\n\t}\n\trespNoBody = lResp.executedResponseWithoutBody\n\tlResp.close()\n\treturn\n}\n\n// PipeBody pipes the body of the response to a writer.\n// If the response status code is not a success status, an error will be returned.\nfunc (lResp *LazyResponse) PipeBody(w io.Writer) (written int64, respNoBody *http.Response, err error) {\n\tlResp.doExecutionOnce()\n\tif lResp.executedError != nil {\n\t\treturn 0, nil, lResp.executedError\n\t}\n\trespNoBody = lResp.executedResponseWithoutBody\n\twritten, err = io.Copy(w, lResp.executedResponseBody)\n\tif err != nil {\n\t\trespNoBody = nil\n\t\terr = ErrRequestFailed.WrapWithNoMessage(err)\n\t\tlResp.executeInfo.Warn(\"Request failed\", err)\n\t}\n\tlResp.close()\n\treturn\n}\n\n// ReadBodyAsBytes reads all body content of the response to a byte slice.\n// If the response status code is not a success status, an error will be returned.\nfunc (lResp *LazyResponse) ReadBodyAsBytes() (bytes []byte, respNoBody *http.Response, err error) {\n\tlResp.doExecutionOnce()\n\tif lResp.executedError != nil {\n\t\treturn nil, nil, lResp.executedError\n\t}\n\trespNoBody = lResp.executedResponseWithoutBody\n\tbytes, err = io.ReadAll(lResp.executedResponseBody)\n\tif err != nil {\n\t\tbytes = nil\n\t\trespNoBody = nil\n\t\terr = ErrRequestFailed.WrapWithNoMessage(err)\n\t\tlResp.executeInfo.Warn(\"Request failed\", err)\n\t}\n\tlResp.close()\n\treturn\n}\n\n// ReadBodyAsString reads all body content of the response to a string.\n// If the response status code is not a success status, an error will be returned.\nfunc (lResp *LazyResponse) ReadBodyAsString() (data string, respNoBody *http.Response, err error) {\n\tbytes, resp, err := lResp.ReadBodyAsBytes()\n\tif err != nil {\n\t\treturn \"\", nil, err\n\t}\n\treturn strings.TrimSpace(string(bytes)), resp, nil\n}\n\n// ReadBodyAsJSON reads all body content of the response as JSON and does unmarshal.\n// If the response status code is not a success status, an error will be returned.\nfunc (lResp *LazyResponse) ReadBodyAsJSON(destination interface{}) (respNoBody *http.Response, err error) {\n\tbytes, resp, err := lResp.ReadBodyAsBytes()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\terr = json.Unmarshal(bytes, destination)\n\tif err != nil {\n\t\terr = ErrRequestFailed.WrapWithNoMessage(err)\n\t\tei := *lResp.executeInfo\n\t\tei.respStatus = lResp.executedResponseWithoutBody.Status\n\t\tei.respBody = string(bytes)\n\t\tei.Warn(\"Request failed\", err)\n\t\treturn nil, err\n\t}\n\treturn resp, nil\n}\n\nfunc (lResp *LazyResponse) finalize() {\n\tif israce.Enabled {\n\t\t// try to catch incorrect usages\n\t\t_, _ = os.Stderr.Write(lResp.stackAtNew)\n\t\tpanic(fmt.Sprintf(\"%T is not used correctly, one of PipeBody(), ReadBodyAsBytes(), ReadBodyAsString(), ReadBodyAsJSON() or Finish() must be called\", lResp))\n\t}\n\t// If a LazyResponse is GCed without actually sending the request, then we can just do nothing.\n\t// There is even no need to close the response body, since the request is not sent.\n}\n"
  },
  {
    "path": "util/client/httpclient/response_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage httpclient\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/stretchr/testify/require\"\n\t\"go.uber.org/atomic\"\n)\n\nfunc TestReadBodyAsString(t *testing.T) {\n\trequestTimes := atomic.Int32{}\n\tresponseStatus := atomic.Int32{}\n\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\trequestTimes.Inc()\n\t\tw.WriteHeader(int(responseStatus.Load()))\n\t\t_, _ = fmt.Fprintf(w, \"Basically OK, Req #%d\", requestTimes.Load())\n\t}))\n\tdefer ts.Close()\n\n\tclient := New(Config{})\n\n\tresponseStatus.Store(200)\n\treq := client.LR()\n\tresp := req.Get(ts.URL)\n\tresponseStatus.Store(202)                       // Lazy request\n\trequire.Equal(t, int32(0), requestTimes.Load()) // Lazy request\n\tdataStr, rawResp, err := resp.ReadBodyAsString()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Basically OK, Req #1\", dataStr)\n\trequire.Nil(t, rawResp.Body)\n\trequire.Equal(t, 202, rawResp.StatusCode) // Due to lazy request, we should get 202\n\n\t// Read again should result in error\n\tdataStrE, rawRespE, err := resp.ReadBodyAsString()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.Contains(t, err.Error(), \"read on closed response body\")\n\trequire.Empty(t, dataStrE)\n\trequire.Nil(t, rawRespE)\n\n\t// Other kind of read operations should also result in error\n\tbytesE, rawRespE, err := resp.ReadBodyAsBytes()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.Contains(t, err.Error(), \"read on closed response body\")\n\trequire.Nil(t, bytesE)\n\trequire.Nil(t, rawRespE)\n\n\t// Test sending a new request via Get() over the same request again\n\tresponseStatus.Store(201)\n\tresp = req.Get(ts.URL)\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\tdataStr2, rawResp2, err := resp.ReadBodyAsString()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Basically OK, Req #2\", dataStr2)\n\trequire.Nil(t, rawResp2.Body)\n\trequire.Equal(t, 202, rawResp.StatusCode) // The previous response should not be changed by a new request\n\trequire.Equal(t, 201, rawResp2.StatusCode)\n\n\t// Sending a new request via LR() over the same client\n\tresponseStatus.Store(200)\n\tresp = client.LR().Get(ts.URL)\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\tdataStr3, rawResp3, err := resp.ReadBodyAsString()\n\trequire.Equal(t, int32(3), requestTimes.Load())\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Basically OK, Req #3\", dataStr3)\n\trequire.Nil(t, rawResp3.Body)\n\trequire.Equal(t, 202, rawResp.StatusCode)\n\trequire.Equal(t, 201, rawResp2.StatusCode)\n\trequire.Equal(t, 200, rawResp3.StatusCode)\n\n\t// Sending a new request via creating a new client\n\tclient2 := New(Config{})\n\tresponseStatus.Store(202)\n\tresp = client2.LR().Get(ts.URL)\n\trequire.Equal(t, int32(3), requestTimes.Load())\n\tdataStr4, rawResp4, err := resp.ReadBodyAsString()\n\trequire.Equal(t, int32(4), requestTimes.Load())\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Basically OK, Req #4\", dataStr4)\n\trequire.Nil(t, rawResp4.Body)\n\trequire.Equal(t, 202, rawResp.StatusCode)\n\trequire.Equal(t, 201, rawResp2.StatusCode)\n\trequire.Equal(t, 200, rawResp3.StatusCode)\n\trequire.Equal(t, 202, rawResp4.StatusCode)\n}\n\nfunc TestFinish(t *testing.T) {\n\trequestTimes := atomic.Int32{}\n\tresponseStatus := atomic.Int32{}\n\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\trequestTimes.Inc()\n\t\tw.WriteHeader(int(responseStatus.Load()))\n\t\t_, _ = fmt.Fprintf(w, \"Basically OK, Req #%d\", requestTimes.Load())\n\t}))\n\tdefer ts.Close()\n\n\tclient := New(Config{})\n\tresponseStatus.Store(200)\n\n\tresp := client.LR().Get(ts.URL)\n\tresponseStatus.Store(202)                       // Lazy request\n\trequire.Equal(t, int32(0), requestTimes.Load()) // Lazy request\n\trawResp, err := resp.Finish()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.NoError(t, err)\n\trequire.Nil(t, rawResp.Body)\n\trequire.Equal(t, 202, rawResp.StatusCode)\n\n\t// Call Finish() again should not send a new request\n\trawResp, err = resp.Finish()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.NoError(t, err)\n\trequire.Nil(t, rawResp.Body)\n\trequire.Equal(t, 202, rawResp.StatusCode)\n\n\t// Read after Finish() should become errors\n\tdataStrE, rawRespE, err := resp.ReadBodyAsString()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.Contains(t, err.Error(), \"read on closed response body\")\n\trequire.Empty(t, dataStrE)\n\trequire.Nil(t, rawRespE)\n\tbytesE, rawRespE, err := resp.ReadBodyAsBytes()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.Contains(t, err.Error(), \"read on closed response body\")\n\trequire.Nil(t, bytesE)\n\trequire.Nil(t, rawRespE)\n\n\t// Finish() after read is fine.\n\tresp = client.LR().Get(ts.URL)\n\tresponseStatus.Store(200)\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\tdataStr, rawResp, err := resp.ReadBodyAsString()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Basically OK, Req #2\", dataStr)\n\trequire.Nil(t, rawResp.Body)\n\trequire.Equal(t, 200, rawResp.StatusCode)\n\trawResp2, err := resp.Finish()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.NoError(t, err)\n\trequire.Same(t, rawResp, rawResp2)\n}\n\nfunc TestReadBodyAsJSON(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\t_, _ = fmt.Fprintln(w, `{\"foo\":\"bar\"}`)\n\t}))\n\tdefer ts.Close()\n\n\t// Unmarshal into map\n\tclient := New(Config{})\n\tvar respMap map[string]interface{}\n\trawResp, err := client.LR().Get(ts.URL).ReadBodyAsJSON(&respMap)\n\trequire.NoError(t, err)\n\trequire.Equal(t, http.StatusOK, rawResp.StatusCode)\n\texpectedMap := map[string]interface{}{\n\t\t\"foo\": \"bar\",\n\t}\n\trequire.Equal(t, expectedMap, respMap)\n\n\t// Unmarshal into struct\n\ttype Response struct {\n\t\tFoo string `json:\"foo\"`\n\t}\n\tvar respStruct Response\n\treq := client.LR().Get(ts.URL)\n\trawResp, err = req.ReadBodyAsJSON(&respStruct)\n\trequire.NoError(t, err)\n\trequire.Equal(t, http.StatusOK, rawResp.StatusCode)\n\trequire.Equal(t, Response{Foo: \"bar\"}, respStruct)\n}\n\nfunc TestReadBodyAsJSON_UnmarshalFailure(t *testing.T) {\n\trequestTimes := atomic.Int32{}\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\trequestTimes.Inc()\n\t\t_, _ = fmt.Fprintln(w, `bad_json`)\n\t}))\n\tdefer ts.Close()\n\n\tclient := New(Config{})\n\n\tvar respMap map[string]interface{}\n\trequire.Equal(t, int32(0), requestTimes.Load())\n\treq := client.LR().Get(ts.URL)\n\trawResp, err := req.ReadBodyAsJSON(&respMap)\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.Contains(t, err.Error(), \"invalid character\")\n\trequire.Nil(t, rawResp)\n\trequire.Nil(t, respMap)\n\n\t// Read JSON again should not send new request\n\trawResp, err = req.ReadBodyAsJSON(&respMap)\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.Contains(t, err.Error(), \"read on closed response body\")\n\trequire.Nil(t, rawResp)\n\trequire.Nil(t, respMap)\n\n\t// Finish should success without sending new requests\n\t// Unlike other Read errors, for unmarshal errors, Finish() will succeed since an OK response is read successfully\n\trawResp, err = req.Finish()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.NoError(t, err)\n\trequire.Equal(t, http.StatusOK, rawResp.StatusCode)\n}\n\ntype myWriter struct {\n\twritedBytes int\n\twriteCalled int\n\terrorRaised int\n}\n\nfunc (w *myWriter) Write(p []byte) (int, error) {\n\tw.writeCalled++\n\tif w.writedBytes > 5 {\n\t\tw.errorRaised++\n\t\treturn 0, fmt.Errorf(\"write too many bytes\")\n\t}\n\tw.writedBytes += len(p)\n\treturn len(p), nil\n}\n\nfunc TestPipeBody(t *testing.T) {\n\trequestTimes := atomic.Int32{}\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\trequestTimes.Inc()\n\t\t_, _ = fmt.Fprintln(w, \"Hello world\")\n\t}))\n\tdefer ts.Close()\n\n\tclient := New(Config{})\n\n\tbuf := bytes.Buffer{}\n\trequire.Equal(t, int32(0), requestTimes.Load())\n\treq := client.LR().Get(ts.URL)\n\twBytes, rawResp, err := req.PipeBody(&buf)\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.NoError(t, err)\n\trequire.Equal(t, http.StatusOK, rawResp.StatusCode)\n\trequire.Equal(t, \"Hello world\\n\", buf.String())\n\trequire.Equal(t, int64(12), wBytes)\n\n\t// The copy chunk size is large, so that there will be only one write call to the writer\n\tw := myWriter{}\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\twBytes, rawResp, err = client.LR().Get(ts.URL).PipeBody(&w)\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.Equal(t, int64(12), wBytes)\n\trequire.NoError(t, err)\n\trequire.Equal(t, http.StatusOK, rawResp.StatusCode)\n\trequire.Equal(t, 1, w.writeCalled)\n\trequire.Equal(t, 12, w.writedBytes)\n\trequire.Equal(t, 0, w.errorRaised)\n\n\t// Now the server write data chunk by chunk...\n\tctx, cancel := context.WithCancel(context.Background())\n\tts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\trequestTimes.Inc()\n\t\t_, _ = w.Write([]byte(\"Partial...\"))\n\t\tw.(http.Flusher).Flush()\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase <-time.After(1 * time.Second):\n\t\t\t_, _ = fmt.Fprintln(w, \"Done\")\n\t\t}\n\t}))\n\tdefer ts.Close()\n\tdefer cancel()\n\n\t// PipeData should produce data chunk by chunk\n\tw = myWriter{}\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\tresp := client.LR().Get(ts.URL)\n\twBytes, rawResp, err = resp.PipeBody(&w)\n\trequire.Equal(t, int32(3), requestTimes.Load())\n\trequire.Equal(t, int64(10), wBytes)\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"write too many bytes\")\n\trequire.Nil(t, rawResp)\n\trequire.Equal(t, 2, w.writeCalled)\n\trequire.Equal(t, 10, w.writedBytes) // The size of the first chunk\n\trequire.Equal(t, 1, w.errorRaised)\n\t// Call PipeBody again should fail due to response is closed\n\twBytes, rawResp, err = resp.PipeBody(&w)\n\trequire.Equal(t, int32(3), requestTimes.Load())\n\trequire.Equal(t, int64(0), wBytes)\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"read on closed response body\")\n\trequire.Nil(t, rawResp)\n\trequire.Equal(t, 2, w.writeCalled) // Unchanged\n\trequire.Equal(t, 10, w.writedBytes)\n\trequire.Equal(t, 1, w.errorRaised)\n\n\t// PipeBody should copy all data when there are multiple chunks from the server\n\tbuf = bytes.Buffer{}\n\trequire.Equal(t, int32(3), requestTimes.Load())\n\treq = client.LR().Get(ts.URL)\n\twBytes, rawResp, err = req.PipeBody(&buf)\n\trequire.Equal(t, int32(4), requestTimes.Load())\n\trequire.NoError(t, err)\n\trequire.Equal(t, http.StatusOK, rawResp.StatusCode)\n\trequire.Equal(t, \"Partial...Done\\n\", buf.String())\n\trequire.Equal(t, int64(15), wBytes)\n}\n\nfunc TestResponseHeader(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\tw.Header().Add(\"foo\", \"bar\")\n\t\tw.WriteHeader(http.StatusAlreadyReported)\n\t\t_, _ = fmt.Fprintln(w, \"Fine!\")\n\t}))\n\tdefer ts.Close()\n\n\tclient := New(Config{})\n\tresp := client.LR().Get(ts.URL)\n\trawResp, err := resp.Finish()\n\trequire.NoError(t, err)\n\trequire.Equal(t, http.StatusAlreadyReported, rawResp.StatusCode)\n\trequire.Equal(t, \"bar\", rawResp.Header.Get(\"foo\"))\n}\n\nfunc TestSetURL(t *testing.T) {\n\tts1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\t_, _ = fmt.Fprintln(w, \"Result from server 1\")\n\t}))\n\tdefer ts1.Close()\n\n\tts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\t_, _ = fmt.Fprintln(w, \"Result from server 2\")\n\t}))\n\tdefer ts2.Close()\n\n\tclient := New(Config{})\n\treq := client.LR()\n\n\t// SetXxx should make changes in place\n\tr1 := req.SetURL(ts1.URL)\n\tr2 := req.SetURL(ts2.URL)\n\trequire.Same(t, r1, r2)\n\tdataStr, _, err := r1.Send().ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 2\", dataStr)\n\tdataStr, _, err = r2.Send().ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 2\", dataStr)\n\n\tr1.SetURL(ts1.URL)\n\tdataStr, _, err = r2.Send().ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 1\", dataStr)\n\n\t// SetURL should not affect another request in the same client\n\treq2 := client.LR()\n\treq2.SetURL(ts2.URL)\n\tdataStr, _, err = r1.Send().ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 1\", dataStr)\n\tdataStr, _, err = r2.Send().ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 1\", dataStr)\n\tdataStr, _, err = req2.Send().ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 2\", dataStr)\n\tdataStr, _, err = req.Send().ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 1\", dataStr)\n\tdataStr, _, err = r1.Send().ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 1\", dataStr)\n}\n\nfunc TestLR(t *testing.T) {\n\tclient := New(Config{})\n\treq1 := client.LR()\n\treq2 := client.LR()\n\trequire.NotSame(t, req1, req2)\n}\n\nfunc TestGet(t *testing.T) {\n\tts1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\t_, _ = fmt.Fprintln(w, \"Result from server 1\")\n\t}))\n\tdefer ts1.Close()\n\n\tts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\t_, _ = fmt.Fprintln(w, \"Result from server 2\")\n\t}))\n\tdefer ts2.Close()\n\n\tclient := New(Config{})\n\n\t// \"Get\" from different requests should not affect each other\n\tresp1 := client.LR().Get(ts1.URL)\n\tresp2 := client.LR().Get(ts2.URL)\n\tdataStr, _, err := resp1.ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 1\", dataStr)\n\tdataStr, _, err = resp2.ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 2\", dataStr)\n\n\t// \"Get\" should not affect each other\n\treq := client.LR()\n\tresp1 = req.Get(ts1.URL)\n\tresp2 = req.Get(ts2.URL)\n\tdataStr, _, err = resp1.ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 1\", dataStr)\n\tdataStr, _, err = resp2.ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 2\", dataStr)\n\tresp3 := req.Get(ts1.URL)\n\tdataStr, _, err = resp3.ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 1\", dataStr)\n\n\t// \"Get()\" should not affect the previous \"SetURL()\" call\n\treq = client.LR()\n\treq.SetURL(ts1.URL)\n\tdataStr, _, err = req.Get(ts2.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 2\", dataStr)\n\tdataStr, _, err = req.Send().ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 1\", dataStr)\n\n\t// \"SetURL()\" should not affect the previous \"Get()\" call\n\treq = client.LR()\n\tresp1 = req.Get(ts1.URL)\n\treq.SetURL(ts2.URL)\n\tdataStr, _, err = resp1.ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 1\", dataStr)\n\tdataStr, _, err = req.Send().ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Result from server 2\", dataStr)\n}\n\nfunc TestPost(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tbody, _ := io.ReadAll(r.Body)\n\t\t_, _ = fmt.Fprintf(w, \"Body is %s\", string(body))\n\t}))\n\tdefer ts.Close()\n\n\tclient := New(Config{})\n\n\t// SetBody from different requests should not affect each other\n\tr1 := client.LR().SetBody(\"foo\")\n\tdataStr, _, err := r1.Post(ts.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Body is foo\", dataStr)\n\n\tr2 := client.LR().SetBody(\"bar\")\n\tdataStr, _, err = r2.Post(ts.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Body is bar\", dataStr)\n\n\tdataStr, _, err = r1.Post(ts.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Body is foo\", dataStr)\n}\n\nfunc TestSetHeader(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = fmt.Fprintln(w, r.Header.Get(\"X-Test\"))\n\t}))\n\tdefer ts.Close()\n\n\tclient := New(Config{})\n\treq := client.LR().SetHeader(\"X-Test\", \"foobar\")\n\n\t// SetHeader from different requests should not affect each other\n\tdataStr, _, err := req.Get(ts.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foobar\", dataStr)\n\n\tdataStr, _, err = client.LR().Get(ts.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"\", dataStr)\n\n\tdataStr, _, err = req.Get(ts.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foobar\", dataStr)\n\n\t// SetHeader after Get should not taking effect\n\treq = client.LR()\n\tresp := req.Get(ts.URL)\n\treq.SetHeader(\"X-Test\", \"hello\")\n\tdataStr, _, err = resp.ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"\", dataStr)\n\n\tresp = req.Get(ts.URL)\n\tdataStr, _, err = resp.ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"hello\", dataStr)\n}\n\nfunc TestSetTLSAwareBaseURL(t *testing.T) {\n\tts1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = fmt.Fprintln(w, \"ts1\"+r.URL.Path)\n\t}))\n\tdefer ts1.Close()\n\n\tts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = fmt.Fprintln(w, \"ts2\"+r.URL.Path)\n\t}))\n\tdefer ts2.Close()\n\n\tclient := New(Config{})\n\tdataStr, _, err := client.LR().SetTLSAwareBaseURL(ts1.URL).Get(\"/foo\").ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"ts1/foo\", dataStr)\n\n\t// base url can be overwritten\n\tdataStr, _, err = client.LR().SetTLSAwareBaseURL(ts1.URL).Get(ts2.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"ts2/\", dataStr)\n\n\t// Rewrite http:// to https:// if TLS config is specified\n\ttsTLS := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t_, _ = fmt.Fprintln(w, \"tsTLS\"+r.URL.Path)\n\t}))\n\tdefer tsTLS.Close()\n\n\thttpURL := \"http://\" + tsTLS.Listener.Addr().String()\n\n\tcertpool := x509.NewCertPool()\n\tcertpool.AddCert(tsTLS.Certificate())\n\tclient = New(Config{TLSConfig: &tls.Config{\n\t\tRootCAs: certpool,\n\t}}) // #nosec G402\n\t_, _, err = client.LR().Get(httpURL).ReadBodyAsString()\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"Response status 400\")\n\n\tdataStr, _, err = client.LR().SetTLSAwareBaseURL(httpURL).Get(\"/bar\").ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"tsTLS/bar\", dataStr)\n}\n\nfunc TestFailureStatusCode(t *testing.T) {\n\trequestTimes := atomic.Int32{}\n\tresponseStatus := atomic.Int32{}\n\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\trequestTimes.Inc()\n\t\tw.WriteHeader(int(responseStatus.Load()))\n\t\t_, _ = fmt.Fprintf(w, \"Fail from req #%d\", requestTimes.Load())\n\t}))\n\tdefer ts.Close()\n\n\t// Although request succeeded, failure status code will turn into errors by design.\n\n\tclient := New(Config{})\n\n\t// ReadBodyAsBytes should fail\n\tresponseStatus.Store(500)\n\trequire.Equal(t, int32(0), requestTimes.Load())\n\tbytes, rawResp, err := client.LR().Get(ts.URL).ReadBodyAsBytes()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Contains(t, err.Error(), \"Response status 500\")\n\trequire.Nil(t, bytes)\n\trequire.Nil(t, rawResp)\n\n\t// ReadBodyAsString should return empty string\n\tresponseStatus.Store(400)\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\tresp := client.LR().Get(ts.URL)\n\tdataStr, rawResp, err := resp.ReadBodyAsString()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Contains(t, err.Error(), \"Response status 400\")\n\trequire.Empty(t, dataStr)\n\trequire.Nil(t, rawResp)\n\t// Read again after failure should not send request again\n\tresponseStatus.Store(500)\n\tdataStr, rawResp, err = resp.ReadBodyAsString()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Contains(t, err.Error(), \"Response status 400\")\n\trequire.Empty(t, dataStr)\n\trequire.Nil(t, rawResp)\n\n\t// ReadBodyAsJSON should fail\n\tvar respMap map[string]interface{}\n\tresp = client.LR().Get(ts.URL)\n\trawResp, err = resp.ReadBodyAsJSON(respMap)\n\trequire.Equal(t, int32(3), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Contains(t, err.Error(), \"Response status 500\")\n\trequire.Empty(t, dataStr)\n\trequire.Nil(t, rawResp)\n\trequire.Nil(t, respMap)\n\trawResp, err = resp.ReadBodyAsJSON(respMap)\n\trequire.Equal(t, int32(3), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Contains(t, err.Error(), \"Response status 500\")\n\trequire.Empty(t, dataStr)\n\trequire.Nil(t, rawResp)\n\trequire.Nil(t, respMap)\n\n\t// Finish should fail\n\tresponseStatus.Store(404)\n\trequire.Equal(t, int32(3), requestTimes.Load())\n\tresp = client.LR().Get(ts.URL)\n\trawResp, err = resp.Finish()\n\trequire.Equal(t, int32(4), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Contains(t, err.Error(), \"Response status 404\")\n\trequire.Nil(t, rawResp)\n\t// Finish again after failure should not send request again\n\tresponseStatus.Store(200)\n\trawResp, err = resp.Finish()\n\trequire.Equal(t, int32(4), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Contains(t, err.Error(), \"Response status 404\")\n\trequire.Nil(t, rawResp)\n\t// Mix Finish() and ReadBodyAsString()\n\tresponseStatus.Store(403)\n\tdataStr, rawResp, err = resp.ReadBodyAsString()\n\trequire.Equal(t, int32(4), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Contains(t, err.Error(), \"Response status 404\")\n\trequire.Empty(t, dataStr)\n\trequire.Nil(t, rawResp)\n\tresponseStatus.Store(200)\n\trawResp, err = resp.Finish()\n\trequire.Equal(t, int32(4), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Contains(t, err.Error(), \"Response status 404\")\n\trequire.Nil(t, rawResp)\n}\n\nfunc TestBadServer(t *testing.T) {\n\trequestTimes := atomic.Int32{}\n\tlistener, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\trequire.NoError(t, err)\n\tgo func() {\n\t\tfor {\n\t\t\tconn, err := listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\trequestTimes.Inc()\n\t\t\t_, _ = conn.Write([]byte(\"Hello\"))\n\t\t\t_ = conn.Close()\n\t\t}\n\t}()\n\tdefer func() {\n\t\t_ = listener.Close()\n\t}()\n\turl := fmt.Sprintf(\"http://%s/foo\", listener.Addr().String())\n\n\tclient := New(Config{})\n\n\t// ReadBodyAsString should return empty string\n\trequire.Equal(t, int32(0), requestTimes.Load())\n\tresp := client.LR().Get(url)\n\tdataStr, rawResp, err := resp.ReadBodyAsString()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Empty(t, dataStr)\n\trequire.Nil(t, rawResp)\n\t// Call multiple times\n\tdataStr, rawResp, err = resp.ReadBodyAsString()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Empty(t, dataStr)\n\trequire.Nil(t, rawResp)\n\n\t// Response should fail\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\tresp = client.LR().Get(url)\n\trawResp, err = resp.Finish()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Nil(t, rawResp)\n\t// Call multiple times\n\trawResp, err = resp.Finish()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Nil(t, rawResp)\n\t// Mix Finish() and ReadBodyAsString()\n\tdataStr, rawResp, err = resp.ReadBodyAsString()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Empty(t, dataStr)\n\trequire.Nil(t, rawResp)\n\trawResp, err = resp.Finish()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Nil(t, rawResp)\n\n\t// ReadBodyASJSON should fail\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\tresp = client.LR().Get(url)\n\tvar respMap map[string]interface{}\n\trawResp, err = resp.ReadBodyAsJSON(respMap)\n\trequire.Equal(t, int32(3), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Nil(t, rawResp)\n\trequire.Nil(t, respMap)\n\t// Call multiple times\n\trawResp, err = resp.ReadBodyAsJSON(respMap)\n\trequire.Equal(t, int32(3), requestTimes.Load())\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Nil(t, rawResp)\n\trequire.Nil(t, respMap)\n}\n\nfunc TestBadScheme(t *testing.T) {\n\tclient := New(Config{})\n\tbytes, rawResp, err := client.LR().Get(\"foo://abc.com\").ReadBodyAsBytes()\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Contains(t, err.Error(), `unsupported protocol scheme \"foo\"`)\n\trequire.Nil(t, bytes)\n\trequire.Nil(t, rawResp)\n\n\trawResp, err = client.LR().Get(\"bar://abc.com\").Finish()\n\trequire.True(t, errorx.IsOfType(err, ErrRequestFailed))\n\trequire.Contains(t, err.Error(), `unsupported protocol scheme \"bar\"`)\n\trequire.Nil(t, rawResp)\n}\n\nfunc TestConnectionReuse(t *testing.T) {\n\tnewConn := atomic.Int32{}\n\tclosedConn := atomic.Int32{}\n\n\trequestTimes := atomic.Int32{}\n\tts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\trequestTimes.Inc()\n\t\t_, _ = fmt.Fprintf(w, \"Req #%d\", requestTimes.Load())\n\t}))\n\tts.Config.ConnState = func(_ net.Conn, cs http.ConnState) {\n\t\tswitch cs {\n\t\tcase http.StateNew:\n\t\t\tnewConn.Inc()\n\t\tcase http.StateHijacked, http.StateClosed:\n\t\t\tclosedConn.Inc()\n\t\tdefault:\n\t\t\t// we do not care other states\n\t\t}\n\t}\n\tts.Start()\n\tdefer ts.Close()\n\n\trequire.Equal(t, int32(0), newConn.Load())\n\trequire.Equal(t, int32(0), closedConn.Load())\n\n\tclient := New(Config{})\n\tdataStr, _, err := client.LR().Get(ts.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Req #1\", dataStr)\n\trequire.Equal(t, int32(1), newConn.Load())\n\trequire.Equal(t, int32(0), closedConn.Load())\n\n\t// Use the same client to send request, the connection is expected to be reused\n\tdataStr, _, err = client.LR().Get(ts.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Req #2\", dataStr)\n\trequire.Equal(t, int32(1), newConn.Load())\n\trequire.Equal(t, int32(0), closedConn.Load())\n\n\t// A new client should create a new connection\n\tclient2 := New(Config{})\n\tdataStr, _, err = client2.LR().Get(ts.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Req #3\", dataStr)\n\trequire.Equal(t, int32(2), newConn.Load())\n\trequire.Equal(t, int32(0), closedConn.Load())\n\n\t// Connections are reused\n\tdataStr, _, err = client.LR().Get(ts.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Req #4\", dataStr)\n\trequire.Equal(t, int32(2), newConn.Load())\n\trequire.Equal(t, int32(0), closedConn.Load())\n\tdataStr, _, err = client2.LR().Get(ts.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"Req #5\", dataStr)\n\trequire.Equal(t, int32(2), newConn.Load())\n\trequire.Equal(t, int32(0), closedConn.Load())\n}\n\nfunc TestClone(t *testing.T) {\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tm := make(map[string]string)\n\t\tfor header, value := range r.Header {\n\t\t\tif strings.HasPrefix(header, \"X-\") {\n\t\t\t\tm[header] = value[0]\n\t\t\t}\n\t\t}\n\t\tj, _ := json.Marshal(m)\n\t\t_, _ = w.Write(j)\n\t}))\n\tdefer ts.Close()\n\n\tclient := New(Config{})\n\n\treq1 := client.LR()\n\treq1.SetHeader(\"x-req1header1\", \"value1\")\n\n\treq2 := req1.Clone()\n\t// After clone, they will not affect each other\n\treq1.SetHeader(\"x-req1header2\", \"value2\")\n\treq2.SetHeader(\"x-req2header1\", \"value1\")\n\n\tdataStr, _, err := req1.Get(ts.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.JSONEq(t, `{\"X-Req1header1\":\"value1\",\"X-Req1header2\":\"value2\"}`, dataStr)\n\n\tdataStr, _, err = req2.Get(ts.URL).ReadBodyAsString()\n\trequire.NoError(t, err)\n\trequire.JSONEq(t, `{\"X-Req1header1\":\"value1\",\"X-Req2header1\":\"value1\"}`, dataStr)\n}\n\nfunc TestTimeoutHeader(t *testing.T) {\n\trequestTimes := atomic.Int32{}\n\tctx, cancel := context.WithCancel(context.Background())\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\trequestTimes.Inc()\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\tw.WriteHeader(http.StatusGatewayTimeout)\n\t\tcase <-time.After(1 * time.Second):\n\t\t\t_, _ = fmt.Fprintln(w, \"OK\")\n\t\t}\n\t}))\n\tdefer ts.Close()\n\tdefer cancel()\n\n\tclient := New(Config{})\n\ttBegin := time.Now()\n\trequire.Equal(t, int32(0), requestTimes.Load())\n\tresp := client.LR().SetTimeout(100 * time.Millisecond).Get(ts.URL)\n\t_, rawResp, err := resp.ReadBodyAsString()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.Less(t, time.Since(tBegin), 300*time.Millisecond)\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"Client.Timeout\")\n\trequire.Nil(t, rawResp)\n\t// Read again\n\t_, rawResp, err = resp.ReadBodyAsString()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"Client.Timeout\")\n\trequire.Nil(t, rawResp)\n\trawResp, err = resp.Finish()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"Client.Timeout\")\n\trequire.Nil(t, rawResp)\n\t// Even if the request is finished then, we should still get timeout error.\n\ttime.Sleep(1 * time.Second)\n\t_, rawResp, err = resp.ReadBodyAsString()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"Client.Timeout\")\n\trequire.Nil(t, rawResp)\n\n\t// Call Finish() directly should fail\n\ttBegin = time.Now()\n\tresp = client.LR().SetTimeout(100 * time.Millisecond).Get(ts.URL)\n\trawResp, err = resp.Finish()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.Less(t, time.Since(tBegin), 300*time.Millisecond)\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"Client.Timeout\")\n\trequire.Nil(t, rawResp)\n\n\t// Read using long enough timeout should succeed\n\tresp = client.LR().SetTimeout(1200 * time.Millisecond).Get(ts.URL)\n\tdataStr, rawResp, err := resp.ReadBodyAsString()\n\trequire.Equal(t, int32(3), requestTimes.Load())\n\trequire.NoError(t, err)\n\trequire.Equal(t, http.StatusOK, rawResp.StatusCode)\n\trequire.Equal(t, \"OK\", dataStr)\n}\n\nfunc TestTimeoutBody(t *testing.T) {\n\trequestTimes := atomic.Int32{}\n\tctx, cancel := context.WithCancel(context.Background())\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\trequestTimes.Inc()\n\t\t_, _ = w.Write([]byte(\"Partial...\"))\n\t\tw.(http.Flusher).Flush()\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn\n\t\tcase <-time.After(1 * time.Second):\n\t\t\t_, _ = fmt.Fprintln(w, \"Done\")\n\t\t}\n\t}))\n\tdefer ts.Close()\n\tdefer cancel()\n\n\tclient := New(Config{})\n\n\t// Finish() should succeed, since a header is successfully returned\n\ttBegin := time.Now()\n\tresp := client.LR().SetTimeout(100 * time.Millisecond).Get(ts.URL)\n\trawResp, err := resp.Finish()\n\trequire.Equal(t, int32(1), requestTimes.Load())\n\trequire.Less(t, time.Since(tBegin), 50*time.Millisecond)\n\trequire.NoError(t, err)\n\trequire.Equal(t, http.StatusOK, rawResp.StatusCode)\n\n\t// ReadBodyAsString() should fail\n\ttBegin = time.Now()\n\tresp = client.LR().SetTimeout(100 * time.Millisecond).Get(ts.URL)\n\t_, rawResp, err = resp.ReadBodyAsString()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.Less(t, time.Since(tBegin), 300*time.Millisecond)\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"Client.Timeout\")\n\trequire.Nil(t, rawResp)\n\t// Read again\n\t_, rawResp, err = resp.ReadBodyAsString()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"Client.Timeout\")\n\trequire.Nil(t, rawResp)\n\t// Wait enough time and read again\n\ttime.Sleep(1 * time.Second)\n\t_, rawResp, err = resp.ReadBodyAsString()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"Client.Timeout\")\n\trequire.Nil(t, rawResp)\n\t// Finish() should succeed\n\ttBegin = time.Now()\n\trawResp, err = resp.Finish()\n\trequire.Equal(t, int32(2), requestTimes.Load())\n\trequire.Less(t, time.Since(tBegin), 50*time.Millisecond)\n\trequire.NoError(t, err)\n\trequire.Equal(t, http.StatusOK, rawResp.StatusCode)\n\n\t// PipeBody() should fail\n\tbuf := bytes.Buffer{}\n\ttBegin = time.Now()\n\tresp = client.LR().SetTimeout(100 * time.Millisecond).Get(ts.URL)\n\twBytes, rawResp, err := resp.PipeBody(&buf)\n\trequire.Equal(t, int32(3), requestTimes.Load())\n\trequire.Less(t, time.Since(tBegin), 300*time.Millisecond)\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"Client.Timeout\")\n\trequire.Nil(t, rawResp)\n\trequire.Equal(t, int64(10), wBytes) // The first chunk is written\n\trequire.Equal(t, \"Partial...\", buf.String())\n\t// PipeBody again should fail\n\twBytes, rawResp, err = resp.PipeBody(&buf)\n\trequire.Equal(t, int32(3), requestTimes.Load())\n\trequire.Less(t, time.Since(tBegin), 300*time.Millisecond)\n\trequire.Error(t, err)\n\trequire.Contains(t, err.Error(), \"Client.Timeout\")\n\trequire.Nil(t, rawResp)\n\trequire.Equal(t, int64(0), wBytes) // No more chunk is written\n\trequire.Equal(t, \"Partial...\", buf.String())\n\n\t// Read using long enough timeout should succeed\n\tresp = client.LR().SetTimeout(1200 * time.Millisecond).Get(ts.URL)\n\tdataStr, rawResp, err := resp.ReadBodyAsString()\n\trequire.Equal(t, int32(4), requestTimes.Load())\n\trequire.NoError(t, err)\n\trequire.Equal(t, http.StatusOK, rawResp.StatusCode)\n\trequire.Equal(t, \"Partial...Done\", dataStr)\n}\n\n// FIXME: Seems that there is no way to test the panic happens inside runtime finalizers.\n// func TestUsageCheck(t *testing.T) {\n//\tif !israce.Enabled {\n//\t\tt.Skipf(\"LazyResponse usage check will be tested only when race detector is enabled\")\n//\t\treturn\n//\t}\n//\tclient := New(Config{})\n//\tclient.LR().Get(\"foo://example.com\")\n//\trequire.Panics(t, func() { runtime.GC() })\n// }\n\n// TODO: TestCtxRequest\n\n// TODO: TestCtxResponse\n// This test shows that ctx doesn't really restrict the response's lifetime.\n\n// TODO: Test log output\n"
  },
  {
    "path": "util/client/pdclient/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdclient_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/client/pdclient/etcd_client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdclient\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"time\"\n\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/zap\"\n\t\"google.golang.org/grpc\"\n\t\"google.golang.org/grpc/backoff\"\n)\n\ntype EtcdClientConfig struct {\n\tEndpoints []string\n\tContext   context.Context\n\tTLS       *tls.Config\n}\n\n// NewEtcdClient creates a new etcd client. The client must be closed by calling `client.Close()`.\n// Returns error when config is invalid.\nfunc NewEtcdClient(config EtcdClientConfig) (*clientv3.Client, error) {\n\tzapCfg := zap.NewProductionConfig()\n\tzapCfg.Encoding = log.ZapEncodingName\n\tcli, err := clientv3.New(clientv3.Config{\n\t\tContext:              config.Context,\n\t\tEndpoints:            config.Endpoints,\n\t\tAutoSyncInterval:     30 * time.Second,\n\t\tDialTimeout:          5 * time.Second,\n\t\tDialKeepAliveTime:    10 * time.Second,\n\t\tDialKeepAliveTimeout: 3 * time.Second,\n\t\tPermitWithoutStream:  false,\n\t\tDialOptions: []grpc.DialOption{\n\t\t\tgrpc.WithConnectParams(grpc.ConnectParams{\n\t\t\t\tBackoff: backoff.Config{\n\t\t\t\t\tBaseDelay:  100 * time.Millisecond, // Default was 1 second\n\t\t\t\t\tMultiplier: 1.6,                    // Default\n\t\t\t\t\tJitter:     0.2,                    // Default\n\t\t\t\t\tMaxDelay:   3 * time.Second,        // Default was 120 seconds\n\t\t\t\t},\n\t\t\t\tMinConnectTimeout: 5 * time.Second, // Default was 20 seconds\n\t\t\t}),\n\t\t},\n\t\tTLS:       config.TLS,\n\t\tLogConfig: &zapCfg,\n\t})\n\treturn cli, err\n}\n"
  },
  {
    "path": "util/client/pdclient/fixture/pd_server.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage fixture\n\nimport (\n\t\"github.com/jarcoal/httpmock\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/pdclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/httpmockutil\"\n)\n\nconst BaseURL = \"http://172.16.6.171:2379\"\n\nfunc NewPDServerFixture() (mockTransport *httpmock.MockTransport) {\n\tmockTransport = httpmock.NewMockTransport()\n\tmockTransport.RegisterResponder(\"GET\", \"http://172.16.6.171:2379/pd/api/v1/status\",\n\t\thttpmockutil.StringResponder(`\n{\n  \"build_ts\": \"2021-07-17 05:37:05\",\n  \"version\": \"v4.0.14\",\n  \"git_hash\": \"0c1246dd219fd16b4b2ff5108941e5d3e958922d\",\n  \"start_timestamp\": 1635762685\n}\n`))\n\tmockTransport.RegisterResponder(\"GET\", \"http://172.16.6.171:2379/pd/api/v1/health\",\n\t\thttpmockutil.StringResponder(`\n[\n  {\n    \"name\": \"pd-172.16.6.170-2379\",\n    \"member_id\": 2939568762143497195,\n    \"client_urls\": [\n      \"http://172.16.6.170:2379\"\n    ],\n    \"health\": true\n  },\n  {\n    \"name\": \"pd-172.16.6.169-2379\",\n    \"member_id\": 8776556846936845803,\n    \"client_urls\": [\n      \"http://172.16.6.169:2379\"\n    ],\n    \"health\": true\n  },\n  {\n    \"name\": \"pd-172.16.6.171-2379\",\n    \"member_id\": 13248060353287547571,\n    \"client_urls\": [\n      \"http://172.16.6.171:2379\"\n    ],\n    \"health\": true\n  }\n]\n`))\n\tmockTransport.RegisterResponder(\"GET\", \"http://172.16.6.171:2379/pd/api/v1/members\",\n\t\thttpmockutil.StringResponder(`\n{\n  \"header\": {\n    \"cluster_id\": 6973530669239952773\n  },\n  \"members\": [\n    {\n      \"name\": \"pd-172.16.6.170-2379\",\n      \"member_id\": 2939568762143497195,\n      \"peer_urls\": [\n        \"http://172.16.6.170:2380\"\n      ],\n      \"client_urls\": [\n        \"http://172.16.6.170:2379\"\n      ],\n      \"deploy_path\": \"/home/tidb/tidb-deploy/pd-2379/bin\",\n      \"binary_version\": \"v4.0.14\",\n      \"git_hash\": \"0c1246dd219fd16b4b2ff5108941e5d3e958922d\"\n    },\n    {\n      \"name\": \"pd-172.16.6.169-2379\",\n      \"member_id\": 8776556846936845803,\n      \"peer_urls\": [\n        \"http://172.16.6.169:2380\"\n      ],\n      \"client_urls\": [\n        \"http://172.16.6.169:2379\"\n      ],\n      \"deploy_path\": \"/home/tidb/tidb-deploy/pd-2379/bin\",\n      \"binary_version\": \"v4.0.14\",\n      \"git_hash\": \"0c1246dd219fd16b4b2ff5108941e5d3e958922d\"\n    },\n    {\n      \"name\": \"pd-172.16.6.171-2379\",\n      \"member_id\": 13248060353287547571,\n      \"peer_urls\": [\n        \"http://172.16.6.171:2380\"\n      ],\n      \"client_urls\": [\n        \"http://172.16.6.171:2379\"\n      ],\n      \"deploy_path\": \"/home/tidb/tidb-deploy/pd-2379/bin\",\n      \"binary_version\": \"v4.0.14\",\n      \"git_hash\": \"0c1246dd219fd16b4b2ff5108941e5d3e958922d\"\n    }\n  ],\n  \"leader\": {\n    \"name\": \"pd-172.16.6.171-2379\",\n    \"member_id\": 13248060353287547571,\n    \"peer_urls\": [\n      \"http://172.16.6.171:2380\"\n    ],\n    \"client_urls\": [\n      \"http://172.16.6.171:2379\"\n    ]\n  },\n  \"etcd_leader\": {\n    \"name\": \"pd-172.16.6.171-2379\",\n    \"member_id\": 13248060353287547571,\n    \"peer_urls\": [\n      \"http://172.16.6.171:2380\"\n    ],\n    \"client_urls\": [\n      \"http://172.16.6.171:2379\"\n    ],\n    \"deploy_path\": \"/home/tidb/tidb-deploy/pd-2379/bin\",\n    \"binary_version\": \"v4.0.14\",\n    \"git_hash\": \"0c1246dd219fd16b4b2ff5108941e5d3e958922d\"\n  }\n}\n`))\n\tmockTransport.RegisterResponder(\"GET\", \"http://172.16.6.171:2379/pd/api/v1/stores\",\n\t\thttpmockutil.StringResponder(`\n{\n  \"count\": 3,\n  \"stores\": [\n    {\n      \"store\": {\n        \"id\": 4,\n        \"address\": \"172.16.6.168:20160\",\n        \"version\": \"4.0.14\",\n        \"status_address\": \"172.16.6.168:20180\",\n        \"git_hash\": \"d7dc4fff51ca71c76a928a0780a069efaaeaae70\",\n        \"start_timestamp\": 1636421304,\n        \"deploy_path\": \"/home/tidb/tidb-deploy/tikv-20160/bin\",\n        \"last_heartbeat\": 1639400885792220820,\n        \"state_name\": \"Up\"\n      },\n      \"status\": {\n        \"capacity\": \"446.8GiB\",\n        \"available\": \"432.7GiB\",\n        \"used_size\": \"4.071GiB\",\n        \"leader_count\": 51,\n        \"leader_weight\": 1,\n        \"leader_score\": 51,\n        \"leader_size\": 2839,\n        \"region_count\": 141,\n        \"region_weight\": 1,\n        \"region_score\": 6111,\n        \"region_size\": 6111,\n        \"start_ts\": \"2021-11-09T09:28:24+08:00\",\n        \"last_heartbeat_ts\": \"2021-12-13T21:08:05.79222082+08:00\",\n        \"uptime\": \"827h39m41.79222082s\"\n      }\n    },\n    {\n      \"store\": {\n        \"id\": 5,\n        \"address\": \"172.16.5.218:20160\",\n        \"version\": \"4.0.14\",\n        \"status_address\": \"172.16.5.218:20180\",\n        \"git_hash\": \"d7dc4fff51ca71c76a928a0780a069efaaeaae70\",\n        \"start_timestamp\": 1636421304,\n        \"deploy_path\": \"/home/tidb/tidb-deploy/tikv-20160/bin\",\n        \"last_heartbeat\": 1639400889610431214,\n        \"state_name\": \"Up\"\n      },\n      \"status\": {\n        \"capacity\": \"446.8GiB\",\n        \"available\": \"432GiB\",\n        \"used_size\": \"4.07GiB\",\n        \"leader_count\": 42,\n        \"leader_weight\": 1,\n        \"leader_score\": 42,\n        \"leader_size\": 1016,\n        \"region_count\": 141,\n        \"region_weight\": 1,\n        \"region_score\": 6111,\n        \"region_size\": 6111,\n        \"start_ts\": \"2021-11-09T09:28:24+08:00\",\n        \"last_heartbeat_ts\": \"2021-12-13T21:08:09.610431214+08:00\",\n        \"uptime\": \"827h39m45.610431214s\"\n      }\n    },\n    {\n      \"store\": {\n        \"id\": 1,\n        \"address\": \"172.16.5.141:20160\",\n        \"version\": \"4.0.14\",\n        \"status_address\": \"172.16.5.141:20180\",\n        \"git_hash\": \"d7dc4fff51ca71c76a928a0780a069efaaeaae70\",\n        \"start_timestamp\": 1636421301,\n        \"deploy_path\": \"/home/tidb/tidb-deploy/tikv-20160/bin\",\n        \"last_heartbeat\": 1639400886447728006,\n        \"state_name\": \"Up\"\n      },\n      \"status\": {\n        \"capacity\": \"446.8GiB\",\n        \"available\": \"409.2GiB\",\n        \"used_size\": \"4.077GiB\",\n        \"leader_count\": 48,\n        \"leader_weight\": 1,\n        \"leader_score\": 48,\n        \"leader_size\": 2256,\n        \"region_count\": 141,\n        \"region_weight\": 1,\n        \"region_score\": 6111,\n        \"region_size\": 6111,\n        \"start_ts\": \"2021-11-09T09:28:21+08:00\",\n        \"last_heartbeat_ts\": \"2021-12-13T21:08:06.447728006+08:00\",\n        \"uptime\": \"827h39m45.447728006s\"\n      }\n    }\n  ]\n}\n`))\n\tmockTransport.RegisterResponder(\"GET\", \"http://172.16.6.171:2379/pd/api/v1/config\",\n\t\thttpmockutil.StringResponder(`\n{\n  \"client-urls\": \"http://0.0.0.0:2379\",\n  \"peer-urls\": \"http://0.0.0.0:2380\",\n  \"advertise-client-urls\": \"http://172.16.6.171:2379\",\n  \"advertise-peer-urls\": \"http://172.16.6.171:2380\",\n  \"name\": \"pd-172.16.6.171-2379\",\n  \"data-dir\": \"/home/tidb/tidb-data/pd-2379\",\n  \"force-new-cluster\": false,\n  \"enable-grpc-gateway\": true,\n  \"initial-cluster\": \"pd-172.16.6.169-2379=http://172.16.6.169:2380,pd-172.16.6.170-2379=http://172.16.6.170:2380,pd-172.16.6.171-2379=http://172.16.6.171:2380\",\n  \"initial-cluster-state\": \"new\",\n  \"initial-cluster-token\": \"pd-cluster\",\n  \"join\": \"\",\n  \"lease\": 3,\n  \"log\": {\n    \"level\": \"\",\n    \"format\": \"text\",\n    \"disable-timestamp\": false,\n    \"file\": {\n      \"filename\": \"/home/tidb/tidb-deploy/pd-2379/log/pd.log\",\n      \"max-size\": 300,\n      \"max-days\": 0,\n      \"max-backups\": 0\n    },\n    \"development\": false,\n    \"disable-caller\": false,\n    \"disable-stacktrace\": false,\n    \"disable-error-verbose\": true,\n    \"sampling\": null\n  },\n  \"tso-save-interval\": \"3s\",\n  \"metric\": {\n    \"job\": \"pd-172.16.6.171-2379\",\n    \"address\": \"\",\n    \"interval\": \"15s\"\n  },\n  \"schedule\": {\n    \"max-snapshot-count\": 3,\n    \"max-pending-peer-count\": 16,\n    \"max-merge-region-size\": 20,\n    \"max-merge-region-keys\": 200000,\n    \"split-merge-interval\": \"1h0m0s\",\n    \"enable-one-way-merge\": \"false\",\n    \"enable-cross-table-merge\": \"false\",\n    \"patrol-region-interval\": \"100ms\",\n    \"max-store-down-time\": \"30m0s\",\n    \"leader-schedule-limit\": 4,\n    \"leader-schedule-policy\": \"count\",\n    \"region-schedule-limit\": 2048,\n    \"replica-schedule-limit\": 64,\n    \"merge-schedule-limit\": 8,\n    \"hot-region-schedule-limit\": 4,\n    \"hot-region-cache-hits-threshold\": 3,\n    \"store-limit\": {\n      \"1\": {\n        \"add-peer\": 15,\n        \"remove-peer\": 15\n      },\n      \"4\": {\n        \"add-peer\": 15,\n        \"remove-peer\": 15\n      },\n      \"5\": {\n        \"add-peer\": 15,\n        \"remove-peer\": 15\n      }\n    },\n    \"tolerant-size-ratio\": 0,\n    \"low-space-ratio\": 0.8,\n    \"high-space-ratio\": 0.7,\n    \"scheduler-max-waiting-operator\": 5,\n    \"enable-remove-down-replica\": \"true\",\n    \"enable-replace-offline-replica\": \"true\",\n    \"enable-make-up-replica\": \"true\",\n    \"enable-remove-extra-replica\": \"true\",\n    \"enable-location-replacement\": \"true\",\n    \"enable-debug-metrics\": \"false\",\n    \"schedulers-v2\": [\n      {\n        \"type\": \"balance-region\",\n        \"args\": null,\n        \"disable\": false,\n        \"args-payload\": \"\"\n      },\n      {\n        \"type\": \"balance-leader\",\n        \"args\": null,\n        \"disable\": false,\n        \"args-payload\": \"\"\n      },\n      {\n        \"type\": \"hot-region\",\n        \"args\": null,\n        \"disable\": false,\n        \"args-payload\": \"\"\n      },\n      {\n        \"type\": \"label\",\n        \"args\": null,\n        \"disable\": false,\n        \"args-payload\": \"\"\n      }\n    ],\n    \"schedulers-payload\": {\n      \"balance-hot-region-scheduler\": null,\n      \"balance-leader-scheduler\": {\n        \"name\": \"balance-leader-scheduler\",\n        \"ranges\": [\n          {\n            \"end-key\": \"\",\n            \"start-key\": \"\"\n          }\n        ]\n      },\n      \"balance-region-scheduler\": {\n        \"name\": \"balance-region-scheduler\",\n        \"ranges\": [\n          {\n            \"end-key\": \"\",\n            \"start-key\": \"\"\n          }\n        ]\n      },\n      \"label-scheduler\": {\n        \"name\": \"label-scheduler\",\n        \"ranges\": [\n          {\n            \"end-key\": \"\",\n            \"start-key\": \"\"\n          }\n        ]\n      }\n    },\n    \"store-limit-mode\": \"manual\"\n  },\n  \"replication\": {\n    \"max-replicas\": 3,\n    \"location-labels\": \"\",\n    \"strictly-match-label\": \"false\",\n    \"enable-placement-rules\": \"false\"\n  },\n  \"pd-server\": {\n    \"use-region-storage\": \"true\",\n    \"max-gap-reset-ts\": \"24h0m0s\",\n    \"key-type\": \"table\",\n    \"runtime-services\": \"\",\n    \"metric-storage\": \"\",\n    \"dashboard-address\": \"http://172.16.6.169:2379\",\n    \"trace-region-flow\": \"true\"\n  },\n  \"cluster-version\": \"4.0.14\",\n  \"quota-backend-bytes\": \"8GiB\",\n  \"auto-compaction-mode\": \"periodic\",\n  \"auto-compaction-retention-v2\": \"1h\",\n  \"TickInterval\": \"500ms\",\n  \"ElectionInterval\": \"3s\",\n  \"PreVote\": true,\n  \"security\": {\n    \"cacert-path\": \"\",\n    \"cert-path\": \"\",\n    \"key-path\": \"\",\n    \"cert-allowed-cn\": null\n  },\n  \"label-property\": {},\n  \"WarningMsgs\": null,\n  \"DisableStrictReconfigCheck\": false,\n  \"HeartbeatStreamBindInterval\": \"1m0s\",\n  \"LeaderPriorityCheckInterval\": \"1m0s\",\n  \"dashboard\": {\n    \"tidb-cacert-path\": \"\",\n    \"tidb-cert-path\": \"\",\n    \"tidb-key-path\": \"\",\n    \"public-path-prefix\": \"\",\n    \"internal-proxy\": false,\n    \"enable-telemetry\": true,\n    \"enable-experimental\": false\n  },\n  \"replication-mode\": {\n    \"replication-mode\": \"majority\",\n    \"dr-auto-sync\": {\n      \"label-key\": \"\",\n      \"primary\": \"\",\n      \"dr\": \"\",\n      \"primary-replicas\": 0,\n      \"dr-replicas\": 0,\n      \"wait-store-timeout\": \"1m0s\",\n      \"wait-sync-timeout\": \"1m0s\"\n    }\n  },\n  \"enable-redact-log\": false\n}\n`))\n\tmockTransport.RegisterResponder(\"GET\", \"http://172.16.6.171:2379/pd/api/v1/config/replicate\",\n\t\thttpmockutil.StringResponder(`\n{\n  \"max-replicas\": 3,\n  \"location-labels\": \"\",\n  \"strictly-match-label\": \"false\",\n  \"enable-placement-rules\": \"false\"\n}\n`))\n\treturn\n}\n\n// NewAPIClientFixture returns a PD client whose default Base URL is pointing to a mock PD server.\nfunc NewAPIClientFixture() *pdclient.APIClient {\n\tmockTransport := NewPDServerFixture()\n\tapiClient := pdclient.NewAPIClient(httpclient.Config{})\n\tapiClient.SetDefaultBaseURL(BaseURL)\n\tapiClient.SetDefaultTransport(mockTransport)\n\treturn apiClient\n}\n"
  },
  {
    "path": "util/client/pdclient/pd_api.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdclient\n\nimport (\n\t\"context\"\n)\n\n// TODO: Switch to use swagger.\n\nconst APIPrefix = \"/pd/api/v1\"\n\ntype GetStatusResponse struct {\n\tStartTimestamp int64 `json:\"start_timestamp\"`\n}\n\n// GetStatus returns the content from /status PD API.\n// You must specify the base URL by calling SetDefaultBaseURL() before using this function.\nfunc (api *APIClient) GetStatus(ctx context.Context) (resp *GetStatusResponse, err error) {\n\t_, err = api.LR().SetContext(ctx).Get(APIPrefix + \"/status\").ReadBodyAsJSON(&resp)\n\treturn\n}\n\ntype GetHealthResponseMember struct {\n\tMemberID uint64 `json:\"member_id\"`\n\tHealth   bool   `json:\"health\"`\n}\n\ntype GetHealthResponse []GetHealthResponseMember\n\n// GetHealth returns the content from /health PD API.\n// You must specify the base URL by calling SetDefaultBaseURL() before using this function.\nfunc (api *APIClient) GetHealth(ctx context.Context) (resp *GetHealthResponse, err error) {\n\t_, err = api.LR().SetContext(ctx).Get(APIPrefix + \"/health\").ReadBodyAsJSON(&resp)\n\treturn\n}\n\ntype GetMembersResponseMember struct {\n\tGitHash       string   `json:\"git_hash\"`\n\tClientUrls    []string `json:\"client_urls\"`\n\tDeployPath    string   `json:\"deploy_path\"`\n\tBinaryVersion string   `json:\"binary_version\"`\n\tMemberID      uint64   `json:\"member_id\"`\n}\n\ntype GetMembersResponse struct {\n\tMembers []GetMembersResponseMember `json:\"members\"`\n}\n\n// GetMembers returns the content from /members PD API.\n// You must specify the base URL by calling SetDefaultBaseURL() before using this function.\nfunc (api *APIClient) GetMembers(ctx context.Context) (resp *GetMembersResponse, err error) {\n\t_, err = api.LR().SetContext(ctx).Get(APIPrefix + \"/members\").ReadBodyAsJSON(&resp)\n\treturn\n}\n\ntype GetConfigReplicateResponse struct {\n\tLocationLabels string `json:\"location-labels\"`\n}\n\n// GetConfigReplicate returns the content from /config/replicate PD API.\n// You must specify the base URL by calling SetDefaultBaseURL() before using this function.\nfunc (api *APIClient) GetConfigReplicate(ctx context.Context) (resp *GetConfigReplicateResponse, err error) {\n\t_, err = api.LR().SetContext(ctx).Get(APIPrefix + \"/config/replicate\").ReadBodyAsJSON(&resp)\n\treturn\n}\n\ntype GetStoresResponseStoreLabel struct {\n\tKey   string `json:\"key\"`\n\tValue string `json:\"value\"`\n}\n\ntype GetStoresResponseStore struct {\n\tAddress        string                        `json:\"address\"`\n\tID             int                           `json:\"id\"`\n\tLabels         []GetStoresResponseStoreLabel `json:\"labels\"`\n\tStateName      string                        `json:\"state_name\"`\n\tVersion        string                        `json:\"version\"`\n\tStatusAddress  string                        `json:\"status_address\"`\n\tGitHash        string                        `json:\"git_hash\"`\n\tDeployPath     string                        `json:\"deploy_path\"`\n\tStartTimestamp int64                         `json:\"start_timestamp\"`\n}\n\ntype GetStoresResponseStoresElem struct {\n\tStore GetStoresResponseStore `json:\"store\"`\n}\n\ntype GetStoresResponse struct {\n\tStores []GetStoresResponseStoresElem `json:\"stores\"`\n}\n\n// GetStores returns the content from /stores PD API.\n// You must specify the base URL by calling SetDefaultBaseURL() before using this function.\nfunc (api *APIClient) GetStores(ctx context.Context) (resp *GetStoresResponse, err error) {\n\t_, err = api.LR().SetContext(ctx).Get(APIPrefix + \"/stores\").ReadBodyAsJSON(&resp)\n\treturn\n}\n"
  },
  {
    "path": "util/client/pdclient/pd_api_client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package pdclient provides a flexible PD API access to any PD instance.\npackage pdclient\n\nimport (\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\ntype APIClient struct {\n\t*httpclient.Client\n}\n\nfunc NewAPIClient(config httpclient.Config) *APIClient {\n\tconfig.KindTag = distro.R().PD\n\treturn &APIClient{httpclient.New(config)}\n}\n\nfunc (c *APIClient) Clone() *APIClient {\n\treturn &APIClient{c.Client.Clone()}\n}\n"
  },
  {
    "path": "util/client/pdclient/pd_api_highlevel.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// This file contains high level encapsulations over base PD APIs.\n\npackage pdclient\n\nimport (\n\t\"context\"\n\t\"sort\"\n\t\"strings\"\n)\n\n// HLGetStores returns all stores in PD in order.\n// You must specify the base URL by calling SetDefaultBaseURL() before using this function.\nfunc (api *APIClient) HLGetStores(ctx context.Context) ([]GetStoresResponseStore, error) {\n\tresp, err := api.GetStores(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tstores := make([]GetStoresResponseStore, 0, len(resp.Stores))\n\tfor _, s := range resp.Stores {\n\t\tstores = append(stores, s.Store)\n\t}\n\tsort.Slice(stores, func(i, j int) bool {\n\t\treturn stores[i].Address < stores[j].Address\n\t})\n\treturn stores, nil\n}\n\n// HLGetLocationLabels returns the location label config in PD.\n// You must specify the base URL by calling SetDefaultBaseURL() before using this function.\nfunc (api *APIClient) HLGetLocationLabels(ctx context.Context) ([]string, error) {\n\tresp, err := api.GetConfigReplicate(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(resp.LocationLabels) == 0 {\n\t\treturn []string{}, nil\n\t}\n\tlabels := strings.Split(resp.LocationLabels, \",\")\n\treturn labels, nil\n}\n\ntype StoreLabels struct {\n\tAddress string            `json:\"address\"`\n\tLabels  map[string]string `json:\"labels\"`\n}\n\ntype StoreLocations struct {\n\tLocationLabels []string      `json:\"location_labels\"`\n\tStores         []StoreLabels `json:\"stores\"`\n}\n\n// HLGetStoreLocations returns the stores and their locations.\n// You must specify the base URL by calling SetDefaultBaseURL() before using this function.\nfunc (api *APIClient) HLGetStoreLocations(ctx context.Context) (*StoreLocations, error) {\n\tlocationLabels, err := api.HLGetLocationLabels(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstores, err := api.HLGetStores(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tnodes := make([]StoreLabels, 0, len(stores))\n\tfor _, s := range stores {\n\t\tnode := StoreLabels{\n\t\t\tAddress: s.Address,\n\t\t\tLabels:  map[string]string{},\n\t\t}\n\t\tfor _, l := range s.Labels {\n\t\t\tnode.Labels[l.Key] = l.Value\n\t\t}\n\t\tnodes = append(nodes, node)\n\t}\n\n\treturn &StoreLocations{\n\t\tLocationLabels: locationLabels,\n\t\tStores:         nodes,\n\t}, nil\n}\n"
  },
  {
    "path": "util/client/pdclient/pd_api_highlevel_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdclient_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/client/pdclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/pdclient/fixture\"\n)\n\nfunc TestAPIClient_HLGetLocationLabels(t *testing.T) {\n\tapiClient := fixture.NewAPIClientFixture()\n\tresp, err := apiClient.HLGetLocationLabels(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, []string{}, resp)\n}\n\nfunc TestAPIClient_HLGetStoreLocations(t *testing.T) {\n\tapiClient := fixture.NewAPIClientFixture()\n\tresp, err := apiClient.HLGetStoreLocations(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, &pdclient.StoreLocations{\n\t\tLocationLabels: []string{},\n\t\tStores: []pdclient.StoreLabels{\n\t\t\t{Address: \"172.16.5.141:20160\", Labels: map[string]string{}},\n\t\t\t{Address: \"172.16.5.218:20160\", Labels: map[string]string{}},\n\t\t\t{Address: \"172.16.6.168:20160\", Labels: map[string]string{}},\n\t\t},\n\t}, resp)\n}\n\nfunc TestAPIClient_HLGetStores(t *testing.T) {\n\tapiClient := fixture.NewAPIClientFixture()\n\tresp, err := apiClient.HLGetStores(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, []pdclient.GetStoresResponseStore{\n\t\t{\n\t\t\tAddress:        \"172.16.5.141:20160\",\n\t\t\tID:             1,\n\t\t\tLabels:         nil,\n\t\t\tStateName:      \"Up\",\n\t\t\tVersion:        \"4.0.14\",\n\t\t\tStatusAddress:  \"172.16.5.141:20180\",\n\t\t\tGitHash:        \"d7dc4fff51ca71c76a928a0780a069efaaeaae70\",\n\t\t\tDeployPath:     \"/home/tidb/tidb-deploy/tikv-20160/bin\",\n\t\t\tStartTimestamp: 1636421301,\n\t\t},\n\t\t{\n\t\t\tAddress:        \"172.16.5.218:20160\",\n\t\t\tID:             5,\n\t\t\tLabels:         nil,\n\t\t\tStateName:      \"Up\",\n\t\t\tVersion:        \"4.0.14\",\n\t\t\tStatusAddress:  \"172.16.5.218:20180\",\n\t\t\tGitHash:        \"d7dc4fff51ca71c76a928a0780a069efaaeaae70\",\n\t\t\tDeployPath:     \"/home/tidb/tidb-deploy/tikv-20160/bin\",\n\t\t\tStartTimestamp: 1636421304,\n\t\t},\n\t\t{\n\t\t\tAddress:        \"172.16.6.168:20160\",\n\t\t\tID:             4,\n\t\t\tLabels:         nil,\n\t\t\tStateName:      \"Up\",\n\t\t\tVersion:        \"4.0.14\",\n\t\t\tStatusAddress:  \"172.16.6.168:20180\",\n\t\t\tGitHash:        \"d7dc4fff51ca71c76a928a0780a069efaaeaae70\",\n\t\t\tDeployPath:     \"/home/tidb/tidb-deploy/tikv-20160/bin\",\n\t\t\tStartTimestamp: 1636421304,\n\t\t},\n\t}, resp)\n}\n"
  },
  {
    "path": "util/client/pdclient/pd_api_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdclient_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/client/pdclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/client/pdclient/fixture\"\n)\n\nfunc TestAPIClient_GetConfigReplicate(t *testing.T) {\n\tapiClient := fixture.NewAPIClientFixture()\n\tresp, err := apiClient.GetConfigReplicate(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, &pdclient.GetConfigReplicateResponse{\n\t\tLocationLabels: \"\",\n\t}, resp)\n}\n\nfunc TestAPIClient_GetHealth(t *testing.T) {\n\tapiClient := fixture.NewAPIClientFixture()\n\tresp, err := apiClient.GetHealth(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, &pdclient.GetHealthResponse{\n\t\tpdclient.GetHealthResponseMember{MemberID: 0x28cb7236f465dbeb, Health: true},\n\t\tpdclient.GetHealthResponseMember{MemberID: 0x79cc97f3bcb16deb, Health: true},\n\t\tpdclient.GetHealthResponseMember{MemberID: 0xb7da90b338a3eab3, Health: true},\n\t}, resp)\n}\n\nfunc TestAPIClient_GetMembers(t *testing.T) {\n\tapiClient := fixture.NewAPIClientFixture()\n\tresp, err := apiClient.GetMembers(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, &pdclient.GetMembersResponse{\n\t\tMembers: []pdclient.GetMembersResponseMember{\n\t\t\t{\n\t\t\t\tGitHash:       \"0c1246dd219fd16b4b2ff5108941e5d3e958922d\",\n\t\t\t\tClientUrls:    []string{\"http://172.16.6.170:2379\"},\n\t\t\t\tDeployPath:    \"/home/tidb/tidb-deploy/pd-2379/bin\",\n\t\t\t\tBinaryVersion: \"v4.0.14\",\n\t\t\t\tMemberID:      0x28cb7236f465dbeb,\n\t\t\t}, {\n\t\t\t\tGitHash:       \"0c1246dd219fd16b4b2ff5108941e5d3e958922d\",\n\t\t\t\tClientUrls:    []string{\"http://172.16.6.169:2379\"},\n\t\t\t\tDeployPath:    \"/home/tidb/tidb-deploy/pd-2379/bin\",\n\t\t\t\tBinaryVersion: \"v4.0.14\",\n\t\t\t\tMemberID:      0x79cc97f3bcb16deb,\n\t\t\t}, {\n\t\t\t\tGitHash:       \"0c1246dd219fd16b4b2ff5108941e5d3e958922d\",\n\t\t\t\tClientUrls:    []string{\"http://172.16.6.171:2379\"},\n\t\t\t\tDeployPath:    \"/home/tidb/tidb-deploy/pd-2379/bin\",\n\t\t\t\tBinaryVersion: \"v4.0.14\",\n\t\t\t\tMemberID:      0xb7da90b338a3eab3,\n\t\t\t},\n\t\t},\n\t}, resp)\n}\n\nfunc TestAPIClient_GetStatus(t *testing.T) {\n\tapiClient := fixture.NewAPIClientFixture()\n\tresp, err := apiClient.GetStatus(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, &pdclient.GetStatusResponse{\n\t\tStartTimestamp: 1635762685,\n\t}, resp)\n}\n\nfunc TestAPIClient_GetStores(t *testing.T) {\n\tapiClient := fixture.NewAPIClientFixture()\n\tresp, err := apiClient.GetStores(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, &pdclient.GetStoresResponse{\n\t\tStores: []pdclient.GetStoresResponseStoresElem{\n\t\t\t{\n\t\t\t\tStore: pdclient.GetStoresResponseStore{\n\t\t\t\t\tAddress:        \"172.16.6.168:20160\",\n\t\t\t\t\tID:             4,\n\t\t\t\t\tLabels:         nil,\n\t\t\t\t\tStateName:      \"Up\",\n\t\t\t\t\tVersion:        \"4.0.14\",\n\t\t\t\t\tStatusAddress:  \"172.16.6.168:20180\",\n\t\t\t\t\tGitHash:        \"d7dc4fff51ca71c76a928a0780a069efaaeaae70\",\n\t\t\t\t\tDeployPath:     \"/home/tidb/tidb-deploy/tikv-20160/bin\",\n\t\t\t\t\tStartTimestamp: 1636421304,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tStore: pdclient.GetStoresResponseStore{\n\t\t\t\t\tAddress:        \"172.16.5.218:20160\",\n\t\t\t\t\tID:             5,\n\t\t\t\t\tLabels:         nil,\n\t\t\t\t\tStateName:      \"Up\",\n\t\t\t\t\tVersion:        \"4.0.14\",\n\t\t\t\t\tStatusAddress:  \"172.16.5.218:20180\",\n\t\t\t\t\tGitHash:        \"d7dc4fff51ca71c76a928a0780a069efaaeaae70\",\n\t\t\t\t\tDeployPath:     \"/home/tidb/tidb-deploy/tikv-20160/bin\",\n\t\t\t\t\tStartTimestamp: 1636421304,\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tStore: pdclient.GetStoresResponseStore{\n\t\t\t\t\tAddress:        \"172.16.5.141:20160\",\n\t\t\t\t\tID:             1,\n\t\t\t\t\tLabels:         nil,\n\t\t\t\t\tStateName:      \"Up\",\n\t\t\t\t\tVersion:        \"4.0.14\",\n\t\t\t\t\tStatusAddress:  \"172.16.5.141:20180\",\n\t\t\t\t\tGitHash:        \"d7dc4fff51ca71c76a928a0780a069efaaeaae70\",\n\t\t\t\t\tDeployPath:     \"/home/tidb/tidb-deploy/tikv-20160/bin\",\n\t\t\t\t\tStartTimestamp: 1636421301,\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}, resp)\n}\n"
  },
  {
    "path": "util/client/schedulingclient/status_client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package schedulingclient provides a flexible Scheduling API access to any Scheduling instance.\npackage schedulingclient\n\nimport (\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\ntype StatusClient struct {\n\t*httpclient.Client\n}\n\nfunc NewStatusClient(config httpclient.Config) *StatusClient {\n\tconfig.KindTag = distro.R().Scheduling\n\treturn &StatusClient{httpclient.New(config)}\n}\n\nfunc (c *StatusClient) Clone() *StatusClient {\n\treturn &StatusClient{c.Client.Clone()}\n}\n"
  },
  {
    "path": "util/client/ticdcclient/status_client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package ticdcclient provides a flexible TiCDC API access to any TiCDC instance.\npackage ticdcclient\n\nimport (\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\ntype StatusClient struct {\n\t*httpclient.Client\n}\n\nfunc NewStatusClient(config httpclient.Config) *StatusClient {\n\tconfig.KindTag = distro.R().TiCDC\n\treturn &StatusClient{httpclient.New(config)}\n}\n\nfunc (c *StatusClient) Clone() *StatusClient {\n\treturn &StatusClient{c.Client.Clone()}\n}\n"
  },
  {
    "path": "util/client/tidbclient/sql_client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tidbclient\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"net\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"github.com/VividCortex/mysqlerr\"\n\t\"github.com/go-sql-driver/mysql\"\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\tmysqlDriver \"gorm.io/driver/mysql\"\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\nvar (\n\tErrNS = errorx.NewNamespace(\"tidb_client\")\n\t// ErrAuthFailed means the authentication is failed when connecting to the TiDB Server.\n\tErrAuthFailed = ErrNS.NewType(\"tidb_auth_failed\")\n\t// ErrConnFailed means there is a connection (like network) problem when connecting to the TiDB Server.\n\tErrConnFailed = ErrNS.NewType(\"tidb_conn_failed\")\n)\n\ntype SQLClientConfig struct {\n\tBaseContext context.Context\n\tHost        string\n\tPort        int\n\tTLSKey      string\n}\n\ntype SQLClient struct {\n\tconfig SQLClientConfig\n}\n\nfunc NewSQLClient(config SQLClientConfig) *SQLClient {\n\tclient := &SQLClient{\n\t\tconfig: config,\n\t}\n\treturn client\n}\n\n// OpenConn opens a new connection.\n// NOTICE: The opened connection must be manually closed.\nfunc (c *SQLClient) OpenConn(user string, pass string) (*gorm.DB, error) {\n\tdsnConfig := mysql.NewConfig()\n\tdsnConfig.Net = \"tcp\"\n\tdsnConfig.Addr = net.JoinHostPort(c.config.Host, strconv.Itoa(c.config.Port))\n\tdsnConfig.User = user\n\tdsnConfig.Passwd = pass\n\tdsnConfig.Timeout = time.Second * 60\n\tdsnConfig.ParseTime = true\n\tdsnConfig.Loc = time.Local\n\tdsnConfig.MultiStatements = true // TODO: Disable this, as it increase security risk.\n\tdsnConfig.TLSConfig = c.config.TLSKey\n\tdsn := dsnConfig.FormatDSN()\n\n\tdb, err := gorm.Open(mysqlDriver.Open(dsn))\n\tif err != nil {\n\t\tlog.Warn(\"Failed to open SQL connection\",\n\t\t\tzap.String(\"targetComponent\", distro.R().TiDB),\n\t\t\tzap.Error(err))\n\t\tvar mysqlErr *mysql.MySQLError\n\t\tif errors.As(err, &mysqlErr) {\n\t\t\tif mysqlErr.Number == mysqlerr.ER_ACCESS_DENIED_ERROR {\n\t\t\t\treturn nil, ErrAuthFailed.New(\"Bad SQL username or password\")\n\t\t\t}\n\t\t}\n\t\treturn nil, ErrConnFailed.Wrap(err, \"Failed to connect to %s\", distro.R().TiDB)\n\t}\n\n\t// Ensure that when the App stops resources are released\n\tif c.config.BaseContext != nil {\n\t\tdb = db.WithContext(c.config.BaseContext)\n\t}\n\n\treturn db, nil\n}\n"
  },
  {
    "path": "util/client/tidbclient/status_client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package tidbclient provides a flexible TiDB API access to any TiDB instance.\npackage tidbclient\n\nimport (\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\ntype StatusClient struct {\n\t*httpclient.Client\n}\n\nfunc NewStatusClient(config httpclient.Config) *StatusClient {\n\tconfig.KindTag = distro.R().TiDB\n\treturn &StatusClient{httpclient.New(config)}\n}\n\nfunc (c *StatusClient) Clone() *StatusClient {\n\treturn &StatusClient{c.Client.Clone()}\n}\n"
  },
  {
    "path": "util/client/tidbclient/tidbproto/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tidbproto\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/client/tidbclient/tidbproto/codec.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tidbproto\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\n\t\"github.com/pingcap/errors\"\n)\n\nvar (\n\ttablePrefix  = []byte{'t'}\n\tmetaPrefix   = []byte{'m'}\n\trecordPrefix = []byte{'r'}\n)\n\nconst (\n\tsignMask uint64 = 0x8000000000000000\n\n\tencGroupSize = 8\n\tencMarker    = byte(0xFF)\n\tencPad       = byte(0x0)\n)\n\n// Key represents high-level TiDB Key type.\ntype Key []byte\n\n// KeyInfoBuffer can obtain the meta information of the TiDB Key.\n// It can be reused, thereby reducing memory applications.\ntype KeyInfoBuffer []byte\n\n// DecodeKey obtains the KeyInfoBuffer from a TiDB Key.\nfunc (buf *KeyInfoBuffer) DecodeKey(key Key) (KeyInfoBuffer, error) {\n\t_, result, err := decodeBytes(key, *buf)\n\tif err != nil {\n\t\t*buf = (*buf)[:0]\n\t\treturn nil, err\n\t}\n\n\t*buf = result\n\treturn result, nil\n}\n\n// MetaOrTable checks if the key is a meta key or table key.\n// If the key is a meta key, it returns true and 0.\n// If the key is a table key, it returns false and table ID.\n// Otherwise, it returns false and 0.\nfunc (buf KeyInfoBuffer) MetaOrTable() (isMeta bool, tableID int64) {\n\tif bytes.HasPrefix(buf, metaPrefix) {\n\t\treturn true, 0\n\t}\n\tif bytes.HasPrefix(buf, tablePrefix) {\n\t\t_, tableID, _ := decodeInt(buf[len(tablePrefix):])\n\t\treturn false, tableID\n\t}\n\treturn false, 0\n}\n\n// RowInfo returns the row ID of the key, if the key is not table key, returns 0.\nfunc (buf KeyInfoBuffer) RowInfo() (isCommonHandle bool, rowID int64) {\n\tif !bytes.HasPrefix(buf, tablePrefix) || len(buf) < 19 || (buf[9] != '_' || buf[10] != 'r') {\n\t\treturn\n\t}\n\tisCommonHandle = len(buf) != 19\n\tif !isCommonHandle {\n\t\t_, rowID, _ = decodeInt(buf[11:19])\n\t}\n\treturn\n}\n\n// IndexInfo returns the row ID of the key, if the key is not table key, returns 0.\nfunc (buf KeyInfoBuffer) IndexInfo() (indexID int64) {\n\tif !bytes.HasPrefix(buf, tablePrefix) || len(buf) < 19 || (buf[9] != '_' || buf[10] != 'i') {\n\t\treturn\n\t}\n\t_, indexID, _ = decodeInt(buf[11:19])\n\treturn\n}\n\n// GenerateTableKey generates a table split key.\nfunc (buf *KeyInfoBuffer) GenerateKey(tableID, rowID int64) Key {\n\tif tableID == 0 {\n\t\treturn nil\n\t}\n\n\tdata := *buf\n\tif data == nil {\n\t\tlength := len(tablePrefix) + 8\n\t\tif rowID != 0 {\n\t\t\tlength = len(tablePrefix) + len(recordPrefix) + 8*2\n\t\t}\n\t\tdata = make([]byte, 0, length)\n\t} else {\n\t\tdata = data[:0]\n\t}\n\n\tdata = append(data, tablePrefix...)\n\tdata = encodeInt(data, tableID)\n\tif rowID != 0 {\n\t\tdata = append(data, recordPrefix...)\n\t\tdata = encodeInt(data, rowID)\n\t}\n\n\t*buf = data\n\n\treturn encodeBytes(data)\n}\n\nvar pads = make([]byte, encGroupSize)\n\n// decodeBytes decodes bytes which is encoded by encodeBytes before,\n// returns the leftover bytes and decoded value if no error.\nfunc decodeBytes(b []byte, buf []byte) (rest []byte, result []byte, err error) {\n\tif buf == nil {\n\t\tbuf = make([]byte, 0, len(b))\n\t}\n\tbuf = buf[:0]\n\n\tfor {\n\t\tif len(b) < encGroupSize+1 {\n\t\t\treturn nil, nil, errors.New(\"insufficient bytes to decode value\")\n\t\t}\n\n\t\tgroupBytes := b[:encGroupSize+1]\n\n\t\tgroup := groupBytes[:encGroupSize]\n\t\tmarker := groupBytes[encGroupSize]\n\n\t\tpadCount := encMarker - marker\n\t\tif padCount > encGroupSize {\n\t\t\treturn nil, nil, errors.Errorf(\"invalid marker byte, group bytes %q\", groupBytes)\n\t\t}\n\n\t\trealGroupSize := encGroupSize - padCount\n\t\tbuf = append(buf, group[:realGroupSize]...)\n\t\tb = b[encGroupSize+1:]\n\n\t\tif padCount != 0 {\n\t\t\t// Check validity of padding bytes.\n\t\t\tfor _, v := range group[realGroupSize:] {\n\t\t\t\tif v != encPad {\n\t\t\t\t\treturn nil, nil, errors.Errorf(\"invalid padding byte, group bytes %q\", groupBytes)\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n\n\treturn b, buf, nil\n}\n\n// encodeBytes guarantees the encoded value is in ascending order for comparison,\n// encoding with the following rule:\n//\n//\t[group1][marker1]...[groupN][markerN]\n//\tgroup is 8 bytes slice which is padding with 0.\n//\tmarker is `0xFF - padding 0 count`\n//\n// For example:\n//\n//\t[] -> [0, 0, 0, 0, 0, 0, 0, 0, 247]\n//\t[1, 2, 3] -> [1, 2, 3, 0, 0, 0, 0, 0, 250]\n//\t[1, 2, 3, 0] -> [1, 2, 3, 0, 0, 0, 0, 0, 251]\n//\t[1, 2, 3, 4, 5, 6, 7, 8] -> [1, 2, 3, 4, 5, 6, 7, 8, 255, 0, 0, 0, 0, 0, 0, 0, 0, 247]\n//\n// Refer: https://github.com/facebook/mysql-5.6/wiki/MyRocks-record-format#memcomparable-format\nfunc encodeBytes(data []byte) []byte {\n\t// Allocate more space to avoid unnecessary slice growing.\n\t// Assume that the byte slice size is about `(len(data) / encGroupSize + 1) * (encGroupSize + 1)` bytes,\n\t// that is `(len(data) / 8 + 1) * 9` in our implement.\n\tdLen := len(data)\n\tresult := make([]byte, 0, (dLen/encGroupSize+1)*(encGroupSize+1))\n\tfor idx := 0; idx <= dLen; idx += encGroupSize {\n\t\tremain := dLen - idx\n\t\tpadCount := 0\n\t\tif remain >= encGroupSize {\n\t\t\tresult = append(result, data[idx:idx+encGroupSize]...)\n\t\t} else {\n\t\t\tpadCount = encGroupSize - remain\n\t\t\tresult = append(result, data[idx:]...)\n\t\t\tresult = append(result, pads[:padCount]...)\n\t\t}\n\n\t\tmarker := encMarker - byte(padCount)\n\t\tresult = append(result, marker)\n\t}\n\treturn result\n}\n\n// decodeInt decodes value encoded by EncodeInt before.\n// It returns the leftover un-decoded slice, decoded value if no error.\nfunc decodeInt(b []byte) ([]byte, int64, error) {\n\tif len(b) < 8 {\n\t\treturn nil, 0, errors.New(\"insufficient bytes to decode value\")\n\t}\n\n\tu := binary.BigEndian.Uint64(b[:8])\n\tv := decodeCmpUintToInt(u)\n\tb = b[8:]\n\treturn b, v, nil\n}\n\n// encodeInt appends the encoded value to slice b and returns the appended slice.\n// encodeInt guarantees that the encoded value is in ascending order for comparison.\nfunc encodeInt(b []byte, v int64) []byte {\n\tvar data [8]byte\n\tu := encodeIntToCmpUint(v)\n\tbinary.BigEndian.PutUint64(data[:], u)\n\treturn append(b, data[:]...)\n}\n\nfunc decodeCmpUintToInt(u uint64) int64 {\n\treturn int64(u ^ signMask)\n}\n\nfunc encodeIntToCmpUint(v int64) uint64 {\n\treturn uint64(v) ^ signMask\n}\n"
  },
  {
    "path": "util/client/tidbclient/tidbproto/codec_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tidbproto\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestDecodeBytes(t *testing.T) {\n\tkey := \"abcdefghijklmnopqrstuvwxyz\"\n\tfor i := 0; i < len(key); i++ {\n\t\t_, k, err := decodeBytes(encodeBytes([]byte(key[:i])), nil)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, string(k), key[:i])\n\t}\n}\n\nfunc TestTiDBInfo(t *testing.T) {\n\tbuf := new(KeyInfoBuffer)\n\n\t// no encode\n\t_, err := buf.DecodeKey([]byte(\"t\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\xff\"))\n\trequire.Error(t, err)\n\n\ttestcases := []struct {\n\t\tKey            string\n\t\tIsMeta         bool\n\t\tTableID        int64\n\t\tIsCommonHandle bool\n\t\tRowID          int64\n\t\tIndexID        int64\n\t}{\n\t\t{\n\t\t\t\"T\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xff\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"t\\x80\\x00\\x00\\x00\\x00\\x00\\xff\",\n\t\t\tfalse,\n\t\t\t0,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"t\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\xff\",\n\t\t\tfalse,\n\t\t\t0xff,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"t\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\xff_i\\x01\\x02\",\n\t\t\tfalse,\n\t\t\t0xff,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"t\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\xff_i\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x02\",\n\t\t\tfalse,\n\t\t\t0xff,\n\t\t\tfalse,\n\t\t\t0,\n\t\t\t2,\n\t\t},\n\t\t{\n\t\t\t\"t\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\xff_r\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x02\",\n\t\t\tfalse,\n\t\t\t0xff,\n\t\t\tfalse,\n\t\t\t2,\n\t\t\t0,\n\t\t},\n\t\t{\n\t\t\t\"t\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\xff_r\\x03\\x80\\x00\\x00\\x00\\x00\\x02\\r\\xaf\\x03\\x80\\x00\\x00\\x00\\x00\\x00\\x00\\x03\\x03\\x80\\x00\\x00\\x00\\x00\\x00\\b%\",\n\t\t\tfalse,\n\t\t\t0xff,\n\t\t\ttrue,\n\t\t\t0,\n\t\t\t0,\n\t\t},\n\t}\n\n\tfor _, testcase := range testcases {\n\t\tkey := encodeBytes([]byte(testcase.Key))\n\t\t_, err := buf.DecodeKey(key)\n\t\trequire.NoError(t, err)\n\t\tisMeta, tableID := buf.MetaOrTable()\n\t\trequire.Equal(t, testcase.IsMeta, isMeta)\n\t\trequire.Equal(t, testcase.TableID, tableID)\n\t\tisCommonHandle, rowID := buf.RowInfo()\n\t\trequire.Equal(t, testcase.IsCommonHandle, isCommonHandle)\n\t\trequire.Equal(t, testcase.RowID, rowID)\n\t\tindexID := buf.IndexInfo()\n\t\trequire.Equal(t, testcase.IndexID, indexID)\n\t}\n}\n"
  },
  {
    "path": "util/client/tidbclient/tidbproto/model.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tidbproto\n\n// SchemaState is the state for schema elements.\ntype SchemaState byte\n\nconst (\n\t// StateNone means this schema element is absent and can't be used.\n\tStateNone SchemaState = iota\n\t// StateDeleteOnly means we can only delete items for this schema element.\n\tStateDeleteOnly\n\t// StateWriteOnly means we can use any write operation on this schema element,\n\t// but outer can't read the changed data.\n\tStateWriteOnly\n\t// StateWriteReorganization means we are re-organizing whole data after write only state.\n\tStateWriteReorganization\n\t// StateDeleteReorganization means we are re-organizing whole data after delete only state.\n\tStateDeleteReorganization\n\t// StatePublic means this schema element is ok for all write and read operations.\n\tStatePublic\n)\n\n// CIStr is case insensitive string.\ntype CIStr struct {\n\tO string `json:\"O\"` // Original string.\n\tL string `json:\"L\"` // Lower case string.\n}\n\n// DBInfo provides meta data describing a DB.\ntype DBInfo struct {\n\tID    int64       `json:\"id\"`\n\tName  CIStr       `json:\"db_name\"`\n\tState SchemaState `json:\"state\"`\n}\n\n// IndexInfo provides meta data describing a DB index.\n// It corresponds to the statement `CREATE INDEX Name ON Table (Column);`\n// See https://dev.mysql.com/doc/refman/5.7/en/create-index.html\ntype IndexInfo struct {\n\tID   int64 `json:\"id\"`\n\tName CIStr `json:\"idx_name\"`\n}\n\n// PartitionDefinition defines a single partition.\ntype PartitionDefinition struct {\n\tID   int64 `json:\"id\"`\n\tName CIStr `json:\"name\"`\n}\n\n// PartitionInfo provides table partition info.\ntype PartitionInfo struct {\n\t// User may already creates table with partition but table partition is not\n\t// yet supported back then. When Enable is true, write/read need use tid\n\t// rather than pid.\n\tEnable      bool                   `json:\"enable\"`\n\tDefinitions []*PartitionDefinition `json:\"definitions\"`\n}\n\n// TableInfo provides meta data describing a DB table.\ntype TableInfo struct {\n\tID        int64          `json:\"id\"`\n\tName      CIStr          `json:\"name\"`\n\tIndices   []*IndexInfo   `json:\"index_info\"`\n\tPartition *PartitionInfo `json:\"partition\"`\n}\n\n// GetPartitionInfo returns the partition information.\nfunc (t *TableInfo) GetPartitionInfo() *PartitionInfo {\n\tif t.Partition != nil && t.Partition.Enable {\n\t\treturn t.Partition\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "util/client/tidbclient/tidbproxy/proxy.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package tidbproxy provides a TiDB cluster proxy service. It forwards incoming SQL API and\n// Status API requests to one of the alive TiDB upstream.\npackage tidbproxy\n\nimport (\n\t\"github.com/pingcap/tidb-dashboard/util/nocopy\"\n\t\"github.com/pingcap/tidb-dashboard/util/proxy\"\n)\n\ntype Config struct {\n\tProxy proxy.Config\n}\n\ntype Proxy struct {\n\tnocopy.NoCopy\n\n\tSQLPortProxy    *proxy.Proxy\n\tStatusPortProxy *proxy.Proxy\n}\n\nfunc New(config Config) (*Proxy, error) {\n\tsqlProxy, err := proxy.New(config.Proxy)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tstatusProxy, err := proxy.New(config.Proxy)\n\tif err != nil {\n\t\tsqlProxy.Close()\n\t\treturn nil, err\n\t}\n\n\treturn &Proxy{\n\t\tSQLPortProxy:    sqlProxy,\n\t\tStatusPortProxy: statusProxy,\n\t}, nil\n}\n\nfunc (f *Proxy) Close() {\n\tf.SQLPortProxy.Close()\n\tf.StatusPortProxy.Close()\n}\n"
  },
  {
    "path": "util/client/tiflashclient/status_client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package tiflashclient provides a flexible TiFlash API access to any TiFlash instance.\npackage tiflashclient\n\nimport (\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\ntype StatusClient struct {\n\t*httpclient.Client\n}\n\nfunc NewStatusClient(config httpclient.Config) *StatusClient {\n\tconfig.KindTag = distro.R().TiFlash\n\treturn &StatusClient{httpclient.New(config)}\n}\n\nfunc (c *StatusClient) Clone() *StatusClient {\n\treturn &StatusClient{c.Client.Clone()}\n}\n"
  },
  {
    "path": "util/client/tikvclient/status_client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package tikvclient provides a flexible TiKV API access to any TiKV instance.\npackage tikvclient\n\nimport (\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\ntype StatusClient struct {\n\t*httpclient.Client\n}\n\nfunc NewStatusClient(config httpclient.Config) *StatusClient {\n\tconfig.KindTag = distro.R().TiKV\n\treturn &StatusClient{httpclient.New(config)}\n}\n\nfunc (c *StatusClient) Clone() *StatusClient {\n\treturn &StatusClient{c.Client.Clone()}\n}\n"
  },
  {
    "path": "util/client/tiproxyclient/status_client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package tiproxyclient provides a flexible TiProxy API access to any TiProxy instance.\npackage tiproxyclient\n\nimport (\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\ntype StatusClient struct {\n\t*httpclient.Client\n}\n\nfunc NewStatusClient(config httpclient.Config) *StatusClient {\n\tconfig.KindTag = distro.R().TiProxy\n\treturn &StatusClient{httpclient.New(config)}\n}\n\nfunc (c *StatusClient) Clone() *StatusClient {\n\treturn &StatusClient{c.Client.Clone()}\n}\n"
  },
  {
    "path": "util/client/tsoclient/status_client.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package tsoclient provides a flexible TSO API access to any TSO instance.\npackage tsoclient\n\nimport (\n\t\"github.com/pingcap/tidb-dashboard/util/client/httpclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n)\n\ntype StatusClient struct {\n\t*httpclient.Client\n}\n\nfunc NewStatusClient(config httpclient.Config) *StatusClient {\n\tconfig.KindTag = distro.R().TSO\n\treturn &StatusClient{httpclient.New(config)}\n}\n\nfunc (c *StatusClient) Clone() *StatusClient {\n\treturn &StatusClient{c.Client.Clone()}\n}\n"
  },
  {
    "path": "util/csvutil/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage csvutil\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/csvutil/writer.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage csvutil\n\nimport (\n\t\"encoding/csv\"\n\t\"fmt\"\n\t\"io\"\n\t\"reflect\"\n\t\"time\"\n\n\t\"github.com/fatih/structtag\"\n\t\"github.com/henrylee2cn/ameda\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/reflectutil\"\n\t\"github.com/pingcap/tidb-dashboard/util/timeutil\"\n)\n\n// isFieldTaggedAsTime parses the csv field and check whether there is a `time` option.\nfunc isFieldTaggedAsTime(field reflect.StructField) bool {\n\tswitch field.Type.Kind() {\n\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,\n\t\treflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,\n\t\treflect.Float32, reflect.Float64:\n\t// Accept and do nothing\n\tdefault:\n\t\treturn false\n\t}\n\n\ttags, err := structtag.Parse(string(field.Tag))\n\tif err != nil {\n\t\treturn false\n\t}\n\ttag, err := tags.Get(\"csv\")\n\tif err != nil {\n\t\treturn false\n\t}\n\treturn tag.HasOption(\"time\")\n}\n\ntype CSVWriter struct {\n\tcw *csv.Writer\n\n\trowBuf []string\n\n\t// The following cache is valid when the passed-in interface's type is unchanged.\n\tcacheFieldIsExported []bool\n\tcacheFieldIsTime     []bool\n\tcacheTypeID          uintptr\n}\n\nfunc NewCSVWriter(w io.Writer) *CSVWriter {\n\treturn &CSVWriter{\n\t\tcw:                   csv.NewWriter(w),\n\t\trowBuf:               make([]string, 0, 32),\n\t\tcacheFieldIsExported: make([]bool, 0, 8),\n\t\tcacheFieldIsTime:     make([]bool, 0, 8),\n\t}\n}\n\n// ensureCacheValid caches information about the fields of `s`.\nfunc (w *CSVWriter) ensureCacheValid(objType reflect.Type) {\n\ttypeID := ameda.RuntimeTypeID(objType)\n\tif typeID == w.cacheTypeID {\n\t\treturn\n\t}\n\tw.cacheTypeID = typeID\n\tw.cacheFieldIsExported = w.cacheFieldIsExported[:0]\n\tw.cacheFieldIsTime = w.cacheFieldIsTime[:0]\n\tfieldsCount := objType.NumField()\n\tfor i := range fieldsCount {\n\t\tf := objType.Field(i)\n\t\tw.cacheFieldIsExported = append(w.cacheFieldIsExported, reflectutil.IsFieldExported(f))\n\t\tw.cacheFieldIsTime = append(w.cacheFieldIsTime, isFieldTaggedAsTime(f))\n\t}\n}\n\nfunc (w *CSVWriter) WriteAsHeader(s interface{}) error {\n\tobjValue := reflect.Indirect(reflect.ValueOf(s))\n\tobjType := objValue.Type()\n\tfieldsCount := objType.NumField()\n\tw.ensureCacheValid(objType)\n\n\tw.rowBuf = w.rowBuf[:0]\n\tfor i := range fieldsCount {\n\t\tif !w.cacheFieldIsExported[i] {\n\t\t\tcontinue\n\t\t}\n\t\tname := objType.Field(i).Name\n\t\tw.rowBuf = append(w.rowBuf, name)\n\t}\n\treturn w.cw.Write(w.rowBuf)\n}\n\nfunc (w *CSVWriter) WriteAsRow(s interface{}) error {\n\tobjValue := reflect.Indirect(reflect.ValueOf(s))\n\tobjType := objValue.Type()\n\tfieldsCount := objType.NumField()\n\tw.ensureCacheValid(objType)\n\n\tw.rowBuf = w.rowBuf[:0]\n\tfor i := range fieldsCount {\n\t\tif !w.cacheFieldIsExported[i] {\n\t\t\tcontinue\n\t\t}\n\t\tfv := objValue.Field(i).Interface() // ~300ns\n\t\tif w.cacheFieldIsTime[i] {\n\t\t\tts, _ := ameda.InterfaceToInt64(fv)\n\t\t\tw.rowBuf = append(w.rowBuf, timeutil.FormatInUTC(time.Unix(ts, 0)))\n\t\t\tcontinue\n\t\t}\n\t\tw.rowBuf = append(w.rowBuf, fmt.Sprint(fv)) // ~1000ns\n\t}\n\treturn w.cw.Write(w.rowBuf)\n}\n\nfunc (w *CSVWriter) Flush() {\n\tw.cw.Flush()\n}\n"
  },
  {
    "path": "util/csvutil/writer_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage csvutil\n\nimport (\n\t\"bytes\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype foo struct {\n\tDigest          string\n\tQuery           string\n\tInstance        string\n\tDB              string\n\tConnectionID    string\n\tSuccess         int\n\tTimestamp       float64\n\tTimestampAsTime float64 `csv:\",time\"`\n\tQueryTime       float64\n\tprivateField    string\n}\n\nfunc TestCSVWriter(t *testing.T) {\n\tbuf := bytes.Buffer{}\n\tw := NewCSVWriter(&buf)\n\terr := w.WriteAsHeader(&foo{})\n\trequire.NoError(t, err)\n\n\trec := foo{\n\t\tDigest:          \"digest_foo\",\n\t\tQuery:           \"query_bar\",\n\t\tInstance:        \"instance_box\",\n\t\tDB:              \"db_abc\",\n\t\tConnectionID:    \"id_123\",\n\t\tSuccess:         1,\n\t\tTimestamp:       456,\n\t\tTimestampAsTime: 1633106800.411,\n\t\tQueryTime:       789,\n\t\tprivateField:    \"pfxyz\",\n\t}\n\terr = w.WriteAsRow(&rec)\n\trequire.NoError(t, err)\n\n\trec = foo{\n\t\tConnectionID:    \"id123\",\n\t\tSuccess:         2,\n\t\tTimestamp:       0,\n\t\tTimestampAsTime: 0,\n\t\tQueryTime:       123,\n\t\tprivateField:    \"pro\",\n\t\tDigest:          \"digestFoo\",\n\t\tQuery:           \"queryBar\",\n\t\tInstance:        \"instanceBox\",\n\t\tDB:              \"dbAbc\",\n\t}\n\terr = w.WriteAsRow(&rec)\n\trequire.NoError(t, err)\n\n\tw.Flush()\n\texpected := `\nDigest,Query,Instance,DB,ConnectionID,Success,Timestamp,TimestampAsTime,QueryTime\ndigest_foo,query_bar,instance_box,db_abc,id_123,1,456,2021-10-01 16:46:40 UTC,789\ndigestFoo,queryBar,instanceBox,dbAbc,id123,2,0,1970-01-01 00:00:00 UTC,123\n`\n\trequire.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buf.String()))\n}\n\nfunc TestCSVWriterWriteTimeTag(t *testing.T) {\n\t//nolint:govet\n\ttype fooStruct struct {\n\t\tField1 int `csv:`\n\t\tField2 int `foo`\n\t\tField3 int `foo,bar`\n\t\tField4 int `foo:\"\" csv:\"time\"`\n\t\tField5 int `foo:\"\" csv:\",\"`\n\t\tField6 int `foo:\"\" csv:\",time\"`\n\t}\n\tbuf := bytes.Buffer{}\n\tw := NewCSVWriter(&buf)\n\terr := w.WriteAsRow(fooStruct{})\n\trequire.NoError(t, err)\n\tw.Flush()\n\trequire.Equal(t, \"0,0,0,0,0,1970-01-01 00:00:00 UTC\\n\", buf.String())\n\n\ttype barStruct struct {\n\t\tFieldInt     int     `csv:\",time\"`\n\t\tFieldUint64  uint64  `csv:\",time\"`\n\t\tFieldFloat32 float64 `csv:\",time\"`\n\t}\n\tbuf.Reset()\n\terr = w.WriteAsRow(barStruct{\n\t\tFieldInt:     1633106800,\n\t\tFieldUint64:  1633106801,\n\t\tFieldFloat32: 1633106802,\n\t})\n\trequire.NoError(t, err)\n\tw.Flush()\n\trequire.Equal(t, \"2021-10-01 16:46:40 UTC,2021-10-01 16:46:41 UTC,2021-10-01 16:46:42 UTC\\n\", buf.String())\n}\n\nfunc BenchmarkCSVWriterWriteAsHeader(b *testing.B) {\n\tbuf := bytes.Buffer{}\n\tw := NewCSVWriter(&buf)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = w.WriteAsHeader(&foo{})\n\t}\n}\n\nfunc BenchmarkCSVWriterWriteAsRow(b *testing.B) {\n\tb.ReportAllocs()\n\n\tbuf := bytes.Buffer{}\n\tw := NewCSVWriter(&buf)\n\n\trec := foo{\n\t\tDigest:       \"digest_foo\",\n\t\tQuery:        \"query_bar\",\n\t\tInstance:     \"instance_box\",\n\t\tDB:           \"db_abc\",\n\t\tConnectionID: \"id_123\",\n\t\tSuccess:      1,\n\t\tTimestamp:    456,\n\t\tQueryTime:    789,\n\t\tprivateField: \"pfxyz\",\n\t}\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\t_ = w.WriteAsRow(&rec)\n\t}\n}\n"
  },
  {
    "path": "util/distro/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage distro\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/distro/distro.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package distro provides a type-safe distribution resource framework.\n// Distribution resource determines how component names are displayed in errors, logs and so on.\n// For example, a distribution resource can define \"TiDB\" to be displayed as \"MyTiDB\".\npackage distro\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n\n\t\"go.uber.org/atomic\"\n)\n\ntype DistributionResource struct {\n\tIsDistro   bool   `json:\"is_distro,omitempty\"`\n\tTiDB       string `json:\"tidb,omitempty\"`\n\tTiKV       string `json:\"tikv,omitempty\"`\n\tPD         string `json:\"pd,omitempty\"`\n\tTiFlash    string `json:\"tiflash,omitempty\"`\n\tTiCDC      string `json:\"ticdc,omitempty\"`\n\tTiProxy    string `json:\"tiproxy,omitempty\"`\n\tTSO        string `json:\"tso,omitempty\"`\n\tScheduling string `json:\"scheduling,omitempty\"`\n}\n\nvar defaultDistroRes = DistributionResource{\n\tIsDistro:   false,\n\tTiDB:       \"TiDB\",\n\tTiKV:       \"TiKV\",\n\tPD:         \"PD\",\n\tTiFlash:    \"TiFlash\",\n\tTiCDC:      \"TiCDC\",\n\tTiProxy:    \"TiProxy\",\n\tTSO:        \"TSO\",\n\tScheduling: \"Scheduling\",\n}\n\nvar (\n\tglobalDistroRes atomic.Value\n\treplaceGlobalMu sync.Mutex\n)\n\n// ReplaceGlobal replaces the global distribution resource with the specified one. Missing fields in the\n// resource will be filled using default values.\nfunc ReplaceGlobal(r DistributionResource) func() {\n\t// TODO: To be replaced by atomic.Value.Swap() in Go 1.16\n\treplaceGlobalMu.Lock()\n\tdefer replaceGlobalMu.Unlock()\n\n\t// Save current resources for restoring back.\n\tcurrentGlobals := *R()\n\n\t// Fill missing resources with the default one by using JSON Unmarshal.\n\tnewResource := defaultDistroRes\n\trJSON, _ := json.Marshal(r)             // This will never fail\n\t_ = json.Unmarshal(rJSON, &newResource) // This will never fail\n\tglobalDistroRes.Store(&newResource)\n\n\treturn func() {\n\t\tReplaceGlobal(currentGlobals)\n\t}\n}\n\n// R gets the current global distribution resource. The returned value must NOT be modified.\nfunc R() *DistributionResource {\n\tr := globalDistroRes.Load()\n\tif r == nil {\n\t\treturn &defaultDistroRes\n\t}\n\treturn r.(*DistributionResource)\n}\n\nfunc ReadResourceStringsFromFile(filePath string) (DistributionResource, error) {\n\tdistroStringsRes := DistributionResource{}\n\n\tinfo, err := os.Stat(filePath)\n\tif errors.Is(err, os.ErrNotExist) || info.IsDir() {\n\t\t// ignore if file not exist or it is a folder\n\t\treturn distroStringsRes, nil\n\t}\n\tif err != nil {\n\t\t// may be permission-like errors\n\t\treturn distroStringsRes, err\n\t}\n\n\tdistroStringsFile, err := os.Open(filepath.Clean(filePath))\n\tif err != nil {\n\t\treturn distroStringsRes, err\n\t}\n\tdefer func() {\n\t\t_ = distroStringsFile.Close()\n\t}()\n\n\tdata, err := io.ReadAll(distroStringsFile)\n\tif err != nil {\n\t\treturn distroStringsRes, err\n\t}\n\n\terr = json.Unmarshal(data, &distroStringsRes)\n\treturn distroStringsRes, err\n}\n"
  },
  {
    "path": "util/distro/distro_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage distro\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestR(t *testing.T) {\n\trequire.Equal(t, \"TiDB\", R().TiDB)\n}\n\nfunc TestReplaceGlobal(t *testing.T) {\n\trestoreFn := ReplaceGlobal(DistributionResource{\n\t\tTiDB: \"myTiDB\",\n\t\tPD:   \"\",\n\t})\n\trequire.Equal(t, false, R().IsDistro)\n\trequire.Equal(t, \"myTiDB\", R().TiDB)\n\trequire.Equal(t, \"PD\", R().PD)\n\trequire.Equal(t, \"TiKV\", R().TiKV)\n\trestoreFn()\n\trequire.Equal(t, \"TiDB\", R().TiDB)\n\trequire.Equal(t, \"PD\", R().PD)\n\trequire.Equal(t, \"TiKV\", R().TiKV)\n\n\trestoreFn = ReplaceGlobal(DistributionResource{\n\t\tIsDistro: true,\n\t})\n\trequire.Equal(t, true, R().IsDistro)\n\trequire.Equal(t, \"TiDB\", R().TiDB)\n\trequire.Equal(t, \"PD\", R().PD)\n\trequire.Equal(t, \"TiKV\", R().TiKV)\n\trestoreFn()\n}\n"
  },
  {
    "path": "util/featureflag/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage featureflag\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/featureflag/featureflag.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage featureflag\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/Masterminds/semver\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joomcode/errorx\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nvar ErrFeatureUnsupported = errorx.CommonErrors.NewType(\"feature_unsupported\")\n\ntype FeatureFlag struct {\n\tname        string\n\tconstraints []string\n\tisSupported bool\n}\n\nfunc newFeatureFlag(name, targetVersion string, constraints ...string) *FeatureFlag {\n\tf := &FeatureFlag{name: name, constraints: constraints}\n\tf.isSupported = f.isSupportedIn(targetVersion)\n\treturn f\n}\n\nfunc (f *FeatureFlag) Name() string {\n\treturn f.name\n}\n\nfunc (f *FeatureFlag) IsSupported() bool {\n\treturn f.isSupported\n}\n\n// VersionGuard returns gin.HandlerFunc as guard middleware.\n// It will determine if features are available in the target version.\nfunc (f *FeatureFlag) VersionGuard() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tif !f.isSupported {\n\t\t\trest.Error(c, ErrFeatureUnsupported.New(\"%s\", f.name).WithProperty(rest.HTTPCodeProperty(http.StatusForbidden))) // nolint: vet\n\t\t\tc.Abort()\n\t\t\treturn\n\t\t}\n\n\t\tc.Next()\n\t}\n}\n\n// IsSupportedIn checks if a semantic version fits within a set of constraints\n// pdVersion, standaloneVersion examples: \"v5.2.2\", \"v5.3.0\", \"v5.4.0-alpha-xxx\", \"5.3.0\" (semver can handle `v` prefix by itself)\n// constraints examples: \"~5.2.2\", \">= 5.3.0\", see semver docs to get more information.\nfunc (f *FeatureFlag) isSupportedIn(targetVersion string) bool {\n\t// drop \"-alpha-xxx\" suffix\n\tversionWithoutSuffix := strings.Split(targetVersion, \"-\")[0]\n\tv, err := semver.NewVersion(versionWithoutSuffix)\n\tif err != nil {\n\t\treturn false\n\t}\n\tfor _, ver := range f.constraints {\n\t\tc, err := semver.NewConstraint(ver)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif c.Check(v) {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "util/featureflag/featureflag_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage featureflag\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nfunc Test_Name(t *testing.T) {\n\tf1 := &FeatureFlag{}\n\trequire.Equal(t, f1.Name(), \"\")\n\n\tf2 := newFeatureFlag(\"testFeature\", \"v5.3.0\", \">= 5.3.0\")\n\trequire.Equal(t, f2.Name(), \"testFeature\")\n}\n\nfunc Test_IsSupported(t *testing.T) {\n\ttype Args struct {\n\t\ttarget      string\n\t\tconstraints []string\n\t}\n\ttests := []struct {\n\t\twant bool\n\t\targs Args\n\t}{\n\t\t{want: false, args: Args{target: \"v4.2.0\", constraints: []string{\">= 5.3.0\"}}},\n\t\t{want: false, args: Args{target: \"v5.2.0\", constraints: []string{\">= 5.3.0\"}}},\n\t\t{want: true, args: Args{target: \"v5.3.0\", constraints: []string{\">= 5.3.0\"}}},\n\t\t{want: false, args: Args{target: \"v5.2.0-alpha-xxx\", constraints: []string{\">= 5.3.0\"}}},\n\t\t{want: true, args: Args{target: \"v5.3.0-alpha-xxx\", constraints: []string{\">= 5.3.0\"}}},\n\t\t{want: true, args: Args{target: \"v5.3.0\", constraints: []string{\"= 5.3.0\"}}},\n\t\t{want: false, args: Args{target: \"v5.3.1\", constraints: []string{\"= 5.3.0\"}}},\n\t}\n\n\tfor _, tt := range tests {\n\t\tff := newFeatureFlag(\"testFeature\", tt.args.target, tt.args.constraints...)\n\t\trequire.Equal(t, tt.want, ff.IsSupported())\n\t}\n}\n\nfunc Test_VersionGuard(t *testing.T) {\n\tr := require.New(t)\n\tf1 := newFeatureFlag(\"testFeature1\", \"v5.3.0\", \">= 5.3.0\")\n\tf2 := newFeatureFlag(\"testFeature2\", \"v5.3.0\", \">= 5.3.1\")\n\n\t// success\n\te := gin.Default()\n\te.Use(f1.VersionGuard())\n\te.GET(\"/ping\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"pong\")\n\t})\n\tw := httptest.NewRecorder()\n\treq, _ := http.NewRequest(\"GET\", \"/ping\", nil)\n\te.ServeHTTP(w, req)\n\n\tr.Equal(http.StatusOK, w.Code)\n\tr.Equal(\"pong\", w.Body.String())\n\n\t// abort\n\thandled := false\n\te2 := gin.Default()\n\te2.Use(func(c *gin.Context) {\n\t\tc.Next()\n\n\t\thandled = true\n\t\tr.True(errorx.IsOfType(c.Errors.Last().Err, ErrFeatureUnsupported))\n\t})\n\te2.Use(f1.VersionGuard(), f2.VersionGuard())\n\te2.GET(\"/ping\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"pong\")\n\t})\n\tw2 := httptest.NewRecorder()\n\treq2, _ := http.NewRequest(\"GET\", \"/ping\", nil)\n\te2.ServeHTTP(w2, req2)\n\n\tr.Equal(true, handled)\n}\n\nfunc Test_VersionGuardWith_ErrorHandlerFn(t *testing.T) {\n\tr := require.New(t)\n\tf := newFeatureFlag(\"testFeature\", \"v5.3.0\", \">= 5.3.1\")\n\n\te := gin.Default()\n\te.Use(rest.ErrorHandlerFn())\n\te.Use(f.VersionGuard())\n\te.GET(\"/ping\", func(c *gin.Context) {\n\t\tc.String(http.StatusOK, \"pong\")\n\t})\n\tw2 := httptest.NewRecorder()\n\treq2, _ := http.NewRequest(\"GET\", \"/ping\", nil)\n\te.ServeHTTP(w2, req2)\n\n\tr.Equal(http.StatusForbidden, w2.Code)\n}\n"
  },
  {
    "path": "util/featureflag/registry.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage featureflag\n\nimport (\n\t\"sort\"\n)\n\ntype Registry struct {\n\tversion           string\n\tflags             map[string]*FeatureFlag\n\tsupportedFeatures map[string]struct{}\n}\n\nfunc NewRegistry(version string) *Registry {\n\treturn &Registry{\n\t\tversion:           version,\n\t\tflags:             map[string]*FeatureFlag{},\n\t\tsupportedFeatures: map[string]struct{}{},\n\t}\n}\n\n// Register create and register feature flag to registry.\nfunc (m *Registry) Register(name string, constraints ...string) *FeatureFlag {\n\tif f, ok := m.flags[name]; ok {\n\t\treturn f\n\t}\n\n\tnf := newFeatureFlag(name, m.version, constraints...)\n\tm.flags[name] = nf\n\tif nf.IsSupported() {\n\t\tm.supportedFeatures[nf.Name()] = struct{}{}\n\t}\n\treturn nf\n}\n\n// SupportedFeatures returns supported feature's names.\nfunc (m *Registry) SupportedFeatures() []string {\n\tsf := make([]string, 0, len(m.supportedFeatures))\n\tfor k := range m.supportedFeatures {\n\t\tsf = append(sf, k)\n\t}\n\tsort.Strings(sf)\n\treturn sf\n}\n"
  },
  {
    "path": "util/featureflag/registry_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage featureflag\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc Test_Register(t *testing.T) {\n\tm := NewRegistry(\"v5.3.0\")\n\ttests := []*struct {\n\t\tsupported   bool\n\t\tname        string\n\t\tconstraints []string\n\t\tflag        *FeatureFlag\n\t}{\n\t\t{supported: true, name: \"testFeature1\", constraints: []string{\">= 5.3.0\"}},\n\t\t{supported: true, name: \"testFeature2\", constraints: []string{\">= 4.0.0\"}},\n\t\t{supported: false, name: \"testFeature3\", constraints: []string{\">= 5.3.1\"}},\n\t}\n\n\tfor _, tt := range tests {\n\t\ttt.flag = m.Register(tt.name, tt.constraints...)\n\t}\n\n\tfor _, tt := range tests {\n\t\t// check whether flag is in flags & supportedFeatures\n\t\trequire.Equal(t, m.flags[tt.flag.name], tt.flag)\n\t\t_, ok := m.supportedFeatures[tt.flag.name]\n\t\trequire.Equal(t, tt.supported, ok)\n\t}\n\n\t// duplicated register\n\tf := m.Register(\"testFeature3\", \">= 5.3.2\")\n\trequire.Equal(t, f.name, \"testFeature3\")\n\trequire.Equal(t, f.constraints[0], \">= 5.3.1\")\n}\n\nfunc Test_SupportedFeatures(t *testing.T) {\n\tm := NewRegistry(\"v5.3.0\")\n\ttests := []*struct {\n\t\tsupported   bool\n\t\tname        string\n\t\tconstraints []string\n\t}{\n\t\t{supported: true, name: \"testFeature1\", constraints: []string{\">= 5.3.0\"}},\n\t\t{supported: true, name: \"testFeature2\", constraints: []string{\">= 4.0.0\"}},\n\t\t{supported: false, name: \"testFeature3\", constraints: []string{\">= 5.3.1\"}},\n\t}\n\n\tfor _, tt := range tests {\n\t\tm.Register(tt.name, tt.constraints...)\n\t}\n\n\trequire.Equal(t, []string{\"testFeature1\", \"testFeature2\"}, m.SupportedFeatures())\n}\n"
  },
  {
    "path": "util/fxprinter/fxprinter.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage fxprinter\n\nimport (\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/fx\"\n)\n\ntype DebugLogPrinter struct{}\n\nfunc (p DebugLogPrinter) Printf(m string, args ...interface{}) {\n\tlog.S().Debugf(m, args...)\n}\n\nfunc NewDebugLogPrinter() fx.Printer {\n\treturn DebugLogPrinter{}\n}\n"
  },
  {
    "path": "util/gormutil/datatype/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage datatype\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/gormutil/datatype/int.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage datatype\n\nimport (\n\t\"database/sql\"\n\t\"database/sql/driver\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"gorm.io/gorm/schema\"\n)\n\n// Int is an alternative type to the standard int type. Int can be mapped to all numeric SQL field types,\n// including floats.\n// NULL value will be scanned as 0. To distinguish zero and null, use nullable.Int instead.\ntype Int int\n\nvar _ sql.Scanner = (*Int)(nil)\n\n// Scan implements sql.Scanner.\nfunc (n *Int) Scan(value interface{}) error {\n\tif value == nil {\n\t\t*n = 0\n\t\treturn nil\n\t}\n\tswitch v := value.(type) {\n\tcase int64:\n\t\t*n = Int(v)\n\t\treturn nil\n\tcase float64:\n\t\t*n = Int(v)\n\t\treturn nil\n\tcase []uint8:\n\t\tnv, err := strconv.Atoi(string(v))\n\t\tif err == nil {\n\t\t\t*n = Int(nv)\n\t\t\treturn nil\n\t\t}\n\t\tfv, err := strconv.ParseFloat(string(v), 64)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\t*n = Int(fv)\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"can't convert %T to Int\", value)\n}\n\nvar _ schema.GormDataTypeInterface = Int(0)\n\n// GormDataType implements schema.GormDataTypeInterface.\nfunc (n Int) GormDataType() string {\n\treturn \"BIGINT\"\n}\n\nvar _ driver.Valuer = Int(0)\n\n// Value implements driver.Valuer.\nfunc (n Int) Value() (driver.Value, error) {\n\treturn int64(n), nil\n}\n\nvar _ json.Marshaler = Int(0)\n\n// MarshalJSON implements json.Marshaler.\nfunc (n Int) MarshalJSON() ([]byte, error) {\n\treturn []byte(strconv.FormatInt(int64(n), 10)), nil\n}\n\nvar _ json.Unmarshaler = (*Int)(nil)\n\n// UnmarshalJSON implements json.Unmarshaler.\nfunc (n *Int) UnmarshalJSON(data []byte) error {\n\tvar v int\n\tif err := json.Unmarshal(data, &v); err != nil {\n\t\treturn err\n\t}\n\t*n = Int(v)\n\treturn nil\n}\n"
  },
  {
    "path": "util/gormutil/datatype/int_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage datatype\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestIntJSON(t *testing.T) {\n\tnv := Int(123)\n\tv, err := json.Marshal(nv)\n\trequire.NoError(t, err)\n\trequire.Equal(t, string(v), \"123\")\n\n\tst := struct {\n\t\tFoo Int\n\t}{\n\t\tFoo: Int(415425),\n\t}\n\tv, err = json.Marshal(st)\n\trequire.NoError(t, err)\n\trequire.JSONEq(t, `{\"Foo\":415425}`, string(v))\n\n\tvar nv2 Int\n\terr = json.Unmarshal([]byte(\"12345\"), &nv2)\n\trequire.NoError(t, err)\n\trequire.Equal(t, Int(12345), nv2)\n\n\terr = json.Unmarshal([]byte(`{\"Foo\":56789}`), &st)\n\trequire.NoError(t, err)\n\trequire.Equal(t, Int(56789), st.Foo)\n\n\terr = json.Unmarshal([]byte(`{\"Foo\":\"123\"}`), &st)\n\trequire.Error(t, err)\n\n\terr = json.Unmarshal([]byte(`{\"Foo\":1300.45}`), &st)\n\trequire.Error(t, err)\n\n\tnv3 := Int(48691071)\n\tv, err = json.Marshal(nv3)\n\trequire.NoError(t, err)\n\terr = json.Unmarshal(v, &nv2)\n\trequire.NoError(t, err)\n\trequire.Equal(t, nv2, nv3)\n}\n"
  },
  {
    "path": "util/gormutil/datatype/int_withdb_test.go",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\n//go:build integration\n\npackage datatype\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\tmysqldriver \"github.com/go-sql-driver/mysql\"\n\t\"github.com/stretchr/testify/suite\"\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil\"\n)\n\ntype IntORMSuite struct {\n\tsuite.Suite\n\tusePrepareStatement bool\n\n\tdb        *testutil.TestDB\n\ttableName string\n}\n\nfunc (suite *IntORMSuite) SetupSuite() {\n\tsuite.db = testutil.OpenTestDB(suite.T(), func(_ *mysqldriver.Config, config *gorm.Config) {\n\t\tconfig.PrepareStmt = suite.usePrepareStatement\n\t})\n\tsuite.tableName = \"`\" + suite.db.NewID() + \"`\"\n\tsuite.db.MustExec(fmt.Sprintf(`CREATE TABLE %s (\n\t\ta bigint,\n\t\tb double,\n\t\tc varchar(50),\n\t\td boolean,\n\t\te varchar(10)\n\t)`, suite.tableName))\n\tsuite.db.MustExec(fmt.Sprintf(`INSERT INTO %s VALUES (\n\t\t73371569,\n\t\t965511.2870,\n\t\t\"123456\",\n\t\t1,\n\t\t\"abc\"\n\t)`, suite.tableName))\n}\n\nfunc (suite *IntORMSuite) TearDownSuite() {\n\tsuite.db.MustExec(fmt.Sprintf(`DROP TABLE IF EXISTS %s`, suite.tableName))\n\tsuite.db.MustClose()\n}\n\nfunc (suite *IntORMSuite) TestScanFromInt() {\n\tvar r struct {\n\t\tA Int\n\t}\n\terr := suite.db.Gorm().Table(suite.tableName).Select(\"a\").Take(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(Int(73371569), r.A)\n}\n\nfunc (suite *IntORMSuite) TestScanFromDouble() {\n\tvar r struct {\n\t\tB Int\n\t}\n\terr := suite.db.Gorm().Table(suite.tableName).Select(\"b\").Take(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(Int(965511), r.B)\n}\n\nfunc (suite *IntORMSuite) TestScanFromString() {\n\tvar r struct {\n\t\tC Int\n\t}\n\terr := suite.db.Gorm().Table(suite.tableName).Select(\"c\").Take(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(Int(123456), r.C)\n}\n\nfunc (suite *IntORMSuite) TestScanFromBoolean() {\n\tvar r struct {\n\t\tD Int\n\t}\n\terr := suite.db.Gorm().Table(suite.tableName).Select(\"d\").Take(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(Int(1), r.D)\n}\n\nfunc (suite *IntORMSuite) TestScanFromNonNumericString() {\n\tvar r struct {\n\t\tE Int\n\t}\n\terr := suite.db.Gorm().Table(suite.tableName).Select(\"e\").Take(&r).Error\n\tsuite.Require().Error(err)\n}\n\n// Scanning double into int is invalid. That's why we need Int.\nfunc (suite *IntORMSuite) TestScanDoubleToStdInt() {\n\tvar r struct {\n\t\tB int\n\t}\n\terr := suite.db.Gorm().Table(suite.tableName).Select(\"b\").Take(&r).Error\n\tsuite.Require().Error(err)\n}\n\nfunc (suite *IntORMSuite) TestWhere() {\n\tvar r struct {\n\t\tA Int\n\t}\n\terr := suite.db.Gorm().\n\t\tTable(suite.tableName).\n\t\tSelect(\"a\").\n\t\tWhere(\"a = ?\", Int(73371569)).\n\t\tTake(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(Int(73371569), r.A)\n\n\terr = suite.db.Gorm().\n\t\tTable(suite.tableName).\n\t\tSelect(\"a\").\n\t\tWhere(\"a > ?\", Int(0)).\n\t\tTake(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(Int(73371569), r.A)\n\n\terr = suite.db.Gorm().\n\t\tTable(suite.tableName).\n\t\tSelect(\"a\").\n\t\tWhere(\"a = ?\", Int(123)).\n\t\tTake(&r).Error\n\tsuite.Require().Error(err)\n\tsuite.Require().Equal(gorm.ErrRecordNotFound, err)\n\n\terr = suite.db.Gorm().\n\t\tTable(suite.tableName).\n\t\tSelect(\"a\").\n\t\tWhere(\"a > ?\", Int(73371570)).\n\t\tTake(&r).Error\n\tsuite.Require().Error(err)\n\tsuite.Require().Equal(gorm.ErrRecordNotFound, err)\n\n\terr = suite.db.Gorm().\n\t\tTable(suite.tableName).\n\t\tSelect(\"a\").\n\t\tWhere(\"a = ?\", \"73371569\").\n\t\tTake(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(Int(73371569), r.A)\n\n\t// Matching a double field will never succeed, since int lose precision\n\tval := 965511.2870\n\terr = suite.db.Gorm().\n\t\tTable(suite.tableName).\n\t\tSelect(\"b\").\n\t\tWhere(\"b = ?\", Int(val)).\n\t\tTake(&r).Error\n\tsuite.Require().Error(err)\n\tsuite.Require().Equal(gorm.ErrRecordNotFound, err)\n}\n\nfunc (suite *IntORMSuite) TestWhereInIndex() {\n\ttableName := \"`\" + suite.db.NewID() + \"`\"\n\n\tsuite.db.MustExec(fmt.Sprintf(`CREATE TABLE %s (\n\t\tid int,\n\t\tval int,\n\t\tINDEX idx (val)\n\t)`, tableName))\n\tsuite.db.MustExec(fmt.Sprintf(`INSERT INTO %s VALUES (\n\t\t1,\n\t\t42160690\n\t)`, tableName))\n\n\tdefer suite.db.MustExec(fmt.Sprintf(`DROP TABLE IF EXISTS %s`, tableName))\n\n\tvar r struct {\n\t\tVal Int\n\t}\n\terr := suite.db.Gorm().\n\t\tTable(tableName).\n\t\tSelect(\"val\").\n\t\tWhere(\"val = ?\", Int(42160690)).\n\t\tTake(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(Int(42160690), r.Val)\n\n\t// Verify Plan is Index Scan\n\texplain := suite.db.\n\t\tMustExplain(fmt.Sprintf(\"SELECT val FROM %s WHERE val = ?\", tableName), Int(42160690))\n\ttestutil.RequireIndexRangeScan(suite.T(), explain)\n}\n\nfunc (suite *IntORMSuite) TestInsert() {\n\ttableName := \"`\" + suite.db.NewID() + \"`\"\n\n\tsuite.db.MustExec(fmt.Sprintf(`CREATE TABLE %s (\n\t\tid int,\n\t\tval bigint\n\t)`, tableName))\n\n\tdefer suite.db.MustExec(fmt.Sprintf(`DROP TABLE IF EXISTS %s`, tableName))\n\n\ttype model struct {\n\t\tID  int\n\t\tVal Int\n\t}\n\terr := suite.db.Gorm().Table(tableName).Create(model{\n\t\tID:  10,\n\t\tVal: Int(12346),\n\t}).Error\n\tsuite.Require().NoError(err)\n\n\tvar r model\n\terr = suite.db.Gorm().Table(tableName).Take(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(10, r.ID)\n\tsuite.Require().Equal(Int(12346), r.Val)\n}\n\nfunc (suite *IntORMSuite) TestScanNull() {\n\ttableName := \"`\" + suite.db.NewID() + \"`\"\n\n\tsuite.db.MustExec(fmt.Sprintf(`CREATE TABLE %s (\n\t\tid int,\n\t\tval bigint\n\t)`, tableName))\n\tsuite.db.MustExec(fmt.Sprintf(`INSERT INTO %s VALUES (\n\t\t100,\n\t\tnull\n\t)`, tableName))\n\n\tdefer suite.db.MustExec(fmt.Sprintf(`DROP TABLE IF EXISTS %s`, tableName))\n\n\tvar r struct {\n\t\tVal Int\n\t}\n\terr := suite.db.Gorm().\n\t\tTable(tableName).\n\t\tSelect(\"val\").\n\t\tTake(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(Int(0), r.Val)\n}\n\nfunc TestIntInORM(t *testing.T) {\n\tsuite.Run(t, &IntORMSuite{\n\t\tusePrepareStatement: false,\n\t})\n}\n\nfunc TestIntInORMWithPrepared(t *testing.T) {\n\tsuite.Run(t, &IntORMSuite{\n\t\tusePrepareStatement: true,\n\t})\n}\n"
  },
  {
    "path": "util/gormutil/datatype/nullable/nullable.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage nullable\n\n// NullableTimestamp, NullableInt to be implemented\n"
  },
  {
    "path": "util/gormutil/datatype/timestamp.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage datatype\n\nimport (\n\t\"database/sql\"\n\t\"database/sql/driver\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"strconv\"\n\t\"time\"\n\n\t\"gorm.io/gorm/schema\"\n)\n\n// Timestamp is serialized as microsecond-precision unix timestamps in JSON, and can be mapped to\n// the \"timestamp\" field type in MySQL / TiDB.\n// NULL value will be scanned as unix timestamp 0. To distinguish zero timestamp and null timestamp,\n// use nullable.Timestamp instead.\n//\n// This struct is only used for compatibility with existing \"timestamp\" field types.\n// Int should be always preferred to store timestamps wherever possible.\ntype Timestamp struct {\n\ttime.Time\n}\n\nvar _ sql.Scanner = (*Timestamp)(nil)\n\n// Scan implements sql.Scanner.\nfunc (n *Timestamp) Scan(value interface{}) error {\n\tif value == nil {\n\t\tn.Time = time.Unix(0, 0)\n\t\treturn nil\n\t}\n\tswitch v := value.(type) {\n\tcase time.Time:\n\t\tn.Time = v\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"can't convert %T to Timestamp\", value)\n}\n\nvar _ schema.GormDataTypeInterface = Timestamp{}\n\n// GormDataType implements schema.GormDataTypeInterface.\nfunc (n Timestamp) GormDataType() string {\n\treturn \"TIMESTAMP\"\n}\n\nvar _ driver.Valuer = Timestamp{}\n\n// Value implements driver.Valuer.\nfunc (n Timestamp) Value() (driver.Value, error) {\n\treturn n.Time, nil\n}\n\nvar _ json.Marshaler = Timestamp{}\n\n// MarshalJSON implements json.Marshaler.\nfunc (n Timestamp) MarshalJSON() ([]byte, error) {\n\tts := n.UnixNano() / 1e3\n\treturn []byte(strconv.FormatInt(ts, 10)), nil\n}\n\nvar _ json.Unmarshaler = (*Timestamp)(nil)\n\n// UnmarshalJSON implements json.Unmarshaler.\nfunc (n *Timestamp) UnmarshalJSON(data []byte) error {\n\tvar ts int64\n\tif err := json.Unmarshal(data, &ts); err != nil {\n\t\treturn err\n\t}\n\tn.Time = time.Unix(0, ts*1e3)\n\treturn nil\n}\n"
  },
  {
    "path": "util/gormutil/datatype/timestamp_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage datatype\n\nimport (\n\t\"encoding/json\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil\"\n)\n\nfunc TestTimestampORMType(t *testing.T) {\n\ttype TestModel struct {\n\t\tField Timestamp\n\t}\n\n\tdb := testutil.OpenMockDB(t)\n\tdefer db.MustClose()\n\n\tdb.Mocker().\n\t\tExpectExec(\"CREATE TABLE `test_models` (`field` TIMESTAMP)\").\n\t\tWillReturnResult(sqlmock.NewResult(0, 1))\n\n\terr := db.Gorm().Migrator().CreateTable(TestModel{})\n\trequire.NoError(t, err)\n\n\tdb.MustMeetMockExpectation()\n}\n\nfunc TestTimestampJSON(t *testing.T) {\n\tts := Timestamp{Time: time.Unix(0, 1633880141307801631)}\n\tv, err := json.Marshal(ts)\n\trequire.NoError(t, err)\n\trequire.Equal(t, string(v), \"1633880141307801\")\n\n\tts = Timestamp{Time: time.Unix(0, 500)}\n\tv, err = json.Marshal(ts)\n\trequire.NoError(t, err)\n\trequire.Equal(t, string(v), \"0\")\n\n\tst := struct {\n\t\tFoo Timestamp\n\t}{\n\t\tFoo: Timestamp{Time: time.Unix(0, 1633880141307801631)},\n\t}\n\tv, err = json.Marshal(st)\n\trequire.NoError(t, err)\n\trequire.JSONEq(t, `{\"Foo\":1633880141307801}`, string(v))\n\n\tvar ts2 Timestamp\n\terr = json.Unmarshal([]byte(\"12345\"), &ts2)\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(12345000), ts2.UnixNano())\n\n\terr = json.Unmarshal([]byte(`{\"Foo\":12345}`), &st)\n\trequire.NoError(t, err)\n\trequire.Equal(t, int64(12345000), st.Foo.UnixNano())\n\n\terr = json.Unmarshal([]byte(`{\"Foo\":\"54321\"}`), &st)\n\trequire.Error(t, err)\n\n\terr = json.Unmarshal([]byte(`{\"Foo\":123.45}`), &st)\n\trequire.Error(t, err)\n\n\tts3 := Timestamp{Time: time.Unix(0, 1633880141307801000)}\n\tv, err = json.Marshal(ts3)\n\trequire.NoError(t, err)\n\terr = json.Unmarshal(v, &ts2)\n\trequire.NoError(t, err)\n\trequire.Equal(t, ts2, ts3)\n}\n"
  },
  {
    "path": "util/gormutil/datatype/timestamp_withdb_test.go",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\n//go:build integration\n\npackage datatype\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\tmysqldriver \"github.com/go-sql-driver/mysql\"\n\t\"github.com/stretchr/testify/suite\"\n\t\"gorm.io/gorm\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil\"\n)\n\ntype TimestampORMSuite struct {\n\tsuite.Suite\n\tusePrepareStatement bool\n\n\tdb        *testutil.TestDB\n\ttableName string\n}\n\nfunc (suite *TimestampORMSuite) SetupSuite() {\n\tsuite.db = testutil.OpenTestDB(suite.T(), func(_ *mysqldriver.Config, config *gorm.Config) {\n\t\tconfig.PrepareStmt = suite.usePrepareStatement\n\t})\n\tsuite.tableName = \"`\" + suite.db.NewID() + \"`\"\n\tsuite.db.MustExec(fmt.Sprintf(`CREATE TABLE %s (\n\t\ta bigint,\n\t\tb double,\n\t\tc varchar(50),\n\t\td timestamp(6),\n\t\te datetime(6)\n\t)`, suite.tableName))\n\tsuite.db.MustExec(fmt.Sprintf(`INSERT INTO %s VALUES (\n\t\t1633803684694123,\n\t\t1633803684.694123,\n\t\t\"2021-10-09 18:21:24.694123\",\n\t\tFROM_UNIXTIME(\"1633803684.694123\"),\n\t\t\"2021-10-09 18:21:24.694123\"\n\t)`, suite.tableName))\n}\n\nfunc (suite *TimestampORMSuite) TearDownSuite() {\n\tsuite.db.MustExec(fmt.Sprintf(`DROP TABLE IF EXISTS %s`, suite.tableName))\n\tsuite.db.MustClose()\n}\n\nfunc (suite *TimestampORMSuite) SetupTest() {\n\tsuite.db.MustExec(\"SET time_zone = '+00:00'\")\n}\n\nfunc (suite *TimestampORMSuite) TestCheckFixture() {\n\tvar row struct {\n\t\tC1 float64\n\t\tC2 float64\n\t}\n\terr := suite.db.Gorm().Table(suite.tableName).\n\t\tSelect([]string{\"UNIX_TIMESTAMP(d) AS C1\", \"UNIX_TIMESTAMP(e) AS C2\"}).\n\t\tFind(&row).\n\t\tError\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(1633803684.694123, row.C1)\n\tsuite.Require().Equal(1633803684.694123, row.C2)\n}\n\nfunc (suite *TimestampORMSuite) TestScanFromInt() {\n\tvar r Timestamp\n\terr := suite.db.Gorm().Table(suite.tableName).Select(\"a\").Take(&r).Error\n\tsuite.Require().Error(err)\n}\n\nfunc (suite *TimestampORMSuite) TestScanFromDouble() {\n\tvar r Timestamp\n\terr := suite.db.Gorm().Table(suite.tableName).Select(\"b\").Take(&r).Error\n\tsuite.Require().Error(err)\n}\n\nfunc (suite *TimestampORMSuite) TestScanFromString() {\n\tvar r Timestamp\n\terr := suite.db.Gorm().Table(suite.tableName).Select(\"c\").Take(&r).Error\n\tsuite.Require().Error(err)\n}\n\nfunc (suite *TimestampORMSuite) TestScanFromTimestamp() {\n\tvar r Timestamp\n\n\t// WARN: This test case shows that, even for \"TIMESTAMP\" field types, MySQL will transmit the value\n\t// in civil format (like Y-m-d H:m:s) and drop the timezone part. Then, if the go-mysql driver thinks\n\t// it's in UTC time zone then we will get the wrong result.\n\t// To ensure the result correctness for \"TIMESTAMP\" field types, the UTC time_zone must be enforced in both\n\t// driver side and database side.\n\n\tsuite.db.MustExec(\"SET time_zone = '+08:00'\")\n\terr := suite.db.Gorm().Table(suite.tableName).Select(\"d\").Take(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(int64(1633832484694123000), r.UnixNano())\n\n\tsuite.db.MustExec(\"SET time_zone = '+00:00'\")\n\terr = suite.db.Gorm().Table(suite.tableName).Select(\"d\").Take(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(int64(1633803684694123000), r.UnixNano())\n\n\t// Another safe way to deal with the TIMESTAMP is to use UNIX_TIMESTAMP function:\n\tsuite.db.MustExec(\"SET time_zone = '+03:00'\") // Session time zone doesn't matter\n\tvar r2 float64\n\terr = suite.db.Gorm().Table(suite.tableName).Select(\"UNIX_TIMESTAMP(d)\").Take(&r2).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(1633803684.694123, r2)\n}\n\nfunc (suite *TimestampORMSuite) TestScanFromDatetime() {\n\tvar r Timestamp\n\n\tsuite.db.MustExec(\"SET time_zone = '+08:00'\")\n\terr := suite.db.Gorm().Table(suite.tableName).Select(\"e\").Take(&r).Error\n\tsuite.Require().NoError(err)\n\t// Note: MySQL return the \"Y-m-d H:m:s\" as it is, while the driver will treat it as in UTC.\n\tsuite.Require().Equal(int64(1633803684694123000), r.UnixNano())\n\n\tsuite.db.MustExec(\"SET time_zone = '+04:00'\") // Session time zone doesn't matter.\n\terr = suite.db.Gorm().Table(suite.tableName).Select(\"e\").Take(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(int64(1633803684694123000), r.UnixNano())\n}\n\n// TestScanToGoTypes verifies different behaviours when scanning a MySQL TIMESTAMP field type into different\n// Golang types.\nfunc (suite *TimestampORMSuite) TestScanToGoTypes() {\n\tvar r1 int\n\terr := suite.db.Gorm().Table(suite.tableName).Select(\"d\").Take(&r1).Error\n\tsuite.Require().Error(err)\n\n\tvar r2 float64\n\terr = suite.db.Gorm().Table(suite.tableName).Select(\"d\").Take(&r2).Error\n\tsuite.Require().Error(err)\n\n\t// Scanning a TIMESTAMP field type into String is valid.\n\tvar r3 string\n\tsuite.db.MustExec(\"SET time_zone = '+00:00'\")\n\terr = suite.db.Gorm().Table(suite.tableName).Select(\"d\").Take(&r3).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(\"2021-10-09T18:21:24.694123Z\", r3)\n\n\tsuite.db.MustExec(\"SET time_zone = '+03:00'\")\n\terr = suite.db.Gorm().Table(suite.tableName).Select(\"d\").Take(&r3).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(\"2021-10-09T21:21:24.694123Z\", r3)\n}\n\nfunc (suite *TimestampORMSuite) TestWhere() {\n\tvar r Timestamp\n\terr := suite.db.Gorm().\n\t\tTable(suite.tableName).\n\t\tSelect(\"d\").\n\t\tWhere(\"d = ?\", Timestamp{Time: time.Unix(0, 1633803684694123000)}).\n\t\tTake(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(int64(1633803684694123000), r.UnixNano())\n\n\terr = suite.db.Gorm().\n\t\tTable(suite.tableName).\n\t\tSelect(\"d\").\n\t\tWhere(\"d > ?\", Timestamp{Time: time.Unix(0, 1633803684694000000)}).\n\t\tTake(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(int64(1633803684694123000), r.UnixNano())\n\n\terr = suite.db.Gorm().\n\t\tTable(suite.tableName).\n\t\tSelect(\"d\").\n\t\tWhere(\"d = ?\", Timestamp{Time: time.Unix(0, 1633803684694000000)}).\n\t\tTake(&r).Error\n\tsuite.Require().Error(err)\n\tsuite.Require().Equal(gorm.ErrRecordNotFound, err)\n\n\terr = suite.db.Gorm().\n\t\tTable(suite.tableName).\n\t\tSelect(\"d\").\n\t\tWhere(\"d > ?\", Timestamp{Time: time.Unix(0, 1633803684694123001)}).\n\t\tTake(&r).Error\n\tsuite.Require().Error(err)\n\tsuite.Require().Equal(gorm.ErrRecordNotFound, err)\n\n\t// It is also possible to specify string directly for a TIMESTAMP type.\n\terr = suite.db.Gorm().\n\t\tTable(suite.tableName).\n\t\tSelect(\"d\").\n\t\tWhere(\"d = ?\", \"2021-10-09 18:21:24.694123\").\n\t\tTake(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(int64(1633803684694123000), r.UnixNano())\n}\n\nfunc (suite *TimestampORMSuite) TestWhereInIndex() {\n\ttableName := \"`\" + suite.db.NewID() + \"`\"\n\n\tsuite.db.MustExec(fmt.Sprintf(`CREATE TABLE %s (\n\t\tid int,\n\t\tts timestamp(6),\n\t\tINDEX idx (ts)\n\t)`, tableName))\n\tsuite.db.MustExec(fmt.Sprintf(`INSERT INTO %s VALUES (\n\t\t1,\n\t\tFROM_UNIXTIME(1633880141.307801)\n\t)`, tableName))\n\n\tdefer suite.db.MustExec(fmt.Sprintf(`DROP TABLE IF EXISTS %s`, tableName))\n\n\tvar r Timestamp\n\terr := suite.db.Gorm().\n\t\tTable(tableName).\n\t\tSelect(\"ts\").\n\t\tWhere(\"ts = ?\", Timestamp{Time: time.Unix(0, 1633880141307801000)}).\n\t\tTake(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(int64(1633880141307801000), r.UnixNano())\n\n\t// Verify Plan is Index Scan\n\texplain := suite.db.\n\t\tMustExplain(fmt.Sprintf(\"SELECT ts FROM %s WHERE ts = ?\", tableName),\n\t\t\tTimestamp{Time: time.Unix(0, 1633880141307801000)})\n\ttestutil.RequireIndexRangeScan(suite.T(), explain)\n}\n\nfunc (suite *TimestampORMSuite) TestInsert() {\n\ttableName := \"`\" + suite.db.NewID() + \"`\"\n\n\tsuite.db.MustExec(fmt.Sprintf(`CREATE TABLE %s (\n\t\tid int,\n\t\tts timestamp(6)\n\t)`, tableName))\n\n\tdefer suite.db.MustExec(fmt.Sprintf(`DROP TABLE IF EXISTS %s`, tableName))\n\n\ttype model struct {\n\t\tID int\n\t\tTs Timestamp\n\t}\n\terr := suite.db.Gorm().Table(tableName).Create(model{\n\t\tID: 5,\n\t\tTs: Timestamp{Time: time.Unix(0, 1633880957785123456)},\n\t}).Error\n\tsuite.Require().NoError(err)\n\n\tvar r model\n\terr = suite.db.Gorm().Table(tableName).Take(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(5, r.ID)\n\tsuite.Require().Equal(int64(1633880957785123000), r.Ts.UnixNano())\n}\n\nfunc (suite *TimestampORMSuite) TestScanNull() {\n\ttableName := \"`\" + suite.db.NewID() + \"`\"\n\n\tsuite.db.MustExec(fmt.Sprintf(`CREATE TABLE %s (\n\t\tid int,\n\t\tts timestamp(6)\n\t)`, tableName))\n\tsuite.db.MustExec(fmt.Sprintf(`INSERT INTO %s VALUES (\n\t\t100,\n\t\tnull\n\t)`, tableName))\n\n\tdefer suite.db.MustExec(fmt.Sprintf(`DROP TABLE IF EXISTS %s`, tableName))\n\n\tvar r Timestamp\n\terr := suite.db.Gorm().\n\t\tTable(tableName).\n\t\tSelect(\"ts\").\n\t\tTake(&r).Error\n\tsuite.Require().NoError(err)\n\tsuite.Require().Equal(int64(0), r.UnixNano())\n}\n\nfunc TestTimestampInORM(t *testing.T) {\n\tsuite.Run(t, &TimestampORMSuite{\n\t\tusePrepareStatement: false,\n\t})\n}\n\nfunc TestTimestampInORMWithPrepared(t *testing.T) {\n\tsuite.Run(t, &TimestampORMSuite{\n\t\tusePrepareStatement: true,\n\t})\n}\n"
  },
  {
    "path": "util/gormutil/virtualview/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage virtualview\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/gormutil/virtualview/schema.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage virtualview\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/antonmedv/expr/ast\"\n\t\"github.com/antonmedv/expr/parser\"\n\t\"github.com/fatih/structtag\"\n\t\"gorm.io/gorm/schema\"\n)\n\ntype viewFieldProps struct {\n\tjsonNameL            string   // JSON name in lower case. Possibly empty, means field is JSON unexported.\n\tviewExpr             string   // Possibly empty, means either vexpr tag is not set, or is set to empty\n\tcolumnNameL          string   // DB column name in lower case.\n\tdependOnColumnNamesL []string // Possibly nil when viewExpr is empty.\n\n\t// isInvalid indicates whether this field can be safely used to construct SQL clauses.\n\t// Fields are not invalid by default. It can be updated by updateFieldAvailability.\n\tisInvalid bool\n}\n\nfunc decodeField(ft reflect.StructField) (props viewFieldProps, err error) {\n\t// parse vexpr tag\n\tprops.viewExpr = ft.Tag.Get(\"vexpr\")\n\n\t// parse JSON tag\n\t{\n\t\tprops.jsonNameL = strings.ToLower(ft.Name)\n\t\ttags, _ := structtag.Parse(string(ft.Tag))\n\t\tif tags != nil {\n\t\t\tjsonTag, _ := tags.Get(\"json\")\n\t\t\tif jsonTag != nil && jsonTag.Name != \"\" {\n\t\t\t\tprops.jsonNameL = strings.ToLower(jsonTag.Name)\n\t\t\t}\n\t\t\tif props.jsonNameL == \"-\" {\n\t\t\t\tprops.jsonNameL = \"\"\n\t\t\t}\n\t\t}\n\t}\n\n\t// parse gorm tag\n\t{\n\t\tgormTag := schema.ParseTagSetting(ft.Tag.Get(\"gorm\"), \";\")\n\t\tdbName := gormTag[\"COLUMN\"]\n\t\tif dbName == \"\" {\n\t\t\tdbName = schema.NamingStrategy{}.ColumnName(\"\", ft.Name)\n\t\t}\n\t\tprops.columnNameL = strings.ToLower(dbName)\n\t}\n\n\tif props.viewExpr != \"\" {\n\t\tdepFields, err := parseExprDependencies(props.viewExpr)\n\t\tif err != nil {\n\t\t\treturn viewFieldProps{}, err\n\t\t}\n\t\tprops.dependOnColumnNamesL = depFields\n\t}\n\n\treturn\n}\n\ntype identVisitor struct {\n\tidents []string\n}\n\nfunc (v *identVisitor) Enter(_ *ast.Node) {}\nfunc (v *identVisitor) Exit(node *ast.Node) {\n\tif n, ok := (*node).(*ast.IdentifierNode); ok {\n\t\tv.idents = append(v.idents, n.Value)\n\t}\n}\n\n// Note: not all SQL statement is supported.\n// For example, CAST(.. AS SIGNED) ans `COLUMN` is unsupported.\nfunc parseExprDependencies(expr string) ([]string, error) {\n\t// Parse idents\n\ttree, err := parser.Parse(expr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvisitor := &identVisitor{}\n\tast.Walk(&tree.Node, visitor)\n\n\t// Normalize and deduplicate idents\n\tidentMap := map[string]struct{}{}\n\tfor _, ident := range visitor.idents {\n\t\tidentMap[strings.ToLower(ident)] = struct{}{}\n\t}\n\tidents := make([]string, 0, len(identMap))\n\tfor ident := range identMap {\n\t\tidents = append(idents, ident)\n\t}\n\tsort.Strings(idents)\n\treturn idents, nil\n}\n\n// updateAvailability updates field's availability according to the `knownColumnNamesL` parameter:\n//   - For calculated fields (vexpr is specified), the field is available when\n//     all of its dependency columns exist in knownColumnNamesL.\n//   - For normal fields (vexpr is not specified), the field is available when\n//     itself exists in knownColumnNamesL.\n//\n// If knownColumnNamesL is nil, all fields will be set to available.\nfunc (fp *viewFieldProps) updateAvailability(knownColumnNamesL map[string]struct{}) {\n\tfp.isInvalid = false\n\tif knownColumnNamesL == nil {\n\t\treturn\n\t}\n\n\tif fp.viewExpr == \"\" {\n\t\t// This is not a calculated field.\n\t\tif _, ok := knownColumnNamesL[fp.columnNameL]; !ok {\n\t\t\tfp.isInvalid = true\n\t\t\treturn\n\t\t}\n\t}\n\n\t// This is a calculated field. Any missing dependency field result in a invalid status.\n\tfor _, columnNameL := range fp.dependOnColumnNamesL {\n\t\tif _, ok := knownColumnNamesL[columnNameL]; !ok {\n\t\t\tfp.isInvalid = true\n\t\t\treturn\n\t\t}\n\t}\n}\n\ntype viewSchema struct {\n\tfields             []*viewFieldProps\n\tfieldByJSONNameL   map[string]*viewFieldProps\n\tfieldByColumnNameL map[string]*viewFieldProps\n}\n\nfunc parseViewModelSchema(model interface{}) (viewSchema, error) {\n\tv := reflect.Indirect(reflect.ValueOf(model))\n\tvt := v.Type()\n\n\tfields := make([]*viewFieldProps, 0)\n\n\tfor i := 0; i < vt.NumField(); i++ {\n\t\tft := vt.Field(i)\n\t\tprops, err := decodeField(ft)\n\t\tif err != nil {\n\t\t\treturn viewSchema{}, fmt.Errorf(\"field %s is invalid: %w\", ft.Name, err)\n\t\t}\n\t\tif props.jsonNameL == \"\" {\n\t\t\t// this field is unexported, skip.\n\t\t\tcontinue\n\t\t}\n\t\tfields = append(fields, &props)\n\t}\n\n\tvs := viewSchema{\n\t\tfields:             fields,\n\t\tfieldByJSONNameL:   map[string]*viewFieldProps{},\n\t\tfieldByColumnNameL: map[string]*viewFieldProps{},\n\t}\n\tfor _, field := range fields {\n\t\tvs.fieldByJSONNameL[field.jsonNameL] = field\n\t\tvs.fieldByColumnNameL[field.columnNameL] = field\n\t}\n\treturn vs, nil\n}\n\nfunc (schema *viewSchema) updateFieldsAvailability(knownColumnNames []string) {\n\tif knownColumnNames == nil {\n\t\tfor _, field := range schema.fields {\n\t\t\tfield.updateAvailability(nil)\n\t\t}\n\t\treturn\n\t}\n\n\tcolumnNamesL := map[string]struct{}{}\n\tfor _, columnName := range knownColumnNames {\n\t\tcolumnNamesL[strings.ToLower(columnName)] = struct{}{}\n\t}\n\tfor _, field := range schema.fields {\n\t\tfield.updateAvailability(columnNamesL)\n\t}\n}\n"
  },
  {
    "path": "util/gormutil/virtualview/schema_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage virtualview\n\nimport (\n\t\"encoding/json\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil\"\n)\n\nfunc TestParseExprDependencies(t *testing.T) {\n\ttests := []struct {\n\t\twant []string\n\t\targs string\n\t}{\n\t\t{[]string{\"my_col\"}, \"my_col\"},\n\t\t{[]string{\"col2\", \"my_col\"}, \"my_col + 1.0 * col2\"},\n\t\t{[]string{\"time\"}, \"(UNIX_TIMESTAMP(Time) + 0E0)\"},\n\t\t{[]string{\"bar\", \"time\"}, \"Floor(Foo(Time, 'abc', Def(), Bar))\"},\n\t\t{[]string{\"avg_process_time\", \"exec_count\", \"sum_cop_task_num\"}, \"SUM(exec_count * avg_process_time) / SUM(sum_cop_task_num)\"},\n\t\t{[]string{\"bar\"}, \"Def() + Bar\"},\n\t\t{[]string{\"col0\", \"col1\"}, \"Plus(Col1 * col1) + COL0\"},\n\t}\n\tfor _, tt := range tests {\n\t\tv, err := parseExprDependencies(tt.args)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, tt.want, v)\n\t}\n\n\tfailTests := []string{\n\t\t\"CAST(exec_count AS SIGNED)\",\n\t\t\"Floor(\",\n\t\t\"Def() + Bar)\",\n\t\t\"\",\n\t}\n\tfor _, tt := range failTests {\n\t\t_, err := parseExprDependencies(tt)\n\t\trequire.Error(t, err)\n\t}\n}\n\nfunc TestDecodeFieldAssumptionJSON(t *testing.T) {\n\t// For JSON, when it is not specified in the json tag, field name is used:\n\ttype TestModel struct {\n\t\tQueryValue     string\n\t\tJSONUnexported string `json:\"-\"`\n\t\tJSONSkip       string `json:\",omitempty\"`\n\t}\n\tval, err := json.Marshal(TestModel{\n\t\tQueryValue:     \"a\",\n\t\tJSONUnexported: \"b\",\n\t\tJSONSkip:       \"c\",\n\t})\n\trequire.NoError(t, err)\n\trequire.JSONEq(t, `{\"QueryValue\":\"a\",\"JSONSkip\":\"c\"}`, string(val))\n}\n\nfunc TestDecodeFieldAssumptionGORM(t *testing.T) {\n\t// For GORM, when it is not specified in the tag, default naming strategy will be used:\n\t//nolint:govet\n\ttype TestModel struct {\n\t\tGORMOmit    string\n\t\tQueryValue  string `gorm:\"column:qv\"`\n\t\tGORMSkip    string `gorm:\"primaryKey\"`\n\t\tGORMInvalid string `gorm:\"foo\"`\n\t\tInvalidTag  string `abc`\n\t}\n\n\tdb := testutil.OpenMockDB(t)\n\tdefer db.MustClose()\n\n\tdb.Mocker().\n\t\tExpectExec(\"CREATE TABLE `test_models` (`gorm_omit` longtext,`qv` longtext,`gorm_skip` varchar(191),`gorm_invalid` longtext,`invalid_tag` longtext,PRIMARY KEY (`gorm_skip`))\").\n\t\tWillReturnResult(sqlmock.NewResult(0, 1))\n\n\terr := db.Gorm().Migrator().CreateTable(TestModel{})\n\trequire.NoError(t, err)\n\n\tdb.MustMeetMockExpectation()\n}\n\n//nolint:govet\ntype SampleModel struct {\n\tDigest         string `gorm:\"column:MyDigest\" json:\"digest\"`\n\tQueryValue     string\n\tTimestamp      float64 `gorm:\"column:timestamp\" vexpr:\"PLUS(a, b)\"`\n\tJSONUnexported string  `json:\"-\"`\n\tJSONSkip       string  `json:\",omitempty\"`\n\tInvalidTag     string  `abc`\n\tGORMInvalid    string  `gorm:\"foo\"`\n\tJSONAlias      string  `vexpr:\"SUM(a+1)\" json:\"foo\"`\n\tFull           string  `vexpr:\"AVG(Time)\" json:\"bar\" gorm:\"column:full_col\"`\n}\n\nfunc TestDecodeField(t *testing.T) {\n\tft := reflect.TypeFor[SampleModel]()\n\tf, err := decodeField(ft.Field(0))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"digest\", f.jsonNameL)\n\trequire.Equal(t, \"\", f.viewExpr)\n\trequire.Equal(t, \"mydigest\", f.columnNameL)\n\n\tf, err = decodeField(ft.Field(1))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"queryvalue\", f.jsonNameL)\n\trequire.Equal(t, \"\", f.viewExpr)\n\trequire.Equal(t, \"query_value\", f.columnNameL)\n\n\tf, err = decodeField(ft.Field(2))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"timestamp\", f.jsonNameL)\n\trequire.Equal(t, \"PLUS(a, b)\", f.viewExpr)\n\trequire.Equal(t, \"timestamp\", f.columnNameL)\n\n\tf, err = decodeField(ft.Field(3))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"\", f.jsonNameL)\n\trequire.Equal(t, \"\", f.viewExpr)\n\trequire.Equal(t, \"json_unexported\", f.columnNameL)\n\n\tf, err = decodeField(ft.Field(4))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"jsonskip\", f.jsonNameL)\n\trequire.Equal(t, \"\", f.viewExpr)\n\trequire.Equal(t, \"json_skip\", f.columnNameL)\n\n\tf, err = decodeField(ft.Field(5))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"invalidtag\", f.jsonNameL)\n\trequire.Equal(t, \"\", f.viewExpr)\n\trequire.Equal(t, \"invalid_tag\", f.columnNameL)\n\n\tf, err = decodeField(ft.Field(6))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"gorminvalid\", f.jsonNameL)\n\trequire.Equal(t, \"\", f.viewExpr)\n\trequire.Equal(t, \"gorm_invalid\", f.columnNameL)\n\n\tf, err = decodeField(ft.Field(7))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foo\", f.jsonNameL)\n\trequire.Equal(t, \"SUM(a+1)\", f.viewExpr)\n\trequire.Equal(t, \"json_alias\", f.columnNameL)\n\n\tf, err = decodeField(ft.Field(8))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"bar\", f.jsonNameL)\n\trequire.Equal(t, \"AVG(Time)\", f.viewExpr)\n\trequire.Equal(t, \"full_col\", f.columnNameL)\n}\n\nfunc TestParseViewModelSchemaSuccess(t *testing.T) {\n\tschema, err := parseViewModelSchema(&SampleModel{})\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, 8, len(schema.fields))\n\trequire.Equal(t, \"digest\", schema.fields[0].jsonNameL)\n\trequire.Equal(t, \"queryvalue\", schema.fields[1].jsonNameL)\n\trequire.Equal(t, \"timestamp\", schema.fields[2].jsonNameL)\n\trequire.Equal(t, \"jsonskip\", schema.fields[3].jsonNameL)\n\trequire.Equal(t, \"invalidtag\", schema.fields[4].jsonNameL)\n\trequire.Equal(t, \"gorminvalid\", schema.fields[5].jsonNameL)\n\trequire.Equal(t, \"foo\", schema.fields[6].jsonNameL)\n\trequire.Equal(t, \"bar\", schema.fields[7].jsonNameL)\n\n\trequire.Equal(t, schema.fields[0], schema.fieldByJSONNameL[\"digest\"])\n\trequire.Equal(t, schema.fields[1], schema.fieldByJSONNameL[\"queryvalue\"])\n\trequire.Equal(t, schema.fields[3], schema.fieldByJSONNameL[\"jsonskip\"])\n\trequire.Equal(t, schema.fields[6], schema.fieldByJSONNameL[\"foo\"])\n\trequire.Equal(t, schema.fields[7], schema.fieldByJSONNameL[\"bar\"])\n\n\trequire.Equal(t, schema.fields[0], schema.fieldByColumnNameL[\"mydigest\"])\n\trequire.Equal(t, schema.fields[1], schema.fieldByColumnNameL[\"query_value\"])\n\trequire.Equal(t, schema.fields[4], schema.fieldByColumnNameL[\"invalid_tag\"])\n\trequire.Equal(t, schema.fields[6], schema.fieldByColumnNameL[\"json_alias\"])\n\trequire.Equal(t, schema.fields[7], schema.fieldByColumnNameL[\"full_col\"])\n\n\t// Test passing struct directly\n\tschema, err = parseViewModelSchema(SampleModel{})\n\trequire.NoError(t, err)\n\n\trequire.Equal(t, 8, len(schema.fields))\n\trequire.Equal(t, \"digest\", schema.fields[0].jsonNameL)\n\trequire.Equal(t, \"queryvalue\", schema.fields[1].jsonNameL)\n\trequire.Equal(t, \"timestamp\", schema.fields[2].jsonNameL)\n\trequire.Equal(t, \"jsonskip\", schema.fields[3].jsonNameL)\n\trequire.Equal(t, \"invalidtag\", schema.fields[4].jsonNameL)\n\trequire.Equal(t, \"gorminvalid\", schema.fields[5].jsonNameL)\n\trequire.Equal(t, \"foo\", schema.fields[6].jsonNameL)\n\trequire.Equal(t, \"bar\", schema.fields[7].jsonNameL)\n}\n\nfunc TestParseViewModelSchemaFailure(t *testing.T) {\n\ttype Model struct {\n\t\tDigest string `gorm:\"column:MyDigest\" json:\"digest\"`\n\t\tFoo    string `vexpr:\"invalidExpr(a,\"`\n\t}\n\t_, err := parseViewModelSchema(&Model{})\n\trequire.Error(t, err)\n\n\t_, err = parseViewModelSchema(Model{})\n\trequire.Error(t, err)\n}\n\nfunc TestUpdateFieldsAvailability(t *testing.T) {\n\tschema, err := parseViewModelSchema(&SampleModel{})\n\trequire.NoError(t, err)\n\trequire.Equal(t, 8, len(schema.fields))\n\n\trequireFieldsInvalid := func(status ...bool) {\n\t\trequire.Equal(t, len(status), len(schema.fields))\n\t\tfor idx, s := range status {\n\t\t\trequire.Equal(t, s, schema.fields[idx].isInvalid,\n\t\t\t\t\"expect invalid=%v for field[%d], got invalid=%v\",\n\t\t\t\ts, idx, schema.fields[idx].isInvalid)\n\t\t}\n\t}\n\n\trequireFieldsInvalid(false, false, false, false, false, false, false, false)\n\n\tschema.updateFieldsAvailability([]string{})\n\trequireFieldsInvalid(true, true, true, true, true, true, true, true)\n\n\tschema.updateFieldsAvailability(nil)\n\trequireFieldsInvalid(false, false, false, false, false, false, false, false)\n\n\tschema.updateFieldsAvailability([]string{\"Query_Value\"})\n\trequireFieldsInvalid(true, false, true, true, true, true, true, true)\n\n\tschema.updateFieldsAvailability([]string{\"c\"})\n\trequireFieldsInvalid(true, true, true, true, true, true, true, true)\n\n\tschema.updateFieldsAvailability([]string{\"a\"})\n\trequireFieldsInvalid(true, true, true, true, true, true, false, true)\n\n\tschema.updateFieldsAvailability([]string{\"a\", \"B\"})\n\trequireFieldsInvalid(true, true, false, true, true, true, false, true)\n\n\tschema.updateFieldsAvailability([]string{\"query_value\", \"A\", \"B\"})\n\trequireFieldsInvalid(true, false, false, true, true, true, false, true)\n\n\tschema.updateFieldsAvailability([]string{\"queryvalue\", \"A\", \"B\"})\n\trequireFieldsInvalid(true, true, false, true, true, true, false, true)\n\n\tschema.updateFieldsAvailability([]string{\"timestamp\"})\n\trequireFieldsInvalid(true, true, true, true, true, true, true, true)\n\n\tschema.updateFieldsAvailability([]string{\"query_value\", \"GORM_INVALID\", \"A\", \"B\"})\n\trequireFieldsInvalid(true, false, false, true, true, false, false, true)\n\n\tschema.updateFieldsAvailability([]string{\"query_value\", \"digest\", \"json_skip\", \"foobar\"})\n\trequireFieldsInvalid(true, false, true, false, true, true, true, true)\n\n\tschema.updateFieldsAvailability([]string{\"query_value\", \"digest\", \"MYDIGEST\", \"json_skip\", \"foobar\"})\n\trequireFieldsInvalid(false, false, true, false, true, true, true, true)\n}\n"
  },
  {
    "path": "util/gormutil/virtualview/virtualview.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage virtualview\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"gorm.io/gorm/clause\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/nocopy\"\n)\n\n// VirtualView is a helper to construct SQL clauses for view-like models, based on the view definition in\n// the `vexpr` tag in the model.\n//\n// For a model field like:\n//\n//\tAggLastSeen   int  `vexpr:\"UNIX_TIMESTAMP(MAX(last_seen))\"`\n//\n// VirtualView can build projections like:\n//\n//\t    SELECT UNIX_TIMESTAMP(MAX(last_seen)) AS agg_last_seen ....\n//\t\t\t\t\t\t\t\t\t\t\t\t\t^^^^^^^^^^^^^ This follows the GORM naming strategy and\n//\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t  can be controlled by gorm:\"column:xxx\".\n//\n// Then, when selecting the result of this projection into the same model, fields will be filled out naturally:\n//\n//\t    {\n//\t\t     AggLastSeen: <the result of `UNIX_TIMESTAMP(MAX(last_seen))`>\n//\t    }\n//\n// If `vexpr` is not specified in the model field, the field can be transparently used. For example:\n//\n//\tFieldFoo  int\n//\n// The projection will be:\n//\n//\tSELECT field_foo ...\n//\n// Callers must specify the fields to be used in clauses. The field can be specified via its JSON name.\n//\n// VirtualView is safe to be used concurrently.\ntype VirtualView struct {\n\tnocopy.NoCopy\n\tfullSchema viewSchema\n\n\tmu sync.Mutex\n}\n\nfunc New(model interface{}) (*VirtualView, error) {\n\tschema, err := parseViewModelSchema(model)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &VirtualView{\n\t\tfullSchema: schema,\n\t}, nil\n}\n\nfunc MustNew(model interface{}) *VirtualView {\n\tvv, err := New(model)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn vv\n}\n\n// SetSourceDBColumns restricts SelectClause and OrderByClause to only build clauses over these\n// specified db columns or fields calculated from these db columns. The restriction will be removed\n// if nil is given.\n// This is useful when the source table does not fully match the model, e.g. when there are extra\n// fields in the model, this can avoid view to be broken.\n//\n// This function is concurrent-safe.\nfunc (vv *VirtualView) SetSourceDBColumns(dbColumnNames []string) {\n\tvv.mu.Lock()\n\tvv.fullSchema.updateFieldsAvailability(dbColumnNames)\n\tvv.mu.Unlock()\n}\n\nfunc (vv *VirtualView) Clauses(jsonFieldNames []string) Clauses {\n\tm := map[string]struct{}{}\n\tfor _, name := range jsonFieldNames {\n\t\tm[strings.ToLower(name)] = struct{}{}\n\t}\n\treturn Clauses{\n\t\tvv:                vv,\n\t\tjsonFieldNames:    jsonFieldNames,\n\t\tjsonFieldsByNameL: m,\n\t}\n}\n\ntype Clauses struct {\n\tvv                *VirtualView\n\tjsonFieldNames    []string\n\tjsonFieldsByNameL map[string]struct{}\n}\n\n// Select builds a Select clause that return all fields specified in Clauses().\n//\n// This function is concurrent-safe.\nfunc (vvc Clauses) Select() clause.Expression {\n\tvvc.vv.mu.Lock()\n\tdefer vvc.vv.mu.Unlock()\n\n\tselectColumns := make([]clause.Column, 0, len(vvc.jsonFieldNames))\n\tfor _, fieldName := range vvc.jsonFieldNames {\n\t\tfieldNameL := strings.ToLower(fieldName)\n\t\tfield := vvc.vv.fullSchema.fieldByJSONNameL[fieldNameL]\n\t\tif field == nil {\n\t\t\t// The specified JSON field does not exist in the schema, ignoring.\n\t\t\tcontinue\n\t\t}\n\t\tif field.isInvalid {\n\t\t\t// The field has already been filtered out using SetSourceDBColumns\n\t\t\tcontinue\n\t\t}\n\t\tif field.viewExpr == \"\" {\n\t\t\t// Not a computed field, just use the column name.\n\t\t\tselectColumns = append(selectColumns, clause.Column{\n\t\t\t\tName: field.columnNameL,\n\t\t\t})\n\t\t} else {\n\t\t\t// Computed field, build SQL like:\n\t\t\t// SELECT PLUS(a,b) AS column_name, ...\n\t\t\t//        ^^^^^^^^^^^^^^^^^^^^^^^^\n\t\t\tselectColumns = append(selectColumns, clause.Column{\n\t\t\t\tName: fmt.Sprintf(\"%s AS %s\", field.viewExpr, field.columnNameL),\n\t\t\t\tRaw:  true,\n\t\t\t})\n\t\t\t// TODO: We'd better quote the alias field name.\n\t\t}\n\t}\n\n\tif len(selectColumns) == 0 {\n\t\t// Avoid becoming \"SELECT *\".\n\t\tselectColumns = append(selectColumns, clause.Column{\n\t\t\tName: \"NULL AS __HIDDEN_FIELD__\",\n\t\t\tRaw:  true,\n\t\t})\n\t}\n\n\treturn clause.Select{\n\t\tDistinct: false,\n\t\tColumns:  selectColumns,\n\t}\n}\n\ntype OrderByField struct {\n\tJSONFieldName string `json:\"json_field_name\"`\n\tIsDesc        bool   `json:\"is_desc\"`\n}\n\n// OrderBy builds a Order By clause based on the given fields. Order by fields that do not exist\n// when calling Clauses() will be ignored.\n//\n// This function is concurrent-safe.\nfunc (vvc Clauses) OrderBy(fields []OrderByField) clause.Expression {\n\tvvc.vv.mu.Lock()\n\tdefer vvc.vv.mu.Unlock()\n\n\torderByColumns := make([]clause.OrderByColumn, 0, len(fields))\n\tfor _, f := range fields {\n\t\tfieldNameL := strings.ToLower(f.JSONFieldName)\n\t\tif _, ok := vvc.jsonFieldsByNameL[fieldNameL]; !ok {\n\t\t\t// This field does not exist in the select clause. It should not be allowed to order by.\n\t\t\tcontinue\n\t\t}\n\t\tfield := vvc.vv.fullSchema.fieldByJSONNameL[fieldNameL]\n\t\tif field == nil {\n\t\t\tcontinue\n\t\t}\n\t\tif field.isInvalid {\n\t\t\tcontinue\n\t\t}\n\t\torderByColumns = append(orderByColumns, clause.OrderByColumn{\n\t\t\tColumn: clause.Column{Name: field.columnNameL},\n\t\t\tDesc:   f.IsDesc,\n\t\t})\n\t}\n\n\t// If non of the order by field is valid, no ordering will be specified.\n\tif len(orderByColumns) == 0 {\n\t\treturn nopClause{}\n\t}\n\n\treturn clause.OrderBy{Columns: orderByColumns}\n}\n\nvar (\n\t_ clause.Interface  = nopClause{}\n\t_ clause.Expression = nopClause{}\n)\n\n// nopClause is a clause that will be never embedded into the SQL statement.\ntype nopClause struct{}\n\nfunc (c nopClause) Name() string {\n\treturn \"__DUMMY_CLAUSE__\"\n}\n\nfunc (c nopClause) Build(_ clause.Builder) {}\n\nfunc (c nopClause) MergeClause(_ *clause.Clause) {}\n"
  },
  {
    "path": "util/gormutil/virtualview/virtualview_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage virtualview\n\nimport (\n\t\"testing\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil\"\n)\n\nfunc TestVirtualViewSelect(t *testing.T) {\n\tvv := MustNew(SampleModel{})\n\n\tdb := testutil.OpenMockDB(t)\n\tdefer db.MustClose()\n\n\tvar results []SampleModel\n\n\t// No field\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr := db.Gorm().\n\t\tClauses(vv.Clauses([]string{}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A field with GORM specified column name\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `mydigest` FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"digest\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A field with alternative JSON letter case\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `mydigest` FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"DIgest\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A field without GORM specified column name\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `query_value` FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"QueryValue\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `query_value` FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"queryVALUE\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// Multiple fields\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `query_value`,`mydigest` FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"QueryValue\", \"digest\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A field with json alias\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"foo\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"Foo\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A field that is not exported\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"jsonunexported\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A field with the original JSON name of the field (which should be failed).\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"JSONAlias\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// There is an unknown field\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `mydigest` FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"query_value\", \"DIGEST\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// All fields are unknown\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"query_value\", \"xyz\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A field with vexpr\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT PLUS(a, b) AS timestamp FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"timestamp\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// Complex field selection\n\tallFieldNames := []string{\"invalidtag\", \"jsonalias\", \"foo\", \"gorminvalid\", \"queryvalue\", \"full\", \"timestamp\", \"bar\", \"digest\", \"jsonunexported\", \"jsonskip\"}\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `invalid_tag`,SUM(a+1) AS json_alias,`gorm_invalid`,`query_value`,PLUS(a, b) AS timestamp,AVG(Time) AS full_col,`mydigest`,`json_skip` FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses(allFieldNames).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// Update Source Columns.\n\t// All columns are not a used column.\n\tvv.SetSourceDBColumns([]string{\"abc\", \"Timestamp\", \"digest\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses(allFieldNames).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A computed field is partially matched.\n\tvv.SetSourceDBColumns([]string{\"a\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"TimeStamp\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A computed field is fully matched.\n\tvv.SetSourceDBColumns([]string{\"a\", \"b\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT PLUS(a, b) AS timestamp FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"TimeStamp\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A computed field itself is given as the source column -- should be treated as missing.\n\tvv.SetSourceDBColumns([]string{\"timestamp\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"TimeStamp\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// When a computed field itself is given, and all of its base columns are also given, we should\n\t// always build a computed expression.\n\tvv.SetSourceDBColumns([]string{\"timestamp\", \"a\", \"b\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT PLUS(a, b) AS timestamp FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"TIMESTAMP\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// Selecting base columns of a computed expression should not match any columns.\n\tvv.SetSourceDBColumns([]string{\"timestamp\", \"a\", \"b\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"a\", \"b\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A non-computed field is given, using its GORM column name.\n\tvv.SetSourceDBColumns([]string{\"mydigest\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `mydigest` FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"digest\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// If a GORM column name is specified but is not used, then it should be regarded as missing.\n\tvv.SetSourceDBColumns([]string{\"digest\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"digest\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// When a field contains both GORM column name and vexpr, column name should not be not a filter.\n\tvv.SetSourceDBColumns([]string{\"time\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT AVG(Time) AS full_col FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"bar\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tvv.SetSourceDBColumns([]string{\"full_col\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"bar\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A base column is used in multiple computed columns.\n\tvv.SetSourceDBColumns([]string{\"a\", \"b\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias,PLUS(a, b) AS timestamp FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"foo\", \"timestamp\", \"digest\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// Remove Source column filter.\n\tvv.SetSourceDBColumns(nil)\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias,PLUS(a, b) AS timestamp,`mydigest` FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(vv.Clauses([]string{\"foo\", \"timestamp\", \"digest\"}).Select()).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.MustMeetMockExpectation()\n}\n\nfunc TestVirtualViewOrderBy(t *testing.T) {\n\tvv := MustNew(SampleModel{})\n\n\tdb := testutil.OpenMockDB(t)\n\tdefer db.MustClose()\n\n\tvar results []SampleModel\n\n\t// No field in Clauses().\n\tc := vv.Clauses([]string{})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr := db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"foo\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// Field is specified in Clauses(), but no field in OrderBy().\n\tc = vv.Clauses([]string{\"foo\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A field not in Clauses() will be ignored.\n\tc = vv.Clauses([]string{\"digest\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `mydigest` FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"foo\", false}}),\n\t\t).\n\t\tFind(&results).Error\n\trequire.NoError(t, err)\n\n\t// A field does not really exist will be ignored.\n\tc = vv.Clauses([]string{\"xyz\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"xyz\", false}}),\n\t\t).\n\t\tFind(&results).Error\n\trequire.NoError(t, err)\n\n\t// A field with GORM specified column name\n\tc = vv.Clauses([]string{\"digest\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `mydigest` FROM `sample_models` ORDER BY `mydigest` DESC\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"DIGEST\", true}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A field without GORM specified column name\n\tc = vv.Clauses([]string{\"QueryValue\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `query_value` FROM `sample_models` ORDER BY `query_value`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"queryValue\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tc = vv.Clauses([]string{\"queryVALUE\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `query_value` FROM `sample_models` ORDER BY `query_value`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"QUERYVALUE\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// Multiple fields in clauses, use some of it\n\tc = vv.Clauses([]string{\"queryvalue\", \"digest\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `query_value`,`mydigest` FROM `sample_models` ORDER BY `query_value`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"queryValue\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `query_value`,`mydigest` FROM `sample_models` ORDER BY `mydigest`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"digest\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `query_value`,`mydigest` FROM `sample_models` ORDER BY `mydigest` DESC,`query_value`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{\n\t\t\t\t{\"digest\", true},\n\t\t\t\t{\"queryValue\", false},\n\t\t\t}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT `query_value`,`mydigest` FROM `sample_models` ORDER BY `mydigest` DESC,`query_value`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{\n\t\t\t\t{\"digest\", true},\n\t\t\t\t{\"timestamp\", false}, // This field is not in the Clauses()\n\t\t\t\t{\"queryValue\", false},\n\t\t\t}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A field with json alias\n\tc = vv.Clauses([]string{\"foo\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias FROM `sample_models` ORDER BY `json_alias`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"foo\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tc = vv.Clauses([]string{\"FOO\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias FROM `sample_models` ORDER BY `json_alias`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"Foo\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// A field with the original JSON name of the field should be ignored\n\tc = vv.Clauses([]string{\"foo\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"json_alias\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"jsonalias\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tc = vv.Clauses([]string{\"JSONAlias\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"jsonalias\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT NULL AS __HIDDEN_FIELD__ FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"foo\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// Update Source Columns.\n\t// Fully match some computed fields.\n\tvv.SetSourceDBColumns([]string{\"a\", \"b\"})\n\tc = vv.Clauses([]string{\"foo\", \"timestamp\", \"digest\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias,PLUS(a, b) AS timestamp FROM `sample_models` ORDER BY `json_alias`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"foo\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias,PLUS(a, b) AS timestamp FROM `sample_models` ORDER BY `timestamp`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"timestamp\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias,PLUS(a, b) AS timestamp FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"digest\", false}}), // ignored\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias,PLUS(a, b) AS timestamp FROM `sample_models` ORDER BY `json_alias`,`timestamp`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{\n\t\t\t\t{\"foo\", false},\n\t\t\t\t{\"digest\", false}, // ignored\n\t\t\t\t{\"timestamp\", false},\n\t\t\t}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// Partially match\n\tvv.SetSourceDBColumns([]string{\"a\"})\n\tc = vv.Clauses([]string{\"foo\", \"timestamp\", \"digest\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias FROM `sample_models` ORDER BY `json_alias`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"foo\", false}}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"timestamp\", false}}), // ignored\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias FROM `sample_models`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{{\"digest\", false}}), // ignored\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\t// Match a normal field.\n\tvv.SetSourceDBColumns([]string{\"a\", \"b\", \"mydigest\"})\n\tc = vv.Clauses([]string{\"foo\", \"timestamp\", \"digest\"})\n\tdb.Mocker().\n\t\tExpectQuery(\"SELECT SUM(a+1) AS json_alias,PLUS(a, b) AS timestamp,`mydigest` FROM `sample_models` ORDER BY `json_alias`,`mydigest`,`timestamp`\").\n\t\tWillReturnRows(sqlmock.NewRows([]string{\"some_column\"}))\n\terr = db.Gorm().\n\t\tClauses(\n\t\t\tc.Select(),\n\t\t\tc.OrderBy([]OrderByField{\n\t\t\t\t{\"foo\", false},\n\t\t\t\t{\"digest\", false},\n\t\t\t\t{\"timestamp\", false},\n\t\t\t}),\n\t\t).Find(&results).Error\n\trequire.NoError(t, err)\n\n\tdb.MustMeetMockExpectation()\n}\n"
  },
  {
    "path": "util/israce/no_race.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n//go:build !race\n\n// Package israce reports if the Go race detector is enabled.\npackage israce\n\n// Enabled reports if the race detector is enabled.\nconst Enabled = false\n"
  },
  {
    "path": "util/israce/race.go",
    "content": "// Copyright 2024 PingCAP, Inc. Licensed under Apache-2.0.\n\n//go:build race\n\n// Package israce reports if the Go race detector is enabled.\npackage israce\n\n// Enabled reports if the race detector is enabled.\nconst Enabled = true\n"
  },
  {
    "path": "util/jsonserde/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage jsonserde\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/jsonserde/convert.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Copyright (c) 2017 Eason Lin\n\npackage jsonserde\n\nimport (\n\t\"unicode\"\n)\n\nfunc swagToSnakeCase(in string) string {\n\t// Copied from https://github.com/swaggo/swag/blob/8ffc6c29c01a13fb01183ee91d0fcc5fc586b431/field_parser.go#L81\n\t// so that the serialized value can be aligned with the swagger spec.\n\n\trunes := []rune(in)\n\tlength := len(runes)\n\n\tout := make([]rune, 0, length)\n\tfor i := range length {\n\t\tif i > 0 && unicode.IsUpper(runes[i]) &&\n\t\t\t((i+1 < length && unicode.IsLower(runes[i+1])) || unicode.IsLower(runes[i-1])) {\n\t\t\tout = append(out, '_')\n\t\t}\n\t\tout = append(out, unicode.ToLower(runes[i]))\n\t}\n\n\treturn string(out)\n}\n"
  },
  {
    "path": "util/jsonserde/default.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package jsonserde sets default config for json-iterator.\n//\n// If this package is imported, json-iterator will:\n// - encode time as millisecond timestamps\n// - encode field names as lower_snake_case\npackage jsonserde\n\nimport (\n\t\"time\"\n\n\tjsoniter \"github.com/json-iterator/go\"\n\t\"github.com/json-iterator/go/extra\"\n)\n\nfunc init() {\n\textra.RegisterTimeAsInt64Codec(time.Millisecond)\n\textra.SetNamingStrategy(swagToSnakeCase)\n}\n\nvar Default = jsoniter.ConfigCompatibleWithStandardLibrary\n"
  },
  {
    "path": "util/jsonserde/default_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage jsonserde\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype testStruct struct {\n\tFooBar  string\n\tSQLTime time.Time\n}\n\nfunc TestMarshal(t *testing.T) {\n\tdata := testStruct{\n\t\tFooBar:  \"zoo\",\n\t\tSQLTime: time.Unix(0, 1641733771580123000),\n\t}\n\tval, err := Default.Marshal(data)\n\trequire.NoError(t, err)\n\trequire.Equal(t, `{\"foo_bar\":\"zoo\",\"sql_time\":1641733771580}`, string(val))\n}\n\nfunc TestUnmarshal(t *testing.T) {\n\tvar data testStruct\n\terr := Default.Unmarshal([]byte(`{\"foo_bar\":\"zoo\",\"sql_time\":1641733771580}`), &data)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"zoo\", data.FooBar)\n\trequire.Equal(t, time.Unix(0, 1641733771580000000), data.SQLTime)\n}\n"
  },
  {
    "path": "util/jsonserde/ginadapter/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage ginadapter\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/jsonserde/ginadapter/binding.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Copyright 2014 Manu Martinez-Almeida.\n\npackage ginadapter\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin/binding\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/jsonserde\"\n)\n\nvar Binding = jsonBinding{}\n\ntype jsonBinding struct{}\n\nfunc (jsonBinding) Name() string {\n\treturn \"json\"\n}\n\nfunc (jsonBinding) Bind(req *http.Request, obj interface{}) error {\n\tif req == nil || req.Body == nil {\n\t\treturn fmt.Errorf(\"invalid request\")\n\t}\n\treturn decodeJSON(req.Body, obj)\n}\n\nfunc (jsonBinding) BindBody(body []byte, obj interface{}) error {\n\treturn decodeJSON(bytes.NewReader(body), obj)\n}\n\nfunc decodeJSON(r io.Reader, obj interface{}) error {\n\tdecoder := jsonserde.Default.NewDecoder(r)\n\tif err := decoder.Decode(obj); err != nil {\n\t\treturn err\n\t}\n\treturn validate(obj)\n}\n\nfunc validate(obj interface{}) error {\n\tif binding.Validator == nil {\n\t\treturn nil\n\t}\n\treturn binding.Validator.ValidateStruct(obj)\n}\n"
  },
  {
    "path": "util/jsonserde/ginadapter/binding_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage ginadapter\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestJSONBindingBindBody(t *testing.T) {\n\ttype sampleStruct struct {\n\t\tABCFoo string\n\t\tBar    string\n\t\tBox    string `json:\"_box\"`\n\t}\n\tvar s sampleStruct\n\terr := jsonBinding{}.BindBody([]byte(`{\"Abc_foo\": \"FOO\", \"Box\": \"zzz\", \"Bar\": \"xyz\"}`), &s)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"FOO\", s.ABCFoo)\n\trequire.Equal(t, \"xyz\", s.Bar)\n\trequire.Equal(t, \"\", s.Box)\n\n\ts = sampleStruct{}\n\terr = jsonBinding{}.BindBody([]byte(`{\"ABCFoo\": \"z\", \"_Box\": \"yo\"}`), &s)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"\", s.ABCFoo)\n\trequire.Equal(t, \"\", s.Bar)\n\trequire.Equal(t, \"yo\", s.Box)\n\n\ts = sampleStruct{}\n\terr = jsonBinding{}.BindBody([]byte(`{\"abc_foo\": \"x\", \"_box\": \"yoo\", \"bar\": \"jojo\"}`), &s)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"x\", s.ABCFoo)\n\trequire.Equal(t, \"jojo\", s.Bar)\n\trequire.Equal(t, \"yoo\", s.Box)\n}\n\nfunc TestJSONBindingBindBodyMap(t *testing.T) {\n\ts := make(map[string]string)\n\terr := jsonBinding{}.BindBody([]byte(`{\"foo\": \"FOO\",\"Hello\":\"world\"}`), &s)\n\trequire.NoError(t, err)\n\trequire.Len(t, s, 2)\n\trequire.Equal(t, \"FOO\", s[\"foo\"])\n\trequire.Equal(t, \"world\", s[\"Hello\"])\n}\n"
  },
  {
    "path": "util/jsonserde/ginadapter/render.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Copyright 2014 Manu Martinez-Almeida.\n\npackage ginadapter\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/jsonserde\"\n)\n\nvar jsonContentType = []string{\"application/json; charset=utf-8\"}\n\nfunc writeContentType(w http.ResponseWriter, value []string) {\n\theader := w.Header()\n\tif val := header[\"Content-Type\"]; len(val) == 0 {\n\t\theader[\"Content-Type\"] = value\n\t}\n}\n\ntype Renderer struct {\n\tData interface{}\n}\n\nfunc (j Renderer) Render(w http.ResponseWriter) error {\n\twriteContentType(w, jsonContentType)\n\tjsonBytes, err := jsonserde.Default.Marshal(j.Data)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = w.Write(jsonBytes)\n\treturn err\n}\n\nfunc (j Renderer) WriteContentType(w http.ResponseWriter) {\n\twriteContentType(w, jsonContentType)\n}\n"
  },
  {
    "path": "util/jsonserde/ginadapter/render_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage ginadapter\n\nimport (\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestRenderer(t *testing.T) {\n\tw := httptest.NewRecorder()\n\ttype User struct {\n\t\tFullName string\n\t\tAge      int\n\t}\n\tdata := User{\n\t\tFullName: \"zoo\",\n\t\tAge:      18,\n\t}\n\n\terr := (Renderer{data}).Render(w)\n\n\trequire.NoError(t, err)\n\trequire.Equal(t, `{\"full_name\":\"zoo\",\"age\":18}`, w.Body.String())\n\trequire.Equal(t, `application/json; charset=utf-8`, w.Header().Get(\"Content-Type\"))\n}\n\nfunc TestRendererError(t *testing.T) {\n\tw := httptest.NewRecorder()\n\tdata := make(chan int)\n\n\terr := (Renderer{data}).Render(w)\n\trequire.EqualError(t, err, \"chan int is unsupported type\")\n}\n"
  },
  {
    "path": "util/lifecyclectx/fx.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage lifecyclectx\n\nimport (\n\t\"context\"\n\t\"sync/atomic\"\n\n\t\"go.uber.org/fx\"\n)\n\n// Provider is an easy way to access a lifecycle context on-demand.\ntype Provider interface {\n\tGetLifecycleCtx() context.Context\n}\n\n// FxCtx provide the lifecycle from Fx App Lifecycle.\ntype FxCtx struct {\n\tctx atomic.Value\n}\n\nfunc NewFxCtx(lc fx.Lifecycle) Provider {\n\tp := &FxCtx{\n\t\tctx: atomic.Value{},\n\t}\n\tlc.Append(fx.Hook{\n\t\tOnStart: func(ctx context.Context) error {\n\t\t\tif ctx != nil {\n\t\t\t\tp.ctx.Store(ctx)\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t})\n\treturn p\n}\n\nfunc (p FxCtx) GetLifecycleCtx() context.Context {\n\tctx := p.ctx.Load()\n\tif ctx == nil {\n\t\tpanic(\"lifecyclectx.FxCtx is not registered or not initialized yet\")\n\t}\n\treturn ctx.(context.Context)\n}\n"
  },
  {
    "path": "util/lifecyclectx/lifecyclectxtest/test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage lifecyclectxtest\n\nimport (\n\t\"context\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/lifecyclectx\"\n)\n\ntype TestCtx struct {\n\tctx context.Context\n}\n\nfunc BgCtx() lifecyclectx.Provider {\n\treturn TestCtx{\n\t\tctx: context.Background(),\n\t}\n}\n\nfunc (p TestCtx) GetLifecycleCtx() context.Context {\n\treturn p.ctx\n}\n"
  },
  {
    "path": "util/netutil/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage netutil\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/netutil/parse.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage netutil\n\nimport (\n\t\"net\"\n\t\"net/url\"\n\t\"strconv\"\n\n\t\"github.com/joomcode/errorx\"\n)\n\nvar (\n\tErrNS             = errorx.NewNamespace(\"net_util\")\n\tErrInvalidAddress = ErrNS.NewType(\"invalid_address\")\n)\n\n// ParseHostAndPortFromAddress parse an address in format `host:port` like `127.0.0.1:2379`.\n// Returns error if parse failed.\nfunc ParseHostAndPortFromAddress(address string) (string, uint, error) {\n\thost, port, err := net.SplitHostPort(address)\n\tif err != nil {\n\t\treturn \"\", 0, ErrInvalidAddress.New(\"Invalid address `%s`, expect format `host:port`\", address)\n\t}\n\tportNumeric, err := strconv.Atoi(port)\n\tif err != nil || portNumeric == 0 {\n\t\treturn \"\", 0, ErrInvalidAddress.New(\"Invalid address `%s`, expect port to be numeric\", address)\n\t}\n\treturn host, uint(portNumeric), nil\n}\n\n// ParseHostAndPortFromAddressURL parse an address in format `protocol://ip:port/...` like `http://127.0.0.1:2379`.\n// Returns error if parse failed.\nfunc ParseHostAndPortFromAddressURL(urlString string) (string, uint, error) {\n\tu, err := url.Parse(urlString)\n\tif err != nil {\n\t\treturn \"\", 0, ErrInvalidAddress.New(\"Invalid address `%s`, expect format `http(s)://host:port/...`\", urlString)\n\t}\n\thost, port, err := ParseHostAndPortFromAddress(u.Host)\n\tif err != nil {\n\t\treturn \"\", 0, err\n\t}\n\treturn host, port, nil\n}\n"
  },
  {
    "path": "util/netutil/parse_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage netutil\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestParseHostAndPortFromAddress(t *testing.T) {\n\thost, port, err := ParseHostAndPortFromAddress(\"abc.com:123\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"abc.com\", host)\n\trequire.Equal(t, uint(123), port)\n\n\thost, port, err = ParseHostAndPortFromAddress(\"192.168.31.1:1234\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"192.168.31.1\", host)\n\trequire.Equal(t, uint(1234), port)\n\n\thost, port, err = ParseHostAndPortFromAddress(\"[::ffff:1.2.3.4]:10023\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"::ffff:1.2.3.4\", host)\n\trequire.Equal(t, uint(10023), port)\n\n\thost, port, err = ParseHostAndPortFromAddress(\"[2001:0db8::1428:57ab]:80\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"2001:0db8::1428:57ab\", host)\n\trequire.Equal(t, uint(80), port)\n\n\t_, _, err = ParseHostAndPortFromAddress(\"http://abc.com:123\")\n\trequire.Error(t, err)\n\n\t_, _, err = ParseHostAndPortFromAddress(\"abc.com\")\n\trequire.Error(t, err)\n\n\t_, _, err = ParseHostAndPortFromAddress(\"localhost\")\n\trequire.Error(t, err)\n\n\t_, _, err = ParseHostAndPortFromAddress(\"abc.com:def\")\n\trequire.Error(t, err)\n\n\t_, _, err = ParseHostAndPortFromAddress(\"::ffff:1.2.3.4:10023\")\n\trequire.Error(t, err)\n\n\t_, _, err = ParseHostAndPortFromAddress(\"2001:0db8::1428:57ab:80\")\n\trequire.Error(t, err)\n}\n\nfunc TestParseHostAndPortFromAddressURL(t *testing.T) {\n\thost, port, err := ParseHostAndPortFromAddressURL(\"http://abc.com:123\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"abc.com\", host)\n\trequire.Equal(t, uint(123), port)\n\n\thost, port, err = ParseHostAndPortFromAddressURL(\"https://192.168.31.1:1234\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"192.168.31.1\", host)\n\trequire.Equal(t, uint(1234), port)\n\n\thost, port, err = ParseHostAndPortFromAddressURL(\"abc://[::ffff:1.2.3.4]:10023\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"::ffff:1.2.3.4\", host)\n\trequire.Equal(t, uint(10023), port)\n\n\thost, port, err = ParseHostAndPortFromAddressURL(\"foo://[2001:0db8::1428:57ab]:80\")\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"2001:0db8::1428:57ab\", host)\n\trequire.Equal(t, uint(80), port)\n\n\t_, _, err = ParseHostAndPortFromAddressURL(\"http://abc.com\")\n\trequire.Error(t, err)\n\n\t_, _, err = ParseHostAndPortFromAddressURL(\"abc.com:345\")\n\trequire.Error(t, err)\n\n\t_, _, err = ParseHostAndPortFromAddressURL(\"http://localhost\")\n\trequire.Error(t, err)\n\n\t_, _, err = ParseHostAndPortFromAddressURL(\"http://abc.com:def\")\n\trequire.Error(t, err)\n\n\t_, _, err = ParseHostAndPortFromAddressURL(\"http://::ffff:1.2.3.4:10023\")\n\trequire.Error(t, err)\n\n\t_, _, err = ParseHostAndPortFromAddressURL(\"http://2001:0db8::1428:57ab:80\")\n\trequire.Error(t, err)\n}\n"
  },
  {
    "path": "util/nocopy/nocopy.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage nocopy\n\n// NoCopy may be embedded into structs which must not be copied\n// after the first use.\n//\n// See https://github.com/golang/go/issues/8005\ntype NoCopy struct{}\n\n// Lock is a no-op used by -copylocks checker from `go vet`.\nfunc (*NoCopy) Lock()   {}\nfunc (*NoCopy) UnLock() {}\n"
  },
  {
    "path": "util/proxy/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage proxy\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/proxy/proxy.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\n// Package proxy provides a TCP reverse proxy. Unlike normal reverse proxy, the upstream is intentionally fixed.\n// A new upstream will be selected if the current upstream is down.\npackage proxy\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/atomic\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/nocopy\"\n)\n\ntype Proxy struct {\n\tnocopy.NoCopy\n\n\tconfig Config\n\n\tlistener  net.Listener\n\tctx       context.Context\n\tctxCancel context.CancelFunc\n\n\tupstreams           sync.Map // The key is the address in string type. The value is in type *upstream.\n\tnoActiveUpstream    *atomic.Bool\n\tcurrentUpstreamAddr *atomic.String\n}\n\ntype Config struct {\n\tKindTag               string\n\tUpstreamProbeInterval time.Duration\n\tDialTimeout           time.Duration\n}\n\nconst (\n\tDefaultUpstreamProbeInterval = 2 * time.Second\n\tDefaultDialTimeout           = 3 * time.Second\n)\n\nfunc New(config Config) (*Proxy, error) {\n\tl, err := net.Listen(\"tcp\", \"127.0.0.1:0\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tctx, cancel := context.WithCancel(context.Background())\n\n\tif config.UpstreamProbeInterval <= 0 {\n\t\tconfig.UpstreamProbeInterval = DefaultUpstreamProbeInterval\n\t}\n\tif config.DialTimeout <= 0 {\n\t\tconfig.DialTimeout = DefaultDialTimeout\n\t}\n\tp := &Proxy{\n\t\tconfig:              config,\n\t\tlistener:            l,\n\t\tctx:                 ctx,\n\t\tctxCancel:           cancel,\n\t\tupstreams:           sync.Map{},\n\t\tnoActiveUpstream:    atomic.NewBool(true),\n\t\tcurrentUpstreamAddr: atomic.NewString(\"\"),\n\t}\n\n\tgo p.runListenerLoop()\n\tgo p.runProbeLoop()\n\n\treturn p, nil\n}\n\n// HasActiveUpstream returns whether there is an active upstream.\nfunc (p *Proxy) HasActiveUpstream() bool {\n\treturn !p.noActiveUpstream.Load()\n}\n\n// Close stops any running loops and stops the listener.\n// This function is concurrent-safe.\nfunc (p *Proxy) Close() {\n\tp.ctxCancel()\n\t_ = p.listener.Close()\n}\n\n// Port returns the actual listening port.\nfunc (p *Proxy) Port() int {\n\treturn p.listener.Addr().(*net.TCPAddr).Port\n}\n\n// SetUpstreams sets the upstream address list.\n// This function is concurrent-safe.\nfunc (p *Proxy) SetUpstreams(addresses []string) {\n\tif len(addresses) == 0 {\n\t\tlog.Debug(\"All endpoints are removed from the upstream list\",\n\t\t\tzap.String(\"kindTag\", p.config.KindTag))\n\t\tp.upstreams.Range(func(key, _ interface{}) bool {\n\t\t\tp.upstreams.Delete(key)\n\t\t\treturn true\n\t\t})\n\t\tp.currentUpstreamAddr.Store(\"\")\n\t\tp.noActiveUpstream.Store(true)\n\t\treturn\n\t}\n\n\t// Add new upstreams\n\tfor _, addr := range addresses {\n\t\tif _, ok := p.upstreams.Load(addr); !ok {\n\t\t\tlog.Debug(\"An endpoint is added to the upstream list\",\n\t\t\t\tzap.String(\"kindTag\", p.config.KindTag),\n\t\t\t\tzap.String(\"addr\", addr))\n\t\t\tp.upstreams.Store(addr, newUpstream(p.config.KindTag, addr))\n\t\t}\n\t}\n\n\t// Remove old upstreams\n\taddrSet := make(map[string]struct{})\n\tfor _, addr := range addresses {\n\t\taddrSet[addr] = struct{}{}\n\t}\n\tp.upstreams.Range(func(key, _ interface{}) bool {\n\t\taddr := key.(string)\n\t\tif _, ok := addrSet[addr]; !ok {\n\t\t\tlog.Debug(\"An endpoint is removed from the upstream list\",\n\t\t\t\tzap.String(\"kindTag\", p.config.KindTag),\n\t\t\t\tzap.String(\"addr\", addr))\n\t\t\tp.upstreams.Delete(key)\n\t\t}\n\t\treturn true\n\t})\n}\n\nfunc (p *Proxy) serveConnection(in net.Conn) {\n\tout := p.pickActiveUpstreamAndConnect()\n\tif out == nil {\n\t\t_ = in.Close()\n\t\treturn\n\t}\n\t// bidirectional copy\n\tgo func() {\n\t\t_, _ = io.Copy(in, out)\n\t\t_ = in.Close()\n\t\t_ = out.Close()\n\t}()\n\t_, _ = io.Copy(out, in)\n\t_ = out.Close()\n\t_ = in.Close()\n}\n\nfunc (p *Proxy) pickActiveUpstreamAndConnect() net.Conn {\n\tfor {\n\t\tpicked := p.pickOneLastActiveUpstream()\n\t\tif picked == nil {\n\t\t\t// There is no active upstream for now.\n\t\t\t// We stop and wait the prober to discover an active upstreams later.\n\t\t\t{\n\t\t\t\tcurrentAddr := p.currentUpstreamAddr.Load()\n\t\t\t\tif currentAddr != \"\" {\n\t\t\t\t\tlog.Warn(\"No upstream is active\",\n\t\t\t\t\t\tzap.String(\"kindTag\", p.config.KindTag),\n\t\t\t\t\t\tzap.String(\"lastUpstreamAddr\", currentAddr))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tp.currentUpstreamAddr.Store(\"\")\n\t\t\tp.noActiveUpstream.Store(true)\n\t\t\treturn nil\n\t\t}\n\n\t\tconn, err := picked.Connect(p.config.DialTimeout)\n\t\tif err == nil {\n\t\t\t// Connect is successful, memorize this upstream for future connection.\n\t\t\t{\n\t\t\t\tcurrentAddr := p.currentUpstreamAddr.Load()\n\t\t\t\tif currentAddr != picked.addr {\n\t\t\t\t\tlog.Info(\"Using new upstream\",\n\t\t\t\t\t\tzap.String(\"kindTag\", p.config.KindTag),\n\t\t\t\t\t\tzap.String(\"lastUpstreamAddr\", currentAddr),\n\t\t\t\t\t\tzap.String(\"newUpstreamAddr\", picked.addr))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tp.currentUpstreamAddr.Store(picked.addr)\n\t\t\tp.noActiveUpstream.Store(false)\n\t\t\treturn conn\n\t\t}\n\n\t\t// We know that this upstream doesn't look good. Update its status.\n\t\tp.currentUpstreamAddr.Store(\"\")\n\t}\n}\n\n// pickOneLastActiveUpstream returns an upstream which is last known as active.\n// If there is no active upstream, nil will be returned.\nfunc (p *Proxy) pickOneLastActiveUpstream() *upstream {\n\tcurrentUpstream := p.currentUpstreamAddr.Load()\n\tif currentUpstream != \"\" {\n\t\t// It is possible that the upstream list has been changed.\n\t\tr, ok := p.upstreams.Load(currentUpstream)\n\t\tif ok {\n\t\t\tpicked := r.(*upstream)\n\t\t\tif picked.IsActive() {\n\t\t\t\treturn picked\n\t\t\t}\n\t\t}\n\t}\n\n\tvar picked *upstream\n\tp.upstreams.Range(func(_, value interface{}) bool {\n\t\tr := value.(*upstream)\n\t\tif r.IsActive() {\n\t\t\tpicked = r\n\t\t\treturn false\n\t\t}\n\t\treturn true\n\t})\n\treturn picked\n}\n\n// probeActiveUpstreams iterates all inactive upstreams and try to update its status to active.\nfunc (p *Proxy) probeActiveUpstreams() {\n\tactiveUpstreams := 0\n\n\tp.upstreams.Range(func(_, value interface{}) bool {\n\t\tr := value.(*upstream)\n\t\tif r.IsActive() {\n\t\t\tactiveUpstreams++\n\t\t} else {\n\t\t\tr.TryProbeAsync(p.config.DialTimeout)\n\t\t}\n\t\treturn true\n\t})\n\n\tif activeUpstreams > 0 {\n\t\t// As long as there is any upstream recognized as active, there is active upstream.\n\t\tp.noActiveUpstream.Store(false)\n\t}\n}\n\nfunc (p *Proxy) runProbeLoop() {\n\tfor {\n\t\tselect {\n\t\tcase <-p.ctx.Done():\n\t\t\treturn\n\t\tcase <-time.After(p.config.UpstreamProbeInterval):\n\t\t\tp.probeActiveUpstreams()\n\t\t}\n\t}\n}\n\nfunc (p *Proxy) runListenerLoop() {\n\tfor {\n\t\tselect {\n\t\tcase <-p.ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t\tconn, err := p.listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\t// Ignore listener close error\n\t\t\t\t// TODO: Use `if !errors.Is(err, net.ErrClosed)` for higher Golang compilers.\n\t\t\t\tlog.Warn(\"Accept incoming connection failed\",\n\t\t\t\t\tzap.String(\"remoteAddr\", p.listener.Addr().String()),\n\t\t\t\t\tzap.Error(err))\n\t\t\t} else {\n\t\t\t\tgo p.serveConnection(conn)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "util/proxy/proxy_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage proxy\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/go-resty/resty/v2\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil\"\n)\n\nvar configForTest = Config{\n\tUpstreamProbeInterval: time.Millisecond * 200,\n}\n\nconst probeWait = time.Millisecond * 500 // UpstreamProbeInterval*2.5\n\nfunc sendGetToProxy(proxy *Proxy) (*resty.Response, error) {\n\turl := fmt.Sprintf(\"http://127.0.0.1:%d\", proxy.Port())\n\treturn resty.New().SetTimeout(time.Millisecond * 500).R().Get(url)\n}\n\nfunc TestNoUpstream(t *testing.T) {\n\tp, err := New(configForTest)\n\trequire.NoError(t, err)\n\tdefer p.Close()\n\n\trequire.False(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n}\n\nfunc TestAddUpstream(t *testing.T) {\n\tp, err := New(configForTest)\n\trequire.NoError(t, err)\n\tdefer p.Close()\n\n\tserver := testutil.NewHTTPServer(\"foo\")\n\tdefer server.Close()\n\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server)})\n\trequire.False(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\t// Incoming connection will not be established until a probe interval.\n\trequire.Error(t, err)\n\trequire.False(t, p.HasActiveUpstream())\n\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foo\", resp.String())\n}\n\nfunc TestAddMultipleUpstream(t *testing.T) {\n\tp, err := New(configForTest)\n\trequire.NoError(t, err)\n\tdefer p.Close()\n\n\tservers := testutil.NewMultiServer(5, \"foo#%d\")\n\tdefer servers.CloseAll()\n\n\trequire.False(t, p.HasActiveUpstream())\n\tp.SetUpstreams(servers.GetEndpoints())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\trequire.False(t, p.HasActiveUpstream())\n\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Contains(t, resp.String(), \"foo#\")\n\trequire.Equal(t, servers.LastResp(), resp.String())\n}\n\nfunc TestRemoveAllUpstreams(t *testing.T) {\n\tp, err := New(configForTest)\n\trequire.NoError(t, err)\n\tdefer p.Close()\n\n\tserver := testutil.NewHTTPServer(\"foo\")\n\tdefer server.Close()\n\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server)})\n\trequire.False(t, p.HasActiveUpstream())\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foo\", resp.String())\n\n\tp.SetUpstreams([]string{})\n\trequire.False(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n}\n\nfunc TestRemoveOneUpstream(t *testing.T) {\n\tp, err := New(configForTest)\n\trequire.NoError(t, err)\n\tdefer p.Close()\n\n\tserver1 := testutil.NewHTTPServer(\"foo\")\n\tdefer server1.Close()\n\n\tserver2 := testutil.NewHTTPServer(\"bar\")\n\tdefer server2.Close()\n\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server1)})\n\trequire.False(t, p.HasActiveUpstream())\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foo\", resp.String())\n\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server1), testutil.GetHTTPServerHost(server2)})\n\trequire.True(t, p.HasActiveUpstream())\n\ttime.Sleep(probeWait)\n\tresp, err = sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foo\", resp.String())\n\trequire.True(t, p.HasActiveUpstream())\n\n\t// The active upstream is removed, another upstream should be used.\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server2)})\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err = sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"bar\", resp.String())\n\n\t// Add upstream back\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server1), testutil.GetHTTPServerHost(server2)})\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err = sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"bar\", resp.String())\n\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err = sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"bar\", resp.String())\n}\n\nfunc TestPickLastActiveUpstream(t *testing.T) {\n\tp, err := New(configForTest)\n\trequire.NoError(t, err)\n\tdefer p.Close()\n\n\tserver1 := testutil.NewHTTPServer(\"foo\")\n\tdefer server1.Close()\n\n\tserver2 := testutil.NewHTTPServer(\"bar\")\n\tdefer server2.Close()\n\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server1)})\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foo\", resp.String())\n\n\t// Even if SetUpstreams is called, the active upstream should be unchanged.\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server1), testutil.GetHTTPServerHost(server2)})\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err = sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foo\", resp.String())\n\n\ttime.Sleep(probeWait)\n\tfor range 5 {\n\t\t// Let's try multiple times! We should always get \"foo\".\n\t\trequire.True(t, p.HasActiveUpstream())\n\t\tresp, err = sendGetToProxy(p)\n\t\trequire.NoError(t, err)\n\t\trequire.Equal(t, \"foo\", resp.String())\n\t}\n}\n\nfunc TestAllUpstreamDown(t *testing.T) {\n\tp, err := New(configForTest)\n\trequire.NoError(t, err)\n\tdefer p.Close()\n\n\tservers := testutil.NewMultiServer(3, \"foo#%d\")\n\tdefer servers.CloseAll()\n\n\tp.SetUpstreams(servers.GetEndpoints())\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Contains(t, resp.String(), \"foo#\")\n\trequire.Equal(t, servers.LastResp(), resp.String())\n\n\tservers.CloseAll()\n\trequire.True(t, p.HasActiveUpstream())\n\n\ttime.Sleep(probeWait)\n\t// Since we only set inactive when new connection is established (lazily), HasActiveUpstream is still true here.\n\trequire.True(t, p.HasActiveUpstream())\n\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\trequire.False(t, p.HasActiveUpstream())\n}\n\nfunc TestActiveUpstreamDown(t *testing.T) {\n\tp, err := New(configForTest)\n\trequire.NoError(t, err)\n\tdefer p.Close()\n\n\tservers := testutil.NewMultiServer(5, \"foo#%d\")\n\tdefer servers.CloseAll()\n\n\tp.SetUpstreams(servers.GetEndpoints())\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Contains(t, resp.String(), \"foo#\")\n\trequire.Equal(t, servers.LastResp(), resp.String())\n\trequire.Equal(t, fmt.Sprintf(\"foo#%d\", servers.LastID()), resp.String())\n\n\t// Close the last accessed server\n\tservers.Servers[servers.LastID()].Close()\n\n\t// The connection is still succeeded, but forwarded to another upstream.\n\tresp2, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Contains(t, resp2.String(), \"foo#\")\n\trequire.Equal(t, servers.LastResp(), resp2.String())\n\trequire.NotEqual(t, resp.String(), resp2.String()) // Check upstream has changed\n\n\ttime.Sleep(probeWait)\n\tresp3, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, resp3.String(), resp2.String()) // Unchanged\n}\n\nfunc TestNonActiveUpstreamDown(t *testing.T) {\n\tp, err := New(configForTest)\n\trequire.NoError(t, err)\n\tdefer p.Close()\n\n\tservers := testutil.NewMultiServer(5, \"foo#%d\")\n\tdefer servers.CloseAll()\n\n\tp.SetUpstreams(servers.GetEndpoints())\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, fmt.Sprintf(\"foo#%d\", servers.LastID()), resp.String())\n\n\t// Close other non active servers\n\tfor i := range 5 {\n\t\tif i != servers.LastID() {\n\t\t\tservers.Servers[i].Close()\n\t\t}\n\t}\n\n\tresp2, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, resp.String(), resp2.String()) // Unchanged\n\n\ttime.Sleep(probeWait)\n\tresp3, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, resp.String(), resp3.String()) // Unchanged\n}\n\nfunc TestBrokenServer(t *testing.T) {\n\tp, err := New(configForTest)\n\trequire.NoError(t, err)\n\tdefer p.Close()\n\n\tserver := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\t_, _ = fmt.Fprintln(w, \"foo\")\n\t}))\n\tdefer server.Close()\n\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server)})\n\trequire.False(t, p.HasActiveUpstream())\n\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\trequire.True(t, os.IsTimeout(err))\n\trequire.True(t, p.HasActiveUpstream())\n\n\t// In this case, proxy will not switch the upstream by design. Let's check it is still\n\t// connecting the original \"broken\" upstream.\n\tserver2 := testutil.NewHTTPServer(\"foo\")\n\tdefer server2.Close()\n\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server), testutil.GetHTTPServerHost(server2)})\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\trequire.True(t, os.IsTimeout(err))\n\n\t// Let's remove the first upstream! We should get success response immediately without waiting probe.\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server2)})\n\tresp, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foo\", resp.String())\n}\n\nfunc TestUpstreamBack(t *testing.T) {\n\tp, err := New(configForTest)\n\trequire.NoError(t, err)\n\tdefer p.Close()\n\n\tserver := testutil.NewHTTPServer(\"foo\")\n\tdefer server.Close()\n\thost := testutil.GetHTTPServerHost(server)\n\n\tp.SetUpstreams([]string{host})\n\trequire.False(t, p.HasActiveUpstream())\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foo\", resp.String())\n\n\t// Close the upstream server\n\tserver.Close()\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\trequire.False(t, p.HasActiveUpstream())\n\n\t// Start the upstream server again at the original listen address\n\tserver2 := testutil.NewHTTPServerAtHost(\"bar\", host)\n\tdefer server2.Close()\n\t// We will still get failure here, even if the upstream is back. It will recover at next probe round.\n\trequire.False(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\trequire.False(t, p.HasActiveUpstream())\n\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err = sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"bar\", resp.String())\n}\n\nfunc TestUpstreamSwitchComplex(t *testing.T) {\n\tp, err := New(configForTest)\n\trequire.NoError(t, err)\n\tdefer p.Close()\n\n\tserver := testutil.NewHTTPServer(\"foo\")\n\tdefer server.Close()\n\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server)})\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foo\", resp.String())\n\n\tserver2 := testutil.NewHTTPServer(\"bar\")\n\tdefer server2.Close()\n\n\t// Let's close the current upstream\n\tserver.Close()\n\trequire.True(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\trequire.False(t, p.HasActiveUpstream())\n\n\t// Wait one round probe, nothing is changed\n\ttime.Sleep(probeWait)\n\trequire.False(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\n\t// Add a new alive upstream\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server), testutil.GetHTTPServerHost(server2)})\n\trequire.False(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err = sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"bar\", resp.String())\n\n\t// Bring down the new upstream again!\n\tserver2.Close()\n\n\trequire.True(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\n\tserver3 := testutil.NewHTTPServer(\"box\")\n\tdefer server3.Close()\n\thost3 := testutil.GetHTTPServerHost(server3)\n\n\t// Add a new alive upstream\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server), testutil.GetHTTPServerHost(server2), host3})\n\trequire.False(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err = sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"box\", resp.String())\n\n\tserver3.Close()\n\n\tserver4 := testutil.NewHTTPServer(\"car\")\n\thost4 := testutil.GetHTTPServerHost(server4)\n\tserver4.Close()\n\n\t// Add a bad upstream\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server), testutil.GetHTTPServerHost(server2), host3, host4})\n\trequire.True(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\trequire.False(t, p.HasActiveUpstream())\n\n\ttime.Sleep(probeWait)\n\trequire.False(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\n\t// Bring back server3\n\tserver3New := testutil.NewHTTPServerAtHost(\"newBox\", host3)\n\tdefer server3New.Close()\n\n\trequire.False(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err = sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"newBox\", resp.String())\n\n\t// Remove server3\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server), testutil.GetHTTPServerHost(server2), host4})\n\trequire.True(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\trequire.False(t, p.HasActiveUpstream())\n\ttime.Sleep(probeWait)\n\trequire.False(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\n\t// Remove server4\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server), testutil.GetHTTPServerHost(server2)})\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\trequire.False(t, p.HasActiveUpstream())\n\n\t// Start server4 again, nothing should be changed (keep failure).\n\tserver4New := testutil.NewHTTPServerAtHost(\"newCar\", host4)\n\tdefer server4New.Close()\n\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\trequire.False(t, p.HasActiveUpstream())\n\ttime.Sleep(probeWait)\n\trequire.False(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\n\t// Add server3 back to the upstream\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server2), host3})\n\trequire.False(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err = sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"newBox\", resp.String())\n\trequire.True(t, p.HasActiveUpstream())\n\n\t// Change upstream to host4\n\tp.SetUpstreams([]string{host4})\n\trequire.True(t, p.HasActiveUpstream())\n\t_, err = sendGetToProxy(p) // At this time, active upstream is host3, and host4 is not recognized as alive, so it should fail\n\trequire.Error(t, err)\n\trequire.False(t, p.HasActiveUpstream())\n\n\ttime.Sleep(probeWait)\n\tresp, err = sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"newCar\", resp.String())\n\trequire.True(t, p.HasActiveUpstream())\n}\n\nfunc TestClose(t *testing.T) {\n\tp, err := New(configForTest)\n\trequire.NoError(t, err)\n\tdefer p.Close()\n\n\tserver := testutil.NewHTTPServer(\"foo\")\n\tdefer server.Close()\n\n\tp.SetUpstreams([]string{testutil.GetHTTPServerHost(server)})\n\ttime.Sleep(probeWait)\n\trequire.True(t, p.HasActiveUpstream())\n\tresp, err := sendGetToProxy(p)\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"foo\", resp.String())\n\n\tp.Close()\n\trequire.True(t, p.HasActiveUpstream()) // TODO: Should we fix this behaviour?\n\t_, err = sendGetToProxy(p)\n\trequire.Error(t, err)\n\n\tp.Close() // Close again should be fine!\n}\n"
  },
  {
    "path": "util/proxy/upstream.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage proxy\n\nimport (\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/atomic\"\n\t\"go.uber.org/zap\"\n)\n\ntype upstream struct {\n\tkindTag string\n\taddr    string\n\tactive  *atomic.Bool\n}\n\nfunc newUpstream(kindTag, addr string) *upstream {\n\treturn &upstream{\n\t\tkindTag: kindTag,\n\t\taddr:    addr,\n\t\tactive:  atomic.NewBool(false),\n\t}\n}\n\n// IsActive is concurrent-safe.\nfunc (r *upstream) IsActive() bool {\n\treturn r.active.Load()\n}\n\n// setInactive is concurrent-safe.\nfunc (r *upstream) setInactive() {\n\tlastIsActive := r.active.Swap(false)\n\tif lastIsActive {\n\t\tlog.Info(\"An upstream becomes inactive\",\n\t\t\tzap.String(\"kindTag\", r.kindTag),\n\t\t\tzap.String(\"addr\", r.addr))\n\t}\n}\n\n// setActive is concurrent-safe.\nfunc (r *upstream) setActive() {\n\tlastIsActive := r.active.Swap(true)\n\tif !lastIsActive {\n\t\tlog.Debug(\"An upstream becomes active\",\n\t\t\tzap.String(\"kindTag\", r.kindTag),\n\t\t\tzap.String(\"addr\", r.addr))\n\t}\n}\n\n// Connect connects to the upstream no matter it is active or not, and update the active status according to connect status.\n// When connect failed, the error will be returned and the status will be updated to inactive.\n// When connect succeeded, the connection will be returned and the status will be updated to active.\n// This function is concurrent-safe.\nfunc (r *upstream) Connect(dialTimeout time.Duration) (net.Conn, error) {\n\tlog.Debug(\"Trying to connect to upstream\",\n\t\tzap.String(\"kindTag\", r.kindTag),\n\t\tzap.Bool(\"lastIsActive\", r.active.Load()),\n\t\tzap.String(\"addr\", r.addr))\n\n\tconn, err := net.DialTimeout(\"tcp\", r.addr, dialTimeout)\n\tif err != nil {\n\t\tr.setInactive()\n\t\treturn nil, err\n\t}\n\tr.setActive()\n\treturn conn, nil\n}\n\n// TryProbeAsync probes the upstream if it is not last known as active.\nfunc (r *upstream) TryProbeAsync(dialTimeout time.Duration) {\n\tif r.IsActive() {\n\t\treturn\n\t}\n\tgo func() {\n\t\tconn, err := r.Connect(dialTimeout)\n\t\tif err != nil {\n\t\t\t// TODO: Reduce number of logs\n\t\t\tlog.Debug(\"The upstream is still inactive, will be probed again later.\",\n\t\t\t\tzap.String(\"kindTag\", r.kindTag),\n\t\t\t\tzap.String(\"addr\", r.addr),\n\t\t\t\tzap.Error(err))\n\t\t\treturn\n\t\t}\n\n\t\t// Connect is succeeded, close the connection.\n\t\t_ = conn.Close()\n\t}()\n}\n"
  },
  {
    "path": "util/reflectutil/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage reflectutil\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/reflectutil/field.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage reflectutil\n\nimport \"reflect\"\n\n// See https://cs.opensource.google/go/go/+/refs/tags/go1.17.1:src/reflect/type.go;l=619\nfunc IsFieldExported(field reflect.StructField) bool {\n\treturn field.PkgPath == \"\"\n}\n"
  },
  {
    "path": "util/reflectutil/field_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage reflectutil\n\nimport (\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestIsFieldExported(t *testing.T) {\n\ttype f struct {\n\t\ta   string //nolint:unused\n\t\tB   string\n\t\tab  string //nolint:unused\n\t\taBC string //nolint:unused\n\t}\n\trt := reflect.TypeFor[f]()\n\trequire.Equal(t, 4, rt.NumField())\n\trequire.False(t, IsFieldExported(rt.Field(0)))\n\trequire.Equal(t, \"a\", rt.Field(0).Name)\n\trequire.True(t, IsFieldExported(rt.Field(1)))\n\trequire.Equal(t, \"B\", rt.Field(1).Name)\n\trequire.False(t, IsFieldExported(rt.Field(2)))\n\trequire.Equal(t, \"ab\", rt.Field(2).Name)\n\trequire.False(t, IsFieldExported(rt.Field(3)))\n\trequire.Equal(t, \"aBC\", rt.Field(3).Name)\n\n\ttype F2 struct {\n\t\tf\n\t}\n\trt = reflect.TypeFor[F2]()\n\trequire.Equal(t, 1, rt.NumField())\n\trequire.False(t, IsFieldExported(rt.Field(0)))\n\trequire.Equal(t, \"f\", rt.Field(0).Name)\n\n\ttype F3 struct {\n\t\tF2\n\t}\n\trt = reflect.TypeFor[F3]()\n\trequire.Equal(t, 1, rt.NumField())\n\trequire.True(t, IsFieldExported(rt.Field(0)))\n\trequire.Equal(t, \"F2\", rt.Field(0).Name)\n}\n"
  },
  {
    "path": "util/reflectutil/tag.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage reflectutil\n\nimport (\n\t\"reflect\"\n)\n\n// GetFieldsAndTags return fields' tags assign by `tags` parameter.\nfunc GetFieldsAndTags(obj interface{}, tags []string) []Field {\n\tt := reflect.TypeOf(obj)\n\tfNum := t.NumField()\n\tfieldTags := make([]Field, 0, fNum)\n\tfor i := range fNum {\n\t\tf := Field{Tags: map[string]string{}}\n\t\tstructField := t.Field(i)\n\n\t\tf.Name = structField.Name\n\t\tfor _, tagName := range tags {\n\t\t\tf.Tags[tagName] = structField.Tag.Get(tagName)\n\t\t}\n\n\t\tfieldTags = append(fieldTags, f)\n\t}\n\n\treturn fieldTags\n}\n\ntype Field struct {\n\tName string\n\tTags map[string]string\n}\n"
  },
  {
    "path": "util/reflectutil/tag_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage reflectutil\n\nimport (\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\ntype MyStruct struct {\n\tFirstField  string `matched:\"first tag\" value:\"whatever\"`\n\tSecondField string `matched:\"second tag\" value:\"another whatever\"`\n}\n\nfunc TestGetFieldTags(t *testing.T) {\n\trst := GetFieldsAndTags(MyStruct{}, []string{\"matched\", \"value\"})\n\n\trequire.Equal(t, rst, []Field{\n\t\t{\n\t\t\tName: \"FirstField\",\n\t\t\tTags: map[string]string{\n\t\t\t\t\"matched\": \"first tag\",\n\t\t\t\t\"value\":   \"whatever\",\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tName: \"SecondField\",\n\t\t\tTags: map[string]string{\n\t\t\t\t\"matched\": \"second tag\",\n\t\t\t\t\"value\":   \"another whatever\",\n\t\t\t},\n\t\t},\n\t})\n}\n\n// // TODO: support nested struct\n// func TestGetFieldTags_with_nested_struct(t *testing.T) {}\n"
  },
  {
    "path": "util/rest/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage rest\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/rest/context_helpers.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage rest\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/jsonserde/ginadapter\"\n)\n\n// Error appends an error to the context, which will later becomes an error message returned to the client.\n// You should not write any other body to the client before or after calling this function.\n// Otherwise there will be no error message written to the client.\n// See `ErrorHandlerFn` for more details.\nfunc Error(c *gin.Context, err error) {\n\t// For security reasons, we need to hide detailed stacktrace info.\n\t_ = c.Error(err) // before: c.Error(errorx.EnsureStackTrace(err))\n}\n\n// JSON writes a JSON string to the client with the given status code.\n// The key of te `obj` will be serialized in snake_case by default (see `jsonserde` package).\nfunc JSON(c *gin.Context, code int, obj interface{}) {\n\tc.Render(code, ginadapter.Renderer{Data: obj})\n}\n\n// OK writes a JSON string to the client with the status code 200.\n// The key of te `obj` will be serialized in snake_case by default (see `jsonserde` package).\nfunc OK(c *gin.Context, obj interface{}) {\n\tJSON(c, http.StatusOK, obj)\n}\n\n// MustBind decodes the request body to the passed struct pointer.\n// If error occurs, `ErrBadRequest` will be recorded in the context and `false` will be returned. You should early\n// return the handler in this case.\nfunc MustBind(c *gin.Context, obj interface{}) bool {\n\tif err := c.ShouldBindWith(obj, ginadapter.Binding); err != nil {\n\t\tError(c, ErrBadRequest.WrapWithNoMessage(err))\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "util/rest/context_helpers_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage rest\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/gintest\"\n)\n\nfunc TestError(t *testing.T) {\n\tc, r := gintest.CtxGet(nil)\n\tError(c, fmt.Errorf(\"my error\"))\n\trequire.Len(t, c.Errors, 1)\n\trequire.EqualError(t, c.Errors[0].Err, \"my error\")\n\trequire.Empty(t, r.Body.String())\n}\n\nfunc TestJSON(t *testing.T) {\n\tc, r := gintest.CtxGet(nil)\n\tJSON(c, http.StatusBadRequest, \"foo\")\n\trequire.Empty(t, c.Errors)\n\trequire.Equal(t, http.StatusBadRequest, r.Code)\n\trequire.Equal(t, `\"foo\"`, r.Body.String())\n\n\ttype example struct {\n\t\tFooBar string\n\t}\n\tc, r = gintest.CtxGet(nil)\n\tJSON(c, http.StatusOK, example{FooBar: \"value\"})\n\trequire.Empty(t, c.Errors)\n\trequire.Equal(t, http.StatusOK, r.Code)\n\trequire.Equal(t, `{\"foo_bar\":\"value\"}`, r.Body.String())\n}\n\nfunc TestMustBind(t *testing.T) {\n\tc, r := gintest.CtxPost(nil, `\"abc\"`)\n\n\tvar v string\n\tbindResult := MustBind(c, &v)\n\trequire.True(t, bindResult)\n\trequire.Equal(t, \"abc\", v)\n\trequire.Empty(t, c.Errors)\n\trequire.Empty(t, r.Body.String())\n\n\tc, r = gintest.CtxPost(nil, `123`)\n\tbindResult = MustBind(c, &v)\n\trequire.False(t, bindResult)\n\trequire.Len(t, c.Errors, 1)\n\trequire.Error(t, c.Errors[0].Err)\n\trequire.True(t, errorx.IsOfType(c.Errors[0].Err, ErrBadRequest))\n\trequire.Empty(t, r.Body.String())\n}\n\nfunc TestOK(t *testing.T) {\n\tc, r := gintest.CtxGet(nil)\n\tOK(c, \"xyz\")\n\trequire.Empty(t, c.Errors)\n\trequire.Equal(t, http.StatusOK, r.Code)\n\trequire.Equal(t, `\"xyz\"`, r.Body.String())\n}\n"
  },
  {
    "path": "util/rest/empty_resp.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage rest\n\ntype EmptyResponse struct{}\n"
  },
  {
    "path": "util/rest/error.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage rest\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n)\n\nvar (\n\tErrUnauthenticated = errorx.CommonErrors.NewType(\"unauthenticated\")\n\tErrForbidden       = errorx.CommonErrors.NewType(\"forbidden\")\n\tErrBadRequest      = errorx.CommonErrors.NewType(\"bad_request\")\n\tErrNotFound        = errorx.CommonErrors.NewType(\"not_found\")\n\n\terrInternal  = errorx.CommonErrors.NewType(\"internal\")\n\tpropHTTPCode = errorx.RegisterProperty(\"http_code\")\n)\n\nfunc HTTPCodeProperty(code int) (errorx.Property, int) {\n\treturn propHTTPCode, code\n}\n\nfunc extractHTTPCodeFromError(err error) int {\n\tif err == nil {\n\t\treturn http.StatusOK\n\t}\n\n\tex := errorx.Cast(err)\n\tif ex == nil {\n\t\treturn http.StatusInternalServerError\n\t}\n\n\t// If there is a Status Code property inside, take it.\n\tv, ok := ex.Property(propHTTPCode)\n\tif ok {\n\t\treturn v.(int)\n\t}\n\n\t// Is it a well-known error type?\n\tif ex.IsOfType(ErrUnauthenticated) {\n\t\t// See https://stackoverflow.com/questions/3297048/403-forbidden-vs-401-unauthorized-http-responses\n\t\t// for why StatusUnauthorized comes from ErrUnauthenticated\n\t\treturn http.StatusUnauthorized\n\t}\n\tif ex.IsOfType(ErrForbidden) {\n\t\treturn http.StatusForbidden\n\t}\n\tif ex.IsOfType(ErrBadRequest) {\n\t\treturn http.StatusBadRequest\n\t}\n\tif ex.IsOfType(ErrNotFound) {\n\t\treturn http.StatusNotFound\n\t}\n\n\treturn http.StatusInternalServerError\n}\n\n// ErrorHandlerFn creates a handler func that turns (last) error in the context into an APIError json response.\n// In handlers, `rest.Error(c, err)` can be used to attach the error to the context.\n// When error is attached in the context:\n// - The handler can optionally assign the HTTP status code.\n// - The handler must not self-generate a response body.\nfunc ErrorHandlerFn() gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tc.Next()\n\n\t\terr := c.Errors.Last()\n\t\tif err == nil {\n\t\t\treturn\n\t\t}\n\n\t\tif c.Writer.Size() > 0 {\n\t\t\treturn\n\t\t}\n\n\t\tstatusCode := c.Writer.Status()\n\t\tif statusCode == http.StatusOK {\n\t\t\t// Change the status code if it is not specified.\n\t\t\tstatusCode = extractHTTPCodeFromError(err.Err)\n\t\t}\n\n\t\terrResponse := NewErrorResponse(err.Err)\n\n\t\tlog.Warn(\"Error when handling request\",\n\t\t\tzap.String(\"uri\", c.Request.RequestURI),\n\t\t\tzap.String(\"remoteAddr\", c.Request.RemoteAddr),\n\t\t\tzap.String(\"errorCode\", errResponse.Code),\n\t\t\tzap.String(\"errorMessage\", errResponse.Message),\n\t\t\tzap.String(\"errorFullText\", errResponse.FullText), // empty unless on debug level\n\t\t)\n\t\tc.AbortWithStatusJSON(statusCode, errResponse)\n\t}\n}\n"
  },
  {
    "path": "util/rest/error_resp.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage rest\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap/zapcore\"\n)\n\ntype ErrorResponse struct {\n\tError    bool   `json:\"error\"`\n\tMessage  string `json:\"message\"`\n\tCode     string `json:\"code\"`\n\tFullText string `json:\"full_text\"`\n}\n\n// buildSimpleMessage traverses through the error chain and builds a simple error message.\nfunc buildSimpleMessage(err error) string {\n\tif err == nil {\n\t\treturn \"\"\n\t}\n\n\tmb := strings.Builder{}\n\tisFirstMsg := true\n\tcause := err\n\tfor cause != nil {\n\t\tcauseEx := errorx.Cast(cause)\n\t\tvar msg string\n\t\tif causeEx == nil {\n\t\t\t// cause exists, but is not an errorx type\n\t\t\tmsg = cause.Error()\n\t\t} else {\n\t\t\tmsg = causeEx.Message()\n\t\t}\n\n\t\tif len(msg) > 0 {\n\t\t\tif !isFirstMsg {\n\t\t\t\tmb.WriteString(\", caused by: \")\n\t\t\t}\n\t\t\tmb.WriteString(msg)\n\t\t\tisFirstMsg = false\n\t\t}\n\n\t\tif causeEx == nil {\n\t\t\t// This is already an error interface. It is not possible to get cause any more.\n\t\t\tbreak\n\t\t}\n\t\tcause = causeEx.Cause()\n\t}\n\n\tif isFirstMsg {\n\t\t// No message is successfully extracted\n\t\treturn err.Error()\n\t}\n\treturn mb.String()\n}\n\nfunc buildCode(err error) string {\n\tif err == nil {\n\t\treturn \"\"\n\t}\n\n\tcause := err\n\tfor cause != nil {\n\t\tcauseEx := errorx.Cast(cause)\n\t\tif causeEx == nil {\n\t\t\tbreak\n\t\t}\n\t\tif causeEx.Type().RootNamespace().FullName() == \"synthetic\" {\n\t\t\t// Ignore standard transparent types.\n\t\t\t// User-defined transparent types are not detectable, however.\n\t\t\tcause = causeEx.Cause()\n\t\t} else {\n\t\t\treturn causeEx.Type().FullName()\n\t\t}\n\t}\n\treturn errInternal.FullName()\n}\n\n// Note: This function only exists for compatibility during the refactoring. Before refactoring,\n// all error codes begin with \"error.\". We will migrate more and more error codes to not begin with \"error.\".\n// Finally, after all error codes are migrated, this function is no longer needed.\nfunc removeErrorPrefix(code string) string {\n\treturn strings.TrimPrefix(code, \"error.\")\n}\n\nfunc buildDetailMessage(err error) string {\n\tif err == nil {\n\t\treturn \"\"\n\t}\n\treturn fmt.Sprintf(\"%+v\", errorx.EnsureStackTrace(err))\n}\n\nfunc NewErrorResponse(err error) ErrorResponse {\n\tlogLevel := log.GetLevel()\n\tfullText := \"\"\n\tif logLevel == zapcore.DebugLevel {\n\t\tfullText = buildDetailMessage(err)\n\t}\n\treturn ErrorResponse{\n\t\tError:   true,\n\t\tMessage: buildSimpleMessage(err),\n\t\tCode:    removeErrorPrefix(buildCode(err)),\n\t\t// For security reasons, we need to hide detailed stacktrace info in prod.\n\t\tFullText: fullText,\n\t}\n}\n"
  },
  {
    "path": "util/rest/error_resp_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage rest\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestErrors(t *testing.T) {\n\tns := errorx.NewNamespace(\"ns\")\n\terrTypeInner := ns.NewType(\"errInner\")\n\terrTypeOuter := ns.NewType(\"errOuter\")\n\n\ttests := []struct {\n\t\terr                 error\n\t\texpectCode          string\n\t\texpectSimpleMessage string\n\t\texpectDetailMessage string\n\t}{\n\t\t{\n\t\t\tfmt.Errorf(\"\"),\n\t\t\t\"common.internal\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t},\n\t\t{\n\t\t\tfmt.Errorf(\"foo\"),\n\t\t\t\"common.internal\",\n\t\t\t\"foo\",\n\t\t\t\"foo\",\n\t\t},\n\t\t{\n\t\t\tos.ErrNotExist,\n\t\t\t\"common.internal\",\n\t\t\t\"file does not exist\",\n\t\t\t\"file does not exist\",\n\t\t},\n\t\t{\n\t\t\tfmt.Errorf(\"internal error: %w\", os.ErrNotExist),\n\t\t\t\"common.internal\",\n\t\t\t\"internal error: file does not exist\",\n\t\t\t\"internal error: file does not exist\",\n\t\t},\n\t\t{\n\t\t\terrTypeInner.NewWithNoMessage(),\n\t\t\t\"ns.errInner\",\n\t\t\t\"ns.errInner\",\n\t\t\t\"ns.errInner\",\n\t\t},\n\t\t{\n\t\t\terrTypeInner.New(\"foo\"),\n\t\t\t\"ns.errInner\",\n\t\t\t\"foo\",\n\t\t\t\"ns.errInner: foo\",\n\t\t},\n\t\t{\n\t\t\terrTypeOuter.WrapWithNoMessage(os.ErrNotExist),\n\t\t\t\"ns.errOuter\",\n\t\t\t\"file does not exist\",\n\t\t\t\"ns.errOuter: file does not exist\",\n\t\t},\n\t\t{\n\t\t\terrTypeOuter.Wrap(os.ErrNotExist, \"internal error\"),\n\t\t\t\"ns.errOuter\",\n\t\t\t\"internal error, caused by: file does not exist\",\n\t\t\t\"ns.errOuter: internal error, cause: file does not exist\",\n\t\t},\n\t\t{\n\t\t\terrTypeOuter.WrapWithNoMessage(errTypeInner.NewWithNoMessage()),\n\t\t\t\"ns.errOuter\",\n\t\t\t\"ns.errOuter: ns.errInner\",\n\t\t\t\"ns.errOuter: ns.errInner\",\n\t\t},\n\t\t{\n\t\t\terrTypeOuter.WrapWithNoMessage(errTypeInner.New(\"foo\")),\n\t\t\t\"ns.errOuter\",\n\t\t\t\"foo\",\n\t\t\t\"ns.errOuter: ns.errInner: foo\",\n\t\t},\n\t\t{\n\t\t\terrTypeOuter.WrapWithNoMessage(errTypeInner.WrapWithNoMessage(os.ErrNotExist)),\n\t\t\t\"ns.errOuter\",\n\t\t\t\"file does not exist\",\n\t\t\t\"ns.errOuter: ns.errInner: file does not exist\",\n\t\t},\n\t\t{\n\t\t\terrTypeOuter.WrapWithNoMessage(errTypeInner.WrapWithNoMessage(fmt.Errorf(\"\"))),\n\t\t\t\"ns.errOuter\",\n\t\t\t\"ns.errOuter: ns.errInner\",\n\t\t\t\"ns.errOuter: ns.errInner\",\n\t\t},\n\t\t{\n\t\t\terrTypeOuter.WrapWithNoMessage(errTypeInner.Wrap(os.ErrNotExist, \"internal error\")),\n\t\t\t\"ns.errOuter\",\n\t\t\t\"internal error, caused by: file does not exist\",\n\t\t\t\"ns.errOuter: ns.errInner: internal error, cause: file does not exist\",\n\t\t},\n\t\t{\n\t\t\terrTypeOuter.Wrap(errTypeInner.NewWithNoMessage(), \"gateway error\"),\n\t\t\t\"ns.errOuter\",\n\t\t\t\"gateway error\",\n\t\t\t\"ns.errOuter: gateway error, cause: ns.errInner\",\n\t\t},\n\t\t{\n\t\t\terrTypeOuter.Wrap(errTypeInner.New(\"foo\"), \"gateway error\"),\n\t\t\t\"ns.errOuter\",\n\t\t\t\"gateway error, caused by: foo\",\n\t\t\t\"ns.errOuter: gateway error, cause: ns.errInner: foo\",\n\t\t},\n\t\t{\n\t\t\terrTypeOuter.Wrap(errTypeInner.WrapWithNoMessage(os.ErrNotExist), \"gateway error\"),\n\t\t\t\"ns.errOuter\",\n\t\t\t\"gateway error, caused by: file does not exist\",\n\t\t\t\"ns.errOuter: gateway error, cause: ns.errInner: file does not exist\",\n\t\t},\n\t\t{\n\t\t\terrTypeOuter.Wrap(errTypeInner.Wrap(os.ErrNotExist, \"internal error\"), \"gateway error\"),\n\t\t\t\"ns.errOuter\",\n\t\t\t\"gateway error, caused by: internal error, caused by: file does not exist\",\n\t\t\t\"ns.errOuter: gateway error, cause: ns.errInner: internal error, cause: file does not exist\",\n\t\t},\n\t\t{\n\t\t\terrorx.Decorate(errorx.IllegalState.New(\"unfortunate\"), \"this could be so much better\"),\n\t\t\t\"common.illegal_state\",\n\t\t\t\"this could be so much better, caused by: unfortunate\",\n\t\t\t\"this could be so much better, cause: common.illegal_state: unfortunate\",\n\t\t},\n\t\t{\n\t\t\terrorx.Decorate(os.ErrNotExist, \"this could be so much better\"),\n\t\t\t\"common.internal\",\n\t\t\t\"this could be so much better, caused by: file does not exist\",\n\t\t\t\"this could be so much better, cause: file does not exist\",\n\t\t},\n\t}\n\n\tfor idx, tt := range tests {\n\t\tt.Run(fmt.Sprintf(\"Case #%d\", idx), func(t *testing.T) {\n\t\t\trequire.Equal(t, tt.expectSimpleMessage, buildSimpleMessage(tt.err))\n\t\t\trequire.Equal(t, tt.expectCode, buildCode(tt.err))\n\t\t\trequireErrorAndStack(t, buildDetailMessage(tt.err), tt.expectDetailMessage)\n\t\t})\n\t}\n\n\trequire.Equal(t, \"\", buildSimpleMessage(nil))\n\trequire.Equal(t, \"\", buildCode(nil))\n\trequire.Equal(t, \"\", buildDetailMessage(nil))\n}\n\nfunc requireErrorAndStack(t *testing.T, src string, errMessage string) {\n\tlines := strings.SplitN(src, \"\\n\", 2)\n\trequire.Equal(t, 2, len(lines))\n\trequire.Equal(t, errMessage, lines[0])\n\trequire.NotEmpty(t, lines[1])\n\n\tstacks := strings.Split(lines[1], \"\\n\")\n\trequire.GreaterOrEqual(t, len(stacks), 2)\n\trequire.True(t, regexp.MustCompile(`^\\s*at github\\.com/.*?\\(\\)`).MatchString(stacks[0]))\n\trequire.True(t, regexp.MustCompile(`\\.go:\\d+$`).MatchString(stacks[1]))\n}\n"
  },
  {
    "path": "util/rest/error_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage rest\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/stretchr/testify/require\"\n\t\"github.com/stretchr/testify/suite\"\n\t\"go.uber.org/atomic\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/assertutil\"\n)\n\nfunc TestExtractHTTPCodeFromError(t *testing.T) {\n\tns := errorx.NewNamespace(\"ns\")\n\tet := ns.NewType(\"err1\")\n\n\ttests := []struct {\n\t\twant int\n\t\targs error\n\t}{\n\t\t{http.StatusOK, nil},\n\t\t{http.StatusInternalServerError, fmt.Errorf(\"foo\")},\n\t\t{http.StatusBadRequest, ErrBadRequest.NewWithNoMessage()},\n\t\t{http.StatusBadRequest, ErrBadRequest.WrapWithNoMessage(fmt.Errorf(\"foo\"))},\n\t\t{http.StatusBadRequest, errorx.Decorate(ErrBadRequest.NewWithNoMessage(), \"parameter foo is invalid\")},\n\t\t{http.StatusInternalServerError, et.NewWithNoMessage()},\n\t\t{http.StatusInternalServerError, et.WrapWithNoMessage(ErrBadRequest.NewWithNoMessage())},\n\t\t{http.StatusBadGateway, et.NewWithNoMessage().WithProperty(HTTPCodeProperty(http.StatusBadGateway))},\n\t\t{http.StatusConflict, ErrBadRequest.NewWithNoMessage().WithProperty(HTTPCodeProperty(http.StatusConflict))},\n\t}\n\tfor _, tt := range tests {\n\t\trequire.Equal(t, tt.want, extractHTTPCodeFromError(tt.args))\n\t}\n}\n\ntype ErrorHandlerFnTestSuite struct {\n\tsuite.Suite\n}\n\nfunc (suite *ErrorHandlerFnTestSuite) TestNoError() {\n\tengine := gin.New()\n\tengine.Use(ErrorHandlerFn())\n\tengine.GET(\"/test\", func(c *gin.Context) {\n\t\tOK(c, gin.H{\n\t\t\t\"foo\": \"bar\",\n\t\t})\n\t})\n\n\tr := httptest.NewRecorder()\n\treq, _ := http.NewRequest(\"GET\", \"/foo/\", nil)\n\tengine.ServeHTTP(r, req)\n\tsuite.Require().Equal(http.StatusNotFound, r.Code)\n\n\tr = httptest.NewRecorder()\n\treq, _ = http.NewRequest(\"GET\", \"/test\", nil)\n\tengine.ServeHTTP(r, req)\n\tsuite.Require().Equal(http.StatusOK, r.Code)\n\tsuite.Require().JSONEq(`{\"foo\":\"bar\"}`, r.Body.String())\n}\n\nfunc (suite *ErrorHandlerFnTestSuite) TestNormalError() {\n\tengine := gin.New()\n\tengine.Use(ErrorHandlerFn())\n\tengine.GET(\"/test\", func(c *gin.Context) {\n\t\tError(c, fmt.Errorf(\"some error\"))\n\t})\n\n\tr := httptest.NewRecorder()\n\treq, _ := http.NewRequest(\"GET\", \"/test\", nil)\n\tengine.ServeHTTP(r, req)\n\tsuite.Require().Equal(http.StatusInternalServerError, r.Code)\n\tassertutil.RequireJSONContains(suite.T(), r.Body.String(), `{\"error\":true,\"message\":\"some error\",\"code\":\"common.internal\"}`)\n}\n\nfunc (suite *ErrorHandlerFnTestSuite) TestBuiltinError() {\n\tengine := gin.New()\n\tengine.Use(ErrorHandlerFn())\n\tengine.GET(\"/test\", func(c *gin.Context) {\n\t\tError(c, ErrBadRequest.NewWithNoMessage())\n\t})\n\n\tr := httptest.NewRecorder()\n\treq, _ := http.NewRequest(\"GET\", \"/test\", nil)\n\tengine.ServeHTTP(r, req)\n\tsuite.Require().Equal(http.StatusBadRequest, r.Code)\n\tassertutil.RequireJSONContains(suite.T(), r.Body.String(), `{\"error\":true,\"message\":\"common.bad_request\",\"code\":\"common.bad_request\"}`)\n}\n\nfunc (suite *ErrorHandlerFnTestSuite) TestOverrideStatusCode() {\n\tengine := gin.New()\n\tengine.Use(ErrorHandlerFn())\n\tengine.GET(\"/test\", func(c *gin.Context) {\n\t\tError(c, ErrBadRequest.NewWithNoMessage())\n\t\tc.Status(http.StatusBadGateway)\n\t})\n\n\tr := httptest.NewRecorder()\n\treq, _ := http.NewRequest(\"GET\", \"/test\", nil)\n\tengine.ServeHTTP(r, req)\n\tsuite.Require().Equal(http.StatusBadGateway, r.Code)\n\tassertutil.RequireJSONContains(suite.T(), r.Body.String(), `{\"error\":true,\"message\":\"common.bad_request\",\"code\":\"common.bad_request\"}`)\n}\n\nfunc (suite *ErrorHandlerFnTestSuite) TestResponseAfterError() {\n\tengine := gin.New()\n\tengine.Use(ErrorHandlerFn())\n\tengine.GET(\"/test\", func(c *gin.Context) {\n\t\tError(c, ErrBadRequest.NewWithNoMessage())\n\t\t// If normal response is returned, no error message will be generated\n\t\tc.String(http.StatusNotFound, \"foobar\")\n\t})\n\n\tr := httptest.NewRecorder()\n\treq, _ := http.NewRequest(\"GET\", \"/test\", nil)\n\tengine.ServeHTTP(r, req)\n\tsuite.Require().Equal(http.StatusNotFound, r.Code)\n\tsuite.Require().Equal(`foobar`, r.Body.String())\n}\n\nfunc (suite *ErrorHandlerFnTestSuite) TestNextMiddleware() {\n\tmiddlewareCalled := atomic.NewBool(false)\n\n\tengine := gin.New()\n\tengine.Use(ErrorHandlerFn())\n\tengine.Use(func(c *gin.Context) {\n\t\t// Middleware after the ErrorHandlerFn is called even if error is returned,\n\t\t// as ErrorHandlerFn handles errors after processing the request\n\t\tc.Next()\n\t\tmiddlewareCalled.Store(true)\n\t})\n\tengine.GET(\"/test\", func(c *gin.Context) {\n\t\tError(c, ErrBadRequest.NewWithNoMessage())\n\t})\n\n\tr := httptest.NewRecorder()\n\treq, _ := http.NewRequest(\"GET\", \"/test\", nil)\n\tengine.ServeHTTP(r, req)\n\tsuite.Require().Equal(http.StatusBadRequest, r.Code)\n\tsuite.Require().True(middlewareCalled.Load())\n}\n\n// When panic happened, ErrorHandlerFn will not be invoked.\nfunc (suite *ErrorHandlerFnTestSuite) TestWithRecoveryMiddleware() {\n\tengine := gin.New()\n\tengine.Use(gin.Recovery())\n\tengine.Use(ErrorHandlerFn())\n\tengine.GET(\"/test\", func(_ *gin.Context) {\n\t\tpanic(\"some panic\")\n\t})\n\n\tr := httptest.NewRecorder()\n\treq, _ := http.NewRequest(\"GET\", \"/test\", nil)\n\tengine.ServeHTTP(r, req)\n\tsuite.Require().Equal(http.StatusInternalServerError, r.Code)\n\tsuite.Require().Equal(\"\", r.Body.String())\n}\n\nfunc TestErrorHandlerFn(t *testing.T) {\n\tsuite.Run(t, &ErrorHandlerFnTestSuite{})\n}\n"
  },
  {
    "path": "util/rest/fileswap/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage fileswap\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/rest/fileswap/server.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage fileswap\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\tjwt \"github.com/golang-jwt/jwt/v4\"\n\t\"github.com/gtank/cryptopasta\"\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/minio/sio\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/nocopy\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\n// Handler provides a file-based data serving HTTP handler.\n// Arbitrary data stream can be stored in the file in encrypted form temporarily, and then downloaded by the user later.\n// As data is stored in the file, large chunk of data is supported.\n//\n// Note: the download token cannot be mixed in different Handler instances.\ntype Handler struct {\n\tnocopy.NoCopy\n\n\t// The secret is used to sign the download token as well as encrypting the file in the FS.\n\tsecret []byte\n}\n\nfunc New() *Handler {\n\treturn &Handler{\n\t\tsecret: cryptopasta.NewEncryptionKey()[:],\n\t}\n}\n\n// NewFileWriter creates a writer for storing data into FS. A download token can be generated from the writer\n// for downloading later. The downloading can be handled by the HandleDownloadRequest.\nfunc (s *Handler) NewFileWriter(tempFilePattern string) (*FileWriter, error) {\n\tfile, err := os.CreateTemp(\"\", tempFilePattern)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tw, err := sio.EncryptWriter(file, sio.Config{Key: s.secret})\n\tif err != nil {\n\t\t_ = file.Close()\n\t\t_ = os.Remove(file.Name())\n\t\treturn nil, err\n\t}\n\n\treturn &FileWriter{\n\t\tWriteCloser: w,\n\t\tsecret:      s.secret,\n\t\tfilePath:    file.Name(),\n\t}, nil\n}\n\ntype downloadTokenClaims struct {\n\tjwt.StandardClaims\n\tTempFileName     string\n\tDownloadFileName string\n}\n\nfunc (s *Handler) parseClaimsFromToken(tokenString string) (*downloadTokenClaims, error) {\n\ttoken, err := jwt.ParseWithClaims(\n\t\ttokenString,\n\t\t&downloadTokenClaims{},\n\t\tfunc(_ *jwt.Token) (interface{}, error) {\n\t\t\treturn s.secret, nil\n\t\t})\n\tif token != nil {\n\t\tif claims, ok := token.Claims.(*downloadTokenClaims); ok && token.Valid {\n\t\t\treturn claims, nil\n\t\t}\n\t}\n\tvar ve *jwt.ValidationError\n\tif errors.As(err, &ve) && ve.Errors&jwt.ValidationErrorExpired != 0 {\n\t\treturn nil, errorx.Decorate(err, \"download token is expired\")\n\t}\n\treturn nil, errorx.Decorate(err, \"download token is invalid\")\n}\n\n// HandleDownloadRequest handles a gin Request for serving the file in the FS by using a download token.\n// The file will be removed after it is successfully served to the user.\nfunc (s *Handler) HandleDownloadRequest(c *gin.Context) {\n\tclaims, err := s.parseClaimsFromToken(c.Query(\"token\"))\n\tif err != nil {\n\t\trest.Error(c, rest.ErrBadRequest.Wrap(err, \"Invalid download request\"))\n\t\treturn\n\t}\n\n\tfile, err := os.Open(claims.TempFileName)\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\t// It is possible that token is reused. In this case, raise invalid request error.\n\t\t\trest.Error(c, rest.ErrBadRequest.Wrap(err, \"Download file not found. Please retry.\"))\n\t\t} else {\n\t\t\trest.Error(c, err)\n\t\t}\n\t\treturn\n\t}\n\tdefer func() {\n\t\t_ = file.Close()\n\t\t_ = os.Remove(claims.TempFileName)\n\t}()\n\n\tc.Writer.Header().Set(\"Content-type\", \"application/octet-stream\")\n\tc.Writer.Header().Set(\"Content-Disposition\", fmt.Sprintf(`attachment; filename=\"%s\"`, claims.DownloadFileName))\n\n\t_, err = sio.Decrypt(c.Writer, file, sio.Config{\n\t\tKey: s.secret,\n\t})\n\tif err != nil {\n\t\trest.Error(c, err)\n\t\treturn\n\t}\n}\n\ntype FileWriter struct {\n\tnocopy.NoCopy\n\tio.WriteCloser\n\n\tsecret   []byte\n\tfilePath string\n}\n\nfunc (fw *FileWriter) Remove() {\n\t_ = fw.Close()\n\t_ = os.Remove(fw.filePath)\n}\n\n// GetDownloadToken generates a download token for downloading this file later.\n// The downloading can be handled by the Handler.HandleDownloadRequest.\nfunc (fw *FileWriter) GetDownloadToken(downloadFileName string, expireIn time.Duration) (string, error) {\n\tclaims := downloadTokenClaims{\n\t\tTempFileName:     fw.filePath,\n\t\tDownloadFileName: downloadFileName,\n\t\tStandardClaims: jwt.StandardClaims{ //nolint:staticcheck // StandardClaims is deprecated, but we use it here temporarily\n\t\t\tExpiresAt: time.Now().Add(expireIn).Unix(),\n\t\t},\n\t}\n\ttoken := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)\n\ttokenSigned, err := token.SignedString(fw.secret)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn tokenSigned, nil\n}\n"
  },
  {
    "path": "util/rest/fileswap/server_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage fileswap\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/assertutil\"\n\t\"github.com/pingcap/tidb-dashboard/util/rest\"\n)\n\nfunc (s *Handler) mustGetDownloadToken(t *testing.T, fileContent string, downloadFileName string, expireIn time.Duration) string {\n\tfw, err := s.NewFileWriter(\"test\")\n\trequire.NoError(t, err)\n\t_, err = fmt.Fprint(fw, fileContent)\n\trequire.NoError(t, err)\n\terr = fw.Close()\n\trequire.NoError(t, err)\n\ttoken, err := fw.GetDownloadToken(downloadFileName, expireIn)\n\trequire.NoError(t, err)\n\treturn token\n}\n\nfunc TestDownload(t *testing.T) {\n\thandler := New()\n\ttoken := handler.mustGetDownloadToken(t, \"foobar\", \"file.txt\", time.Second*5)\n\n\tr := httptest.NewRecorder()\n\tc, _ := gin.CreateTestContext(r)\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/download?token=\"+token, nil)\n\thandler.HandleDownloadRequest(c)\n\n\trequire.Len(t, c.Errors, 0)\n\trequire.Equal(t, http.StatusOK, r.Code)\n\trequire.Equal(t, `attachment; filename=\"file.txt\"`, r.Header().Get(\"Content-Disposition\"))\n\trequire.Equal(t, `application/octet-stream`, r.Header().Get(\"Content-Type\"))\n\trequire.Equal(t, \"foobar\", r.Body.String())\n\n\t// Download again\n\tr = httptest.NewRecorder()\n\tc, _ = gin.CreateTestContext(r)\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/download?token=\"+token, nil)\n\thandler.HandleDownloadRequest(c)\n\n\trequire.Len(t, c.Errors, 1)\n\trequire.True(t, errorx.IsOfType(c.Errors[0].Err, rest.ErrBadRequest))\n\trequire.Contains(t, c.Errors[0].Error(), \"Download file not found\")\n}\n\nfunc TestDownloadAnotherInstance(t *testing.T) {\n\thandler := New()\n\ttoken := handler.mustGetDownloadToken(t, \"foobar\", \"file.txt\", time.Second*5)\n\n\thandler2 := New()\n\tr := httptest.NewRecorder()\n\tc, _ := gin.CreateTestContext(r)\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/download?token=\"+token, nil)\n\thandler2.HandleDownloadRequest(c)\n\n\trequire.Len(t, c.Errors, 1)\n\trequire.True(t, errorx.IsOfType(c.Errors[0].Err, rest.ErrBadRequest))\n\trequire.Contains(t, c.Errors[0].Error(), \"Invalid download request\")\n\trequire.Contains(t, c.Errors[0].Error(), \"download token is invalid\")\n}\n\nfunc TestExpiredToken(t *testing.T) {\n\thandler := New()\n\ttoken := handler.mustGetDownloadToken(t, \"foobar\", \"file.txt\", 0)\n\n\t// Note: token expiration precision is 1sec.\n\ttime.Sleep(time.Millisecond * 1100)\n\n\tr := httptest.NewRecorder()\n\tc, _ := gin.CreateTestContext(r)\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/download?token=\"+token, nil)\n\thandler.HandleDownloadRequest(c)\n\n\trequire.Len(t, c.Errors, 1)\n\trequire.True(t, errorx.IsOfType(c.Errors[0].Err, rest.ErrBadRequest))\n\trequire.Contains(t, c.Errors[0].Error(), \"Invalid download request\")\n\trequire.Contains(t, c.Errors[0].Error(), \"download token is expired\")\n}\n\nfunc TestNotAToken(t *testing.T) {\n\thandler := New()\n\n\tr := httptest.NewRecorder()\n\tc, _ := gin.CreateTestContext(r)\n\tc.Request, _ = http.NewRequest(http.MethodGet, \"/download?token=abc\", nil)\n\thandler.HandleDownloadRequest(c)\n\n\trequire.Len(t, c.Errors, 1)\n\trequire.True(t, errorx.IsOfType(c.Errors[0].Err, rest.ErrBadRequest))\n\trequire.Contains(t, c.Errors[0].Error(), \"Invalid download request\")\n\trequire.Contains(t, c.Errors[0].Error(), \"download token is invalid\")\n}\n\nfunc TestDownloadInMiddleware(t *testing.T) {\n\thandler := New()\n\ttoken := handler.mustGetDownloadToken(t, \"abc\", \"myfile.bin\", time.Second*5)\n\n\tengine := gin.New()\n\tengine.Use(rest.ErrorHandlerFn())\n\tengine.GET(\"/download\", handler.HandleDownloadRequest)\n\n\t// A normal request\n\tr := httptest.NewRecorder()\n\treq, _ := http.NewRequest(\"GET\", \"/download?token=\"+token, nil)\n\tengine.ServeHTTP(r, req)\n\trequire.Equal(t, http.StatusOK, r.Code)\n\trequire.Equal(t, `attachment; filename=\"myfile.bin\"`, r.Header().Get(\"Content-Disposition\"))\n\trequire.Equal(t, `application/octet-stream`, r.Header().Get(\"Content-Type\"))\n\trequire.Equal(t, \"abc\", r.Body.String())\n\n\t// A request without token\n\tr = httptest.NewRecorder()\n\treq, _ = http.NewRequest(\"GET\", \"/download\", nil)\n\tengine.ServeHTTP(r, req)\n\trequire.Equal(t, http.StatusBadRequest, r.Code)\n\trequire.Equal(t, \"\", r.Header().Get(\"Content-Disposition\"))\n\trequire.Equal(t, \"application/json; charset=utf-8\", r.Header().Get(\"Content-Type\"))\n\tassertutil.RequireJSONContains(t, r.Body.String(), `{\"code\":\"common.bad_request\", \"error\":true}`)\n}\n"
  },
  {
    "path": "util/sqlitestore/sqlitestore.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage dbstore\n\nimport (\n\t\"context\"\n\t\"os\"\n\t\"path\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/fx\"\n\t\"go.uber.org/zap\"\n\t\"gorm.io/driver/sqlite\"\n\t\"gorm.io/gorm\"\n\t\"moul.io/zapgorm2\"\n)\n\ntype SqliteDB struct {\n\t*gorm.DB\n}\n\ntype Config struct {\n\tDbFilePath string\n}\n\n// NewSqliteStore creates a new SQLite storage. When lifecycle is ended, the storage will be closed.\nfunc NewSqliteStore(lc fx.Lifecycle, config Config) (*SqliteDB, error) {\n\tdataDir := path.Dir(config.DbFilePath)\n\n\terr := os.MkdirAll(dataDir, 0o700)\n\tif err != nil {\n\t\tlog.Error(\"Failed to create Dashboard storage directory\", zap.Error(err))\n\t\treturn nil, err\n\t}\n\n\tlog.Info(\"Dashboard initializing local storage file\", zap.String(\"path\", config.DbFilePath))\n\tgormDB, err := gorm.Open(sqlite.Open(config.DbFilePath), &gorm.Config{\n\t\tLogger: zapgorm2.New(log.L()),\n\t})\n\tif err != nil {\n\t\tlog.Error(\"Failed to open Dashboard storage file\", zap.Error(err))\n\t\treturn nil, err\n\t}\n\n\tdb := &SqliteDB{gormDB}\n\n\tlc.Append(fx.Hook{\n\t\tOnStop: func(context.Context) error {\n\t\t\tsqlDB, err := db.DB.DB()\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\treturn sqlDB.Close()\n\t\t},\n\t})\n\n\treturn db, nil\n}\n"
  },
  {
    "path": "util/testutil/db.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage testutil\n\nimport (\n\t\"encoding/hex\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/DATA-DOG/go-sqlmock\"\n\tmysqldriver \"github.com/go-sql-driver/mysql\"\n\t\"github.com/google/uuid\"\n\t\"github.com/pingcap/log\"\n\t\"github.com/stretchr/testify/require\"\n\t\"gorm.io/driver/mysql\"\n\t\"gorm.io/gorm\"\n\t\"moul.io/zapgorm2\"\n)\n\ntype TestDB struct {\n\tinner   *gorm.DB\n\trequire *require.Assertions\n\n\tisUnderlyingMocked bool\n\tmock               sqlmock.Sqlmock\n}\n\nfunc OpenTestDB(t *testing.T, configModifier ...func(*mysqldriver.Config, *gorm.Config)) *TestDB {\n\tr := require.New(t)\n\n\tdsn := mysqldriver.NewConfig()\n\tdsn.Net = \"tcp\"\n\tdsn.Addr = \"127.0.0.1:4000\"\n\tdsn.Params = map[string]string{\"time_zone\": \"'+00:00'\"}\n\tdsn.ParseTime = true\n\tdsn.Loc = time.UTC\n\tdsn.User = \"root\"\n\tdsn.DBName = \"test\"\n\n\tconfig := &gorm.Config{\n\t\tLogger: zapgorm2.New(log.L()),\n\t}\n\n\tfor _, m := range configModifier {\n\t\tm(dsn, config)\n\t}\n\n\tdb, err := gorm.Open(mysql.Open(dsn.FormatDSN()), config)\n\tr.NoError(err)\n\n\treturn &TestDB{\n\t\tinner:   db.Debug(),\n\t\trequire: r,\n\t}\n}\n\nfunc OpenMockDB(t *testing.T, configModifier ...func(*gorm.Config)) *TestDB {\n\tr := require.New(t)\n\n\tsqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))\n\trequire.NoError(t, err)\n\n\tconfig := &gorm.Config{\n\t\tLogger: zapgorm2.New(log.L()),\n\t}\n\n\tfor _, m := range configModifier {\n\t\tm(config)\n\t}\n\n\tdb, err := gorm.Open(mysql.New(mysql.Config{\n\t\tConn:                      sqlDB,\n\t\tSkipInitializeWithVersion: true,\n\t}), config)\n\tr.NoError(err)\n\n\treturn &TestDB{\n\t\tinner:   db.Debug(),\n\t\trequire: r,\n\n\t\tisUnderlyingMocked: true,\n\t\tmock:               mock,\n\t}\n}\n\nfunc (db *TestDB) MustClose() {\n\tif db.isUnderlyingMocked {\n\t\tdb.mock.ExpectClose()\n\t}\n\n\td, err := db.inner.DB()\n\tdb.require.NoError(err)\n\n\terr = d.Close()\n\tdb.require.NoError(err)\n}\n\nfunc (db *TestDB) NewID() string {\n\tid := uuid.New()\n\treturn hex.EncodeToString(id[:])\n}\n\nfunc (db *TestDB) Gorm() *gorm.DB {\n\treturn db.inner\n}\n\nfunc (db *TestDB) MustExec(sql string, values ...interface{}) {\n\terr := db.inner.Exec(sql, values...).Error\n\tdb.require.NoError(err)\n}\n\ntype ExplainRow struct {\n\tID string `gorm:\"column:id\"`\n}\n\nfunc (db *TestDB) MustExplain(sql string, values ...interface{}) []ExplainRow {\n\tvar rows []ExplainRow\n\terr := db.Gorm().Raw(\"EXPLAIN \"+sql, values...).Scan(&rows).Error\n\tdb.require.NoError(err)\n\treturn rows\n}\n\nfunc (db *TestDB) Mocker() sqlmock.Sqlmock {\n\tdb.require.True(db.isUnderlyingMocked)\n\treturn db.mock\n}\n\nfunc (db *TestDB) MustMeetMockExpectation() {\n\tdb.require.Nil(db.Mocker().ExpectationsWereMet())\n}\n\nfunc RequireIndexRangeScan(t *testing.T, explain []ExplainRow) {\n\thasIndexRange := false\n\tfor _, r := range explain {\n\t\tif strings.Contains(r.ID, \"IndexRangeScan\") {\n\t\t\thasIndexRange = true\n\t\t\tbreak\n\t\t}\n\t}\n\trequire.True(t, hasIndexRange, \"IndexRangeScan is not contained in the explain result\", explain)\n}\n"
  },
  {
    "path": "util/testutil/gintest/context.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage gintest\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"net/url\"\n\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc CtxGet(queryParams url.Values) (c *gin.Context, r *httptest.ResponseRecorder) {\n\tr = httptest.NewRecorder()\n\tc, _ = gin.CreateTestContext(r)\n\tu := \"/\"\n\tif queryParams != nil {\n\t\tu = \"/?\" + queryParams.Encode()\n\t}\n\tc.Request, _ = http.NewRequest(http.MethodGet, u, nil)\n\treturn\n}\n\nfunc CtxPost(queryParams url.Values, postBody string) (c *gin.Context, r *httptest.ResponseRecorder) {\n\tr = httptest.NewRecorder()\n\tc, _ = gin.CreateTestContext(r)\n\tu := \"/\"\n\tif queryParams != nil {\n\t\tu = \"/?\" + queryParams.Encode()\n\t}\n\tc.Request, _ = http.NewRequest(http.MethodPost, u, bytes.NewBuffer([]byte(postBody)))\n\treturn\n}\n"
  },
  {
    "path": "util/testutil/http_server.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage testutil\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\n\t\"go.uber.org/atomic\"\n)\n\nfunc GetHTTPServerHost(server *httptest.Server) string {\n\treturn server.Listener.Addr().String()\n}\n\nfunc NewHTTPServer(response string) *httptest.Server {\n\treturn httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\t_, _ = fmt.Fprintln(w, response)\n\t}))\n}\n\nfunc NewHTTPServerAtHost(response string, host string) *httptest.Server {\n\tl, err := net.Listen(\"tcp\", host)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tserver := &httptest.Server{\n\t\tListener: l,\n\t\tConfig: &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { // nolint:gosec\n\t\t\t_, _ = fmt.Fprintln(w, response)\n\t\t})},\n\t}\n\tserver.Start()\n\treturn server\n}\n\ntype MultiServerHelper struct {\n\tServers      []*httptest.Server\n\tlastActiveID *atomic.Int32\n\tlastResponse *atomic.String\n}\n\nfunc NewMultiServer(n int, responsePattern string) *MultiServerHelper {\n\tservers := make([]*httptest.Server, 0)\n\tlastActiveID := atomic.NewInt32(-1)\n\tlastResponse := atomic.NewString(\"\")\n\n\tfor i := range n {\n\t\tfunc(i int) {\n\t\t\ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {\n\t\t\t\tresp := fmt.Sprintf(responsePattern, i)\n\t\t\t\tlastActiveID.Store(int32(i))\n\t\t\t\tlastResponse.Store(resp)\n\t\t\t\t_, _ = fmt.Fprintln(w, resp)\n\t\t\t}))\n\t\t\tservers = append(servers, s)\n\t\t}(i)\n\t}\n\treturn &MultiServerHelper{\n\t\tServers:      servers,\n\t\tlastActiveID: lastActiveID,\n\t\tlastResponse: lastResponse,\n\t}\n}\n\nfunc (m *MultiServerHelper) LastResp() string {\n\treturn m.lastResponse.Load()\n}\n\nfunc (m *MultiServerHelper) LastID() int {\n\treturn int(m.lastActiveID.Load())\n}\n\nfunc (m *MultiServerHelper) CloseAll() {\n\tfor _, s := range m.Servers {\n\t\ts.Close()\n\t}\n}\n\nfunc (m *MultiServerHelper) GetEndpoints() []string {\n\tl := make([]string, 0, len(m.Servers))\n\tfor _, s := range m.Servers {\n\t\tl = append(l, GetHTTPServerHost(s))\n\t}\n\treturn l\n}\n"
  },
  {
    "path": "util/testutil/httpmockutil/responder.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage httpmockutil\n\nimport (\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/jarcoal/httpmock\"\n)\n\nfunc StringResponder(body string) httpmock.Responder {\n\treturn httpmock.NewStringResponder(200, strings.TrimSpace(body))\n}\n\nfunc ChanStringResponder(ch chan string) httpmock.Responder {\n\treturn func(*http.Request) (*http.Response, error) {\n\t\tv := <-ch\n\t\treturn httpmock.NewStringResponse(200, v), nil\n\t}\n}\n"
  },
  {
    "path": "util/testutil/testdefault/main.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage testdefault\n\nimport (\n\t\"runtime\"\n\t\"testing\"\n\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/goleak\"\n)\n\nfunc enableDebugLog() {\n\tlogger, prop, err := log.InitLogger(&log.Config{\n\t\tLevel: \"debug\",\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tlog.ReplaceGlobals(logger, prop)\n}\n\nfunc TestMain(m *testing.M) {\n\tenableDebugLog()\n\tgin.SetMode(gin.TestMode)\n\topts := []goleak.Option{\n\t\tgoleak.IgnoreTopFunction(\"github.com/ReneKroon/ttlcache/v2.(*Cache).startExpirationProcessing\"),\n\t}\n\tgoleak.VerifyTestMain(m, opts...)\n\truntime.GC()\n}\n"
  },
  {
    "path": "util/timeutil/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage timeutil\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/timeutil/format.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage timeutil\n\nimport \"time\"\n\nconst (\n\tDateTimeFormat = \"2006-01-02 15:04:05 MST\"\n)\n\nfunc FormatInUTC(t time.Time) string {\n\treturn t.UTC().Format(DateTimeFormat)\n}\n"
  },
  {
    "path": "util/timeutil/format_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage timeutil\n\nimport (\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestFormatInUTC(t *testing.T) {\n\trequire.Equal(t, \"2021-10-01 16:53:55 UTC\", FormatInUTC(time.Unix(1633107235, 0)))\n}\n"
  },
  {
    "path": "util/tlsutil/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tlsutil\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/tlsutil/config_ext.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tlsutil\n\nimport (\n\t\"crypto/tls\"\n\t\"strings\"\n)\n\nfunc GetHTTPScheme(c *tls.Config) string {\n\tif c == nil {\n\t\treturn \"http\"\n\t}\n\treturn \"https\"\n}\n\nfunc NormalizeURL(c *tls.Config, url string) string {\n\tconst httpPrefix = \"http://\"\n\tconst httpsPrefix = \"https://\"\n\tconst httpPrefixLen = len(httpPrefix)\n\n\tisLeadingHTTP := strings.HasPrefix(url, httpPrefix)\n\tisLeadingHTTPS := strings.HasPrefix(url, httpsPrefix)\n\tif !isLeadingHTTP && !isLeadingHTTPS {\n\t\treturn GetHTTPScheme(c) + \"://\" + url\n\t}\n\tif c != nil && isLeadingHTTP {\n\t\treturn httpsPrefix + url[httpPrefixLen:]\n\t}\n\treturn url\n}\n"
  },
  {
    "path": "util/tlsutil/config_ext_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage tlsutil\n\nimport (\n\t\"crypto/tls\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestNormalizeURL(t *testing.T) {\n\tvar c *tls.Config\n\n\tc = nil\n\trequire.Equal(t, \"http://foo\", NormalizeURL(c, \"foo\"))\n\trequire.Equal(t, \"http://foo\", NormalizeURL(c, \"http://foo\"))\n\trequire.Equal(t, \"https://foo\", NormalizeURL(c, \"https://foo\"))\n\trequire.Equal(t, \"http://ftp://foo\", NormalizeURL(c, \"ftp://foo\"))\n\n\tc = &tls.Config{} // #nosec G402\n\trequire.Equal(t, \"https://foo\", NormalizeURL(c, \"foo\"))\n\trequire.Equal(t, \"https://foo\", NormalizeURL(c, \"http://foo\"))\n\trequire.Equal(t, \"https://foo\", NormalizeURL(c, \"https://foo\"))\n\trequire.Equal(t, \"https://ftp://foo\", NormalizeURL(c, \"ftp://foo\"))\n}\n"
  },
  {
    "path": "util/topo/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topo\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/topo/mock_TopologyProvider.go",
    "content": "// Code generated by mockery v2.40.3. DO NOT EDIT.\n\npackage topo\n\nimport (\n\tcontext \"context\"\n\n\tmock \"github.com/stretchr/testify/mock\"\n)\n\n// MockTopologyProvider is an autogenerated mock type for the TopologyProvider type\ntype MockTopologyProvider struct {\n\tmock.Mock\n}\n\n// GetAlertManager provides a mock function with given fields: ctx\nfunc (_m *MockTopologyProvider) GetAlertManager(ctx context.Context) (*AlertManagerInfo, error) {\n\tret := _m.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetAlertManager\")\n\t}\n\n\tvar r0 *AlertManagerInfo\n\tvar r1 error\n\tif rf, ok := ret.Get(0).(func(context.Context) (*AlertManagerInfo, error)); ok {\n\t\treturn rf(ctx)\n\t}\n\tif rf, ok := ret.Get(0).(func(context.Context) *AlertManagerInfo); ok {\n\t\tr0 = rf(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*AlertManagerInfo)\n\t\t}\n\t}\n\n\tif rf, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = rf(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\n\treturn r0, r1\n}\n\n// GetGrafana provides a mock function with given fields: ctx\nfunc (_m *MockTopologyProvider) GetGrafana(ctx context.Context) (*GrafanaInfo, error) {\n\tret := _m.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetGrafana\")\n\t}\n\n\tvar r0 *GrafanaInfo\n\tvar r1 error\n\tif rf, ok := ret.Get(0).(func(context.Context) (*GrafanaInfo, error)); ok {\n\t\treturn rf(ctx)\n\t}\n\tif rf, ok := ret.Get(0).(func(context.Context) *GrafanaInfo); ok {\n\t\tr0 = rf(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*GrafanaInfo)\n\t\t}\n\t}\n\n\tif rf, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = rf(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\n\treturn r0, r1\n}\n\n// GetPD provides a mock function with given fields: ctx\nfunc (_m *MockTopologyProvider) GetPD(ctx context.Context) ([]PDInfo, error) {\n\tret := _m.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetPD\")\n\t}\n\n\tvar r0 []PDInfo\n\tvar r1 error\n\tif rf, ok := ret.Get(0).(func(context.Context) ([]PDInfo, error)); ok {\n\t\treturn rf(ctx)\n\t}\n\tif rf, ok := ret.Get(0).(func(context.Context) []PDInfo); ok {\n\t\tr0 = rf(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]PDInfo)\n\t\t}\n\t}\n\n\tif rf, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = rf(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\n\treturn r0, r1\n}\n\n// GetPrometheus provides a mock function with given fields: ctx\nfunc (_m *MockTopologyProvider) GetPrometheus(ctx context.Context) (*PrometheusInfo, error) {\n\tret := _m.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetPrometheus\")\n\t}\n\n\tvar r0 *PrometheusInfo\n\tvar r1 error\n\tif rf, ok := ret.Get(0).(func(context.Context) (*PrometheusInfo, error)); ok {\n\t\treturn rf(ctx)\n\t}\n\tif rf, ok := ret.Get(0).(func(context.Context) *PrometheusInfo); ok {\n\t\tr0 = rf(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).(*PrometheusInfo)\n\t\t}\n\t}\n\n\tif rf, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = rf(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\n\treturn r0, r1\n}\n\n// GetTiDB provides a mock function with given fields: ctx\nfunc (_m *MockTopologyProvider) GetTiDB(ctx context.Context) ([]TiDBInfo, error) {\n\tret := _m.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetTiDB\")\n\t}\n\n\tvar r0 []TiDBInfo\n\tvar r1 error\n\tif rf, ok := ret.Get(0).(func(context.Context) ([]TiDBInfo, error)); ok {\n\t\treturn rf(ctx)\n\t}\n\tif rf, ok := ret.Get(0).(func(context.Context) []TiDBInfo); ok {\n\t\tr0 = rf(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]TiDBInfo)\n\t\t}\n\t}\n\n\tif rf, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = rf(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\n\treturn r0, r1\n}\n\n// GetTiFlash provides a mock function with given fields: ctx\nfunc (_m *MockTopologyProvider) GetTiFlash(ctx context.Context) ([]TiFlashStoreInfo, error) {\n\tret := _m.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetTiFlash\")\n\t}\n\n\tvar r0 []TiFlashStoreInfo\n\tvar r1 error\n\tif rf, ok := ret.Get(0).(func(context.Context) ([]TiFlashStoreInfo, error)); ok {\n\t\treturn rf(ctx)\n\t}\n\tif rf, ok := ret.Get(0).(func(context.Context) []TiFlashStoreInfo); ok {\n\t\tr0 = rf(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]TiFlashStoreInfo)\n\t\t}\n\t}\n\n\tif rf, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = rf(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\n\treturn r0, r1\n}\n\n// GetTiKV provides a mock function with given fields: ctx\nfunc (_m *MockTopologyProvider) GetTiKV(ctx context.Context) ([]TiKVStoreInfo, error) {\n\tret := _m.Called(ctx)\n\n\tif len(ret) == 0 {\n\t\tpanic(\"no return value specified for GetTiKV\")\n\t}\n\n\tvar r0 []TiKVStoreInfo\n\tvar r1 error\n\tif rf, ok := ret.Get(0).(func(context.Context) ([]TiKVStoreInfo, error)); ok {\n\t\treturn rf(ctx)\n\t}\n\tif rf, ok := ret.Get(0).(func(context.Context) []TiKVStoreInfo); ok {\n\t\tr0 = rf(ctx)\n\t} else {\n\t\tif ret.Get(0) != nil {\n\t\t\tr0 = ret.Get(0).([]TiKVStoreInfo)\n\t\t}\n\t}\n\n\tif rf, ok := ret.Get(1).(func(context.Context) error); ok {\n\t\tr1 = rf(ctx)\n\t} else {\n\t\tr1 = ret.Error(1)\n\t}\n\n\treturn r0, r1\n}\n\n// NewMockTopologyProvider creates a new instance of MockTopologyProvider. 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 NewMockTopologyProvider(t interface {\n\tmock.TestingT\n\tCleanup(func())\n}) *MockTopologyProvider {\n\tmock := &MockTopologyProvider{}\n\tmock.Mock.Test(t)\n\n\tt.Cleanup(func() { mock.AssertExpectations(t) })\n\n\treturn mock\n}\n"
  },
  {
    "path": "util/topo/model.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topo\n\ntype CompStatus string\n\nconst (\n\tCompStatusUnknown     CompStatus = \"unknown\"\n\tCompStatusUnreachable CompStatus = \"unreachable\"\n\tCompStatusUp          CompStatus = \"up\"\n\tCompStatusTombstone   CompStatus = \"tombstone\"\n\tCompStatusLeaving     CompStatus = \"leaving\"\n\tCompStatusDown        CompStatus = \"down\"\n)\n\ntype Kind string\n\nconst (\n\tKindTiDB         Kind = \"tidb\"\n\tKindTiKV         Kind = \"tikv\"\n\tKindPD           Kind = \"pd\"\n\tKindTiFlash      Kind = \"tiflash\"\n\tKindTiCDC        Kind = \"ticdc\"\n\tKindTiProxy      Kind = \"tiproxy\"\n\tKindTSO          Kind = \"tso\"\n\tKindScheduling   Kind = \"scheduling\"\n\tKindAlertManager Kind = \"alert_manager\"\n\tKindGrafana      Kind = \"grafana\"\n\tKindPrometheus   Kind = \"prometheus\"\n)\n\ntype PDInfo struct {\n\tGitHash        string\n\tVersion        string\n\tIP             string\n\tPort           uint\n\tDeployPath     string\n\tStatus         CompStatus\n\tStartTimestamp int64 // Ts = 0 means unknown\n}\n\nvar _ Info = &PDInfo{}\n\nfunc (i *PDInfo) Info() CompInfo {\n\treturn CompInfo{\n\t\tCompDescriptor: CompDescriptor{\n\t\t\tIP:   i.IP,\n\t\t\tPort: i.Port,\n\t\t\tKind: KindPD,\n\t\t},\n\t\tVersion: i.Version,\n\t\tStatus:  i.Status,\n\t}\n}\n\ntype TiDBInfo struct {\n\tGitHash        string\n\tVersion        string\n\tIP             string\n\tPort           uint\n\tDeployPath     string\n\tStatus         CompStatus\n\tStatusPort     uint\n\tStartTimestamp int64\n}\n\nvar _ Info = &TiDBInfo{}\n\nfunc (i *TiDBInfo) Info() CompInfo {\n\treturn CompInfo{\n\t\tCompDescriptor: CompDescriptor{\n\t\t\tIP:         i.IP,\n\t\t\tPort:       i.Port,\n\t\t\tStatusPort: i.StatusPort,\n\t\t\tKind:       KindTiDB,\n\t\t},\n\t\tVersion: i.Version,\n\t\tStatus:  i.Status,\n\t}\n}\n\n// StoreInfo may be either a TiKV store info or a TiFlash store info.\ntype StoreInfo struct {\n\tGitHash        string\n\tVersion        string\n\tIP             string\n\tPort           uint\n\tDeployPath     string\n\tStatus         CompStatus\n\tStatusPort     uint\n\tLabels         map[string]string\n\tStartTimestamp int64\n}\n\ntype TiKVStoreInfo StoreInfo\n\nvar _ Info = &TiKVStoreInfo{}\n\nfunc (i *TiKVStoreInfo) Info() CompInfo {\n\treturn CompInfo{\n\t\tCompDescriptor: CompDescriptor{\n\t\t\tIP:         i.IP,\n\t\t\tPort:       i.Port,\n\t\t\tStatusPort: i.StatusPort,\n\t\t\tKind:       KindTiKV,\n\t\t},\n\t\tVersion: i.Version,\n\t\tStatus:  i.Status,\n\t}\n}\n\ntype TiFlashStoreInfo StoreInfo\n\nvar _ Info = &TiFlashStoreInfo{}\n\nfunc (i *TiFlashStoreInfo) Info() CompInfo {\n\treturn CompInfo{\n\t\tCompDescriptor: CompDescriptor{\n\t\t\tIP:         i.IP,\n\t\t\tPort:       i.Port,\n\t\t\tStatusPort: i.StatusPort,\n\t\t\tKind:       KindTiFlash,\n\t\t},\n\t\tVersion: i.Version,\n\t\tStatus:  i.Status,\n\t}\n}\n\ntype TiCDCInfo struct {\n\tClusterName    string\n\tGitHash        string\n\tVersion        string\n\tIP             string\n\tPort           uint\n\tDeployPath     string\n\tStatus         CompStatus\n\tStatusPort     uint\n\tStartTimestamp int64\n}\n\nvar _ Info = &TiCDCInfo{}\n\nfunc (i *TiCDCInfo) Info() CompInfo {\n\treturn CompInfo{\n\t\tCompDescriptor: CompDescriptor{\n\t\t\tIP:         i.IP,\n\t\t\tPort:       i.Port,\n\t\t\tStatusPort: i.StatusPort,\n\t\t\tKind:       KindTiCDC,\n\t\t},\n\t\tVersion: i.Version,\n\t\tStatus:  i.Status,\n\t}\n}\n\ntype TiProxyInfo struct {\n\tGitHash        string\n\tVersion        string\n\tIP             string\n\tPort           uint\n\tDeployPath     string\n\tStatus         CompStatus\n\tStatusPort     uint\n\tStartTimestamp int64\n}\n\nvar _ Info = &TiProxyInfo{}\n\nfunc (i *TiProxyInfo) Info() CompInfo {\n\treturn CompInfo{\n\t\tCompDescriptor: CompDescriptor{\n\t\t\tIP:         i.IP,\n\t\t\tPort:       i.Port,\n\t\t\tStatusPort: i.StatusPort,\n\t\t\tKind:       KindTiProxy,\n\t\t},\n\t\tVersion: i.Version,\n\t\tStatus:  i.Status,\n\t}\n}\n\ntype TSOInfo struct {\n\tGitHash        string\n\tVersion        string\n\tIP             string\n\tPort           uint\n\tDeployPath     string\n\tStatus         CompStatus\n\tStartTimestamp int64\n}\n\nvar _ Info = &TSOInfo{}\n\nfunc (i *TSOInfo) Info() CompInfo {\n\treturn CompInfo{\n\t\tCompDescriptor: CompDescriptor{\n\t\t\tIP:   i.IP,\n\t\t\tPort: i.Port,\n\t\t\tKind: KindTSO,\n\t\t},\n\t\tVersion: i.Version,\n\t\tStatus:  i.Status,\n\t}\n}\n\ntype SchedulingInfo struct {\n\tGitHash        string\n\tVersion        string\n\tIP             string\n\tPort           uint\n\tDeployPath     string\n\tStatus         CompStatus\n\tStartTimestamp int64\n}\n\nvar _ Info = &SchedulingInfo{}\n\nfunc (i *SchedulingInfo) Info() CompInfo {\n\treturn CompInfo{\n\t\tCompDescriptor: CompDescriptor{\n\t\t\tIP:   i.IP,\n\t\t\tPort: i.Port,\n\t\t\tKind: KindScheduling,\n\t\t},\n\t\tVersion: i.Version,\n\t\tStatus:  i.Status,\n\t}\n}\n\ntype StandardDeployInfo struct {\n\tIP   string\n\tPort uint\n}\n\ntype AlertManagerInfo StandardDeployInfo\n\nvar _ Info = &AlertManagerInfo{}\n\nfunc (i *AlertManagerInfo) Info() CompInfo {\n\treturn CompInfo{\n\t\tCompDescriptor: CompDescriptor{\n\t\t\tIP:   i.IP,\n\t\t\tPort: i.Port,\n\t\t\tKind: KindAlertManager,\n\t\t},\n\t\tVersion: \"\",\n\t\tStatus:  CompStatusUnknown,\n\t}\n}\n\ntype GrafanaInfo StandardDeployInfo\n\nvar _ Info = &GrafanaInfo{}\n\nfunc (i *GrafanaInfo) Info() CompInfo {\n\treturn CompInfo{\n\t\tCompDescriptor: CompDescriptor{\n\t\t\tIP:   i.IP,\n\t\t\tPort: i.Port,\n\t\t\tKind: KindGrafana,\n\t\t},\n\t\tVersion: \"\",\n\t\tStatus:  CompStatusUnknown,\n\t}\n}\n\ntype PrometheusInfo StandardDeployInfo\n\nvar _ Info = &PrometheusInfo{}\n\nfunc (i *PrometheusInfo) Info() CompInfo {\n\treturn CompInfo{\n\t\tCompDescriptor: CompDescriptor{\n\t\t\tIP:   i.IP,\n\t\t\tPort: i.Port,\n\t\t\tKind: KindPrometheus,\n\t\t},\n\t\tVersion: \"\",\n\t\tStatus:  CompStatusUnknown,\n\t}\n}\n"
  },
  {
    "path": "util/topo/model_desc.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topo\n\n// CompDescriptor (Component Desc) is a unique identifier for a component.\n// It is secure to be persisted, but is not secure to be accepted from the user input.\n// To securely accept a Comp from user input, see SignedCompDescriptor.\ntype CompDescriptor struct {\n\tIP         string\n\tPort       uint\n\tStatusPort uint\n\tKind       Kind\n\t// WARN: Extreme care should be taken when adding more fields here,\n\t// as this struct is widely used or persisted.\n}\n"
  },
  {
    "path": "util/topo/model_info.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\n// CompInfo provides common information for a component.\n// It must not be persisted, as the runtime status may change at any time.\n// It must not be accepted directly from the user input. See SignedCompDescriptor.\n// The contained descriptor is unsigned, which means it may not be very useful to be passed to users.\n// Call WithSignature() if you want to pass to users.\ntype CompInfo struct {\n\tCompDescriptor\n\tVersion string\n\tStatus  CompStatus\n}\n\n// Info is an interface implemented by all component info structures.\ntype Info interface {\n\tInfo() CompInfo\n}\n\nfunc GetInfoByKind(ctx context.Context, p TopologyProvider, kind Kind) ([]CompInfo, error) {\n\tswitch kind {\n\tcase KindTiDB:\n\t\tv, err := p.GetTiDB(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresult := make([]CompInfo, 0, len(v))\n\t\tfor _, info := range v {\n\t\t\tresult = append(result, info.Info())\n\t\t}\n\t\treturn result, nil\n\tcase KindTiKV:\n\t\tv, err := p.GetTiKV(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresult := make([]CompInfo, 0, len(v))\n\t\tfor _, info := range v {\n\t\t\tresult = append(result, info.Info())\n\t\t}\n\t\treturn result, nil\n\tcase KindPD:\n\t\tv, err := p.GetPD(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresult := make([]CompInfo, 0, len(v))\n\t\tfor _, info := range v {\n\t\t\tresult = append(result, info.Info())\n\t\t}\n\t\treturn result, nil\n\tcase KindTiFlash:\n\t\tv, err := p.GetTiFlash(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tresult := make([]CompInfo, 0, len(v))\n\t\tfor _, info := range v {\n\t\t\tresult = append(result, info.Info())\n\t\t}\n\t\treturn result, nil\n\tcase KindAlertManager:\n\t\tv, err := p.GetAlertManager(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []CompInfo{v.Info()}, nil\n\tcase KindGrafana:\n\t\tv, err := p.GetGrafana(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []CompInfo{v.Info()}, nil\n\tcase KindPrometheus:\n\t\tv, err := p.GetPrometheus(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treturn []CompInfo{v.Info()}, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported component %s\", kind)\n\t}\n}\n"
  },
  {
    "path": "util/topo/pdtopo/1_main_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdtopo_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/testutil/testdefault\"\n)\n\nfunc TestMain(m *testing.M) {\n\ttestdefault.TestMain(m)\n}\n"
  },
  {
    "path": "util/topo/pdtopo/monitor.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdtopo\n\nimport (\n\t\"context\"\n\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/topo\"\n)\n\nfunc GetAlertManagerInstance(ctx context.Context, etcdClient *clientv3.Client) (*topo.AlertManagerInfo, error) {\n\ti, err := fetchStandardComponentTopology(ctx, \"alertmanager\", etcdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif i == nil {\n\t\treturn nil, nil\n\t}\n\treturn (*topo.AlertManagerInfo)(i), nil\n}\n\nfunc GetGrafanaInstance(ctx context.Context, etcdClient *clientv3.Client) (*topo.GrafanaInfo, error) {\n\ti, err := fetchStandardComponentTopology(ctx, \"grafana\", etcdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif i == nil {\n\t\treturn nil, nil\n\t}\n\treturn (*topo.GrafanaInfo)(i), nil\n}\n\nfunc GetPrometheusInstance(ctx context.Context, etcdClient *clientv3.Client) (*topo.PrometheusInfo, error) {\n\ti, err := fetchStandardComponentTopology(ctx, \"prometheus\", etcdClient)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif i == nil {\n\t\treturn nil, nil\n\t}\n\treturn (*topo.PrometheusInfo)(i), nil\n}\n"
  },
  {
    "path": "util/topo/pdtopo/pd.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdtopo\n\nimport (\n\t\"context\"\n\t\"sort\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/client/pdclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n\t\"github.com/pingcap/tidb-dashboard/util/netutil\"\n\t\"github.com/pingcap/tidb-dashboard/util/topo\"\n)\n\nfunc GetPDInstances(ctx context.Context, pdAPI *pdclient.APIClient) ([]topo.PDInfo, error) {\n\tds, err := pdAPI.GetMembers(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\thealth, err := pdAPI.GetHealth(ctx)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\thealthMap := map[uint64]struct{}{}\n\tfor _, v := range *health {\n\t\tif v.Health {\n\t\t\thealthMap[v.MemberID] = struct{}{}\n\t\t}\n\t}\n\n\tnodes := make([]topo.PDInfo, 0)\n\n\tfor _, ds := range ds.Members {\n\t\tu := ds.ClientUrls[0]\n\t\thostname, port, err := netutil.ParseHostAndPortFromAddressURL(u)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\n\t\ttsResp, err := pdAPI.GetStatus(ctx)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"Failed to fetch start timestamp\",\n\t\t\t\tzap.String(\"component\", distro.R().PD),\n\t\t\t\tzap.String(\"targetNode\", u),\n\t\t\t\tzap.Error(err))\n\t\t\ttsResp = &pdclient.GetStatusResponse{}\n\t\t}\n\n\t\tstoreStatus := topo.CompStatusUnreachable\n\t\tif _, ok := healthMap[ds.MemberID]; ok {\n\t\t\tstoreStatus = topo.CompStatusUp\n\t\t}\n\n\t\tnodes = append(nodes, topo.PDInfo{\n\t\t\tGitHash:        ds.GitHash,\n\t\t\tVersion:        ds.BinaryVersion,\n\t\t\tIP:             hostname,\n\t\t\tPort:           port,\n\t\t\tDeployPath:     ds.DeployPath,\n\t\t\tStatus:         storeStatus,\n\t\t\tStartTimestamp: tsResp.StartTimestamp,\n\t\t})\n\t}\n\n\tsort.Slice(nodes, func(i, j int) bool {\n\t\tif nodes[i].IP < nodes[j].IP {\n\t\t\treturn true\n\t\t}\n\t\tif nodes[i].IP > nodes[j].IP {\n\t\t\treturn false\n\t\t}\n\t\treturn nodes[i].Port < nodes[j].Port\n\t})\n\n\treturn nodes, nil\n}\n"
  },
  {
    "path": "util/topo/pdtopo/pd_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdtopo_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/client/pdclient/fixture\"\n\t\"github.com/pingcap/tidb-dashboard/util/topo\"\n\t\"github.com/pingcap/tidb-dashboard/util/topo/pdtopo\"\n)\n\nfunc TestGetPDInstances(t *testing.T) {\n\tapiClient := fixture.NewAPIClientFixture()\n\tresp, err := pdtopo.GetPDInstances(context.Background(), apiClient)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []topo.PDInfo{\n\t\t{\n\t\t\tGitHash:        \"0c1246dd219fd16b4b2ff5108941e5d3e958922d\",\n\t\t\tVersion:        \"v4.0.14\",\n\t\t\tIP:             \"172.16.6.169\",\n\t\t\tPort:           2379,\n\t\t\tDeployPath:     \"/home/tidb/tidb-deploy/pd-2379/bin\",\n\t\t\tStatus:         topo.CompStatusUp,\n\t\t\tStartTimestamp: 1635762685,\n\t\t},\n\t\t{\n\t\t\tGitHash:        \"0c1246dd219fd16b4b2ff5108941e5d3e958922d\",\n\t\t\tVersion:        \"v4.0.14\",\n\t\t\tIP:             \"172.16.6.170\",\n\t\t\tPort:           2379,\n\t\t\tDeployPath:     \"/home/tidb/tidb-deploy/pd-2379/bin\",\n\t\t\tStatus:         topo.CompStatusUp,\n\t\t\tStartTimestamp: 1635762685,\n\t\t},\n\t\t{\n\t\t\tGitHash:        \"0c1246dd219fd16b4b2ff5108941e5d3e958922d\",\n\t\t\tVersion:        \"v4.0.14\",\n\t\t\tIP:             \"172.16.6.171\",\n\t\t\tPort:           2379,\n\t\t\tDeployPath:     \"/home/tidb/tidb-deploy/pd-2379/bin\",\n\t\t\tStatus:         topo.CompStatusUp,\n\t\t\tStartTimestamp: 1635762685,\n\t\t},\n\t}, resp)\n}\n"
  },
  {
    "path": "util/topo/pdtopo/provider_pd.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdtopo\n\nimport (\n\t\"context\"\n\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/client/pdclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/topo\"\n)\n\n// TopologyFromPD provides the topology information from PD.\ntype TopologyFromPD struct {\n\tetcdClient *clientv3.Client\n\tpdAPI      *pdclient.APIClient\n}\n\nvar _ topo.TopologyProvider = (*TopologyFromPD)(nil)\n\n// NewTopologyProviderFromPD creates a provider that gets topology information from PD.\n// The base URL of the PD API client must be correctly set.\nfunc NewTopologyProviderFromPD(etcdClient *clientv3.Client, pdAPI *pdclient.APIClient) topo.TopologyProvider {\n\treturn &TopologyFromPD{\n\t\tetcdClient: etcdClient,\n\t\tpdAPI:      pdAPI,\n\t}\n}\n\nfunc (p *TopologyFromPD) GetPD(ctx context.Context) ([]topo.PDInfo, error) {\n\treturn GetPDInstances(ctx, p.pdAPI)\n}\n\nfunc (p *TopologyFromPD) GetTiDB(ctx context.Context) ([]topo.TiDBInfo, error) {\n\treturn GetTiDBInstances(ctx, p.etcdClient)\n}\n\nfunc (p *TopologyFromPD) GetTiKV(ctx context.Context) ([]topo.TiKVStoreInfo, error) {\n\ttikvStores, _, err := GetStoreInstances(ctx, p.pdAPI)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn tikvStores, nil\n}\n\nfunc (p *TopologyFromPD) GetTiFlash(ctx context.Context) ([]topo.TiFlashStoreInfo, error) {\n\t_, tiFlashStores, err := GetStoreInstances(ctx, p.pdAPI)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn tiFlashStores, nil\n}\n\nfunc (p *TopologyFromPD) GetPrometheus(ctx context.Context) (*topo.PrometheusInfo, error) {\n\treturn GetPrometheusInstance(ctx, p.etcdClient)\n}\n\nfunc (p *TopologyFromPD) GetGrafana(ctx context.Context) (*topo.GrafanaInfo, error) {\n\treturn GetGrafanaInstance(ctx, p.etcdClient)\n}\n\nfunc (p *TopologyFromPD) GetAlertManager(ctx context.Context) (*topo.AlertManagerInfo, error) {\n\treturn GetAlertManagerInstance(ctx, p.etcdClient)\n}\n"
  },
  {
    "path": "util/topo/pdtopo/std_comp.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdtopo\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\n\t\"github.com/joomcode/errorx\"\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/topo\"\n)\n\nvar (\n\tErrNS                  = errorx.NewNamespace(\"topo.pd\")\n\tErrEtcdRequestFailed   = ErrNS.NewType(\"etcd_request_failed\")\n\tErrInvalidTopologyData = ErrNS.NewType(\"invalid_topology_data\")\n)\n\nfunc fetchStandardComponentTopology(ctx context.Context, componentName string, etcdClient *clientv3.Client) (*topo.StandardDeployInfo, error) {\n\tkey := \"/topology/\" + componentName\n\tresp, err := etcdClient.Get(ctx, key, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn nil, ErrEtcdRequestFailed.Wrap(err, \"Failed to read topology from etcd key `%s`\", key)\n\t}\n\tif resp.Count == 0 {\n\t\treturn nil, nil\n\t}\n\tinfo := topo.StandardDeployInfo{}\n\tkv := resp.Kvs[0]\n\tif err = json.Unmarshal(kv.Value, &info); err != nil {\n\t\tlog.Warn(\"Failed to unmarshal topology value\",\n\t\t\tzap.String(\"key\", string(kv.Key)),\n\t\t\tzap.String(\"value\", string(kv.Value)))\n\t\treturn nil, nil\n\t}\n\treturn &info, nil\n}\n"
  },
  {
    "path": "util/topo/pdtopo/store.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdtopo\n\nimport (\n\t\"context\"\n\t\"strings\"\n\n\t\"github.com/pingcap/log\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/client/pdclient\"\n\t\"github.com/pingcap/tidb-dashboard/util/netutil\"\n\t\"github.com/pingcap/tidb-dashboard/util/topo\"\n)\n\n// GetStoreInstances returns TiKV info and TiFlash info.\nfunc GetStoreInstances(ctx context.Context, pdAPI *pdclient.APIClient) ([]topo.TiKVStoreInfo, []topo.TiFlashStoreInfo, error) {\n\tstores, err := pdAPI.HLGetStores(ctx)\n\tif err != nil {\n\t\treturn nil, nil, err\n\t}\n\n\ttiKVStores := make([]pdclient.GetStoresResponseStore, 0, len(stores))\n\ttiFlashStores := make([]pdclient.GetStoresResponseStore, 0, len(stores))\n\tfor _, store := range stores {\n\t\tisTiFlash := false\n\t\tfor _, label := range store.Labels {\n\t\t\tif label.Key == \"engine\" && label.Value == \"tiflash\" {\n\t\t\t\tisTiFlash = true\n\t\t\t}\n\t\t}\n\t\tif isTiFlash {\n\t\t\ttiFlashStores = append(tiFlashStores, store)\n\t\t} else {\n\t\t\ttiKVStores = append(tiKVStores, store)\n\t\t}\n\t}\n\n\tsiTiKV := buildStoreTopology(tiKVStores)\n\tstoresTiKV := make([]topo.TiKVStoreInfo, 0, len(siTiKV))\n\tfor _, si := range siTiKV {\n\t\tstoresTiKV = append(storesTiKV, topo.TiKVStoreInfo(si))\n\t}\n\n\tsiTiFlash := buildStoreTopology(tiFlashStores)\n\tstoresTiFlash := make([]topo.TiFlashStoreInfo, 0, len(siTiFlash))\n\tfor _, si := range siTiFlash {\n\t\tstoresTiFlash = append(storesTiFlash, topo.TiFlashStoreInfo(si))\n\t}\n\n\treturn storesTiKV, storesTiFlash, nil\n}\n\nfunc buildStoreTopology(stores []pdclient.GetStoresResponseStore) []topo.StoreInfo {\n\tnodes := make([]topo.StoreInfo, 0, len(stores))\n\tfor _, v := range stores {\n\t\thostname, port, err := netutil.ParseHostAndPortFromAddress(v.Address)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"Failed to parse store address\", zap.Any(\"store\", v))\n\t\t\tcontinue\n\t\t}\n\t\t_, statusPort, err := netutil.ParseHostAndPortFromAddress(v.StatusAddress)\n\t\tif err != nil {\n\t\t\tlog.Warn(\"Failed to parse store status address\", zap.Any(\"store\", v))\n\t\t\tcontinue\n\t\t}\n\t\t// In current TiKV, it's version may not start with 'v',\n\t\t// so we may need to add a prefix 'v' for it.\n\t\tversion := strings.Trim(v.Version, \"\\n \")\n\t\tif !strings.HasPrefix(version, \"v\") {\n\t\t\tversion = \"v\" + version\n\t\t}\n\t\tnode := topo.StoreInfo{\n\t\t\tVersion:        version,\n\t\t\tIP:             hostname,\n\t\t\tPort:           port,\n\t\t\tGitHash:        v.GitHash,\n\t\t\tDeployPath:     v.DeployPath,\n\t\t\tStatus:         parseStoreState(v.StateName),\n\t\t\tStatusPort:     statusPort,\n\t\t\tLabels:         map[string]string{},\n\t\t\tStartTimestamp: v.StartTimestamp,\n\t\t}\n\t\tfor _, v := range v.Labels {\n\t\t\tnode.Labels[v.Key] = v.Value\n\t\t}\n\t\tnodes = append(nodes, node)\n\t}\n\n\treturn nodes\n}\n\nfunc parseStoreState(state string) topo.CompStatus {\n\tstate = strings.Trim(strings.ToLower(state), \"\\n \")\n\tswitch state {\n\tcase \"up\":\n\t\treturn topo.CompStatusUp\n\tcase \"tombstone\":\n\t\treturn topo.CompStatusTombstone\n\tcase \"offline\":\n\t\treturn topo.CompStatusLeaving\n\tcase \"down\":\n\t\treturn topo.CompStatusDown\n\tcase \"disconnected\":\n\t\treturn topo.CompStatusUnreachable\n\tdefault:\n\t\treturn topo.CompStatusUnreachable\n\t}\n}\n"
  },
  {
    "path": "util/topo/pdtopo/store_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdtopo_test\n\nimport (\n\t\"context\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/require\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/client/pdclient/fixture\"\n\t\"github.com/pingcap/tidb-dashboard/util/topo\"\n\t\"github.com/pingcap/tidb-dashboard/util/topo/pdtopo\"\n)\n\nfunc TestGetStoreInstances(t *testing.T) {\n\tapiClient := fixture.NewAPIClientFixture()\n\ttiKvStores, tiFlashStores, err := pdtopo.GetStoreInstances(context.Background(), apiClient)\n\trequire.NoError(t, err)\n\trequire.Equal(t, []topo.TiKVStoreInfo{\n\t\t{\n\t\t\tGitHash:        \"d7dc4fff51ca71c76a928a0780a069efaaeaae70\",\n\t\t\tVersion:        \"v4.0.14\",\n\t\t\tIP:             \"172.16.5.141\",\n\t\t\tPort:           20160,\n\t\t\tDeployPath:     \"/home/tidb/tidb-deploy/tikv-20160/bin\",\n\t\t\tStatus:         topo.CompStatusUp,\n\t\t\tStatusPort:     20180,\n\t\t\tLabels:         map[string]string{},\n\t\t\tStartTimestamp: 1636421301,\n\t\t},\n\t\t{\n\t\t\tGitHash:        \"d7dc4fff51ca71c76a928a0780a069efaaeaae70\",\n\t\t\tVersion:        \"v4.0.14\",\n\t\t\tIP:             \"172.16.5.218\",\n\t\t\tPort:           20160,\n\t\t\tDeployPath:     \"/home/tidb/tidb-deploy/tikv-20160/bin\",\n\t\t\tStatus:         topo.CompStatusUp,\n\t\t\tStatusPort:     20180,\n\t\t\tLabels:         map[string]string{},\n\t\t\tStartTimestamp: 1636421304,\n\t\t},\n\t\t{\n\t\t\tGitHash:        \"d7dc4fff51ca71c76a928a0780a069efaaeaae70\",\n\t\t\tVersion:        \"v4.0.14\",\n\t\t\tIP:             \"172.16.6.168\",\n\t\t\tPort:           20160,\n\t\t\tDeployPath:     \"/home/tidb/tidb-deploy/tikv-20160/bin\",\n\t\t\tStatus:         topo.CompStatusUp,\n\t\t\tStatusPort:     20180,\n\t\t\tLabels:         map[string]string{},\n\t\t\tStartTimestamp: 1636421304,\n\t\t},\n\t}, tiKvStores)\n\trequire.Equal(t, []topo.TiFlashStoreInfo{}, tiFlashStores)\n}\n"
  },
  {
    "path": "util/topo/pdtopo/tidb.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage pdtopo\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/pingcap/log\"\n\tclientv3 \"go.etcd.io/etcd/client/v3\"\n\t\"go.uber.org/zap\"\n\n\t\"github.com/pingcap/tidb-dashboard/util/distro\"\n\t\"github.com/pingcap/tidb-dashboard/util/netutil\"\n\t\"github.com/pingcap/tidb-dashboard/util/topo\"\n)\n\nconst tidbTopologyKeyPrefix = \"/topology/tidb/\"\n\nfunc GetTiDBInstances(ctx context.Context, etcdClient *clientv3.Client) ([]topo.TiDBInfo, error) {\n\tresp, err := etcdClient.Get(ctx, tidbTopologyKeyPrefix, clientv3.WithPrefix())\n\tif err != nil {\n\t\treturn nil, ErrEtcdRequestFailed.Wrap(err, \"Failed to read topology from etcd key `%s`\", tidbTopologyKeyPrefix)\n\t}\n\n\tnodesAlive := make(map[string]struct{}, len(resp.Kvs))\n\tnodesInfo := make(map[string]*topo.TiDBInfo, len(resp.Kvs))\n\n\tfor _, kv := range resp.Kvs {\n\t\tkey := string(kv.Key)\n\t\tif !strings.HasPrefix(key, tidbTopologyKeyPrefix) {\n\t\t\tcontinue\n\t\t}\n\t\t// remainingKey looks like `ip:port/info` or `ip:port/ttl`.\n\t\tremainingKey := key[len(tidbTopologyKeyPrefix):]\n\t\tkeyParts := strings.Split(remainingKey, \"/\")\n\t\tif len(keyParts) != 2 {\n\t\t\tlog.Warn(\"Ignored invalid topology key\",\n\t\t\t\tzap.String(\"component\", distro.R().TiDB),\n\t\t\t\tzap.String(\"key\", key))\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch keyParts[1] {\n\t\tcase \"info\":\n\t\t\tnode, err := parseTiDBInfo(keyParts[0], kv.Value)\n\t\t\tif err == nil {\n\t\t\t\tnodesInfo[keyParts[0]] = node\n\t\t\t} else {\n\t\t\t\tlog.Warn(\"Ignored invalid topology info entry\",\n\t\t\t\t\tzap.String(\"component\", distro.R().TiDB),\n\t\t\t\t\tzap.String(\"key\", key),\n\t\t\t\t\tzap.String(\"value\", string(kv.Value)),\n\t\t\t\t\tzap.Error(err))\n\t\t\t}\n\t\tcase \"ttl\":\n\t\t\talive, err := parseTiDBAliveness(kv.Value)\n\t\t\tif err == nil {\n\t\t\t\tnodesAlive[keyParts[0]] = struct{}{}\n\t\t\t\tif !alive {\n\t\t\t\t\tlog.Warn(\"Component alive TTL has expired (maybe local time are not synchronized)\",\n\t\t\t\t\t\tzap.String(\"component\", distro.R().TiDB),\n\t\t\t\t\t\tzap.String(\"key\", key),\n\t\t\t\t\t\tzap.String(\"value\", string(kv.Value)))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog.Warn(\"Ignored invalid topology TTL entry\",\n\t\t\t\t\tzap.String(\"component\", distro.R().TiDB),\n\t\t\t\t\tzap.String(\"key\", key),\n\t\t\t\t\tzap.String(\"value\", string(kv.Value)),\n\t\t\t\t\tzap.Error(err))\n\t\t\t}\n\t\t}\n\t}\n\n\tnodes := make([]topo.TiDBInfo, 0)\n\n\tfor addr, info := range nodesInfo {\n\t\tif _, ok := nodesAlive[addr]; ok {\n\t\t\tinfo.Status = topo.CompStatusUp\n\t\t}\n\t\tnodes = append(nodes, *info)\n\t}\n\n\tsort.Slice(nodes, func(i, j int) bool {\n\t\tif nodes[i].IP < nodes[j].IP {\n\t\t\treturn true\n\t\t}\n\t\tif nodes[i].IP > nodes[j].IP {\n\t\t\treturn false\n\t\t}\n\t\treturn nodes[i].Port < nodes[j].Port\n\t})\n\n\treturn nodes, nil\n}\n\nfunc parseTiDBInfo(address string, value []byte) (*topo.TiDBInfo, error) {\n\tds := struct {\n\t\tVersion        string `json:\"version\"`\n\t\tGitHash        string `json:\"git_hash\"`\n\t\tStatusPort     uint   `json:\"status_port\"`\n\t\tDeployPath     string `json:\"deploy_path\"`\n\t\tStartTimestamp int64  `json:\"start_timestamp\"`\n\t}{}\n\n\terr := json.Unmarshal(value, &ds)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"Read topology value failed\")\n\t}\n\thostname, port, err := netutil.ParseHostAndPortFromAddress(address)\n\tif err != nil {\n\t\treturn nil, ErrInvalidTopologyData.Wrap(err, \"Read topology address failed\")\n\t}\n\n\treturn &topo.TiDBInfo{\n\t\tGitHash:        ds.GitHash,\n\t\tVersion:        ds.Version,\n\t\tIP:             hostname,\n\t\tPort:           port,\n\t\tDeployPath:     ds.DeployPath,\n\t\tStatus:         topo.CompStatusUnreachable,\n\t\tStatusPort:     ds.StatusPort,\n\t\tStartTimestamp: ds.StartTimestamp,\n\t}, nil\n}\n\nfunc parseTiDBAliveness(value []byte) (bool, error) {\n\tunixTimestampNano, err := strconv.ParseUint(string(value), 10, 64)\n\tif err != nil {\n\t\treturn false, ErrInvalidTopologyData.Wrap(err, \"Parse topology TTL info failed\")\n\t}\n\tt := time.Unix(0, int64(unixTimestampNano))\n\tif time.Since(t) > time.Second*45 {\n\t\treturn false, nil\n\t}\n\treturn true, nil\n}\n"
  },
  {
    "path": "util/topo/provider.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topo\n\nimport (\n\t\"context\"\n)\n\n// TopologyProvider provides the topology information for different components.\ntype TopologyProvider interface {\n\tGetPD(ctx context.Context) ([]PDInfo, error)\n\tGetTiDB(ctx context.Context) ([]TiDBInfo, error)\n\tGetTiKV(ctx context.Context) ([]TiKVStoreInfo, error)\n\tGetTiFlash(ctx context.Context) ([]TiFlashStoreInfo, error)\n\tGetPrometheus(ctx context.Context) (*PrometheusInfo, error)\n\tGetGrafana(ctx context.Context) (*GrafanaInfo, error)\n\tGetAlertManager(ctx context.Context) (*AlertManagerInfo, error)\n}\n\n//go:generate mockery --name TopologyProvider --inpackage\nvar _ TopologyProvider = (*MockTopologyProvider)(nil)\n"
  },
  {
    "path": "util/topo/provider_cached.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topo\n\nimport (\n\t\"context\"\n\t\"runtime\"\n\t\"time\"\n\n\t\"github.com/ReneKroon/ttlcache/v2\"\n)\n\n// CachedTopology provides topology over an underlying topology provider with a TTL cache.\n// This struct is concurrent-safe.\ntype CachedTopology struct {\n\tp     TopologyProvider\n\tcache *ttlcache.Cache\n}\n\nvar _ TopologyProvider = (*CachedTopology)(nil)\n\nfunc NewCachedTopology(p TopologyProvider, ttl time.Duration) TopologyProvider {\n\tcache := ttlcache.NewCache()\n\tcache.SkipTTLExtensionOnHit(true)\n\t_ = cache.SetTTL(ttl)\n\n\tct := &CachedTopology{\n\t\tp:     p,\n\t\tcache: cache,\n\t}\n\t// Destroy the internal cache goroutine when no one is referencing this struct anymore.\n\truntime.SetFinalizer(ct, (*CachedTopology).finalize)\n\treturn ct\n}\n\nfunc (c *CachedTopology) getOrFillCache(key string, backSource func() (interface{}, error)) (interface{}, error) {\n\tif data, err := c.cache.Get(key); err == nil {\n\t\treturn data, nil\n\t}\n\t// TODO: use singleflight.\n\tsrc, err := backSource()\n\tif err != nil {\n\t\t// Error is never cached.\n\t\treturn nil, err\n\t}\n\t_ = c.cache.Set(key, src)\n\truntime.KeepAlive(c)\n\treturn src, nil\n}\n\nfunc (c *CachedTopology) GetPD(ctx context.Context) ([]PDInfo, error) {\n\tv, err := c.getOrFillCache(\"pd\", func() (interface{}, error) {\n\t\treturn c.p.GetPD(ctx)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn v.([]PDInfo), nil\n}\n\nfunc (c *CachedTopology) GetTiDB(ctx context.Context) ([]TiDBInfo, error) {\n\tv, err := c.getOrFillCache(\"tidb\", func() (interface{}, error) {\n\t\treturn c.p.GetTiDB(ctx)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn v.([]TiDBInfo), nil\n}\n\nfunc (c *CachedTopology) GetTiKV(ctx context.Context) ([]TiKVStoreInfo, error) {\n\tv, err := c.getOrFillCache(\"tikv\", func() (interface{}, error) {\n\t\treturn c.p.GetTiKV(ctx)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn v.([]TiKVStoreInfo), nil\n}\n\nfunc (c *CachedTopology) GetTiFlash(ctx context.Context) ([]TiFlashStoreInfo, error) {\n\tv, err := c.getOrFillCache(\"tiflash\", func() (interface{}, error) {\n\t\treturn c.p.GetTiFlash(ctx)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn v.([]TiFlashStoreInfo), nil\n}\n\nfunc (c *CachedTopology) GetPrometheus(ctx context.Context) (*PrometheusInfo, error) {\n\tv, err := c.getOrFillCache(\"prometheus\", func() (interface{}, error) {\n\t\treturn c.p.GetPrometheus(ctx)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn v.(*PrometheusInfo), nil\n}\n\nfunc (c *CachedTopology) GetGrafana(ctx context.Context) (*GrafanaInfo, error) {\n\tv, err := c.getOrFillCache(\"grafana\", func() (interface{}, error) {\n\t\treturn c.p.GetGrafana(ctx)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn v.(*GrafanaInfo), nil\n}\n\nfunc (c *CachedTopology) GetAlertManager(ctx context.Context) (*AlertManagerInfo, error) {\n\tv, err := c.getOrFillCache(\"alert_manager\", func() (interface{}, error) {\n\t\treturn c.p.GetAlertManager(ctx)\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn v.(*AlertManagerInfo), nil\n}\n\nfunc (c *CachedTopology) finalize() {\n\t_ = c.cache.Close()\n}\n"
  },
  {
    "path": "util/topo/provider_cached_test.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage topo\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/mock\"\n\t\"github.com/stretchr/testify/require\"\n)\n\nfunc TestCachedTopologyCacheValue(t *testing.T) {\n\ttype mockKey string\n\n\tconst mockKey1 = mockKey(\"useMock1\")\n\tconst mockKey2 = mockKey(\"useMock2\")\n\n\tmp := new(MockTopologyProvider)\n\tmp.\n\t\tOn(\"GetPrometheus\", mock.MatchedBy(func(ctx context.Context) bool {\n\t\t\tctxV := ctx.Value(mockKey1)\n\t\t\treturn ctxV != nil && ctxV.(bool) == true\n\t\t})).\n\t\tReturn(&PrometheusInfo{\n\t\t\tIP:   \"192.168.35.10\",\n\t\t\tPort: 1234,\n\t\t}, nil).\n\t\tOn(\"GetPrometheus\", mock.MatchedBy(func(ctx context.Context) bool {\n\t\t\tctxV := ctx.Value(mockKey2)\n\t\t\treturn ctxV != nil && ctxV.(bool) == true\n\t\t})).\n\t\tReturn(&PrometheusInfo{\n\t\t\tIP:   \"192.168.100.5\",\n\t\t\tPort: 5414,\n\t\t}, nil).\n\t\tOn(\"GetPrometheus\", mock.Anything).\n\t\tReturn((*PrometheusInfo)(nil), fmt.Errorf(\"some error\"))\n\n\tcp := NewCachedTopology(mp, time.Millisecond*500)\n\n\t// Error response should not be cached\n\tv, err := cp.GetPrometheus(context.Background())\n\trequire.Error(t, err)\n\trequire.Equal(t, err.Error(), \"some error\")\n\trequire.Nil(t, v)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 1)\n\n\tv, err = cp.GetPrometheus(context.Background())\n\trequire.Error(t, err)\n\trequire.Equal(t, err.Error(), \"some error\")\n\trequire.Nil(t, v)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 2)\n\n\t// Non error response should be cached\n\tv, err = cp.GetPrometheus(context.WithValue(context.Background(), mockKey1, true))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"192.168.35.10\", v.IP)\n\trequire.Equal(t, uint(1234), v.Port)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 3)\n\n\tv, err = cp.GetPrometheus(context.WithValue(context.Background(), mockKey1, true))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"192.168.35.10\", v.IP)\n\trequire.Equal(t, uint(1234), v.Port)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 3)\n\n\tv, err = cp.GetPrometheus(context.WithValue(context.Background(), mockKey2, true))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"192.168.35.10\", v.IP) // Unchanged since it is cached\n\trequire.Equal(t, uint(1234), v.Port)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 3)\n\n\t// Wait until expired\n\ttime.Sleep(time.Millisecond * 550)\n\tv, err = cp.GetPrometheus(context.WithValue(context.Background(), mockKey2, true))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"192.168.100.5\", v.IP)\n\trequire.Equal(t, uint(5414), v.Port)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 4)\n\n\tv, err = cp.GetPrometheus(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"192.168.100.5\", v.IP)\n\trequire.Equal(t, uint(5414), v.Port)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 4)\n\n\t// Wait until expired\n\ttime.Sleep(time.Millisecond * 550)\n\tv, err = cp.GetPrometheus(context.Background())\n\trequire.Error(t, err)\n\trequire.Equal(t, err.Error(), \"some error\")\n\trequire.Nil(t, v)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 5)\n\n\tv, err = cp.GetPrometheus(context.Background())\n\trequire.Error(t, err)\n\trequire.Equal(t, err.Error(), \"some error\")\n\trequire.Nil(t, v)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 6)\n\n\tv, err = cp.GetPrometheus(context.WithValue(context.Background(), mockKey1, true))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"192.168.35.10\", v.IP)\n\trequire.Equal(t, uint(1234), v.Port)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 7)\n\n\tv, err = cp.GetPrometheus(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"192.168.35.10\", v.IP)\n\trequire.Equal(t, uint(1234), v.Port)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 7)\n\n\t// Read should not extend TTL\n\ttime.Sleep(time.Millisecond * 550)\n\ttBegin := time.Now()\n\tv, err = cp.GetPrometheus(context.WithValue(context.Background(), mockKey1, true))\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"192.168.35.10\", v.IP)\n\trequire.Equal(t, uint(1234), v.Port)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 8)\n\n\ttime.Sleep(time.Millisecond * 400)\n\tv, err = cp.GetPrometheus(context.Background())\n\trequire.NoError(t, err)\n\trequire.Equal(t, \"192.168.35.10\", v.IP)\n\trequire.Equal(t, uint(1234), v.Port)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 8)\n\n\ttime.Sleep(time.Millisecond * 150) // 550ms has passed since first put, so we should expect cache to expire\n\tv, err = cp.GetPrometheus(context.Background())\n\trequire.Error(t, err)\n\trequire.Equal(t, err.Error(), \"some error\")\n\trequire.Nil(t, v)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 9)\n\n\t// Let's see that the expiration is not caused by 400ms+500ms.\n\trequire.True(t, tBegin.Add(time.Millisecond*800).After(time.Now()))\n\n\tmp.AssertExpectations(t)\n}\n\nfunc TestCachedTopologyCacheNil(t *testing.T) {\n\t// No prometheus exists.\n\tmp := new(MockTopologyProvider)\n\tmp.\n\t\tOn(\"GetPrometheus\", mock.Anything).\n\t\tReturn((*PrometheusInfo)(nil), nil)\n\n\tcp := NewCachedTopology(mp, time.Millisecond*500)\n\n\tv, err := cp.GetPrometheus(context.Background())\n\trequire.NoError(t, err)\n\trequire.Nil(t, v)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 1)\n\n\t// Nil (but success) result is cached.\n\tv, err = cp.GetPrometheus(context.Background())\n\trequire.NoError(t, err)\n\trequire.Nil(t, v)\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 1)\n\n\tmp.AssertExpectations(t)\n}\n\nfunc TestCachedTopologyConcurrentGet(t *testing.T) {\n\tmp := new(MockTopologyProvider)\n\tmp.\n\t\tOn(\"GetPrometheus\", mock.Anything).\n\t\tAfter(time.Second).\n\t\tReturn((*PrometheusInfo)(nil), nil)\n\n\tcp := NewCachedTopology(mp, time.Millisecond*500)\n\n\tvar wg sync.WaitGroup\n\tfor range 5 {\n\t\twg.Go(func() {\n\t\t\tv, err := cp.GetPrometheus(context.Background())\n\t\t\trequire.NoError(t, err)\n\t\t\trequire.Nil(t, v)\n\t\t})\n\t}\n\twg.Wait()\n\n\t// There is no singleflight behavior.\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 5)\n\n\tv, err := cp.GetPrometheus(context.Background())\n\trequire.NoError(t, err)\n\trequire.Nil(t, v)\n\n\tmp.AssertNumberOfCalls(t, \"GetPrometheus\", 5)\n\n\tmp.AssertExpectations(t)\n}\n\nfunc TestCachedTopologyAllMethods(t *testing.T) {\n\t// Hopefully we can find cache key is not mixed via this test.\n\tmp := new(MockTopologyProvider)\n\tmp.\n\t\tOn(\"GetPD\", mock.Anything).Return([]PDInfo{{IP: \"addr-pd.internal\"}}, nil).\n\t\tOn(\"GetTiDB\", mock.Anything).Return([]TiDBInfo{{IP: \"addr-tidb-2.internal\"}, {IP: \"addr-tidb-1.internal\"}}, nil).\n\t\tOn(\"GetTiKV\", mock.Anything).Return([]TiKVStoreInfo{{IP: \"addr-tikv-3.internal\"}}, nil).\n\t\tOn(\"GetTiFlash\", mock.Anything).Return([]TiFlashStoreInfo{}, nil).\n\t\tOn(\"GetPrometheus\", mock.Anything).Return(nil, nil).\n\t\tOn(\"GetGrafana\", mock.Anything).Return(&GrafanaInfo{IP: \"addr-grafana.internal\"}, nil).\n\t\tOn(\"GetAlertManager\", mock.Anything).Return(&AlertManagerInfo{IP: \"addr-am-x.internal\"}, nil)\n\n\tcp := NewCachedTopology(mp, time.Millisecond*500)\n\t{\n\t\tv, err := cp.GetPD(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, v)\n\t\trequire.Equal(t, 1, len(v))\n\t\trequire.Equal(t, \"addr-pd.internal\", v[0].IP)\n\t}\n\t{\n\t\tv, err := cp.GetTiDB(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, v)\n\t\trequire.Equal(t, 2, len(v))\n\t\trequire.Equal(t, \"addr-tidb-2.internal\", v[0].IP)\n\t\trequire.Equal(t, \"addr-tidb-1.internal\", v[1].IP)\n\t}\n\t{\n\t\tv, err := cp.GetTiKV(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, v)\n\t\trequire.Equal(t, 1, len(v))\n\t\trequire.Equal(t, \"addr-tikv-3.internal\", v[0].IP)\n\t}\n\t{\n\t\tv, err := cp.GetTiFlash(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, v)\n\t\trequire.Equal(t, 0, len(v))\n\t}\n\t{\n\t\tv, err := cp.GetPrometheus(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.Nil(t, v)\n\t}\n\t{\n\t\tv, err := cp.GetGrafana(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, v)\n\t\trequire.Equal(t, \"addr-grafana.internal\", v.IP)\n\t}\n\t{\n\t\tv, err := cp.GetAlertManager(context.Background())\n\t\trequire.NoError(t, err)\n\t\trequire.NotNil(t, v)\n\t\trequire.Equal(t, \"addr-am-x.internal\", v.IP)\n\t}\n\n\tmp.AssertExpectations(t)\n}\n"
  },
  {
    "path": "util/ziputil/zip_writer.go",
    "content": "// Copyright 2026 PingCAP, Inc. Licensed under Apache-2.0.\n\npackage ziputil\n\nimport (\n\t\"archive/zip\"\n\t\"io\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"time\"\n)\n\n// WriteZipFromFiles compresses `files` using zip and write the zip in a streaming way to the io Writer `w`.\n// The files will be flattened in the zip file, i.e. `/a/b/c.txt` becomes `c.txt`.\n// FIXME: This function does not handle with encrypted files on the disk.\nfunc WriteZipFromFiles(w io.Writer, files []string, compress bool) error {\n\tzw := zip.NewWriter(w)\n\tdefer func() {\n\t\t_ = zw.Close()\n\t}()\n\n\t// TODO: Handle with duplicate file names.\n\tfor _, file := range files {\n\t\terr := writeZipFromFile(zw, file, compress)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc writeZipFromFile(zw *zip.Writer, file string, compress bool) error {\n\tf, err := os.Open(filepath.Clean(file))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() {\n\t\t_ = f.Close()\n\t}()\n\n\tfileInfo, err := f.Stat()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tzipMethod := zip.Store // no compress\n\tif compress {\n\t\tzipMethod = zip.Deflate // compress\n\t}\n\tzipFile, err := zw.CreateHeader(&zip.FileHeader{\n\t\tName:     fileInfo.Name(),\n\t\tMethod:   zipMethod,\n\t\tModified: time.Now(),\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = io.Copy(zipFile, f)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n"
  }
]