Browse Source

Adding the Armeria framework to the tests (#4041)

* Adding the Armeria framework to the tests

* update for requested changes

* update database name

* fix mustache factory

* refactoring httpheaders and db constants; convert try catch to throws;

* make getHttpHeader a static method

* use Hikari data sources and try for connections

* update software infastructure

* include preparedstatements in try

* include armeria in travis
davidlee-techempower 6 years ago
parent
commit
8bdbb21f9d

+ 1 - 0
.travis.yml

@@ -38,6 +38,7 @@ env:
      - "TESTDIR=Haskell/spock"
      - "TESTDIR=Java/act"
      - "TESTDIR=Java/activeweb"
+     - "TESTDIR=Java/armeria"
      - "TESTDIR=Java/baratine"
      - "TESTDIR=Java/bayou"
      - "TESTDIR=Java/blade"

+ 45 - 0
frameworks/Java/armeria/README.md

@@ -0,0 +1,45 @@
+# Armeria Benchmarking Test
+
+This is the armeria portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+## Infrastructure Software Versions
+
+* [Armeria 0.71.1](https://line.github.io/armeria/)
+* [HikariCP 2.7.8](https://github.com/brettwooldridge/HikariCP)
+* [Postgresql 42.1.4](https://jdbc.postgresql.org/)
+* [Mustache 0.9.5](https://mustache.github.io/)
+
+## Source for Tests
+
+* [JSON serialization test source](src/main/java/hello/services/HelloService.java)
+* [Single database query test source](src/main/java/hello/services/DbService.java)
+* [Multiple database query test source](src/main/java/hello/services/DbService.java)
+* [Fortunes test source](src/main/java/hello/services/FortunesService.java)
+* [Database test source](src/main/java/hello/services/DbService.java)
+* [Plaintext test source](src/main/java/hello/HelloService.java)
+
+## Test URLs
+
+### JSON Serialization Test
+
+    http://localhost:8080/json
+
+### Single Database Query
+
+    http://localhost:8080/db
+
+### Multiple Database Query
+
+    http://localhost:8080/queries/
+
+### Fortunes
+
+    http://localhost:8080/fortunes
+
+### Database Updates
+
+    http://localhost:8080/updates/
+
+### Plaintext Test
+
+    http://localhost:8080/plaintext

+ 10 - 0
frameworks/Java/armeria/armeria.dockerfile

@@ -0,0 +1,10 @@
+FROM maven:3.5.3-jdk-10-slim as maven
+WORKDIR /armeria
+COPY src src
+COPY pom.xml pom.xml
+RUN mvn compile assembly:single -q
+
+FROM openjdk:10-jre-slim
+WORKDIR /armeria
+COPY --from=maven /armeria/target/hello-1.0-SNAPSHOT-jar-with-dependencies.jar app.jar
+CMD ["java", "-server", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-XX:+AggressiveOpts", "-jar", "app.jar"]

+ 27 - 0
frameworks/Java/armeria/benchmark_config.json

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

+ 77 - 0
frameworks/Java/armeria/pom.xml

@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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>hello</groupId>
+  <artifactId>hello</artifactId>
+  <version>1.0-SNAPSHOT</version>
+
+  <properties>
+    <!-- Compiler options -->
+    <maven.compiler.source>10</maven.compiler.source>
+    <maven.compiler.target>10</maven.compiler.target>
+
+    <!-- Dependency versions -->
+    <armeria.version>0.71.1</armeria.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.linecorp.armeria</groupId>
+      <artifactId>armeria</artifactId>
+      <version>${armeria.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.postgresql</groupId>
+      <artifactId>postgresql</artifactId>
+      <version>42.1.4</version>
+    </dependency>
+    <dependency>
+      <groupId>com.github.spullara.mustache.java</groupId>
+      <artifactId>compiler</artifactId>
+      <version>0.9.5</version>
+    </dependency>
+    <dependency>
+      <groupId>com.zaxxer</groupId>
+      <artifactId>HikariCP</artifactId>
+      <version>2.7.8</version>
+      <scope>compile</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+
+      <!-- Build an executable JAR with dependencies -->
+      <plugin>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <version>3.1.0</version>
+        <configuration>
+          <archive>
+            <manifest>
+              <mainClass>hello.App</mainClass>
+            </manifest>
+          </archive>
+          <descriptorRefs>
+            <descriptorRef>jar-with-dependencies</descriptorRef>
+          </descriptorRefs>
+        </configuration>
+        <executions>
+          <execution>
+            <id>make-assembly</id>
+            <phase>package</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+    </plugins>
+  </build>
+
+</project>

+ 23 - 0
frameworks/Java/armeria/src/main/java/hello/App.java

@@ -0,0 +1,23 @@
+package hello;
+
+import hello.services.HelloService;
+import hello.services.PostgresDbService;
+import hello.services.PostgresFortunesService;
+
+import com.linecorp.armeria.common.SessionProtocol;
+import com.linecorp.armeria.server.Server;
+import com.linecorp.armeria.server.ServerBuilder;
+
+public final class App {
+  public static void main(String[] args) {
+    ServerBuilder sb = new ServerBuilder();
+
+    sb.port(8080, SessionProtocol.HTTP)
+      .annotatedService("/", new HelloService())
+      .annotatedService("/", new PostgresDbService())
+      .annotatedService("/", new PostgresFortunesService());
+
+    Server server = sb.build();
+    server.start().join();
+  }
+}

+ 22 - 0
frameworks/Java/armeria/src/main/java/hello/helpers/HttpHeadersHelper.java

@@ -0,0 +1,22 @@
+package hello.helpers;
+
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+
+import com.linecorp.armeria.common.HttpHeaderNames;
+import com.linecorp.armeria.common.HttpHeaders;
+import com.linecorp.armeria.common.HttpStatus;
+import com.linecorp.armeria.common.MediaType;
+
+public class HttpHeadersHelper {
+  public static HttpHeaders getHttpHeader(MediaType mediaType) {
+    return HttpHeaders
+        .of(HttpStatus.OK)
+        .add(HttpHeaderNames.SERVER, "armeria")
+        .add(HttpHeaderNames.DATE,
+             DateTimeFormatter.RFC_1123_DATE_TIME
+                 .format(ZonedDateTime.now(ZoneOffset.UTC)))
+        .contentType(mediaType);
+  }
+}

+ 13 - 0
frameworks/Java/armeria/src/main/java/hello/helpers/PostgresDbHelper.java

@@ -0,0 +1,13 @@
+package hello.helpers;
+
+import com.zaxxer.hikari.HikariConfig;
+
+public final class PostgresDbHelper {
+  public static HikariConfig hikariConfig() {
+    HikariConfig config = new HikariConfig();
+    config.setJdbcUrl("jdbc:postgresql://tfb-database:5432/hello_world");
+    config.setUsername("benchmarkdbuser");
+    config.setPassword("benchmarkdbpass");
+    return config;
+  }
+}

+ 15 - 0
frameworks/Java/armeria/src/main/java/hello/models/Fortune.java

@@ -0,0 +1,15 @@
+package hello.models;
+
+public class Fortune {
+  public int id;
+  public String message;
+
+  public String getMessage() {
+    return this.message;
+  }
+
+  public Fortune(int id, String message) {
+    this.id = id;
+    this.message = message;
+  }
+}

+ 9 - 0
frameworks/Java/armeria/src/main/java/hello/models/Message.java

@@ -0,0 +1,9 @@
+package hello.models;
+
+public class Message {
+  public String message;
+
+  public Message(String message) {
+    this.message = message;
+  }
+}

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

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

+ 36 - 0
frameworks/Java/armeria/src/main/java/hello/services/HelloService.java

@@ -0,0 +1,36 @@
+package hello.services;
+
+import hello.models.Message;
+import hello.helpers.HttpHeadersHelper;
+
+import java.nio.charset.StandardCharsets;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.core.JsonProcessingException;
+
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpResponse;
+import com.linecorp.armeria.common.MediaType;
+import com.linecorp.armeria.server.annotation.Get;
+import com.linecorp.armeria.server.annotation.ProducesJson;
+
+public class HelloService {
+  private static final byte[] PLAINTEXT =
+      "Hello, World!".getBytes(StandardCharsets.UTF_8);
+  private static final ObjectMapper MAPPER = new ObjectMapper();
+
+  @Get("/plaintext")
+  public HttpResponse plaintext() {
+    return HttpResponse.of(
+        HttpHeadersHelper.getHttpHeader(MediaType.PLAIN_TEXT_UTF_8),
+        HttpData.of(PLAINTEXT));
+  }
+
+  @Get("/json")
+  @ProducesJson
+  public HttpResponse json() throws JsonProcessingException {
+    return HttpResponse.of(
+        HttpHeadersHelper.getHttpHeader(MediaType.JSON_UTF_8),
+        HttpData.of(MAPPER.writeValueAsBytes(new Message("Hello, World!"))));
+  }
+}

+ 160 - 0
frameworks/Java/armeria/src/main/java/hello/services/PostgresDbService.java

@@ -0,0 +1,160 @@
+package hello.services;
+
+import com.zaxxer.hikari.HikariDataSource;
+import hello.models.World;
+import hello.helpers.PostgresDbHelper;
+import hello.helpers.HttpHeadersHelper;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpResponse;
+import com.linecorp.armeria.common.MediaType;
+import com.linecorp.armeria.server.annotation.Default;
+import com.linecorp.armeria.server.annotation.Get;
+import com.linecorp.armeria.server.annotation.Param;
+import com.linecorp.armeria.server.annotation.ProducesJson;
+
+public class PostgresDbService {
+  private static final ObjectMapper MAPPER = new ObjectMapper();
+
+  private static final String SELECT_QUERY = "SELECT * FROM world WHERE id = ?";
+  private static final String UPDATE_QUERY =
+      "UPDATE world SET randomNumber = ? WHERE id = ?";
+
+  private DataSource dataSource;
+
+  public PostgresDbService() {
+    dataSource = new HikariDataSource(PostgresDbHelper.hikariConfig());
+  }
+
+  @Get("/db")
+  @ProducesJson
+  public HttpResponse db() throws Exception {
+    return HttpResponse.of(
+        HttpHeadersHelper.getHttpHeader(MediaType.JSON_UTF_8),
+        HttpData.of(MAPPER.writeValueAsBytes(getWorld(getRandomNumber()))));
+  }
+
+  // need to use regex as /queries/{count} doesn't work when count is null
+  @Get("regex:^/queries/(?<count>.*)$")
+  @ProducesJson
+  public HttpResponse queries(
+      @Param("count")
+      @Default("")
+          String count) throws JsonProcessingException, SQLException {
+    return HttpResponse.of(
+        HttpHeadersHelper.getHttpHeader(MediaType.JSON_UTF_8),
+        HttpData.of(
+            MAPPER.writeValueAsBytes(getWorlds(getSanitizedCount(count)))));
+  }
+
+  @Get("regex:^/updates/(?<count>.*)$")
+  @ProducesJson
+  public HttpResponse update(
+      @Param("count")
+      @Default("")
+          String count) throws JsonProcessingException, SQLException {
+    return HttpResponse.of(
+        HttpHeadersHelper.getHttpHeader(MediaType.JSON_UTF_8),
+        HttpData.of(
+            MAPPER.writeValueAsBytes(
+                getUpdatedWorlds(getSanitizedCount(count)))));
+  }
+
+  private static int getRandomNumber() {
+    return 1 + ThreadLocalRandom.current().nextInt(10000);
+  }
+
+  private int getSanitizedCount(String count) {
+    try {
+      int intCount = Integer.parseInt(count);
+      if (intCount < 1) {
+        return 1;
+      }
+      if (intCount > 500) {
+        return 500;
+      }
+      return intCount;
+    } catch (NumberFormatException e) {
+      return 1;
+    }
+  }
+
+  private World getWorld(int number) throws SQLException {
+    try (final Connection connection = dataSource.getConnection();
+         final PreparedStatement statement =
+             connection.prepareStatement(SELECT_QUERY)) {
+
+      statement.setInt(1, number);
+
+      try (final ResultSet resultSet = statement.executeQuery()) {
+        resultSet.next();
+        return new World(resultSet.getInt(1), resultSet.getInt(2));
+      }
+    }
+  }
+
+  private World[] getWorlds(int count) throws SQLException {
+    World[] worlds = new World[count];
+
+    try (final Connection connection = dataSource.getConnection()) {
+      for (int i = 0; i < count; i++) {
+        final int id = getRandomNumber();
+
+        try (final PreparedStatement statement =
+                 connection.prepareStatement(SELECT_QUERY)) {
+          statement.setInt(1, id);
+
+          try (final ResultSet resultSet = statement.executeQuery()) {
+            resultSet.next();
+            worlds[i] = new World(id, resultSet.getInt(2));
+          }
+        }
+      }
+    }
+    return worlds;
+  }
+
+  private World[] getUpdatedWorlds(int count) throws SQLException {
+    World[] worlds = new World[count];
+
+    try (final Connection connection = dataSource.getConnection()) {
+      for (int i = 0; i < count; i++) {
+        final int id = getRandomNumber();
+        final int randomNumber = getRandomNumber();
+
+        try (final PreparedStatement select =
+                 connection.prepareStatement(SELECT_QUERY);
+             final PreparedStatement update =
+                 connection.prepareStatement(UPDATE_QUERY)) {
+
+          // get
+          select.setInt(1, id);
+
+          try (final ResultSet set = select.executeQuery()) {
+            set.next();
+
+            // update
+            update.setInt(1, randomNumber);
+            update.setInt(2, id);
+            update.execute();
+
+            worlds[i] = new World(id, set.getInt(2));
+            worlds[i].randomNumber = randomNumber;
+          }
+        }
+      }
+    }
+    return worlds;
+  }
+}

+ 84 - 0
frameworks/Java/armeria/src/main/java/hello/services/PostgresFortunesService.java

@@ -0,0 +1,84 @@
+package hello.services;
+
+import com.zaxxer.hikari.HikariDataSource;
+import hello.helpers.PostgresDbHelper;
+import hello.models.Fortune;
+import hello.helpers.HttpHeadersHelper;
+
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import javax.sql.DataSource;
+
+import com.linecorp.armeria.common.HttpData;
+import com.linecorp.armeria.common.HttpResponse;
+import com.linecorp.armeria.common.MediaType;
+import com.linecorp.armeria.server.annotation.Get;
+
+import com.github.mustachejava.DefaultMustacheFactory;
+import com.github.mustachejava.Mustache;
+import com.github.mustachejava.MustacheFactory;
+
+public class PostgresFortunesService {
+  private static final String SELECT_QUERY = "SELECT * FROM fortune";
+
+  // Mustache cannot find a classloader on the current thread. Have to pass the classloader manually.
+  // TODO: Look into why mustache cannot find a classloader.
+  private final MustacheFactory mustacheFactory = new DefaultMustacheFactory(
+      name -> new InputStreamReader(
+          HelloService.class.getClassLoader().getResourceAsStream(name)));
+
+  private DataSource dataSource;
+
+  public PostgresFortunesService() {
+    dataSource = new HikariDataSource(PostgresDbHelper.hikariConfig());
+  }
+
+  @Get("/fortunes")
+  public HttpResponse fortunes() throws SQLException, IOException {
+    List<Fortune> fortunes = getFortunes();
+    fortunes.add(
+        new Fortune(0, "Additional fortune added at request time."));
+    fortunes.sort(Comparator.comparing(Fortune::getMessage));
+
+    return HttpResponse.of(
+        HttpHeadersHelper.getHttpHeader(MediaType.HTML_UTF_8),
+        HttpData.ofUtf8(buildMustacheTemplate(fortunes)));
+  }
+
+  private List<Fortune> getFortunes() throws SQLException {
+    List<Fortune> fortunes = new ArrayList<>();
+
+    try (final Connection connection = dataSource.getConnection();
+         final PreparedStatement statement =
+             connection.prepareStatement(SELECT_QUERY);
+         final ResultSet resultSet = statement.executeQuery()) {
+
+      while (resultSet.next()) {
+        fortunes.add(
+            new Fortune(
+                resultSet.getInt(1),
+                resultSet.getString(2)));
+      }
+    }
+    return fortunes;
+  }
+
+  private String buildMustacheTemplate(List<Fortune> fortunes)
+      throws IOException {
+    Mustache mustache = mustacheFactory.compile("fortunes.mustache");
+    StringWriter stringWriter = new StringWriter();
+
+    mustache.execute(stringWriter, fortunes).flush();
+
+    return stringWriter.toString();
+  }
+}

+ 20 - 0
frameworks/Java/armeria/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>{{#.}}<tr>
+      <td>{{id}}</td>
+      <td>{{message}}</td>
+    </tr>{{/.}}
+  </table>
+</body>
+
+</html>