From 248305ab942b93e29fcc7bb2d19519d2c7a0f375 Mon Sep 17 00:00:00 2001 From: Vladislav Polyakov Date: Sun, 26 Apr 2026 13:47:01 +0300 Subject: [PATCH 1/6] Add SLO workflow for the Java SDK Add two GitHub Actions workflows that drive ydb-platform/ydb-slo-action against the Java SDK: - slo.yml: builds Docker images for the current PR and the merge-base baseline, then hands them to ydb-slo-action/init@v2 with KV read/write RPS flags. Triggered on PRs with the `SLO` label, on push to master and via workflow_dispatch. - slo-report.yml: waits on the SLO workflow via workflow_run, publishes the comparison report through ydb-slo-action/report@v2, and removes the `SLO` label from the PR. The workload sources live in ydb-platform/ydb-java-examples, in a new `slo` Maven module. The build script in this commit (.github/scripts/build-slo-image.sh) assembles a temporary build context with both checkouts side by side and feeds it to the Dockerfile shipped with the workload, so the Java SDK under test is built from source and pinned into the workload build without ever needing to publish snapshots. The workflow checks out ydb-java-examples at `master` by default; `workflow_dispatch` exposes an `examples_ref` input for testing against unmerged workload changes. --- .github/scripts/build-slo-image.sh | 146 ++++++++++++++++++++++++ .github/workflows/slo-report.yml | 58 ++++++++++ .github/workflows/slo.yml | 172 +++++++++++++++++++++++++++++ 3 files changed, 376 insertions(+) create mode 100755 .github/scripts/build-slo-image.sh create mode 100644 .github/workflows/slo-report.yml create mode 100644 .github/workflows/slo.yml diff --git a/.github/scripts/build-slo-image.sh b/.github/scripts/build-slo-image.sh new file mode 100755 index 000000000..2c487b2e8 --- /dev/null +++ b/.github/scripts/build-slo-image.sh @@ -0,0 +1,146 @@ +#!/usr/bin/env bash +# +# Builds the Docker image for the YDB Java SDK SLO workload. +# +# The script assembles a temporary build context containing two checkouts +# side by side — the SDK source tree and the ydb-java-examples checkout — +# and feeds that context to `docker build` using the Dockerfile shipped +# inside `ydb-java-examples/slo/`. +# +# The Dockerfile takes care of building the SDK from source, installing it +# into an in-image local Maven repository, and then building the workload +# against that exact SDK version. So the script does not need any Maven / +# JDK setup on the host; only `docker` and standard POSIX tools. +# +# If the initial build fails and `--fallback-image` is provided, the script +# tags the fallback image as `--tag` and exits successfully. This mirrors +# the behaviour of the equivalent script in `ydb-go-sdk` and is used by the +# baseline build, where we want to keep going even if the historical commit +# can't be built any more. + +set -euo pipefail + +usage() { + cat >&2 <<'EOF' +Usage: + build-slo-image.sh \ + --sdk \ + --examples \ + --tag \ + [--fallback-image ] + +Options: + --sdk Path to the ydb-java-sdk checkout to build against. + --examples Path to the ydb-java-examples checkout that owns the + SLO workload sources (must contain slo/Dockerfile). + --tag Docker tag to assign to the built image + (e.g. ydb-app-current). + --fallback-image If the initial Docker build fails, tag this image as + --tag and exit successfully. Useful for the baseline + build, which may be unable to compile a historical + SDK commit. +EOF +} + +die() { + echo "ERROR: $*" >&2 + exit 1 +} + +sdk_dir="" +examples_dir="" +tag="" +fallback_image="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --sdk) + sdk_dir="${2:-}" + shift 2 + ;; + --examples) + examples_dir="${2:-}" + shift 2 + ;; + --tag) + tag="${2:-}" + shift 2 + ;; + --fallback-image) + fallback_image="${2:-}" + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + die "Unknown argument: $1 (use --help)" + ;; + esac +done + +if [[ -z "$sdk_dir" || -z "$examples_dir" || -z "$tag" ]]; then + usage + die "Incomplete argument set" +fi + +[[ -d "$sdk_dir" ]] || die "--sdk does not exist: $sdk_dir" +[[ -d "$examples_dir" ]] || die "--examples does not exist: $examples_dir" + +sdk_dir="$(cd "$sdk_dir" && pwd)" +examples_dir="$(cd "$examples_dir" && pwd)" + +dockerfile="${examples_dir}/slo/Dockerfile" +[[ -f "$dockerfile" ]] || die "Dockerfile not found: $dockerfile" + +# Assemble a build context that contains the two checkouts side by side. +# We use hard links where possible to avoid copying large amounts of data; +# `cp -al` falls back to a regular copy when hard links aren't supported +# (e.g. across filesystems on the GitHub runner cache). +context_dir="$(mktemp -d)" +trap 'rm -rf "$context_dir"' EXIT + +echo "Assembling build context in $context_dir" +echo " ydb-java-sdk: $sdk_dir" +echo " ydb-java-examples: $examples_dir" +echo " tag: $tag" + +copy_tree() { + local src="$1" + local dst="$2" + if cp -al "$src" "$dst" 2>/dev/null; then + return 0 + fi + cp -a "$src" "$dst" +} + +copy_tree "$sdk_dir" "$context_dir/ydb-java-sdk" +copy_tree "$examples_dir" "$context_dir/ydb-java-examples" + +# Drop any leftover .git directories from the build context so we don't ship +# them into image layers and don't confuse Maven plugins that probe for git. +find "$context_dir" -maxdepth 3 -type d -name '.git' -prune -exec rm -rf {} + + +set +e +docker build \ + --platform linux/amd64 \ + -t "$tag" \ + -f "$dockerfile" \ + "$context_dir" +exit_code=$? +set -e + +if [[ $exit_code -eq 0 ]]; then + echo "Docker image $tag built successfully" + exit 0 +fi + +echo "Docker build for $tag failed (exit code $exit_code)" >&2 + +if [[ -z "$fallback_image" ]]; then + die "Docker build failed and --fallback-image is not set" +fi + +echo "Falling back to image $fallback_image, tagging as $tag" +docker tag "$fallback_image" "$tag" diff --git a/.github/workflows/slo-report.yml b/.github/workflows/slo-report.yml new file mode 100644 index 000000000..8d16ffb08 --- /dev/null +++ b/.github/workflows/slo-report.yml @@ -0,0 +1,58 @@ +name: slo-report + +on: + workflow_run: + workflows: ["SLO"] + types: + - completed + +jobs: + publish-slo-report: + runs-on: ubuntu-latest + name: Publish YDB SLO Report + permissions: + checks: write + contents: read + pull-requests: write + if: github.event.workflow_run.conclusion == 'success' + steps: + - name: Publish YDB SLO Report + uses: ydb-platform/ydb-slo-action/report@v2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + github_run_id: ${{ github.event.workflow_run.id }} + + remove-slo-label: + needs: publish-slo-report + if: always() && github.event.workflow_run.event == 'pull_request' + runs-on: ubuntu-latest + name: Remove SLO Label + permissions: + pull-requests: write + steps: + - name: Remove SLO label from PR + uses: actions/github-script@v7 + with: + script: | + const pullRequests = context.payload.workflow_run.pull_requests; + if (!pullRequests || pullRequests.length === 0) { + console.log('No pull requests associated with this workflow run'); + return; + } + for (const pr of pullRequests) { + try { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + name: 'SLO' + }); + console.log(`Removed SLO label from PR #${pr.number}`); + } catch (error) { + if (error.status === 404) { + console.log(`SLO label not found on PR #${pr.number}, skipping`); + } else { + throw error; + } + } + } diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml new file mode 100644 index 000000000..475b329ac --- /dev/null +++ b/.github/workflows/slo.yml @@ -0,0 +1,172 @@ +name: SLO + +on: + push: + branches: + - master + - develop + + pull_request: + types: [opened, reopened, synchronize, labeled] + branches: + - master + - develop + + workflow_dispatch: + inputs: + github_issue: + description: "GitHub issue number where the SLO results will be reported" + required: true + baseline_ref: + description: "Baseline commit/branch/tag to compare against (leave empty to auto-detect merge-base with master)" + required: false + slo_workload_duration_seconds: + description: "Duration of the SLO workload in seconds" + required: false + default: "600" + slo_workload_read_max_rps: + description: "Maximum read RPS for the SLO workload" + required: false + default: "1000" + slo_workload_write_max_rps: + description: "Maximum write RPS for the SLO workload" + required: false + default: "100" + examples_ref: + description: "Branch/tag/SHA of ydb-java-examples to use for the workload sources" + required: false + default: "master" + +jobs: + ydb-slo-action: + if: contains(github.event.pull_request.labels.*.name, 'SLO') || github.event_name == 'workflow_dispatch' || github.event_name == 'push' + + name: Run YDB SLO Tests + runs-on: ubuntu-latest + + permissions: + contents: read + + strategy: + fail-fast: false + matrix: + sdk: + - name: java-query-kv + command: "" + + concurrency: + group: slo-${{ github.ref }}-${{ matrix.sdk.name }} + cancel-in-progress: true + + steps: + - name: Install dependencies + run: | + set -euxo pipefail + YQ_VERSION=v4.48.2 + BUILDX_VERSION=0.30.1 + COMPOSE_VERSION=2.40.3 + + sudo curl -L "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" \ + -o /usr/local/bin/yq + sudo chmod +x /usr/local/bin/yq + + sudo mkdir -p /usr/local/lib/docker/cli-plugins + + sudo curl -fLo /usr/local/lib/docker/cli-plugins/docker-buildx \ + "https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION}/buildx-v${BUILDX_VERSION}.linux-amd64" + sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-buildx + + sudo curl -fLo /usr/local/lib/docker/cli-plugins/docker-compose \ + "https://github.com/docker/compose/releases/download/v${COMPOSE_VERSION}/docker-compose-linux-x86_64" + sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose + + yq --version + docker --version + docker buildx version + docker compose version + + - name: Checkout current SDK version + uses: actions/checkout@v5 + with: + path: sdk-current + fetch-depth: 0 + + - name: Determine baseline commit + id: baseline + working-directory: sdk-current + run: | + set -euo pipefail + if [[ -n "${{ inputs.baseline_ref }}" ]]; then + BASELINE="${{ inputs.baseline_ref }}" + else + BASELINE=$(git merge-base HEAD origin/master) + fi + echo "sha=${BASELINE}" >> "$GITHUB_OUTPUT" + + if git merge-base --is-ancestor "${BASELINE}" origin/master && \ + [ "$(git rev-parse origin/master)" = "${BASELINE}" ]; then + BASELINE_REF="master" + else + BRANCH=$(git branch -r --contains "${BASELINE}" | grep -v HEAD | head -1 | sed 's|.*/||' || echo "") + if [ -n "${BRANCH}" ]; then + BASELINE_REF="${BRANCH}@${BASELINE:0:7}" + else + BASELINE_REF="${BASELINE:0:7}" + fi + fi + echo "ref=${BASELINE_REF}" >> "$GITHUB_OUTPUT" + + - name: Checkout baseline SDK version + uses: actions/checkout@v5 + with: + ref: ${{ steps.baseline.outputs.sha }} + path: sdk-baseline + fetch-depth: 1 + + - name: Checkout ydb-java-examples + uses: actions/checkout@v5 + with: + repository: ydb-platform/ydb-java-examples + ref: ${{ inputs.examples_ref || 'master' }} + path: examples + + - name: Build current workload image + run: | + set -euxo pipefail + chmod +x sdk-current/.github/scripts/build-slo-image.sh + sdk-current/.github/scripts/build-slo-image.sh \ + --sdk "${GITHUB_WORKSPACE}/sdk-current" \ + --examples "${GITHUB_WORKSPACE}/examples" \ + --tag ydb-app-current + + - name: Build baseline workload image + run: | + set -euxo pipefail + # Reuse the build script from the current checkout — it doesn't + # depend on SDK contents, only on the layout it produces. + sdk-current/.github/scripts/build-slo-image.sh \ + --sdk "${GITHUB_WORKSPACE}/sdk-baseline" \ + --examples "${GITHUB_WORKSPACE}/examples" \ + --tag ydb-app-baseline \ + --fallback-image ydb-app-current + + - name: Run SLO Tests + uses: ydb-platform/ydb-slo-action/init@v2 + timeout-minutes: 30 + with: + github_issue: ${{ github.event.inputs.github_issue }} + github_token: ${{ secrets.GITHUB_TOKEN }} + workload_name: ${{ matrix.sdk.name }} + workload_duration: ${{ inputs.slo_workload_duration_seconds || '600' }} + workload_current_ref: ${{ github.head_ref || github.ref_name }} + workload_current_image: ydb-app-current + workload_current_command: >- + ${{ matrix.sdk.command }} + --read-rps ${{ inputs.slo_workload_read_max_rps || '1000' }} + --write-rps ${{ inputs.slo_workload_write_max_rps || '100' }} + workload_baseline_ref: ${{ steps.baseline.outputs.ref }} + workload_baseline_image: ydb-app-baseline + workload_baseline_command: >- + ${{ matrix.sdk.command }} + --read-rps ${{ inputs.slo_workload_read_max_rps || '1000' }} + --write-rps ${{ inputs.slo_workload_write_max_rps || '100' }} From bb24819a6e62cfff69b329e77f5fd6e0b119a07a Mon Sep 17 00:00:00 2001 From: Vladislav Polyakov Date: Mon, 4 May 2026 16:52:15 +0300 Subject: [PATCH 2/6] Remove separate SLO report workflow and integrate into main SLO workflow The report publishing and SLO label removal are now handled directly in the SLO workflow after tests complete. The workflow trigger is simplified to only run on labeled pull requests or manual dispatch. --- .github/workflows/slo-report.yml | 58 -------------------------------- .github/workflows/slo.yml | 39 ++++++++++++--------- 2 files changed, 24 insertions(+), 73 deletions(-) delete mode 100644 .github/workflows/slo-report.yml diff --git a/.github/workflows/slo-report.yml b/.github/workflows/slo-report.yml deleted file mode 100644 index 8d16ffb08..000000000 --- a/.github/workflows/slo-report.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: slo-report - -on: - workflow_run: - workflows: ["SLO"] - types: - - completed - -jobs: - publish-slo-report: - runs-on: ubuntu-latest - name: Publish YDB SLO Report - permissions: - checks: write - contents: read - pull-requests: write - if: github.event.workflow_run.conclusion == 'success' - steps: - - name: Publish YDB SLO Report - uses: ydb-platform/ydb-slo-action/report@v2 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - github_run_id: ${{ github.event.workflow_run.id }} - - remove-slo-label: - needs: publish-slo-report - if: always() && github.event.workflow_run.event == 'pull_request' - runs-on: ubuntu-latest - name: Remove SLO Label - permissions: - pull-requests: write - steps: - - name: Remove SLO label from PR - uses: actions/github-script@v7 - with: - script: | - const pullRequests = context.payload.workflow_run.pull_requests; - if (!pullRequests || pullRequests.length === 0) { - console.log('No pull requests associated with this workflow run'); - return; - } - for (const pr of pullRequests) { - try { - await github.rest.issues.removeLabel({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - name: 'SLO' - }); - console.log(`Removed SLO label from PR #${pr.number}`); - } catch (error) { - if (error.status === 404) { - console.log(`SLO label not found on PR #${pr.number}, skipping`); - } else { - throw error; - } - } - } diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index 475b329ac..bc30c1160 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -1,16 +1,8 @@ name: SLO on: - push: - branches: - - master - - develop - pull_request: - types: [opened, reopened, synchronize, labeled] - branches: - - master - - develop + types: [labeled] workflow_dispatch: inputs: @@ -39,7 +31,9 @@ on: jobs: ydb-slo-action: - if: contains(github.event.pull_request.labels.*.name, 'SLO') || github.event_name == 'workflow_dispatch' || github.event_name == 'push' + if: >- + (github.event_name == 'pull_request' && github.event.label.name == 'SLO') + || github.event_name == 'workflow_dispatch' name: Run YDB SLO Tests runs-on: ubuntu-latest @@ -66,8 +60,8 @@ jobs: BUILDX_VERSION=0.30.1 COMPOSE_VERSION=2.40.3 - sudo curl -L "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" \ - -o /usr/local/bin/yq + sudo curl -fLo /usr/local/bin/yq \ + "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" sudo chmod +x /usr/local/bin/yq sudo mkdir -p /usr/local/lib/docker/cli-plugins @@ -94,10 +88,12 @@ jobs: - name: Determine baseline commit id: baseline working-directory: sdk-current + env: + INPUT_BASELINE_REF: ${{ inputs.baseline_ref }} run: | set -euo pipefail - if [[ -n "${{ inputs.baseline_ref }}" ]]; then - BASELINE="${{ inputs.baseline_ref }}" + if [[ -n "${INPUT_BASELINE_REF}" ]]; then + BASELINE="${INPUT_BASELINE_REF}" else BASELINE=$(git merge-base HEAD origin/master) fi @@ -154,7 +150,7 @@ jobs: uses: ydb-platform/ydb-slo-action/init@v2 timeout-minutes: 30 with: - github_issue: ${{ github.event.inputs.github_issue }} + github_issue: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.github_issue || github.event.pull_request.number }} github_token: ${{ secrets.GITHUB_TOKEN }} workload_name: ${{ matrix.sdk.name }} workload_duration: ${{ inputs.slo_workload_duration_seconds || '600' }} @@ -170,3 +166,16 @@ jobs: ${{ matrix.sdk.command }} --read-rps ${{ inputs.slo_workload_read_max_rps || '1000' }} --write-rps ${{ inputs.slo_workload_write_max_rps || '100' }} + + - name: Publish SLO report + if: always() + uses: ydb-platform/ydb-slo-action/report@v2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + github_run_id: ${{ github.run_id }} + + - name: Remove SLO label + if: always() && github.event_name == 'pull_request' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh pr edit "${{ github.event.pull_request.number }}" --remove-label SLO --repo "$GITHUB_REPOSITORY" From 79adf6440f5146ed24e2b13619ef3f4e8573a196 Mon Sep 17 00:00:00 2001 From: Vladislav Polyakov Date: Mon, 4 May 2026 17:54:00 +0300 Subject: [PATCH 3/6] Simplify SLO workflow to run on pull request events Remove manual workflow_dispatch trigger and associated inputs. Trigger on pull request open, reopen, synchronize, and label events. Use a larger runner for performance tests. --- .github/workflows/slo.yml | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index bc30c1160..1469e8f44 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -2,41 +2,14 @@ name: SLO on: pull_request: - types: [labeled] - - workflow_dispatch: - inputs: - github_issue: - description: "GitHub issue number where the SLO results will be reported" - required: true - baseline_ref: - description: "Baseline commit/branch/tag to compare against (leave empty to auto-detect merge-base with master)" - required: false - slo_workload_duration_seconds: - description: "Duration of the SLO workload in seconds" - required: false - default: "600" - slo_workload_read_max_rps: - description: "Maximum read RPS for the SLO workload" - required: false - default: "1000" - slo_workload_write_max_rps: - description: "Maximum write RPS for the SLO workload" - required: false - default: "100" - examples_ref: - description: "Branch/tag/SHA of ydb-java-examples to use for the workload sources" - required: false - default: "master" + types: [opened, reopened, synchronize, labeled] jobs: ydb-slo-action: - if: >- - (github.event_name == 'pull_request' && github.event.label.name == 'SLO') - || github.event_name == 'workflow_dispatch' + if: contains(github.event.pull_request.labels.*.name, 'SLO') name: Run YDB SLO Tests - runs-on: ubuntu-latest + runs-on: large-runner-java-sdk permissions: contents: read From 5ba7b4f43fc68630724ec102e77ef09abd955d59 Mon Sep 17 00:00:00 2001 From: Vladislav Polyakov Date: Mon, 4 May 2026 18:09:36 +0300 Subject: [PATCH 4/6] Fix SLO build context copy and replace gh with curl on self-hosted runner copy_tree fell back to `cp -a src dst` after a partial `cp -al`, nesting the SDK inside itself and leaving /src/ydb-java-sdk without a pom.xml at build time. Copy contents explicitly via `src/. dst/` and verify the expected layout before invoking docker build. The Remove SLO label step used `gh`, which is not installed on the self-hosted runner; call the REST API directly with curl instead. --- .github/scripts/build-slo-image.sh | 28 +++++++++++++++++---- .github/workflows/slo.yml | 40 +++++++++++++++--------------- 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/.github/scripts/build-slo-image.sh b/.github/scripts/build-slo-image.sh index 2c487b2e8..cd2972b0e 100755 --- a/.github/scripts/build-slo-image.sh +++ b/.github/scripts/build-slo-image.sh @@ -109,18 +109,36 @@ echo " tag: $tag" copy_tree() { local src="$1" local dst="$2" - if cp -al "$src" "$dst" 2>/dev/null; then + mkdir -p "$dst" + # The "/." suffix on src and "/" on dst asks cp to copy CONTENTS of src + # into dst, regardless of whether dst pre-existed. Without this, partial + # hardlink failures can leave dst partially populated and the fallback + # then nests src inside dst (dst/src/...) instead of overwriting. + if cp -al "$src"/. "$dst"/ 2>/dev/null; then return 0 fi - cp -a "$src" "$dst" + cp -a "$src"/. "$dst"/ } copy_tree "$sdk_dir" "$context_dir/ydb-java-sdk" copy_tree "$examples_dir" "$context_dir/ydb-java-examples" -# Drop any leftover .git directories from the build context so we don't ship -# them into image layers and don't confuse Maven plugins that probe for git. -find "$context_dir" -maxdepth 3 -type d -name '.git' -prune -exec rm -rf {} + +# Drop .git from each copied tree so it doesn't ship into image layers or +# confuse Maven plugins that probe for git metadata. +rm -rf "$context_dir/ydb-java-sdk/.git" "$context_dir/ydb-java-examples/.git" + +# Fail fast with a clear message if the layout is wrong (e.g. because of a +# silent copy failure on the runner). +for required in \ + "$context_dir/ydb-java-sdk/pom.xml" \ + "$context_dir/ydb-java-examples/slo/Dockerfile" +do + [[ -f "$required" ]] || die "Build context missing required file: $required" +done + +echo "Build context layout:" +ls -la "$context_dir" +echo " ydb-java-sdk: $(ls -1 "$context_dir/ydb-java-sdk" | wc -l) entries" set +e docker build \ diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index 1469e8f44..c4724cb8d 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -61,15 +61,9 @@ jobs: - name: Determine baseline commit id: baseline working-directory: sdk-current - env: - INPUT_BASELINE_REF: ${{ inputs.baseline_ref }} run: | set -euo pipefail - if [[ -n "${INPUT_BASELINE_REF}" ]]; then - BASELINE="${INPUT_BASELINE_REF}" - else - BASELINE=$(git merge-base HEAD origin/master) - fi + BASELINE=$(git merge-base HEAD origin/master) echo "sha=${BASELINE}" >> "$GITHUB_OUTPUT" if git merge-base --is-ancestor "${BASELINE}" origin/master && \ @@ -96,7 +90,7 @@ jobs: uses: actions/checkout@v5 with: repository: ydb-platform/ydb-java-examples - ref: ${{ inputs.examples_ref || 'master' }} + ref: master path: examples - name: Build current workload image @@ -123,22 +117,16 @@ jobs: uses: ydb-platform/ydb-slo-action/init@v2 timeout-minutes: 30 with: - github_issue: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.github_issue || github.event.pull_request.number }} + github_issue: ${{ github.event.pull_request.number }} github_token: ${{ secrets.GITHUB_TOKEN }} workload_name: ${{ matrix.sdk.name }} - workload_duration: ${{ inputs.slo_workload_duration_seconds || '600' }} + workload_duration: "600" workload_current_ref: ${{ github.head_ref || github.ref_name }} workload_current_image: ydb-app-current - workload_current_command: >- - ${{ matrix.sdk.command }} - --read-rps ${{ inputs.slo_workload_read_max_rps || '1000' }} - --write-rps ${{ inputs.slo_workload_write_max_rps || '100' }} + workload_current_command: ${{ matrix.sdk.command }} --read-rps 1000 --write-rps 100 workload_baseline_ref: ${{ steps.baseline.outputs.ref }} workload_baseline_image: ydb-app-baseline - workload_baseline_command: >- - ${{ matrix.sdk.command }} - --read-rps ${{ inputs.slo_workload_read_max_rps || '1000' }} - --write-rps ${{ inputs.slo_workload_write_max_rps || '100' }} + workload_baseline_command: ${{ matrix.sdk.command }} --read-rps 1000 --write-rps 100 - name: Publish SLO report if: always() @@ -150,5 +138,17 @@ jobs: - name: Remove SLO label if: always() && github.event_name == 'pull_request' env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: gh pr edit "${{ github.event.pull_request.number }}" --remove-label SLO --repo "$GITHUB_REPOSITORY" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + set -euo pipefail + STATUS=$(curl -sS -X DELETE \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/labels/SLO" \ + -o /dev/null -w "%{http_code}") + case "${STATUS}" in + 200|404) echo "SLO label remove: HTTP ${STATUS}" ;; + *) echo "Failed to remove SLO label: HTTP ${STATUS}" >&2; exit 1 ;; + esac From 4cab7f8865ce6be476f971c029c4cdede58a7328 Mon Sep 17 00:00:00 2001 From: Vladislav Polyakov Date: Tue, 5 May 2026 09:00:11 +0300 Subject: [PATCH 5/6] Move SLO report and label removal to workflow_run MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit init@v2 uploads its workload artifacts in a post-hook, so an inline report@v2 step in the same job runs before the upload and sees zero artifacts. Move report publication into a separate workflow triggered on workflow_run: completed so it runs after init's post-hook has finished. Pair the label removal with it. Trigger the removal only on success or failure conclusions — cancelled and skipped runs leave the label in place so a concurrent re-run can proceed with the label still set. Look up the PR by head SHA when workflow_run.pull_requests is empty (fork PRs). --- .github/workflows/slo-report.yml | 66 ++++++++++++++++++++++++++++++++ .github/workflows/slo.yml | 25 ------------ 2 files changed, 66 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/slo-report.yml diff --git a/.github/workflows/slo-report.yml b/.github/workflows/slo-report.yml new file mode 100644 index 000000000..1aceaeb72 --- /dev/null +++ b/.github/workflows/slo-report.yml @@ -0,0 +1,66 @@ +name: slo-report + +on: + workflow_run: + workflows: ["SLO"] + types: + - completed + +jobs: + publish-slo-report: + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + name: Publish YDB SLO Report + permissions: + checks: write + contents: read + pull-requests: write + steps: + - name: Publish YDB SLO Report + uses: ydb-platform/ydb-slo-action/report@v2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + github_run_id: ${{ github.event.workflow_run.id }} + + remove-slo-label: + if: >- + github.event.workflow_run.event == 'pull_request' + && (github.event.workflow_run.conclusion == 'success' + || github.event.workflow_run.conclusion == 'failure') + runs-on: ubuntu-latest + name: Remove SLO Label + permissions: + pull-requests: write + steps: + - name: Remove SLO label from PR + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PRS: ${{ toJSON(github.event.workflow_run.pull_requests) }} + REPO: ${{ github.event.workflow_run.repository.full_name }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + run: | + set -euo pipefail + PR=$(jq -r '.[0].number // empty' <<<"$PRS") + if [[ -z "$PR" ]]; then + # Forks: workflow_run.pull_requests is empty. Look up by head SHA. + PR=$(curl -sS \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/${REPO}/commits/${HEAD_SHA}/pulls" \ + | jq -r '.[0].number // empty') + fi + if [[ -z "$PR" ]]; then + echo "No PR found for head SHA ${HEAD_SHA} — skipping label removal" + exit 0 + fi + STATUS=$(curl -sS -X DELETE \ + -H "Authorization: Bearer ${GITHUB_TOKEN}" \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/${REPO}/issues/${PR}/labels/SLO" \ + -o /dev/null -w "%{http_code}") + case "${STATUS}" in + 200|404) echo "SLO label remove on PR #${PR}: HTTP ${STATUS}" ;; + *) echo "Failed to remove SLO label: HTTP ${STATUS}" >&2; exit 1 ;; + esac diff --git a/.github/workflows/slo.yml b/.github/workflows/slo.yml index c4724cb8d..f8d301069 100644 --- a/.github/workflows/slo.yml +++ b/.github/workflows/slo.yml @@ -127,28 +127,3 @@ jobs: workload_baseline_ref: ${{ steps.baseline.outputs.ref }} workload_baseline_image: ydb-app-baseline workload_baseline_command: ${{ matrix.sdk.command }} --read-rps 1000 --write-rps 100 - - - name: Publish SLO report - if: always() - uses: ydb-platform/ydb-slo-action/report@v2 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - github_run_id: ${{ github.run_id }} - - - name: Remove SLO label - if: always() && github.event_name == 'pull_request' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PR_NUMBER: ${{ github.event.pull_request.number }} - run: | - set -euo pipefail - STATUS=$(curl -sS -X DELETE \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/labels/SLO" \ - -o /dev/null -w "%{http_code}") - case "${STATUS}" in - 200|404) echo "SLO label remove: HTTP ${STATUS}" ;; - *) echo "Failed to remove SLO label: HTTP ${STATUS}" >&2; exit 1 ;; - esac From 99bdb11e86e13bfa9a677400d57985fc04f81230 Mon Sep 17 00:00:00 2001 From: Vladislav Polyakov Date: Tue, 5 May 2026 09:03:04 +0300 Subject: [PATCH 6/6] Simplify slo-report: always() removal and direct gh usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the label on any SLO run completion — success, failure, cancelled, or skipped — so the PR never gets stuck with the label. Drop the fork fallback that looked up the PR by head SHA; same-repo PRs are the only supported path. Use gh directly instead of curl since slo-report runs on the shared ubuntu-latest runner where gh is preinstalled. --- .github/workflows/slo-report.yml | 34 ++++---------------------------- 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/.github/workflows/slo-report.yml b/.github/workflows/slo-report.yml index 1aceaeb72..cd02c703f 100644 --- a/.github/workflows/slo-report.yml +++ b/.github/workflows/slo-report.yml @@ -23,10 +23,7 @@ jobs: github_run_id: ${{ github.event.workflow_run.id }} remove-slo-label: - if: >- - github.event.workflow_run.event == 'pull_request' - && (github.event.workflow_run.conclusion == 'success' - || github.event.workflow_run.conclusion == 'failure') + if: github.event.workflow_run.event == 'pull_request' runs-on: ubuntu-latest name: Remove SLO Label permissions: @@ -34,33 +31,10 @@ jobs: steps: - name: Remove SLO label from PR env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PRS: ${{ toJSON(github.event.workflow_run.pull_requests) }} REPO: ${{ github.event.workflow_run.repository.full_name }} - HEAD_SHA: ${{ github.event.workflow_run.head_sha }} run: | set -euo pipefail - PR=$(jq -r '.[0].number // empty' <<<"$PRS") - if [[ -z "$PR" ]]; then - # Forks: workflow_run.pull_requests is empty. Look up by head SHA. - PR=$(curl -sS \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/${REPO}/commits/${HEAD_SHA}/pulls" \ - | jq -r '.[0].number // empty') - fi - if [[ -z "$PR" ]]; then - echo "No PR found for head SHA ${HEAD_SHA} — skipping label removal" - exit 0 - fi - STATUS=$(curl -sS -X DELETE \ - -H "Authorization: Bearer ${GITHUB_TOKEN}" \ - -H "Accept: application/vnd.github+json" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - "https://api.github.com/repos/${REPO}/issues/${PR}/labels/SLO" \ - -o /dev/null -w "%{http_code}") - case "${STATUS}" in - 200|404) echo "SLO label remove on PR #${PR}: HTTP ${STATUS}" ;; - *) echo "Failed to remove SLO label: HTTP ${STATUS}" >&2; exit 1 ;; - esac + PR=$(jq -r '.[0].number' <<<"$PRS") + gh pr edit "$PR" --repo "$REPO" --remove-label SLO