Browse Source

Add single-query DB test type to Java Microhttp framework. Update dependencies. (#7469)

* Add db test type (single-database-query) to Java Microhttp framework benchmark suite.

* Remove exception print stack trace.

* Update Microhttp dependency to 0.8.

Co-authored-by: Elliot Barlas <[email protected]>
Elliot Barlas 3 years ago
parent
commit
8627b29641

+ 4 - 2
frameworks/Java/microhttp/README.md

@@ -4,13 +4,15 @@
 
 
 * [JSON](src/main/java/hello/HelloWebServer.java)
 * [JSON](src/main/java/hello/HelloWebServer.java)
 * [PLAINTEXT](src/main/java/hello/HelloWebServer.java)
 * [PLAINTEXT](src/main/java/hello/HelloWebServer.java)
+* [DB](src/main/java/db/DbWebServer.java)
 
 
 ## Important Libraries
 ## Important Libraries
 
 
 The tests were run with:
 The tests were run with:
 * [OpenJDK 17.0.2](http://openjdk.java.net/)
 * [OpenJDK 17.0.2](http://openjdk.java.net/)
-* [Microhttp 0.7](https://github.com/ebarlas/microhttp)
-* [Jackson 2.13.2](https://github.com/FasterXML/jackson)
+* [Microhttp 0.8](https://github.com/ebarlas/microhttp)
+* [Jackson 2.13.3](https://github.com/FasterXML/jackson)
+* [MySQL Connector 8.0.29](https://github.com/mysql/mysql-connector-j)
 
 
 ## Test URLs
 ## Test URLs
 ### JSON
 ### JSON

+ 18 - 0
frameworks/Java/microhttp/benchmark_config.json

@@ -20,6 +20,24 @@
         "display_name": "microhttp",
         "display_name": "microhttp",
         "notes": "",
         "notes": "",
         "versus": "None"
         "versus": "None"
+      },
+      "mysql": {
+        "db_url": "/db",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Platform",
+        "database": "MySQL",
+        "framework": "microhttp",
+        "language": "Java",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "Microhttp",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "microhttp",
+        "notes": "",
+        "versus": "None"
       }
       }
     }
     }
   ]
   ]

+ 13 - 0
frameworks/Java/microhttp/microhttp-mysql.dockerfile

@@ -0,0 +1,13 @@
+FROM maven:3.8.4-openjdk-17-slim as maven
+WORKDIR /microhttp
+COPY pom.xml pom.xml
+COPY src src
+RUN mvn compile assembly:single -q
+
+FROM openjdk:17.0.2
+WORKDIR /microhttp
+COPY --from=maven /microhttp/target/microhttp-example-0.1-jar-with-dependencies.jar app.jar
+
+EXPOSE 8080
+
+CMD ["java", "-cp", "app.jar", "db.DbWebServer"]

+ 8 - 3
frameworks/Java/microhttp/pom.xml

@@ -19,17 +19,22 @@
         <dependency>
         <dependency>
             <groupId>org.microhttp</groupId>
             <groupId>org.microhttp</groupId>
             <artifactId>microhttp</artifactId>
             <artifactId>microhttp</artifactId>
-            <version>0.7</version>
+            <version>0.8</version>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-core</artifactId>
             <artifactId>jackson-core</artifactId>
-            <version>2.13.2</version>
+            <version>2.13.3</version>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
             <artifactId>jackson-databind</artifactId>
-            <version>2.13.2.2</version>
+            <version>2.13.3</version>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+            <version>8.0.29</version>
         </dependency>
         </dependency>
         <dependency>
         <dependency>
             <groupId>org.junit.jupiter</groupId>
             <groupId>org.junit.jupiter</groupId>

+ 58 - 0
frameworks/Java/microhttp/src/main/java/db/DbConnection.java

@@ -0,0 +1,58 @@
+package db;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.Duration;
+import java.util.Properties;
+
+public class DbConnection {
+
+    private static final Properties PROPERTIES = getProperties();
+    private static final String URL = "jdbc:mysql://tfb-database:3306/hello_world";
+    private static final String SELECT = "select id, randomNumber from world where id = ?";
+
+    private static Properties getProperties() {
+        Properties properties = new Properties();
+        properties.put("user", "benchmarkdbuser");
+        properties.put("password", "benchmarkdbpass");
+        properties.put("useSSL", "false");
+        return properties;
+    }
+
+    private final Connection connection;
+    private final PreparedStatement statement;
+
+    private long lastUse;
+
+    public DbConnection() throws SQLException {
+        this.connection = DriverManager.getConnection(URL, PROPERTIES);
+        this.statement = connection.prepareStatement(SELECT);
+        this.lastUse = System.nanoTime();
+    }
+
+    public WorldRow executeQuery(int id) throws SQLException {
+        lastUse = System.nanoTime();
+        statement.setInt(1, id);
+        try (ResultSet rs = statement.executeQuery()) {
+            rs.next();
+            return new WorldRow(rs.getInt(1), rs.getInt(2));
+        }
+    }
+
+    public boolean isIdle(Duration maxIdle) {
+        return System.nanoTime() - lastUse > maxIdle.toNanos();
+    }
+
+    public void close() {
+        try {
+            statement.close();
+        } catch (SQLException ignore) {}
+        try {
+            connection.close();
+        } catch (SQLException ignore) {}
+    }
+
+}

+ 83 - 0
frameworks/Java/microhttp/src/main/java/db/DbConnectionPool.java

@@ -0,0 +1,83 @@
+package db;
+
+import java.sql.SQLException;
+import java.time.Duration;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+public class DbConnectionPool {
+
+    private final ConcurrentLinkedQueue<DbConnection> connections;
+
+    private final int initialSize;
+    private final Duration maxIdle;
+
+    public DbConnectionPool(int initialSize, Duration maxIdle) {
+        this.connections = new ConcurrentLinkedQueue<>();
+        this.initialSize = initialSize;
+        this.maxIdle = maxIdle;
+    }
+
+    public void start() {
+        startDaemon(this::initialize, "db-initialize");
+        startDaemon(this::keepAlive, "db-keep-alive");
+    }
+
+    private static void startDaemon(Runnable task, String name) {
+        Thread t = new Thread(task, name);
+        t.setDaemon(true);
+        t.start();
+    }
+
+    public WorldRow executeQuery(int id) throws SQLException {
+        DbConnection connection = connections.poll();
+        if (connection == null) {
+            connection = new DbConnection();
+        }
+
+        try {
+            WorldRow result = connection.executeQuery(id);
+            connections.add(connection);
+            return result;
+        } catch (SQLException e) {
+            connection.close();
+            throw e;
+        }
+    }
+
+    private void initialize() {
+        for (int i = 0; i < initialSize; i++) {
+            try {
+                connections.add(new DbConnection());
+            } catch (SQLException e) {
+                break;
+            }
+        }
+    }
+
+    private void keepAlive() {
+        while (true) {
+            DbConnection connection;
+            while ((connection = connections.peek()) != null && connection.isIdle(maxIdle)) {
+                rotateConnection(); // probabilistic - may rotate next connection in line, that's okay
+            }
+            try {
+                Thread.sleep(1_000); // wait a moment for next idle check
+            } catch (InterruptedException e) {
+                break;
+            }
+        }
+    }
+
+    private void rotateConnection() {
+        DbConnection connection = connections.poll(); // take from front of queue
+        if (connection != null) {
+            try {
+                connection.executeQuery(1);
+                connections.add(connection); // rotate to back of queue
+            } catch (SQLException ignore) {
+                connection.close();
+            }
+        }
+    }
+
+}

+ 138 - 0
frameworks/Java/microhttp/src/main/java/db/DbWebServer.java

@@ -0,0 +1,138 @@
+package db;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.microhttp.EventLoop;
+import org.microhttp.Header;
+import org.microhttp.LogEntry;
+import org.microhttp.Logger;
+import org.microhttp.Options;
+import org.microhttp.Request;
+import org.microhttp.Response;
+
+import java.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.ZoneOffset;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Consumer;
+
+public class DbWebServer {
+
+    static final String SERVER = "microhttp";
+
+    static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.RFC_1123_DATE_TIME.withZone(ZoneOffset.UTC);
+
+    static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+    final int port;
+    final DbConnectionPool connectionPool;
+    final Executor executor;
+
+    volatile String date = DATE_FORMATTER.format(Instant.now());
+
+    DbWebServer(int port) {
+        this.port = port;
+        this.connectionPool = new DbConnectionPool(32, Duration.ofSeconds(30));
+        this.executor = Executors.newFixedThreadPool(256);
+    }
+
+    void start() throws IOException, InterruptedException {
+        connectionPool.start();
+        startDateUpdater();
+        Options options = new Options()
+                .withHost(null) // wildcard any-address binding
+                .withPort(port)
+                .withReuseAddr(true)
+                .withReusePort(true)
+                .withAcceptLength(8_192)
+                .withMaxRequestSize(1_024 * 1_024)
+                .withReadBufferSize(1_024 * 64)
+                .withResolution(Duration.ofMillis(1_000))
+                .withRequestTimeout(Duration.ofSeconds(90));
+        EventLoop eventLoop = new EventLoop(options, new DisabledLogger(), this::handle);
+        eventLoop.start();
+        eventLoop.join();
+    }
+
+    void startDateUpdater() {
+        Thread thread = new Thread(this::runDateUpdater);
+        thread.setDaemon(true);
+        thread.setPriority(Thread.MIN_PRIORITY);
+        thread.start();
+    }
+
+    void runDateUpdater() {
+        while (true) {
+            try {
+                Thread.sleep(1_000);
+            } catch (InterruptedException e) {
+                return;
+            }
+            date = DATE_FORMATTER.format(Instant.now());
+        }
+    }
+
+    void handle(Request request, Consumer<Response> callback) {
+        if (request.uri().equals("/db")) {
+            executor.execute(() -> handleDbQuery(callback));
+        } else {
+            List<Header> headers = List.of(
+                    new Header("Date", date),
+                    new Header("Server", SERVER));
+            callback.accept(new Response(404, "Not Found", headers, new byte[0]));
+        }
+    }
+
+    void handleDbQuery(Consumer<Response> callback) {
+        try {
+            WorldRow row = connectionPool.executeQuery(1 + ThreadLocalRandom.current().nextInt(10_000));
+            List<Header> headers = List.of(
+                    new Header("Content-Type", "application/json"),
+                    new Header("Date", date),
+                    new Header("Server", SERVER));
+            callback.accept(new Response(200, "OK", headers, jsonBody(row)));
+        } catch (Exception e) {
+            List<Header> headers = List.of(
+                    new Header("Date", date),
+                    new Header("Server", SERVER));
+            callback.accept(new Response(500, "Internal Server Error", headers, new byte[0]));
+        }
+    }
+
+    static byte[] jsonBody(WorldRow row) {
+        try {
+            return OBJECT_MAPPER.writeValueAsBytes(row);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void main(String[] args) throws IOException, InterruptedException {
+        int port = args.length > 0
+                ? Integer.parseInt(args[0])
+                : 8080;
+        new DbWebServer(port).start();
+    }
+
+    static class DisabledLogger implements Logger {
+        @Override
+        public boolean enabled() {
+            return false;
+        }
+
+        @Override
+        public void log(LogEntry... logEntries) {
+
+        }
+
+        @Override
+        public void log(Exception e, LogEntry... logEntries) {
+
+        }
+    }
+
+}

+ 3 - 0
frameworks/Java/microhttp/src/main/java/db/WorldRow.java

@@ -0,0 +1,3 @@
+package db;
+
+public record WorldRow(int id, int randomNumber) {}

+ 3 - 2
frameworks/Java/microhttp/src/main/java/hello/HelloWebServer.java

@@ -40,7 +40,7 @@ public class HelloWebServer {
         this.port = port;
         this.port = port;
     }
     }
 
 
-    void start() throws IOException {
+    void start() throws IOException, InterruptedException {
         startUpdater();
         startUpdater();
         Options options = new Options()
         Options options = new Options()
                 .withHost(null) // wildcard any-address binding
                 .withHost(null) // wildcard any-address binding
@@ -54,6 +54,7 @@ public class HelloWebServer {
                 .withRequestTimeout(Duration.ofSeconds(90));
                 .withRequestTimeout(Duration.ofSeconds(90));
         EventLoop eventLoop = new EventLoop(options, new DisabledLogger(), this::handle);
         EventLoop eventLoop = new EventLoop(options, new DisabledLogger(), this::handle);
         eventLoop.start();
         eventLoop.start();
+        eventLoop.join();
     }
     }
 
 
     void startUpdater() {
     void startUpdater() {
@@ -103,7 +104,7 @@ public class HelloWebServer {
         }
         }
     }
     }
 
 
-    public static void main(String[] args) throws IOException {
+    public static void main(String[] args) throws IOException, InterruptedException {
         int port = args.length > 0
         int port = args.length > 0
                 ? Integer.parseInt(args[0])
                 ? Integer.parseInt(args[0])
                 : 8080;
                 : 8080;