Skip to content
Open
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
7 changes: 7 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*.sh text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
*.py text eol=lf
*.json text eol=lf
*.jsonl text eol=lf
*.md text eol=lf
134 changes: 34 additions & 100 deletions .github/workflows/gas-benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ on:
paths:
- "stellar-lend/contracts/**"
- "stellar-lend/benchmarks/**"
- "scripts/gas_benchmark_report.py"
- ".github/workflows/gas-benchmarks.yml"
pull_request:
branches: ["main", "dev"]
paths:
- "stellar-lend/contracts/**"
- "stellar-lend/benchmarks/**"
- "scripts/gas_benchmark_report.py"
- ".github/workflows/gas-benchmarks.yml"
# Allow manual trigger for on-demand profiling
workflow_dispatch:
inputs:
compare_baseline:
description: "Compare against baseline (true/false)"
description: "Compare against baseline and enforce the 10% regression gate"
required: false
default: "true"

Expand All @@ -26,14 +27,15 @@ concurrency:
cancel-in-progress: true

jobs:
# ─────────────────────────────────────────────────────────────────────────────
# Gas Benchmark Job
# ─────────────────────────────────────────────────────────────────────────────
gas-benchmarks:
name: Gas Benchmarks — All Contracts
name: Gas Benchmarks
runs-on: ubuntu-latest
timeout-minutes: 30
env:
CARGO_TERM_COLOR: always
BENCHMARK_RESULTS: stellar-lend/benchmark-results.json
BENCHMARK_DASHBOARD: stellar-lend/benchmark-dashboard.md
BENCHMARK_HISTORY: stellar-lend/benchmark-history.jsonl
Comment on lines +36 to +38

steps:
- name: Checkout code
Expand All @@ -44,7 +46,7 @@ jobs:
with:
toolchain: stable

- name: Cache cargo registry & build
- name: Cache cargo registry and build artifacts
uses: actions/cache@v4
with:
path: |
Expand All @@ -59,118 +61,50 @@ jobs:
- name: Build benchmark suite
run: |
cd stellar-lend
cargo build --bin run_benchmarks --release 2>&1 | tee benchmark-build.log
echo "Build exit code: $?"
cargo build -p stellarlend-benchmarks --release 2>&1 | tee benchmark-build.log

- name: Run gas benchmarks
id: run_benchmarks
run: |
cd stellar-lend
cargo run --bin run_benchmarks -- \
cargo run -p stellarlend-benchmarks --bin run_benchmarks --release -- \
--output benchmark-results.json \
2>&1 | tee benchmark-output.log
echo "Benchmark exit code: $?"

- name: Run benchmarks with baseline comparison
id: baseline_check
if: ${{ github.event.inputs.compare_baseline != 'false' }}
- name: Generate dashboard and enforce gates
env:
COMPARE_BASELINE: ${{ inputs.compare_baseline || 'true' }}
run: |
cd stellar-lend
# Use baseline from repo if it exists and has results
BASELINE="benchmarks/baseline.json"
RESULTS=$(python3 -c "import json; d=json.load(open('$BASELINE')); print(len(d.get('results', [])))" 2>/dev/null || echo "0")

if [ "$RESULTS" -gt "0" ]; then
echo "Comparing against baseline ($RESULTS recorded operations)..."
cargo run --bin run_benchmarks -- \
--compare "$BASELINE" \
--output benchmark-results.json \
2>&1 | tee benchmark-comparison.log
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo "::error::Gas regression detected! See benchmark-comparison.log for details."
exit $EXIT_CODE
fi
echo "All operations within gas budgets."
else
echo "No baseline results found — skipping regression check."
echo "Run benchmarks locally and commit baseline.json to enable regression detection."
EXTRA_ARGS=""
if [ "$COMPARE_BASELINE" = "true" ]; then
EXTRA_ARGS="--baseline stellar-lend/benchmarks/baseline.json --fail-on-regression"
fi

- name: Generate benchmark summary
python3 scripts/gas_benchmark_report.py \
--results "$BENCHMARK_RESULTS" \
--coverage stellar-lend/benchmarks/public-functions.json \
--dashboard "$BENCHMARK_DASHBOARD" \
--history stellar-lend/benchmarks/history.jsonl \
--history-out "$BENCHMARK_HISTORY" \
--max-regression-pct 10 \
$EXTRA_ARGS

- name: Publish benchmark dashboard
if: always()
run: |
cd stellar-lend
if [ -f benchmark-results.json ]; then
echo "## Gas Benchmark Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Contract | Operations | Max Instructions | Avg Instructions | Over Budget |" >> $GITHUB_STEP_SUMMARY
echo "|----------|-----------|-----------------|-----------------|-------------|" >> $GITHUB_STEP_SUMMARY
python3 - <<'EOF'
import json, os

with open("benchmark-results.json") as f:
report = json.load(f)

summary = report.get("summary_by_contract", {})
with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as out:
for contract, s in sorted(summary.items()):
over = s.get("over_budget_count", 0)
status = "✗" if over > 0 else "✓"
out.write(
f"| {contract} | {s['total_operations']} | "
f"{s['max_instructions']:,} | {s['avg_instructions']:,} | "
f"{status} {over} |\n"
)

total = report["total_benchmarks"]
passed = report["passed"]
failed = report["failed"]
out.write(f"\n**Total:** {total} | **Passed:** {passed} | **Failed:** {failed}\n")
EOF
if [ -f "$BENCHMARK_DASHBOARD" ]; then
cat "$BENCHMARK_DASHBOARD" >> "$GITHUB_STEP_SUMMARY"
fi

- name: Upload benchmark results
- name: Upload benchmark artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: gas-benchmark-results-${{ github.sha }}
path: |
stellar-lend/benchmark-results.json
stellar-lend/benchmark-dashboard.md
stellar-lend/benchmark-history.jsonl
stellar-lend/benchmark-output.log
stellar-lend/benchmark-comparison.log
stellar-lend/benchmark-build.log
if-no-files-found: warn
retention-days: 90

- name: Upload build log
if: failure()
uses: actions/upload-artifact@v4
with:
name: benchmark-build-log
path: stellar-lend/benchmark-build.log

# ── Budget Alert Gate ──────────────────────────────────────────────────
- name: Enforce gas budget gate
if: always()
run: |
cd stellar-lend
if [ ! -f benchmark-results.json ]; then
echo "::error::benchmark-results.json not found — benchmarks may have failed to run."
exit 1
fi

FAILED=$(python3 -c "
import json
with open('benchmark-results.json') as f:
r = json.load(f)
over = [x for x in r['results'] if not x['within_budget'] and x['budget'] > 0]
for o in over:
print(f\" {o['operation']}: {o['instructions']:,} instructions (budget: {o['budget']:,})\")
print(len(over))
" | tail -1)

if [ "$FAILED" -gt "0" ]; then
echo "::error::$FAILED operation(s) exceeded gas budget. See benchmark-results.json for details."
exit 1
fi

echo "All operations within gas budgets."
154 changes: 56 additions & 98 deletions run-benchmarks.sh
Original file line number Diff line number Diff line change
@@ -1,132 +1,90 @@
#!/usr/bin/env bash
# run-benchmarks.sh — Local gas benchmark runner for StellarLend
#
# Usage:
# ./run-benchmarks.sh # Run all benchmarks
# ./run-benchmarks.sh --compare # Compare against baseline
# ./run-benchmarks.sh --update-baseline # Run and update baseline.json
# ./run-benchmarks.sh --help # Show help
# Local gas benchmark runner for StellarLend.

set -euo pipefail

# ── Colors ────────────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'

BENCH_DIR="stellar-lend"
BASELINE="stellar-lend/benchmarks/baseline.json"
OUTPUT="stellar-lend/benchmark-results.json"
BASELINE="$BENCH_DIR/benchmarks/baseline.json"
COVERAGE="$BENCH_DIR/benchmarks/public-functions.json"
HISTORY="$BENCH_DIR/benchmarks/history.jsonl"
OUTPUT="$BENCH_DIR/benchmark-results.json"
DASHBOARD="$BENCH_DIR/benchmark-dashboard.md"
HISTORY_OUT="$BENCH_DIR/benchmark-history.jsonl"

# ── Argument parsing ──────────────────────────────────────────────────────────
COMPARE=false
UPDATE_BASELINE=false
SHOW_HELP=false

for arg in "$@"; do
case $arg in
--compare) COMPARE=true ;;
case "$arg" in
--compare) COMPARE=true ;;
--update-baseline) UPDATE_BASELINE=true ;;
--help|-h) SHOW_HELP=true ;;
--help|-h) SHOW_HELP=true ;;
*)
echo "Unknown argument: $arg" >&2
exit 2
;;
esac
done

if $SHOW_HELP; then
echo ""
echo " StellarLend Gas Benchmark Runner"
echo ""
echo " Usage:"
echo " ./run-benchmarks.sh Run all benchmarks"
echo " ./run-benchmarks.sh --compare Compare against baseline (fail on regression)"
echo " ./run-benchmarks.sh --update-baseline Run and save results as new baseline"
echo ""
echo " Output:"
echo " benchmark-results.json Latest results (always written)"
echo " benchmarks/baseline.json Baseline for regression detection"
echo ""
cat <<'EOF'
StellarLend Gas Benchmark Runner

Usage:
./run-benchmarks.sh Run all benchmarks and generate dashboard
./run-benchmarks.sh --compare Fail on budget, coverage, or >10% baseline regression
./run-benchmarks.sh --update-baseline Run and save current results as the new baseline

Outputs:
stellar-lend/benchmark-results.json
stellar-lend/benchmark-dashboard.md
stellar-lend/benchmark-history.jsonl
EOF
exit 0
fi

# ── Prerequisites ─────────────────────────────────────────────────────────────
echo -e "${BLUE}╔══════════════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ StellarLend Gas Benchmark Suite ║${NC}"
echo -e "${BLUE}╚══════════════════════════════════════════════════════════╝${NC}"
echo ""
if ! command -v cargo >/dev/null 2>&1; then
echo "Rust/Cargo not found. Install from https://rustup.rs" >&2
exit 1
fi

if ! command -v cargo &>/dev/null; then
echo -e "${RED}✗ Rust/Cargo not found. Install from https://rustup.rs${NC}"
if ! command -v python3 >/dev/null 2>&1; then
echo "python3 not found. It is required for dashboard and regression gates." >&2
exit 1
fi

if [ ! -d "$BENCH_DIR" ]; then
echo -e "${RED}✗ stellar-lend directory not found. Run from project root.${NC}"
echo "stellar-lend directory not found. Run from the project root." >&2
exit 1
fi

# ── Build ─────────────────────────────────────────────────────────────────────
echo -e "${YELLOW}▶ Building benchmark suite...${NC}"
(cd "$BENCH_DIR" && cargo build --bin run_benchmarks --release 2>&1)
echo -e "${GREEN}✓ Build complete${NC}"
echo ""
echo "Building benchmark suite..."
(cd "$BENCH_DIR" && cargo build -p stellarlend-benchmarks --release)

# ── Run benchmarks ────────────────────────────────────────────────────────────
echo -e "${YELLOW}▶ Running gas benchmarks...${NC}"
echo ""
echo "Running gas benchmarks..."
(cd "$BENCH_DIR" && cargo run -p stellarlend-benchmarks --bin run_benchmarks --release -- --output "../$OUTPUT")

REPORT_ARGS=(
--results "$OUTPUT"
--coverage "$COVERAGE"
--dashboard "$DASHBOARD"
--history "$HISTORY"
--history-out "$HISTORY_OUT"
--max-regression-pct 10
)

if $COMPARE; then
RESULTS_COUNT=$(python3 -c "import json; d=json.load(open('$BASELINE')); print(len(d.get('results', [])))" 2>/dev/null || echo "0")
if [ "$RESULTS_COUNT" -gt "0" ]; then
echo -e "${CYAN} Comparing against baseline ($RESULTS_COUNT operations)...${NC}"
(cd "$BENCH_DIR" && cargo run --bin run_benchmarks --release -- \
--compare "../$BASELINE" \
--output "../$OUTPUT")
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo ""
echo -e "${RED}✗ Gas regression detected! Review benchmark-results.json${NC}"
exit $EXIT_CODE
fi
else
echo -e "${YELLOW} No baseline results found — running without comparison.${NC}"
echo -e "${YELLOW} Run with --update-baseline to create a baseline.${NC}"
(cd "$BENCH_DIR" && cargo run --bin run_benchmarks --release -- --output "../$OUTPUT")
fi
elif $UPDATE_BASELINE; then
echo -e "${CYAN} Running benchmarks and updating baseline...${NC}"
(cd "$BENCH_DIR" && cargo run --bin run_benchmarks --release -- --output "../$OUTPUT")
cp "$OUTPUT" "$BASELINE"
echo ""
echo -e "${GREEN}✓ Baseline updated: $BASELINE${NC}"
echo -e "${YELLOW} Commit this file to track gas usage over time.${NC}"
else
(cd "$BENCH_DIR" && cargo run --bin run_benchmarks --release -- --output "../$OUTPUT")
REPORT_ARGS+=(--baseline "$BASELINE" --fail-on-regression)
fi

echo ""
echo -e "${GREEN}✓ Benchmarks complete. Results: $OUTPUT${NC}"

# ── Quick summary ─────────────────────────────────────────────────────────────
if command -v python3 &>/dev/null && [ -f "$OUTPUT" ]; then
echo ""
python3 - <<'EOF'
import json
python3 scripts/gas_benchmark_report.py "${REPORT_ARGS[@]}"

with open("stellar-lend/benchmark-results.json") as f:
report = json.load(f)

total = report["total_benchmarks"]
passed = report["passed"]
failed = report["failed"]

print(f" Summary: {total} benchmarks | {passed} passed | {failed} failed")

if failed > 0:
print("\n Over-budget operations:")
for r in report["results"]:
if not r["within_budget"] and r["budget"] > 0:
print(f" ✗ {r['operation']}: {r['instructions']:,} (budget: {r['budget']:,})")
EOF
if $UPDATE_BASELINE; then
cp "$OUTPUT" "$BASELINE"
echo "Baseline updated: $BASELINE"
fi

echo "Benchmarks complete:"
echo " Results: $OUTPUT"
echo " Dashboard: $DASHBOARD"
Loading