Skip to content

Commit 4af6b11

Browse files
Add workflow to profile runner resource usage
This workflow allows manual profiling of key CI jobs to determine if runners are appropriately sized. It monitors CPU and memory usage during build, test, and ruling jobs. Features: - Manual dispatch with job type and runner size selection - Resource monitoring with 2-second sampling interval - Peak and average usage statistics - Automated recommendations based on utilization thresholds - Metrics artifact upload for detailed analysis 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent d068b80 commit 4af6b11

File tree

1 file changed

+276
-0
lines changed

1 file changed

+276
-0
lines changed
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
name: Profile Runner Usage
2+
# Manual trigger to profile resource usage of key jobs
3+
# Run this to determine if runners are oversized
4+
5+
on:
6+
push:
7+
branches:
8+
- feat/mmatuszny/*
9+
workflow_dispatch:
10+
inputs:
11+
job_to_profile:
12+
description: 'Job to profile'
13+
required: true
14+
type: choice
15+
options:
16+
- build
17+
- test_js
18+
- ruling
19+
- plugin_qa
20+
- all_quick
21+
runner_size:
22+
description: 'Runner size to test'
23+
required: true
24+
type: choice
25+
options:
26+
- github-ubuntu-latest-s
27+
- github-ubuntu-latest-m
28+
- github-ubuntu-latest-l
29+
30+
jobs:
31+
show_runner_specs:
32+
runs-on: ${{ inputs.runner_size }}
33+
name: Runner Specifications
34+
steps:
35+
- name: Display runner specs
36+
run: |
37+
echo "=== Runner: ${{ inputs.runner_size }} ==="
38+
echo ""
39+
echo "=== CPU ==="
40+
nproc
41+
cat /proc/cpuinfo | grep "model name" | head -1
42+
echo ""
43+
echo "=== Memory ==="
44+
free -h
45+
echo ""
46+
echo "=== Disk ==="
47+
df -h /
48+
echo ""
49+
echo "=== Full CPU Info ==="
50+
lscpu
51+
52+
profile_build:
53+
if: inputs.job_to_profile == 'build' || inputs.job_to_profile == 'all_quick'
54+
runs-on: ${{ inputs.runner_size }}
55+
name: Profile - Build
56+
steps:
57+
- uses: actions/checkout@v4
58+
59+
- uses: jdx/mise-action@v3.5.1
60+
with:
61+
version: 2025.11.2
62+
mise_toml: |
63+
[tools]
64+
java = "21.0"
65+
maven = "3.9"
66+
node = "24.11.0"
67+
68+
- name: Start resource monitor
69+
run: |
70+
mkdir -p /tmp/metrics
71+
echo "timestamp,cpu_percent,mem_used_mb,mem_total_mb,mem_percent" > /tmp/metrics/usage.csv
72+
(while true; do
73+
ts=$(date +%s)
74+
cpu=$(grep 'cpu ' /proc/stat | awk '{print ($2+$4)*100/($2+$4+$5)}')
75+
mem_info=$(free -m | awk '/^Mem:/ {print $3","$2","$3/$2*100}')
76+
echo "$ts,$cpu,$mem_info" >> /tmp/metrics/usage.csv
77+
sleep 2
78+
done) &
79+
echo $! > /tmp/metrics/pid
80+
81+
- name: Run build (profiled)
82+
run: |
83+
echo "Starting build at $(date)"
84+
/usr/bin/time -v npm ci 2>&1 | tee /tmp/metrics/npm-ci-time.log || true
85+
/usr/bin/time -v mvn verify -T1C -DskipTests 2>&1 | tee /tmp/metrics/mvn-time.log || true
86+
echo "Build completed at $(date)"
87+
88+
- name: Stop monitor and analyze
89+
if: always()
90+
run: |
91+
kill $(cat /tmp/metrics/pid) 2>/dev/null || true
92+
93+
echo "=== RESOURCE USAGE SUMMARY ==="
94+
echo ""
95+
echo "Runner size: ${{ inputs.runner_size }}"
96+
echo "Available CPUs: $(nproc)"
97+
echo "Available Memory: $(free -h | awk '/^Mem:/ {print $2}')"
98+
echo ""
99+
100+
echo "=== Peak Usage ==="
101+
awk -F',' 'NR>1 {
102+
if($2>maxcpu)maxcpu=$2
103+
if($3>maxmem)maxmem=$3
104+
if($5>maxpct)maxpct=$5
105+
sumcpu+=$2; summem+=$5; n++
106+
} END {
107+
printf "Peak CPU: %.1f%%\n", maxcpu
108+
printf "Peak Memory: %.0f MB (%.1f%%)\n", maxmem, maxpct
109+
printf "Avg CPU: %.1f%%\n", sumcpu/n
110+
printf "Avg Memory: %.1f%%\n", summem/n
111+
}' /tmp/metrics/usage.csv
112+
113+
echo ""
114+
echo "=== NPM CI Memory (from time -v) ==="
115+
grep -E "(Maximum resident set size|Elapsed)" /tmp/metrics/npm-ci-time.log || echo "N/A"
116+
117+
echo ""
118+
echo "=== Maven Build Memory (from time -v) ==="
119+
grep -E "(Maximum resident set size|Elapsed)" /tmp/metrics/mvn-time.log || echo "N/A"
120+
121+
echo ""
122+
echo "=== RECOMMENDATION ==="
123+
peak_mem_pct=$(awk -F',' 'NR>1 {if($5>max)max=$5} END {print max}' /tmp/metrics/usage.csv)
124+
peak_cpu=$(awk -F',' 'NR>1 {if($2>max)max=$2} END {print max}' /tmp/metrics/usage.csv)
125+
126+
if (( $(echo "$peak_mem_pct < 50" | bc -l) )) && (( $(echo "$peak_cpu < 50" | bc -l) )); then
127+
echo "LOW UTILIZATION: Consider downsizing runner"
128+
elif (( $(echo "$peak_mem_pct > 85" | bc -l) )) || (( $(echo "$peak_cpu > 90" | bc -l) )); then
129+
echo "HIGH UTILIZATION: Current size appropriate or consider upsizing"
130+
else
131+
echo "MODERATE UTILIZATION: Current size is reasonable"
132+
fi
133+
134+
- name: Upload metrics
135+
if: always()
136+
uses: actions/upload-artifact@v4
137+
with:
138+
name: build-metrics-${{ inputs.runner_size }}
139+
path: /tmp/metrics/
140+
141+
profile_test_js:
142+
if: inputs.job_to_profile == 'test_js' || inputs.job_to_profile == 'all_quick'
143+
runs-on: ${{ inputs.runner_size }}
144+
name: Profile - JS Tests
145+
steps:
146+
- uses: actions/checkout@v4
147+
148+
- uses: jdx/mise-action@v3.5.1
149+
with:
150+
version: 2025.11.2
151+
mise_toml: |
152+
[tools]
153+
java = "21.0"
154+
maven = "3.9"
155+
node = "24.11.0"
156+
157+
- name: Start resource monitor
158+
run: |
159+
mkdir -p /tmp/metrics
160+
echo "timestamp,cpu_percent,mem_used_mb,mem_total_mb,mem_percent" > /tmp/metrics/usage.csv
161+
(while true; do
162+
ts=$(date +%s)
163+
cpu=$(grep 'cpu ' /proc/stat | awk '{print ($2+$4)*100/($2+$4+$5)}')
164+
mem_info=$(free -m | awk '/^Mem:/ {print $3","$2","$3/$2*100}')
165+
echo "$ts,$cpu,$mem_info" >> /tmp/metrics/usage.csv
166+
sleep 2
167+
done) &
168+
echo $! > /tmp/metrics/pid
169+
170+
- name: Run JS tests (profiled)
171+
run: |
172+
npm ci
173+
npm run generate-meta
174+
npm run bridge:compile
175+
/usr/bin/time -v npm run bridge:test:cov 2>&1 | tee /tmp/metrics/test-time.log || true
176+
177+
- name: Stop monitor and analyze
178+
if: always()
179+
run: |
180+
kill $(cat /tmp/metrics/pid) 2>/dev/null || true
181+
182+
echo "=== RESOURCE USAGE SUMMARY ==="
183+
echo "Runner size: ${{ inputs.runner_size }}"
184+
echo "Available CPUs: $(nproc)"
185+
echo "Available Memory: $(free -h | awk '/^Mem:/ {print $2}')"
186+
187+
echo ""
188+
echo "=== Peak Usage ==="
189+
awk -F',' 'NR>1 {
190+
if($2>maxcpu)maxcpu=$2
191+
if($3>maxmem)maxmem=$3
192+
if($5>maxpct)maxpct=$5
193+
} END {
194+
printf "Peak CPU: %.1f%%\n", maxcpu
195+
printf "Peak Memory: %.0f MB (%.1f%%)\n", maxmem, maxpct
196+
}' /tmp/metrics/usage.csv
197+
198+
echo ""
199+
echo "=== Test Memory (from time -v) ==="
200+
grep -E "(Maximum resident set size|Elapsed)" /tmp/metrics/test-time.log || echo "N/A"
201+
202+
- name: Upload metrics
203+
if: always()
204+
uses: actions/upload-artifact@v4
205+
with:
206+
name: test-js-metrics-${{ inputs.runner_size }}
207+
path: /tmp/metrics/
208+
209+
profile_ruling:
210+
if: inputs.job_to_profile == 'ruling'
211+
runs-on: ${{ inputs.runner_size }}
212+
name: Profile - Ruling Test
213+
steps:
214+
- uses: actions/checkout@v4
215+
with:
216+
submodules: true
217+
218+
- uses: jdx/mise-action@v3.5.1
219+
with:
220+
version: 2025.11.2
221+
mise_toml: |
222+
[tools]
223+
java = "21.0"
224+
maven = "3.9"
225+
node = "24.11.0"
226+
227+
- name: Start resource monitor
228+
run: |
229+
mkdir -p /tmp/metrics
230+
echo "timestamp,cpu_percent,mem_used_mb,mem_total_mb,mem_percent" > /tmp/metrics/usage.csv
231+
(while true; do
232+
ts=$(date +%s)
233+
cpu=$(grep 'cpu ' /proc/stat | awk '{print ($2+$4)*100/($2+$4+$5)}')
234+
mem_info=$(free -m | awk '/^Mem:/ {print $3","$2","$3/$2*100}')
235+
echo "$ts,$cpu,$mem_info" >> /tmp/metrics/usage.csv
236+
sleep 2
237+
done) &
238+
echo $! > /tmp/metrics/pid
239+
240+
- name: Run ruling (profiled)
241+
run: |
242+
npm ci
243+
npm run generate-meta
244+
/usr/bin/time -v npm run ruling 2>&1 | tee /tmp/metrics/ruling-time.log || true
245+
246+
- name: Stop monitor and analyze
247+
if: always()
248+
run: |
249+
kill $(cat /tmp/metrics/pid) 2>/dev/null || true
250+
251+
echo "=== RESOURCE USAGE SUMMARY ==="
252+
echo "Runner size: ${{ inputs.runner_size }}"
253+
echo "Available CPUs: $(nproc)"
254+
echo "Available Memory: $(free -h | awk '/^Mem:/ {print $2}')"
255+
256+
echo ""
257+
echo "=== Peak Usage ==="
258+
awk -F',' 'NR>1 {
259+
if($2>maxcpu)maxcpu=$2
260+
if($3>maxmem)maxmem=$3
261+
if($5>maxpct)maxpct=$5
262+
} END {
263+
printf "Peak CPU: %.1f%%\n", maxcpu
264+
printf "Peak Memory: %.0f MB (%.1f%%)\n", maxmem, maxpct
265+
}' /tmp/metrics/usage.csv
266+
267+
echo ""
268+
echo "=== Ruling Memory (from time -v) ==="
269+
grep -E "(Maximum resident set size|Elapsed)" /tmp/metrics/ruling-time.log || echo "N/A"
270+
271+
- name: Upload metrics
272+
if: always()
273+
uses: actions/upload-artifact@v4
274+
with:
275+
name: ruling-metrics-${{ inputs.runner_size }}
276+
path: /tmp/metrics/

0 commit comments

Comments
 (0)