diff --git a/benchmarks/build.gradle b/benchmarks/build.gradle index 709350df4fa..6b55a29ca42 100644 --- a/benchmarks/build.gradle +++ b/benchmarks/build.gradle @@ -1,6 +1,6 @@ plugins { id 'io.micronaut.build.internal.convention-base' - id "me.champeau.jmh" version "0.7.1" + id "me.champeau.jmh" } dependencies { diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index a4cd40d3ab8..1bb21958ed7 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -14,4 +14,7 @@ dependencies { implementation "org.tomlj:tomlj:1.1.0" implementation "me.champeau.gradle:japicmp-gradle-plugin:0.4.2" implementation "org.graalvm.buildtools.native:org.graalvm.buildtools.native.gradle.plugin:0.9.26" + implementation "io.micronaut.gradle:micronaut-gradle-plugin:4.1.1" + implementation "me.champeau.jmh:jmh-gradle-plugin:0.7.1" + implementation "com.github.johnrengelman:shadow:8.1.1" } diff --git a/buildSrc/src/main/groovy/io.micronaut.build.internal.convention-base.gradle b/buildSrc/src/main/groovy/io.micronaut.build.internal.convention-base.gradle index 9d16ebd2472..517fc836dbc 100644 --- a/buildSrc/src/main/groovy/io.micronaut.build.internal.convention-base.gradle +++ b/buildSrc/src/main/groovy/io.micronaut.build.internal.convention-base.gradle @@ -3,6 +3,7 @@ import io.micronaut.build.internal.ext.DefaultMicronautCoreExtension plugins { id "io.micronaut.build.internal.common" + id "io.micronaut.build.internal.substitutions" } micronautBuild { @@ -62,20 +63,6 @@ tasks.withType(Jar).configureEach { preserveFileTimestamps = false } -configurations.all { - resolutionStrategy.dependencySubstitution { - rootProject.subprojects.each { - if (!it.name.startsWith('test-')) { - if (it.name.contains('bom')) { - substitute platform(module("io.micronaut:micronaut-${it.name}")) using platform(project(":${it.name}")) because "we want to test with what we're building" - } else { - substitute module("io.micronaut:micronaut-${it.name}") using project(":${it.name}") because "we want to test with what we're building" - } - } - } - } -} - dependencies { annotationProcessor libs.bundles.asm annotationProcessor(libs.micronaut.docs.map { diff --git a/buildSrc/src/main/groovy/io.micronaut.build.internal.convention-benchmark.gradle b/buildSrc/src/main/groovy/io.micronaut.build.internal.convention-benchmark.gradle new file mode 100644 index 00000000000..d0e5c8f9878 --- /dev/null +++ b/buildSrc/src/main/groovy/io.micronaut.build.internal.convention-benchmark.gradle @@ -0,0 +1,108 @@ +import me.champeau.jmh.JmhBytecodeGeneratorTask + +plugins { + id("io.micronaut.application") + id("io.micronaut.aot") + id("com.github.johnrengelman.shadow") + id("me.champeau.jmh") + id("io.micronaut.build.internal.substitutions") +} + +repositories { + mavenCentral() +} + +application { + mainClass = "io.micronaut.http.bench.Application" +} + +micronaut { + runtime("netty") + testRuntime("junit5") + processing { + incremental(true) + annotations("io.micronaut.http.bench.*") + } + + aot { + version = libs.versions.micronaut.aot + optimizeServiceLoading = true + convertYamlToJava = false + precomputeOperations = true + cacheEnvironment = true + optimizeClassLoading = true + deduceEnvironment = true + optimizeNetty = true + } +} + +dependencies { + annotationProcessor(libs.micronaut.serde.processor) + implementation(libs.micronaut.serde.jackson) + runtimeOnly(libs.logback.classic) +} + +jmh { + includeTests = false +} + +def optimizedJmhJar = tasks.register("optimizedJmhJar", Jar) { + group = "jmh" + description = "Generates a JMH benchmark jar using the Micronaut AOT optimized jar" + archiveAppendix = 'jmh-all' + manifest { + attributes 'Main-Class': 'org.openjdk.jmh.Main' + } + from(configurations.jmh.incoming.artifacts.resolvedArtifacts.map { + it.collect { artifact -> zipTree(artifact.file )} + }) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from(tasks.named("optimizedJitJarAll", Jar).map { zipTree(it.archiveFile.get().asFile)}) + from(sourceSets.jmh.output) + def bytecodeGen = tasks.named("jmhRunBytecodeGenerator", JmhBytecodeGeneratorTask) + from(tasks.named("jmhCompileGeneratedClasses", JavaCompile)) + from(bytecodeGen.map { + it.generatedResourcesDir + }) + // For build reproducibility + preserveFileTimestamps = false + reproducibleFileOrder = true +} + +graalvmNative { + toolchainDetection = false + metadataRepository { + enabled = true + } + binaries { + all { + resources.autodetect() + } + } + binaries { + jmhAot { + classpath.from(optimizedJmhJar) + mainClass = 'org.openjdk.jmh.Main' + // We're using a fat jar, so we copy the configuration from the main binary in order + // to include configuration for the embedded jars + configurationFileDirectories.from(graalvmNative.binaries.main.configurationFileDirectories) + } + } +} + +tasks.withType(me.champeau.jmh.JMHTask).configureEach { + doFirst { + throw new UnsupportedOperationException( + """ + Running benchmarks directly from Gradle is not supported. + You can run java -jar ${it.jarArchive.get().asFile} + """.stripIndent() + ) + } +} + +tasks.named("nativeJmhAotCompile") { + doFirst { + println graalvmNative.binaries.jmhAot.configurationFileDirectories.files + } +} diff --git a/buildSrc/src/main/groovy/io.micronaut.build.internal.substitutions.gradle b/buildSrc/src/main/groovy/io.micronaut.build.internal.substitutions.gradle new file mode 100644 index 00000000000..6dad6ffd068 --- /dev/null +++ b/buildSrc/src/main/groovy/io.micronaut.build.internal.substitutions.gradle @@ -0,0 +1,18 @@ +/** + * This plugin applies dependency substitutions so that modules + * which are built locally are preferred over their binary versions. + */ + +configurations.all { + resolutionStrategy.dependencySubstitution { + rootProject.subprojects.each { + if (!it.name.startsWith('test-')) { + if (it.name.contains('bom')) { + substitute platform(module("io.micronaut:micronaut-${it.name}")) using platform(project(":${it.name}")) because "we want to test with what we're building" + } else { + substitute module("io.micronaut:micronaut-${it.name}") using project(":${it.name}") because "we want to test with what we're building" + } + } + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8ae588deaca..60133b42103 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,9 +32,12 @@ junit-platform="1.9.3" logback = "1.4.11" logbook-netty = "2.16.0" log4j = "2.20.0" +micronaut = "4.1.1" # This version is a *platform* version used in benchmarks only +micronaut-aot = "2.0.1" micronaut-aws = "4.0.4" micronaut-groovy = "4.0.1" micronaut-session = "4.0.0" +micronaut-serde = "2.2.4" micronaut-sql = "5.0.0-M7" micronaut-test = "4.0.2" micronaut-validation = "4.0.0-M9" @@ -83,6 +86,7 @@ test-boms-micronaut-validation = { module = "io.micronaut.validation:micronaut-v test-boms-micronaut-rxjava2 = { module = "io.micronaut.rxjava2:micronaut-rxjava2-bom", version.ref = "micronaut-rxjava2" } test-boms-micronaut-rxjava3 = { module = "io.micronaut.rxjava3:micronaut-rxjava3-bom", version.ref = "micronaut-rxjava3" } test-boms-micronaut-reactor = { module = "io.micronaut.reactor:micronaut-reactor-bom", version.ref = "micronaut-reactor" } +test-boms-micronaut-serde = { module = "io.micronaut.serde:micronaut-serde", version.ref = "micronaut-serde" } boms-groovy = { module = "org.apache.groovy:groovy-bom", version.ref = "managed-groovy" } boms-kotlin = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "managed-kotlin" } @@ -214,6 +218,10 @@ logback-classic = { module = "ch.qos.logback:logback-classic", version.ref = "lo logbook-netty = { module = "org.zalando:logbook-netty", version.ref = "logbook-netty" } +# This is only present so that the "micronaut" version is updated by renovatebot +micronaut-platform = { module = "io.micronaut.platform:micronaut-platform", version.ref = "micronaut"} + +micronaut-aot-bom = { module = "io.micronaut.aot:micronaut-aot-bom", version.ref = "micronaut-aot" } micronaut-docs = { module = "io.micronaut.docs:micronaut-docs-asciidoc-config-props", version.ref = "micronaut-docs" } micronaut-runtime-groovy = { module = "io.micronaut.groovy:micronaut-runtime-groovy", version.ref = "micronaut-groovy" } micronaut-session = { module = "io.micronaut.session:micronaut-session", version.ref = "micronaut-session" } @@ -225,6 +233,10 @@ micronaut-test-spock = { module = "io.micronaut.test:micronaut-test-spock", vers micronaut-sql-jdbc = { module = "io.micronaut.sql:micronaut-jdbc", version.ref = "micronaut-sql" } micronaut-sql-jdbc-tomcat = { module = "io.micronaut.sql:micronaut-jdbc-tomcat", version.ref = "micronaut-sql" } + +micronaut-serde-processor = { module = "io.micronaut.serde:micronaut-serde-processor", version.ref = "micronaut-serde" } +micronaut-serde-jackson = { module = "io.micronaut.serde:micronaut-serde-jackson", version.ref = "micronaut-serde" } + mysql-driver = { module = "mysql:mysql-connector-java" } neo4j-bolt = { module = "org.neo4j.driver:neo4j-java-driver", version.ref = "neo4j-java-driver" } diff --git a/settings.gradle b/settings.gradle index 2c5b7f2e6b3..6026ba7d499 100644 --- a/settings.gradle +++ b/settings.gradle @@ -67,6 +67,7 @@ include "websocket" // test suites include "test-suite" +include "test-suite-benchmarks" include "test-suite-geb" include "test-suite-helper" include "test-suite-http-client-jdk-ssl" diff --git a/test-suite-benchmarks/build.gradle b/test-suite-benchmarks/build.gradle new file mode 100644 index 00000000000..1c7525405df --- /dev/null +++ b/test-suite-benchmarks/build.gradle @@ -0,0 +1,7 @@ +plugins { + id("io.micronaut.build.internal.convention-benchmark") +} + +application { + mainClass = "io.micronaut.http.bench.Application" +} diff --git a/test-suite-benchmarks/src/jmh/java/io/micronaut/http/bench/SimpleControllerBenchmark.java b/test-suite-benchmarks/src/jmh/java/io/micronaut/http/bench/SimpleControllerBenchmark.java new file mode 100644 index 00000000000..70469bdf8fd --- /dev/null +++ b/test-suite-benchmarks/src/jmh/java/io/micronaut/http/bench/SimpleControllerBenchmark.java @@ -0,0 +1,71 @@ +/* + * Copyright 2003-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ +package io.micronaut.http.bench; + +import io.micronaut.context.ApplicationContext; +import io.micronaut.http.HttpStatus; +import io.micronaut.runtime.Micronaut; +import io.micronaut.runtime.server.EmbeddedServer; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.infra.Blackhole; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; + +public class SimpleControllerBenchmark { + @State(Scope.Benchmark) + public static class BenchmarkState { + private HttpClient client; + private URI uri; + private ApplicationContext context; + private HttpRequest listPersons; + + @Setup + public void setup() { + context = Micronaut.run(BenchmarkState.class); + uri = context.getBean(EmbeddedServer.class).getContextURI(); + client = HttpClient.newBuilder() + .build(); + listPersons = HttpRequest.newBuilder() + .GET() + .uri(uri) + .build(); + } + + @TearDown + public void shutdown() { + context.close(); + } + + } + + @Benchmark + public void getSomeJson(Blackhole blackhole, BenchmarkState app) throws IOException, InterruptedException { + var response = app.client.send(app.listPersons, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == HttpStatus.OK.getCode()) { + blackhole.consume( + response.body() + ); + } + } +} diff --git a/test-suite-benchmarks/src/main/java/io/micronaut/http/bench/Application.java b/test-suite-benchmarks/src/main/java/io/micronaut/http/bench/Application.java new file mode 100644 index 00000000000..324e5add23a --- /dev/null +++ b/test-suite-benchmarks/src/main/java/io/micronaut/http/bench/Application.java @@ -0,0 +1,24 @@ +/* + * Copyright 2003-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ +package io.micronaut.http.bench; + +import io.micronaut.runtime.Micronaut; + +public class Application { + public static void main(String[] args) { + Micronaut.run(Application.class); + } +} diff --git a/test-suite-benchmarks/src/main/java/io/micronaut/http/bench/Person.java b/test-suite-benchmarks/src/main/java/io/micronaut/http/bench/Person.java new file mode 100644 index 00000000000..f6114d017b8 --- /dev/null +++ b/test-suite-benchmarks/src/main/java/io/micronaut/http/bench/Person.java @@ -0,0 +1,10 @@ +package io.micronaut.http.bench; + +import io.micronaut.serde.annotation.Serdeable; + +@Serdeable +public record Person( + String name, + int age +) { +} diff --git a/test-suite-benchmarks/src/main/java/io/micronaut/http/bench/SimpleController.java b/test-suite-benchmarks/src/main/java/io/micronaut/http/bench/SimpleController.java new file mode 100644 index 00000000000..62d0178030d --- /dev/null +++ b/test-suite-benchmarks/src/main/java/io/micronaut/http/bench/SimpleController.java @@ -0,0 +1,34 @@ +/* + * Copyright 2003-2021 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ +package io.micronaut.http.bench; + +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; + +import java.util.List; + +@Controller +public class SimpleController { + private final List PERSONS = List.of( + new Person("Bob", 44), + new Person("Alice", 42) + ); + + @Get + public List list() { + return PERSONS; + } +} diff --git a/test-suite-benchmarks/src/main/resources/application.properties b/test-suite-benchmarks/src/main/resources/application.properties new file mode 100644 index 00000000000..c055eb7f1cc --- /dev/null +++ b/test-suite-benchmarks/src/main/resources/application.properties @@ -0,0 +1 @@ +micronaut.server.port=-1 diff --git a/test-suite-benchmarks/src/main/resources/logback.xml b/test-suite-benchmarks/src/main/resources/logback.xml new file mode 100644 index 00000000000..44b79c40d49 --- /dev/null +++ b/test-suite-benchmarks/src/main/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + +