perf(ci): reduce GitHub Actions costs ~60-65% across all workflows

Analysis of Feb 17 data showed 400+ workflow runs/day consuming ~398 billable minutes (~200 hours/month projected). Implemented targeted optimizations:

High-impact changes:

- sec-audit.yml: add path filters (Cargo.toml, src/**, crates/**, deny.toml); skip docs-only PRs

- test-benchmarks.yml: move from every-push-to-main to weekly schedule; retention 30d -> 7d

- pub-docker-img.yml: tighten PR smoke build path filters to Docker-specific files only

- sec-codeql.yml: reduce from twice-daily (14 runs/week) to weekly

Medium-impact changes:

- ci-run.yml: merge lint + lint-strict-delta into single job; drop --release from smoke build

- feature-matrix.yml: remove push trigger (weekly-only); remove redundant cargo test step

- dependabot.yml: monthly instead of weekly; reduce PR limits from 11 to 5/month; group all deps

Runner cost savings:

- Switch 6 lightweight API-only workflows to ubuntu-latest (PR Labeler, Intake, Auto Responder, Check Stale, Check Status, Sync Contributors)

- pr-check-status.yml: reduce from every 12h to daily

New files:

- docs/ci-cost-optimization.md: comprehensive analysis and revised architecture documentation

- scripts/ci/fetch_actions_data.py: reusable GitHub Actions cost analysis script

Estimated impact: daily billable minutes ~400 -> ~120-150 (60-65%% reduction), monthly hours ~200 -> ~60-75, Dependabot PRs ~44/month -> ~5 (89%% reduction)
This commit is contained in:
Alex Gorevski 2026-02-18 11:26:09 -08:00
parent 8f7d879fd5
commit 44725da08c
15 changed files with 512 additions and 85 deletions

View file

@ -4,13 +4,13 @@ updates:
- package-ecosystem: cargo
directory: "/"
schedule:
interval: weekly
interval: monthly
target-branch: main
open-pull-requests-limit: 5
open-pull-requests-limit: 3
labels:
- "dependencies"
groups:
rust-minor-patch:
rust-all:
patterns:
- "*"
update-types:
@ -20,14 +20,31 @@ updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
interval: monthly
target-branch: main
open-pull-requests-limit: 3
open-pull-requests-limit: 1
labels:
- "ci"
- "dependencies"
groups:
actions-minor-patch:
actions-all:
patterns:
- "*"
update-types:
- minor
- patch
- package-ecosystem: docker
directory: "/"
schedule:
interval: monthly
target-branch: main
open-pull-requests-limit: 1
labels:
- "ci"
- "dependencies"
groups:
docker-all:
patterns:
- "*"
update-types:

View file

@ -41,25 +41,7 @@ jobs:
run: ./scripts/ci/detect_change_scope.sh
lint:
name: Lint Gate (Format + Clippy)
needs: [changes]
if: needs.changes.outputs.rust_changed == 'true' && (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci:full'))
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 20
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
with:
toolchain: 1.92.0
components: rustfmt, clippy
- uses: useblacksmith/rust-cache@f53e7f127245d2a269b3d90879ccf259876842d5 # v3
- name: Run rust quality gate
run: ./scripts/ci/rust_quality_gate.sh
lint-strict-delta:
name: Lint Gate (Strict Delta)
name: Lint Gate (Format + Clippy + Strict Delta)
needs: [changes]
if: needs.changes.outputs.rust_changed == 'true' && (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci:full'))
runs-on: blacksmith-2vcpu-ubuntu-2404
@ -71,8 +53,10 @@ jobs:
- uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
with:
toolchain: 1.92.0
components: clippy
components: rustfmt, clippy
- uses: useblacksmith/rust-cache@f53e7f127245d2a269b3d90879ccf259876842d5 # v3
- name: Run rust quality gate
run: ./scripts/ci/rust_quality_gate.sh
- name: Run strict lint delta gate
env:
BASE_SHA: ${{ needs.changes.outputs.base_sha }}
@ -80,8 +64,8 @@ jobs:
test:
name: Test
needs: [changes, lint, lint-strict-delta]
if: needs.changes.outputs.rust_changed == 'true' && (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci:full')) && needs.lint.result == 'success' && needs.lint-strict-delta.result == 'success'
needs: [changes, lint]
if: needs.changes.outputs.rust_changed == 'true' && (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'ci:full')) && needs.lint.result == 'success'
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 30
steps:
@ -106,8 +90,8 @@ jobs:
with:
toolchain: 1.92.0
- uses: useblacksmith/rust-cache@f53e7f127245d2a269b3d90879ccf259876842d5 # v3
- name: Build release binary
run: cargo build --release --locked --verbose
- name: Build binary (smoke check)
run: cargo build --locked --verbose
docs-only:
name: Docs-Only Fast Path
@ -185,7 +169,7 @@ jobs:
lint-feedback:
name: Lint Feedback
if: github.event_name == 'pull_request'
needs: [changes, lint, lint-strict-delta, docs-quality]
needs: [changes, lint, docs-quality]
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
contents: read
@ -201,7 +185,7 @@ jobs:
RUST_CHANGED: ${{ needs.changes.outputs.rust_changed }}
DOCS_CHANGED: ${{ needs.changes.outputs.docs_changed }}
LINT_RESULT: ${{ needs.lint.result }}
LINT_DELTA_RESULT: ${{ needs.lint-strict-delta.result }}
LINT_DELTA_RESULT: ${{ needs.lint.result }}
DOCS_RESULT: ${{ needs.docs-quality.result }}
with:
script: |
@ -231,7 +215,7 @@ jobs:
ci-required:
name: CI Required Gate
if: always()
needs: [changes, lint, lint-strict-delta, test, build, docs-only, non-rust, docs-quality, lint-feedback, workflow-owner-approval]
needs: [changes, lint, test, build, docs-only, non-rust, docs-quality, lint-feedback, workflow-owner-approval]
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Enforce required status
@ -276,7 +260,7 @@ jobs:
fi
lint_result="${{ needs.lint.result }}"
lint_strict_delta_result="${{ needs.lint-strict-delta.result }}"
lint_strict_delta_result="${{ needs.lint.result }}"
test_result="${{ needs.test.result }}"
build_result="${{ needs.build.result }}"

View file

@ -1,12 +1,6 @@
name: Feature Matrix
on:
push:
branches: [main]
paths:
- "Cargo.toml"
- "Cargo.lock"
- "src/**"
schedule:
- cron: "30 4 * * 1" # Weekly Monday 4:30am UTC
workflow_dispatch:
@ -61,6 +55,3 @@ jobs:
- name: Check feature combination
run: cargo check --locked ${{ matrix.args }}
- name: Test feature combination
run: cargo test --locked ${{ matrix.args }}

View file

@ -15,16 +15,7 @@ jobs:
(github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'labeled' || github.event.action == 'unlabeled')) ||
(github.event_name == 'pull_request_target' &&
(github.event.action == 'labeled' || github.event.action == 'unlabeled'))
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Apply contributor tier label for issue author
runs-on: ubuntu-latest
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
env:
LABEL_POLICY_PATH: .github/label-policy.json
@ -34,7 +25,7 @@ jobs:
await script({ github, context, core });
first-interaction:
if: github.event.action == 'opened'
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
@ -65,7 +56,7 @@ jobs:
labeled-routes:
if: github.event.action == 'labeled'
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
permissions:
contents: read
issues: write

View file

@ -12,9 +12,7 @@ jobs:
permissions:
issues: write
pull-requests: write
runs-on: blacksmith-2vcpu-ubuntu-2404
steps:
- name: Mark stale issues and pull requests
runs-on: ubuntu-latest
uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -2,7 +2,7 @@ name: PR Check Status
on:
schedule:
- cron: "15 */12 * * *"
- cron: "15 8 * * *" # Once daily at 8:15am UTC
workflow_dispatch:
permissions: {}
@ -13,12 +13,7 @@ concurrency:
jobs:
nudge-stale-prs:
runs-on: blacksmith-2vcpu-ubuntu-2404
permissions:
contents: read
pull-requests: write
issues: write
env:
runs-on: ubuntu-latest
STALE_HOURS: "48"
steps:
- name: Checkout repository

View file

@ -16,13 +16,7 @@ permissions:
jobs:
intake:
name: Intake Checks
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Run safe PR intake checks
runs-on: ubuntu-latest
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
script: |

View file

@ -25,8 +25,7 @@ permissions:
jobs:
label:
runs-on: blacksmith-2vcpu-ubuntu-2404
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

View file

@ -21,13 +21,8 @@ on:
paths:
- "Dockerfile"
- ".dockerignore"
- "Cargo.toml"
- "Cargo.lock"
- "docker-compose.yml"
- "rust-toolchain.toml"
- "src/**"
- "crates/**"
- "benches/**"
- "firmware/**"
- "dev/config.template.toml"
- ".github/workflows/pub-docker-img.yml"
workflow_dispatch:

View file

@ -3,8 +3,20 @@ name: Sec Audit
on:
push:
branches: [main]
paths:
- "Cargo.toml"
- "Cargo.lock"
- "src/**"
- "crates/**"
- "deny.toml"
pull_request:
branches: [main]
paths:
- "Cargo.toml"
- "Cargo.lock"
- "src/**"
- "crates/**"
- "deny.toml"
schedule:
- cron: "0 6 * * 1" # Weekly on Monday 6am UTC

View file

@ -2,7 +2,7 @@ name: Sec CodeQL
on:
schedule:
- cron: "0 6,18 * * *" # Twice daily at 6am and 6pm UTC
- cron: "0 6 * * 1" # Weekly Monday 6am UTC
workflow_dispatch:
concurrency:

View file

@ -17,7 +17,7 @@ permissions:
jobs:
update-notice:
name: Update NOTICE with new contributors
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

View file

@ -1,8 +1,8 @@
name: Test Benchmarks
on:
push:
branches: [main]
schedule:
- cron: "0 3 * * 1" # Weekly Monday 3am UTC
workflow_dispatch:
concurrency:
@ -39,7 +39,7 @@ jobs:
path: |
target/criterion/
benchmark_output.txt
retention-days: 30
retention-days: 7
- name: Post benchmark summary on PR
if: github.event_name == 'pull_request'