Browse Source

Benchmark submission for Oracle Helidon framework (#4149)

jringuette 6 years ago
parent
commit
6103a6b063

+ 1 - 0
.travis.yml

@@ -49,6 +49,7 @@ env:
      - "TESTDIR=Java/greenlightning"
      - "TESTDIR=Java/grizzly"
      - "TESTDIR=Java/grizzly-jersey"
+     - "TESTDIR=Java/helidon"
      - "TESTDIR=Java/jawn"
      - "TESTDIR=Java/javalin"
      - "TESTDIR=Java/jetty"

+ 61 - 0
frameworks/Java/helidon/README.md

@@ -0,0 +1,61 @@
+# Helidon Benchmarking Test
+
+This is the Helidon portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+There is currently one repository implementation.
+* [JdbcRepository](src/main/java/io/helidon/benchmark/models/JdbcRepository.java) is using JDBC and an io thread pool of size (2 * cores count) to prevent blocking netty's main event loop. It uses hikaricp to manage the connection pool. It is configured for a maximum of (2 * cores count) concurrent connections. See [About-Pool-Sizing](https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing) for more information. I am assuming that the DB is running on a computer with the same spec as the one running the app, which seems to be the case based on what is written [here](https://www.techempower.com/benchmarks/#section=environment&hw=ph&test=db&l=fjd9b3)
+
+### Plaintext Test
+
+* [Plaintext test source](src/main/java/io/helidon/benchmark/services/PlainTextService.java)
+
+### JSON Serialization Test
+
+* [JSON test source](src/main/java/io/helidon/benchmark/services/JsonService.java)
+
+### Database Query Test
+
+* [Database Query test source](src/main/java/io/helidon/benchmark/services/DbService.java)
+
+### Database Queries Test
+
+* [Database Queries test source](src/main/java/io/helidon/benchmark/services/DbService.java)
+
+### Database Update Test
+
+* [Database Update test source](src/main/java/io/helidon/benchmark/services/DbService.java)
+
+### Template rendering Test
+
+* [Template rendering test source](src/main/java/io/helidon/benchmark/services/FortuneService.java)
+
+## Versions
+
+* [Java OpenJDK 1.8](http://openjdk.java.net/)
+* [Helidon 0.10.1](http://helidon.io/)
+
+## Test URLs
+
+### Plaintext Test
+
+    http://localhost:8080/plaintext
+
+### JSON Encoding Test
+
+    http://localhost:8080/json
+
+### Database Query Test
+
+    http://localhost:8080/db
+
+### Database Queries Test
+
+    http://localhost:8080/queries?queries=5
+
+### Database Update Test
+
+    http://localhost:8080/updates?queries=5
+
+### Template rendering Test
+
+    http://localhost:8080/fortunes

+ 30 - 0
frameworks/Java/helidon/benchmark_config.json

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

+ 11 - 0
frameworks/Java/helidon/helidon.dockerfile

@@ -0,0 +1,11 @@
+FROM maven:3.5.3-jdk-8-slim as maven
+WORKDIR /helidon
+COPY src src
+COPY pom.xml pom.xml
+RUN mvn package -q
+
+FROM openjdk:8-jre-slim
+WORKDIR /helidon
+COPY --from=maven /helidon/target/libs libs
+COPY --from=maven /helidon/target/benchmark.jar app.jar
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-Dlogging.level.root=DEBUG", "-jar", "app.jar"]

+ 195 - 0
frameworks/Java/helidon/pom.xml

@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+
+    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
+
+        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.
+
+--><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>
+
+    <groupId>io.helidon</groupId>
+    <artifactId>benchmark</artifactId>
+    <version>1.0-SNAPSHOT</version>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+
+    <properties>
+        <helidon.version>0.10.1</helidon.version>
+        <!-- Default package. Will be overriden by Maven archetype -->
+        <package>io.helidon.examples.quickstart.se</package>
+        <mainClass>io.helidon.benchmark.Main</mainClass>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>${maven.compiler.source}</maven.compiler.target>
+        <libs.classpath.prefix>libs</libs.classpath.prefix>
+        <copied.libs.dir>${project.build.directory}/${libs.classpath.prefix}</copied.libs.dir>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <argLine>-Dfile.encoding=UTF-8</argLine>
+    </properties>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <pluginManagement>
+            <plugins>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-compiler-plugin</artifactId>
+                    <version>3.1</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-surefire-plugin</artifactId>
+                    <version>2.19.1</version>
+                    <dependencies>
+                        <dependency>
+                            <groupId>org.junit.platform</groupId>
+                            <artifactId>junit-platform-surefire-provider</artifactId>
+                            <version>1.1.0</version>
+                        </dependency>
+                    </dependencies>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-dependency-plugin</artifactId>
+                    <version>2.9</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-resources-plugin</artifactId>
+                    <version>3.0.2</version>
+                </plugin>
+                <plugin>
+                    <groupId>org.apache.maven.plugins</groupId>
+                    <artifactId>maven-jar-plugin</artifactId>
+                    <version>2.5</version>
+                    <configuration>
+                        <archive>
+                            <manifest>
+                                <addClasspath>true</addClasspath>
+                                <classpathPrefix>${libs.classpath.prefix}</classpathPrefix>
+                                <mainClass>${mainClass}</mainClass>
+                            </manifest>
+                        </archive>
+                    </configuration>
+                </plugin>
+            </plugins>
+        </pluginManagement>
+
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>copy-dependencies</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                        <configuration>
+                            <outputDirectory>${copied.libs.dir}</outputDirectory>
+                            <overWriteReleases>false</overWriteReleases>
+                            <overWriteSnapshots>false</overWriteSnapshots>
+                            <overWriteIfNewer>true</overWriteIfNewer>
+                            <overWriteIfNewer>true</overWriteIfNewer>
+                            <includeScope>runtime</includeScope>
+                            <excludeScope>test</excludeScope>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>io.helidon</groupId>
+                <artifactId>helidon-bom</artifactId>
+                <version>${helidon.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>io.reactivex.rxjava2</groupId>
+                <artifactId>rxjava</artifactId>
+                <version>2.2.2</version>
+            </dependency>
+            <dependency>
+                <groupId>com.zaxxer</groupId>
+                <artifactId>HikariCP</artifactId>
+                <version>2.7.8</version>
+            </dependency>
+            <dependency>
+                <groupId>com.github.spullara.mustache.java</groupId>
+                <artifactId>compiler</artifactId>
+                <version>0.9.5</version>
+            </dependency>
+            <dependency>
+                <groupId>org.postgresql</groupId>
+                <artifactId>postgresql</artifactId>
+                <version>42.2.5</version>
+                <scope>runtime</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.junit.jupiter</groupId>
+                <artifactId>junit-jupiter-api</artifactId>
+                <version>5.1.0</version>
+            </dependency>
+            <dependency>
+                <groupId>org.junit.jupiter</groupId>
+                <artifactId>junit-jupiter-engine</artifactId>
+                <version>5.1.0</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>io.helidon.webserver</groupId>
+            <artifactId>helidon-webserver-bundle</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.helidon.config</groupId>
+            <artifactId>helidon-config-yaml</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.reactivex.rxjava2</groupId>
+            <artifactId>rxjava</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.zaxxer</groupId>
+            <artifactId>HikariCP</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.github.spullara.mustache.java</groupId>
+            <artifactId>compiler</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>

+ 137 - 0
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/Main.java

@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * 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
+ *
+ *     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.
+ */
+
+package io.helidon.benchmark;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import io.helidon.benchmark.models.DbRepository;
+import io.helidon.benchmark.models.JdbcRepository;
+import io.helidon.benchmark.services.DbService;
+import io.helidon.benchmark.services.FortuneService;
+import io.helidon.benchmark.services.JsonService;
+import io.helidon.benchmark.services.PlainTextService;
+import io.helidon.config.Config;
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.ServerConfiguration;
+import io.helidon.webserver.WebServer;
+import io.reactivex.Scheduler;
+import io.reactivex.schedulers.Schedulers;
+
+import javax.sql.DataSource;
+import java.io.IOException;
+import java.util.concurrent.Executors;
+import java.util.logging.LogManager;
+
+/**
+ * Simple Hello World rest application.
+ */
+public final class Main {
+
+    /**
+     * Cannot be instantiated.
+     */
+    private Main() { }
+
+    private static Scheduler getScheduler() {
+        return Schedulers.from(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2));
+    }
+
+    private static DataSource getDataSource(Config config) {
+        HikariConfig hikariConfig = new HikariConfig();
+        hikariConfig.setJdbcUrl(config.get("jdbcUrl").asString());
+        hikariConfig.setUsername(config.get("username").asString());
+        hikariConfig.setPassword(config.get("password").asString());
+        hikariConfig.setMaximumPoolSize(Runtime.getRuntime().availableProcessors() * 2);
+
+        return new HikariDataSource(hikariConfig);
+    }
+
+    private static DbRepository getRepository(Config config) {
+        DataSource dataSource = getDataSource(config.get("dataSource"));
+        Scheduler scheduler = getScheduler();
+        return new JdbcRepository(dataSource, scheduler);
+    }
+
+    private static Mustache getTemplate() {
+        MustacheFactory mf = new DefaultMustacheFactory();
+        return mf.compile("fortunes.mustache");
+    }
+
+    /**
+     * Creates new {@link Routing}.
+     *
+     * @return the new instance
+     */
+    private static Routing createRouting(Config config) {
+        DbRepository repository = getRepository(config);
+
+        return Routing.builder()
+                .any((req, res) -> {
+                    res.headers().add("Server", "Helidon");
+                    req.next();
+                })
+                .register(new JsonService())
+                .register(new PlainTextService())
+                .register(new DbService(repository))
+                .register(new FortuneService(repository, getTemplate()))
+                .build();
+    }
+
+    /**
+     * Application main entry point.
+     * @param args command line arguments.
+     * @throws IOException if there are problems reading logging properties
+     */
+    public static void main(final String[] args) throws IOException {
+        startServer();
+    }
+
+    /**
+     * Start the server.
+     * @return the created {@link WebServer} instance
+     * @throws IOException if there are problems reading logging properties
+     */
+    protected static WebServer startServer() throws IOException {
+
+        // load logging configuration
+        LogManager.getLogManager().readConfiguration(
+                Main.class.getResourceAsStream("/logging.properties"));
+
+        // By default this will pick up application.yaml from the classpath
+        Config config = Config.create();
+
+        // Get webserver config from the "server" section of application.yaml
+        ServerConfiguration serverConfig =
+                ServerConfiguration.fromConfig(config.get("server"));
+
+        WebServer server = WebServer.create(serverConfig, createRouting(config));
+
+        // Start the server and print some info.
+        server.start().thenAccept(ws -> {
+            System.out.println("WEB server is up! http://localhost:" + ws.port());
+        });
+
+        // Server threads are not demon. NO need to block. Just react.
+        server.whenShutdown().thenRun(()
+                -> System.out.println("WEB server is DOWN. Good bye!"));
+
+        return server;
+    }
+}

+ 13 - 0
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/models/DbRepository.java

@@ -0,0 +1,13 @@
+package io.helidon.benchmark.models;
+
+import io.reactivex.Single;
+
+import java.util.List;
+
+public interface DbRepository {
+    Single<World> getWorld(int id);
+
+    Single<World> updateWorld(World world);
+
+    Single<List<Fortune>> getFortunes();
+}

+ 19 - 0
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/models/Fortune.java

@@ -0,0 +1,19 @@
+package io.helidon.benchmark.models;
+
+public final class Fortune {
+    public int id;
+    public String message;
+
+    public Fortune(int id, String message) {
+        this.id = id;
+        this.message = message;
+    }
+
+    public int getId() {
+        return id;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}

+ 67 - 0
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/models/JdbcRepository.java

@@ -0,0 +1,67 @@
+package io.helidon.benchmark.models;
+
+import io.reactivex.Scheduler;
+import io.reactivex.Single;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.util.ArrayList;
+import java.util.List;
+
+public class JdbcRepository implements DbRepository {
+    private final DataSource dataSource;
+    private final Scheduler scheduler;
+
+    public JdbcRepository(DataSource dataSource, Scheduler scheduler) {
+        this.dataSource = dataSource;
+        this.scheduler = scheduler;
+    }
+
+    @Override
+    public Single<World> getWorld(int id) {
+        return Single.fromCallable(() -> {
+            try (Connection connection = dataSource.getConnection()) {
+                    PreparedStatement statement = connection.prepareStatement("SELECT id, randomnumber FROM world WHERE id = ?");
+                    statement.setInt(1, id);
+                    ResultSet rs = statement.executeQuery();
+                    rs.next();
+                    World world = new World(rs.getInt(1), rs.getInt(2));
+                    statement.close();
+                    return world;
+            }
+        }).subscribeOn(this.scheduler);
+    }
+
+    @Override
+    public Single<World> updateWorld(World world) {
+        return Single.fromCallable(() -> {
+                    try (Connection connection = dataSource.getConnection()) {
+                        PreparedStatement statement = connection.prepareStatement("UPDATE world SET randomnumber = ? WHERE id = ?");
+                        statement.setInt(1, world.randomNumber);
+                        statement.setInt(2, world.id);
+                        statement.execute();
+                        statement.close();
+                        return world;
+                    }
+        }).subscribeOn(this.scheduler);
+    }
+
+    @Override
+    public Single<List<Fortune>> getFortunes() {
+        return Single.fromCallable(() -> {
+            try (Connection connection = dataSource.getConnection()) {
+                PreparedStatement statement = connection.prepareStatement("SELECT id, message FROM fortune");
+                ResultSet rs = statement.executeQuery();
+
+                List<Fortune> fortunes = new ArrayList<>();
+                while (rs.next()) {
+                    fortunes.add(new Fortune(rs.getInt(1), rs.getString(2)));
+                }
+                statement.close();
+                return fortunes;
+            }
+        }).subscribeOn(this.scheduler);
+    }
+}

+ 18 - 0
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/models/World.java

@@ -0,0 +1,18 @@
+package io.helidon.benchmark.models;
+
+import javax.json.Json;
+import javax.json.JsonObject;
+
+public final class World {
+    public int id;
+    public int randomNumber;
+
+    public World(int id, int randomNumber) {
+        this.id = id;
+        this.randomNumber = randomNumber;
+    }
+
+    public JsonObject toJson() {
+        return Json.createObjectBuilder().add("id", id).add("randomNumber", randomNumber).build();
+    }
+}

+ 129 - 0
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/services/DbService.java

@@ -0,0 +1,129 @@
+package io.helidon.benchmark.services;
+
+import io.helidon.benchmark.models.DbRepository;
+import io.helidon.benchmark.models.World;
+import io.helidon.common.http.DataChunk;
+import io.helidon.common.http.MediaType;
+import io.helidon.common.http.Parameters;
+import io.helidon.common.reactive.ReactiveStreamsAdapter;
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.ServerRequest;
+import io.helidon.webserver.ServerResponse;
+import io.helidon.webserver.Service;
+import io.reactivex.Flowable;
+import io.reactivex.Single;
+
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonStructure;
+import javax.json.JsonWriter;
+import javax.json.JsonWriterFactory;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class DbService implements Service {
+
+    private final DbRepository repository;
+    private JsonWriterFactory jsonWriterFactory;
+
+    public DbService(DbRepository repository) {
+        this.repository = repository;
+
+        this.jsonWriterFactory = Json.createWriterFactory(null);
+    }
+
+    @Override
+    public void update(Routing.Rules rules) {
+        rules.get("/db", this::db);
+        rules.get("/queries", this::queries);
+        rules.get("/updates", this::updates);
+    }
+
+    private void db(final ServerRequest request,
+                          final ServerResponse response) {
+        Single<DataChunk> result = repository.getWorld(randomWorldNumber())
+                .map(World::toJson)
+                .map(jsonObject -> getChunk(jsonObject));
+
+        send(response, result);
+    }
+
+    private void queries(final ServerRequest request,
+                           final ServerResponse response) {
+        Flowable<JsonObject>[] worlds = new Flowable[parseQueryCount(request.queryParams())];
+        Arrays.setAll(worlds, i -> repository.getWorld(randomWorldNumber()).map(World::toJson).toFlowable());
+
+        Single<DataChunk> result = marshall(worlds);
+
+        send(response, result);
+    }
+
+    private void updates(final ServerRequest request,
+                         final ServerResponse response) {
+        Flowable<JsonObject>[] worlds = new Flowable[parseQueryCount(request.queryParams())];
+
+        Arrays.setAll(worlds, i -> {
+            return repository.getWorld(randomWorldNumber()).flatMapPublisher(world -> {
+                world.randomNumber = randomWorldNumber();
+                return repository.updateWorld(world).map(World::toJson).toFlowable();
+            });
+        });
+
+        Single<DataChunk> result = marshall(worlds);
+
+        send(response, result);
+    }
+
+    private Single<DataChunk> marshall(Flowable<JsonObject>[] worlds) {
+        return Flowable.mergeArray(worlds)
+                .collect(() -> new ArrayList<JsonObject>(), (worlds1, world) -> worlds1.add(world))
+                .map(jsonObjects -> buildArray(jsonObjects))
+                .map(jsonValues -> getChunk(jsonValues))
+                .doOnError(Throwable::printStackTrace);
+    }
+
+    private JsonArray buildArray(ArrayList<JsonObject> jsonObjects) {
+        return jsonObjects.stream().reduce(
+                Json.createArrayBuilder(),
+                (jsonArrayBuilder, jsonObject) -> jsonArrayBuilder.add(jsonObject),
+                (jsonArrayBuilder, jsonArrayBuilder2) -> jsonArrayBuilder.addAll(jsonArrayBuilder2))
+                .build();
+    }
+
+    private void send(final ServerResponse response, Single<DataChunk> result) {
+        response.headers().contentType(MediaType.APPLICATION_JSON);
+        response.send(ReactiveStreamsAdapter.publisherToFlow(result.toFlowable()));
+    }
+
+    private DataChunk getChunk(JsonStructure jsonStructure) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        JsonWriter writer = jsonWriterFactory.createWriter(baos);
+        writer.write(jsonStructure);
+        writer.close();
+
+        return DataChunk.create(baos.toByteArray());
+    }
+
+    private int randomWorldNumber() {
+        return 1 + ThreadLocalRandom.current().nextInt(10000);
+    }
+
+    private int parseQueryCount(Parameters parameters) {
+        Optional<String> textValue = parameters.first("queries");
+
+        if (!textValue.isPresent()) {
+            return 1;
+        }
+        int parsedValue;
+        try {
+            parsedValue = Integer.parseInt(textValue.get());
+        } catch (NumberFormatException e) {
+            return 1;
+        }
+        return Math.min(500, Math.max(1, parsedValue));
+    }
+}

+ 66 - 0
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/services/FortuneService.java

@@ -0,0 +1,66 @@
+package io.helidon.benchmark.services;
+
+import com.github.mustachejava.Mustache;
+import io.helidon.benchmark.models.DbRepository;
+import io.helidon.benchmark.models.Fortune;
+import io.helidon.common.http.DataChunk;
+import io.helidon.common.http.MediaType;
+import io.helidon.common.reactive.ReactiveStreamsAdapter;
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.ServerRequest;
+import io.helidon.webserver.ServerResponse;
+import io.helidon.webserver.Service;
+import io.reactivex.Single;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.List;
+
+import static java.util.Comparator.comparing;
+
+public class FortuneService implements Service {
+
+    private final DbRepository repository;
+    private final Mustache template;
+
+    public FortuneService(DbRepository repository, Mustache template) {
+        this.repository = repository;
+        this.template = template;
+    }
+
+    @Override
+    public void update(Routing.Rules rules) {
+        rules.get("/fortunes", this::fortunes);
+    }
+
+    private void fortunes(ServerRequest request,
+                          ServerResponse response) {
+        Single<DataChunk> result = repository.getFortunes()
+                .map(fortunes -> {
+                    fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+                    fortunes.sort(comparing(fortune -> fortune.message));
+                    return fortunes;
+                })
+            .map(fortunes -> this.getChunk(fortunes));
+
+        send(response, result);
+    }
+
+    private DataChunk getChunk(List<Fortune> fortunes) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        OutputStreamWriter writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8.name());
+
+        template.execute(writer, Collections.singletonMap("fortunes", fortunes));
+        writer.flush();
+
+        return DataChunk.create(baos.toByteArray());
+    }
+
+    private void send(final ServerResponse response, Single<DataChunk> result) {
+        response.headers().contentType(MediaType.TEXT_HTML.withCharset(StandardCharsets.UTF_8.name()));
+        response.send(ReactiveStreamsAdapter.publisherToFlow(result.toFlowable()));
+    }
+}

+ 18 - 0
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/services/JsonService.java

@@ -0,0 +1,18 @@
+package io.helidon.benchmark.services;
+
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.Service;
+import io.helidon.webserver.json.JsonSupport;
+
+import javax.json.Json;
+import java.util.Collections;
+
+public class JsonService implements Service {
+
+    @Override
+    public void update(Routing.Rules rules) {
+        rules.register("/json", JsonSupport.get());
+        rules.get("/json",
+                (req, res) -> res.send(Json.createObjectBuilder(Collections.singletonMap("message", "Hello, World!")).build()));
+    }
+}

+ 13 - 0
frameworks/Java/helidon/src/main/java/io/helidon/benchmark/services/PlainTextService.java

@@ -0,0 +1,13 @@
+package io.helidon.benchmark.services;
+
+import io.helidon.webserver.Routing;
+import io.helidon.webserver.Service;
+
+public class PlainTextService implements Service {
+
+    @Override
+    public void update(Routing.Rules rules) {
+        rules.get("/plaintext",
+                (req, res) -> res.send("Hello, World!"));
+    }
+}

+ 8 - 0
frameworks/Java/helidon/src/main/resources/application.yaml

@@ -0,0 +1,8 @@
+server:
+  port: 8080
+  host: 0.0.0.0
+
+dataSource:
+  jdbcUrl: "jdbc:postgresql://tfb-database:5432/hello_world"
+  username: benchmarkdbuser
+  password: benchmarkdbpass

+ 20 - 0
frameworks/Java/helidon/src/main/resources/fortunes.mustache

@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Fortunes</title>
+</head>
+<body>
+<table>
+    <tr>
+        <th>id</th>
+        <th>message</th>
+    </tr>
+    {{#fortunes}}
+        <tr>
+            <td>{{id}}</td>
+            <td>{{message}}</td>
+        </tr>
+    {{/fortunes}}
+</table>
+</body>
+</html>

+ 37 - 0
frameworks/Java/helidon/src/main/resources/logging.properties

@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# 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
+#
+#     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.
+#
+
+# Example Logging Configuration File
+# For more information see $JAVA_HOME/jre/lib/logging.properties
+
+# Send messages to the console
+handlers=java.util.logging.ConsoleHandler
+
+# Global default logging level. Can be overriden by specific handlers and loggers
+.level=INFO
+
+# Helidon Web Server has a custom log formatter that extends SimpleFormatter.
+# It replaces "!thread!" with the current thread name
+java.util.logging.ConsoleHandler.level=INFO
+java.util.logging.ConsoleHandler.formatter=io.helidon.webserver.netty.WebServerLogFormatter
+java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
+
+#Component specific log levels
+#io.helidon.webserver.level=INFO
+#io.helidon.config.level=INFO
+#io.helidon.security.level=INFO
+#io.helidon.common.level=INFO
+#io.netty.level=INFO