Browse Source

[Java/muserver] Add muserver (#9902)

* [Java/muserver] Add muserver.

* Create object on request to adhere TFB rules.
gyakkun 3 months ago
parent
commit
a40500b310

+ 60 - 0
frameworks/Java/muserver/README.md

@@ -0,0 +1,60 @@
+# muserver Benchmarking Test
+
+### Test Type Implementation Source Code
+
+* [JSON](src/main/java/benchmark/TFBBase.java)
+* [PLAINTEXT](src/main/java/benchmark/TFBBase.java)
+* [DB](src/main/java/benchmark/TFBBase.java)
+* [QUERY](src/main/java/benchmark/TFBBase.java)
+* [UPDATE](src/main/java/benchmark/TFBBase.java)
+* [FORTUNES](src/main/java/benchmark/TFBBase.java)
+
+## Important Libraries
+
+The tests were run with:
+
+* [Java 21](https://jdk.java.net/21/)
+* [muserver 2.1.10](https://muserver.io/)
+* [Jackson 2.19.0](https://github.com/FasterXML/jackson)
+* [Pebble 3.2.4](https://pebbletemplates.io/)
+* [Postgres JDBC Driver 42.7.5](https://jdbc.postgresql.org/)
+* [HikariCP 6.3.0](https://github.com/brettwooldridge/HikariCP)
+
+## Test URLs
+
+For running default test (TFBRest.java), please append "/rest" to the path.
+
+### 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
+
+
+## Reference
+
+The Loom support and IO_URING support are modified from [netty test](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Java/netty).
+
+The database connection part is modified from [Javalin test](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Java/javalin).

+ 53 - 0
frameworks/Java/muserver/benchmark_config.json

@@ -0,0 +1,53 @@
+{
+  "framework": "muserver",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/rest/json",
+        "plaintext_url": "/rest/plaintext",
+        "db_url": "/rest/db",
+        "query_url": "/rest/queries?queries=",
+        "update_url": "/rest/updates?queries=",
+        "fortune_url": "/rest/fortunes",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "Postgres",
+        "framework": "muserver",
+        "language": "Java",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "Netty",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "mu-rest-pg",
+        "notes": "Jax-rs rest resource handler implementation",
+        "versus": "None"
+      },
+      "pg": {
+        "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": "muserver",
+        "language": "Java",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "Netty",
+        "webserver": "None",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "mu-pg",
+        "notes": "MuHandler implementation",
+        "versus": "None"
+      }
+    }
+  ]
+}

+ 15 - 0
frameworks/Java/muserver/muserver-pg.dockerfile

@@ -0,0 +1,15 @@
+FROM maven:3.9.9-eclipse-temurin-21 as maven
+WORKDIR /netty
+COPY pom.xml pom.xml
+COPY src src
+RUN mvn compile assembly:single -q
+
+FROM maven:3.9.9-eclipse-temurin-21
+WORKDIR /netty
+COPY --from=maven /netty/target/app.jar app.jar
+COPY run-pg.sh run-pg.sh
+
+EXPOSE 8080
+# see https://github.com/netty/netty/issues/14942
+# remember to run this with --privileged since https://github.com/TechEmpower/FrameworkBenchmarks/blob/c94f7f95bd751f86a57dea8b63fb8f336bdbbde3/toolset/utils/docker_helper.py#L239 does it
+ENTRYPOINT "./run-pg.sh"

+ 15 - 0
frameworks/Java/muserver/muserver.dockerfile

@@ -0,0 +1,15 @@
+FROM maven:3.9.9-eclipse-temurin-21 as maven
+WORKDIR /netty
+COPY pom.xml pom.xml
+COPY src src
+RUN mvn compile assembly:single -q
+
+FROM maven:3.9.9-eclipse-temurin-21
+WORKDIR /netty
+COPY --from=maven /netty/target/app.jar app.jar
+COPY run.sh run.sh
+
+EXPOSE 8080
+# see https://github.com/netty/netty/issues/14942
+# remember to run this with --privileged since https://github.com/TechEmpower/FrameworkBenchmarks/blob/c94f7f95bd751f86a57dea8b63fb8f336bdbbde3/toolset/utils/docker_helper.py#L239 does it
+ENTRYPOINT "./run.sh"

+ 95 - 0
frameworks/Java/muserver/pom.xml

@@ -0,0 +1,95 @@
+<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/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>tfb.benchmark</groupId>
+    <artifactId>muserver</artifactId>
+    <version>0.1</version>
+
+    <properties>
+        <maven.compiler.source>21</maven.compiler.source>
+        <maven.compiler.target>21</maven.compiler.target>
+    </properties>
+
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>2.0.17</version>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+            <version>2.0.17</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.muserver</groupId>
+            <artifactId>mu-server</artifactId>
+            <version>2.1.10</version>
+        </dependency>
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+            <version>42.7.5</version>
+        </dependency>
+        <dependency>
+            <groupId>com.zaxxer</groupId>
+            <artifactId>HikariCP</artifactId>
+            <version>6.3.0</version>
+        </dependency>
+        <dependency>
+            <groupId>io.pebbletemplates</groupId>
+            <artifactId>pebble</artifactId>
+            <version>3.2.4</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.jakarta.rs</groupId>
+            <artifactId>jackson-jakarta-rs-json-provider</artifactId>
+            <version>2.19.0</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <inherited>true</inherited>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.14.0</version>
+                <configuration>
+                    <debug>false</debug>
+                </configuration>
+            </plugin>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>3.7.1</version>
+                <configuration>
+                    <finalName>app</finalName>
+                    <archive>
+                        <manifest>
+                            <mainClass>hello.HelloWebServer</mainClass>
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                    <appendAssemblyId>false</appendAssemblyId>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id> <!-- this is used for inheritance merges -->
+                        <phase>package</phase> <!-- bind to the packaging phase -->
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 9 - 0
frameworks/Java/muserver/run-pg.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+# PROFILING: -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints
+JAVA_OPTIONS="-XX:+UseNUMA \
+  -XX:+UseZGC \
+  -XX:+ZGenerational \
+  $@"
+
+java $JAVA_OPTIONS -cp app.jar benchmark.TFBPg

+ 9 - 0
frameworks/Java/muserver/run.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+# PROFILING: -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints
+JAVA_OPTIONS="-XX:+UseNUMA \
+  -XX:+UseZGC \
+  -XX:+ZGenerational \
+  $@"
+
+java $JAVA_OPTIONS -cp app.jar benchmark.TFBRest

+ 179 - 0
frameworks/Java/muserver/src/main/java/benchmark/TFBBase.java

@@ -0,0 +1,179 @@
+package benchmark;
+
+import benchmark.model.Fortune;
+import benchmark.model.World;
+import benchmark.repository.DbFactory;
+import benchmark.repository.DbService;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
+import io.muserver.Method;
+import io.muserver.MuResponse;
+import io.muserver.MuServerBuilder;
+import io.muserver.Mutils;
+import io.muserver.rest.RestHandlerBuilder;
+import io.pebbletemplates.pebble.PebbleEngine;
+import io.pebbletemplates.pebble.template.PebbleTemplate;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.QueryParam;
+import jakarta.ws.rs.core.Context;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import static jakarta.ws.rs.core.MediaType.*;
+
+public class TFBBase {
+    public static final Logger LOGGER = LoggerFactory.getLogger(TFBBase.class);
+    public static PebbleEngine engine = new PebbleEngine.Builder().build();
+    public static PebbleTemplate compiledTemplate = engine.getTemplate("templates/fortune.peb");
+
+    public static MuServerBuilder commonBuilderWithMuHandler() {
+        var jackson = new ObjectMapper();
+        var dbService = DbFactory.INSTANCE.getDbService(DbFactory.DbType.POSTGRES);
+        return MuServerBuilder.httpServer()
+                .addHandler((ignore, res) -> {
+                    res.headers().add("Server", "muserver");
+                    return false;
+                })
+                .addHandler(Method.GET, "/plaintext", (req, res, pp) -> {
+                    var ah = req.handleAsync();
+                    res.headers().add("content-type", "text/plain");
+                    ah.write(Mutils.toByteBuffer("Hello, World!"), (optEx) -> ah.complete());
+                })
+                .addHandler(Method.GET, "/json", (req, res, pp) -> {
+                    res.headers().add("content-type", "application/json");
+                    jackson.writeValue(res.writer(), Map.of("message", "Hello, World!"));
+                })
+                .addHandler(Method.GET, "/db", (req, res, pp) -> {
+                    res.headers().add("content-type", "application/json");
+                    jackson.writeValue(res.writer(), dbService.getWorld(1).get(0));
+                })
+                .addHandler(Method.GET, "/queries", (req, res, pp) -> {
+                    res.headers().add("content-type", "application/json");
+                    jackson.writeValue(
+                            res.writer(),
+                            dbService.getWorld(getBoundedRowNumber(req.query().get("queries", "")))
+                    );
+                })
+                .addHandler(Method.GET, "/fortunes", (req, res, pp) -> {
+                    res.headers().set("content-type", "text/html;charset=utf-8");
+                    compiledTemplate.evaluate(
+                            res.writer(),
+                            Map.of("fortunes", dbService.getFortune().stream().map(Fortune::toMap).toList()));
+                })
+                .addHandler(Method.GET, "/updates", (req, res, pp) -> {
+                    res.headers().add("content-type", "application/json");
+                    jackson.writeValue(
+                            res.writer(),
+                            dbService.updateWorld(getBoundedRowNumber(req.query().get("queries", "")))
+                    );
+                })
+                .withInterface("0.0.0.0")
+                .withHttpPort(8080)
+                .addShutdownHook(true);
+    }
+
+    public static MuServerBuilder commonBuilderWithRestHandler() {
+        return MuServerBuilder.httpServer()
+                .addHandler((req, res) -> {
+                    res.headers().add("Server", "muserver");
+                    return false;
+                })
+                .addHandler(RestHandlerBuilder.restHandler(new TFBResource(
+                        DbFactory.INSTANCE.getDbService(DbFactory.DbType.POSTGRES),
+                        compiledTemplate
+                )).addCustomWriter(new JacksonJsonProvider()))
+                .withInterface("0.0.0.0")
+                .withHttpPort(8080)
+                .addShutdownHook(true);
+    }
+
+    @Path("/rest")
+    public static class TFBResource {
+        public final DbService dbService;
+        public final PebbleTemplate template;
+
+        public TFBResource(
+                DbService dbService,
+                PebbleTemplate template
+        ) {
+            this.dbService = dbService;
+            this.template = template;
+        }
+
+        @GET
+        @Path("/plaintext")
+        @Produces(TEXT_PLAIN)
+        public String plaintext() {
+            return "Hello, World!";
+        }
+
+        @GET
+        @Path("/json")
+        @Produces(APPLICATION_JSON)
+        public Map<String, String> json() {
+            return Map.of("message", "Hello, World!");
+        }
+
+        @GET
+        @Path("/db")
+        @Produces(APPLICATION_JSON)
+        public World db() {
+            return dbService.getWorld(1).get(0);
+        }
+
+        @GET
+        @Path("/queries")
+        @Produces(APPLICATION_JSON)
+        public List<World> queries(
+                @QueryParam("queries")
+                String queries
+        ) {
+            int num = getBoundedRowNumber(queries);
+            return dbService.getWorld(num);
+        }
+
+
+        @GET
+        @Path("/fortunes")
+        @Produces(TEXT_HTML)
+        public void fortunes(
+                @Context MuResponse res
+        ) throws IOException {
+            res.headers().set("content-type", "text/html;charset=utf-8");
+            List<Fortune> fortuneList = dbService.getFortune();
+            var writer = res.writer();
+            // writer will be flushed
+            template.evaluate(writer, Map.of("fortunes", fortuneList.stream().map(Fortune::toMap).toList()));
+        }
+
+        @GET
+        @Path("/updates")
+        @Produces(APPLICATION_JSON)
+        public List<World> updates(
+                @QueryParam("queries")
+                String queries
+        ) {
+            return dbService.updateWorld(getBoundedRowNumber(queries));
+        }
+    }
+
+
+    private static final int MIN_QUERIES = 1;
+    private static final int MAX_QUERIES = 500;
+
+    private static int getBoundedRowNumber(String number) {
+        int num;
+        try {
+            num = Integer.parseInt(number);
+        } catch (NumberFormatException e) {
+            num = MIN_QUERIES;
+        }
+        return Math.max(MIN_QUERIES, Math.min(num, MAX_QUERIES));
+    }
+}

+ 8 - 0
frameworks/Java/muserver/src/main/java/benchmark/TFBPg.java

@@ -0,0 +1,8 @@
+package benchmark;
+
+public class TFBPg extends TFBBase {
+    public static void main(String[] args) {
+        var server = commonBuilderWithMuHandler().start();
+        LOGGER.info("Server started at {}", server.uri());
+    }
+}

+ 9 - 0
frameworks/Java/muserver/src/main/java/benchmark/TFBRest.java

@@ -0,0 +1,9 @@
+package benchmark;
+
+public class TFBRest extends TFBBase {
+    public static void main(String[] args) {
+        var server = commonBuilderWithRestHandler().start();
+        LOGGER.info("Server started at {}", server.uri());
+    }
+
+}

+ 12 - 0
frameworks/Java/muserver/src/main/java/benchmark/model/Fortune.java

@@ -0,0 +1,12 @@
+package benchmark.model;
+
+import java.util.Map;
+
+public record Fortune(
+        int id,
+        String message
+) {
+    public Map<String, Object> toMap() {
+        return Map.of("id", id, "message", message);
+    }
+}

+ 12 - 0
frameworks/Java/muserver/src/main/java/benchmark/model/World.java

@@ -0,0 +1,12 @@
+package benchmark.model;
+
+import java.util.Objects;
+
+public record World(
+        int id,
+        int randomNumber
+) {
+    public World copy(Integer id, Integer randomNumber) {
+        return new World(Objects.requireNonNullElse(id, this.id), Objects.requireNonNullElse(randomNumber, this.randomNumber));
+    }
+}

+ 62 - 0
frameworks/Java/muserver/src/main/java/benchmark/repository/DbFactory.java

@@ -0,0 +1,62 @@
+package benchmark.repository;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+
+public enum DbFactory {
+
+    INSTANCE;
+
+    public enum DbType {POSTGRES}
+
+    public DbService getDbService(DbType type) {
+
+        DbService dbService;
+
+        switch (type) {
+            case POSTGRES:
+                dbService = new JDBCDbService();
+                break;
+            default:
+                dbService = null;
+        }
+
+        return dbService;
+    }
+
+    public int getMaxPoolSize(DbType dbType) {
+
+        int maxPoolSize;
+        String env = System.getenv("BENCHMARK_ENV");
+        String propertiesFileName = "/environment.properties";
+        File propFile = new File(propertiesFileName);
+
+        try (InputStream is = propFile.isFile() ?
+                new FileInputStream(propFile) :
+                this.getClass().getResourceAsStream(propertiesFileName)) {
+            Properties prop = new Properties();
+            prop.load(is);
+
+            switch (dbType) {
+                case POSTGRES:
+                    if (prop.getProperty("physicalTag").equals(env)) {
+                        maxPoolSize = Integer.parseInt(prop.getProperty("postgresPhysicalPoolSize"));
+                    } else if (prop.getProperty("cloudTag").equals(env)) {
+                        maxPoolSize = Integer.parseInt(prop.getProperty("postgresCloudPoolSize"));
+                    } else {
+                        maxPoolSize = Integer.parseInt(prop.getProperty("postgresDefaultPoolSize"));
+                    }
+                    break;
+                default:
+                    maxPoolSize = 100;
+            }
+        } catch (IOException | NumberFormatException e) {
+            throw new RuntimeException("Failed to read property file " + propertiesFileName);
+        }
+
+        return maxPoolSize;
+    }
+}

+ 33 - 0
frameworks/Java/muserver/src/main/java/benchmark/repository/DbService.java

@@ -0,0 +1,33 @@
+package benchmark.repository;
+
+import benchmark.model.Fortune;
+import benchmark.model.World;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
+
+public interface DbService {
+
+    int MIN_RANDOM_NUMBER = 1;
+    int MAX_RANDOM_NUMBER_PLUS_ONE = 10001;
+    int defaultFortuneId = 0;
+    String defaultFortuneMessage = "Additional fortune added at request time.";
+
+
+    List<World> getWorld(int num);
+    List<Fortune> getFortune();
+    List<World> updateWorld(int num);
+
+    default int getRandomNumber() {
+        return ThreadLocalRandom.current().nextInt(MIN_RANDOM_NUMBER, MAX_RANDOM_NUMBER_PLUS_ONE);
+    }
+
+    default Set<Integer> getRandomNumberSet(int num) {
+        Set<Integer> set = new HashSet<>();
+        while (set.size() < num)
+            set.add(getRandomNumber());
+        return set;
+    }
+}

+ 28 - 0
frameworks/Java/muserver/src/main/java/benchmark/repository/JDBCConnectionFactory.java

@@ -0,0 +1,28 @@
+package benchmark.repository;
+
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+
+
+public enum JDBCConnectionFactory {
+
+    INSTANCE;
+
+    private final HikariDataSource ds;
+
+    JDBCConnectionFactory() {
+        String propertiesFileName = "/hikari.properties";
+        HikariConfig config = new HikariConfig(propertiesFileName);
+        int maxPoolSize = DbFactory.INSTANCE.getMaxPoolSize(DbFactory.DbType.POSTGRES);
+
+        ds = new HikariDataSource(config);
+        ds.setMaximumPoolSize(maxPoolSize);
+    }
+
+    public Connection getConnection() throws SQLException {
+        return ds.getConnection();
+    }
+}

+ 94 - 0
frameworks/Java/muserver/src/main/java/benchmark/repository/JDBCDbService.java

@@ -0,0 +1,94 @@
+package benchmark.repository;
+
+import benchmark.model.Fortune;
+import benchmark.model.World;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+public class JDBCDbService implements DbService {
+
+    @Override
+    public List<World> getWorld(int num) {
+
+        String select = "select id, randomNumber from World where id = ?";
+        List<World> worldList = new ArrayList<>();
+
+        try (Connection conn = JDBCConnectionFactory.INSTANCE.getConnection();
+             PreparedStatement pstm = conn.prepareStatement(select)) {
+
+            for (int randomId : getRandomNumberSet(num)) {
+                pstm.setInt(1, randomId);
+                try (ResultSet rs = pstm.executeQuery()) {
+                    rs.next();
+                    World world = new World(rs.getInt("id"),rs.getInt("randomNumber"));
+                    worldList.add(world);
+                }
+            }
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+
+        return worldList;
+    }
+
+    @Override
+    public List<Fortune> getFortune() {
+
+        String select = "select id, message from Fortune";
+        List<Fortune> fortuneList = new ArrayList<>();
+
+        try (Connection conn = JDBCConnectionFactory.INSTANCE.getConnection();
+             PreparedStatement pstm = conn.prepareStatement(select);
+             ResultSet rs = pstm.executeQuery()) {
+
+            while (rs.next()) {
+                Fortune fortune = new Fortune(rs.getInt("id"), rs.getString("message"));
+                fortuneList.add(fortune);
+            }
+            fortuneList.add(new Fortune(defaultFortuneId, defaultFortuneMessage));
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+
+        fortuneList.sort(Comparator.comparing(Fortune::message));
+        return fortuneList;
+    }
+
+    @Override
+    public List<World> updateWorld(int num) {
+
+        String update = "update World set randomNumber = ? where id = ?";
+        List<World> worldList = getWorld(num);
+        List<World> updatedWorldList = new ArrayList<>(num);
+
+        try (Connection conn = JDBCConnectionFactory.INSTANCE.getConnection();
+             PreparedStatement pstm = conn.prepareStatement(update)) {
+
+            conn.setAutoCommit(false);
+            for (World world : worldList) {
+                int newRandomNumber;
+                do {
+                    newRandomNumber = getRandomNumber();
+                } while (newRandomNumber == world.randomNumber());
+
+                pstm.setInt(1, newRandomNumber);
+                pstm.setInt(2, world.id());
+                pstm.addBatch();
+
+                updatedWorldList.add(world.copy(null, newRandomNumber));
+            }
+            pstm.executeBatch();
+            conn.commit();
+        } catch (SQLException e) {
+            throw new RuntimeException(e);
+        }
+
+        return updatedWorldList;
+    }
+}

+ 7 - 0
frameworks/Java/muserver/src/main/resources/environment.properties

@@ -0,0 +1,7 @@
+physicalTag=Citrine
+cloudTag=Azure
+
+postgresPhysicalPoolSize=56
+postgresCloudPoolSize=16
+# test in 2c kvm
+postgresDefaultPoolSize=10

+ 7 - 0
frameworks/Java/muserver/src/main/resources/hikari.properties

@@ -0,0 +1,7 @@
+dataSourceClassName=org.postgresql.ds.PGSimpleDataSource
+dataSource.serverName=tfb-database
+dataSource.portNumber=5432
+dataSource.user=benchmarkdbuser
+dataSource.password=benchmarkdbpass
+dataSource.databaseName=hello_world
+

+ 12 - 0
frameworks/Java/muserver/src/main/resources/templates/fortune.peb

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+<tr><th>id</th><th>message</th></tr>
+{% for f in fortunes %}
+<tr><td>{{f.id}}</td><td>{{f.message}}</td></tr>
+{% endfor %}
+</table>
+</body>
+</html>