Преглед на файлове

Inverno framework test (#6771)

* Inverno framework test

* Remove unused code

* Optimization

* remove db.sql

* Upgrade to Inverno 1.2.2
Jeremy Kuhn преди 3 години
родител
ревизия
6b5da4f533
променени са 18 файла, в които са добавени 1017 реда и са изтрити 0 реда
  1. 45 0
      frameworks/Java/inverno/README.md
  2. 47 0
      frameworks/Java/inverno/benchmark_config.json
  3. 11 0
      frameworks/Java/inverno/inverno-postgres.dockerfile
  4. 9 0
      frameworks/Java/inverno/inverno.dockerfile
  5. 125 0
      frameworks/Java/inverno/pom.xml
  6. 99 0
      frameworks/Java/inverno/src/jmods/io.vertx.core/module-info.java
  7. 38 0
      frameworks/Java/inverno/src/jmods/r2dbc.postgresql/module-info.java
  8. 36 0
      frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/AppConfiguration.java
  9. 21 0
      frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/Main.java
  10. 306 0
      frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/internal/Handler.java
  11. 67 0
      frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/internal/PoolSqlClientReactorScope.java
  12. 67 0
      frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/internal/PooledClientSqlClientReactorScope.java
  13. 25 0
      frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/model/Fortune.java
  14. 14 0
      frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/model/Message.java
  15. 33 0
      frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/model/World.java
  16. 30 0
      frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/templates/FortunesTemplate.irt
  17. 28 0
      frameworks/Java/inverno/src/main/java/module-info.java
  18. 16 0
      frameworks/Java/inverno/src/main/resources/configuration.cprops

+ 45 - 0
frameworks/Java/inverno/README.md

@@ -0,0 +1,45 @@
+# inverno Benchmarking Test
+
+### Test Type Implementation Source Code
+
+* [JSON](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
+* [PLAINTEXT](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
+* [DB](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
+* [QUERY](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
+* [CACHED QUERY](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
+* [UPDATE](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
+* [FORTUNES](src/main/java/com/techempower/inverno/benchmark/internal/Handler.java)
+
+## Important Libraries
+The tests were run with:
+* [Java OpenJDK 16](https://openjdk.java.net/)
+* [Inverno 1.2.1](https://inverno.io)
+
+## Test URLs
+### JSON
+
+http://localhost:8080/json
+
+### PLAINTEXT
+
+http://localhost:8080/plaintext
+
+### DB
+
+http://localhost:8080/db
+
+### QUERY
+
+http://localhost:8080/query?queries=
+
+### CACHED QUERY
+
+http://localhost:8080/cached_query?queries=
+
+### UPDATE
+
+http://localhost:8080/update?queries=
+
+### FORTUNES
+
+http://localhost:8080/fortunes

+ 47 - 0
frameworks/Java/inverno/benchmark_config.json

@@ -0,0 +1,47 @@
+{
+  "framework": "inverno",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "None",
+        "framework": "inverno",
+        "language": "Java",
+        "flavor": "None",
+        "orm": "Micro",
+        "platform": "Inverno",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "inverno",
+        "notes": "",
+        "versus": "None"
+      },
+      "postgres": {
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "postgres",
+        "framework": "inverno",
+        "language": "Java",
+        "flavor": "None",
+        "orm": "Micro",
+        "platform": "Inverno",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "inverno",
+        "notes": "",
+        "versus": "None"
+      }
+    }
+  ]
+}

+ 11 - 0
frameworks/Java/inverno/inverno-postgres.dockerfile

@@ -0,0 +1,11 @@
+FROM maven:3.8.2-openjdk-16 as maven
+WORKDIR /inverno
+COPY src src
+COPY pom.xml pom.xml
+RUN mvn package -q
+
+EXPOSE 8080
+
+# CMD [ "target/maven-inverno/application_linux_amd64/inverno-benchmark-1.0.0-SNAPSHOT/bin/inverno-benchmark" ]
+CMD export DBIP=`getent hosts tfb-database | awk '{ print $1 }'` && \
+    target/maven-inverno/application_linux_amd64/inverno-benchmark-1.0.0-SNAPSHOT/bin/inverno-benchmark --com.techempower.inverno.benchmark.appConfiguration.db_host=\"$DBIP\"

+ 9 - 0
frameworks/Java/inverno/inverno.dockerfile

@@ -0,0 +1,9 @@
+FROM maven:3.8.2-openjdk-16 as maven
+WORKDIR /inverno
+COPY src src
+COPY pom.xml pom.xml
+RUN mvn package -q
+
+EXPOSE 8080
+
+CMD [ "target/maven-inverno/application_linux_amd64/inverno-benchmark-1.0.0-SNAPSHOT/bin/inverno-benchmark", "--com.techempower.inverno.benchmark.appConfiguration.boot.reactor_prefer_vertx=false" ]

+ 125 - 0
frameworks/Java/inverno/pom.xml

@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>io.inverno.dist</groupId>
+		<artifactId>inverno-parent</artifactId>
+		<version>1.2.2</version>
+	</parent>
+	<groupId>com.techempower</groupId>
+	<artifactId>inverno-benchmark</artifactId>
+	<version>1.0.0-SNAPSHOT</version>
+	<packaging>jar</packaging>
+	
+	<name>inverno-benchmark</name>
+	<description>Inverno framework benchmark test</description>
+	
+	<properties>
+		<maven.compiler.source>16</maven.compiler.source>
+		<maven.compiler.target>16</maven.compiler.target>
+		<maven.compiler.release>16</maven.compiler.release>
+	</properties>
+	
+	<dependencies>
+		<dependency>
+			<groupId>io.inverno</groupId>
+			<artifactId>inverno-core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.inverno.mod</groupId>
+			<artifactId>inverno-boot</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.inverno.mod</groupId>
+			<artifactId>inverno-configuration</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.inverno.mod</groupId>
+			<artifactId>inverno-http-server</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.inverno.mod</groupId>
+			<artifactId>inverno-irt</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.inverno.mod</groupId>
+			<artifactId>inverno-sql-vertx</artifactId>
+		</dependency>
+		
+		<dependency>
+			<groupId>org.unbescape</groupId>
+			<artifactId>unbescape</artifactId>
+			<version>1.1.6.RELEASE</version>
+		</dependency>
+		<dependency>
+			<groupId>io.vertx</groupId>
+			<artifactId>vertx-pg-client</artifactId>
+		</dependency>
+		
+		<dependency>
+			<groupId>io.netty</groupId>
+			<artifactId>netty-codec-dns</artifactId>
+			<version>${version.netty}</version>
+		</dependency>
+		<dependency>
+			<groupId>io.netty</groupId>
+			<artifactId>netty-codec-socks</artifactId>
+			<version>${version.netty}</version>
+		</dependency>
+		<dependency>
+			<groupId>io.netty</groupId>
+			<artifactId>netty-handler-proxy</artifactId>
+			<version>${version.netty}</version>
+		</dependency>
+		<dependency>
+			<groupId>io.netty</groupId>
+			<artifactId>netty-resolver-dns</artifactId>
+			<version>${version.netty}</version>
+		</dependency>
+
+		<dependency>
+			<groupId>io.netty</groupId>
+			<artifactId>netty-transport-native-epoll</artifactId>
+			<classifier>linux-x86_64</classifier>
+		</dependency>
+		
+		<dependency>
+			<groupId>org.apache.logging.log4j</groupId>
+			<artifactId>log4j-core</artifactId>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>io.inverno.tool</groupId>
+				<artifactId>inverno-maven-plugin</artifactId>
+				<executions>
+					<execution>
+						<id>inverno-package</id>
+						<phase>package</phase>
+						<goals>
+							<goal>build-app</goal>
+						</goals>
+						<configuration>
+							<vm>server</vm>
+							<launchers>
+								<launcher>
+									<name>inverno-benchmark</name>
+									<vmOptions>-Xms2g -Xmx2g -server -XX:+UseNUMA -XX:+UseParallelGC -Dio.netty.leakDetection.level=disabled -Dvertx.disableHttpHeadersValidation=true -Dvertx.disableMetrics=true -Dvertx.disableH2c=true -Dvertx.disableWebsockets=true -Dvertx.flashPolicyHandler=false -Dvertx.threadChecks=false -Dvertx.disableContextTimings=true -Dvertx.disableTCCL=true --add-modules io.netty.transport.unix.common,io.netty.transport.epoll</vmOptions>
+								</launcher>
+							</launchers>
+							<formats>
+								<format>zip</format>
+							</formats>
+						</configuration>
+					</execution>
+				</executions>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
+

+ 99 - 0
frameworks/Java/inverno/src/jmods/io.vertx.core/module-info.java

@@ -0,0 +1,99 @@
+module io.vertx.core {
+    requires io.netty.handler.proxy;
+    requires io.netty.resolver.dns;
+    requires io.netty.transport.epoll;
+    requires io.netty.transport.unix.common;
+    requires java.naming;
+    requires org.apache.logging.log4j;
+
+    requires transitive com.fasterxml.jackson.core;
+    requires transitive com.fasterxml.jackson.databind;
+    requires transitive io.netty.buffer;
+    requires transitive io.netty.codec;
+    requires transitive io.netty.codec.dns;
+    requires transitive io.netty.codec.http;
+    requires transitive io.netty.codec.http2;
+    requires transitive io.netty.common;
+    requires transitive io.netty.handler;
+    requires transitive io.netty.resolver;
+    requires transitive io.netty.transport;
+    requires transitive java.compiler;
+    requires transitive java.logging;
+
+    exports io.vertx.core;
+    exports io.vertx.core.buffer;
+    exports io.vertx.core.buffer.impl;
+    exports io.vertx.core.cli;
+    exports io.vertx.core.cli.annotations;
+    exports io.vertx.core.cli.converters;
+    exports io.vertx.core.cli.impl;
+    exports io.vertx.core.datagram;
+    exports io.vertx.core.datagram.impl;
+    exports io.vertx.core.dns;
+    exports io.vertx.core.dns.impl;
+    exports io.vertx.core.dns.impl.decoder;
+    exports io.vertx.core.eventbus;
+    exports io.vertx.core.eventbus.impl;
+    exports io.vertx.core.eventbus.impl.clustered;
+    exports io.vertx.core.eventbus.impl.codecs;
+    exports io.vertx.core.file;
+    exports io.vertx.core.file.impl;
+    exports io.vertx.core.http;
+    exports io.vertx.core.http.impl;
+    exports io.vertx.core.http.impl.cgbystrom;
+    exports io.vertx.core.http.impl.headers;
+    exports io.vertx.core.http.impl.ws;
+    exports io.vertx.core.impl;
+    exports io.vertx.core.impl.cpu;
+    exports io.vertx.core.impl.future;
+    exports io.vertx.core.impl.launcher;
+    exports io.vertx.core.impl.launcher.commands;
+    exports io.vertx.core.impl.logging;
+    exports io.vertx.core.impl.resolver;
+    exports io.vertx.core.impl.utils;
+    exports io.vertx.core.impl.verticle;
+    exports io.vertx.core.json;
+    exports io.vertx.core.json.impl;
+    exports io.vertx.core.json.jackson;
+    exports io.vertx.core.json.pointer;
+    exports io.vertx.core.json.pointer.impl;
+    exports io.vertx.core.logging;
+    exports io.vertx.core.metrics;
+    exports io.vertx.core.metrics.impl;
+    exports io.vertx.core.net;
+    exports io.vertx.core.net.impl;
+    exports io.vertx.core.net.impl.pkcs1;
+    exports io.vertx.core.net.impl.pool;
+    exports io.vertx.core.net.impl.transport;
+    exports io.vertx.core.parsetools;
+    exports io.vertx.core.parsetools.impl;
+    exports io.vertx.core.shareddata;
+    exports io.vertx.core.shareddata.impl;
+    exports io.vertx.core.spi;
+    exports io.vertx.core.spi.cluster;
+    exports io.vertx.core.spi.cluster.impl;
+    exports io.vertx.core.spi.cluster.impl.selector;
+    exports io.vertx.core.spi.json;
+    exports io.vertx.core.spi.launcher;
+    exports io.vertx.core.spi.logging;
+    exports io.vertx.core.spi.metrics;
+    exports io.vertx.core.spi.observability;
+    exports io.vertx.core.spi.resolver;
+    exports io.vertx.core.spi.tracing;
+    exports io.vertx.core.streams;
+    exports io.vertx.core.streams.impl;
+    exports io.vertx.core.tracing;
+
+    provides io.vertx.core.spi.launcher.CommandFactory with
+        io.vertx.core.impl.launcher.commands.RunCommandFactory,
+        io.vertx.core.impl.launcher.commands.VersionCommandFactory,
+        io.vertx.core.impl.launcher.commands.BareCommandFactory,
+        io.vertx.core.impl.launcher.commands.ListCommandFactory,
+        io.vertx.core.impl.launcher.commands.StartCommandFactory,
+        io.vertx.core.impl.launcher.commands.StopCommandFactory;
+    
+    uses io.vertx.core.spi.VertxServiceProvider;
+    uses io.vertx.core.spi.VerticleFactory;
+    uses io.vertx.core.spi.JsonFactory;
+
+}

+ 38 - 0
frameworks/Java/inverno/src/jmods/r2dbc.postgresql/module-info.java

@@ -0,0 +1,38 @@
+module r2dbc.postgresql {
+    requires com.ongres.scram.client;
+    requires com.ongres.scram.common;
+    requires io.netty.codec;
+    requires io.netty.resolver;
+    requires io.netty.transport;
+    requires io.netty.transport.epoll;
+    requires io.netty.transport.unix.common;
+    requires java.naming;
+
+    requires transitive io.netty.buffer;
+    requires transitive io.netty.common;
+    requires transitive io.netty.handler;
+    requires transitive org.reactivestreams;
+    requires transitive r2dbc.spi;
+    requires transitive reactor.core;
+    requires transitive reactor.netty.core;
+
+    exports io.r2dbc.postgresql;
+    exports io.r2dbc.postgresql.api;
+    exports io.r2dbc.postgresql.authentication;
+    exports io.r2dbc.postgresql.client;
+    exports io.r2dbc.postgresql.codec;
+    exports io.r2dbc.postgresql.extension;
+    exports io.r2dbc.postgresql.message;
+    exports io.r2dbc.postgresql.message.backend;
+    exports io.r2dbc.postgresql.message.frontend;
+    exports io.r2dbc.postgresql.replication;
+    exports io.r2dbc.postgresql.util;
+
+    provides io.r2dbc.postgresql.extension.Extension with
+        io.r2dbc.postgresql.codec.BuiltinDynamicCodecs;
+    provides io.r2dbc.spi.ConnectionFactoryProvider with
+        io.r2dbc.postgresql.PostgresqlConnectionFactoryProvider;
+    
+    uses io.r2dbc.postgresql.extension.Extension;
+
+}

+ 36 - 0
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/AppConfiguration.java

@@ -0,0 +1,36 @@
+package com.techempower.inverno.benchmark;
+
+import io.inverno.core.annotation.NestedBean;
+import io.inverno.mod.boot.BootConfiguration;
+import io.inverno.mod.configuration.Configuration;
+import io.inverno.mod.http.server.HttpServerConfiguration;
+
+@Configuration
+public interface AppConfiguration {
+
+	@NestedBean
+	BootConfiguration boot();
+	
+	@NestedBean
+	HttpServerConfiguration http_server();
+	
+	default String db_database() {
+		return "postgres";
+	}
+	
+	default String db_host() {
+		return "localhost";
+	}
+	
+	default int db_port() {
+		return 5432;
+	}
+	
+	default String db_username() {
+		return "postgres";
+	}
+	
+	default String db_password() {
+		return "password";
+	}
+}

+ 21 - 0
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/Main.java

@@ -0,0 +1,21 @@
+package com.techempower.inverno.benchmark;
+
+import java.io.IOException;
+import java.util.function.Supplier;
+
+import io.inverno.core.annotation.Bean;
+import io.inverno.core.v1.Application;
+import io.inverno.mod.configuration.ConfigurationSource;
+import io.inverno.mod.configuration.source.BootstrapConfigurationSource;
+
+public class Main {
+
+	@Bean
+	public interface AppConfigurationSource extends Supplier<ConfigurationSource<?, ?, ?>> {}
+	
+	public static void main(String[] args) throws IllegalStateException, IOException {
+		Application.with(new Benchmark.Builder()
+			.setAppConfigurationSource(new BootstrapConfigurationSource(Main.class.getModule(), args))
+		).run();
+	}
+}

+ 306 - 0
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/internal/Handler.java

@@ -0,0 +1,306 @@
+package com.techempower.inverno.benchmark.internal;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Collections;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.techempower.inverno.benchmark.model.Fortune;
+import com.techempower.inverno.benchmark.model.Message;
+import com.techempower.inverno.benchmark.model.World;
+import com.techempower.inverno.benchmark.templates.FortunesTemplate;
+
+import io.inverno.core.annotation.Bean;
+import io.inverno.core.annotation.Bean.Visibility;
+import io.inverno.core.annotation.Destroy;
+import io.inverno.core.annotation.Init;
+import io.inverno.mod.base.Charsets;
+import io.inverno.mod.base.concurrent.Reactor;
+import io.inverno.mod.base.concurrent.ReactorScope;
+import io.inverno.mod.base.converter.ConverterException;
+import io.inverno.mod.http.base.HttpException;
+import io.inverno.mod.http.base.InternalServerErrorException;
+import io.inverno.mod.http.base.Parameter;
+import io.inverno.mod.http.base.Status;
+import io.inverno.mod.http.server.Exchange;
+import io.inverno.mod.http.server.ExchangeHandler;
+import io.inverno.mod.sql.SqlClient;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.EventLoopGroup;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.netty.util.AsciiString;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+@Bean( visibility = Visibility.PRIVATE )
+public class Handler implements ExchangeHandler<Exchange> {
+
+	private static final String PATH_PLAINTEXT = "/plaintext";
+	private static final String PATH_JSON = "/json";
+	private static final String PATH_DB = "/db";
+	private static final String PATH_QUERIES = "/queries";
+	private static final String PATH_UPDATES = "/updates";
+	private static final String PATH_FORTUNES = "/fortunes";
+	
+	private static final CharSequence STATIC_SERVER = AsciiString.cached("inverno");
+
+	private final Reactor reactor;
+	private final ObjectMapper mapper;
+	private final ReactorScope<SqlClient> pooledClientSqlClient;
+	private final ReactorScope<SqlClient> poolSqlClient;
+	
+	private EventLoopGroup dateEventLoopGroup;
+	
+	private CharSequence date;
+	
+	public Handler(Reactor reactor, 
+			ObjectMapper mapper,
+			ReactorScope<SqlClient> pooledClientSqlClient,
+			ReactorScope<SqlClient> poolSqlClient
+		) {
+		this.reactor = reactor;
+		this.mapper = mapper;
+		this.pooledClientSqlClient = pooledClientSqlClient;
+		this.poolSqlClient = poolSqlClient;
+	}
+	
+	@Init
+	public void init() {
+		this.dateEventLoopGroup = this.reactor.createIoEventLoopGroup(1);
+		this.dateEventLoopGroup.scheduleAtFixedRate(() -> {
+			this.date = new AsciiString(DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now()));
+		}, 0, 1000, TimeUnit.MILLISECONDS);
+	}
+	
+	@Destroy
+	public void destroy() {
+		this.dateEventLoopGroup.shutdownGracefully();
+	}
+	
+	@Override
+	public void handle(Exchange exchange) throws HttpException {
+		switch(exchange.request().getPath()) {
+			case PATH_PLAINTEXT: {
+				this.handle_plaintext(exchange);
+				break;
+			}
+			case PATH_JSON: {
+				this.handle_json(exchange);
+				break;
+			}
+			case PATH_DB: {
+				this.handle_db(exchange);
+				break;
+			}
+			case PATH_FORTUNES: {
+				this.handle_fortunes(exchange);
+				break;
+			}
+			default: {
+				switch(exchange.request().getPathAbsolute()) {
+					case PATH_QUERIES: {
+						this.handle_queries(exchange);
+						break;
+					}
+					case PATH_UPDATES: {
+						this.handle_updates(exchange);
+						break;
+					}
+					default: {
+						exchange.response()
+							.headers(h -> h.status(Status.NOT_FOUND))
+							.body()
+								.empty();
+					}
+				}
+			}
+		}
+	}
+
+	private static final byte[] STATIC_PLAINTEXT = "Hello, World!".getBytes(Charsets.UTF_8);
+	private static final int STATIC_PLAINTEXT_LEN = STATIC_PLAINTEXT.length;
+
+	private static final ByteBuf STATIC_PLAINTEXT_BYTEBUF;
+	static {
+		ByteBuf tmpBuf = Unpooled.directBuffer(STATIC_PLAINTEXT_LEN);
+		tmpBuf.writeBytes(STATIC_PLAINTEXT);
+		STATIC_PLAINTEXT_BYTEBUF = Unpooled.unreleasableBuffer(tmpBuf);
+	}
+	
+	private static final CharSequence STATIC_PLAINTEXT_LEN_VALUE = AsciiString.cached(String.valueOf(STATIC_PLAINTEXT_LEN));
+	
+	private static class PlaintextSupplier implements Supplier<ByteBuf> {
+		@Override
+		public ByteBuf get() {
+			return STATIC_PLAINTEXT_BYTEBUF.duplicate();
+		}
+	}
+	
+	private static final Mono<ByteBuf> PLAIN_TEXT_MONO = Mono.fromSupplier(new PlaintextSupplier());
+	
+	public void handle_plaintext(Exchange exchange) throws HttpException {
+		exchange.response()
+			.headers(h -> h
+				.add(HttpHeaderNames.SERVER, STATIC_SERVER)
+				.add(HttpHeaderNames.DATE, date)
+				.add(HttpHeaderNames.CONTENT_LENGTH, STATIC_PLAINTEXT_LEN_VALUE)
+				.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN)
+			)
+			.body()
+				.raw()
+					.stream(PLAIN_TEXT_MONO);
+	}
+	
+	public void handle_json(Exchange exchange) throws HttpException {
+		try {
+			exchange.response()
+				.headers(h -> h
+					.add(HttpHeaderNames.SERVER, STATIC_SERVER)
+					.add(HttpHeaderNames.DATE, this.date)
+					.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON)
+				)
+				.body()
+					.raw()
+						.value(Unpooled.unreleasableBuffer(Unpooled.wrappedBuffer(this.mapper.writeValueAsBytes(new Message("Hello, World!")))));
+		} 
+		catch (JsonProcessingException | IllegalStateException e) {
+			throw new InternalServerErrorException("Error serializing message as JSON", e);
+		}
+	}
+
+	private static final String DB_SELECT_WORLD = "SELECT id, randomnumber from WORLD where id = $1";
+	
+	private static int randomWorldId() {
+		return 1 + ThreadLocalRandom.current().nextInt(10000);
+	}
+	
+	public void handle_db(Exchange exchange) throws HttpException {
+		exchange.response()
+			.headers(h -> h
+				.add(HttpHeaderNames.SERVER, STATIC_SERVER)
+				.add(HttpHeaderNames.DATE, this.date)
+				.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON)
+			)
+			.body()
+				.raw().stream(this.pooledClientSqlClient.get().queryForObject(
+						DB_SELECT_WORLD, 
+						row -> {
+							try {
+								return Unpooled.unreleasableBuffer(Unpooled.wrappedBuffer(this.mapper.writeValueAsBytes(new World(row.getInteger(0), row.getInteger(1)))));
+							} 
+							catch (JsonProcessingException e) {
+								throw new InternalServerErrorException(e);
+							}
+						}, 
+						randomWorldId()
+					)
+				);
+	}
+	
+	private static final String PARAMETER_QUERIES = "queries";
+
+	private int extractQueriesParameter(Exchange exchange) {
+		try {
+			return Math.min(500, Math.max(1, exchange.request().queryParameters().get(PARAMETER_QUERIES).map(Parameter::asInteger).orElse(1)));
+		}
+		catch (ConverterException e) { // TODO
+			return 1;
+		}
+	}
+	
+	public void handle_queries(Exchange exchange) throws HttpException {
+		int queries = this.extractQueriesParameter(exchange);
+		exchange.response()
+			.headers(h -> h
+				.add(HttpHeaderNames.SERVER, STATIC_SERVER)
+				.add(HttpHeaderNames.DATE, this.date)
+				.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON)
+			)
+			.body()
+				.raw().stream(Flux.range(0, queries)
+					.flatMap(ign -> this.pooledClientSqlClient.get().queryForObject(
+						DB_SELECT_WORLD, 
+						row -> new World(row.getInteger(0), row.getInteger(1)), 
+						randomWorldId()
+					))
+					.collectList()
+					.map(worlds -> {
+						try {
+							return Unpooled.unreleasableBuffer(Unpooled.wrappedBuffer(this.mapper.writeValueAsBytes(worlds)));
+						} 
+						catch (JsonProcessingException e) {
+							throw new InternalServerErrorException(e);
+						}
+					})
+				);
+	}
+	
+	private static final String DB_UPDATE_WORLD = "UPDATE world SET randomnumber=$1 WHERE id=$2";
+	
+	public void handle_updates(Exchange exchange) throws HttpException {
+		int queries = this.extractQueriesParameter(exchange);
+		
+		exchange.response()
+			.headers(h -> h
+				.add(HttpHeaderNames.SERVER, STATIC_SERVER)
+				.add(HttpHeaderNames.DATE, this.date)
+				.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.APPLICATION_JSON)
+			)
+			.body()
+				.raw().stream(this.poolSqlClient.get().connection(ops -> Flux.range(0, queries)
+					.flatMap(ign -> ops.queryForObject(
+							DB_SELECT_WORLD, 
+							row -> new World(row.getInteger(0), randomWorldId()), 
+							randomWorldId()
+						)
+					)
+					.collectSortedList()
+					.delayUntil(worlds -> ops.batchUpdate(
+							DB_UPDATE_WORLD, 
+							worlds.stream().map(world -> new Object[] { world.getRandomNumber(), world.getId() })
+						)
+					)
+					.map(worlds -> {
+						try {
+							return Unpooled.unreleasableBuffer(Unpooled.wrappedBuffer(this.mapper.writeValueAsBytes(worlds)));
+						} 
+						catch (JsonProcessingException e) {
+							throw new InternalServerErrorException(e);
+						}
+					}))
+				);
+	}
+	
+	private static final String DB_SELECT_FORTUNE = "SELECT id, message from FORTUNE";
+	private static final CharSequence MEDIA_TEXT_HTML_UTF8 = AsciiString.cached("text/html; charset=utf-8");
+	
+	private static final FortunesTemplate.Renderer<CompletableFuture<ByteBuf>> FORTUNES_RENDERER = FortunesTemplate.bytebuf(() -> Unpooled.unreleasableBuffer(Unpooled.buffer()));
+	
+	public void handle_fortunes(Exchange exchange) throws HttpException {
+		exchange.response()
+			.headers(h -> h
+				.add(HttpHeaderNames.SERVER, STATIC_SERVER)
+				.add(HttpHeaderNames.DATE, this.date)
+				.add(HttpHeaderNames.CONTENT_TYPE, MEDIA_TEXT_HTML_UTF8)
+			)
+			.body()
+				.raw().stream(Flux.from(this.pooledClientSqlClient.get().query(
+						DB_SELECT_FORTUNE, 
+						row -> new Fortune(row.getInteger(0), row.getString(1))
+					))
+					.collectList()
+					.flatMap(fortunes -> {
+						fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+						Collections.sort(fortunes);
+						return Mono.fromFuture(() -> FORTUNES_RENDERER.render(fortunes));
+					})
+				);
+	}
+}

+ 67 - 0
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/internal/PoolSqlClientReactorScope.java

@@ -0,0 +1,67 @@
+package com.techempower.inverno.benchmark.internal;
+
+import com.techempower.inverno.benchmark.AppConfiguration;
+
+import io.inverno.core.annotation.Bean;
+import io.inverno.core.annotation.Destroy;
+import io.inverno.core.annotation.Init;
+import io.inverno.core.annotation.Bean.Visibility;
+import io.inverno.mod.base.concurrent.Reactor;
+import io.inverno.mod.base.concurrent.ReactorScope;
+import io.inverno.mod.base.concurrent.VertxReactor;
+import io.inverno.mod.sql.SqlClient;
+import io.inverno.mod.sql.vertx.PoolSqlClient;
+import io.vertx.core.Vertx;
+import io.vertx.core.VertxOptions;
+import io.vertx.pgclient.PgConnectOptions;
+import io.vertx.pgclient.PgPool;
+import io.vertx.sqlclient.PoolOptions;
+
+@Bean( name = "poolSqlClient", visibility = Visibility.PRIVATE )
+public class PoolSqlClientReactorScope extends ReactorScope<SqlClient> {
+
+	private final AppConfiguration configuration;
+	private final Reactor reactor;
+	
+	private Vertx vertx;
+	private PgConnectOptions connectOptions;
+	private PoolOptions poolOptions;
+	
+	public PoolSqlClientReactorScope(AppConfiguration configuration, Reactor reactor) {
+		this.configuration = configuration;
+		this.reactor = reactor;
+	}
+	
+	@Init
+	public void init() {
+		if(this.reactor instanceof VertxReactor) {
+			this.vertx = ((VertxReactor)this.reactor).getVertx();			
+		}
+		else {
+			this.vertx = Vertx.vertx(new VertxOptions().setPreferNativeTransport(this.configuration.boot().prefer_native_transport()));
+		}
+		
+		this.connectOptions = new PgConnectOptions()
+				.setHost(this.configuration.db_host())
+				.setPort(this.configuration.db_port())
+				.setDatabase(this.configuration.db_database())
+				.setUser(this.configuration.db_username())
+				.setPassword(this.configuration.db_password())
+				.setCachePreparedStatements(true);
+		
+		this.poolOptions = new PoolOptions().setMaxSize(4);
+	}
+	
+	@Destroy
+	public void destroy() {
+		if(!(this.reactor instanceof VertxReactor)) {
+			this.vertx.close();			
+		}
+	}
+	
+	@Override
+	protected SqlClient create() {
+		return new PoolSqlClient(PgPool.pool(this.vertx, this.connectOptions, this.poolOptions));
+	}
+
+}

+ 67 - 0
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/internal/PooledClientSqlClientReactorScope.java

@@ -0,0 +1,67 @@
+package com.techempower.inverno.benchmark.internal;
+
+import com.techempower.inverno.benchmark.AppConfiguration;
+
+import io.inverno.core.annotation.Bean;
+import io.inverno.core.annotation.Bean.Visibility;
+import io.inverno.core.annotation.Destroy;
+import io.inverno.core.annotation.Init;
+import io.inverno.mod.base.concurrent.Reactor;
+import io.inverno.mod.base.concurrent.ReactorScope;
+import io.inverno.mod.base.concurrent.VertxReactor;
+import io.inverno.mod.sql.SqlClient;
+import io.inverno.mod.sql.vertx.PooledClientSqlClient;
+import io.vertx.core.Vertx;
+import io.vertx.core.VertxOptions;
+import io.vertx.pgclient.PgConnectOptions;
+import io.vertx.pgclient.PgPool;
+import io.vertx.sqlclient.PoolOptions;
+
+@Bean( name = "pooledClientSqlClient", visibility = Visibility.PRIVATE )
+public class PooledClientSqlClientReactorScope extends ReactorScope<SqlClient> {
+
+	private final AppConfiguration configuration;
+	private final Reactor reactor;
+	
+	private Vertx vertx;
+	private PgConnectOptions connectOptions;
+	private PoolOptions poolOptions;
+	
+	public PooledClientSqlClientReactorScope(AppConfiguration configuration, Reactor reactor) {
+		this.configuration = configuration;
+		this.reactor = reactor;
+	}
+	
+	@Init
+	public void init() {
+		if(this.reactor instanceof VertxReactor) {
+			this.vertx = ((VertxReactor)this.reactor).getVertx();			
+		}
+		else {
+			this.vertx = Vertx.vertx(new VertxOptions().setPreferNativeTransport(this.configuration.boot().prefer_native_transport()));
+		}
+		
+		this.connectOptions = new PgConnectOptions()
+				.setHost(this.configuration.db_host())
+				.setPort(this.configuration.db_port())
+				.setDatabase(this.configuration.db_database())
+				.setUser(this.configuration.db_username())
+				.setPassword(this.configuration.db_password())
+				.setCachePreparedStatements(true);
+		
+		this.poolOptions = new PoolOptions().setMaxSize(1);
+	}
+	
+	@Destroy
+	public void destroy() {
+		if(!(this.reactor instanceof VertxReactor)) {
+			this.vertx.close();			
+		}
+	}
+	
+	@Override
+	protected SqlClient create() {
+		return new PooledClientSqlClient(PgPool.client(this.vertx, this.connectOptions, this.poolOptions));
+	}
+
+}

+ 25 - 0
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/model/Fortune.java

@@ -0,0 +1,25 @@
+package com.techempower.inverno.benchmark.model;
+
+public final class Fortune implements Comparable<Fortune> {
+
+	private final int id;
+	private final String message;
+
+	public Fortune(int id, String message) {
+		this.id = id;
+		this.message = message;
+	}
+
+	public int getId() {
+		return this.id;
+	}
+
+	public String getMessage() {
+		return this.message;
+	}
+
+	@Override
+	public int compareTo(Fortune other) {
+		return getMessage().compareTo(other.getMessage());
+	}
+}

+ 14 - 0
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/model/Message.java

@@ -0,0 +1,14 @@
+package com.techempower.inverno.benchmark.model;
+
+public final class Message {
+
+	private final String message;
+	
+	public Message(String message) {
+		this.message = message;
+	}
+
+	public String getMessage() {
+		return this.message;
+	}
+}

+ 33 - 0
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/model/World.java

@@ -0,0 +1,33 @@
+package com.techempower.inverno.benchmark.model;
+
+public final class World implements Comparable<World> {
+
+	private final int id;
+	private int randomNumber;
+
+	public World(int id) {
+		this.id = id;
+	}
+	
+	public World(int id, int randomNumber) {
+		this.id = id;
+		this.randomNumber = randomNumber;
+	}
+	
+	public int getId() {
+		return this.id;
+	}
+
+	public int getRandomNumber() {
+		return this.randomNumber;
+	}
+	
+	public void setRandomNumber(int randomNumber) {
+		this.randomNumber = randomNumber;
+	}
+
+	@Override
+	public int compareTo(World o) {
+		return Integer.compare(id, o.id);
+	}
+}

+ 30 - 0
frameworks/Java/inverno/src/main/java/com/techempower/inverno/benchmark/templates/FortunesTemplate.irt

@@ -0,0 +1,30 @@
+package com.techempower.inverno.benchmark.templates;
+
+import com.techempower.inverno.benchmark.model.Fortune;
+
+import java.util.List;
+import org.unbescape.html.HtmlEscape;
+
+option modes = {"STRING", "BYTEBUF", "STREAM", "PUBLISHER_STRING", "PUBLISHER_BYTEBUF"};
+option charset = "utf-8";
+
+(List<Fortune> fortunes) -> {
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+	<tr>
+		<th>id</th>
+		<th>message</th>
+	</tr>{fortunes}
+</table>
+</body>
+</html>
+}
+
+(Fortune fortune) -> {
+	<tr>
+		<td>{@fortune.id}</td>
+		<td>{@fortune.message|((String s) -> HtmlEscape.escapeHtml5Xml(s))}</td>
+	</tr>}

+ 28 - 0
frameworks/Java/inverno/src/main/java/module-info.java

@@ -0,0 +1,28 @@
[email protected]( excludes = { "io.inverno.mod.sql.vertx" } )
+
[email protected](beans="pooledClientSqlClient", into="handler:pooledClientSqlClient") 
[email protected](beans="poolSqlClient", into="handler:poolSqlClient") 
+module com.techempower.inverno.benchmark {
+	requires io.inverno.mod.boot;
+	requires io.inverno.mod.http.server;
+	requires io.inverno.mod.irt;
+	
+	requires io.inverno.mod.sql;
+	requires io.inverno.mod.sql.vertx;
+	
+	requires io.netty.common;
+	requires io.netty.codec.http;
+	requires unbescape;
+	
+	requires io.vertx.client.sql.pg;
+	requires io.vertx.client.sql;
+	requires io.vertx.core;
+	requires java.sql;
+	
+	requires transitive io.netty.transport;
+	requires static io.netty.transport.unix.common;
+	requires static io.netty.transport.epoll;
+	
+	exports com.techempower.inverno.benchmark;
+	exports com.techempower.inverno.benchmark.model;
+}

+ 16 - 0
frameworks/Java/inverno/src/main/resources/configuration.cprops

@@ -0,0 +1,16 @@
+com.techempower.inverno.benchmark.appConfiguration {
+	boot {
+		reactor_prefer_vertx = true
+		accept_backlog = null
+	}
+	http_server { 
+		tls_enabled = false
+		server_port = 8080
+		h2c_enabled = false
+	}
+	db_database = "hello_world"
+	db_host = "tfb-database"
+	db_port = 5432
+	db_username = "benchmarkdbuser"
+	db_password = "benchmarkdbpass"
+}