Browse Source

jooby: pgclient tests + upgrade (#4995)

Edgar Espina 6 years ago
parent
commit
f74a929cf4

+ 21 - 0
frameworks/Java/jooby2/benchmark_config.json

@@ -69,6 +69,27 @@
       "notes": "Jooby 2 using Jetty",
       "display_name": "jooby2-jetty",
       "versus": "jetty"
+    },
+    "pgclient": {
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Fullstack",
+      "framework": "jooby 2.x",
+      "language": "Java",
+      "flavor": "None",
+      "platform": "Undertow",
+      "database": "Postgres",
+      "database_os": "Linux",
+      "orm": "Raw",
+      "webserver": "None",
+      "os": "Linux",
+      "notes": "Jooby 2 with Reactive PG client",
+      "display_name": "jooby2-pgclient",
+      "versus": "undertow"
     }
   }]
 }

+ 4 - 1
frameworks/Java/jooby2/conf/application.conf

@@ -1,3 +1,6 @@
-db.url = "jdbc:postgresql://tfb-database:5432/hello_world"
+db.databaseName = hello_world
+db.serverName = tfb-database
+db.portNumber = 5432
 db.user = benchmarkdbuser
 db.password = benchmarkdbpass
+db.dataSourceClassName = org.postgresql.ds.PGSimpleDataSource

+ 12 - 0
frameworks/Java/jooby2/jooby2-pgclient.dockerfile

@@ -0,0 +1,12 @@
+FROM maven:3.6.1-jdk-11-slim as maven
+WORKDIR /jooby2
+COPY pom.xml pom.xml
+COPY src src
+COPY public public
+RUN mvn package -q -P pgclient
+
+FROM openjdk:11.0.3-jdk-slim
+WORKDIR /jooby2
+COPY --from=maven /jooby2/target/jooby-2x.jar app.jar
+COPY conf conf
+CMD ["java", "-server", "-Xms512m", "-Xmx2g", "-cp", "app.jar", "com.techempower.ReactivePg"]

+ 20 - 12
frameworks/Java/jooby2/pom.xml

@@ -7,7 +7,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>jooby-project</artifactId>
-    <version>2.0.3</version>
+    <version>2.0.5</version>
   </parent>
 
   <artifactId>jooby2</artifactId>
@@ -17,11 +17,13 @@
   <name>jooby 2.x</name>
 
   <properties>
-    <jooby.version>2.0.3</jooby.version>
+    <jooby.version>2.0.5</jooby.version>
+    <!-- downgrade netty and make pg-client happy -->
+    <netty.version>4.1.34.Final</netty.version>
     <postgresql.version>42.2.5</postgresql.version>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    <maven.compiler.source>8</maven.compiler.source>
-    <maven.compiler.target>8</maven.compiler.target>
+    <maven.compiler.source>1.8</maven.compiler.source>
+    <maven.compiler.target>1.8</maven.compiler.target>
 
     <!-- Startup class -->
     <application.class>com.techempower.App</application.class>
@@ -65,11 +67,10 @@
       <artifactId>logback-classic</artifactId>
     </dependency>
 
-    <!-- ASM library -->
     <dependency>
-      <groupId>org.ow2.asm</groupId>
-      <artifactId>asm</artifactId>
-      <version>7.1</version>
+      <groupId>io.reactiverse</groupId>
+      <artifactId>reactive-pg-client</artifactId>
+      <version>0.11.4</version>
     </dependency>
   </dependencies>
 
@@ -99,9 +100,6 @@
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
-         <configuration>
-           <debug>false</debug>
-         </configuration>
        </plugin>
       <!-- Build fat jar -->
       <plugin>
@@ -145,6 +143,16 @@
         </dependency>
       </dependencies>
     </profile>
-  </profiles>
 
+    <profile>
+      <id>pgclient</id>
+      <dependencies>
+        <dependency>
+          <groupId>io.jooby</groupId>
+          <artifactId>jooby-utow</artifactId>
+          <version>${jooby.version}</version>
+        </dependency>
+      </dependencies>
+    </profile>
+  </profiles>
 </project>

+ 7 - 40
frameworks/Java/jooby2/src/main/java/com/techempower/App.java

@@ -1,9 +1,7 @@
 package com.techempower;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
-import io.jooby.Context;
 import io.jooby.Jooby;
-import io.jooby.ServerOptions;
 import io.jooby.hikari.HikariModule;
 import io.jooby.json.JacksonModule;
 import io.jooby.rocker.RockerModule;
@@ -15,12 +13,11 @@ import java.sql.ResultSet;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Random;
 import java.util.StringJoiner;
-import java.util.concurrent.ThreadLocalRandom;
 
 import javax.sql.DataSource;
 
+import static com.techempower.Util.randomWorld;
 import static io.jooby.ExecutionMode.EVENT_LOOP;
 import static io.jooby.MediaType.JSON;
 
@@ -28,8 +25,6 @@ public class App extends Jooby {
 
   private static final String SELECT_WORLD = "select * from world where id=?";
 
-  private static final int DB_ROWS = 10000;
-
   private static final String MESSAGE = "Hello, World!";
 
   private static final byte[] MESSAGE_BYTES = MESSAGE.getBytes(StandardCharsets.UTF_8);
@@ -39,12 +34,6 @@ public class App extends Jooby {
   }
 
   {
-    /** Server options (netty only): */
-    setServerOptions(new ServerOptions()
-        .setSingleLoop(true)
-        .setDirectBuffers(true)
-    );
-
     /** JSON: */
     install(new JacksonModule());
     ObjectMapper mapper = require(ObjectMapper.class);
@@ -70,12 +59,10 @@ public class App extends Jooby {
 
       /** Single query: */
       get("/db", ctx -> {
-        Random rnd = ThreadLocalRandom.current();
         World result;
         try (Connection conn = ds.getConnection()) {
-          int id = nextRandom(rnd);
           try (final PreparedStatement statement = conn.prepareStatement(SELECT_WORLD)) {
-            statement.setInt(1, id);
+            statement.setInt(1, randomWorld());
             try (ResultSet rs = statement.executeQuery()) {
               rs.next();
               result = new World(rs.getInt("id"), rs.getInt("randomNumber"));
@@ -89,13 +76,11 @@ public class App extends Jooby {
 
       /** Multiple queries: */
       get("/queries", ctx -> {
-        World[] result = new World[queries(ctx)];
-        Random rnd = ThreadLocalRandom.current();
+        World[] result = new World[Util.queries(ctx)];
         try (Connection conn = ds.getConnection()) {
           for (int i = 0; i < result.length; i++) {
-            int id = nextRandom(rnd);
             try (final PreparedStatement statement = conn.prepareStatement(SELECT_WORLD)) {
-              statement.setInt(1, id);
+              statement.setInt(1, randomWorld());
               try (ResultSet rs = statement.executeQuery()) {
                 rs.next();
                 result[i] = new World(rs.getInt("id"), rs.getInt("randomNumber"));
@@ -110,9 +95,7 @@ public class App extends Jooby {
 
       /** Updates: */
       get("/updates", ctx -> {
-        World[] result = new World[queries(ctx)];
-        Random rnd = ThreadLocalRandom.current();
-
+        World[] result = new World[Util.queries(ctx)];
         StringJoiner updateSql = new StringJoiner(
             ", ",
             "UPDATE world SET randomNumber = temp.randomNumber FROM (VALUES ",
@@ -121,7 +104,7 @@ public class App extends Jooby {
         try (Connection connection = ds.getConnection()) {
           try (PreparedStatement statement = connection.prepareStatement(SELECT_WORLD)) {
             for (int i = 0; i < result.length; i++) {
-              statement.setInt(1, nextRandom(rnd));
+              statement.setInt(1, randomWorld());
               try (ResultSet rs = statement.executeQuery()) {
                 rs.next();
                 result[i] = new World(rs.getInt("id"), rs.getInt("randomNumber"));
@@ -134,7 +117,7 @@ public class App extends Jooby {
           try (PreparedStatement statement = connection.prepareStatement(updateSql.toString())) {
             int i = 0;
             for (World world : result) {
-              world.randomNumber = nextRandom(rnd);
+              world.randomNumber = randomWorld();
               statement.setInt(++i, world.id);
               statement.setInt(++i, world.randomNumber);
             }
@@ -169,20 +152,4 @@ public class App extends Jooby {
   public static void main(final String[] args) {
     runApp(args, EVENT_LOOP, App::new);
   }
-
-  private final int nextRandom(Random rnd) {
-    return rnd.nextInt(DB_ROWS) + 1;
-  }
-
-  private int queries(Context ctx) {
-    String value = ctx.query("queries").value("");
-    if (value.length() == 0) {
-      return 1;
-    }
-    try {
-      return Math.min(500, Math.max(1, Integer.parseInt(value)));
-    } catch (NumberFormatException x) {
-      return 1;
-    }
-  }
 }

+ 178 - 0
frameworks/Java/jooby2/src/main/java/com/techempower/ReactivePg.java

@@ -0,0 +1,178 @@
+package com.techempower;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.typesafe.config.Config;
+import io.jooby.Jooby;
+import io.jooby.json.JacksonModule;
+import io.jooby.rocker.RockerModule;
+import io.reactiverse.pgclient.PgClient;
+import io.reactiverse.pgclient.PgIterator;
+import io.reactiverse.pgclient.PgPoolOptions;
+import io.reactiverse.pgclient.Row;
+import io.reactiverse.pgclient.Tuple;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static com.techempower.Util.randomWorld;
+import static io.jooby.ExecutionMode.EVENT_LOOP;
+import static io.jooby.MediaType.JSON;
+
+public class ReactivePg extends Jooby {
+
+  private static final String UPDATE_WORLD = "UPDATE world SET randomnumber=$1 WHERE id=$2";
+  private static final String SELECT_WORLD = "SELECT id, randomnumber from WORLD where id=$1";
+  private static final String SELECT_FORTUNE = "SELECT id, message from FORTUNE";
+
+  {
+    /** PG client: */
+    PgClient client = database(getConfig().getConfig("db"));
+
+    /** Template engine: */
+    install(new RockerModule());
+
+    /** JSON: */
+    install(new JacksonModule());
+    ObjectMapper mapper = require(ObjectMapper.class);
+
+    /** Single query: */
+    get("/db", ctx -> {
+      client.preparedQuery(SELECT_WORLD, Tuple.of(randomWorld()), rsp -> {
+        try {
+          if (rsp.succeeded()) {
+            PgIterator rs = rsp.result().iterator();
+            Row row = rs.next();
+            ctx.setResponseType(JSON)
+                .send(mapper.writeValueAsBytes(new World(row.getInteger(0), row.getInteger(1))));
+          } else {
+            ctx.sendError(rsp.cause());
+          }
+        } catch (IOException x) {
+          ctx.sendError(x);
+        }
+      });
+      return ctx;
+    });
+
+    /** Multiple queries: */
+    get("/queries", ctx -> {
+      int queries = Util.queries(ctx);
+      AtomicInteger counter = new AtomicInteger();
+      AtomicBoolean failed = new AtomicBoolean(false);
+      World[] result = new World[queries];
+      for (int i = 0; i < result.length; i++) {
+        client.preparedQuery(SELECT_WORLD, Tuple.of(randomWorld()), rsp -> {
+          if (rsp.succeeded()) {
+            PgIterator rs = rsp.result().iterator();
+            Row row = rs.next();
+            result[counter.get()] = new World(row.getInteger(0), row.getInteger(1));
+          } else {
+            if (failed.compareAndSet(false, true)) {
+              ctx.sendError(rsp.cause());
+            }
+          }
+          // ready?
+          if (counter.incrementAndGet() == queries && !failed.get()) {
+            try {
+              ctx.setResponseType(JSON)
+                  .send(mapper.writeValueAsBytes(result));
+            } catch (IOException x) {
+              ctx.sendError(x);
+            }
+          }
+        });
+      }
+      return ctx;
+    });
+
+    /** Update queries: */
+    get("/updates", ctx -> {
+      int queries = Util.queries(ctx);
+      World[] result = new World[queries];
+      AtomicInteger counter = new AtomicInteger(0);
+      AtomicBoolean failed = new AtomicBoolean(false);
+
+      for (int i = 0; i < queries; i++) {
+        client.preparedQuery(SELECT_WORLD, Tuple.of(randomWorld()), rsp -> {
+          if (rsp.succeeded()) {
+            PgIterator rs = rsp.result().iterator();
+            Tuple row = rs.next();
+            World world = new World(row.getInteger(0), row.getInteger(1));
+            world.randomNumber = randomWorld();
+            result[counter.get()] = world;
+          } else {
+            if (failed.compareAndSet(false, true)) {
+              ctx.sendError(rsp.cause());
+            }
+          }
+
+          if (counter.incrementAndGet() == queries && !failed.get()) {
+            List<Tuple> batch = new ArrayList<>(queries);
+            for (World world : result) {
+              batch.add(Tuple.of(world.randomNumber, world.id));
+            }
+
+            client.preparedBatch(UPDATE_WORLD, batch, ar -> {
+              if (ar.failed()) {
+                ctx.sendError(ar.cause());
+              } else {
+                try {
+                  ctx.setResponseType(JSON)
+                      .send(mapper.writeValueAsBytes(result));
+                } catch (IOException x) {
+                  ctx.sendError(x);
+                }
+              }
+            });
+          }
+        });
+      }
+      return ctx;
+    });
+
+    /** Fortunes: */
+    get("/fortunes", ctx -> {
+      client.preparedQuery(SELECT_FORTUNE, rsp -> {
+        if (rsp.succeeded()) {
+          PgIterator rs = rsp.result().iterator();
+
+          List<Fortune> fortunes = new ArrayList<>();
+
+          while (rs.hasNext()) {
+            Row row = rs.next();
+            fortunes.add(new Fortune(row.getInteger(0), row.getString(1)));
+          }
+
+          fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+          Collections.sort(fortunes);
+
+          /** render view: */
+          views.fortunes template = views.fortunes.template(fortunes);
+          ctx.render(template);
+        } else {
+          ctx.sendError(rsp.cause());
+        }
+      });
+      return ctx;
+    });
+  }
+
+  private PgClient database(Config config) {
+    PgPoolOptions options = new PgPoolOptions();
+    options.setDatabase(config.getString("databaseName"));
+    options.setHost(config.getString("serverName"));
+    options.setPort(config.getInt("portNumber"));
+    options.setUser(config.getString("user"));
+    options.setPassword(config.getString("password"));
+    options.setCachePreparedStatements(true);
+    return PgClient.pool(new PgPoolOptions(options).setMaxSize(4));
+  }
+
+  public static void main(String[] args) {
+    runApp(args, EVENT_LOOP, ReactivePg::new);
+  }
+}

+ 24 - 0
frameworks/Java/jooby2/src/main/java/com/techempower/Util.java

@@ -0,0 +1,24 @@
+package com.techempower;
+
+import io.jooby.Context;
+import io.jooby.Value;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+public class Util {
+
+  public static int queries(Context ctx) {
+    try {
+      Value queries = ctx.query("queries");
+      return queries.isMissing()
+          ? 1
+          : Math.min(500, Math.max(1, Integer.parseInt(queries.value())));
+    } catch (NumberFormatException x) {
+      return 1;
+    }
+  }
+
+  public static int randomWorld() {
+    return 1 + ThreadLocalRandom.current().nextInt(10000);
+  }
+}

+ 2 - 3
frameworks/Kotlin/kooby/pom.xml

@@ -8,7 +8,7 @@
   <parent>
     <groupId>io.jooby</groupId>
     <artifactId>jooby-project</artifactId>
-    <version>2.0.3</version>
+    <version>2.0.5</version>
   </parent>
 
   <artifactId>kooby</artifactId>
@@ -18,7 +18,7 @@
   <name>kooby: jooby + kotlin</name>
 
   <properties>
-    <jooby.version>2.0.3</jooby.version>
+    <jooby.version>2.0.5</jooby.version>
     <postgresql.version>42.2.5</postgresql.version>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <maven.compiler.source>1.8</maven.compiler.source>
@@ -171,7 +171,6 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-shade-plugin</artifactId>
       </plugin>
-
     </plugins>
   </build>
 </project>

+ 0 - 6
frameworks/Kotlin/kooby/src/main/kotlin/kooby/App.kt

@@ -32,12 +32,6 @@ data class Fortune(val id: Int, var message: String)
 fun main(args: Array<String>) {
   runApp(args, EVENT_LOOP) {
 
-    /** Server options (netty only): */
-    serverOptions {
-      singleLoop = true
-      directBuffers = true
-    }
-
     /** JSON: */
     install(JacksonModule())
     val mapper = require(ObjectMapper::class)