1- name : Benchmark Generator
1+ name : Benchmark Generators
22
33on :
44 workflow_dispatch :
1111 benchmark :
1212 strategy :
1313 matrix :
14- os : [ubuntu-latest, windows-latest, macos-latest ]
14+ os : [ubuntu-latest, windows-latest]
1515 runs-on : ${{ matrix.os }}
1616 steps :
1717 - uses : actions/checkout@v6
@@ -28,11 +28,20 @@ jobs:
2828 with :
2929 python-version : ' 3.13'
3030
31+ - name : Install system dependencies (Ubuntu)
32+ if : runner.os == 'Linux'
33+ run : sudo apt-get update && sudo apt-get install -y libcairo2-dev
34+
35+ - name : Install Python dependencies
36+ run : pip install pyyaml cairosvg
37+
3138 - name : Run benchmark
3239 shell : bash
3340 run : |
3441 JAR="html-generators/generate.jar"
3542 AOT="html-generators/generate.aot"
43+ OG_JAR="html-generators/generateog.jar"
44+ OG_AOT="html-generators/generateog.aot"
3645 STEADY_RUNS=5
3746
3847 snippet_count=$(find content -name '*.json' | wc -l | tr -d ' ')
@@ -42,60 +51,110 @@ jobs:
4251 # Timing helper using bash TIMEFORMAT
4352 measure() {
4453 TIMEFORMAT='%R'
45- local t
46- t=$( { time "$@" > /dev/null 2>&1; } 2>&1 )
54+ local t ec
55+ t=$( { time "$@" > /dev/null 2>&1; ec=$?; } 2>&1 )
56+ if [[ ${ec:-1} -ne 0 ]]; then
57+ # Re-check: the subshell exit code isn't always captured
58+ if ! "$@" > /dev/null 2>&1; then
59+ echo "FAIL"
60+ return 1
61+ fi
62+ fi
4763 echo "$t"
4864 }
4965
5066 avg_runs() {
5167 local n="$1"; shift
52- local sum=0
68+ local sum=0 failures=0
5369 for ((i = 1; i <= n; i++)); do
5470 local t
55- t=$(measure "$@")
71+ t=$(measure "$@") || true
72+ if [[ "$t" == "FAIL" ]]; then
73+ ((failures++)) || true
74+ continue
75+ fi
5676 sum=$(awk "BEGIN {print $sum + $t}")
5777 done
58- awk "BEGIN {printf \"%.2f\", $sum / $n}"
78+ local success=$((n - failures))
79+ if [[ $success -eq 0 ]]; then
80+ echo "FAIL"
81+ return 0
82+ fi
83+ awk "BEGIN {printf \"%.2f\", $sum / $success}"
5984 }
6085
6186 echo "Running benchmark on $os_name (Java $java_ver, $snippet_count snippets)..."
6287
6388 # --- Phase 1: Training / build cost ---
64- rm -f "$JAR" "$AOT"
89+ rm -f "$JAR" "$AOT" "$OG_JAR" "$OG_AOT"
6590 find html-generators -name '__pycache__' -type d -exec rm -rf {} + 2>/dev/null || true
6691
92+ # HTML generator
6793 PY_TRAIN=$(measure python3 html-generators/generate.py)
6894 JBANG_EXPORT=$(measure jbang export fatjar --force --output "$JAR" html-generators/generate.java)
6995 AOT_TRAIN=$(measure java -XX:AOTCacheOutput="$AOT" -jar "$JAR")
7096
97+ # OG generator
98+ OG_PY_TRAIN=$(measure python3 html-generators/generateog.py)
99+ OG_JBANG_EXPORT=$(measure jbang export fatjar --force --output "$OG_JAR" html-generators/generateog.java)
100+ OG_AOT_TRAIN=$(measure java -XX:AOTCacheOutput="$OG_AOT" -jar "$OG_JAR")
101+
71102 # --- Phase 2: Steady-state execution ---
103+ # HTML generator
72104 PY_STEADY=$(avg_runs $STEADY_RUNS python3 html-generators/generate.py)
73105 JBANG_STEADY=$(avg_runs $STEADY_RUNS jbang html-generators/generate.java)
74106 JAR_STEADY=$(avg_runs $STEADY_RUNS java -jar "$JAR")
75107 AOT_STEADY=$(avg_runs $STEADY_RUNS java -XX:AOTCache="$AOT" -jar "$JAR")
76108
109+ # OG generator
110+ OG_PY_STEADY=$(avg_runs $STEADY_RUNS python3 html-generators/generateog.py)
111+ OG_JBANG_STEADY=$(avg_runs $STEADY_RUNS jbang html-generators/generateog.java)
112+ OG_JAR_STEADY=$(avg_runs $STEADY_RUNS java -jar "$OG_JAR")
113+ OG_AOT_STEADY=$(avg_runs $STEADY_RUNS java -XX:AOTCache="$OG_AOT" -jar "$OG_JAR")
114+
77115 # Write to GitHub Actions Job Summary
78116 {
79117 echo "## Benchmark Results — \`$os_name\`"
80118 echo ""
81119 echo "Java $java_ver · $snippet_count snippets"
82120 echo ""
83- echo "### Phase 1: Training / Build Cost (one-time)"
121+ echo "### HTML Generator"
122+ echo ""
123+ echo "#### Phase 1: Training / Build Cost (one-time)"
84124 echo ""
85125 echo "| Step | Time | What it does |"
86126 echo "|------|------|-------------|"
87127 echo "| Python first run | ${PY_TRAIN}s | Interprets source, creates \`__pycache__\` bytecode |"
88128 echo "| JBang export | ${JBANG_EXPORT}s | Compiles source + bundles dependencies into fat JAR |"
89129 echo "| AOT training run | ${AOT_TRAIN}s | Runs JAR once to record class loading, produces \`.aot\` cache |"
90130 echo ""
91- echo "### Phase 2: Steady-State Execution (avg of $STEADY_RUNS runs)"
131+ echo "#### Phase 2: Steady-State Execution (avg of $STEADY_RUNS runs)"
92132 echo ""
93133 echo "| Method | Avg Time |"
94134 echo "|--------|---------|"
95135 echo "| **Fat JAR + AOT** | **${AOT_STEADY}s** |"
96136 echo "| **Fat JAR** | ${JAR_STEADY}s |"
97137 echo "| **JBang** | ${JBANG_STEADY}s |"
98138 echo "| **Python** | ${PY_STEADY}s |"
139+ echo ""
140+ echo "### OG Card Generator"
141+ echo ""
142+ echo "#### Phase 1: Training / Build Cost (one-time)"
143+ echo ""
144+ echo "| Step | Time | What it does |"
145+ echo "|------|------|-------------|"
146+ echo "| Python first run | ${OG_PY_TRAIN}s | Interprets source, generates SVG+PNG via cairosvg |"
147+ echo "| JBang export | ${OG_JBANG_EXPORT}s | Compiles source + bundles Batik dependencies into fat JAR |"
148+ echo "| AOT training run | ${OG_AOT_TRAIN}s | Runs JAR once to record class loading, produces \`.aot\` cache |"
149+ echo ""
150+ echo "#### Phase 2: Steady-State Execution (avg of $STEADY_RUNS runs)"
151+ echo ""
152+ echo "| Method | Avg Time |"
153+ echo "|--------|---------|"
154+ echo "| **Fat JAR + AOT** | **${OG_AOT_STEADY}s** |"
155+ echo "| **Fat JAR** | ${OG_JAR_STEADY}s |"
156+ echo "| **JBang** | ${OG_JBANG_STEADY}s |"
157+ echo "| **Python** | ${OG_PY_STEADY}s |"
99158 } >> "$GITHUB_STEP_SUMMARY"
100159
101160 # ---------------------------------------------------------------------------
@@ -107,7 +166,7 @@ jobs:
107166 build-jar :
108167 strategy :
109168 matrix :
110- os : [ubuntu-latest, windows-latest, macos-latest ]
169+ os : [ubuntu-latest, windows-latest]
111170 runs-on : ${{ matrix.os }}
112171 steps :
113172 - uses : actions/checkout@v6
@@ -119,10 +178,12 @@ jobs:
119178
120179 - uses : jbangdev/setup-jbang@main
121180
122- - name : Build fat JAR and AOT cache
181+ - name : Build fat JARs and AOT caches
123182 run : |
124183 jbang export fatjar --force --output html-generators/generate.jar html-generators/generate.java
125184 java -XX:AOTCacheOutput=html-generators/generate.aot -jar html-generators/generate.jar
185+ jbang export fatjar --force --output html-generators/generateog.jar html-generators/generateog.java
186+ java -XX:AOTCacheOutput=html-generators/generateog.aot -jar html-generators/generateog.jar
126187
127188 - name : Upload JAR and AOT
128189 uses : actions/upload-artifact@v4
@@ -131,12 +192,14 @@ jobs:
131192 path : |
132193 html-generators/generate.jar
133194 html-generators/generate.aot
195+ html-generators/generateog.jar
196+ html-generators/generateog.aot
134197
135198 ci-cold-start :
136199 needs : build-jar
137200 strategy :
138201 matrix :
139- os : [ubuntu-latest, windows-latest, macos-latest ]
202+ os : [ubuntu-latest, windows-latest]
140203 runs-on : ${{ matrix.os }}
141204 steps :
142205 - uses : actions/checkout@v6
@@ -151,6 +214,13 @@ jobs:
151214 with :
152215 python-version : ' 3.13'
153216
217+ - name : Install system dependencies (Ubuntu)
218+ if : runner.os == 'Linux'
219+ run : sudo apt-get update && sudo apt-get install -y libcairo2-dev
220+
221+ - name : Install Python dependencies
222+ run : pip install pyyaml cairosvg
223+
154224 - name : Download JAR and AOT
155225 uses : actions/download-artifact@v4
156226 with :
@@ -166,27 +236,50 @@ jobs:
166236
167237 measure() {
168238 TIMEFORMAT='%R'
169- local t
170- t=$( { time "$@" > /dev/null 2>&1; } 2>&1 )
239+ local t ec
240+ t=$( { time "$@" > /dev/null 2>&1; ec=$?; } 2>&1 )
241+ if [[ ${ec:-1} -ne 0 ]]; then
242+ if ! "$@" > /dev/null 2>&1; then
243+ echo "FAIL"
244+ return 1
245+ fi
246+ fi
171247 echo "$t"
172248 }
173249
174250 # Everything is cold: no __pycache__, no JBang cache, fresh JVM
251+
252+ # HTML generator
175253 PY_CI=$(measure python3 html-generators/generate.py)
176254 JAR_CI=$(measure java -jar html-generators/generate.jar)
177255 AOT_CI=$(measure java -XX:AOTCache=html-generators/generate.aot -jar html-generators/generate.jar)
178256
257+ # OG generator
258+ OG_PY_CI=$(measure python3 html-generators/generateog.py)
259+ OG_JAR_CI=$(measure java -jar html-generators/generateog.jar)
260+ OG_AOT_CI=$(measure java -XX:AOTCache=html-generators/generateog.aot -jar html-generators/generateog.jar)
261+
179262 {
180263 echo "## CI Cold Start — \`$os_name\`"
181264 echo ""
182265 echo "Java $java_ver · $snippet_count snippets"
183266 echo ""
184- echo "Fresh runner, no caches. JAR and AOT restored from artifact"
267+ echo "Fresh runner, no caches. JARs and AOT restored from artifact"
185268 echo "(simulates actions cache restore in deploy workflow)."
186269 echo ""
270+ echo "### HTML Generator"
271+ echo ""
187272 echo "| Method | Time |"
188273 echo "|--------|------|"
189274 echo "| **Fat JAR + AOT** | **${AOT_CI}s** |"
190275 echo "| **Fat JAR** | ${JAR_CI}s |"
191276 echo "| **Python** | ${PY_CI}s |"
277+ echo ""
278+ echo "### OG Card Generator"
279+ echo ""
280+ echo "| Method | Time |"
281+ echo "|--------|------|"
282+ echo "| **Fat JAR + AOT** | **${OG_AOT_CI}s** |"
283+ echo "| **Fat JAR** | ${OG_JAR_CI}s |"
284+ echo "| **Python** | ${OG_PY_CI}s |"
192285 } >> "$GITHUB_STEP_SUMMARY"
0 commit comments