Skip to content
This repository was archived by the owner on Aug 14, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions .boringcache.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
workspace = "boringcache/benchmark-discourse"
workspace = "discourse/benchmark-discourse"

[adapters.docker]
tag = "discourse-image-factory-rolling-amd64"
Expand All @@ -8,11 +8,14 @@ fail-on-cache-error = true
metadata-hints = ["benchmark=discourse", "upstream-job=base", "lane=rolling", "arch=amd64"]
command = [
"docker", "buildx", "bake",
"base-runtime-deps",
"base-slim",
"base-web-only",
"base-release",
"test-release-amd64",
"--set=base-runtime-deps-*.no-cache=true",
"--load",
"base-runtime-deps", "base-slim-main", "base-slim-stable",
"base-web-only-main", "base-web-only-stable",
"base-release-main", "base-release-stable", "test-release",
]

[adapters.ccache]
tag = "discourse-ccache-rolling-amd64"
no-platform = true
no-git = true
fail-on-cache-error = true
metadata-hints = ["benchmark=discourse", "upstream-job=base", "lane=rolling", "arch=amd64", "tool=ccache"]
221 changes: 172 additions & 49 deletions .github/actions/discourse-image-factory/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ inputs:
phase: {required: true}
arch: {required: true}
benchmark_id: {required: true}
cache_profile: {required: false, default: ""}
plan_variant: {required: false, default: ""}
run_specs: {required: false, default: "true"}
report_strategy: {required: false, default: ""}
discourse_ref: {required: false, default: ""}
docker_ref: {required: false, default: ""}
cli_version: {required: false, default: ""}
buildkit_image: {required: false, default: ""}
runs:
Expand All @@ -18,10 +24,49 @@ runs:

- name: Checkout pinned Discourse sources
shell: bash
env:
DISCOURSE_REF: ${{ inputs.discourse_ref }}
DOCKER_REF: ${{ inputs.docker_ref }}
run: |
set -euo pipefail
git submodule sync --recursive
git -c protocol.version=2 submodule update --init --depth 1 --recommend-shallow --jobs 4 upstream docker-upstream

checkout_immutable_ref() {
local repository="$1"
local requested_ref="$2"
local label="$3"
local depth="$4"
if [[ -z "$requested_ref" ]]; then
return
fi
if [[ ! "$requested_ref" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "$label ref must be a full immutable commit SHA: $requested_ref" >&2
exit 1
fi
git -C "$repository" fetch --depth "$depth" origin "$requested_ref"
git -C "$repository" checkout --detach FETCH_HEAD
}

checkout_immutable_ref upstream "$DISCOURSE_REF" "Discourse" 1
checkout_immutable_ref docker-upstream "$DOCKER_REF" "discourse_docker" 128

if [[ -n "$DOCKER_REF" ]]; then
mozilla_key_fix="a68d4b8707fd653697e8b6b27b336d093dbed5e4"
if ! git -C docker-upstream cat-file -e "${mozilla_key_fix}^{commit}" 2>/dev/null; then
git -C docker-upstream fetch --depth 1 origin "$mozilla_key_fix"
fi
if ! git -C docker-upstream merge-base --is-ancestor "$mozilla_key_fix" HEAD; then
if [[ "$(git -C docker-upstream rev-parse --is-shallow-repository)" == "true" ]]; then
git -C docker-upstream fetch --unshallow origin
fi
fi
if ! git -C docker-upstream merge-base --is-ancestor "$mozilla_key_fix" HEAD; then
echo "discourse_docker ref must contain the Mozilla signing-key fix $mozilla_key_fix" >&2
exit 1
fi
fi

- name: Verify the committed image-factory plans
shell: bash
run: python3 ./scripts/verify-upstream-recipe.py
Expand All @@ -32,36 +77,28 @@ runs:
PREPARE_PHASE: ${{ inputs.phase == 'warm' && 'warm1' || 'base' }}
run: ./scripts/prepare-source.sh "$PREPARE_PHASE"

- name: Prepare the benchmark cache profile
shell: bash
env:
CACHE_PROFILE: ${{ inputs.cache_profile }}
run: python3 ./scripts/prepare_discourse_cache_profile.py "${CACHE_PROFILE:-baseline}"

- name: Prepare ccache 4.13.6 for the Docker build context
if: inputs.cache_profile == 'ccache' || inputs.cache_profile == 'bundler-ccache'
shell: bash
run: ./scripts/stage-ccache-binary.sh "${{ inputs.arch }}" docker-upstream/image/base/ccache

- name: Set up Buildx for the GitHub Actions comparison
if: inputs.strategy == 'actions-cache'
uses: docker/setup-buildx-action@v4

- name: Install the released BoringCache CLI
if: inputs.strategy == 'boringcache'
shell: bash
env:
CLI_VERSION: ${{ inputs.cli_version }}
run: |
set -euo pipefail
installer="$(mktemp)"
trap 'rm -f "$installer"' EXIT
curl -fsSL -H "Cache-Control: no-cache" -H "Pragma: no-cache" \
https://install.boringcache.com/install.sh -o "$installer"
if [[ -n "$CLI_VERSION" ]]; then
export BORINGCACHE_INSTALLER_SOURCE_ONLY=1
source "$installer"
install_binary linux "$(detect_arch)" "$CLI_VERSION"
else
bash "$installer"
fi
echo "${HOME}/.local/bin" >> "$GITHUB_PATH"

- name: Select the cache cohort
id: scope
shell: bash
env:
BENCHMARK_ID: ${{ inputs.benchmark_id }}
CACHE_LANE: ${{ inputs.cache_lane }}
CACHE_PROFILE: ${{ inputs.cache_profile }}
ARCH: ${{ inputs.arch }}
run: |
set -euo pipefail
Expand All @@ -70,52 +107,125 @@ runs:
ref_slug="${ref_slug#-}"
ref_slug="${ref_slug%-}"
ref_slug="${ref_slug:-main}"
profile_slug="${CACHE_PROFILE:+-${CACHE_PROFILE}}"
if [[ "$CACHE_LANE" == "rolling" ]]; then
cache_scope="${BENCHMARK_ID}-rolling-${ref_slug}-${ARCH}"
cache_scope="${BENCHMARK_ID}${profile_slug}-rolling-${ref_slug}-${ARCH}"
else
cache_scope="${BENCHMARK_ID}-run-r${GITHUB_RUN_ID}-a${GITHUB_RUN_ATTEMPT}-${ARCH}"
cache_scope="${BENCHMARK_ID}${profile_slug}-run-r${GITHUB_RUN_ID}-a${GITHUB_RUN_ATTEMPT}-${ARCH}"
fi
# Managed BuildKit appends target names; leave room under OCI's 128-character tag limit.
if (( ${#cache_scope} > 80 )); then
scope_hash="$(printf '%s' "$cache_scope" | sha256sum | cut -c1-8)"
cache_scope="${cache_scope:0:71}-${scope_hash}"
fi
echo "cache_scope=${cache_scope}" >> "$GITHUB_OUTPUT"
echo "discourse_source_sha=${tests_passed_sha}" >> "$GITHUB_OUTPUT"
echo "discourse_source_datestamp=$(git -C upstream show -s --format=%cd --date=format:%Y%m%d HEAD)" >> "$GITHUB_OUTPUT"
echo "source_sha=$(git -C docker-upstream rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- name: Start image-factory timing
id: build_timer
shell: bash
run: echo "started_at=$(date +%s)" >> "$GITHUB_OUTPUT"

- name: Select the BoringCache image-factory plan
if: inputs.strategy == 'boringcache'
shell: bash
env:
PLAN: ${{ format('{0}-{1}', inputs.cache_lane, inputs.arch) }}
PLAN_VARIANT: ${{ inputs.plan_variant }}
CACHE_SCOPE: ${{ steps.scope.outputs.cache_scope }}
run: python3 ./scripts/select-boringcache-plan.py "$PLAN" --cache-tag "$CACHE_SCOPE"
run: |
if [[ -n "$PLAN_VARIANT" ]]; then
PLAN="${PLAN}-${PLAN_VARIANT}"
fi
python3 ./scripts/select-boringcache-plan.py "$PLAN" \
--cache-tag "$CACHE_SCOPE" \
--ccache-tag "${CACHE_SCOPE}-compiler"

# boringcache/one owns the reviewed CLI installation. Discourse still runs
# its upstream target-by-target Bake commands through the shared CLI plan.
- name: Set up BoringCache for the upstream Bake graph
if: inputs.strategy == 'boringcache'
uses: boringcache/one@e24257b122813ad11d53b9ed024b474ca4946ad2 # v1.19.1
with:
cli-version: ${{ inputs.cli_version }}
trust-policy: restore
setup: none
mode: docker
working-directory: docker-upstream/image
docker-command: setup
managed-buildkit-image: ${{ inputs.buildkit_image }}
proxy-port: "22244"
diagnostics: summary
fail-on-cache-error: true
metadata-hints: benchmark=discourse,purpose=bake-cli-setup
env:
BORINGCACHE_RESTORE_TOKEN: ${{ env.BORINGCACHE_RESTORE_TOKEN }}
BORINGCACHE_SAVE_TOKEN: ""

- name: Start image-factory timing
id: build_timer
shell: bash
run: echo "started_at=$(date +%s)" >> "$GITHUB_OUTPUT"

- name: Build with the GitHub Actions cache
if: inputs.strategy == 'actions-cache'
shell: bash
run: >-
python3 ./scripts/run-actions-cache-plan.py
"${{ inputs.cache_lane }}-${{ inputs.arch }}"
--cache-scope "${{ steps.scope.outputs.cache_scope }}"
env:
ARCH: ${{ inputs.arch }}
DATESTAMP: ${{ steps.scope.outputs.discourse_source_datestamp }}
DISCOURSE_REF: ${{ inputs.discourse_ref }}
PLAN: ${{ format('{0}-{1}', inputs.cache_lane, inputs.arch) }}
PLAN_VARIANT: ${{ inputs.plan_variant }}
READ_ONLY: ${{ inputs.phase == 'warm' && 'true' || 'false' }}
run: |
if [[ -n "$PLAN_VARIANT" ]]; then
PLAN="${PLAN}-${PLAN_VARIANT}"
fi
args=(--cache-scope "${{ steps.scope.outputs.cache_scope }}")
if [[ "$READ_ONLY" == "true" ]]; then
args+=(--read-only)
fi
python3 ./scripts/run-actions-cache-plan.py "$PLAN" "${args[@]}"

- name: Build with BoringCache
if: inputs.strategy == 'boringcache'
shell: bash
working-directory: docker-upstream/image
env:
ARCH: ${{ inputs.arch }}
DATESTAMP: ${{ steps.scope.outputs.discourse_source_datestamp }}
DISCOURSE_REF: ${{ inputs.discourse_ref }}
BORINGCACHE_MANAGED_BUILDKIT_IMAGE: ${{ inputs.buildkit_image }}
BORINGCACHE_MANAGED_BUILDKIT_LOG_PATH: ${{ runner.temp }}/boringcache-managed-buildkit-${{ inputs.arch }}-${{ inputs.cache_lane }}-${{ inputs.phase }}.log
READ_ONLY: ${{ inputs.phase == 'warm' && 'true' || 'false' }}
CACHE_PROFILE: ${{ inputs.cache_profile }}
PLAN: ${{ format('{0}-{1}', inputs.cache_lane, inputs.arch) }}
PLAN_VARIANT: ${{ inputs.plan_variant }}
RUST_LOG: warn
run: |
set -euo pipefail
if [[ -n "$PLAN_VARIANT" ]]; then
PLAN="${PLAN}-${PLAN_VARIANT}"
fi
args=()
if [[ "$READ_ONLY" == "true" ]]; then
args+=(--read-only)
fi
boringcache docker "${args[@]}"
if [[ "$CACHE_PROFILE" == "bundler" || "$CACHE_PROFILE" == "bundler-ccache" ]]; then
args+=(--mount-cache)
fi
if [[ "$CACHE_PROFILE" == "ccache" || "$CACHE_PROFILE" == "bundler-ccache" ]]; then
args+=(--tool-cache-ccache)
fi
python3 ./scripts/run-boringcache-plan.py "$PLAN" "${args[@]}"

- name: Capture image-factory timing
id: timing
shell: bash
env:
BUILD_STARTED_AT: ${{ steps.build_timer.outputs.started_at }}
run: echo "build_seconds=$(( $(date +%s) - BUILD_STARTED_AT ))" >> "$GITHUB_OUTPUT"

- name: Smoke test the runtime-deps image
if: inputs.plan_variant == 'runtime-deps'
shell: bash
run: docker run --rm "${BASE_IMAGE}:runtime-deps" /bin/true

- name: Show managed BuildKit errors
if: ${{ inputs.strategy == 'boringcache' && failure() }}
Expand All @@ -130,30 +240,36 @@ runs:
fi

- name: Run upstream's image specs
shell: bash
run: >-
docker run --rm -e RUBY_ONLY=1 -e USE_TURBO=1 -e SKIP_PLUGINS=1
-e SKIP_LINT=1 -e DISCOURSE_TURBO_RSPEC_RETRY_AND_LOG_FLAKY_TESTS=1
"${TEST_IMAGE}:release-${{ inputs.arch }}"

- name: Capture image-factory timing
id: timing
if: inputs.run_specs == 'true'
id: image_specs
continue-on-error: true
shell: bash
env:
BUILD_STARTED_AT: ${{ steps.build_timer.outputs.started_at }}
run: echo "build_seconds=$(( $(date +%s) - BUILD_STARTED_AT ))" >> "$GITHUB_OUTPUT"
DISCOURSE_SOURCE_SHA: ${{ steps.scope.outputs.discourse_source_sha }}
run: |
timeout --foreground --signal=TERM --kill-after=1m 45m \
docker run --rm \
-e CI=true \
-e RUBY_ONLY=1 \
-e USE_TURBO=1 \
-e SKIP_PLUGINS=1 \
-e SKIP_LINT=1 \
-e DISCOURSE_TURBO_RSPEC_RETRY_AND_LOG_FLAKY_TESTS=1 \
-e COMMIT_HASH="$DISCOURSE_SOURCE_SHA" \
"${TEST_IMAGE}:release"

- name: Write benchmark evidence
shell: bash
env:
BENCHMARK_ID: ${{ inputs.benchmark_id }}
STRATEGY: ${{ inputs.strategy }}
STRATEGY: ${{ inputs.report_strategy || inputs.strategy }}
CACHE_LANE: ${{ inputs.cache_lane }}
REPORT_PHASE: ${{ inputs.cache_lane == 'rolling' && 'commit' || inputs.phase == 'warm' && 'warm' || 'cold' }}
WORKFLOW_STARTED_AT: ${{ steps.workflow_timer.outputs.started_at }}
BUILD_SECONDS: ${{ steps.timing.outputs.build_seconds }}
CACHE_SCOPE: ${{ steps.scope.outputs.cache_scope }}
SOURCE_SHA: ${{ steps.scope.outputs.source_sha }}
DISCOURSE_SOURCE_SHA: ${{ steps.scope.outputs.discourse_source_sha }}
ARCH: ${{ inputs.arch }}
run: |
python3 ./scripts/benchmark-report.py phase \
Expand All @@ -166,13 +282,20 @@ runs:
--workflow-seconds "$(( $(date +%s) - WORKFLOW_STARTED_AT ))" \
--build-seconds "$BUILD_SECONDS" \
--cache-tag "$CACHE_SCOPE" \
--workspace boringcache/benchmark-discourse \
--source-repository discourse/discourse_docker \
--source-sha "$SOURCE_SHA"
--workspace discourse/benchmark-discourse \
--source-repository boringcache/discourse_docker \
--source-sha "$SOURCE_SHA" \
--workload-repository discourse/discourse \
--workload-sha "$DISCOURSE_SOURCE_SHA"

- name: Retain benchmark evidence
uses: actions/upload-artifact@v6
with:
name: phase-${{ inputs.benchmark_id }}-${{ inputs.strategy }}-${{ inputs.arch }}-${{ inputs.cache_lane }}-${{ inputs.phase }}
name: phase-${{ inputs.benchmark_id }}-${{ inputs.report_strategy || inputs.strategy }}-${{ inputs.arch }}-${{ inputs.cache_lane }}-${{ inputs.phase }}
path: benchmark-results/
if-no-files-found: error

- name: Enforce the upstream image specs
if: inputs.run_specs == 'true' && steps.image_specs.outcome != 'success'
shell: bash
run: exit 1
Loading