[CI] Probe why ccache misses with a full cache

Throwaway diagnostic, not for merge. Builds the Arch leg's real command
(--server --debug --test) three times against a 6G bound and a cache the job
owns, so the working set can be read off du and reproducibility tested without
touching the repository's cache quota. Also recompresses the cache to measure
what the same configuration costs compressed, since a compressed cache is the
only way one configuration can fit a per-leg bound.
This commit is contained in:
Lukas Brübach 2026-11-04 11:24:07 +01:00
parent 7ca697348b
commit a1069edd8a

110
.github/workflows/ccache-probe.yml vendored Normal file
View file

@ -0,0 +1,110 @@
---
# Throwaway diagnostic: why does master's ccache serve only ~14% of calls when a
# whole pull-request cache is restored?
#
# It builds the Arch leg's real command (`--server --debug --test`, the same as
# .github/workflows/desktop-build.yml) three times, with no Actions cache at all,
# so the repository's 10 GB quota is untouched:
#
# 1. cold, with a 6G bound that cannot be the limit: `du` and the ccache stats
# give the true working set of one configuration.
# 2. again from a deleted build directory with the same image, sources and
# flags: anything below ~95% hits means the hash inputs differ, not the size.
# 3. again after `ccache --recompress 1`, which is the candidate fix if one
# configuration turns out to be larger than a per-leg bound: it measures what
# the same configuration costs when the objects are compressed.
#
# Two runs of this workflow also compare the image id and the compiler's mtime,
# which is what ccache hashes by default (compiler_check = mtime).
name: ccache probe
on:
push:
branches:
- ci/ccache-probe
pull_request:
branches:
- master
concurrency:
group: ${{ github.workflow }} @ ${{ github.ref_name }}
cancel-in-progress: true
jobs:
probe:
name: Arch probe
runs-on: ubuntu-latest
timeout-minutes: 150
env:
NAME: Arch
CACHE: ${{ github.workspace }}/.cache/Arch
# Far above any candidate working set: a cache that evicts cannot be measured.
CCACHE_BOUND: 6G
# The leg's own invocation, so the probe cannot measure a configuration the
# workflow does not build.
LEG_ARGS: --server --debug --test --cmake-generator Ninja
steps:
- name: "Checkout"
uses: actions/checkout@v7
- name: "Build image and record the compiler's identity"
shell: bash
run: |
source .ci/docker.sh --build
echo "=== disk available to the build"
df -h / | sed 's/^/ /'
echo "=== image"
docker image inspect --format 'image id: {{.Id}}' "$IMAGE_NAME"
docker image inspect --format 'created: {{.Created}}' "$IMAGE_NAME"
echo "=== compiler as ccache sees it (compiler_check = mtime hashes mtime and size)"
docker run --rm "$IMAGE_NAME" bash -c 'for c in c++ cc gcc g++; do p="$(command -v $c)"; [ -n "$p" ] && stat -c "%n size=%s mtime=%Y" "$p"; done; c++ --version | head -1'
- name: "Build 1 cold, 6G bound"
shell: bash
run: |
source .ci/docker.sh
RUN $LEG_ARGS --ccache "$CCACHE_BOUND"
# du cannot see a size the cache was not allowed to keep, which is why the
# bound is 6G here. The stats travel inside the cache directory, so the
# numbers below are this run's, not a restored history.
- name: "Working set of one configuration, uncompressed"
shell: bash
run: |
source .ci/docker.sh
echo "=== ccache dir on the host"
du -sh "$CCACHE_DIR"
find "$CCACHE_DIR" -type f -name '*.result' | wc -l | sed 's/^/result files: /'
find "$CCACHE_DIR" -type f -name '*.manifest' | wc -l | sed 's/^/manifest files: /'
echo "=== config in effect"
docker run --rm --mount "type=bind,source=$CCACHE_DIR,target=/.ccache" \
--env CCACHE_DIR=/.ccache "$IMAGE_NAME" ccache --show-config | grep -E 'compiler_check|hash_dir|compression|direct_mode|depend_mode'
- name: "Build 2, same image, same sources, deleted build directory"
shell: bash
run: |
rm -rf build
source .ci/docker.sh
RUN $LEG_ARGS --ccache "$CCACHE_BOUND"
# A compressed cache is the only way one configuration can fit a per-leg
# bound, so measure it on the cache build 2 just used.
- name: "Working set after recompressing to level 1"
shell: bash
run: |
source .ci/docker.sh
docker run --rm --mount "type=bind,source=$CCACHE_DIR,target=/.ccache" \
--env CCACHE_DIR=/.ccache "$IMAGE_NAME" ccache --recompress 1
du -sh "$CCACHE_DIR"
- name: "Build 3, compressed cache, deleted build directory"
shell: bash
run: |
rm -rf build
source .ci/docker.sh
# Writes stay compressed, so this is the configuration a fix would ship.
export RUN_ARGS="$RUN_ARGS -e CCACHE_COMPRESS=1"
RUN $LEG_ARGS --ccache "$CCACHE_BOUND"
echo "=== cache after build 3"
du -sh "$CCACHE_DIR"