Skip to content

Commit ba009a8

Browse files
authored
Merge pull request #29 from ElevatedDev/feat/consistency
Feat/consistency
2 parents 3154a86 + 0c7c847 commit ba009a8

22 files changed

Lines changed: 2748 additions & 806 deletions

File tree

build.gradle

Lines changed: 102 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
plugins {
2-
id 'java'
3-
id 'application'
4-
id 'com.google.protobuf' version '0.9.5'
5-
id 'io.freefair.lombok' version '8.13.1'
6-
id 'com.gradleup.shadow' version '8.3.6'
7-
id 'me.champeau.jmh' version '0.7.3'
8-
}
9-
1+
plugins {
2+
id 'java'
3+
id 'application'
4+
id 'com.google.protobuf' version '0.9.5'
5+
id 'io.freefair.lombok' version '8.13.1'
6+
id 'com.gradleup.shadow' version '8.3.6'
7+
id 'me.champeau.jmh' version '0.7.3'
8+
}
9+
1010
group = 'io.ringbroker'
1111
version = '0.0.1-BETA'
12-
13-
/* ---------- JVM ---------- */
12+
13+
/* ---------- JVM ---------- */
1414
java {
1515
toolchain {
1616
languageVersion = JavaLanguageVersion.of(21)
@@ -24,6 +24,19 @@ tasks.withType(JavaCompile).configureEach {
2424
options.encoding = 'UTF-8'
2525
}
2626

27+
/* ---------- Toolchain launcher (ensures JavaExec/Test run on JDK 21, not Gradle daemon JDK) ---------- */
28+
def jdk21Launcher = javaToolchains.launcherFor {
29+
languageVersion = JavaLanguageVersion.of(21)
30+
}
31+
32+
tasks.withType(JavaExec).configureEach {
33+
javaLauncher = jdk21Launcher
34+
}
35+
36+
tasks.withType(Test).configureEach {
37+
javaLauncher = jdk21Launcher
38+
}
39+
2740
/* ---------- Repos & Versions ---------- */
2841
repositories { mavenCentral() }
2942

@@ -32,11 +45,11 @@ ext {
3245
protobufVersion = '3.25.7'
3346
jacksonVersion = '2.19.0'
3447
picocliVersion = '4.7.7'
35-
nettyVersion = '4.2.1.Final'
48+
nettyVersion = '4.2.1.Final'
3649
jmhVersion = '1.37'
3750
slf4jVersion = '2.0.17'
3851
jupiterVersion = '5.12.2'
39-
junitPlatformVersion = '1.12.2' // Added matching platform version
52+
junitPlatformVersion = '1.12.2'
4053
annotationVersion = '1.3.2'
4154
testcontainersVersion = '1.20.3'
4255
}
@@ -46,25 +59,25 @@ dependencies {
4659
// gRPC and Protobuf
4760
implementation "io.grpc:grpc-netty-shaded:$grpcVersion"
4861
implementation "io.grpc:grpc-stub:$grpcVersion"
49-
implementation "io.grpc:grpc-protobuf:$grpcVersion"
50-
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
51-
implementation "io.netty:netty-all:$nettyVersion"
52-
53-
// Lombok (enabled via plugin)
54-
compileOnly "org.projectlombok:lombok"
55-
annotationProcessor "org.projectlombok:lombok"
56-
57-
// Jackson YAML for config parsing
58-
implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
59-
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jacksonVersion"
60-
61-
// Picocli CLI support
62-
implementation "info.picocli:picocli:$picocliVersion"
63-
annotationProcessor "info.picocli:picocli-codegen:$picocliVersion"
64-
65-
// SLF4J API (you can choose a backend like logback)
66-
implementation "org.slf4j:slf4j-api:$slf4jVersion"
67-
runtimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
62+
implementation "io.grpc:grpc-protobuf:$grpcVersion"
63+
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
64+
implementation "io.netty:netty-all:$nettyVersion"
65+
66+
// Lombok (enabled via plugin)
67+
compileOnly "org.projectlombok:lombok"
68+
annotationProcessor "org.projectlombok:lombok"
69+
70+
// Jackson YAML for config parsing
71+
implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
72+
implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:$jacksonVersion"
73+
74+
// Picocli CLI support
75+
implementation "info.picocli:picocli:$picocliVersion"
76+
annotationProcessor "info.picocli:picocli-codegen:$picocliVersion"
77+
78+
// SLF4J API + simple backend
79+
implementation "org.slf4j:slf4j-api:$slf4jVersion"
80+
runtimeOnly "org.slf4j:slf4j-simple:$slf4jVersion"
6881

6982
// JUnit 5
7083
testImplementation "org.junit.jupiter:junit-jupiter:$jupiterVersion"
@@ -75,25 +88,25 @@ dependencies {
7588

7689
compileOnly "javax.annotation:javax.annotation-api:$annotationVersion"
7790

78-
// JMH dependencies
79-
jmh "org.openjdk.jmh:jmh-core:${jmhVersion}"
80-
jmh "org.openjdk.jmh:jmh-generator-annprocess:${jmhVersion}"
81-
}
82-
83-
/* ---------- Protobuf / gRPC codegen ---------- */
84-
protobuf {
85-
protoc { artifact = "com.google.protobuf:protoc:${protobufVersion}" }
86-
plugins {
87-
grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
88-
}
89-
generateProtoTasks {
90-
all().each { task ->
91-
task.plugins { grpc {} }
92-
}
93-
}
94-
}
95-
96-
/* ---------- Application entrypoint ---------- */
91+
// ---- JMH (FIXED): ensure BenchmarkList is generated ----
92+
jmhImplementation "org.openjdk.jmh:jmh-core:${jmhVersion}"
93+
jmhAnnotationProcessor "org.openjdk.jmh:jmh-generator-annprocess:${jmhVersion}"
94+
}
95+
96+
/* ---------- Protobuf / gRPC code-gen ---------- */
97+
protobuf {
98+
protoc { artifact = "com.google.protobuf:protoc:${protobufVersion}" }
99+
plugins {
100+
grpc { artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" }
101+
}
102+
generateProtoTasks {
103+
all().each { task ->
104+
task.plugins { grpc {} }
105+
}
106+
}
107+
}
108+
109+
/* ---------- Application entry-point ---------- */
97110
application {
98111
mainClass = 'io.ringbroker.Application'
99112
}
@@ -105,8 +118,8 @@ test {
105118
// Fat jar is required for the Testcontainers-based cluster integration test.
106119
dependsOn tasks.named('shadowJar')
107120
}
108-
109-
/* ---------- Jar manifest ---------- */
121+
122+
/* ---------- Jar manifest ---------- */
110123
jar {
111124
manifest {
112125
attributes(
@@ -116,21 +129,20 @@ jar {
116129
)
117130
}
118131
}
119-
120-
/* ---------- JMH Configuration ---------- */
132+
133+
/* ---------- JMH Configuration ---------- */
121134
jmh {
122-
includes = ['.*Benchmark.*'] // Include classes with "Benchmark" in their name
123-
resultFormat = 'JSON' // Output format for results
135+
includes = ['.*Benchmark.*']
136+
resultFormat = 'JSON'
124137
resultsFile = project.file("${project.buildDir}/reports/jmh/results.json")
125-
timeOnIteration = '1s' // Time per iteration
126-
warmupIterations = 2 // Number of warmup iterations
127-
iterations = 5 // Number of measurement iterations
128-
fork = 2 // Number of forks
129-
failOnError = true // Fail build on errors during benchmarking
130-
forceGC = true // Force GC between iterations
131-
jvmArgsAppend = ['--enable-preview'] // Add any JVM args needed for your project
132-
133-
// Allow quick overrides from the command line (e.g. -PjmhInclude=Foo -PjmhIterations=1).
138+
timeOnIteration = '1s'
139+
warmupIterations = 2
140+
iterations = 5
141+
fork = 2
142+
failOnError = true
143+
forceGC = true
144+
jvmArgsAppend = ['--enable-preview']
145+
134146
if (project.hasProperty('jmhInclude')) {
135147
includes = [project.property('jmhInclude')]
136148
}
@@ -147,3 +159,27 @@ jmh {
147159
jvmArgsAppend += ['-Djmh.ignoreLock=true']
148160
}
149161
}
162+
163+
/* ---------- Custom Benchmarker runner ---------- */
164+
tasks.register('benchmarkProfile', JavaExec) {
165+
group = 'benchmark'
166+
description = 'Runs the custom JMH Benchmarker main (Windows: JFR via Benchmarker; non-Windows: async-profiler).'
167+
168+
dependsOn tasks.named('jmhClasses')
169+
170+
// IMPORTANT: include both JMH + MAIN outputs & deps so forks can load project classes
171+
classpath = files(
172+
sourceSets.jmh.runtimeClasspath,
173+
sourceSets.main.runtimeClasspath
174+
)
175+
176+
mainClass = 'io.ringbroker.benchmark.Benchmarker'
177+
178+
jvmArgs '--enable-preview', '-XX:+UnlockDiagnosticVMOptions', '-XX:+DebugNonSafepoints'
179+
180+
if (project.hasProperty('asyncLibPath')) systemProperty 'ringbroker.async.libPath', project.property('asyncLibPath')
181+
if (project.hasProperty('asyncDir')) systemProperty 'ringbroker.async.dir', project.property('asyncDir')
182+
if (project.hasProperty('asyncEvent')) systemProperty 'ringbroker.async.event', project.property('asyncEvent')
183+
if (project.hasProperty('asyncOutput')) systemProperty 'ringbroker.async.output', project.property('asyncOutput')
184+
if (project.hasProperty('profileDir')) systemProperty 'ringbroker.profile.dir', project.property('profileDir')
185+
}

src/jmh/java/io/ringbroker/benchmark/Benchmarker.java

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,93 @@
55
import org.openjdk.jmh.runner.options.Options;
66
import org.openjdk.jmh.runner.options.OptionsBuilder;
77

8+
import java.io.IOException;
9+
import java.nio.file.Files;
10+
import java.nio.file.Path;
11+
import java.nio.file.Paths;
12+
813
/**
914
* Main benchmark suite runner for RingBroker performance testing.
1015
* This class serves as the entry point for running all benchmarks.
1116
*/
1217
public class Benchmarker {
1318

1419
public static void main(final String[] args) throws RunnerException {
15-
final Options opt = new OptionsBuilder()
16-
.include("io.ringbroker.benchmark.*Benchmark")
17-
.exclude(Benchmarker.class.getSimpleName())
18-
.exclude(RawTcpClient.class.getSimpleName())
19-
.build();
20+
final boolean isWindows = System.getProperty("os.name", "").toLowerCase().contains("win");
21+
22+
// Where all profiler artifacts go (JFR or async-profiler outputs).
23+
final Path outDir = Paths.get(System.getProperty(
24+
"ringbroker.profile.dir",
25+
System.getProperty("ringbroker.async.dir", "build/reports/jmh/profile")
26+
)).toAbsolutePath().normalize();
27+
ensureDirectory(outDir);
28+
29+
final OptionsBuilder builder = new OptionsBuilder();
30+
builder.include("io.ringbroker.benchmark.*Benchmark");
31+
builder.exclude(Benchmarker.class.getSimpleName());
32+
builder.exclude(RawTcpClient.class.getSimpleName());
33+
34+
// Ensure forks also get these (NOT just the JavaExec runner JVM)
35+
builder.jvmArgsAppend(
36+
"--enable-preview",
37+
"-XX:+UnlockDiagnosticVMOptions",
38+
"-XX:+DebugNonSafepoints"
39+
);
40+
41+
// Pick profiler backend:
42+
// - Windows: use JFR (built-in, no native DLL needed)
43+
// - Others: use async-profiler (as you had)
44+
if (isWindows) {
45+
final String jfrSettings = System.getProperty("ringbroker.jfr.settings", "profile");
46+
final int stackDepth = Integer.getInteger("ringbroker.jfr.stackdepth", 256);
47+
48+
// JFR supports %p (pid) and %t (timestamp) filename expansion. :contentReference[oaicite:2]{index=2}
49+
final String jfrFile = outDir.resolve("ringbroker-%p-%t.jfr").toString();
50+
51+
builder.jvmArgsAppend(
52+
"-XX:StartFlightRecording=filename=" + jfrFile + ",settings=" + jfrSettings,
53+
"-XX:FlightRecorderOptions=stackdepth=" + stackDepth
54+
);
55+
} else {
56+
final String asyncLibPath = firstNonBlank(
57+
System.getProperty("ringbroker.async.libPath"),
58+
System.getenv("ASYNC_PROFILER_LIB"),
59+
"/opt/async-profiler/lib/libasyncProfiler.so"
60+
);
2061

62+
// Important: JMH async profiler options are separated by ';' not ','.
63+
final String asyncProfilerOptions = String.join(";",
64+
"libPath=" + asyncLibPath,
65+
"event=" + System.getProperty("ringbroker.async.event", "cpu"),
66+
"output=" + System.getProperty("ringbroker.async.output", "flamegraph"),
67+
"dir=" + outDir
68+
);
69+
70+
builder.addProfiler("async", asyncProfilerOptions);
71+
}
72+
73+
if (Boolean.getBoolean("ringbroker.profile.gc")) {
74+
builder.addProfiler("gc");
75+
}
76+
77+
final Options opt = builder.build();
2178
new Runner(opt).run();
2279
}
23-
}
80+
81+
private static String firstNonBlank(final String... values) {
82+
for (final String value : values) {
83+
if (value != null && !value.isBlank()) {
84+
return value;
85+
}
86+
}
87+
throw new IllegalStateException("No non-blank value provided");
88+
}
89+
90+
private static void ensureDirectory(final Path path) {
91+
try {
92+
Files.createDirectories(path);
93+
} catch (final IOException e) {
94+
throw new IllegalStateException("Failed to create profiler output directory: " + path, e);
95+
}
96+
}
97+
}

src/jmh/java/io/ringbroker/benchmark/RingBrokerBenchmark.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,4 +406,4 @@ private static void wipeDir(final Path root) throws IOException {
406406
});
407407
}
408408
}
409-
}
409+
}

0 commit comments

Comments
 (0)