This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new a7afd1c4d3 add compiler performance to dashboard
a7afd1c4d3 is described below
commit a7afd1c4d37354823caa0d5e7a3cc97ce7add411
Author: Paul King <[email protected]>
AuthorDate: Thu May 14 23:49:50 2026 +1000
add compiler performance to dashboard
---
.github/workflows/groovy-perf-daily.yml | 102 +++++++++++
.../groovy/gradle/PerformanceTestSummary.groovy | 32 +++-
subprojects/performance/dashboard/index.html | 194 +++++++++++++++++++++
.../groovy/perf/CompilerPerformanceTest.java | 4 +-
4 files changed, 327 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/groovy-perf-daily.yml
b/.github/workflows/groovy-perf-daily.yml
new file mode 100644
index 0000000000..82160f6b52
--- /dev/null
+++ b/.github/workflows/groovy-perf-daily.yml
@@ -0,0 +1,102 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: perf-daily
+
+on:
+ schedule:
+ - cron: '30 6 * * *' # 06:30 UTC daily (after jmh-daily 06:00)
+ workflow_dispatch:
+
+permissions:
+ contents: write # gh-pages push
+ deployments: write
+ issues: write # commit comments
+
+jobs:
+ benchmark:
+ runs-on: ubuntu-latest
+ env:
+ DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+ steps:
+ - uses: actions/checkout@v6
+ - uses: actions/setup-java@v5
+ with:
+ distribution: 'zulu'
+ java-version: 21
+ check-latest: true
+ - uses:
gradle/actions/setup-gradle@0723195856401067f7a2779048b490ace7a47d7c # v5.0.2
+
+ # Compiles a fixed set of source files using current + latest
3.x/4.x/5.x.
+ # Versions tracked are configured in subprojects/performance/build.gradle
+ # via `versions 'current', versions.latest3, versions.latest4,
versions.latest5`.
+ - name: Compiler performance tests
+ run: ./gradlew :perf:performanceTests
+ timeout-minutes: 120
+
+ # Dashboard: https://apache.github.io/groovy/dev/bench/perf/compiler/
+ # Data is appended to dev/bench/perf/compiler/data.js on the gh-pages
branch.
+ # To remove a noisy run for commit <BAD_SHA>:
+ # git checkout gh-pages
+ # F=dev/bench/perf/compiler/data.js
+ # sed 's/^window\.BENCHMARK_DATA = //; s/;[[:space:]]*$//' "$F" >
/tmp/d.json
+ # jq 'del(.entries[][] | select(.commit.id == "<BAD_SHA>"))'
/tmp/d.json > /tmp/c.json
+ # { printf 'window.BENCHMARK_DATA = '; cat /tmp/c.json; printf ';\n';
} > "$F"
+ # git commit -am "Drop noisy perf run for <BAD_SHA>" && git push
+ - name: Publish to dashboard
+ uses:
benchmark-action/github-action-benchmark@52576c92bccf6ac60c8223ec7eb2565637cae9ba
# v1.22.1
+ with:
+ name: 'Compiler Performance'
+ tool: 'customSmallerIsBetter'
+ output-file-path:
subprojects/performance/build/performance-results.json
+ benchmark-data-dir-path: dev/bench/perf/compiler
+ gh-pages-branch: gh-pages
+ auto-push: true
+ comment-always: true
+ alert-threshold: '140%'
+ fail-on-alert: false
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+
+ # The action writes a default index.html on first run and leaves it in
+ # place after that. We overlay our custom (4-series, normalised) page
+ # every run, but only commit if it has actually changed.
+ - name: Install custom dashboard page
+ run: |
+ set -euo pipefail
+ SRC=subprojects/performance/dashboard/index.html
+ DEST_DIR=dev/bench/perf/compiler
+ cp "$SRC" /tmp/perf-index.html
+ git fetch origin gh-pages
+ git worktree add /tmp/gh-pages gh-pages
+ mkdir -p "/tmp/gh-pages/$DEST_DIR"
+ if ! cmp -s /tmp/perf-index.html
"/tmp/gh-pages/$DEST_DIR/index.html"; then
+ cp /tmp/perf-index.html "/tmp/gh-pages/$DEST_DIR/index.html"
+ cd /tmp/gh-pages
+ git config user.name 'github-actions[bot]'
+ git config user.email
'41898282+github-actions[bot]@users.noreply.github.com'
+ git add "$DEST_DIR/index.html"
+ git commit -m 'Update compiler performance dashboard page'
+ git push origin gh-pages
+ else
+ echo 'Custom index.html already up to date.'
+ fi
+
+ - name: Upload reports-perf-compiler
+ uses: actions/upload-artifact@v7
+ with:
+ name: reports-perf-compiler
+ path: |
+ subprojects/performance/build/performance-results.json
+ subprojects/performance/build/compilation-stats-*.csv
diff --git
a/build-logic/src/main/groovy/org/apache/groovy/gradle/PerformanceTestSummary.groovy
b/build-logic/src/main/groovy/org/apache/groovy/gradle/PerformanceTestSummary.groovy
index f1d7302dfe..7e52792b51 100644
---
a/build-logic/src/main/groovy/org/apache/groovy/gradle/PerformanceTestSummary.groovy
+++
b/build-logic/src/main/groovy/org/apache/groovy/gradle/PerformanceTestSummary.groovy
@@ -18,9 +18,12 @@
*/
package org.apache.groovy.gradle
+import groovy.json.JsonOutput
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.InputFiles
+import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
@@ -32,6 +35,10 @@ class PerformanceTestSummary extends DefaultTask {
@PathSensitive(PathSensitivity.RELATIVE)
final ConfigurableFileCollection csvFiles =
project.objects.fileCollection()
+ @OutputFile
+ final RegularFileProperty jsonReport = project.objects.fileProperty()
+
.convention(project.layout.buildDirectory.file('performance-results.json'))
+
@TaskAction
void summarize() {
def versions = []
@@ -46,10 +53,10 @@ class PerformanceTestSummary extends DefaultTask {
}
}
- versions = versions.sort { ((List) it)[1] }
- def fastest = ((List) versions[0])[1]
+ def sorted = versions.toSorted { ((List) it)[1] }
+ def fastest = ((List) sorted[0])[1]
def df = new DecimalFormat("#.##")
- versions.each { version, mean, stdDev ->
+ sorted.each { version, mean, stdDev ->
print "Groovy ${sprintf '%-20s', version} Average
${df.format(mean)}ms ± ${df.format(stdDev)}ms "
if (mean > fastest) {
def diff = 100 * (mean - fastest) / fastest
@@ -57,5 +64,24 @@ class PerformanceTestSummary extends DefaultTask {
}
println()
}
+
+ def json = versions.collect { id, mean, stdDev ->
+ [
+ name : seriesName(id),
+ unit : 'ms',
+ value: mean,
+ range: "±${df.format(stdDev)}".toString(),
+ extra: id,
+ ]
+ }
+ def out = jsonReport.get().asFile
+ out.parentFile.mkdirs()
+ out.text = JsonOutput.prettyPrint(JsonOutput.toJson(json))
+ }
+
+ private static String seriesName(String id) {
+ if (id == 'current') return 'compile@current'
+ def m = id =~ /^(\d+)\./
+ return m.find() ? "compile@groovy-${m.group(1)}" : "compile@${id}"
}
}
diff --git a/subprojects/performance/dashboard/index.html
b/subprojects/performance/dashboard/index.html
new file mode 100644
index 0000000000..8aec88ef61
--- /dev/null
+++ b/subprojects/performance/dashboard/index.html
@@ -0,0 +1,194 @@
+<!DOCTYPE html>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<title>Groovy Compiler Performance Dashboard</title>
+<style>
+ :root { color-scheme: light; }
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif;
+ margin: 2em auto;
+ max-width: 1200px;
+ padding: 0 1em;
+ color: #222;
+ background: #fff;
+ }
+ h1 { font-weight: 500; margin-bottom: 0.25em; }
+ .intro { color: #444; line-height: 1.45; margin-bottom: 1.5em; max-width:
70ch; }
+ .chart-wrap { position: relative; height: 480px; margin-bottom: 1.5em; }
+ .meta { color: #666; font-size: 0.9em; margin-top: 1em; line-height: 1.5; }
+ .meta a { color: #4A8CC2; text-decoration: none; }
+ .meta a:hover { text-decoration: underline; }
+ code { background: #f5f5f7; padding: 0.05em 0.3em; border-radius: 3px;
font-size: 0.92em; }
+ .empty { color: #888; font-style: italic; }
+</style>
+</head>
+<body>
+<h1>Groovy compiler performance (normalised against current)</h1>
+<p class="intro">
+ Each daily run compiles a fixed set of Groovy source files using
<code>current</code>
+ (master) and the latest releases of Groovy 3.x, 4.x and 5.x. Every series is
divided by
+ the <code>current</code> measurement from the same run, so <code>current =
1.0</code> by
+ construction. Values below 1 mean that version compiles faster than current;
values above
+ 1 mean it compiles slower. The trends show how current drifts relative to
the released lines.
+</p>
+
+<div class="chart-wrap"><canvas id="ratioChart"></canvas></div>
+
+<div class="meta">
+ <div id="lastUpdate"></div>
+ <div id="latest"></div>
+ <div>
+ Raw data: <a href="data.js">data.js</a> ·
+ Generated by <a
href="https://github.com/benchmark-action/github-action-benchmark">github-action-benchmark</a>
·
+ <a href="../../../../">Apache Groovy benchmark home</a>
+ </div>
+</div>
+
+<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
+<script
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
+<script src="data.js"></script>
+<script>
+(function () {
+ const SERIES = [
+ { name: 'compile@current', label: 'current (master)', color: '#1F77B4' },
+ { name: 'compile@groovy-5', label: 'Groovy 5.x', color: '#2CA02C' },
+ { name: 'compile@groovy-4', label: 'Groovy 4.x', color: '#FF7F0E' },
+ { name: 'compile@groovy-3', label: 'Groovy 3.x', color: '#D62728' },
+ ];
+
+ const data = window.BENCHMARK_DATA || {};
+ const suites = data.entries || {};
+ const suiteName = Object.keys(suites)[0];
+ if (!suiteName) {
+ document.querySelector('.chart-wrap').innerHTML =
+ '<p class="empty">No benchmark data yet — waiting for the first
<code>perf-daily</code> run.</p>';
+ return;
+ }
+ const runs = suites[suiteName] || [];
+
+ const datasets = SERIES.map(s => ({
+ label: s.label,
+ borderColor: s.color,
+ backgroundColor: s.color,
+ pointRadius: 2,
+ pointHoverRadius: 5,
+ borderWidth: 2,
+ tension: 0.15,
+ spanGaps: true,
+ data: [],
+ }));
+
+ const latestVersions = {};
+ for (const run of runs) {
+ const byName = {};
+ for (const b of (run.benches || [])) byName[b.name] = b;
+ const current = byName['compile@current'];
+ if (!current || !current.value) continue;
+ const x = new Date(run.date || (run.commit && run.commit.timestamp) || 0);
+ SERIES.forEach((s, idx) => {
+ const b = byName[s.name];
+ if (!b || !b.value) return;
+ if (b.extra) latestVersions[s.name] = b.extra;
+ datasets[idx].data.push({
+ x,
+ y: b.value / current.value,
+ absMs: b.value,
+ range: b.range || '',
+ commit: (run.commit && run.commit.id) ? run.commit.id.slice(0, 7) : '',
+ commitUrl: run.commit && run.commit.url,
+ version: b.extra || '',
+ });
+ });
+ }
+
+ const baselineLine = {
+ id: 'baselineLine',
+ afterDatasetsDraw(chart) {
+ const { ctx, chartArea: { left, right }, scales: { y } } = chart;
+ const yPx = y.getPixelForValue(1);
+ ctx.save();
+ ctx.strokeStyle = 'rgba(0,0,0,0.35)';
+ ctx.setLineDash([4, 4]);
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ ctx.moveTo(left, yPx);
+ ctx.lineTo(right, yPx);
+ ctx.stroke();
+ ctx.restore();
+ }
+ };
+
+ const ctx = document.getElementById('ratioChart').getContext('2d');
+ new Chart(ctx, {
+ type: 'line',
+ data: { datasets },
+ plugins: [baselineLine],
+ options: {
+ responsive: true,
+ maintainAspectRatio: false,
+ interaction: { mode: 'index', intersect: false },
+ scales: {
+ x: {
+ type: 'time',
+ time: { unit: 'day', tooltipFormat: 'yyyy-MM-dd' },
+ title: { display: true, text: 'Run date' },
+ },
+ y: {
+ title: { display: true, text: 'compile time / current (ratio)' },
+ grid: { color: 'rgba(0,0,0,0.06)' },
+ },
+ },
+ plugins: {
+ legend: { position: 'top' },
+ tooltip: {
+ callbacks: {
+ title(items) {
+ const r = items[0] && items[0].raw;
+ const d = new Date(items[0].parsed.x).toISOString().slice(0, 10);
+ return r && r.commit ? r.commit + ' ' + d : d;
+ },
+ label(item) {
+ const r = item.raw;
+ const ratio = item.parsed.y.toFixed(3);
+ const abs = r.absMs ? r.absMs.toFixed(1) + ' ms' : '';
+ const rng = r.range ? ' ' + r.range : '';
+ const ver = r.version ? ' [' + r.version + ']' : '';
+ return item.dataset.label + ': ' + ratio + ' x (' + abs + rng +
')' + ver;
+ },
+ },
+ },
+ },
+ },
+ });
+
+ if (data.lastUpdate) {
+ document.getElementById('lastUpdate').textContent =
+ 'Last update: ' + new Date(data.lastUpdate).toISOString().replace('T', '
').slice(0, 19) + ' UTC';
+ }
+ const pieces = SERIES
+ .filter(s => latestVersions[s.name])
+ .map(s => s.label + ': ' + latestVersions[s.name]);
+ if (pieces.length) {
+ document.getElementById('latest').textContent = 'Latest versions in chart:
' + pieces.join(' | ');
+ }
+})();
+</script>
+</body>
+</html>
diff --git
a/subprojects/performance/src/test/java/org/apache/groovy/perf/CompilerPerformanceTest.java
b/subprojects/performance/src/test/java/org/apache/groovy/perf/CompilerPerformanceTest.java
index e4383be4a1..64c4d75224 100644
---
a/subprojects/performance/src/test/java/org/apache/groovy/perf/CompilerPerformanceTest.java
+++
b/subprojects/performance/src/test/java/org/apache/groovy/perf/CompilerPerformanceTest.java
@@ -30,8 +30,8 @@ import java.util.List;
public class CompilerPerformanceTest {
private final static String GROOVY_VERSION = GroovySystem.getVersion();
- private final static int WARMUP = 100;
- private final static int REPEAT = 500;
+ private final static int WARMUP = 50;
+ private final static int REPEAT = 300;
public static void main(String[] args) throws Exception {
List<File> sources = new ArrayList<>();