+ Jawk publishes release-time JMH benchmark
+ results for selected hot runtime paths. These numbers are useful for comparisons within the same benchmark
+ environment, but should not be treated as absolute performance claims across different JVMs, operating systems,
+ or hardware.
+
+
+
+ AngularJS is not available, so benchmark results cannot be loaded dynamically.
+
+
+
+
+
+
+
+
diff --git a/src/test/java/io/jawk/JRTTest.java b/src/test/java/io/jawk/JRTTest.java
index 6dbb3957..335e6ae4 100644
--- a/src/test/java/io/jawk/JRTTest.java
+++ b/src/test/java/io/jawk/JRTTest.java
@@ -115,6 +115,45 @@ public void testCompare2Uninitialized() {
assertTrue(JRT.compare2(1, new UninitializedObject(), 1));
}
+ @Test
+ public void testCompare2NumericOperands() {
+ assertTrue(JRT.compare2(3L, 3L, 0));
+ assertFalse(JRT.compare2(3L, 4L, 0));
+ assertTrue(JRT.compare2(3L, 4L, -1));
+ assertTrue(JRT.compare2(4L, 3L, 1));
+ assertTrue(JRT.compare2(3.5D, 3.5D, 0));
+ assertTrue(JRT.compare2(3L, 3.0D, 0));
+ assertTrue(JRT.compare2(3L, 3.5D, -1));
+ }
+
+ @Test
+ public void testCompare2NumericStrings() {
+ assertTrue(JRT.compare2("3", "3.0", 0));
+ assertTrue(JRT.compare2("3", "4.0", -1));
+ assertTrue(JRT.compare2("4.0", "3", 1));
+ assertTrue(JRT.compare2("1e2", "100", 0));
+ assertTrue(JRT.compare2("+.5", "0.5", 0));
+ assertTrue(JRT.compare2("5.", "5.0", 0));
+ assertTrue(JRT.compare2("-1E+2", "-100", 0));
+ }
+
+ @Test
+ public void testCompare2MixedNumberAndString() {
+ assertTrue(JRT.compare2(3L, "3.0", 0));
+ assertTrue(JRT.compare2("3.0", 3L, 0));
+ assertTrue(JRT.compare2(3L, "4", -1));
+ assertTrue(JRT.compare2("4", 3L, 1));
+ }
+
+ @Test
+ public void testCompare2FallsBackToStringComparison() {
+ assertFalse(JRT.compare2("3x", "3.0", 0));
+ assertTrue(JRT.compare2("3x", "4", -1));
+ assertTrue(JRT.compare2(10L, "2x", -1));
+ assertTrue(JRT.compare2("2x", 10L, 1));
+ assertTrue(JRT.compare2("1e", "2", -1));
+ }
+
@Test
public void testSpawnProcessCat() throws Exception {
Assume.assumeFalse(IS_WINDOWS);
diff --git a/src/test/java/io/jawk/jrt/JRTComparisonNumberTest.java b/src/test/java/io/jawk/jrt/JRTComparisonNumberTest.java
new file mode 100644
index 00000000..b3e3c6c2
--- /dev/null
+++ b/src/test/java/io/jawk/jrt/JRTComparisonNumberTest.java
@@ -0,0 +1,69 @@
+package io.jawk.jrt;
+
+/*-
+ * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
+ * Jawk
+ * ჻჻჻჻჻჻
+ * Copyright (C) 2006 - 2026 MetricsHub
+ * ჻჻჻჻჻჻
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Lesser Public License for more details.
+ *
+ * You should have received a copy of the GNU General Lesser Public
+ * License along with this program. If not, see
+ * .
+ * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
+ */
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class JRTComparisonNumberTest {
+
+ @Test
+ public void testIsComparisonNumberAcceptsDecimalForms() {
+ assertTrue(JRT.isComparisonNumber("0"));
+ assertTrue(JRT.isComparisonNumber("123"));
+ assertTrue(JRT.isComparisonNumber("+123"));
+ assertTrue(JRT.isComparisonNumber("-123"));
+ assertTrue(JRT.isComparisonNumber("123.45"));
+ assertTrue(JRT.isComparisonNumber("+.5"));
+ assertTrue(JRT.isComparisonNumber("5."));
+ assertTrue(JRT.isComparisonNumber("1e2"));
+ assertTrue(JRT.isComparisonNumber("1E2"));
+ assertTrue(JRT.isComparisonNumber("-1E+2"));
+ assertTrue(JRT.isComparisonNumber("+1e-2"));
+ }
+
+ @Test
+ public void testIsComparisonNumberRejectsInvalidDecimalForms() {
+ assertFalse(JRT.isComparisonNumber(""));
+ assertFalse(JRT.isComparisonNumber("+"));
+ assertFalse(JRT.isComparisonNumber("-"));
+ assertFalse(JRT.isComparisonNumber("."));
+ assertFalse(JRT.isComparisonNumber("e1"));
+ assertFalse(JRT.isComparisonNumber("1e"));
+ assertFalse(JRT.isComparisonNumber("1e+"));
+ assertFalse(JRT.isComparisonNumber("1e-"));
+ assertFalse(JRT.isComparisonNumber("1.2.3"));
+ assertFalse(JRT.isComparisonNumber("123abc"));
+ assertFalse(JRT.isComparisonNumber("abc123"));
+ }
+
+ @Test
+ public void testIsComparisonNumberRejectsHexadecimal() {
+ assertFalse(JRT.isComparisonNumber("0x0"));
+ assertFalse(JRT.isComparisonNumber("0x10"));
+ assertFalse(JRT.isComparisonNumber("-0x10"));
+ assertFalse(JRT.isComparisonNumber("+0XFF"));
+ }
+}
From 817f43ad41cfe044d14d183998a3e931c15e448f Mon Sep 17 00:00:00 2001
From: Bertrand Martin
Date: Wed, 13 May 2026 20:52:00 +0200
Subject: [PATCH 2/3] Refine benchmark component alias and template bindings
---
.github/workflows/benchmarks.yml | 10 +-
src/main/java/io/jawk/jrt/JRT.java | 16 +-
src/site/resources/css/site.css | 4 +-
src/site/resources/js/benchmarks.js | 166 ++++++++++++
src/site/resources/templates/benchmarks.html | 101 +++++++
src/site/xhtml/benchmarks.xhtml | 261 +------------------
src/test/java/io/jawk/JRTTest.java | 2 +
7 files changed, 294 insertions(+), 266 deletions(-)
create mode 100644 src/site/resources/js/benchmarks.js
create mode 100644 src/site/resources/templates/benchmarks.html
diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml
index 0d445fe5..d56c37a6 100644
--- a/.github/workflows/benchmarks.yml
+++ b/.github/workflows/benchmarks.yml
@@ -208,6 +208,7 @@ jobs:
const siteBase = new URL(process.env.BENCHMARK_SITE_URL || 'https://jawk.io/');
const siteRoot = 'target/site';
+ const resolvedSiteRoot = path.resolve(siteRoot);
const indexPath = path.join(siteRoot, 'benchmarks', 'index.json');
function getText(url) {
@@ -244,11 +245,18 @@ jobs:
return;
}
const normalizedPath = relativePath.replace(/^\/+/, '');
+ if (normalizedPath.includes('\\') || normalizedPath.split('/').includes('..')) {
+ return;
+ }
const content = await getText(new URL(normalizedPath, siteBase));
if (content === null) {
return;
}
- const outputPath = path.join(siteRoot, normalizedPath);
+ const outputPath = path.resolve(resolvedSiteRoot, normalizedPath);
+ const outputRelativePath = path.relative(resolvedSiteRoot, outputPath);
+ if (outputRelativePath.startsWith('..') || path.isAbsolute(outputRelativePath)) {
+ return;
+ }
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, content);
}
diff --git a/src/main/java/io/jawk/jrt/JRT.java b/src/main/java/io/jawk/jrt/JRT.java
index 4b1b917e..90980e33 100644
--- a/src/main/java/io/jawk/jrt/JRT.java
+++ b/src/main/java/io/jawk/jrt/JRT.java
@@ -687,16 +687,24 @@ public static boolean compare2(Object o1, Object o2, int mode) {
if (o1Numeric) {
o1Number = ((Number) o1).doubleValue();
} else if (isComparisonNumber(o1String)) {
- o1Number = new BigDecimal(o1String).doubleValue();
- o1Numeric = true;
+ try {
+ o1Number = new BigDecimal(o1String).doubleValue();
+ o1Numeric = true;
+ } catch (NumberFormatException nfe) { // NOPMD - ignore invalid number
+ o1Number = 0.0;
+ }
} else {
o1Number = 0.0;
}
if (o2Numeric) {
o2Number = ((Number) o2).doubleValue();
} else if (isComparisonNumber(o2String)) {
- o2Number = new BigDecimal(o2String).doubleValue();
- o2Numeric = true;
+ try {
+ o2Number = new BigDecimal(o2String).doubleValue();
+ o2Numeric = true;
+ } catch (NumberFormatException nfe) { // NOPMD - ignore invalid number
+ o2Number = 0.0;
+ }
} else {
o2Number = 0.0;
}
diff --git a/src/site/resources/css/site.css b/src/site/resources/css/site.css
index f5f9c3cc..16ad9906 100644
--- a/src/site/resources/css/site.css
+++ b/src/site/resources/css/site.css
@@ -675,7 +675,9 @@ body.dark {
/* Benchmarks */
.benchmarks-page[ng-cloak],
-.benchmarks-page [ng-cloak] {
+.benchmarks-page [ng-cloak],
+.benchmarks-page[data-ng-cloak],
+.benchmarks-page [data-ng-cloak] {
display: none !important;
}
diff --git a/src/site/resources/js/benchmarks.js b/src/site/resources/js/benchmarks.js
new file mode 100644
index 00000000..803a8a14
--- /dev/null
+++ b/src/site/resources/js/benchmarks.js
@@ -0,0 +1,166 @@
+/*
+ * Jawk
+ * Copyright (C) 2006 - 2026 MetricsHub
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ */
+(function() {
+ 'use strict';
+
+ function installBenchmarkSupport(angular) {
+ var siteModule = angular.module('sentry.site');
+ var host = document.getElementById('benchmark-application');
+ if (host) {
+ host.innerHTML = '';
+ }
+
+ siteModule.component('jawkBenchmarkReport', {
+ controller: ['$http', BenchmarkController],
+ controllerAs: '$ctrl',
+ templateUrl: 'templates/benchmarks.html'
+ });
+ }
+
+ function BenchmarkController($http) {
+ var $ctrl = this;
+ var indexUrl = 'benchmarks/index.json';
+
+ $ctrl.loading = true;
+ $ctrl.releaseLoading = false;
+ $ctrl.error = null;
+ $ctrl.releaseError = null;
+ $ctrl.releases = [];
+ $ctrl.results = [];
+ $ctrl.environment = null;
+
+ $ctrl.metric = function(result) {
+ return result.primaryMetric || {};
+ };
+
+ $ctrl.shortBenchmarkName = function(benchmark) {
+ var marker = 'JRTCompare2Benchmark.';
+ var index;
+ if (!benchmark) {
+ return '';
+ }
+ index = benchmark.indexOf(marker);
+ return index >= 0 ? benchmark.substring(index + marker.length) : benchmark;
+ };
+
+ $ctrl.forkCount = function(result) {
+ var metric = $ctrl.metric(result);
+ return metric.rawData ? metric.rawData.length : '';
+ };
+
+ $ctrl.releaseKey = function(release) {
+ return release.versionPath || release.version;
+ };
+
+ $ctrl.releaseLabel = function(release) {
+ if (!release) {
+ return '';
+ }
+ return release.date ? release.version + ' (' + release.date + ')' : release.version;
+ };
+
+ $ctrl.runnerLabel = function(environment) {
+ if (!environment || !environment.runner) {
+ return '';
+ }
+ return [environment.runner.os, environment.runner.arch, environment.runner.name].filter(Boolean).join(' / ');
+ };
+
+ $ctrl.systemLabel = function(environment) {
+ var label;
+ var system;
+ if (!environment || !environment.system) {
+ return '';
+ }
+ system = environment.system;
+ label = [system.platform, system.release, system.arch].filter(Boolean).join(' / ');
+ if (system.cpuCount) {
+ label += ' / ' + system.cpuCount + ' CPUs';
+ }
+ if (system.cpus && system.cpus.length) {
+ label += ' / ' + system.cpus.join(', ');
+ }
+ return label;
+ };
+
+ $ctrl.loadRelease = function(release) {
+ if (!release) {
+ return;
+ }
+
+ $ctrl.releaseLoading = true;
+ $ctrl.releaseError = null;
+ $ctrl.results = [];
+ $ctrl.environment = null;
+
+ $http.get(release.jmh, { cache: false }).then(function(response) {
+ $ctrl.results = response.data || [];
+ $ctrl.results.sort(function(left, right) {
+ return left.benchmark.localeCompare(right.benchmark);
+ });
+ }, function() {
+ $ctrl.releaseError = 'Benchmark results could not be loaded for ' + release.version + '.';
+ }).finally(function() {
+ $ctrl.releaseLoading = false;
+ });
+
+ if (release.environment) {
+ $http.get(release.environment, { cache: false }).then(function(response) {
+ $ctrl.environment = response.data;
+ });
+ }
+ };
+
+ $http.get(indexUrl, { cache: false }).then(function(response) {
+ var data = response.data || {};
+ var latest = data.latest;
+ var releases = data.releases || [];
+ var selected = releases.length ? releases[0] : null;
+ var index;
+
+ $ctrl.releases = releases;
+ for (index = 0; index < releases.length; index++) {
+ if (releases[index].version === latest) {
+ selected = releases[index];
+ break;
+ }
+ }
+
+ if (!selected) {
+ $ctrl.error = 'No published benchmark releases were found in ' + indexUrl + '.';
+ return;
+ }
+
+ $ctrl.selectedRelease = selected;
+ $ctrl.loadRelease(selected);
+ }, function() {
+ $ctrl.error = 'No benchmark index is published yet. Release benchmarks will create ' + indexUrl + '.';
+ }).finally(function() {
+ $ctrl.loading = false;
+ });
+ }
+
+ function showAngularMissing() {
+ var host = document.getElementById('benchmark-application');
+ if (host) {
+ host.innerHTML = '
AngularJS is not available, so benchmark results cannot be loaded dynamically.
+ Compare releases only after checking the environment details below. JMH scores depend on JVM version, JVM flags,
+ operating system, runner load, and CPU model.
+