hermes-agent/.github/actions/profile/action.yml
ethernet 6c21f63c96 fix(ci): review fixes — PR-read-only buildx cache, per-arch profile labels
Address review findings on the ARC migration:

- docker.yml: WIF auth (and therefore Artifact Registry cache WRITES)
  now only runs on non-PR events. The build job runs PR-controlled code
  and the publish job reads the same buildcache ref, so a PR-writable
  cache was a layer-poisoning vector. PRs of any origin keep cache
  READS via the runner pod's GKE Workload Identity — that's where the
  15min -> 2-3min win comes from; main pushes repopulate writes.
- docker.yml: profile label is now docker-tests-<arch>. Both matrix
  legs uploaded resource-profile-docker-tests; upload-artifact v4+
  rejects the duplicate and continue-on-error swallowed it, silently
  dropping one arch's profile.
- actions/profile: run the wrapped command with bash -eo pipefail to
  match normal `run:` step semantics (a failing `source .venv/...`
  must fail the step, not fall through).
- js/e2e/site workflows: bake node22 into the node_modules cache keys
  so a future node-version bump can't restore stale native builds
  (node-pty, electron postinstall) against an unchanged lockfile.
- test_container_restart_stale_pid: forward deadline_s/interval_s to
  wait_for_log instead of silently dropping them.
- doctor.py: refresh a stale comment on the in-container docker branch.
2026-07-31 14:15:26 -04:00

91 lines
3.1 KiB
YAML

name: Profile a command (CPU/RAM/Disk)
description: >-
Run a shell command while sampling CPU, RAM, and disk IO every second.
Produces a resource-profile.json artifact per job so the CI timing
report can show per-job resource usage and identify bottlenecks.
inputs:
command:
description: Shell command to run (and profile).
required: true
label:
description: Label for this profile (e.g. "tests slice 1/8").
required: true
working-directory:
description: Directory to run in.
default: '.'
runs:
using: composite
steps:
- name: Start resource profiler
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
# Start profiler in background. It writes to resource-profile.json
# on SIGTERM (or when the command finishes and we signal it).
python3 scripts/ci/resource_profile.py \
--output resource-profile.json \
--label "$PROFILE_LABEL" &
echo $! > "$RUNNER_TEMP/profiler.pid"
env:
PROFILE_LABEL: ${{ inputs.label }}
- name: Run command
shell: bash
working-directory: ${{ inputs.working-directory }}
env:
_CMD: ${{ inputs.command }}
run: |
# -e / pipefail: match the semantics of a normal `run:` step
# (bash -e {0}) so a failing early line (e.g. `source .venv/...`)
# fails the step instead of silently running the rest.
bash -eo pipefail -c "$_CMD"
- name: Stop profiler and collect results
id: stop-profiler
if: always()
shell: bash
working-directory: ${{ inputs.working-directory }}
run: |
if [ -f "$RUNNER_TEMP/profiler.pid" ]; then
PID=$(cat "$RUNNER_TEMP/profiler.pid")
if kill -0 "$PID" 2>/dev/null; then
kill -TERM "$PID"
# Give it a moment to write the JSON
for i in 1 2 3 4 5; do
if kill -0 "$PID" 2>/dev/null; then
sleep 0.2
else
break
fi
done
kill -KILL "$PID" 2>/dev/null || true
fi
fi
# hashFiles() only matches inside the workspace, so surface file
# existence as a step output instead for the upload condition.
if [ -s resource-profile.json ]; then
echo "profile_written=true" >> "$GITHUB_OUTPUT"
else
echo "profile_written=false" >> "$GITHUB_OUTPUT"
fi
- name: Sanitize resource profile label
id: sanitize
if: always()
shell: bash
env:
PROFILE_LABEL: ${{ inputs.label }}
run: |
SAFE=$(printf '%s' "$PROFILE_LABEL" | sed -E 's/[^a-zA-Z0-9]+/-/g')
echo "safe_label=$SAFE" >> "$GITHUB_OUTPUT"
- name: Upload resource profile
if: always() && steps.stop-profiler.outputs.profile_written == 'true'
continue-on-error: true
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: resource-profile-${{ steps.sanitize.outputs.safe_label }}
path: ${{ inputs.working-directory }}/resource-profile.json
retention-days: 14