Skip to content

Commit c691ceb

Browse files
committed
Initial benchmark workflow
1 parent 3bb1a45 commit c691ceb

File tree

1 file changed

+340
-0
lines changed

1 file changed

+340
-0
lines changed

.github/workflows/benchmark.yml

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
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+
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+
173+
- name: yalc publish for react-on-rails
174+
run: cd packages/react-on-rails && yarn install --no-progress --no-emoji --frozen-lockfile && yalc publish
175+
176+
- name: yalc add react-on-rails
177+
run: cd spec/dummy && yalc add react-on-rails
178+
179+
- name: Install Node modules with Yarn for dummy app
180+
run: cd spec/dummy && yarn install --no-progress --no-emoji
181+
182+
- name: Save dummy app ruby gems to cache
183+
uses: actions/cache@v4
184+
with:
185+
path: spec/dummy/vendor/bundle
186+
key: dummy-app-gem-cache-${{ hashFiles('spec/dummy/Gemfile.lock') }}
187+
188+
- name: Install Ruby Gems for dummy app
189+
run: |
190+
cd spec/dummy
191+
bundle lock --add-platform 'x86_64-linux'
192+
if ! bundle check --path=vendor/bundle; then
193+
bundle _2.5.9_ install --path=vendor/bundle --jobs=4 --retry=3
194+
fi
195+
196+
- name: generate file system-based packs
197+
run: cd spec/dummy && RAILS_ENV="production" bundle exec rake react_on_rails:generate_packs
198+
199+
- name: Prepare production assets
200+
run: |
201+
set -e # Exit on any error
202+
echo "🔨 Building production assets..."
203+
cd spec/dummy
204+
205+
if ! bin/prod-assets; then
206+
echo "❌ ERROR: Failed to build production assets"
207+
exit 1
208+
fi
209+
210+
echo "✅ Production assets built successfully"
211+
212+
- name: Start production server
213+
run: |
214+
set -e # Exit on any error
215+
echo "🚀 Starting production server..."
216+
cd spec/dummy
217+
218+
# Start server in background
219+
bin/prod &
220+
SERVER_PID=$!
221+
echo "Server started with PID: ${SERVER_PID}"
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 -sf http://localhost:3001 > /dev/null 2>&1; 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+
run: |
243+
set -e # Exit on any error
244+
echo "🏃 Running benchmark suite..."
245+
echo "Script: spec/performance/bench.sh"
246+
echo ""
247+
echo "Benchmark parameters:"
248+
echo " - RATE: ${RATE}"
249+
echo " - DURATION_SEC: ${DURATION_SEC}"
250+
echo " - VUS: ${VUS}"
251+
echo " - TOOLS: ${TOOLS}"
252+
echo ""
253+
254+
# Make script executable and run
255+
chmod +x spec/performance/bench.sh
256+
257+
if ! spec/performance/bench.sh; then
258+
echo "❌ ERROR: Benchmark execution failed"
259+
exit 1
260+
fi
261+
262+
echo "✅ Benchmark suite completed successfully"
263+
264+
- name: Validate benchmark results
265+
run: |
266+
set -e # Exit on any error
267+
echo "🔍 Validating benchmark output files..."
268+
269+
RESULTS_DIR="bench_results"
270+
REQUIRED_FILES=("summary.txt")
271+
MISSING_FILES=()
272+
273+
# Check if results directory exists
274+
if [ ! -d "${RESULTS_DIR}" ]; then
275+
echo "❌ ERROR: Benchmark results directory '${RESULTS_DIR}' not found"
276+
exit 1
277+
fi
278+
279+
# List all generated files
280+
echo "Generated files:"
281+
ls -lh ${RESULTS_DIR}/ || true
282+
echo ""
283+
284+
# Check for required files
285+
for file in "${REQUIRED_FILES[@]}"; do
286+
if [ ! -f "${RESULTS_DIR}/${file}" ]; then
287+
MISSING_FILES+=("${file}")
288+
fi
289+
done
290+
291+
# Report validation results
292+
if [ ${#MISSING_FILES[@]} -eq 0 ]; then
293+
echo "✅ All required benchmark output files present"
294+
echo "📊 Summary preview:"
295+
head -20 ${RESULTS_DIR}/summary.txt || true
296+
else
297+
echo "⚠️ WARNING: Some required files are missing:"
298+
printf ' - %s\n' "${MISSING_FILES[@]}"
299+
echo "Continuing with available results..."
300+
fi
301+
302+
# ============================================
303+
# STEP 6: COLLECT BENCHMARK RESULTS
304+
# ============================================
305+
306+
- name: Upload benchmark results
307+
uses: actions/upload-artifact@v4
308+
if: always() # Upload even if benchmark fails
309+
with:
310+
name: benchmark-results-${{ github.run_number }}
311+
path: bench_results/
312+
retention-days: 30
313+
if-no-files-found: warn
314+
315+
- name: Verify artifact upload
316+
if: success()
317+
run: |
318+
echo "✅ Benchmark results uploaded as workflow artifacts"
319+
echo "📦 Artifact name: benchmark-results-${{ github.run_number }}"
320+
echo "🔗 Access artifacts from the Actions tab in GitHub"
321+
322+
# ============================================
323+
# WORKFLOW COMPLETION
324+
# ============================================
325+
326+
- name: Workflow summary
327+
if: always()
328+
run: |
329+
echo "📋 Benchmark Workflow Summary"
330+
echo "=============================="
331+
echo "Status: ${{ job.status }}"
332+
echo "Run number: ${{ github.run_number }}"
333+
echo "Triggered by: ${{ github.actor }}"
334+
echo "Branch: ${{ github.ref_name }}"
335+
echo ""
336+
if [ "${{ job.status }}" == "success" ]; then
337+
echo "✅ All steps completed successfully"
338+
else
339+
echo "❌ Workflow encountered errors - check logs above"
340+
fi

0 commit comments

Comments
 (0)