mirror of
https://github.com/GreptimeTeam/greptimedb.git
synced 2026-10-03 10:35:35 +00:00
* fix(ci): teach check-builder-rust-version.sh to handle stable channels
The script extracted a YYYY-MM-DD date from rust-toolchain.toml to
compare against the rustc build date inside the dev-builder image —
a nightly-era design. With channel = "1.96.1" there is no date in
the file, so every release build failed with 'Error: No rust toolchain
version found in rust-toolchain.toml'.
Extract the channel token instead and branch on it:
- stable channel (X.Y[.Z]): require the builder image's rustc to
exactly match the pinned version
- nightly-YYYY-MM-DD: keep the legacy date-difference check
Verified against a mocked docker for all four paths (stable match /
mismatch, nightly fresh / stale).
Part of #9289.
Signed-off-by: Ning Sun <sunning@greptime.com>
* chore(toolchain): finish stable-migration cleanup in docs and query-regression pin
- README/AGENTS: the toolchain is now stable Rust pinned by
rust-toolchain.toml, not nightly
- query-regression: align the benchmark toolchain pin with the
workspace (nightly-2026-03-21 = 1.96.0-nightly -> stable 1.96.1),
including the exact-version assertions (cargo 356927216, rustc
31fca3adb, both 2026-06-26) and the runner image default
The query-regression runner image must be rebuilt and
QUERY_REGRESSION_ECS_IMAGE_ID bumped together with these pins
(per .github/runner-scale-sets/query-regression/README.md) before
the next regression run.
Part of #9289.
Signed-off-by: Ning Sun <sunning@greptime.com>
* ci(query-regression): derive the Rust toolchain pin from rust-toolchain.toml
Replace the hard-coded RUSTUP_TOOLCHAIN value and the hard-coded
version strings in the runner Verify assertions with a pin resolved
from rust-toolchain.toml:
- the always-running test-tooling job exports the channel parsed from
rust-toolchain.toml as a job output
- query-regression sets RUSTUP_TOOLCHAIN from that output
- the Verify step escapes the pin into the cargo/rustc/active-toolchain
regexes at runtime; the exact commit hash and date are asserted
generically since a stable version identifies the release
Removing the redundant require_eq (workflow yaml vs runner env) since
both now flow from the single source of truth. When rust-toolchain.toml
is bumped, the run fails with a clear signal until the runner image is
rebuilt with the new toolchain, keeping the existing lockstep contract.
Part of #9289.
Signed-off-by: Ning Sun <sunning@greptime.com>
* ci(query-regression): derive the runner image toolchain from rust-toolchain.toml
Remove the hard-coded 'ARG RUST_TOOLCHAIN=1.96.1' from the
query-regression runner Dockerfile. The pin is now parsed from a
COPY'd rust-toolchain.toml at build time (the bootstrap script builds
with the repo root as context, so the file is in the build context):
- rustup-init installs the parsed channel as the default toolchain
- the baked ENV RUSTUP_TOOLCHAIN is dropped: the rustup default makes
bare cargo/rustc resolve correctly without it, and the workflow
supplies RUSTUP_TOOLCHAIN explicitly at run time
- the build-time self-verification asserts the active toolchain
against the same parsed pin
With this, rust-toolchain.toml is the single source of truth for the
benchmark toolchain end to end: the image bakes whatever the toml says
at build time and the workflow asserts against the toml at run time.
A toolchain bump now only requires rebuilding the image.
The changed mechanics were verified natively with the real rustup-init
1.29.0 and the real 1.96.1 toolchain (registry pulls are unavailable
in this sandbox): parsing, default-toolchain installation without
RUSTUP_TOOLCHAIN env, bare cargo/rustc resolution, and the
active-toolchain assertion for both '(default)' and '(overridden)'
output forms.
Part of #9289.
Signed-off-by: Ning Sun <sunning@greptime.com>
* ci(query-regression): rebuild the runner image automatically on toolchain changes
Mirror the dev-builder automation for the query-regression ECS runner
image: a new rebuild-query-regression-runner-image.yaml workflow runs
whenever rust-toolchain.toml or the query-regression runner directory
changes on main (or via manual dispatch). It drives the existing
build-ecs-image.py ops tool, then completes the documented lockstep
updates in order: bump RUNNER_IMAGE_EPOCH in query-regression.yml and
push the commit to main, and only then point the
QUERY_REGRESSION_ECS_IMAGE_ID repo variable at the new image, so the
next regression run picks up image and epoch together.
Also fix build-ecs-image.py to stage rust-toolchain.toml into the
temporary docker build context: the AMI path builds the embedded
Dockerfile from an empty /tmp/image-context, which would break on the
Dockerfile's COPY of rust-toolchain.toml introduced earlier. The
user-data now base64-stages the toml next to the Dockerfile before
docker build.
Verified: py_compile, render_user_data round-trip (mkdir -> stage ->
docker build ordering), and the RUNNER_IMAGE_EPOCH bump sed against
the real workflow file. Requires a new ALIYUN_ECS_BASE_IMAGE_ID repo
variable (Ubuntu 24.04 public image id in the region); all other
secrets/vars are shared with the provisioning job.
Part of #9289.
Signed-off-by: Ning Sun <sunning@greptime.com>
* ci: fold the query-regression runner rebuild into release-dev-builder-images.yaml
Merge the standalone rebuild workflow into the existing builder-image
release workflow, as one entry point for all builder artifacts:
- push paths extended with .github/runner-scale-sets/query-regression/**
- new 'release_query_regression_runner_image' dispatch input
- a 'changes' job diffs the pushed range (github.event.before..sha,
with an everything-changed fallback for dispatch or unknown bases)
so each expensive rebuild only fires for its own paths:
rust-toolchain.toml gates both, docker/dev-builder/** gates the
dev-builder images, the query-regression runner directory gates the
ECS image rebuild
- the rebuild job itself is unchanged from the standalone workflow
(build-ecs-image.py, then RUNNER_IMAGE_EPOCH commit to main, then
the QUERY_REGRESSION_ECS_IMAGE_ID variable update)
The dev-builder jobs, their ECR/CN/tag-update dependents, and the
runner rebuild now share one workflow; the changes filter preserves
the previous on-push behavior for the dev-builder images while the
runner rebuild keeps its own trigger. The epoch-bump commit only
touches query-regression.yml, which is outside the trigger paths, so
no re-trigger loop.
Part of #9289.
Signed-off-by: Ning Sun <sunning@greptime.com>
* fix(ci): auto-resolve the ECS base image for the runner rebuild
The automated rebuild failed with 'Missing required configuration:
--base-image-id' because the ALIYUN_ECS_BASE_IMAGE_ID repo variable
does not exist yet (it was flagged as a one-time setup item).
Remove the setup dependency instead: build-ecs-image.py now defaults
--base-image-id to the latest public Ubuntu 24.04 x86_64 system image
in the region (DescribeImages with image_owner_alias=system), so no
manual variable is required. The runner Dockerfile pins every tool
version itself, so base-image drift is low-risk; --base-image-id or
the ALIYUN_ECS_BASE_IMAGE_ID variable still pin a specific base image
deterministically, and the workflow only passes the flag when the
variable is set.
Part of #9289.
Signed-off-by: Ning Sun <sunning@greptime.com>
* docs(query-regression): clarify what an image rebuild requires
A routine runner-image rebuild needs no manual file updates: the
rebuild job updates QUERY_REGRESSION_ECS_IMAGE_ID and
RUNNER_IMAGE_EPOCH; the toolchain derives from rust-toolchain.toml;
uv, sccache, otelgen, rustup, and the runner base are pinned by
digest/sha/commit in the Dockerfile. Only an apt package revision
bump (mold, protoc, python3) between rebuilds requires bumping the
corresponding Verify pins, and that failure is loud with the observed
version.
Part of #9289.
Signed-off-by: Ning Sun <sunning@greptime.com>
* fix(ci): correct SDK field names in the base-image resolver
DescribeImagesRequest takes 'ostype' (not 'os_type') and the image
items expose 'osname'/'osname_en' (not 'os_name') in the pinned
alibabacloud_ecs20140526 SDK range, so the auto-resolution added in
5a9fd2c769 crashed with a TypeError before describing anything.
Fix the request fields, move the architecture filter server-side, and
paginate (page_size=100 until a short page) instead of relying on a
single default-sized response. Match Ubuntu 24.04 on the localized
osname or the English osname_en.
Verified against the real SDK models (uv run --with
'alibabacloud_ecs20140526>=4.1.0,<6'): a two-page fake client picks
the newest Ubuntu 24.04 via osname_en and rejects 22.04/Windows
decoys.
Part of #9289.
Signed-off-by: Ning Sun <sunning@greptime.com>
* fix(ci): correct the repo-root path in build-ecs-image.py
ASSETS_DIR.parent.parent lands on runner-scale-sets, not the repo
root -- the toml lookup failed with FileNotFoundError. The root is
four levels above ecs-image; express it as an explicit REPO_ROOT
constant (ASSETS_DIR.parents[3]).
Verified every path main() reads against the real checkout layout
(Dockerfile, rust-toolchain.toml, start-runner.sh, the systemd unit,
plus REPO_ROOT sanity against Cargo.toml/.git), re-checked the
user-data toml staging round-trip, and re-ran the base-image
resolver regression test.
Part of #9289.
Signed-off-by: Ning Sun <sunning@greptime.com>
---------
Signed-off-by: Ning Sun <sunning@greptime.com>
920 lines
40 KiB
YAML
920 lines
40 KiB
YAML
name: Query Regression
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
case:
|
|
description: Query perf case path(s) in candidate checkout, all, or heavy
|
|
required: false
|
|
type: string
|
|
default: all
|
|
allow_large_fixture:
|
|
description: Pass --allow-large-fixture to runner
|
|
required: false
|
|
type: boolean
|
|
default: true
|
|
http_timeout:
|
|
description: Runner HTTP timeout seconds
|
|
required: false
|
|
type: string
|
|
default: "300"
|
|
base_ref:
|
|
description: Base ref/sha to build
|
|
required: true
|
|
type: string
|
|
candidate_ref:
|
|
description: Candidate ref/sha to build; reusable release calls must provide an immutable full SHA
|
|
required: true
|
|
type: string
|
|
cargo_profile:
|
|
description: Cargo profile to build base and candidate binaries with
|
|
required: false
|
|
type: string
|
|
default: nightly
|
|
runner:
|
|
description: Self-hosted runner label; aliyun-ecs provisions a fresh ECS instance per run
|
|
required: false
|
|
type: string
|
|
default: aliyun-ecs
|
|
pr_number:
|
|
description: >-
|
|
When set, treat this reusable call as PR admission (merge-SHA
|
|
verification, trusted helper scripts, sticky comment artifact)
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
head_sha:
|
|
description: PR head SHA snapshotted at admission (required with pr_number)
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
head_repo:
|
|
description: PR head repository full_name (required with pr_number)
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
base_repo:
|
|
description: PR base repository full_name (required with pr_number)
|
|
required: false
|
|
type: string
|
|
default: ""
|
|
workflow_dispatch:
|
|
inputs:
|
|
case:
|
|
description: Query perf case path(s) in candidate checkout, all, or heavy
|
|
required: true
|
|
default: all
|
|
base_ref:
|
|
description: Base ref/sha to build
|
|
required: true
|
|
default: main
|
|
candidate_ref:
|
|
description: Candidate ref/sha to build (empty = current ref)
|
|
required: false
|
|
default: ""
|
|
allow_large_fixture:
|
|
description: Pass --allow-large-fixture to runner
|
|
type: boolean
|
|
default: true
|
|
http_timeout:
|
|
description: Runner HTTP timeout seconds
|
|
required: true
|
|
default: "300"
|
|
cargo_profile:
|
|
description: Cargo profile to build base and candidate binaries with
|
|
required: true
|
|
type: choice
|
|
default: nightly
|
|
options:
|
|
- nightly
|
|
- release
|
|
- dev
|
|
runner:
|
|
description: >-
|
|
Self-hosted runner label; aliyun-ecs provisions a fresh ECS instance
|
|
per run, any other value is used as a literal runner label
|
|
required: true
|
|
type: string
|
|
default: aliyun-ecs
|
|
keep_instance:
|
|
description: >-
|
|
Debug: keep the ECS instance after the run (skip teardown) so its
|
|
runner _diag logs, journal, and telemetry can be inspected; the
|
|
janitor still sweeps it after the TTL
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
|
|
permissions:
|
|
# Load-bearing: every job, including ECS, gets this GITHUB_TOKEN.
|
|
# Do not grant issues or pull-requests write; candidate code could
|
|
# otherwise forge the HMAC admission marker on a victim PR.
|
|
contents: read
|
|
|
|
jobs:
|
|
test-tooling:
|
|
# Stdlib unittests for the workflow Python (case selection, report
|
|
# helpers, nightly SHA picking, comment-command admission, and rendered
|
|
# ECS user-data). They do not talk to Aliyun or the Actions runner
|
|
# process; running them on ubuntu-latest fails fast before any ECS spend.
|
|
# Ordinary PRs also run the same tests from checks.yml.
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
outputs:
|
|
# The Rust toolchain pin, resolved from rust-toolchain.toml so the
|
|
# benchmark always runs the workspace toolchain without a second
|
|
# hard-coded copy in this workflow.
|
|
rust_toolchain: ${{ steps.rust-toolchain.outputs.pin }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Resolve Rust toolchain pin
|
|
id: rust-toolchain
|
|
run: |
|
|
pin="$(grep -E '^channel' rust-toolchain.toml | cut -d'"' -f2)"
|
|
echo "pin=${pin}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Test query regression tooling
|
|
run: |
|
|
python3 tests/perf/test_query_regression_runner_compaction_toctou.py
|
|
python3 tests/perf/test_query_regression_runner_otlp_trace_load.py
|
|
python3 tests/perf/test_query_regression_summary_otlp.py
|
|
python3 tests/perf/test_query_regression_case_selection.py
|
|
python3 tests/perf/test_query_regression_nightly_refs.py
|
|
python3 tests/perf/test_query_regression_slash.py
|
|
python3 tests/perf/test_aliyun_ecs_runner_scripts.py
|
|
|
|
provision:
|
|
# Runs when the aliyun-ecs path is selected via the runner input (PR
|
|
# comment admission can redirect with QUERY_REGRESSION_PR_RUNNER). Uses
|
|
# trusted scripts from the caller ref, never from candidate code. Waits
|
|
# for test-tooling so a broken user-data template does not still create a VM.
|
|
needs: [test-tooling]
|
|
if: ${{ !failure() && !cancelled() && inputs.runner == 'aliyun-ecs' }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 45
|
|
outputs:
|
|
label: ${{ steps.provision.outputs.label }}
|
|
instance_id: ${{ steps.provision.outputs.instance_id }}
|
|
runner_name: ${{ steps.provision.outputs.runner_name }}
|
|
steps:
|
|
- name: Checkout trusted provisioning scripts
|
|
uses: actions/checkout@v4
|
|
with:
|
|
repository: ${{ github.repository }}
|
|
ref: ${{ github.sha }}
|
|
persist-credentials: false
|
|
|
|
- name: Provision ECS runner
|
|
id: provision
|
|
uses: ./.github/actions/aliyun-ecs-create
|
|
with:
|
|
access-key-id: ${{ secrets.ALICLOUD_ECS_ACCESS_KEY_ID }}
|
|
access-key-secret: ${{ secrets.ALICLOUD_ECS_ACCESS_KEY_SECRET }}
|
|
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
|
region-id: ${{ vars.ALIYUN_ECS_REGION_ID }}
|
|
vswitch-id: ${{ vars.ALIYUN_ECS_VSWITCH_ID }}
|
|
security-group-id: ${{ vars.ALIYUN_ECS_SECURITY_GROUP_ID }}
|
|
instance-type: ${{ vars.ALIYUN_ECS_INSTANCE_TYPE }}
|
|
# Preserve query regression's original resource and janitor budgets.
|
|
system-disk-gib: '80'
|
|
ttl-hours: '4'
|
|
resource-group-id: ${{ vars.ALIYUN_ECS_RESOURCE_GROUP_ID }}
|
|
image-id: ${{ vars.QUERY_REGRESSION_ECS_IMAGE_ID }}
|
|
runner-uid: ${{ vars.QUERY_REGRESSION_RUNNER_UID || '1001' }}
|
|
runner-gid: ${{ vars.QUERY_REGRESSION_RUNNER_GID || '1001' }}
|
|
|
|
query-regression:
|
|
needs: [provision, test-tooling]
|
|
# `!failure() && !cancelled()` both suppresses the implicit success() and
|
|
# lets the job run when provision was intentionally skipped because a
|
|
# literal runner label was selected; a failed or cancelled provision still
|
|
# blocks the run because no ECS runner would be waiting.
|
|
if: ${{ !failure() && !cancelled() }}
|
|
runs-on: ${{ needs.provision.outputs.label || inputs.runner }}
|
|
timeout-minutes: 180
|
|
env:
|
|
CARGO_PROFILE: ${{ inputs.cargo_profile }}
|
|
CARGO_HOME: /home/runner/.cargo
|
|
UV_CACHE_DIR: /home/runner/.cargo/uv-cache
|
|
RUSTUP_HOME: /opt/rustup
|
|
RUSTUP_TOOLCHAIN: ${{ needs.test-tooling.outputs.rust_toolchain }}
|
|
RUSTUP_AUTO_INSTALL: "0"
|
|
CARGO_TARGET_DIR: /home/runner/query-regression-target
|
|
QUERY_REGRESSION_CACHE_META: /home/runner/query-regression-cache-meta
|
|
RUSTC_WRAPPER: /usr/local/bin/sccache
|
|
SCCACHE_DIR: /home/runner/.cache/sccache
|
|
# Caps the local sccache on the system disk next to target and cargo.
|
|
SCCACHE_CACHE_SIZE: 10G
|
|
CARGO_INCREMENTAL: "0"
|
|
RUSTFLAGS: -D warnings -C link-arg=-fuse-ld=mold
|
|
QUERY_REGRESSION_CACHE_EPOCH: "1"
|
|
# Reusable workflows keep the caller's github.event_name (slash is
|
|
# repository_dispatch; nightly is workflow_run), never "workflow_call".
|
|
# PR admission is the explicit pr_number input from the slash wrapper.
|
|
PR_ADMISSION: ${{ inputs.pr_number != '' }}
|
|
PR_NUMBER: ${{ inputs.pr_number }}
|
|
HEAD_REPO: ${{ inputs.head_repo }}
|
|
BASE_REPO: ${{ inputs.base_repo }}
|
|
BASE_REF: ${{ inputs.base_ref }}
|
|
CANDIDATE_REF: ${{ inputs.candidate_ref || github.ref }}
|
|
EVENT_MERGE_SHA: ${{ inputs.pr_number != '' && inputs.candidate_ref || '' }}
|
|
EVENT_HEAD_SHA: ${{ inputs.head_sha }}
|
|
EVENT_BASE_SHA: ${{ inputs.pr_number != '' && inputs.base_ref || '' }}
|
|
# Runner identity contract. The ECS image uses 1001; a manually prepared
|
|
# host may override via repo variables when 1001 is already taken.
|
|
EXPECTED_RUNNER_UID: ${{ vars.QUERY_REGRESSION_RUNNER_UID || '1001' }}
|
|
EXPECTED_RUNNER_GID: ${{ vars.QUERY_REGRESSION_RUNNER_GID || '1001' }}
|
|
steps:
|
|
- name: Report provisioned ECS runner
|
|
if: ${{ needs.provision.outputs.instance_id != '' }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
{
|
|
printf -- '- ECS instance: `%s` (type `%s`, image `%s`, region `%s`)\n' \
|
|
"${{ needs.provision.outputs.instance_id }}" \
|
|
"${{ vars.ALIYUN_ECS_INSTANCE_TYPE }}" \
|
|
"${{ vars.QUERY_REGRESSION_ECS_IMAGE_ID }}" \
|
|
"${{ vars.ALIYUN_ECS_REGION_ID }}"
|
|
} >> "${GITHUB_STEP_SUMMARY}"
|
|
|
|
- name: Start machine telemetry sampler
|
|
shell: bash
|
|
# The ECS instance is deleted when the run ends, so machine state must
|
|
# be captured while the job runs. A background sampler appends load,
|
|
# memory, disk, and top-memory processes to a log every 30s; the
|
|
# "Dump machine telemetry" step (if: always()) prints and uploads it.
|
|
# Note: when the job is *cancelled*, even always() steps are killed —
|
|
# telemetry covers failures, not cancellations.
|
|
run: |
|
|
set -euo pipefail
|
|
log="${GITHUB_WORKSPACE}/machine-telemetry.log"
|
|
{
|
|
echo "== baseline $(date -u +%Y-%m-%dT%H:%M:%SZ) =="
|
|
nproc
|
|
free -h
|
|
df -h / /home/runner
|
|
} >> "${log}"
|
|
{
|
|
printf '## Disk (start)\n\n```\n'
|
|
df -hP / /home/runner
|
|
printf '```\n'
|
|
} >> "${GITHUB_STEP_SUMMARY}"
|
|
nohup bash -c "
|
|
while true; do
|
|
{
|
|
date -u '+== %Y-%m-%dT%H:%M:%SZ =='
|
|
uptime
|
|
free -m
|
|
df -h / /home/runner
|
|
ps -eo pid,comm,%mem,%cpu --sort=-%mem | head -6
|
|
} >> '${log}' 2>&1
|
|
sleep 30
|
|
done
|
|
" >/dev/null 2>&1 &
|
|
echo "Telemetry sampler started (pid $!), logging to ${log}"
|
|
|
|
- name: Checkout base source
|
|
uses: actions/checkout@v4
|
|
with:
|
|
repository: ${{ github.repository }}
|
|
ref: ${{ env.BASE_REF }}
|
|
path: src
|
|
persist-credentials: false
|
|
|
|
- name: Resolve immutable candidate and build base
|
|
working-directory: src
|
|
shell: bash
|
|
env:
|
|
CALLER_WORKFLOW_REF: ${{ github.workflow_ref }}
|
|
DEFINING_WORKFLOW_REF: ${{ job.workflow_ref }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
fail_closed() {
|
|
printf '%s\n' \
|
|
"PR changed or merge result unavailable; maintainer must re-admit the current revision by commenting /query-regression. $1" >&2
|
|
exit 1
|
|
}
|
|
|
|
is_full_sha() {
|
|
[[ "$1" =~ ^[[:xdigit:]]{40}$ ]]
|
|
}
|
|
|
|
is_reusable_call=false
|
|
if [[ "${CALLER_WORKFLOW_REF}" != "${DEFINING_WORKFLOW_REF}" ]]; then
|
|
is_reusable_call=true
|
|
fi
|
|
|
|
fetch_with_retry() {
|
|
local ref="$1"
|
|
local depth="$2"
|
|
local attempt=1
|
|
local max_attempts=5
|
|
local delay
|
|
|
|
while (( attempt <= max_attempts )); do
|
|
if git fetch --no-tags --prune --depth="${depth}" origin "${ref}"; then
|
|
return 0
|
|
fi
|
|
printf 'Git fetch failed for %s (attempt %d/%d).\n' \
|
|
"${ref}" "${attempt}" "${max_attempts}" >&2
|
|
if (( attempt < max_attempts )); then
|
|
delay=$(( 2 ** (attempt - 1) ))
|
|
printf 'Retrying git fetch for %s in %d seconds.\n' "${ref}" "${delay}" >&2
|
|
sleep "${delay}"
|
|
fi
|
|
((attempt += 1))
|
|
done
|
|
return 1
|
|
}
|
|
|
|
initially_checked_out_base_sha="$(git rev-parse --verify HEAD)"
|
|
is_full_sha "${initially_checked_out_base_sha}" || {
|
|
printf 'Could not resolve initially checked-out BASE_REF commit.\n' >&2
|
|
exit 1
|
|
}
|
|
|
|
if [[ "${PR_ADMISSION}" == "true" ]]; then
|
|
for identity in EVENT_MERGE_SHA EVENT_HEAD_SHA EVENT_BASE_SHA; do
|
|
value="${!identity-}"
|
|
is_full_sha "${value}" || fail_closed "Missing or invalid ${identity}."
|
|
done
|
|
|
|
if ! fetch_with_retry "${EVENT_MERGE_SHA}" 2; then
|
|
fail_closed "Could not fetch event merge SHA ${EVENT_MERGE_SHA}."
|
|
fi
|
|
fetched_sha="$(git rev-parse --verify FETCH_HEAD 2>/dev/null)" \
|
|
|| fail_closed "Fetched event merge SHA could not be resolved."
|
|
[[ "${fetched_sha,,}" == "${EVENT_MERGE_SHA,,}" ]] \
|
|
|| fail_closed "FETCH_HEAD ${fetched_sha} does not match event merge SHA ${EVENT_MERGE_SHA}."
|
|
|
|
read -r -a parents <<< "$(git show -s --format=%P FETCH_HEAD)"
|
|
(( ${#parents[@]} == 2 )) \
|
|
|| fail_closed "Event merge SHA ${EVENT_MERGE_SHA} is not a two-parent merge commit."
|
|
head_parent_count=0
|
|
for parent in "${parents[@]}"; do
|
|
if [[ "${parent,,}" == "${EVENT_HEAD_SHA,,}" ]]; then
|
|
((head_parent_count += 1))
|
|
else
|
|
VERIFIED_BASE_SHA="${parent,,}"
|
|
fi
|
|
done
|
|
(( head_parent_count == 1 )) \
|
|
|| fail_closed "Event merge SHA must have exactly one EVENT_HEAD_SHA parent."
|
|
is_full_sha "${VERIFIED_BASE_SHA-}" \
|
|
|| fail_closed "Event merge SHA has no non-head parent for the base build."
|
|
VERIFIED_CANDIDATE_SHA="${fetched_sha,,}"
|
|
else
|
|
if [[ "${is_reusable_call}" == true ]] && ! is_full_sha "${CANDIDATE_REF}"; then
|
|
printf 'Reusable calls require CANDIDATE_REF to be an immutable full SHA, got: %s\n' \
|
|
"${CANDIDATE_REF}" >&2
|
|
exit 1
|
|
fi
|
|
if ! fetch_with_retry "${CANDIDATE_REF}" 1; then
|
|
printf 'Could not fetch candidate ref %s.\n' "${CANDIDATE_REF}" >&2
|
|
exit 1
|
|
fi
|
|
fetched_sha="$(git rev-parse --verify FETCH_HEAD)"
|
|
if { [[ "${is_reusable_call}" == true ]] || is_full_sha "${CANDIDATE_REF}"; } && \
|
|
[[ "${fetched_sha,,}" != "${CANDIDATE_REF,,}" ]]; then
|
|
printf 'FETCH_HEAD %s does not match immutable candidate SHA %s.\n' \
|
|
"${fetched_sha}" "${CANDIDATE_REF}" >&2
|
|
exit 1
|
|
fi
|
|
VERIFIED_CANDIDATE_SHA="${fetched_sha}"
|
|
VERIFIED_BASE_SHA="${initially_checked_out_base_sha}"
|
|
fi
|
|
|
|
is_full_sha "${VERIFIED_CANDIDATE_SHA}" || {
|
|
printf 'Could not resolve candidate SHA.\n' >&2
|
|
exit 1
|
|
}
|
|
is_full_sha "${VERIFIED_BASE_SHA}" || {
|
|
printf 'Could not resolve build base SHA.\n' >&2
|
|
exit 1
|
|
}
|
|
printf 'VERIFIED_CANDIDATE_SHA=%s\n' "${VERIFIED_CANDIDATE_SHA,,}" >> "${GITHUB_ENV}"
|
|
printf 'VERIFIED_BASE_SHA=%s\n' "${VERIFIED_BASE_SHA,,}" >> "${GITHUB_ENV}"
|
|
printf 'Verified candidate SHA: %s\n' "${VERIFIED_CANDIDATE_SHA,,}"
|
|
printf 'Verified build base SHA: %s\n' "${VERIFIED_BASE_SHA,,}"
|
|
printf -- '- Verified candidate SHA: `%s`; built base SHA: `%s`\n' \
|
|
"${VERIFIED_CANDIDATE_SHA,,}" "${VERIFIED_BASE_SHA,,}" >> "${GITHUB_STEP_SUMMARY}"
|
|
git reset --hard "${VERIFIED_BASE_SHA}"
|
|
git clean -ffdx
|
|
|
|
- name: Preserve trusted query regression scripts
|
|
working-directory: src
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
helper_ref="${VERIFIED_CANDIDATE_SHA}"
|
|
if [[ "${PR_ADMISSION}" == "true" ]]; then
|
|
helper_ref="${VERIFIED_BASE_SHA}"
|
|
fi
|
|
mkdir -p "${GITHUB_WORKSPACE}/query-regression-trusted-scripts"
|
|
git show "${helper_ref}:.github/scripts/query-regression-pr-metadata.py" \
|
|
> "${GITHUB_WORKSPACE}/query-regression-trusted-scripts/query-regression-pr-metadata.py"
|
|
git show "${helper_ref}:.github/scripts/query-regression-summary.py" \
|
|
> "${GITHUB_WORKSPACE}/query-regression-trusted-scripts/query-regression-summary.py"
|
|
|
|
- name: Verify runner image tools
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
errors=()
|
|
record() {
|
|
printf 'FAIL %s\n' "$*"
|
|
errors+=("$*")
|
|
}
|
|
require_eq() {
|
|
local name="$1" actual="$2" expected="$3"
|
|
printf 'check %s: %s\n' "${name}" "${actual}"
|
|
[[ "${actual}" == "${expected}" ]] || record "${name}: expected '${expected}', got '${actual}'"
|
|
}
|
|
require_match() {
|
|
local name="$1" actual="$2" pattern="$3"
|
|
printf 'check %s: %s\n' "${name}" "${actual}"
|
|
[[ "${actual}" =~ $pattern ]] || record "${name}: expected to match ${pattern}, got '${actual}'"
|
|
}
|
|
require() {
|
|
local name="$1"
|
|
shift
|
|
printf 'check %s\n' "${name}"
|
|
"$@" || record "${name}"
|
|
}
|
|
capture() {
|
|
local out
|
|
if out="$("$@" 2>&1)"; then
|
|
printf '%s' "${out}"
|
|
else
|
|
printf '<failed to run: %s>' "$*"
|
|
fi
|
|
}
|
|
|
|
printf 'uid=%s gid=%s PATH=%s CARGO_HOME=%s\n' "$(id -u)" "$(id -g)" "${PATH}" "${CARGO_HOME}"
|
|
ls -la "${CARGO_HOME}" 2>&1 || printf '(CARGO_HOME missing)\n'
|
|
|
|
# Image/host hygiene: CARGO_HOME must be empty of config and
|
|
# install-state before cargo/rustup run. Those tools create
|
|
# .package-cache / bin on first use, so this cannot come after.
|
|
cargo_home_dirty=()
|
|
for entry in config config.toml credentials credentials.toml bin .crates.toml .crates2.json .global-cache .package-cache; do
|
|
if [[ -e "${CARGO_HOME}/${entry}" ]]; then
|
|
cargo_home_dirty+=("${entry}")
|
|
record "CARGO_HOME must not contain ${entry}"
|
|
fi
|
|
done
|
|
if (( ${#cargo_home_dirty[@]} > 0 )); then
|
|
printf 'CARGO_HOME contents:\n' >&2
|
|
ls -la "${CARGO_HOME}" >&2 || true
|
|
fi
|
|
|
|
require_eq uid "$(id -u)" "${EXPECTED_RUNNER_UID}"
|
|
require_eq gid "$(id -g)" "${EXPECTED_RUNNER_GID}"
|
|
require_eq UV_CACHE_DIR "${UV_CACHE_DIR}" "/home/runner/.cargo/uv-cache"
|
|
require_eq protoc "$(capture protoc --version)" "libprotoc 3.21.12"
|
|
temporary_proto_dir="$(mktemp --directory)"
|
|
trap 'rm -rf "${temporary_proto_dir}"' EXIT
|
|
require "readable /usr/include/google/protobuf/any.proto" \
|
|
test -r /usr/include/google/protobuf/any.proto
|
|
require "readable /usr/include/google/protobuf/empty.proto" \
|
|
test -r /usr/include/google/protobuf/empty.proto
|
|
printf '%s\n' \
|
|
'syntax = "proto3";' \
|
|
'package smoke;' \
|
|
'import "google/protobuf/any.proto";' \
|
|
'import "google/protobuf/empty.proto";' \
|
|
'message Smoke { google.protobuf.Any any = 1; google.protobuf.Empty empty = 2; }' \
|
|
> "${temporary_proto_dir}/smoke.proto"
|
|
if protoc --proto_path="${temporary_proto_dir}" --proto_path=/usr/include \
|
|
--descriptor_set_out="${temporary_proto_dir}/smoke.pb" \
|
|
"${temporary_proto_dir}/smoke.proto"; then
|
|
require "protoc smoke descriptor is non-empty" test -s "${temporary_proto_dir}/smoke.pb"
|
|
else
|
|
record "protoc smoke compile failed"
|
|
fi
|
|
require_match uv "$(capture uv --version)" '^uv[[:space:]]0\.11\.26([[:space:]]|$)'
|
|
require_match mold "$(capture mold --version)" '^mold[[:space:]]2\.40\.4([[:space:]]|$)'
|
|
require_eq python3 "$(capture python3 --version)" "Python 3.14.4"
|
|
otelgen_path="$(command -v otelgen || true)"
|
|
otelgen_version="<unavailable>"
|
|
if [[ -n "${otelgen_path}" ]]; then
|
|
otelgen_version="$(capture "${otelgen_path}" --version)"
|
|
fi
|
|
require_eq otelgen_path "${otelgen_path}" "/usr/local/bin/otelgen"
|
|
printf 'check otelgen_version: %s\n' "${otelgen_version}"
|
|
[[ "${otelgen_version}" == *"863a3f395d062c7322cc1de08a38774b7fdaa6c8"* ]] \
|
|
|| record "otelgen_version: expected commit 863a3f395d062c7322cc1de08a38774b7fdaa6c8, got '${otelgen_version}'"
|
|
require_match sccache "$(capture sccache --version)" '^sccache[[:space:]]0\.16\.0([[:space:]]|$)'
|
|
require_eq rustup_path "$(command -v rustup || true)" "/opt/cargo/bin/rustup"
|
|
require_eq cargo_path "$(command -v cargo || true)" "/opt/cargo/bin/cargo"
|
|
require_eq rustc_path "$(command -v rustc || true)" "/opt/cargo/bin/rustc"
|
|
require_match rustup "$(capture rustup --version)" '^rustup[[:space:]]1\.29\.0([[:space:]]|$)'
|
|
# The toolchain pin flows from rust-toolchain.toml (resolved in the
|
|
# test-tooling job); escape it for the regex assertions below.
|
|
pin_regex="${RUSTUP_TOOLCHAIN//./\\.}"
|
|
require_match cargo "$(capture cargo --version)" \
|
|
"^cargo[[:space:]]${pin_regex}[[:space:]]\([0-9a-f]+[[:space:]][0-9]{4}-[0-9]{2}-[0-9]{2}\)$"
|
|
require_match rustc "$(capture rustc --version)" \
|
|
"^rustc[[:space:]]${pin_regex}[[:space:]]\([0-9a-f]+[[:space:]][0-9]{4}-[0-9]{2}-[0-9]{2}\)$"
|
|
require_match active_toolchain "$(capture rustup show active-toolchain)" \
|
|
"^${pin_regex}-x86_64-unknown-linux-gnu([[:space:]]|$)"
|
|
require_eq RUSTUP_HOME "${RUSTUP_HOME}" "/opt/rustup"
|
|
require_eq RUSTUP_AUTO_INSTALL "${RUSTUP_AUTO_INSTALL}" "0"
|
|
require "readable /opt/rustup" test -r /opt/rustup
|
|
require "executable /opt/rustup" test -x /opt/rustup
|
|
require "runner cannot write /opt/rustup" test ! -w /opt/rustup
|
|
require "runner cannot write /opt/cargo/bin" test ! -w /opt/cargo/bin
|
|
mkdir -p "${CARGO_HOME}/registry" "${CARGO_HOME}/git"
|
|
if (( ${#errors[@]} > 0 )); then
|
|
printf '\nVerify runner image tools failed (%d checks):\n' "${#errors[@]}" >&2
|
|
printf ' - %s\n' "${errors[@]}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Prepare query regression cache
|
|
shell: bash
|
|
working-directory: src
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
readonly EXPECTED_CARGO_HOME="/home/runner/.cargo"
|
|
readonly EXPECTED_CARGO_REGISTRY="/home/runner/.cargo/registry"
|
|
readonly EXPECTED_CARGO_GIT="/home/runner/.cargo/git"
|
|
readonly EXPECTED_UV_CACHE_DIR="/home/runner/.cargo/uv-cache"
|
|
readonly EXPECTED_RUSTUP_HOME="/opt/rustup"
|
|
readonly EXPECTED_TARGET_DIR="/home/runner/query-regression-target"
|
|
readonly EXPECTED_CACHE_META="/home/runner/query-regression-cache-meta"
|
|
readonly EXPECTED_SCCACHE_DIR="/home/runner/.cache/sccache"
|
|
readonly EXPECTED_RUSTC_WRAPPER="/usr/local/bin/sccache"
|
|
readonly RUNNER_IMAGE_DIGEST="sha256:e713b294e23b7e15184e558866c90025e59930033e72c97650dbc7f1ca022d11"
|
|
readonly RUNNER_IMAGE_EPOCH="5"
|
|
|
|
require_expected_root() {
|
|
local name="$1"
|
|
local actual="$2"
|
|
local expected="$3"
|
|
[[ "${actual}" == /* && "${actual}" == "${expected}" ]] || {
|
|
printf 'Refusing unexpected %s root: %s\n' "${name}" "${actual}" >&2
|
|
exit 1
|
|
}
|
|
}
|
|
|
|
clear_directory() {
|
|
local root="$1"
|
|
[[ "${root}" == /* && "${root}" != "/" ]] || {
|
|
printf 'Refusing to clear non-absolute or root path: %s\n' "${root}" >&2
|
|
exit 1
|
|
}
|
|
case "${root}" in
|
|
"${EXPECTED_TARGET_DIR}"|"${EXPECTED_TARGET_DIR}/debug"|"${EXPECTED_TARGET_DIR}/release"|"${EXPECTED_TARGET_DIR}/nightly") ;;
|
|
*)
|
|
printf 'Refusing to clear unexpected cache path: %s\n' "${root}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
find "${root}" -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +
|
|
}
|
|
|
|
report_cache_usage() {
|
|
du -sh -- "${CARGO_HOME}" "${CARGO_HOME}/registry" "${CARGO_HOME}/git" \
|
|
"${RUSTUP_HOME}" "${CARGO_TARGET_DIR}" "${QUERY_REGRESSION_CACHE_META}" "${SCCACHE_DIR}"
|
|
df -P "${CARGO_TARGET_DIR}"
|
|
df -hP "${CARGO_TARGET_DIR}"
|
|
df -Pi "${CARGO_TARGET_DIR}"
|
|
}
|
|
|
|
require_expected_root CARGO_HOME "${CARGO_HOME}" "${EXPECTED_CARGO_HOME}"
|
|
require_expected_root CARGO_REGISTRY "${CARGO_HOME}/registry" "${EXPECTED_CARGO_REGISTRY}"
|
|
require_expected_root CARGO_GIT "${CARGO_HOME}/git" "${EXPECTED_CARGO_GIT}"
|
|
require_expected_root UV_CACHE_DIR "${UV_CACHE_DIR}" "${EXPECTED_UV_CACHE_DIR}"
|
|
require_expected_root RUSTUP_HOME "${RUSTUP_HOME}" "${EXPECTED_RUSTUP_HOME}"
|
|
require_expected_root CARGO_TARGET_DIR "${CARGO_TARGET_DIR}" "${EXPECTED_TARGET_DIR}"
|
|
require_expected_root QUERY_REGRESSION_CACHE_META "${QUERY_REGRESSION_CACHE_META}" "${EXPECTED_CACHE_META}"
|
|
require_expected_root SCCACHE_DIR "${SCCACHE_DIR}" "${EXPECTED_SCCACHE_DIR}"
|
|
[[ "${RUSTC_WRAPPER}" == "${EXPECTED_RUSTC_WRAPPER}" ]] || {
|
|
printf 'Refusing unexpected RUSTC_WRAPPER: %s\n' "${RUSTC_WRAPPER}" >&2
|
|
exit 1
|
|
}
|
|
[[ "${SCCACHE_CACHE_SIZE}" == "10G" ]] || {
|
|
printf 'Refusing unexpected SCCACHE_CACHE_SIZE: %s\n' "${SCCACHE_CACHE_SIZE}" >&2
|
|
exit 1
|
|
}
|
|
[[ "${CARGO_INCREMENTAL}" == "0" ]] || {
|
|
printf 'Refusing unexpected CARGO_INCREMENTAL: %s\n' "${CARGO_INCREMENTAL}" >&2
|
|
exit 1
|
|
}
|
|
test -r "${RUSTUP_HOME}"
|
|
test -x "${RUSTUP_HOME}"
|
|
test ! -w "${RUSTUP_HOME}"
|
|
mkdir -p "${CARGO_HOME}" "${CARGO_HOME}/registry" "${CARGO_HOME}/git" "${UV_CACHE_DIR}" \
|
|
"${CARGO_TARGET_DIR}" "${QUERY_REGRESSION_CACHE_META}" "${SCCACHE_DIR}"
|
|
for root in "${CARGO_HOME}" "${CARGO_HOME}/registry" "${CARGO_HOME}/git" \
|
|
"${UV_CACHE_DIR}" "${CARGO_TARGET_DIR}" "${QUERY_REGRESSION_CACHE_META}" "${SCCACHE_DIR}"; do
|
|
test -w "${root}"
|
|
touch "${root}/.query-regression-write-test"
|
|
rm -f "${root}/.query-regression-write-test"
|
|
done
|
|
|
|
case "${CARGO_PROFILE}" in
|
|
dev) target_profile="debug" ;;
|
|
release|nightly) target_profile="${CARGO_PROFILE}" ;;
|
|
*) printf 'Unsupported CARGO_PROFILE: %s\n' "${CARGO_PROFILE}" >&2; exit 1 ;;
|
|
esac
|
|
|
|
rustc_vv="$(rustc -vV)"
|
|
if cargo -Vv >/dev/null 2>&1; then
|
|
cargo_vv="$(cargo -Vv)"
|
|
else
|
|
cargo_vv="$(cargo -V)"
|
|
fi
|
|
host_triple="$(printf '%s\n' "${rustc_vv}" | grep '^host: ' | cut -d' ' -f2-)"
|
|
cc_version="$(cc --version)"
|
|
mold_version="$(mold --version | grep -m1 .)"
|
|
protoc_version="$(protoc --version)"
|
|
sccache_version="$(sccache --version)"
|
|
rustup_version="$(rustup --version)"
|
|
active_toolchain="$(rustup show active-toolchain)"
|
|
rustup_path="$(command -v rustup)"
|
|
cargo_path="$(command -v cargo)"
|
|
rustc_path="$(command -v rustc)"
|
|
profile_target_dir="${CARGO_TARGET_DIR}/${target_profile}"
|
|
marker="${QUERY_REGRESSION_CACHE_META}/target-${target_profile}.abi"
|
|
new_marker="$(mktemp "${QUERY_REGRESSION_CACHE_META}/.${target_profile}.abi.XXXXXX")"
|
|
{
|
|
printf 'cache_epoch=%s\n' "${QUERY_REGRESSION_CACHE_EPOCH}"
|
|
printf 'runner_image_digest=%s\n' "${RUNNER_IMAGE_DIGEST}"
|
|
printf 'runner_image_epoch=%s\n' "${RUNNER_IMAGE_EPOCH}"
|
|
printf 'host_triple=%s\n' "${host_triple}"
|
|
printf 'architecture=%s\n' "$(uname -m)"
|
|
printf 'cargo_profile=%s\n' "${CARGO_PROFILE}"
|
|
printf 'target_profile=%s\n' "${target_profile}"
|
|
printf 'RUSTFLAGS=%s\n' "${RUSTFLAGS-}"
|
|
printf 'CARGO_ENCODED_RUSTFLAGS=%s\n' "${CARGO_ENCODED_RUSTFLAGS-}"
|
|
printf 'CARGO_INCREMENTAL=%s\n' "${CARGO_INCREMENTAL-}"
|
|
printf 'RUSTC_WRAPPER=%s\n' "${RUSTC_WRAPPER-}"
|
|
printf 'SCCACHE_DIR=%s\n' "${SCCACHE_DIR-}"
|
|
printf 'SCCACHE_CACHE_SIZE=%s\n' "${SCCACHE_CACHE_SIZE-}"
|
|
printf 'RUSTUP_HOME=%s\n' "${RUSTUP_HOME-}"
|
|
printf 'RUSTUP_TOOLCHAIN=%s\n' "${RUSTUP_TOOLCHAIN-}"
|
|
printf 'RUSTUP_AUTO_INSTALL=%s\n' "${RUSTUP_AUTO_INSTALL-}"
|
|
printf 'PATH=%s\n' "${PATH-}"
|
|
printf '%s\n' "rustc_vV=${rustc_vv}"
|
|
printf '%s\n' "cargo_vV=${cargo_vv}"
|
|
printf '%s\n' "cc_version=${cc_version}"
|
|
printf '%s\n' "mold_version=${mold_version}"
|
|
printf '%s\n' "protoc_version=${protoc_version}"
|
|
printf '%s\n' "sccache_version=${sccache_version}"
|
|
printf '%s\n' "rustup_version=${rustup_version}"
|
|
printf '%s\n' "active_toolchain=${active_toolchain}"
|
|
printf '%s\n' "rustup_path=${rustup_path}"
|
|
printf '%s\n' "cargo_path=${cargo_path}"
|
|
printf '%s\n' "rustc_path=${rustc_path}"
|
|
} > "${new_marker}"
|
|
|
|
if ! cmp -s "${marker}" "${new_marker}"; then
|
|
printf 'Cache ABI marker mismatch; invalidating target profile: %s\n' "${target_profile}"
|
|
mkdir -p "${profile_target_dir}"
|
|
clear_directory "${profile_target_dir}"
|
|
else
|
|
printf 'Cache ABI marker match; reusing target profile: %s\n' "${target_profile}"
|
|
fi
|
|
mv -f "${new_marker}" "${marker}"
|
|
|
|
report_cache_usage
|
|
|
|
# Every run starts from a fresh system disk, so size/free-space
|
|
# watermarks from the retired retained-disk era are not restored.
|
|
# A run that overflows the disk fails the build outright, which the
|
|
# telemetry step makes diagnosable.
|
|
|
|
sccache --start-server
|
|
sccache --zero-stats
|
|
sccache --show-stats
|
|
|
|
- name: Build base greptime
|
|
working-directory: src
|
|
run: |
|
|
set -euo pipefail
|
|
SECONDS=0
|
|
cargo build --profile "${CARGO_PROFILE}" -p cmd --bin greptime
|
|
base_build_elapsed="${SECONDS}"
|
|
printf 'Base greptime cargo build elapsed: %s seconds\n' "${base_build_elapsed}"
|
|
printf -- '- Base greptime cargo build: %s seconds\n' "${base_build_elapsed}" >> "${GITHUB_STEP_SUMMARY}"
|
|
sccache --show-stats
|
|
sccache --zero-stats
|
|
target_dir="${CARGO_PROFILE}"
|
|
if [[ "${CARGO_PROFILE}" == "dev" ]]; then
|
|
target_dir="debug"
|
|
fi
|
|
mkdir -p "${GITHUB_WORKSPACE}/query-regression-bins/base"
|
|
cp "${CARGO_TARGET_DIR}/${target_dir}/greptime" \
|
|
"${GITHUB_WORKSPACE}/query-regression-bins/base/greptime"
|
|
|
|
- name: Switch source to candidate
|
|
working-directory: src
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
[[ "${VERIFIED_CANDIDATE_SHA}" =~ ^[[:xdigit:]]{40}$ ]] || {
|
|
printf 'Verified candidate SHA is missing or invalid.\n' >&2
|
|
exit 1
|
|
}
|
|
git cat-file -e "${VERIFIED_CANDIDATE_SHA}^{commit}" || {
|
|
printf 'Verified candidate SHA is unavailable in the local repository.\n' >&2
|
|
exit 1
|
|
}
|
|
git reset --hard "${VERIFIED_CANDIDATE_SHA}"
|
|
git clean -ffdx
|
|
|
|
- name: Build candidate greptime and query regression helpers
|
|
working-directory: src
|
|
run: |
|
|
set -euo pipefail
|
|
SECONDS=0
|
|
cargo build --profile "${CARGO_PROFILE}" -p cmd --bin greptime
|
|
cargo build --profile "${CARGO_PROFILE}" -p cmd --bin query_perf_fixture --features dev-tools
|
|
cargo build --profile "${CARGO_PROFILE}" -p cmd --bin query_regression_runner --features dev-tools
|
|
candidate_build_elapsed="${SECONDS}"
|
|
printf 'Candidate greptime and query regression helper cargo builds elapsed: %s seconds\n' "${candidate_build_elapsed}"
|
|
printf -- '- Candidate greptime and query regression helper cargo builds: %s seconds\n' "${candidate_build_elapsed}" >> "${GITHUB_STEP_SUMMARY}"
|
|
sccache --show-stats
|
|
target_dir="${CARGO_PROFILE}"
|
|
if [[ "${CARGO_PROFILE}" == "dev" ]]; then
|
|
target_dir="debug"
|
|
fi
|
|
mkdir -p "${GITHUB_WORKSPACE}/query-regression-bins/candidate"
|
|
cp "${CARGO_TARGET_DIR}/${target_dir}/greptime" \
|
|
"${GITHUB_WORKSPACE}/query-regression-bins/candidate/greptime"
|
|
cp "${CARGO_TARGET_DIR}/${target_dir}/query_perf_fixture" \
|
|
"${GITHUB_WORKSPACE}/query-regression-bins/candidate/query_perf_fixture"
|
|
cp "${CARGO_TARGET_DIR}/${target_dir}/query_regression_runner" \
|
|
"${GITHUB_WORKSPACE}/query-regression-bins/candidate/query_regression_runner"
|
|
|
|
- name: Run query regression
|
|
id: run
|
|
env:
|
|
CASE_PATHS: ${{ inputs.case }}
|
|
HTTP_TIMEOUT: ${{ inputs.http_timeout }}
|
|
ALLOW_LARGE_FIXTURE: ${{ inputs.allow_large_fixture }}
|
|
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
CASE_NAME: ${{ inputs.case }}
|
|
BASE_BIN: ${{ github.workspace }}/query-regression-bins/base/greptime
|
|
CANDIDATE_BIN: ${{ github.workspace }}/query-regression-bins/candidate/greptime
|
|
FIXTURE_GENERATOR: ${{ github.workspace }}/query-regression-bins/candidate/query_perf_fixture
|
|
OTELGEN_BIN: /usr/local/bin/otelgen
|
|
SUMMARY_SCRIPT: ${{ env.PR_ADMISSION == 'true' && 'query-regression-trusted-scripts/query-regression-summary.py' || 'src/.github/scripts/query-regression-summary.py' }}
|
|
QUERY_REGRESSION_RUNNER: ${{ github.workspace }}/query-regression-bins/candidate/query_regression_runner
|
|
run: >-
|
|
uv run --no-project python src/.github/scripts/query-regression-run.py
|
|
--base-src src
|
|
--candidate-src src
|
|
--summary-script "${SUMMARY_SCRIPT}"
|
|
|
|
- name: Write PR metadata for trusted comment workflow
|
|
if: ${{ always() && env.PR_ADMISSION == 'true' }}
|
|
env:
|
|
HEAD_SHA: ${{ inputs.head_sha }}
|
|
RUN_ID: ${{ github.run_id }}
|
|
RUN_ATTEMPT: ${{ github.run_attempt }}
|
|
run: uv run --no-project python query-regression-trusted-scripts/query-regression-pr-metadata.py
|
|
|
|
- name: Upload query regression artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: query-regression-report
|
|
path: |
|
|
query-regression-work/**/query-regression-report.json
|
|
query-regression-work/**/base/report.json
|
|
query-regression-work/**/candidate/report.json
|
|
query-regression-work/**/logs/**
|
|
query-regression-work/**/otelgen/**
|
|
query-regression-summary.md
|
|
machine-telemetry.log
|
|
if-no-files-found: warn
|
|
retention-days: 7
|
|
# Re-run failed jobs uploads again in the same run; v4 rejects duplicate names.
|
|
overwrite: true
|
|
|
|
- name: Upload trusted comment artifact
|
|
if: ${{ always() && env.PR_ADMISSION == 'true' }}
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: query-regression-comment
|
|
path: |
|
|
query-regression-pr.json
|
|
query-regression-work/**/query-regression-report.json
|
|
if-no-files-found: warn
|
|
retention-days: 7
|
|
overwrite: true
|
|
|
|
- name: Report cache usage
|
|
if: ${{ always() }}
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
for root in \
|
|
/home/runner/.cargo \
|
|
/home/runner/.cargo/registry \
|
|
/home/runner/.cargo/git \
|
|
/opt/rustup \
|
|
/home/runner/query-regression-target \
|
|
/home/runner/query-regression-cache-meta \
|
|
/home/runner/.cache/sccache; do
|
|
if [[ -e "${root}" ]]; then
|
|
du -sh -- "${root}"
|
|
else
|
|
printf 'Missing cache path (report only): %s\n' "${root}"
|
|
fi
|
|
done
|
|
if [[ -e /opt/rustup ]]; then
|
|
if [[ -w /opt/rustup ]]; then
|
|
printf 'Immutable Rustup path is unexpectedly writable: /opt/rustup\n' >&2
|
|
else
|
|
printf 'Immutable Rustup path is read-only to runner: /opt/rustup\n'
|
|
fi
|
|
fi
|
|
report_root=/home/runner/query-regression-target
|
|
if [[ ! -e "${report_root}" ]]; then
|
|
report_root=/home/runner
|
|
printf 'Target cache path is missing; reporting its filesystem through %s\n' "${report_root}"
|
|
fi
|
|
df -P "${report_root}"
|
|
df -hP "${report_root}"
|
|
df -Pi "${report_root}"
|
|
{
|
|
printf '## Disk (end)\n\n```\n'
|
|
df -hP / /home/runner "${report_root}"
|
|
printf '\n'
|
|
du -sh -- \
|
|
/home/runner/.cargo \
|
|
/home/runner/.cargo/registry \
|
|
/home/runner/.cargo/git \
|
|
/opt/rustup \
|
|
/home/runner/query-regression-target \
|
|
/home/runner/query-regression-cache-meta \
|
|
/home/runner/.cache/sccache \
|
|
2>/dev/null || true
|
|
printf '```\n'
|
|
} >> "${GITHUB_STEP_SUMMARY}"
|
|
if command -v sccache >/dev/null 2>&1; then
|
|
sccache --show-stats || printf 'Unable to show sccache statistics (report only)\n' >&2
|
|
else
|
|
printf 'sccache is unavailable (report only)\n' >&2
|
|
fi
|
|
|
|
- name: Dump machine telemetry
|
|
if: ${{ always() }}
|
|
shell: bash
|
|
run: |
|
|
log="${GITHUB_WORKSPACE}/machine-telemetry.log"
|
|
if [[ -f "${log}" ]]; then
|
|
echo "::group::Machine telemetry (last 200 lines)"
|
|
tail -n 200 "${log}"
|
|
echo "::endgroup::"
|
|
else
|
|
echo "No telemetry log found (sampler never started?)"
|
|
fi
|
|
echo "::group::dmesg tail (OOM killer records)"
|
|
sudo dmesg -T 2>/dev/null | tail -n 50 || dmesg -T 2>/dev/null | tail -n 50 || \
|
|
echo "dmesg unavailable without root"
|
|
echo "::endgroup::"
|
|
|
|
- name: Fail on regression failure
|
|
if: ${{ steps.run.outputs.status != '0' }}
|
|
run: exit 1
|
|
|
|
teardown:
|
|
# Releases the dynamically provisioned ECS runner. Runs even when the
|
|
# benchmark job fails or is cancelled; skipped when a literal runner label
|
|
# was selected because the provision outputs are empty, or when the
|
|
# dispatch set keep_instance to preserve the machine for post-mortem
|
|
# debugging (the janitor sweep still reclaims it after the TTL).
|
|
if: ${{ always() && needs.provision.outputs.instance_id != '' && !inputs.keep_instance }}
|
|
needs: [provision, query-regression]
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- name: Checkout trusted teardown scripts
|
|
uses: actions/checkout@v4
|
|
with:
|
|
repository: ${{ github.repository }}
|
|
ref: ${{ github.sha }}
|
|
persist-credentials: false
|
|
|
|
- name: Teardown ECS runner
|
|
uses: ./.github/actions/aliyun-ecs-delete
|
|
with:
|
|
access-key-id: ${{ secrets.ALICLOUD_ECS_ACCESS_KEY_ID }}
|
|
access-key-secret: ${{ secrets.ALICLOUD_ECS_ACCESS_KEY_SECRET }}
|
|
github-token: ${{ secrets.GH_PERSONAL_ACCESS_TOKEN }}
|
|
region-id: ${{ vars.ALIYUN_ECS_REGION_ID }}
|
|
instance-id: ${{ needs.provision.outputs.instance_id }}
|
|
runner-name: ${{ needs.provision.outputs.runner_name }}
|