Skip to content

Commit ec26e73

Browse files
committed
Initial benchmark workflow
1 parent 2600e52 commit ec26e73

File tree

1 file changed

+341
-0
lines changed

1 file changed

+341
-0
lines changed

.github/workflows/benchmark.yml

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
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+
- master
35+
pull_request:
36+
37+
env:
38+
FORTIO_VERSION: "1.73.0"
39+
K6_VERSION: "1.3.0"
40+
VEGETA_VERSION: "12.13.0"
41+
# Benchmark defaults (overridden by workflow_dispatch inputs)
42+
RATE: ${{ github.event.inputs.rate || '50' }}
43+
DURATION_SEC: ${{ github.event.inputs.duration_sec || '10' }}
44+
VUS: ${{ github.event.inputs.vus || '100' }}
45+
TOOLS: ${{ github.event.inputs.tools || 'fortio,vegeta,k6' }}
46+
47+
jobs:
48+
benchmark:
49+
runs-on: ubuntu-latest
50+
51+
steps:
52+
# ============================================
53+
# STEP 1: CHECKOUT CODE
54+
# ============================================
55+
- name: Checkout repository
56+
uses: actions/checkout@v4
57+
58+
# ============================================
59+
# STEP 2: OPTIONAL SSH ACCESS
60+
# ============================================
61+
# NOTE: Interactive confirmation is not possible in GitHub Actions.
62+
# As a secure workaround, SSH access is gated by the workflow_dispatch
63+
# input variable 'debug_enabled' which defaults to false.
64+
# Users must explicitly set this to true to enable SSH.
65+
66+
- name: SSH Warning
67+
if: ${{ github.event.inputs.debug_enabled }}
68+
run: |
69+
echo "⚠️ ⚠️ ⚠️ SSH ACCESS ENABLED ⚠️ ⚠️ ⚠️"
70+
echo ""
71+
echo "SECURITY NOTICE:"
72+
echo " - SSH access exposes your GitHub Actions runner"
73+
echo " - Only proceed if you understand and accept the risks"
74+
echo " - Do NOT store secrets or sensitive data on the runner"
75+
echo " - Access is limited to the workflow initiator only"
76+
echo " - The session will remain open until manually terminated"
77+
echo ""
78+
echo "⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️ ⚠️"
79+
80+
- name: Setup SSH access (if enabled)
81+
if: ${{ github.event.inputs.debug_enabled }}
82+
uses: mxschmitt/action-tmate@v3
83+
with:
84+
detached: true
85+
limit-access-to-actor: true # Only workflow trigger can access
86+
87+
# ============================================
88+
# STEP 3: INSTALL BENCHMARKING TOOLS
89+
# ============================================
90+
91+
- name: Add tools directory to PATH
92+
run: |
93+
mkdir -p ~/bin
94+
echo "$HOME/bin" >> $GITHUB_PATH
95+
96+
- name: Cache Fortio binary
97+
id: cache-fortio
98+
uses: actions/cache@v4
99+
with:
100+
path: ~/bin/fortio
101+
key: fortio-${{ runner.os }}-${{ runner.arch }}-${{ env.FORTIO_VERSION }}
102+
103+
- name: Install Fortio
104+
if: steps.cache-fortio.outputs.cache-hit != 'true'
105+
run: |
106+
echo "📦 Installing Fortio v${FORTIO_VERSION}"
107+
108+
# Download and extract fortio binary
109+
wget -q https://github.com/fortio/fortio/releases/download/v${FORTIO_VERSION}/fortio-linux_amd64-${FORTIO_VERSION}.tgz
110+
tar -xzf fortio-linux_amd64-${FORTIO_VERSION}.tgz
111+
112+
# Store in cache directory
113+
mv usr/bin/fortio ~/bin/
114+
115+
- name: Cache Vegeta binary
116+
id: cache-vegeta
117+
uses: actions/cache@v4
118+
with:
119+
path: ~/bin/vegeta
120+
key: vegeta-${{ runner.os }}-${{ runner.arch }}-${{ env.VEGETA_VERSION }}
121+
122+
- name: Install Vegeta
123+
if: steps.cache-vegeta.outputs.cache-hit != 'true'
124+
run: |
125+
echo "📦 Installing Vegeta v${VEGETA_VERSION}"
126+
127+
# Download and extract vegeta binary
128+
wget -q https://github.com/tsenart/vegeta/releases/download/v${VEGETA_VERSION}/vegeta_${VEGETA_VERSION}_linux_amd64.tar.gz
129+
tar -xzf vegeta_${VEGETA_VERSION}_linux_amd64.tar.gz
130+
131+
# Store in cache directory
132+
mv vegeta ~/bin/
133+
134+
- name: Setup k6
135+
uses: grafana/setup-k6-action@v1
136+
with:
137+
k6-version: ${{ env.K6_VERSION }}
138+
139+
# ============================================
140+
# STEP 4: START APPLICATION SERVER
141+
# ============================================
142+
143+
- name: Setup Ruby
144+
uses: ruby/setup-ruby@v1
145+
with:
146+
ruby-version: '3.4'
147+
bundler: 2.5.9
148+
149+
- name: Fix dependency for libyaml-dev
150+
run: sudo apt install libyaml-dev -y
151+
152+
- name: Setup Node
153+
uses: actions/setup-node@v4
154+
with:
155+
node-version: '22'
156+
cache: yarn
157+
cache-dependency-path: '**/yarn.lock'
158+
159+
- name: Print system information
160+
run: |
161+
echo "Linux release: "; cat /etc/issue
162+
echo "Current user: "; whoami
163+
echo "Current directory: "; pwd
164+
echo "Ruby version: "; ruby -v
165+
echo "Node version: "; node -v
166+
echo "Yarn version: "; yarn --version
167+
echo "Bundler version: "; bundle --version
168+
169+
- name: Install Node modules with Yarn for renderer package
170+
run: |
171+
yarn install --no-progress --no-emoji --frozen-lockfile
172+
npm install --global yalc
173+
174+
- name: yalc publish for react-on-rails
175+
run: cd packages/react-on-rails && yarn install --no-progress --no-emoji --frozen-lockfile && yalc publish
176+
177+
- name: yalc add react-on-rails
178+
run: cd spec/dummy && yalc add react-on-rails
179+
180+
- name: Install Node modules with Yarn for dummy app
181+
run: cd spec/dummy && yarn install --no-progress --no-emoji
182+
183+
- name: Save dummy app ruby gems to cache
184+
uses: actions/cache@v4
185+
with:
186+
path: spec/dummy/vendor/bundle
187+
key: dummy-app-gem-cache-${{ hashFiles('spec/dummy/Gemfile.lock') }}
188+
189+
- name: Install Ruby Gems for dummy app
190+
run: |
191+
cd spec/dummy
192+
bundle lock --add-platform 'x86_64-linux'
193+
if ! bundle check --path=vendor/bundle; then
194+
bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3
195+
fi
196+
197+
- name: generate file system-based packs
198+
run: cd spec/dummy && RAILS_ENV="production" bundle exec rake react_on_rails:generate_packs
199+
200+
- name: Prepare production assets
201+
run: |
202+
set -e # Exit on any error
203+
echo "🔨 Building production assets..."
204+
cd spec/dummy
205+
206+
if ! bin/prod-assets; then
207+
echo "❌ ERROR: Failed to build production assets"
208+
exit 1
209+
fi
210+
211+
echo "✅ Production assets built successfully"
212+
213+
- name: Start production server
214+
run: |
215+
set -e # Exit on any error
216+
echo "🚀 Starting production server..."
217+
cd spec/dummy
218+
219+
# Start server in background
220+
bin/prod &
221+
echo "Server started in background"
222+
223+
# Wait for server to be ready (max 30 seconds)
224+
echo "⏳ Waiting for server to be ready..."
225+
for i in {1..30}; do
226+
if curl -s http://localhost:3001 > /dev/null; then
227+
echo "✅ Server is ready and responding"
228+
exit 0
229+
fi
230+
echo " Attempt $i/30: Server not ready yet..."
231+
sleep 1
232+
done
233+
234+
echo "❌ ERROR: Server failed to start within 30 seconds"
235+
exit 1
236+
237+
# ============================================
238+
# STEP 5: RUN BENCHMARKS
239+
# ============================================
240+
241+
- name: Execute benchmark suite
242+
timeout-minutes: 20
243+
run: |
244+
set -e # Exit on any error
245+
echo "🏃 Running benchmark suite..."
246+
echo "Script: spec/performance/bench.sh"
247+
echo ""
248+
echo "Benchmark parameters:"
249+
echo " - RATE: ${RATE}"
250+
echo " - DURATION_SEC: ${DURATION_SEC}"
251+
echo " - VUS: ${VUS}"
252+
echo " - TOOLS: ${TOOLS}"
253+
echo ""
254+
255+
# Make script executable and run
256+
chmod +x spec/performance/bench.sh
257+
258+
if ! spec/performance/bench.sh; then
259+
echo "❌ ERROR: Benchmark execution failed"
260+
exit 1
261+
fi
262+
263+
echo "✅ Benchmark suite completed successfully"
264+
265+
- name: Validate benchmark results
266+
run: |
267+
set -e # Exit on any error
268+
echo "🔍 Validating benchmark output files..."
269+
270+
RESULTS_DIR="bench_results"
271+
REQUIRED_FILES=("summary.txt")
272+
MISSING_FILES=()
273+
274+
# Check if results directory exists
275+
if [ ! -d "${RESULTS_DIR}" ]; then
276+
echo "❌ ERROR: Benchmark results directory '${RESULTS_DIR}' not found"
277+
exit 1
278+
fi
279+
280+
# List all generated files
281+
echo "Generated files:"
282+
ls -lh ${RESULTS_DIR}/ || true
283+
echo ""
284+
285+
# Check for required files
286+
for file in "${REQUIRED_FILES[@]}"; do
287+
if [ ! -f "${RESULTS_DIR}/${file}" ]; then
288+
MISSING_FILES+=("${file}")
289+
fi
290+
done
291+
292+
# Report validation results
293+
if [ ${#MISSING_FILES[@]} -eq 0 ]; then
294+
echo "✅ All required benchmark output files present"
295+
echo "📊 Summary preview:"
296+
head -20 ${RESULTS_DIR}/summary.txt || true
297+
else
298+
echo "⚠️ WARNING: Some required files are missing:"
299+
printf ' - %s\n' "${MISSING_FILES[@]}"
300+
echo "Continuing with available results..."
301+
fi
302+
303+
# ============================================
304+
# STEP 6: COLLECT BENCHMARK RESULTS
305+
# ============================================
306+
307+
- name: Upload benchmark results
308+
uses: actions/upload-artifact@v4
309+
if: always() # Upload even if benchmark fails
310+
with:
311+
name: benchmark-results-${{ github.run_number }}
312+
path: bench_results/
313+
retention-days: 30
314+
if-no-files-found: warn
315+
316+
- name: Verify artifact upload
317+
if: success()
318+
run: |
319+
echo "✅ Benchmark results uploaded as workflow artifacts"
320+
echo "📦 Artifact name: benchmark-results-${{ github.run_number }}"
321+
echo "🔗 Access artifacts from the Actions tab in GitHub"
322+
323+
# ============================================
324+
# WORKFLOW COMPLETION
325+
# ============================================
326+
327+
- name: Workflow summary
328+
if: always()
329+
run: |
330+
echo "📋 Benchmark Workflow Summary"
331+
echo "=============================="
332+
echo "Status: ${{ job.status }}"
333+
echo "Run number: ${{ github.run_number }}"
334+
echo "Triggered by: ${{ github.actor }}"
335+
echo "Branch: ${{ github.ref_name }}"
336+
echo ""
337+
if [ "${{ job.status }}" == "success" ]; then
338+
echo "✅ All steps completed successfully"
339+
else
340+
echo "❌ Workflow encountered errors - check logs above"
341+
fi

0 commit comments

Comments
 (0)