From 5c4de5f1c9e9207fb8b37b9f85e0e95ddad041ed Mon Sep 17 00:00:00 2001 From: "Harper, Jason M" Date: Sun, 4 Jan 2026 10:36:17 -0800 Subject: [PATCH 1/7] cach tools build Signed-off-by: Harper, Jason M --- .github/workflows/build-test.yml | 17 +++++++ .gitignore | 1 + Makefile | 2 +- builder/build.sh | 81 ++++++++++++++++++++++++++++++-- 4 files changed, 97 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 571a9555..66f5c763 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -16,6 +16,23 @@ jobs: steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.7.1 + - name: Generate tools directory hash + id: tools-hash + run: | + HASH=$(find tools -type f \( -name "*.Dockerfile" -o -name "Makefile" -o -name "*.patch" \) -exec sha256sum {} \; | sort | sha256sum | cut -d' ' -f1) + echo "hash=$HASH" >> $GITHUB_OUTPUT + - name: Cache tool binaries + id: cache-tools + uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 + with: + path: tools-cache + key: perfspect-tools-binaries-${{ steps.tools-hash.outputs.hash }} + - name: Set cache hit flag + if: steps.cache-tools.outputs.cache-hit == 'true' + run: | + echo "TOOLS_CACHE_HIT=true" >> $GITHUB_ENV - name: build perfspect run: | builder/build.sh diff --git a/.gitignore b/.gitignore index f685c188..3fef3bae 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /debug_out /tools/bin /tools/bin-aarch64 +/tools-cache /dist /internal/script/resources/x86_64 /internal/script/resources/aarch64 diff --git a/Makefile b/Makefile index 9e87ec5b..b883884a 100644 --- a/Makefile +++ b/Makefile @@ -111,7 +111,7 @@ check_static: .PHONY: check_license check_license: @echo "Confirming source files have license headers..." - @for f in `find . -type f ! -path './perfspect_202*' ! -path './tools/bin/*' ! -path './tools/bin-aarch64/*' ! -path './internal/script/resources/*' ! -path './scripts/.venv/*' ! -path './test/output/*' ! -path './debug_out/*' ! -path './tools/perf-archive/*' ! -path './tools/avx-turbo/*' \( -name "*.go" -o -name "*.s" -o -name "*.html" -o -name "Makefile" -o -name "*.sh" -o -name "*.Dockerfile" -o -name "*.py" \)`; do \ + @for f in `find . -type f ! -path './perfspect_202*' ! -path './tools/bin/*' ! -path './tools/bin-aarch64/*' ! -path './tools-cache/*' ! -path './internal/script/resources/*' ! -path './scripts/.venv/*' ! -path './test/output/*' ! -path './debug_out/*' ! -path './tools/perf-archive/*' ! -path './tools/avx-turbo/*' \( -name "*.go" -o -name "*.s" -o -name "*.html" -o -name "Makefile" -o -name "*.sh" -o -name "*.Dockerfile" -o -name "*.py" \)`; do \ if ! grep -E 'SPDX-License-Identifier: BSD-3-Clause' "$$f" >/dev/null; then echo "Error: license not found: $$f"; fail=1; fi; \ done; if [ -n "$$fail" ]; then exit 1; fi diff --git a/builder/build.sh b/builder/build.sh index 216b749b..a2c3f834 100755 --- a/builder/build.sh +++ b/builder/build.sh @@ -8,11 +8,86 @@ set -ex TAG=v1 -# build tools image -docker build -f tools/build.Dockerfile --tag perfspect-tools:$TAG ./tools +# Determine if we're in GitHub Actions +if [ -n "$GITHUB_ACTIONS" ]; then + # Use buildx with GitHub Actions cache + CACHE_FROM="--cache-from type=gha,scope=perfspect-tools" + CACHE_TO="--cache-to type=gha,mode=max,scope=perfspect-tools" + BUILDER_CACHE_FROM="--cache-from type=gha,scope=perfspect-builder" + BUILDER_CACHE_TO="--cache-to type=gha,mode=max,scope=perfspect-builder" + BUILD_CMD="docker buildx build --load" +else + # Local build without cache export + CACHE_FROM="" + CACHE_TO="" + BUILDER_CACHE_FROM="" + BUILDER_CACHE_TO="" + BUILD_CMD="docker build" +fi + +# Check if we can use cached binaries +USE_CACHE="" +if [ "$TOOLS_CACHE_HIT" = "true" ]; then + # GitHub Actions cache hit + USE_CACHE="true" +elif [ -z "$SKIP_TOOLS_CACHE" ] && [ -d "tools-cache/bin" ]; then + # Local cache exists and not disabled + echo "Found local tools cache in tools-cache/" + echo "To force rebuild, run: SKIP_TOOLS_CACHE=1 builder/build.sh" + USE_CACHE="true" +fi + +# build tools image (or use cached binaries) +if [ "$USE_CACHE" = "true" ]; then + echo "Using cached tool binaries, creating minimal tools image" + # Create a minimal Dockerfile that packages the cached binaries + cat > /tmp/cached-tools.Dockerfile << 'EOF' +FROM scratch AS output +COPY tools-cache/bin /bin +COPY tools-cache/oss_source.tgz /oss_source.tgz +COPY tools-cache/oss_source.tgz.md5 /oss_source.tgz.md5 +EOF + docker build -f /tmp/cached-tools.Dockerfile -t perfspect-tools:$TAG . + rm /tmp/cached-tools.Dockerfile +else + echo "Building tools from source" + $BUILD_CMD -f tools/build.Dockerfile \ + $CACHE_FROM $CACHE_TO \ + --tag perfspect-tools:$TAG ./tools + + # Extract binaries for caching (both GitHub Actions and local) + if [ -z "$SKIP_TOOLS_CACHE" ]; then + echo "Extracting tool binaries to tools-cache/ for future builds" + rm -rf tools-cache + mkdir -p tools-cache + # Use a helper container to extract files from the scratch-based image + # Create a temporary Dockerfile that copies from the tools image + cat > /tmp/extract-tools.Dockerfile << EOF +FROM perfspect-tools:$TAG AS source +FROM busybox:latest +COPY --from=source /bin /output/bin +COPY --from=source /oss_source.tgz /output/ +COPY --from=source /oss_source.tgz.md5 /output/ +CMD ["true"] +EOF + # Build the extractor image + docker build -q -f /tmp/extract-tools.Dockerfile -t perfspect-tools-extractor:$TAG . > /dev/null + # Create container and extract files + CONTAINER_ID=$(docker create perfspect-tools-extractor:$TAG) + docker cp $CONTAINER_ID:/output/. tools-cache/ + docker rm $CONTAINER_ID > /dev/null + # Cleanup + docker rmi perfspect-tools-extractor:$TAG > /dev/null + rm /tmp/extract-tools.Dockerfile + echo "Tools cached locally. Next build will be faster!" + fi +fi # build the perfspect builder image -docker build -f builder/build.Dockerfile --build-arg TAG=$TAG --tag perfspect-builder:$TAG . +$BUILD_CMD -f builder/build.Dockerfile \ + --build-arg TAG=$TAG \ + $BUILDER_CACHE_FROM $BUILDER_CACHE_TO \ + --tag perfspect-builder:$TAG . # build perfspect using the builder image docker container run \ From 28bca9ee02f8e433440ebb130e093d689975dec5 Mon Sep 17 00:00:00 2001 From: "Harper, Jason M" Date: Sun, 4 Jan 2026 10:54:22 -0800 Subject: [PATCH 2/7] fix: update build command to ensure access to local Docker daemon images Signed-off-by: Harper, Jason M --- builder/build.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/builder/build.sh b/builder/build.sh index a2c3f834..26c3f2ef 100755 --- a/builder/build.sh +++ b/builder/build.sh @@ -84,9 +84,11 @@ EOF fi # build the perfspect builder image -$BUILD_CMD -f builder/build.Dockerfile \ +# Note: Always use regular docker build (not buildx) because it needs access to the +# locally built perfspect-tools:$TAG image. Buildx runs in an isolated builder context +# that doesn't have access to local Docker daemon images by default. +docker build -f builder/build.Dockerfile \ --build-arg TAG=$TAG \ - $BUILDER_CACHE_FROM $BUILDER_CACHE_TO \ --tag perfspect-builder:$TAG . # build perfspect using the builder image From cd046714a75867431d6bfb4ec41096f640ea9949 Mon Sep 17 00:00:00 2001 From: "Harper, Jason M" Date: Mon, 5 Jan 2026 10:24:08 -0800 Subject: [PATCH 3/7] more robust verification of tools cache Signed-off-by: Harper, Jason M --- .github/workflows/build-test.yml | 7 +--- Makefile | 7 +++- builder/build.sh | 69 +++++++++++++++++++++++--------- 3 files changed, 58 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 66f5c763..4f706373 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -18,17 +18,12 @@ jobs: uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Docker Buildx uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.7.1 - - name: Generate tools directory hash - id: tools-hash - run: | - HASH=$(find tools -type f \( -name "*.Dockerfile" -o -name "Makefile" -o -name "*.patch" \) -exec sha256sum {} \; | sort | sha256sum | cut -d' ' -f1) - echo "hash=$HASH" >> $GITHUB_OUTPUT - name: Cache tool binaries id: cache-tools uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 with: path: tools-cache - key: perfspect-tools-binaries-${{ steps.tools-hash.outputs.hash }} + key: perfspect-tools-binaries-${{ hashFiles('tools/Makefile', 'tools/**/*.Dockerfile', 'tools/**/*.patch') }} - name: Set cache hit flag if: steps.cache-tools.outputs.cache-hit == 'true' run: | diff --git a/Makefile b/Makefile index b883884a..55c09a79 100644 --- a/Makefile +++ b/Makefile @@ -162,9 +162,14 @@ sweep: rm -f perfspect.log .PHONY: clean -clean: sweep +clean: sweep clean-tools-cache @echo "Cleaning up..." rm -f perfspect rm -f perfspect-aarch64 sudo rm -rf dist rm -rf internal/script/resources + +.PHONY: clean-tools-cache +clean-tools-cache: + @echo "Removing cached tool binaries..." + rm -rf tools-cache diff --git a/builder/build.sh b/builder/build.sh index 26c3f2ef..bde2a605 100755 --- a/builder/build.sh +++ b/builder/build.sh @@ -4,24 +4,52 @@ # run this script from repo's root directory -set -ex +set -e TAG=v1 +CACHE_DIR="tools-cache" +# A few files that must be present in the cache for it to be valid +REQUIRED_CACHE_FILES=( + "$CACHE_DIR/bin/x86_64/perf" + "$CACHE_DIR/bin/aarch64/perf" + "$CACHE_DIR/bin/x86_64/fio" + "$CACHE_DIR/bin/aarch64/fio" + "$CACHE_DIR/oss_source.tgz" + "$CACHE_DIR/oss_source.tgz.md5" +) + +cache_ready() { + local missing=0 + for f in "${REQUIRED_CACHE_FILES[@]}"; do + if [ ! -s "$f" ]; then + echo "Cached tools missing required file: $f" + missing=1 + fi + done + if [ "$missing" -eq 1 ]; then + return 1 + fi + return 0 +} + +invalidate_cache() { + if [ -d "$CACHE_DIR" ]; then + echo "Removing incomplete cached tools at $CACHE_DIR" + rm -rf "$CACHE_DIR" + fi +} + # Determine if we're in GitHub Actions if [ -n "$GITHUB_ACTIONS" ]; then # Use buildx with GitHub Actions cache CACHE_FROM="--cache-from type=gha,scope=perfspect-tools" CACHE_TO="--cache-to type=gha,mode=max,scope=perfspect-tools" - BUILDER_CACHE_FROM="--cache-from type=gha,scope=perfspect-builder" - BUILDER_CACHE_TO="--cache-to type=gha,mode=max,scope=perfspect-builder" BUILD_CMD="docker buildx build --load" else # Local build without cache export CACHE_FROM="" CACHE_TO="" - BUILDER_CACHE_FROM="" - BUILDER_CACHE_TO="" BUILD_CMD="docker build" fi @@ -30,36 +58,41 @@ USE_CACHE="" if [ "$TOOLS_CACHE_HIT" = "true" ]; then # GitHub Actions cache hit USE_CACHE="true" -elif [ -z "$SKIP_TOOLS_CACHE" ] && [ -d "tools-cache/bin" ]; then +elif [ -z "$SKIP_TOOLS_CACHE" ] && [ -d "$CACHE_DIR/bin" ]; then # Local cache exists and not disabled - echo "Found local tools cache in tools-cache/" + echo "Found local tools cache in $CACHE_DIR/" echo "To force rebuild, run: SKIP_TOOLS_CACHE=1 builder/build.sh" - USE_CACHE="true" + if cache_ready; then + USE_CACHE="true" + else + echo "Local tools cache is incomplete; it will be discarded and rebuilt." + invalidate_cache + fi fi # build tools image (or use cached binaries) if [ "$USE_CACHE" = "true" ]; then echo "Using cached tool binaries, creating minimal tools image" # Create a minimal Dockerfile that packages the cached binaries - cat > /tmp/cached-tools.Dockerfile << 'EOF' + cat > /tmp/cached-tools.Dockerfile << EOF FROM scratch AS output -COPY tools-cache/bin /bin -COPY tools-cache/oss_source.tgz /oss_source.tgz -COPY tools-cache/oss_source.tgz.md5 /oss_source.tgz.md5 +COPY $CACHE_DIR/bin /bin +COPY $CACHE_DIR/oss_source.tgz /oss_source.tgz +COPY $CACHE_DIR/oss_source.tgz.md5 /oss_source.tgz.md5 EOF docker build -f /tmp/cached-tools.Dockerfile -t perfspect-tools:$TAG . rm /tmp/cached-tools.Dockerfile else echo "Building tools from source" $BUILD_CMD -f tools/build.Dockerfile \ - $CACHE_FROM $CACHE_TO \ + "$CACHE_FROM" "$CACHE_TO" \ --tag perfspect-tools:$TAG ./tools # Extract binaries for caching (both GitHub Actions and local) if [ -z "$SKIP_TOOLS_CACHE" ]; then echo "Extracting tool binaries to tools-cache/ for future builds" - rm -rf tools-cache - mkdir -p tools-cache + rm -rf "$CACHE_DIR" + mkdir -p "$CACHE_DIR" # Use a helper container to extract files from the scratch-based image # Create a temporary Dockerfile that copies from the tools image cat > /tmp/extract-tools.Dockerfile << EOF @@ -74,8 +107,8 @@ EOF docker build -q -f /tmp/extract-tools.Dockerfile -t perfspect-tools-extractor:$TAG . > /dev/null # Create container and extract files CONTAINER_ID=$(docker create perfspect-tools-extractor:$TAG) - docker cp $CONTAINER_ID:/output/. tools-cache/ - docker rm $CONTAINER_ID > /dev/null + docker cp "$CONTAINER_ID":/output/. "$CACHE_DIR"/ + docker rm "$CONTAINER_ID" > /dev/null # Cleanup docker rmi perfspect-tools-extractor:$TAG > /dev/null rm /tmp/extract-tools.Dockerfile @@ -97,4 +130,4 @@ docker container run \ -w /localrepo \ --rm \ perfspect-builder:$TAG \ - make dist \ No newline at end of file + make dist From e3b21b83db9c65e1107ef881adde50382a0867b4 Mon Sep 17 00:00:00 2001 From: "Harper, Jason M" Date: Mon, 5 Jan 2026 10:27:35 -0800 Subject: [PATCH 4/7] quoting in shell Signed-off-by: Harper, Jason M --- builder/build.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builder/build.sh b/builder/build.sh index bde2a605..2f2c3b37 100755 --- a/builder/build.sh +++ b/builder/build.sh @@ -85,7 +85,7 @@ EOF else echo "Building tools from source" $BUILD_CMD -f tools/build.Dockerfile \ - "$CACHE_FROM" "$CACHE_TO" \ + $CACHE_FROM $CACHE_TO \ --tag perfspect-tools:$TAG ./tools # Extract binaries for caching (both GitHub Actions and local) @@ -107,8 +107,8 @@ EOF docker build -q -f /tmp/extract-tools.Dockerfile -t perfspect-tools-extractor:$TAG . > /dev/null # Create container and extract files CONTAINER_ID=$(docker create perfspect-tools-extractor:$TAG) - docker cp "$CONTAINER_ID":/output/. "$CACHE_DIR"/ - docker rm "$CONTAINER_ID" > /dev/null + docker cp $CONTAINER_ID:/output/. "$CACHE_DIR"/ + docker rm $CONTAINER_ID > /dev/null # Cleanup docker rmi perfspect-tools-extractor:$TAG > /dev/null rm /tmp/extract-tools.Dockerfile From 287e365c4a4b401eb88d9bfb3dbf93c14e2b1911 Mon Sep 17 00:00:00 2001 From: "Harper, Jason M" Date: Mon, 5 Jan 2026 11:27:26 -0800 Subject: [PATCH 5/7] cache go tools Signed-off-by: Harper, Jason M --- builder/build.Dockerfile | 20 +++++++++++++++----- builder/build.sh | 21 +++++++++++++++------ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/builder/build.Dockerfile b/builder/build.Dockerfile index da46ad0e..55eab808 100644 --- a/builder/build.Dockerfile +++ b/builder/build.Dockerfile @@ -15,14 +15,24 @@ FROM ${REGISTRY}${PREFIX}perfspect-tools:${TAG} AS tools # STAGE 2 - image contains perfspect's Go components build environment FROM golang:1.25.5@sha256:20b91eda7a9627c127c0225b0d4e8ec927b476fa4130c6760928b849d769c149 +# install system dependencies +RUN apt-get update && apt-get install -y jq +# allow git to operate in the mounted repository regardless of the user +RUN git config --global --add safe.directory /localrepo +# pre-install Go tools used by make check +RUN go install honnef.co/go/tools/cmd/staticcheck@latest && \ + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest && \ + go install golang.org/x/vuln/cmd/govulncheck@latest && \ + go install github.com/securego/gosec/v2/cmd/gosec@latest # copy the tools binaries and source from the previous stage RUN mkdir /prebuilt RUN mkdir /prebuilt/tools COPY --from=tools /bin/ /prebuilt/tools COPY --from=tools /oss_source.tgz /prebuilt/ COPY --from=tools /oss_source.tgz.md5 /prebuilt/ -# allow git to operate in the mounted repository regardless of the user -RUN git config --global --add safe.directory /localrepo -# install jq as it is used in the Makefile to create the manifest -RUN apt-get update -RUN apt-get install -y jq +# pre-download Go module dependencies (changes when go.mod/go.sum change) +WORKDIR /tmp/deps +COPY go.mod go.sum ./ +RUN go mod download + +WORKDIR / \ No newline at end of file diff --git a/builder/build.sh b/builder/build.sh index 2f2c3b37..d4d6061b 100755 --- a/builder/build.sh +++ b/builder/build.sh @@ -117,12 +117,21 @@ EOF fi # build the perfspect builder image -# Note: Always use regular docker build (not buildx) because it needs access to the -# locally built perfspect-tools:$TAG image. Buildx runs in an isolated builder context -# that doesn't have access to local Docker daemon images by default. -docker build -f builder/build.Dockerfile \ - --build-arg TAG=$TAG \ - --tag perfspect-builder:$TAG . +if [ -n "$GITHUB_ACTIONS" ]; then + # In GitHub Actions, use buildx with cache and explicit build context for tools image + # The --build-context flag allows buildx to access the locally built perfspect-tools image + docker buildx build --load -f builder/build.Dockerfile \ + --build-arg TAG=$TAG \ + --build-context perfspect-tools-context=docker-image://perfspect-tools:$TAG \ + --cache-from type=gha,scope=perfspect-builder \ + --cache-to type=gha,mode=max,scope=perfspect-builder \ + --tag perfspect-builder:$TAG . +else + # Local builds use regular docker build (faster, automatically has access to local images) + docker build -f builder/build.Dockerfile \ + --build-arg TAG=$TAG \ + --tag perfspect-builder:$TAG . +fi # build perfspect using the builder image docker container run \ From e16ab92d979c19e98310af335eb920cb7c1977e6 Mon Sep 17 00:00:00 2001 From: "Harper, Jason M" Date: Mon, 5 Jan 2026 11:32:27 -0800 Subject: [PATCH 6/7] use regular docker build Signed-off-by: Harper, Jason M --- builder/build.sh | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/builder/build.sh b/builder/build.sh index d4d6061b..0154dd23 100755 --- a/builder/build.sh +++ b/builder/build.sh @@ -117,21 +117,12 @@ EOF fi # build the perfspect builder image -if [ -n "$GITHUB_ACTIONS" ]; then - # In GitHub Actions, use buildx with cache and explicit build context for tools image - # The --build-context flag allows buildx to access the locally built perfspect-tools image - docker buildx build --load -f builder/build.Dockerfile \ - --build-arg TAG=$TAG \ - --build-context perfspect-tools-context=docker-image://perfspect-tools:$TAG \ - --cache-from type=gha,scope=perfspect-builder \ - --cache-to type=gha,mode=max,scope=perfspect-builder \ - --tag perfspect-builder:$TAG . -else - # Local builds use regular docker build (faster, automatically has access to local images) - docker build -f builder/build.Dockerfile \ - --build-arg TAG=$TAG \ - --tag perfspect-builder:$TAG . -fi +# Note: Use regular docker build (not buildx) because it needs access to the +# locally built perfspect-tools:$TAG image. The Go module and tool caching +# is handled via Docker's built-in layer caching. +docker build -f builder/build.Dockerfile \ + --build-arg TAG=$TAG \ + --tag perfspect-builder:$TAG . # build perfspect using the builder image docker container run \ From 35ceec3343150f8bd6a4b45a22f953f5ee0d0023 Mon Sep 17 00:00:00 2001 From: "Harper, Jason M" Date: Mon, 5 Jan 2026 12:38:18 -0800 Subject: [PATCH 7/7] eliminate docker build warning Signed-off-by: Harper, Jason M --- builder/build.Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/builder/build.Dockerfile b/builder/build.Dockerfile index 55eab808..a92421fa 100644 --- a/builder/build.Dockerfile +++ b/builder/build.Dockerfile @@ -7,11 +7,12 @@ # build perfspect: # $ docker run --rm -v "$PWD":/localrepo -w /localrepo perfspect-builder:v1 make dist -ARG REGISTRY= -ARG PREFIX= -ARG TAG= +ARG REGISTRY +ARG PREFIX +ARG TAG=v1 +ARG TOOLS_IMAGE=${REGISTRY}${PREFIX}perfspect-tools:${TAG} # STAGE 1 - image contains pre-built tools components, rebuild the image to rebuild the tools components -FROM ${REGISTRY}${PREFIX}perfspect-tools:${TAG} AS tools +FROM ${TOOLS_IMAGE} AS tools # STAGE 2 - image contains perfspect's Go components build environment FROM golang:1.25.5@sha256:20b91eda7a9627c127c0225b0d4e8ec927b476fa4130c6760928b849d769c149