Skip to content

Commit 980a424

Browse files
committed
Initial benchmark workflow
1 parent 16fc394 commit 980a424

File tree

1 file changed

+283
-0
lines changed

1 file changed

+283
-0
lines changed

.github/workflows/benchmark.yml

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
name: Benchmark Workflow
2+
3+
on:
4+
# https://github.com/mxschmitt/action-tmate?tab=readme-ov-file#manually-triggered-debug
5+
workflow_dispatch:
6+
inputs:
7+
debug_enabled:
8+
description: 'Enable SSH access (⚠️ Security Risk - read workflow comments)'
9+
required: false
10+
default: false
11+
type: boolean
12+
rate:
13+
description: 'Requests per second (use "max" for maximum throughput)'
14+
required: false
15+
default: '50'
16+
type: string
17+
duration_sec:
18+
description: 'Duration in seconds'
19+
required: false
20+
default: 10
21+
type: number
22+
vus:
23+
description: 'Virtual users for k6'
24+
required: false
25+
default: 100
26+
type: number
27+
tools:
28+
description: 'Comma-separated list of tools to run'
29+
required: false
30+
default: 'fortio,vegeta,k6'
31+
type: string
32+
push:
33+
branches:
34+
- main
35+
36+
env:
37+
FORTIO_VERSION: "1.72.0"
38+
K6_VERSION: "1.3.0"
39+
VEGETA_VERSION: "12.12.0"
40+
# Benchmark defaults (overridden by workflow_dispatch inputs)
41+
RATE: ${{ github.event.inputs.rate || '50' }}
42+
DURATION_SEC: ${{ github.event.inputs.duration_sec || '10' }}
43+
VUS: ${{ github.event.inputs.vus || '100' }}
44+
TOOLS: ${{ github.event.inputs.tools || 'fortio,vegeta,k6' }}
45+
46+
jobs:
47+
benchmark:
48+
runs-on: ubuntu-latest
49+
50+
steps:
51+
# ============================================
52+
# STEP 1: CHECKOUT CODE
53+
# ============================================
54+
- name: Checkout repository
55+
uses: actions/checkout@v4
56+
57+
# ============================================
58+
# STEP 2: OPTIONAL SSH ACCESS
59+
# ============================================
60+
# NOTE: Interactive confirmation is not possible in GitHub Actions.
61+
# As a secure workaround, SSH access is gated by the workflow_dispatch
62+
# input variable 'debug_enabled' which defaults to false.
63+
# Users must explicitly set this to true to enable SSH.
64+
65+
- name: SSH Warning
66+
if: ${{ github.event.inputs.debug_enabled }}
67+
run: |
68+
echo "⚠️ ⚠️ ⚠️ SSH ACCESS ENABLED ⚠️ ⚠️ ⚠️"
69+
echo ""
70+
echo "SECURITY NOTICE:"
71+
echo " - SSH access exposes your GitHub Actions runner"
72+
echo " - Only proceed if you understand and accept the risks"
73+
echo " - Do NOT store secrets or sensitive data on the runner"
74+
echo " - Access is limited to the workflow initiator only"
75+
echo " - The session will remain open until manually terminated"
76+
echo ""
77+
echo "⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️"
78+
79+
- name: Setup SSH access (if enabled)
80+
if: ${{ github.event.inputs.debug_enabled }}
81+
uses: mxschmitt/action-tmate@v3
82+
with:
83+
detached: true
84+
limit-access-to-actor: true # Only workflow trigger can access
85+
86+
# ============================================
87+
# STEP 3: INSTALL BENCHMARKING TOOLS
88+
# ============================================
89+
90+
- name: Add tools directory to PATH
91+
run: |
92+
mkdir -p ~/bin
93+
echo "$HOME/bin" >> $GITHUB_PATH
94+
95+
- name: Cache Fortio binary
96+
id: cache-fortio
97+
uses: actions/cache@v4
98+
with:
99+
path: ~/bin/fortio
100+
key: fortio-${{ runner.os }}-${{ runner.arch }}-${{ env.FORTIO_VERSION }}
101+
102+
- name: Install Fortio
103+
if: steps.cache-fortio.outputs.cache-hit != 'true'
104+
run: |
105+
echo "📦 Installing Fortio v${FORTIO_VERSION}"
106+
107+
# Download and extract fortio binary
108+
wget -q https://github.com/fortio/fortio/releases/download/v${FORTIO_VERSION}/fortio-linux_amd64-${FORTIO_VERSION}.tgz
109+
tar -xzf fortio-linux_amd64-${FORTIO_VERSION}.tgz
110+
111+
# Store in cache directory
112+
mv fortio ~/bin/
113+
114+
- name: Cache Vegeta binary
115+
id: cache-vegeta
116+
uses: actions/cache@v4
117+
with:
118+
path: ~/bin/vegeta
119+
key: vegeta-${{ runner.os }}-${{ runner.arch }}-${{ env.VEGETA_VERSION }}
120+
121+
- name: Install Vegeta
122+
if: steps.cache-vegeta.outputs.cache-hit != 'true'
123+
run: |
124+
echo "📦 Installing Vegeta v${VEGETA_VERSION}"
125+
126+
# Download and extract vegeta binary
127+
wget -q https://github.com/tsenart/vegeta/releases/download/v${VEGETA_VERSION}/vegeta_${VEGETA_VERSION}_linux_amd64.tar.gz
128+
tar -xzf vegeta_${VEGETA_VERSION}_linux_amd64.tar.gz
129+
130+
# Store in cache directory
131+
mv vegeta ~/bin/
132+
133+
- name: Setup k6
134+
uses: grafana/setup-k6-action@v1
135+
with:
136+
k6-version: v${{ env.K6_VERSION }}
137+
138+
# ============================================
139+
# STEP 4: START APPLICATION SERVER
140+
# ============================================
141+
142+
- name: Prepare production assets
143+
run: |
144+
set -e # Exit on any error
145+
echo "🔨 Building production assets..."
146+
cd spec/dummy
147+
148+
if ! bin/prod-assets; then
149+
echo "❌ ERROR: Failed to build production assets"
150+
exit 1
151+
fi
152+
153+
echo "✅ Production assets built successfully"
154+
155+
- name: Start production server
156+
run: |
157+
set -e # Exit on any error
158+
echo "🚀 Starting production server..."
159+
cd spec/dummy
160+
161+
# Start server in background
162+
bin/prod &
163+
SERVER_PID=$!
164+
echo "Server started with PID: ${SERVER_PID}"
165+
166+
# Wait for server to be ready (max 30 seconds)
167+
echo "⏳ Waiting for server to be ready..."
168+
for i in {1..30}; do
169+
if curl -sf http://localhost:3001 > /dev/null 2>&1; then
170+
echo "✅ Server is ready and responding"
171+
exit 0
172+
fi
173+
echo " Attempt $i/30: Server not ready yet..."
174+
sleep 1
175+
done
176+
177+
echo "❌ ERROR: Server failed to start within 30 seconds"
178+
exit 1
179+
180+
# ============================================
181+
# STEP 5: RUN BENCHMARKS
182+
# ============================================
183+
184+
- name: Execute benchmark suite
185+
run: |
186+
set -e # Exit on any error
187+
echo "🏃 Running benchmark suite..."
188+
echo "Script: spec/performance/bench.sh"
189+
echo ""
190+
echo "Benchmark parameters:"
191+
echo " - RATE: ${RATE}"
192+
echo " - DURATION_SEC: ${DURATION_SEC}"
193+
echo " - VUS: ${VUS}"
194+
echo " - TOOLS: ${TOOLS}"
195+
echo ""
196+
197+
# Make script executable and run
198+
chmod +x spec/performance/bench.sh
199+
200+
if ! spec/performance/bench.sh; then
201+
echo "❌ ERROR: Benchmark execution failed"
202+
exit 1
203+
fi
204+
205+
echo "✅ Benchmark suite completed successfully"
206+
207+
- name: Validate benchmark results
208+
run: |
209+
set -e # Exit on any error
210+
echo "🔍 Validating benchmark output files..."
211+
212+
RESULTS_DIR="bench_results"
213+
REQUIRED_FILES=("summary.txt")
214+
MISSING_FILES=()
215+
216+
# Check if results directory exists
217+
if [ ! -d "${RESULTS_DIR}" ]; then
218+
echo "❌ ERROR: Benchmark results directory '${RESULTS_DIR}' not found"
219+
exit 1
220+
fi
221+
222+
# List all generated files
223+
echo "Generated files:"
224+
ls -lh ${RESULTS_DIR}/ || true
225+
echo ""
226+
227+
# Check for required files
228+
for file in "${REQUIRED_FILES[@]}"; do
229+
if [ ! -f "${RESULTS_DIR}/${file}" ]; then
230+
MISSING_FILES+=("${file}")
231+
fi
232+
done
233+
234+
# Report validation results
235+
if [ ${#MISSING_FILES[@]} -eq 0 ]; then
236+
echo "✅ All required benchmark output files present"
237+
echo "📊 Summary preview:"
238+
head -20 ${RESULTS_DIR}/summary.txt || true
239+
else
240+
echo "⚠️ WARNING: Some required files are missing:"
241+
printf ' - %s\n' "${MISSING_FILES[@]}"
242+
echo "Continuing with available results..."
243+
fi
244+
245+
# ============================================
246+
# STEP 6: COLLECT BENCHMARK RESULTS
247+
# ============================================
248+
249+
- name: Upload benchmark results
250+
uses: actions/upload-artifact@v4
251+
if: always() # Upload even if benchmark fails
252+
with:
253+
name: benchmark-results-${{ github.run_number }}
254+
path: bench_results/
255+
retention-days: 30
256+
if-no-files-found: warn
257+
258+
- name: Verify artifact upload
259+
if: success()
260+
run: |
261+
echo "✅ Benchmark results uploaded as workflow artifacts"
262+
echo "📦 Artifact name: benchmark-results-${{ github.run_number }}"
263+
echo "🔗 Access artifacts from the Actions tab in GitHub"
264+
265+
# ============================================
266+
# WORKFLOW COMPLETION
267+
# ============================================
268+
269+
- name: Workflow summary
270+
if: always()
271+
run: |
272+
echo "📋 Benchmark Workflow Summary"
273+
echo "=============================="
274+
echo "Status: ${{ job.status }}"
275+
echo "Run number: ${{ github.run_number }}"
276+
echo "Triggered by: ${{ github.actor }}"
277+
echo "Branch: ${{ github.ref_name }}"
278+
echo ""
279+
if [ "${{ job.status }}" == "success" ]; then
280+
echo "✅ All steps completed successfully"
281+
else
282+
echo "❌ Workflow encountered errors - check logs above"
283+
fi

0 commit comments

Comments
 (0)