Browse Source

Submission for the java ratpack framework (#3956)

* Benchmark the ratpack framework

* Remove the gradle wrapper, which is not required to build and run via docker, but was used by intellij instead of having a system wide install of gradle.
jringuette 7 years ago
parent
commit
950ed7ae52

+ 1 - 0
.travis.yml

@@ -62,6 +62,7 @@ env:
      - "TESTDIR=Java/play2-java"
      - "TESTDIR=Java/play2-java"
      - "TESTDIR=Java/proteus"
      - "TESTDIR=Java/proteus"
      - "TESTDIR=Java/rapidoid"
      - "TESTDIR=Java/rapidoid"
+     - "TESTDIR=Java/ratpack"
      - "TESTDIR=Java/redkale"
      - "TESTDIR=Java/redkale"
      - "TESTDIR=Java/restexpress"
      - "TESTDIR=Java/restexpress"
      - "TESTDIR=Java/revenj-jvm"
      - "TESTDIR=Java/revenj-jvm"

+ 3 - 0
frameworks/Java/ratpack/.gitignore

@@ -0,0 +1,3 @@
+/.gradle
+/build
+/out

+ 64 - 0
frameworks/Java/ratpack/README.md

@@ -0,0 +1,64 @@
+# Ratpack Benchmarking Test
+
+This is the Ratpack portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+Ratpack's [hikari module](https://github.com/ratpack/ratpack/tree/master/ratpack-hikari) is used to managed 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)
+
+Ratpack's [handlebars module](https://github.com/ratpack/ratpack/tree/master/ratpack-handlebars) is used to render the fortune template.
+
+All accesses to the database are done through plain JDBC using an unbounded thread pool to prevent blocking the main event loop.
+
+### Plaintext Test
+
+* [Plaintext test source](src/main/java/handlers/PlainTextHandler.java)
+
+### JSON Serialization Test
+
+* [JSON test source](src/main/java/handlers/JsonHandler.java)
+
+### Database Query Test
+
+* [Database Query test source](src/main/java/handlers/DbHandler.java)
+
+### Database Queries Test
+
+* [Database Queries test source](src/main/java/handlers/QueryHandler.java)
+
+### Database Update Test
+
+* [Database Update test source](src/main/java/handlers/UpdateHandler.java)
+
+### Template rendering Test
+
+* [Template rendering test source](src/main/java/handlers/FortuneHandler.java)
+
+## Versions
+
+* [Java OpenJDK 1.8](http://openjdk.java.net/)
+* [Ratpack 1.5.4](http://ratpack.io/)
+
+## Test URLs
+
+### Plaintext Test
+
+    http://localhost:5050/plaintext
+
+### JSON Encoding Test
+
+    http://localhost:5050/json
+
+### Database Query Test
+
+    http://localhost:5050/db
+
+### Database Queries Test
+
+    http://localhost:5050/queries?queries=5
+
+### Database Update Test
+
+    http://localhost:5050/updates?queries=5
+
+### Template rendering Test
+
+    http://localhost:5050/fortunes

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

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

+ 32 - 0
frameworks/Java/ratpack/build.gradle

@@ -0,0 +1,32 @@
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath "io.ratpack:ratpack-gradle:1.5.4"
+        classpath "com.github.jengelman.gradle.plugins:shadow:1.2.4"
+    }
+}
+
+apply plugin: "io.ratpack.ratpack-java"
+apply plugin: "com.github.johnrengelman.shadow"
+apply plugin: "idea"
+apply plugin: "eclipse"
+
+repositories {
+    jcenter()
+}
+
+dependencies {
+    compile 'io.ratpack:ratpack-guice:1.5.4'
+    compile 'io.ratpack:ratpack-hikari:1.5.4'
+    compile 'io.ratpack:ratpack-handlebars:1.5.4'
+
+    // Default SLF4J binding.  Note that this is a blocking implementation.
+    // See here for a non blocking appender http://logging.apache.org/log4j/2.x/manual/async.html
+    runtime 'org.slf4j:slf4j-simple:1.7.25'
+    runtime 'mysql:mysql-connector-java:5.1.46'
+
+}
+
+mainClassName = "Main"

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

@@ -0,0 +1,11 @@
+FROM gradle:4.7.0-jdk8 as gradle
+USER root
+WORKDIR /ratpack
+COPY build.gradle build.gradle
+COPY src src
+RUN gradle shadowJar
+
+FROM openjdk:8-jre-slim
+WORKDIR /ratpack
+COPY --from=gradle /ratpack/build/libs/ratpack-all.jar app.jar
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-jar", "app.jar"]

+ 37 - 0
frameworks/Java/ratpack/src/main/java/Main.java

@@ -0,0 +1,37 @@
+import handlers.DbHandler;
+import handlers.FortuneHandler;
+import handlers.HeaderHandler;
+import handlers.JsonHandler;
+import handlers.PlainTextHandler;
+import handlers.QueryHandler;
+import handlers.UpdateHandler;
+import ratpack.guice.Guice;
+import ratpack.handlebars.HandlebarsModule;
+import ratpack.hikari.HikariModule;
+import ratpack.server.RatpackServer;
+
+public class Main {
+    public static void main(String... args) throws Exception {
+        RatpackServer.start(server -> server
+                .serverConfig(c -> c.findBaseDir("handlebars"))
+                .registry(Guice.registry(b -> b
+                        .module(HikariModule.class, hikariConfig -> {
+                            hikariConfig.setJdbcUrl("jdbc:mysql://tfb-database:3306/hello_world?elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&cacheServerConfiguration=true&zeroDateTimeBehavior=convertToNull&traceProtocol=false&maintainTimeStats=false");
+                            hikariConfig.setUsername("benchmarkdbuser");
+                            hikariConfig.setPassword("benchmarkdbpass");
+                            hikariConfig.setMaximumPoolSize(Runtime.getRuntime().availableProcessors()*2);
+                        })
+                        .module(HandlebarsModule.class)
+                ))
+                .handlers(chain -> chain
+                        .all(new HeaderHandler())
+                        .get("plaintext", new PlainTextHandler())
+                        .get("json", new JsonHandler())
+                        .get("db", new DbHandler())
+                        .get("queries", new QueryHandler())
+                        .get("fortunes", new FortuneHandler())
+                        .get("updates", new UpdateHandler())
+                )
+        );
+    }
+}

+ 46 - 0
frameworks/Java/ratpack/src/main/java/handlers/BaseWorldHandler.java

@@ -0,0 +1,46 @@
+package handlers;
+
+import models.World;
+import ratpack.handling.Context;
+import ratpack.handling.InjectionHandler;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.concurrent.ThreadLocalRandom;
+
+public abstract class BaseWorldHandler extends InjectionHandler {
+    protected int randomWorldNumber() {
+        return 1 + ThreadLocalRandom.current().nextInt(10000);
+    }
+
+    protected int parseQueryCount(Context ctx) {
+        String textValue = ctx.getRequest().getQueryParams().get("queries");
+
+        if (textValue == null) {
+            return 1;
+        }
+        int parsedValue;
+        try {
+            parsedValue = Integer.parseInt(textValue);
+        } catch (NumberFormatException e) {
+            return 1;
+        }
+        return Math.min(500, Math.max(1, parsedValue));
+    }
+
+    protected World getWorld(Connection connection) {
+        try {
+            PreparedStatement statement = connection.prepareStatement("SELECT id, randomnumber FROM world WHERE id = ?");
+            statement.setInt(1, randomWorldNumber());
+            ResultSet rs = statement.executeQuery();
+            rs.next();
+            World world = new World(rs.getInt(1), rs.getInt(2));
+            statement.close();
+            return world;
+        } catch (SQLException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}

+ 19 - 0
frameworks/Java/ratpack/src/main/java/handlers/DbHandler.java

@@ -0,0 +1,19 @@
+package handlers;
+
+import ratpack.exec.Blocking;
+import ratpack.handling.Context;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+
+import static ratpack.jackson.Jackson.json;
+
+public class DbHandler extends BaseWorldHandler {
+    public void handle(Context ctx, DataSource datasource) {
+        Blocking.get(() -> {
+            try (Connection connection = datasource.getConnection()) {
+                return getWorld(connection);
+            }
+        }).then(result -> ctx.render(json(result)));
+    }
+}

+ 47 - 0
frameworks/Java/ratpack/src/main/java/handlers/FortuneHandler.java

@@ -0,0 +1,47 @@
+package handlers;
+
+import models.Fortune;
+import ratpack.exec.Blocking;
+import ratpack.handlebars.Template;
+import ratpack.handling.Context;
+import ratpack.handling.InjectionHandler;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+public class FortuneHandler extends InjectionHandler {
+    public void handle(Context ctx, DataSource datasource) {
+        Blocking.get(() -> {
+            try (Connection connection = datasource.getConnection()) {
+                return getFortunes(connection);
+            }
+        }).then(fortunes -> {
+            fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+            fortunes.sort(Comparator.comparing(fortune -> fortune.message));
+            ctx.render(Template.handlebarsTemplate("fortunes", Collections.singletonMap("fortunes", fortunes), "text/html; charset=UTF-8"));
+        });
+    }
+
+    private List<Fortune> getFortunes(Connection connection) {
+        try {
+            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;
+        } catch (SQLException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}

+ 47 - 0
frameworks/Java/ratpack/src/main/java/handlers/HeaderHandler.java

@@ -0,0 +1,47 @@
+package handlers;
+
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.util.AsciiString;
+import ratpack.handling.Context;
+import ratpack.handling.Handler;
+import ratpack.http.MutableHeaders;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class HeaderHandler implements Handler {
+    private static final CharSequence SERVER = AsciiString.cached("server");
+    private static final CharSequence RATPACK = AsciiString.cached("ratpack");
+
+    private final DateHelper dateHelper = new DateHelper();
+
+    @Override
+    public void handle(Context ctx) {
+        MutableHeaders headers = ctx.getResponse().getHeaders();
+        headers.set(HttpHeaderNames.DATE, dateHelper.getDate());
+        headers.set(SERVER, RATPACK);
+        ctx.next();
+    }
+
+    static class DateHelper extends TimerTask {
+        private Timer timer = new Timer();
+        private String date = generateDate();
+
+        public DateHelper() {
+            timer.schedule(this, 0, 1000);
+        }
+
+        @Override
+        public void run() {
+            date = generateDate();
+        }
+
+        private String generateDate() {
+            return java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME.format(java.time.ZonedDateTime.now());
+        }
+
+        public String getDate() {
+            return date;
+        }
+    }
+}

+ 18 - 0
frameworks/Java/ratpack/src/main/java/handlers/JsonHandler.java

@@ -0,0 +1,18 @@
+package handlers;
+
+import ratpack.handling.Context;
+import ratpack.handling.Handler;
+
+import java.util.Collections;
+
+import static ratpack.jackson.Jackson.json;
+
+public class JsonHandler implements Handler {
+    private static final String MESSAGE = "message";
+    private static final String HELLO = "Hello, World!";
+
+    @Override
+    public void handle(Context ctx) throws Exception {
+        ctx.render(json(Collections.singletonMap(MESSAGE, HELLO)));
+    }
+}

+ 13 - 0
frameworks/Java/ratpack/src/main/java/handlers/PlainTextHandler.java

@@ -0,0 +1,13 @@
+package handlers;
+
+import ratpack.handling.Context;
+import ratpack.handling.Handler;
+
+public class PlainTextHandler implements Handler {
+    private static final String MESSAGE = "Hello, World!";
+
+    @Override
+    public void handle(Context ctx) {
+        ctx.getResponse().send(MESSAGE);
+    }
+}

+ 25 - 0
frameworks/Java/ratpack/src/main/java/handlers/QueryHandler.java

@@ -0,0 +1,25 @@
+package handlers;
+
+import models.World;
+import ratpack.exec.Blocking;
+import ratpack.handling.Context;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.util.Arrays;
+
+import static ratpack.jackson.Jackson.json;
+
+public class QueryHandler extends BaseWorldHandler {
+    public void handle(Context ctx, DataSource datasource) {
+        int queries = parseQueryCount(ctx);
+
+        Blocking.get(() -> {
+            try (Connection connection = datasource.getConnection()) {
+                World[] worlds = new World[queries];
+                Arrays.setAll(worlds, i -> getWorld(connection));
+                return worlds;
+            }
+        }).then(result -> ctx.render(json(result)));
+    }
+}

+ 43 - 0
frameworks/Java/ratpack/src/main/java/handlers/UpdateHandler.java

@@ -0,0 +1,43 @@
+package handlers;
+
+import models.World;
+import ratpack.exec.Blocking;
+import ratpack.handling.Context;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.Arrays;
+
+import static ratpack.jackson.Jackson.json;
+
+public class UpdateHandler extends BaseWorldHandler {
+    public void handle(Context ctx, DataSource datasource) throws Exception {
+        int queries = parseQueryCount(ctx);
+
+        Blocking.get(() -> {
+            try (Connection connection = datasource.getConnection()) {
+                World[] worlds = new World[queries];
+                Arrays.setAll(worlds, i -> getWorld(connection));
+                update(worlds, connection);
+                return worlds;
+            }
+        }).then(result -> ctx.render(json(result)));
+    }
+
+    private void update(World[] worlds, Connection connection) {
+        try {
+            for (World world : worlds) {
+                PreparedStatement statement = connection.prepareStatement("UPDATE world SET randomnumber = ? WHERE id = ?");
+                world.randomNumber = randomWorldNumber();
+                statement.setInt(1, world.randomNumber);
+                statement.setInt(2, world.id);
+                statement.executeUpdate();
+                statement.close();
+            }
+        } catch (SQLException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+}

+ 19 - 0
frameworks/Java/ratpack/src/main/java/models/Fortune.java

@@ -0,0 +1,19 @@
+package 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;
+    }
+}

+ 11 - 0
frameworks/Java/ratpack/src/main/java/models/World.java

@@ -0,0 +1,11 @@
+package models;
+
+public final class World {
+    public int id;
+    public int randomNumber;
+
+    public World(int id, int randomNumber) {
+        this.id = id;
+        this.randomNumber = randomNumber;
+    }
+}

+ 20 - 0
frameworks/Java/ratpack/src/main/resources/handlebars/fortunes.hbs

@@ -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>