Browse Source

New framework: Pippo (#7907)

* New benchmark: Pippo

http://www.pippo.ro/

* Dummy commit to trigger GitHub Actions

They were disabled after forking.
Gordan Krešić 2 years ago
parent
commit
7a06aef411
45 changed files with 2247 additions and 0 deletions
  1. 5 0
      frameworks/Java/pippo/.gitignore
  2. 59 0
      frameworks/Java/pippo/README.md
  3. 253 0
      frameworks/Java/pippo/benchmark_config.json
  4. 51 0
      frameworks/Java/pippo/build.gradle
  5. BIN
      frameworks/Java/pippo/gradle/wrapper/gradle-wrapper.jar
  6. 5 0
      frameworks/Java/pippo/gradle/wrapper/gradle-wrapper.properties
  7. 240 0
      frameworks/Java/pippo/gradlew
  8. 91 0
      frameworks/Java/pippo/gradlew.bat
  9. 19 0
      frameworks/Java/pippo/pippo-mongodb.dockerfile
  10. 19 0
      frameworks/Java/pippo/pippo-mysql.dockerfile
  11. 19 0
      frameworks/Java/pippo/pippo-postgres.dockerfile
  12. 19 0
      frameworks/Java/pippo/pippo-tomcat-mongodb.dockerfile
  13. 19 0
      frameworks/Java/pippo/pippo-tomcat-mysql.dockerfile
  14. 19 0
      frameworks/Java/pippo/pippo-tomcat-postgres.dockerfile
  15. 19 0
      frameworks/Java/pippo/pippo-tomcat.dockerfile
  16. 19 0
      frameworks/Java/pippo/pippo-undertow-mongodb.dockerfile
  17. 19 0
      frameworks/Java/pippo/pippo-undertow-mysql.dockerfile
  18. 19 0
      frameworks/Java/pippo/pippo-undertow-postgres.dockerfile
  19. 19 0
      frameworks/Java/pippo/pippo-undertow.dockerfile
  20. 19 0
      frameworks/Java/pippo/pippo.dockerfile
  21. 2 0
      frameworks/Java/pippo/settings.gradle
  22. 75 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/BenchmarkApplication.java
  23. 59 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/BenchmarkEnvironment.java
  24. 43 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/BenchmarkUtils.java
  25. 44 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/benchmark/Benchmark.java
  26. 16 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/benchmark/BenchmarkJetty.java
  27. 14 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/benchmark/BenchmarkTomcat.java
  28. 14 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/benchmark/BenchmarkUndertow.java
  29. 18 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/dao/Dao.java
  30. 152 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/dao/MongoDao.java
  31. 157 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/dao/SqlDao.java
  32. 62 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/handler/Test1JsonHandler.java
  33. 66 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/handler/Test2SingleQueryHandler.java
  34. 79 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/handler/Test3MultiQueryHandler.java
  35. 128 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/handler/Test4FortuneHandler.java
  36. 87 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/handler/Test5UpdateHandler.java
  37. 48 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/handler/Test6PlainTextHandler.java
  38. 16 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/model/Fortune.java
  39. 16 0
      frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/model/World.java
  40. 9 0
      frameworks/Java/pippo/src/main/resources/conf/application.properties
  41. 6 0
      frameworks/Java/pippo/src/main/resources/hikari-mysql.properties
  42. 6 0
      frameworks/Java/pippo/src/main/resources/hikari-postgresql.properties
  43. 22 0
      frameworks/Java/pippo/src/main/resources/logback.xml
  44. 12 0
      frameworks/Java/pippo/src/main/resources/templates/fortune.peb
  45. 164 0
      frameworks/Java/pippo/src/test/java/com/techempower/benchmark/pippo/BenchmarkTests.java

+ 5 - 0
frameworks/Java/pippo/.gitignore

@@ -0,0 +1,5 @@
+/.gradle/
+/.idea/
+/build/
+/local/
+/gradle.properties

+ 59 - 0
frameworks/Java/pippo/README.md

@@ -0,0 +1,59 @@
+# Pippo Benchmarking Test
+
+
+### Test Type Implementation Source Code
+
+* [JSON](src/main/java/com/techempower/benchmark/pippo/handler/Test1JsonHandler.java)
+* [PLAINTEXT](src/main/java/com/techempower/benchmark/pippo/handler/Test6PlainTextHandler.java)
+* [DB](src/main/java/com/techempower/benchmark/pippo/handler/Test2SingleQueryHandler.java)
+* [QUERY](src/main/java/com/techempower/benchmark/pippo/handler/Test3MultiQueryHandler.java)
+* [CACHED QUERY](-)
+* [UPDATE](src/main/java/com/techempower/benchmark/pippo/handler/Test5UpdateHandler.java)
+* [FORTUNES](src/main/java/com/techempower/benchmark/pippo/handler/Test4FortuneHandler.java)
+
+## Important Libraries
+
+The tests were run with:
+
+* [Java 17](https://openjdk.java.net)
+* [Pippo 1.14.0](http://www.pippo.ro/)
+* [HikariCP 5.0.1](https://github.com/brettwooldridge/HikariCP)
+* [DSL-JSON 1.10.0](https://github.com/ngs-doo/dsl-json)
+
+## Test URLs
+
+### JSON
+
+http://localhost:8080/json
+
+### PLAINTEXT
+
+http://localhost:8080/plaintext
+
+### DB
+
+http://localhost:8080/postgres/db
+http://localhost:8080/mysql/db
+http://localhost:8080/mongo/db
+
+### QUERY
+
+http://localhost:8080/postgres/queries?queries=
+http://localhost:8080/mysql/queries?queries=
+http://localhost:8080/mongo/queries?queries=
+
+### CACHED QUERY
+
+-
+
+### UPDATE
+
+http://localhost:8080/postgres/updates?queries=
+http://localhost:8080/mysql/updates?queries=
+http://localhost:8080/mongo/updates?queries=
+
+### FORTUNES
+
+http://localhost:8080/postgres/fortunes
+http://localhost:8080/mysql/fortunes
+http://localhost:8080/mongo/fortunes

+ 253 - 0
frameworks/Java/pippo/benchmark_config.json

@@ -0,0 +1,253 @@
+{
+	"framework": "pippo",
+	"tests": [
+		{
+			"default": {
+				"json_url": "/json",
+				"plaintext_url": "/plaintext",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Micro",
+				"database": "None",
+				"framework": "pippo",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "Raw",
+				"platform": "Jetty",
+				"webserver": "None",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "pippo-jetty",
+				"notes": "",
+				"versus": "None"
+			},
+			"postgres": {
+				"db_url": "/postgres/db",
+				"query_url": "/postgres/queries?queries=",
+				"update_url": "/postgres/updates?queries=",
+				"fortune_url": "/postgres/fortunes",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Micro",
+				"database": "Postgres",
+				"framework": "pippo",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "Raw",
+				"platform": "Jetty",
+				"webserver": "None",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "pippo-jetty-postgres",
+				"notes": "",
+				"versus": "None"
+			},
+			"mysql": {
+				"db_url": "/mysql/db",
+				"query_url": "/mysql/queries?queries=",
+				"update_url": "/mysql/updates?queries=",
+				"fortune_url": "/mysql/fortunes",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Micro",
+				"database": "Mysql",
+				"framework": "pippo",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "Raw",
+				"platform": "Jetty",
+				"webserver": "None",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "pippo-jetty-mysql",
+				"notes": "",
+				"versus": "None"
+			},
+			"mongodb": {
+				"db_url": "/mongo/db",
+				"query_url": "/mongo/queries?queries=",
+				"update_url": "/mongo/updates?queries=",
+				"fortune_url": "/mongo/fortunes",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Micro",
+				"database": "MongoDB",
+				"framework": "pippo",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "Raw",
+				"platform": "Jetty",
+				"webserver": "None",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "pippo-jetty-mongodb",
+				"notes": "",
+				"versus": "None"
+			},
+			"tomcat": {
+				"json_url": "/json",
+				"plaintext_url": "/plaintext",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Micro",
+				"database": "None",
+				"framework": "pippo",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "Raw",
+				"platform": "Tomcat",
+				"webserver": "None",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "pippo-tomcat",
+				"notes": "",
+				"versus": "None"
+			},
+			"tomcat-postgres": {
+				"db_url": "/postgres/db",
+				"query_url": "/postgres/queries?queries=",
+				"update_url": "/postgres/updates?queries=",
+				"fortune_url": "/postgres/fortunes",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Micro",
+				"database": "Postgres",
+				"framework": "pippo",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "Raw",
+				"platform": "Tomcat",
+				"webserver": "None",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "pippo-tomcat-postgres",
+				"notes": "",
+				"versus": "None"
+			},
+			"tomcat-mysql": {
+				"db_url": "/mysql/db",
+				"query_url": "/mysql/queries?queries=",
+				"update_url": "/mysql/updates?queries=",
+				"fortune_url": "/mysql/fortunes",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Micro",
+				"database": "Mysql",
+				"framework": "pippo",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "Raw",
+				"platform": "Tomcat",
+				"webserver": "None",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "pippo-tomcat-mysql",
+				"notes": "",
+				"versus": "None"
+			},
+			"tomcat-mongodb": {
+				"db_url": "/mongo/db",
+				"query_url": "/mongo/queries?queries=",
+				"update_url": "/mongo/updates?queries=",
+				"fortune_url": "/mongo/fortunes",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Micro",
+				"database": "MongoDB",
+				"framework": "pippo",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "Raw",
+				"platform": "Tomcat",
+				"webserver": "None",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "pippo-tomcat-mongodb",
+				"notes": "",
+				"versus": "None"
+			},
+			"undertow": {
+				"json_url": "/json",
+				"plaintext_url": "/plaintext",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Micro",
+				"database": "None",
+				"framework": "pippo",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "Raw",
+				"platform": "Undertow",
+				"webserver": "None",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "pippo-undertow",
+				"notes": "",
+				"versus": "None"
+			},
+			"undertow-postgres": {
+				"db_url": "/postgres/db",
+				"query_url": "/postgres/queries?queries=",
+				"update_url": "/postgres/updates?queries=",
+				"fortune_url": "/postgres/fortunes",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Micro",
+				"database": "Postgres",
+				"framework": "pippo",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "Raw",
+				"platform": "Undertow",
+				"webserver": "None",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "pippo-undertow-postgres",
+				"notes": "",
+				"versus": "None"
+			},
+			"undertow-mysql": {
+				"db_url": "/mysql/db",
+				"query_url": "/mysql/queries?queries=",
+				"update_url": "/mysql/updates?queries=",
+				"fortune_url": "/mysql/fortunes",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Micro",
+				"database": "Mysql",
+				"framework": "pippo",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "Raw",
+				"platform": "Undertow",
+				"webserver": "None",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "pippo-undertow-mysql",
+				"notes": "",
+				"versus": "None"
+			},
+			"undertow-mongodb": {
+				"db_url": "/mongo/db",
+				"query_url": "/mongo/queries?queries=",
+				"update_url": "/mongo/updates?queries=",
+				"fortune_url": "/mongo/fortunes",
+				"port": 8080,
+				"approach": "Realistic",
+				"classification": "Micro",
+				"database": "MongoDB",
+				"framework": "pippo",
+				"language": "Java",
+				"flavor": "None",
+				"orm": "Raw",
+				"platform": "Undertow",
+				"webserver": "None",
+				"os": "Linux",
+				"database_os": "Linux",
+				"display_name": "pippo-undertow-mongodb",
+				"notes": "",
+				"versus": "None"
+			}
+		}
+	]
+}

+ 51 - 0
frameworks/Java/pippo/build.gradle

@@ -0,0 +1,51 @@
+plugins {
+	id 'java'
+}
+
+repositories {
+	mavenCentral()
+}
+
+ext {
+	pippoVersion = '1.14.0'
+}
+
+dependencies {
+
+	implementation 'ch.qos.logback:logback-core:1.4.5'
+	implementation 'ch.qos.logback:logback-classic:1.4.5'
+	implementation 'org.slf4j:slf4j-api:2.0.6'
+
+	implementation 'org.apache.commons:commons-lang3:3.12.0'
+
+	implementation "ro.pippo:pippo-core:${pippoVersion}"
+	implementation "ro.pippo:pippo-jetty:${pippoVersion}"
+	implementation "ro.pippo:pippo-tomcat:${pippoVersion}"
+	implementation "ro.pippo:pippo-undertow:${pippoVersion}"
+	implementation "ro.pippo:pippo-pebble:${pippoVersion}"
+
+	implementation 'org.postgresql:postgresql:42.5.3'
+	implementation 'mysql:mysql-connector-java:8.0.32'
+	implementation 'org.mongodb:mongo-java-driver:3.12.11'
+
+	implementation 'com.zaxxer:HikariCP:5.0.1'
+
+	implementation 'com.dslplatform:dsl-json:1.10.0'
+	implementation 'com.dslplatform:dsl-json-java8:1.10.0'
+	annotationProcessor 'com.dslplatform:dsl-json-java8:1.10.0'
+
+	testImplementation 'junit:junit:4.13.2'
+	testImplementation 'com.squareup.okhttp3:okhttp:4.10.0'
+
+}
+
+test {
+	systemProperty 'pippo.mode', 'test'
+}
+
+task fatJar(type: Jar) {
+	archiveBaseName = 'pippo-all'
+	duplicatesStrategy = DuplicatesStrategy.EXCLUDE
+	from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
+	with jar
+}

BIN
frameworks/Java/pippo/gradle/wrapper/gradle-wrapper.jar


+ 5 - 0
frameworks/Java/pippo/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists

+ 240 - 0
frameworks/Java/pippo/gradlew

@@ -0,0 +1,240 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+#   Gradle start up script for POSIX generated by Gradle.
+#
+#   Important for running:
+#
+#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+#       noncompliant, but you have some other compliant shell such as ksh or
+#       bash, then to run this script, type that shell name before the whole
+#       command line, like:
+#
+#           ksh Gradle
+#
+#       Busybox and similar reduced shells will NOT work, because this script
+#       requires all of these POSIX shell features:
+#         * functions;
+#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+#         * compound commands having a testable exit status, especially «case»;
+#         * various built-in commands including «command», «set», and «ulimit».
+#
+#   Important for patching:
+#
+#   (2) This script targets any POSIX shell, so it avoids extensions provided
+#       by Bash, Ksh, etc; in particular arrays are avoided.
+#
+#       The "traditional" practice of packing multiple parameters into a
+#       space-separated string is a well documented source of bugs and security
+#       problems, so this is (mostly) avoided, by progressively accumulating
+#       options in "$@", and eventually passing that to Java.
+#
+#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+#       see the in-line comments for details.
+#
+#       There are tweaks for specific operating systems such as AIX, CygWin,
+#       Darwin, MinGW, and NonStop.
+#
+#   (3) This script is generated from the Groovy template
+#       https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+#       within the Gradle project.
+#
+#       You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
+    [ -h "$app_path" ]
+do
+    ls=$( ls -ld "$app_path" )
+    link=${ls#*' -> '}
+    case $link in             #(
+      /*)   app_path=$link ;; #(
+      *)    app_path=$APP_HOME$link ;;
+    esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&2
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in                #(
+  CYGWIN* )         cygwin=true  ;; #(
+  Darwin* )         darwin=true  ;; #(
+  MSYS* | MINGW* )  msys=true    ;; #(
+  NONSTOP* )        nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD=$JAVA_HOME/jre/sh/java
+    else
+        JAVACMD=$JAVA_HOME/bin/java
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD=java
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        ulimit -n "$MAX_FD" ||
+            warn "Could not set maximum file descriptor limit to $MAX_FD"
+    esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+#   * args from the command line
+#   * the main class name
+#   * -classpath
+#   * -D...appname settings
+#   * --module-path (only if needed)
+#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+    JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    for arg do
+        if
+            case $arg in                                #(
+              -*)   false ;;                            # don't mess with options #(
+              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
+                    [ -e "$t" ] ;;                      #(
+              *)    false ;;
+            esac
+        then
+            arg=$( cygpath --path --ignore --mixed "$arg" )
+        fi
+        # Roll the args list around exactly as many times as the number of
+        # args, so each arg winds up back in the position where it started, but
+        # possibly modified.
+        #
+        # NB: a `for` loop captures its iteration list before it begins, so
+        # changing the positional parameters here affects neither the number of
+        # iterations, nor the values presented in `arg`.
+        shift                   # remove old arg
+        set -- "$@" "$arg"      # push replacement arg
+    done
+fi
+
+# Collect all arguments for the java command;
+#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+#     shell script including quotes and variable substitutions, so put them in
+#     double quotes to make sure that they get re-expanded; and
+#   * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+        "-Dorg.gradle.appname=$APP_BASE_NAME" \
+        -classpath "$CLASSPATH" \
+        org.gradle.wrapper.GradleWrapperMain \
+        "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+    die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+#   set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+        xargs -n1 |
+        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+        tr '\n' ' '
+    )" '"$@"'
+
+exec "$JAVACMD" "$@"

+ 91 - 0
frameworks/Java/pippo/gradlew.bat

@@ -0,0 +1,91 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 19 - 0
frameworks/Java/pippo/pippo-mongodb.dockerfile

@@ -0,0 +1,19 @@
+FROM gradle:7.6.0-jdk17 as gradle
+WORKDIR /pippo
+COPY gradle gradle
+COPY build.gradle build.gradle
+COPY gradlew gradlew
+COPY src src
+RUN ./gradlew fatJar -x test
+
+FROM eclipse-temurin:17.0.6_10-jre-jammy
+WORKDIR /pippo
+COPY --from=gradle /pippo/build/libs/pippo-all.jar app.jar
+
+ARG BENCHMARK_ENV
+
+ENV BENCHMARK_ENV=$BENCHMARK_ENV
+
+EXPOSE 8080
+
+CMD ["java", "-server", "-Xms4G", "-Xmx4G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-cp", "app.jar", "com.techempower.benchmark.pippo.benchmark.BenchmarkJetty"]

+ 19 - 0
frameworks/Java/pippo/pippo-mysql.dockerfile

@@ -0,0 +1,19 @@
+FROM gradle:7.6.0-jdk17 as gradle
+WORKDIR /pippo
+COPY gradle gradle
+COPY build.gradle build.gradle
+COPY gradlew gradlew
+COPY src src
+RUN ./gradlew fatJar -x test
+
+FROM eclipse-temurin:17.0.6_10-jre-jammy
+WORKDIR /pippo
+COPY --from=gradle /pippo/build/libs/pippo-all.jar app.jar
+
+ARG BENCHMARK_ENV
+
+ENV BENCHMARK_ENV=$BENCHMARK_ENV
+
+EXPOSE 8080
+
+CMD ["java", "-server", "-Xms4G", "-Xmx4G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-cp", "app.jar", "com.techempower.benchmark.pippo.benchmark.BenchmarkJetty"]

+ 19 - 0
frameworks/Java/pippo/pippo-postgres.dockerfile

@@ -0,0 +1,19 @@
+FROM gradle:7.6.0-jdk17 as gradle
+WORKDIR /pippo
+COPY gradle gradle
+COPY build.gradle build.gradle
+COPY gradlew gradlew
+COPY src src
+RUN ./gradlew fatJar -x test
+
+FROM eclipse-temurin:17.0.6_10-jre-jammy
+WORKDIR /pippo
+COPY --from=gradle /pippo/build/libs/pippo-all.jar app.jar
+
+ARG BENCHMARK_ENV
+
+ENV BENCHMARK_ENV=$BENCHMARK_ENV
+
+EXPOSE 8080
+
+CMD ["java", "-server", "-Xms4G", "-Xmx4G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-cp", "app.jar", "com.techempower.benchmark.pippo.benchmark.BenchmarkJetty"]

+ 19 - 0
frameworks/Java/pippo/pippo-tomcat-mongodb.dockerfile

@@ -0,0 +1,19 @@
+FROM gradle:7.6.0-jdk17 as gradle
+WORKDIR /pippo
+COPY gradle gradle
+COPY build.gradle build.gradle
+COPY gradlew gradlew
+COPY src src
+RUN ./gradlew fatJar -x test
+
+FROM eclipse-temurin:17.0.6_10-jre-jammy
+WORKDIR /pippo
+COPY --from=gradle /pippo/build/libs/pippo-all.jar app.jar
+
+ARG BENCHMARK_ENV
+
+ENV BENCHMARK_ENV=$BENCHMARK_ENV
+
+EXPOSE 8080
+
+CMD ["java", "-server", "-Xms4G", "-Xmx4G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-cp", "app.jar", "com.techempower.benchmark.pippo.benchmark.BenchmarkTomcat"]

+ 19 - 0
frameworks/Java/pippo/pippo-tomcat-mysql.dockerfile

@@ -0,0 +1,19 @@
+FROM gradle:7.6.0-jdk17 as gradle
+WORKDIR /pippo
+COPY gradle gradle
+COPY build.gradle build.gradle
+COPY gradlew gradlew
+COPY src src
+RUN ./gradlew fatJar -x test
+
+FROM eclipse-temurin:17.0.6_10-jre-jammy
+WORKDIR /pippo
+COPY --from=gradle /pippo/build/libs/pippo-all.jar app.jar
+
+ARG BENCHMARK_ENV
+
+ENV BENCHMARK_ENV=$BENCHMARK_ENV
+
+EXPOSE 8080
+
+CMD ["java", "-server", "-Xms4G", "-Xmx4G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-cp", "app.jar", "com.techempower.benchmark.pippo.benchmark.BenchmarkTomcat"]

+ 19 - 0
frameworks/Java/pippo/pippo-tomcat-postgres.dockerfile

@@ -0,0 +1,19 @@
+FROM gradle:7.6.0-jdk17 as gradle
+WORKDIR /pippo
+COPY gradle gradle
+COPY build.gradle build.gradle
+COPY gradlew gradlew
+COPY src src
+RUN ./gradlew fatJar -x test
+
+FROM eclipse-temurin:17.0.6_10-jre-jammy
+WORKDIR /pippo
+COPY --from=gradle /pippo/build/libs/pippo-all.jar app.jar
+
+ARG BENCHMARK_ENV
+
+ENV BENCHMARK_ENV=$BENCHMARK_ENV
+
+EXPOSE 8080
+
+CMD ["java", "-server", "-Xms4G", "-Xmx4G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-cp", "app.jar", "com.techempower.benchmark.pippo.benchmark.BenchmarkTomcat"]

+ 19 - 0
frameworks/Java/pippo/pippo-tomcat.dockerfile

@@ -0,0 +1,19 @@
+FROM gradle:7.6.0-jdk17 as gradle
+WORKDIR /pippo
+COPY gradle gradle
+COPY build.gradle build.gradle
+COPY gradlew gradlew
+COPY src src
+RUN ./gradlew fatJar -x test
+
+FROM eclipse-temurin:17.0.6_10-jre-jammy
+WORKDIR /pippo
+COPY --from=gradle /pippo/build/libs/pippo-all.jar app.jar
+
+ARG BENCHMARK_ENV
+
+ENV BENCHMARK_ENV=$BENCHMARK_ENV
+
+EXPOSE 8080
+
+CMD ["java", "-server", "-Xms4G", "-Xmx4G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-cp", "app.jar", "com.techempower.benchmark.pippo.benchmark.BenchmarkTomcat"]

+ 19 - 0
frameworks/Java/pippo/pippo-undertow-mongodb.dockerfile

@@ -0,0 +1,19 @@
+FROM gradle:7.6.0-jdk17 as gradle
+WORKDIR /pippo
+COPY gradle gradle
+COPY build.gradle build.gradle
+COPY gradlew gradlew
+COPY src src
+RUN ./gradlew fatJar -x test
+
+FROM eclipse-temurin:17.0.6_10-jre-jammy
+WORKDIR /pippo
+COPY --from=gradle /pippo/build/libs/pippo-all.jar app.jar
+
+ARG BENCHMARK_ENV
+
+ENV BENCHMARK_ENV=$BENCHMARK_ENV
+
+EXPOSE 8080
+
+CMD ["java", "-server", "-Xms4G", "-Xmx4G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-cp", "app.jar", "com.techempower.benchmark.pippo.benchmark.BenchmarkUndertow"]

+ 19 - 0
frameworks/Java/pippo/pippo-undertow-mysql.dockerfile

@@ -0,0 +1,19 @@
+FROM gradle:7.6.0-jdk17 as gradle
+WORKDIR /pippo
+COPY gradle gradle
+COPY build.gradle build.gradle
+COPY gradlew gradlew
+COPY src src
+RUN ./gradlew fatJar -x test
+
+FROM eclipse-temurin:17.0.6_10-jre-jammy
+WORKDIR /pippo
+COPY --from=gradle /pippo/build/libs/pippo-all.jar app.jar
+
+ARG BENCHMARK_ENV
+
+ENV BENCHMARK_ENV=$BENCHMARK_ENV
+
+EXPOSE 8080
+
+CMD ["java", "-server", "-Xms4G", "-Xmx4G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-cp", "app.jar", "com.techempower.benchmark.pippo.benchmark.BenchmarkUndertow"]

+ 19 - 0
frameworks/Java/pippo/pippo-undertow-postgres.dockerfile

@@ -0,0 +1,19 @@
+FROM gradle:7.6.0-jdk17 as gradle
+WORKDIR /pippo
+COPY gradle gradle
+COPY build.gradle build.gradle
+COPY gradlew gradlew
+COPY src src
+RUN ./gradlew fatJar -x test
+
+FROM eclipse-temurin:17.0.6_10-jre-jammy
+WORKDIR /pippo
+COPY --from=gradle /pippo/build/libs/pippo-all.jar app.jar
+
+ARG BENCHMARK_ENV
+
+ENV BENCHMARK_ENV=$BENCHMARK_ENV
+
+EXPOSE 8080
+
+CMD ["java", "-server", "-Xms4G", "-Xmx4G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-cp", "app.jar", "com.techempower.benchmark.pippo.benchmark.BenchmarkUndertow"]

+ 19 - 0
frameworks/Java/pippo/pippo-undertow.dockerfile

@@ -0,0 +1,19 @@
+FROM gradle:7.6.0-jdk17 as gradle
+WORKDIR /pippo
+COPY gradle gradle
+COPY build.gradle build.gradle
+COPY gradlew gradlew
+COPY src src
+RUN ./gradlew fatJar -x test
+
+FROM eclipse-temurin:17.0.6_10-jre-jammy
+WORKDIR /pippo
+COPY --from=gradle /pippo/build/libs/pippo-all.jar app.jar
+
+ARG BENCHMARK_ENV
+
+ENV BENCHMARK_ENV=$BENCHMARK_ENV
+
+EXPOSE 8080
+
+CMD ["java", "-server", "-Xms4G", "-Xmx4G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-cp", "app.jar", "com.techempower.benchmark.pippo.benchmark.BenchmarkUndertow"]

+ 19 - 0
frameworks/Java/pippo/pippo.dockerfile

@@ -0,0 +1,19 @@
+FROM gradle:7.6.0-jdk17 as gradle
+WORKDIR /pippo
+COPY gradle gradle
+COPY build.gradle build.gradle
+COPY gradlew gradlew
+COPY src src
+RUN ./gradlew fatJar -x test
+
+FROM eclipse-temurin:17.0.6_10-jre-jammy
+WORKDIR /pippo
+COPY --from=gradle /pippo/build/libs/pippo-all.jar app.jar
+
+ARG BENCHMARK_ENV
+
+ENV BENCHMARK_ENV=$BENCHMARK_ENV
+
+EXPOSE 8080
+
+CMD ["java", "-server", "-Xms4G", "-Xmx4G", "-XX:+UseNUMA", "-XX:+UseParallelGC", "-cp", "app.jar", "com.techempower.benchmark.pippo.benchmark.BenchmarkJetty"]

+ 2 - 0
frameworks/Java/pippo/settings.gradle

@@ -0,0 +1,2 @@
+rootProject.name = 'pippo'
+

+ 75 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/BenchmarkApplication.java

@@ -0,0 +1,75 @@
+package com.techempower.benchmark.pippo;
+
+import com.techempower.benchmark.pippo.dao.Dao;
+import com.techempower.benchmark.pippo.dao.MongoDao;
+import com.techempower.benchmark.pippo.dao.SqlDao;
+import com.techempower.benchmark.pippo.handler.Test1JsonHandler;
+import com.techempower.benchmark.pippo.handler.Test2SingleQueryHandler;
+import com.techempower.benchmark.pippo.handler.Test3MultiQueryHandler;
+import com.techempower.benchmark.pippo.handler.Test4FortuneHandler;
+import com.techempower.benchmark.pippo.handler.Test5UpdateHandler;
+import com.techempower.benchmark.pippo.handler.Test6PlainTextHandler;
+import ro.pippo.core.Application;
+import ro.pippo.pebble.PebbleTemplateEngine;
+
+public class BenchmarkApplication extends Application {
+
+	@Override
+	protected void onInit() {
+
+		setTemplateEngine(new PebbleTemplateEngine());
+
+		postgresql = new SqlDao("postgresql");
+		mysql = new SqlDao("mysql");
+		mongo = new MongoDao();
+
+		GET("/json", new Test1JsonHandler());
+
+		GET("/postgres/db", new Test2SingleQueryHandler(postgresql));
+		GET("/mysql/db", new Test2SingleQueryHandler(mysql));
+		GET("/mongo/db", new Test2SingleQueryHandler(mongo));
+
+		GET("/postgres/queries", new Test3MultiQueryHandler(postgresql));
+		GET("/mysql/queries", new Test3MultiQueryHandler(mysql));
+		GET("/mongo/queries", new Test3MultiQueryHandler(mongo));
+
+		GET("/postgres/fortunes", new Test4FortuneHandler(postgresql));
+		GET("/mysql/fortunes", new Test4FortuneHandler(mysql));
+		GET("/mongo/fortunes", new Test4FortuneHandler(mongo));
+
+		GET("/postgres/updates", new Test5UpdateHandler(postgresql));
+		GET("/mysql/updates", new Test5UpdateHandler(mysql));
+		GET("/mongo/updates", new Test5UpdateHandler(mongo));
+
+		GET("/plaintext", new Test6PlainTextHandler());
+
+	}
+
+	@Override
+	protected void onDestroy() {
+
+		try {
+			postgresql.close();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+		try {
+			mysql.close();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+		try {
+			mongo.close();
+		} catch (Exception e) {
+			throw new RuntimeException(e);
+		}
+
+		super.onDestroy();
+
+	}
+
+	private Dao postgresql;
+	private Dao mysql;
+	private Dao mongo;
+
+}

+ 59 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/BenchmarkEnvironment.java

@@ -0,0 +1,59 @@
+package com.techempower.benchmark.pippo;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class BenchmarkEnvironment {
+
+	// see: https://github.com/TechEmpower/FrameworkBenchmarks/wiki/Project-Information-Environment
+
+	public enum Environment {
+		Citrine,
+		Azure,
+		Unknown
+	}
+
+	public static Environment $() {
+
+		if (env == null) {
+			envLock.lock();
+			try {
+				if (env == null)
+					env = calculate();
+			} finally {
+				envLock.unlock();
+			}
+		}
+
+		return env;
+
+	}
+
+	private static Environment calculate() {
+
+		String envRaw = System.getenv("BENCHMARK_ENV");
+
+		if (StringUtils.isBlank(envRaw)) {
+			Log.info("No benchmark environment set");
+			return Environment.Unknown;
+		}
+
+		try {
+			return Environment.valueOf(envRaw);
+		} catch (IllegalArgumentException e) {
+			Log.warn("Unknown benchmark environment: '{}'", envRaw);
+			return Environment.Unknown;
+		}
+
+	}
+
+	private static final Logger Log = LoggerFactory.getLogger(BenchmarkEnvironment.class);
+
+	private static final Lock envLock = new ReentrantLock();
+	private static Environment env = null;
+
+}

+ 43 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/BenchmarkUtils.java

@@ -0,0 +1,43 @@
+package com.techempower.benchmark.pippo;
+
+import ro.pippo.core.ParameterValue;
+import ro.pippo.core.route.RouteContext;
+
+import java.util.concurrent.ThreadLocalRandom;
+
+public class BenchmarkUtils {
+
+	// stuff missing from HttpConstants.Header
+	public static final class Header {
+		public static final String SERVER = "Server";
+	}
+
+	public static int random() {
+		return 1 + ThreadLocalRandom.current().nextInt(10_000);
+	}
+
+	public static int getQueriesParam(RouteContext routeContext) {
+
+		ParameterValue param = routeContext.getParameter("queries");
+
+		if (param.isEmpty())
+			return 1;
+
+		int queries;
+		try {
+			queries = Integer.parseInt(param.toString());
+		} catch (NumberFormatException e) {
+			return 1;
+		}
+
+		if (queries < 1)
+			return 1;
+
+		if (queries > 500)
+			return 500;
+
+		return queries;
+
+	}
+
+}

+ 44 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/benchmark/Benchmark.java

@@ -0,0 +1,44 @@
+package com.techempower.benchmark.pippo.benchmark;
+
+import com.techempower.benchmark.pippo.BenchmarkApplication;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import ro.pippo.core.AbstractWebServer;
+import ro.pippo.core.Pippo;
+
+public class Benchmark {
+
+	public Benchmark() {
+		BenchmarkApplication app = new BenchmarkApplication();
+		pippo = new Pippo(app);
+	}
+
+	public Benchmark serverName(String serverName) {
+		this.serverName = serverName;
+		return this;
+	}
+
+	public Benchmark server(AbstractWebServer<?> server) {
+		pippo.setServer(server);
+		return this;
+	}
+
+	public Benchmark start() {
+		Log.info("Starting benchmark {}...", serverName);
+		pippo.start();
+		Log.info("Benchmark {} started", serverName);
+		return this;
+	}
+
+	public Benchmark stop() {
+		pippo.stop();
+		return this;
+	}
+
+	private static final Logger Log = LoggerFactory.getLogger(Benchmark.class);
+
+	private final Pippo pippo;
+
+	private String serverName;
+
+}

+ 16 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/benchmark/BenchmarkJetty.java

@@ -0,0 +1,16 @@
+package com.techempower.benchmark.pippo.benchmark;
+
+import ro.pippo.jetty.JettyServer;
+
+public class BenchmarkJetty {
+
+	public static void main(String[] args) {
+
+		new Benchmark()
+			.serverName("Jetty")
+			.server(new JettyServer())
+			.start();
+
+	}
+
+}

+ 14 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/benchmark/BenchmarkTomcat.java

@@ -0,0 +1,14 @@
+package com.techempower.benchmark.pippo.benchmark;
+
+import ro.pippo.tomcat.TomcatServer;
+
+public class BenchmarkTomcat {
+
+	public static void main(String[] args) {
+		new Benchmark()
+			.serverName("Tomcat")
+			.server(new TomcatServer())
+			.start();
+	}
+
+}

+ 14 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/benchmark/BenchmarkUndertow.java

@@ -0,0 +1,14 @@
+package com.techempower.benchmark.pippo.benchmark;
+
+import ro.pippo.undertow.UndertowServer;
+
+public class BenchmarkUndertow {
+
+	public static void main(String[] args) {
+		new Benchmark()
+			.serverName("Undertow")
+			.server(new UndertowServer())
+			.start();
+	}
+
+}

+ 18 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/dao/Dao.java

@@ -0,0 +1,18 @@
+package com.techempower.benchmark.pippo.dao;
+
+import com.techempower.benchmark.pippo.model.Fortune;
+import com.techempower.benchmark.pippo.model.World;
+
+import java.util.List;
+
+public interface Dao extends AutoCloseable {
+
+	World getRandomWorld();
+
+	List<World> getRandomWorlds(int count);
+
+	void updateRandomWorlds(List<World> model);
+
+	List<Fortune> getFortunes();
+
+}

+ 152 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/dao/MongoDao.java

@@ -0,0 +1,152 @@
+package com.techempower.benchmark.pippo.dao;
+
+import com.mongodb.MongoClient;
+import com.mongodb.MongoClientOptions;
+import com.mongodb.ServerAddress;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoCursor;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.UpdateOneModel;
+import com.techempower.benchmark.pippo.BenchmarkEnvironment;
+import com.techempower.benchmark.pippo.BenchmarkUtils;
+import com.techempower.benchmark.pippo.model.Fortune;
+import com.techempower.benchmark.pippo.model.World;
+import org.bson.Document;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class MongoDao implements Dao {
+
+	@Override
+	public World getRandomWorld() {
+		Document document = getWorldsCollection().find(Filters.eq("_id", BenchmarkUtils.random())).first();
+		int id = document.getInteger("_id");
+		int random = document.getInteger("randomNumber");
+		return new World(id, random);
+	}
+
+	@Override
+	public List<World> getRandomWorlds(int count) {
+		List<World> worlds = new ArrayList<>();
+		MongoCollection<Document> collection = getWorldsCollection();
+		for (int i = 0; i < count; i++) {
+			Document document = collection.find(Filters.eq("_id", BenchmarkUtils.random())).first();
+			int id = document.getInteger("_id");
+			int random = document.getInteger("randomNumber");
+			worlds.add(new World(id, random));
+		}
+		return worlds;
+	}
+
+	@Override
+	public void updateRandomWorlds(List<World> worlds) {
+		MongoCollection<Document> collection = getWorldsCollection();
+		List<UpdateOneModel<Document>> updates = new ArrayList<>();
+		for (World world : worlds) {
+			updates.add(
+				new UpdateOneModel<>(
+					new Document("_id", world.id),
+					new Document("$set", new Document("randomNumber", world.randomNumber))
+				)
+			);
+		}
+		collection.bulkWrite(updates);
+	}
+
+	@Override
+	public List<Fortune> getFortunes() {
+		List<Fortune> fortunes = new ArrayList<>();
+		try (MongoCursor<Document> cursor = getFortunesCollection().find().iterator()) {
+			while (cursor.hasNext()) {
+				Document document = cursor.next();
+				fortunes.add(new Fortune(
+					document.getInteger("_id"),
+					document.getString("message")
+				));
+			}
+			return fortunes;
+		}
+	}
+
+	@Override
+	public void close() {
+		if (client != null)
+			client.close();
+	}
+
+	private MongoClient getClient() {
+		if (client == null) {
+			clientLock.lock();
+			try {
+				if (client == null) {
+					MongoClientOptions options = MongoClientOptions.builder()
+													 .connectionsPerHost(
+														 switch (BenchmarkEnvironment.$()) {
+															 case Citrine -> 100;
+															 case Azure -> 100;
+															 case Unknown -> 50;
+														 }
+													 )
+													 .build();
+					client = new MongoClient(new ServerAddress("tfb-database", 27017), options);
+				}
+			} finally {
+				clientLock.unlock();
+			}
+		}
+		return client;
+	}
+
+	private MongoDatabase getDatabase() {
+		if (database == null) {
+			databaseLock.lock();
+			try {
+				if (database == null)
+					database = getClient().getDatabase("hello_world");
+			} finally {
+				databaseLock.unlock();
+			}
+		}
+		return database;
+	}
+
+	private MongoCollection<Document> getWorldsCollection() {
+		if (worldsCollection == null) {
+			worldsCollectionLock.lock();
+			try {
+				if (worldsCollection == null)
+					worldsCollection = getDatabase().getCollection("world");
+			} finally {
+				worldsCollectionLock.unlock();
+			}
+		}
+		return worldsCollection;
+	}
+
+	private MongoCollection<Document> getFortunesCollection() {
+		if (fortunesCollection == null) {
+			fortunesCollectionLock.lock();
+			try {
+				if (fortunesCollection == null)
+					fortunesCollection = database.getCollection("fortune");
+			} finally {
+				fortunesCollectionLock.unlock();
+			}
+		}
+		return fortunesCollection;
+	}
+
+	private MongoClient client = null;	// lazy init
+	private final Lock clientLock = new ReentrantLock();
+	private MongoDatabase database = null;	// lazy init
+	private final Lock databaseLock = new ReentrantLock();
+	private MongoCollection<Document> worldsCollection = null;	// lazy init
+	private final Lock worldsCollectionLock = new ReentrantLock();
+	private MongoCollection<Document> fortunesCollection = null;	// lazy init
+	private final Lock fortunesCollectionLock = new ReentrantLock();
+
+}

+ 157 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/dao/SqlDao.java

@@ -0,0 +1,157 @@
+package com.techempower.benchmark.pippo.dao;
+
+import com.techempower.benchmark.pippo.BenchmarkEnvironment;
+import com.techempower.benchmark.pippo.BenchmarkUtils;
+import com.techempower.benchmark.pippo.model.Fortune;
+import com.techempower.benchmark.pippo.model.World;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+import javax.sql.DataSource;
+import java.io.InputStream;
+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;
+import java.util.Properties;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class SqlDao implements Dao {
+
+	public SqlDao(String database) {
+		this.database = database;
+	}
+
+	@Override
+	public World getRandomWorld() {
+		try (
+			Connection connection = getDataSource().getConnection();
+			PreparedStatement statement = connection.prepareStatement(SelectRandomWorld)
+		) {
+			statement.setInt(1, BenchmarkUtils.random());
+			try (ResultSet resultSet = statement.executeQuery()) {
+				resultSet.next();
+				int id = resultSet.getInt(1);
+				int randomNumber = resultSet.getInt(2);
+				return new World(id, randomNumber);
+			}
+		} catch (SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@Override
+	public List<World> getRandomWorlds(int count) {
+		List<World> worlds = new ArrayList<>();
+		try (
+			Connection connection = getDataSource().getConnection();
+			PreparedStatement statement = connection.prepareStatement(SelectRandomWorld)
+		) {
+			for (int i = 0; i < count; i++) {
+				statement.setInt(1, BenchmarkUtils.random());
+				try (ResultSet resultSet = statement.executeQuery()) {
+					resultSet.next();
+					int id = resultSet.getInt(1);
+					int randomNumber = resultSet.getInt(2);
+					worlds.add(new World(id, randomNumber));
+				}
+			}
+		} catch (SQLException e) {
+			throw new RuntimeException(e);
+		}
+		return worlds;
+	}
+
+	@Override
+	public void updateRandomWorlds(List<World> worlds) {
+		try (
+			Connection connection = getDataSource().getConnection();
+			PreparedStatement statement = connection.prepareStatement(UpdateRandomWorld)
+		) {
+			connection.setAutoCommit(false);
+			// sort to prevent deadlocks
+			worlds.sort(Comparator.comparing(world -> world.id));
+			for (World world : worlds) {
+				statement.setInt(1, world.randomNumber);
+				statement.setInt(2, world.id);
+				statement.addBatch();
+			}
+			statement.executeBatch();
+			connection.commit();
+		} catch (SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@Override
+	public List<Fortune> getFortunes() {
+		try (
+			Connection connection = getDataSource().getConnection();
+			PreparedStatement statement = connection.prepareStatement(SelectFortunes)
+		) {
+			List<Fortune> fortunes = new ArrayList<>();
+			try (ResultSet resultSet = statement.executeQuery()) {
+				while (resultSet.next()) {
+					int id = resultSet.getInt(1);
+					String message = resultSet.getString(2);
+					fortunes.add(new Fortune(id, message));
+				}
+			}
+			return fortunes;
+		} catch (SQLException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@Override
+	public void close() {
+		if (dataSource != null)
+			dataSource.close();
+	}
+
+	private DataSource getDataSource() {
+
+		if (dataSource == null) {
+			dataSourceLock.lock();
+			try {
+				if (dataSource == null) {
+					HikariConfig config;
+					try (InputStream istream = Thread.currentThread().getContextClassLoader().getResourceAsStream(String.format("hikari-%s.properties", database))) {
+						Properties properties = new Properties();
+						properties.load(istream);
+						config = new HikariConfig(properties);
+						config.setMaximumPoolSize(
+							switch (BenchmarkEnvironment.$()) {
+								case Citrine -> Runtime.getRuntime().availableProcessors() * 2;
+								case Azure -> 16;
+								case Unknown -> Runtime.getRuntime().availableProcessors() * 2;
+							}
+						);
+					} catch (Exception e) {
+						throw new RuntimeException("Error loading HikariCP configuration", e);
+					}
+					dataSource = new HikariDataSource(config);
+				}
+			} finally {
+				dataSourceLock.unlock();
+			}
+		}
+
+		return dataSource;
+
+	}
+
+	private static final String SelectRandomWorld = "SELECT id, randomnumber FROM world WHERE id = ?";
+	private static final String UpdateRandomWorld = "UPDATE world SET randomnumber = ? WHERE id = ?";
+	private static final String SelectFortunes = "SELECT id, message FROM fortune";
+
+	private final String database;
+
+	private HikariDataSource dataSource = null;	// lazy init
+	private final Lock dataSourceLock = new ReentrantLock();
+
+}

+ 62 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/handler/Test1JsonHandler.java

@@ -0,0 +1,62 @@
+package com.techempower.benchmark.pippo.handler;
+
+import com.dslplatform.json.DslJson;
+import com.techempower.benchmark.pippo.BenchmarkUtils;
+import ro.pippo.core.HttpConstants;
+import ro.pippo.core.route.RouteContext;
+import ro.pippo.core.route.RouteHandler;
+
+import java.io.IOException;
+import java.util.Collections;
+
+/**
+ * <p>Test type 1: JSON serialization</p>
+ *
+ * <p>Example request:</p>
+ *
+ * <pre>
+ * GET /json HTTP/1.1
+ * Host: server
+ * User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
+ * Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+ * Accept: text/html,application/xhtml+xml,application/xml;q=0.9,"*&#47;"/"*&#47;";q=0.8
+ * Accept-Language: en-US,en;q=0.5
+ * Connection: keep-alive
+ * </pre>
+ *
+ * <p>Example response</p>
+ *
+ * <pre>
+ * HTTP/1.1 200 OK
+ * Content-Type: application/json
+ * Content-Length: 28
+ * Server: Example
+ * Date: Wed, 17 Apr 2013 12:00:00 GMT
+ *
+ * {"message":"Hello, World!"}
+ * </pre>
+ */
+public class Test1JsonHandler implements RouteHandler<RouteContext> {
+
+	public static final String Message = "message";
+	public static final String Hello = "Hello, World!";
+
+	@Override
+	public void handle(RouteContext routeContext) {
+		try {
+			JsonMapper.serialize(
+				Collections.singletonMap(Message, Hello),
+				routeContext
+					.getResponse()
+					.contentType(HttpConstants.ContentType.APPLICATION_JSON)
+					.header(BenchmarkUtils.Header.SERVER, "Pippo")
+					.getOutputStream()
+			);
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static final DslJson<?> JsonMapper = new DslJson<>();
+
+}

+ 66 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/handler/Test2SingleQueryHandler.java

@@ -0,0 +1,66 @@
+package com.techempower.benchmark.pippo.handler;
+
+import com.dslplatform.json.DslJson;
+import com.techempower.benchmark.pippo.BenchmarkUtils;
+import com.techempower.benchmark.pippo.dao.Dao;
+import com.techempower.benchmark.pippo.model.World;
+import ro.pippo.core.HttpConstants;
+import ro.pippo.core.route.RouteContext;
+import ro.pippo.core.route.RouteHandler;
+
+import java.io.IOException;
+
+/**
+ * <p>Test type 2: Single database query</p>
+ *
+ * <p>Example request:</p>
+ *
+ * <pre>
+ * GET /db HTTP/1.1
+ * Host: server
+ * User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
+ * Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+ * Accept: text/html,application/xhtml+xml,application/xml;q=0.9,"*&#47;"/"*&#47;";q=0.8
+ * Accept-Language: en-US,en;q=0.5
+ * Connection: keep-alive
+ * </pre>
+ *
+ * <p>Example response:</p>
+ *
+ * <pre>
+ * HTTP/1.1 200 OK
+ * Content-Length: 32
+ * Content-Type: application/json
+ * Server: Example
+ * Date: Wed, 17 Apr 2013 12:00:00 GMT
+ *
+ * {"id":3217,"randomNumber":2149}
+ * </pre>
+ */
+public class Test2SingleQueryHandler implements RouteHandler<RouteContext> {
+
+	public Test2SingleQueryHandler(Dao dao) {
+		this.dao = dao;
+	}
+
+	@Override
+	public void handle(RouteContext routeContext) {
+		try {
+			WorldJsonMapper.serialize(
+				dao.getRandomWorld(),
+				routeContext
+					.getResponse()
+					.contentType(HttpConstants.ContentType.APPLICATION_JSON)
+					.header(BenchmarkUtils.Header.SERVER, "Pippo")
+					.getOutputStream()
+			);
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static final DslJson<World> WorldJsonMapper = new DslJson<>();
+
+	private final Dao dao;
+
+}

+ 79 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/handler/Test3MultiQueryHandler.java

@@ -0,0 +1,79 @@
+package com.techempower.benchmark.pippo.handler;
+
+import com.dslplatform.json.DslJson;
+import com.techempower.benchmark.pippo.BenchmarkUtils;
+import com.techempower.benchmark.pippo.dao.Dao;
+import com.techempower.benchmark.pippo.model.World;
+import ro.pippo.core.HttpConstants;
+import ro.pippo.core.route.RouteContext;
+import ro.pippo.core.route.RouteHandler;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>Test type 3: Multiple database queries</p>
+ *
+ * <p>Example request:</p>
+ *
+ * <pre>
+ * GET /queries?queries=10 HTTP/1.1
+ * Host: server
+ * User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
+ * Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+ * Accept: text/html,application/xhtml+xml,application/xml;q=0.9,"*&#47;"/"*&#47;";q=0.8
+ * Accept-Language: en-US,en;q=0.5
+ * Connection: keep-alive
+ * </pre>
+ *
+ * <p>Example response:</p>
+ *
+ * <pre>
+ * HTTP/1.1 200 OK
+ * Content-Length: 315
+ * Content-Type: application/json
+ * Server: Example
+ * Date: Wed, 17 Apr 2013 12:00:00 GMT
+ *
+ * [{"id":4174,"randomNumber":331},{"id":51,"randomNumber":6544},
+ * {"id":4462,"randomNumber":952},{"id":2221,"randomNumber":532},
+ * {"id":9276,"randomNumber":3097},{"id":3056,"randomNumber":7293},
+ * {"id":6964,"randomNumber":620},{"id":675,"randomNumber":6601},
+ * {"id":8414,"randomNumber":6569},{"id":2753,"randomNumber":4065}]
+ * </pre>
+ */
+public class Test3MultiQueryHandler implements RouteHandler<RouteContext> {
+
+	public Test3MultiQueryHandler(Dao dao) {
+		this.dao = dao;
+	}
+
+	@Override public void handle(RouteContext routeContext) {
+
+		int queries = BenchmarkUtils.getQueriesParam(routeContext);
+
+		List<World> worlds = new ArrayList<>(queries);
+		for (int i = 0; i < queries; i++)
+			worlds.add(i, dao.getRandomWorld());
+
+		try {
+			WorldJsonMapper.serialize(
+				worlds,
+				routeContext
+					.getResponse()
+					.contentType(HttpConstants.ContentType.APPLICATION_JSON)
+					.header(BenchmarkUtils.Header.SERVER, "Pippo")
+					.getOutputStream()
+			);
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+
+	}
+
+	private static final DslJson<World> WorldJsonMapper = new DslJson<>();
+
+	private final Dao dao;
+
+}

+ 128 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/handler/Test4FortuneHandler.java

@@ -0,0 +1,128 @@
+package com.techempower.benchmark.pippo.handler;
+
+import com.techempower.benchmark.pippo.BenchmarkUtils;
+import com.techempower.benchmark.pippo.dao.Dao;
+import com.techempower.benchmark.pippo.model.Fortune;
+import ro.pippo.core.route.RouteContext;
+import ro.pippo.core.route.RouteHandler;
+
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * <p>Test type 4: Fortunes</p>
+ *
+ * <p>Example request:</p>
+ *
+ * <pre>
+ * GET /fortunes HTTP/1.1
+ * Host: server
+ * User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
+ * Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+ * Accept: text/html,application/xhtml+xml,application/xml;q=0.9,"*&#47;"/"*&#47;";q=0.8
+ * Accept-Language: en-US,en;q=0.5
+ * Connection: keep-alive
+ * </pre>
+ *
+ * <p>Example response:</p>
+ *
+ * <pre>
+ * HTTP/1.1 200 OK
+ * Content-Length: 1196
+ * Content-Type: text/html; charset=UTF-8
+ * Server: Example
+ * Date: Wed, 17 Apr 2013 12:00:00 GMT
+ *
+ * <!DOCTYPE html>
+ * <html>
+ *   <head>
+ *     <title>Fortunes</title>
+ *   </head>
+ *   <body>
+ *     <table>
+ *       <tr>
+ *         <th>id</th>
+ *         <th>message</th>
+ *       </tr>
+ *       <tr>
+ *         <td>11</td>
+ *         <td>&lt;script&gt;alert(&quot;This should not be displayed in a browser alert
+ * box.&quot;);&lt;/script&gt;</td>
+ *       </tr>
+ *       <tr>
+ *         <td>4</td>
+ *         <td>A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1</td>
+ *       </tr>
+ *       <tr>
+ *         <td>5</td>
+ *         <td>A computer program does what you tell it to do, not what you want it to do.</td>
+ *       </tr>
+ *       <tr>
+ *         <td>2</td>
+ *         <td>A computer scientist is someone who fixes things that aren&apos;t broken.</td>
+ *       </tr>
+ *       <tr>
+ *         <td>8</td>
+ *         <td>A list is only as strong as its weakest link. — Donald Knuth</td>
+ *       </tr>
+ *       <tr>
+ *         <td>0</td>
+ *         <td>Additional fortune added at request time.</td>
+ *       </tr>
+ *       <tr>
+ *         <td>3</td>
+ *         <td>After enough decimal places, nobody gives a damn.</td>
+ *       </tr>
+ *       <tr>
+ *         <td>7</td>
+ *         <td>Any program that runs right is obsolete.</td>
+ *       </tr>
+ *       <tr>
+ *         <td>10</td>
+ *         <td>Computers make very fast, very accurate mistakes.</td>
+ *       </tr>
+ *       <tr>
+ *         <td>6</td>
+ *         <td>Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen</td>
+ *       </tr>
+ *       <tr>
+ *         <td>9</td>
+ *         <td>Feature: A bug with seniority.</td>
+ *       </tr>
+ *       <tr>
+ *         <td>1</td>
+ *         <td>fortune: No such file or directory</td>
+ *       </tr>
+ *       <tr>
+ *         <td>12</td>
+ *         <td>フレームワークのベンチマーク</td>
+ *       </tr>
+ *     </table>
+ *   </body>
+ * </html>
+ * </pre>
+ */
+public class Test4FortuneHandler implements RouteHandler<RouteContext> {
+
+	public Test4FortuneHandler(Dao dao) {
+		this.dao = dao;
+	}
+
+	@Override
+	public void handle(RouteContext routeContext) {
+
+		List<Fortune> fortunes = dao.getFortunes();
+		fortunes.add(new Fortune(0, "Additional fortune added at request time."));
+		fortunes.sort(Comparator.comparing(o -> o.message));
+
+		routeContext.setLocal("fortunes", fortunes);
+		routeContext
+			.getResponse()
+			.header(BenchmarkUtils.Header.SERVER, "Pippo")
+			.render("fortune");
+
+	}
+
+	private final Dao dao;
+
+}

+ 87 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/handler/Test5UpdateHandler.java

@@ -0,0 +1,87 @@
+package com.techempower.benchmark.pippo.handler;
+
+import com.dslplatform.json.DslJson;
+import com.techempower.benchmark.pippo.BenchmarkUtils;
+import com.techempower.benchmark.pippo.dao.Dao;
+import com.techempower.benchmark.pippo.model.World;
+import ro.pippo.core.HttpConstants;
+import ro.pippo.core.route.RouteContext;
+import ro.pippo.core.route.RouteHandler;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * <p>Test type 5: Database updates</p>
+ *
+ * <p>Example request:</p>
+ *
+ * <pre>
+ * GET /updates?queries=10 HTTP/1.1
+ * Host: server
+ * User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
+ * Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+ * Accept: text/html,application/xhtml+xml,application/xml;q=0.9,"*&#47;"/"*&#47;";q=0.8
+ * Accept-Language: en-US,en;q=0.5
+ * Connection: keep-alive
+ * </pre>
+ *
+ * <p>Example response:</p>
+ *
+ * <pre>
+ * HTTP/1.1 200 OK
+ * Content-Length: 315
+ * Content-Type: application/json
+ * Server: Example
+ * Date: Wed, 17 Apr 2013 12:00:00 GMT
+ *
+ * [{"id":4174,"randomNumber":331},{"id":51,"randomNumber":6544},
+ * {"id":4462,"randomNumber":952},{"id":2221,"randomNumber":532},
+ * {"id":9276,"randomNumber":3097},{"id":3056,"randomNumber":7293},
+ * {"id":6964,"randomNumber":620},{"id":675,"randomNumber":6601},
+ * {"id":8414,"randomNumber":6569},{"id":2753,"randomNumber":4065}]
+ * </pre>
+ */
+public class Test5UpdateHandler implements RouteHandler<RouteContext> {
+
+	public Test5UpdateHandler(Dao dao) {
+		this.dao = dao;
+	}
+
+	@Override
+	public void handle(RouteContext routeContext) {
+
+		int queries = BenchmarkUtils.getQueriesParam(routeContext);
+
+		List<World> worlds = dao.getRandomWorlds(queries);
+
+		for (World world : worlds) {
+			int newRandomNumber;
+			do {
+				newRandomNumber = BenchmarkUtils.random();
+			} while (newRandomNumber == world.randomNumber);
+			world.randomNumber = newRandomNumber;
+		}
+
+		dao.updateRandomWorlds(worlds);
+
+		try {
+			WorldJsonMapper.serialize(
+				worlds,
+				routeContext
+					.getResponse()
+					.contentType(HttpConstants.ContentType.APPLICATION_JSON)
+					.header(BenchmarkUtils.Header.SERVER, "Pippo")
+					.getOutputStream()
+			);
+		} catch (IOException e) {
+			throw new RuntimeException(e);
+		}
+
+	}
+
+	private static final DslJson<World> WorldJsonMapper = new DslJson<>();
+
+	private final Dao dao;
+
+}

+ 48 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/handler/Test6PlainTextHandler.java

@@ -0,0 +1,48 @@
+package com.techempower.benchmark.pippo.handler;
+
+import com.techempower.benchmark.pippo.BenchmarkUtils;
+import ro.pippo.core.HttpConstants;
+import ro.pippo.core.route.RouteContext;
+import ro.pippo.core.route.RouteHandler;
+
+/**
+ * <p>Test type 6: Plaintext</p>
+ *
+ * <p>Example request:</p>
+ *
+ * <pre>
+ * GET /plaintext HTTP/1.1
+ * Host: server
+ * User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
+ * Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
+ * Accept: text/html,application/xhtml+xml,application/xml;q=0.9,"*&#47;"/"*&#47;";q=0.8
+ * Accept-Language: en-US,en;q=0.5
+ * Connection: keep-alive
+ * </pre>
+ *
+ * <p>Example response:</p>
+ *
+ * <pre>
+ * HTTP/1.1 200 OK
+ * Content-Length: 15
+ * Content-Type: text/plain; charset=UTF-8
+ * Server: Example
+ * Date: Wed, 17 Apr 2013 12:00:00 GMT
+ *
+ * Hello, World!
+ * </pre>
+ */
+public class Test6PlainTextHandler implements RouteHandler<RouteContext> {
+
+	public static final String Message = "Hello, World!";
+
+	@Override
+	public void handle(RouteContext routeContext) {
+		routeContext
+			.getResponse()
+			.contentType(HttpConstants.ContentType.TEXT_PLAIN)
+			.header(BenchmarkUtils.Header.SERVER, "Pippo")
+			.send(Message);
+	}
+
+}

+ 16 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/model/Fortune.java

@@ -0,0 +1,16 @@
+package com.techempower.benchmark.pippo.model;
+
+import com.dslplatform.json.CompiledJson;
+
+@CompiledJson
+public class Fortune {
+
+	public Fortune(int id, String message) {
+		this.id = id;
+		this.message = message;
+	}
+
+	public int id;
+	public String message;
+
+}

+ 16 - 0
frameworks/Java/pippo/src/main/java/com/techempower/benchmark/pippo/model/World.java

@@ -0,0 +1,16 @@
+package com.techempower.benchmark.pippo.model;
+
+import com.dslplatform.json.CompiledJson;
+
+@CompiledJson
+public class World {
+
+	public World(int id, int randomNumber) {
+		this.id = id;
+		this.randomNumber = randomNumber;
+	}
+
+	public int id;
+	public int randomNumber;
+
+}

+ 9 - 0
frameworks/Java/pippo/src/main/resources/conf/application.properties

@@ -0,0 +1,9 @@
+server.port=8080
+server.host=0.0.0.0
+
+jetty.maxThreads=250
+jetty.minThreads=8
+
+tomcat.maxConnections=250
+
+template.pathPrefix=templates

+ 6 - 0
frameworks/Java/pippo/src/main/resources/hikari-mysql.properties

@@ -0,0 +1,6 @@
+dataSourceClassName=com.mysql.cj.jdbc.MysqlDataSource
+dataSource.user=benchmarkdbuser
+dataSource.password=benchmarkdbpass
+dataSource.databaseName=hello_world
+dataSource.portNumber=3306
+dataSource.serverName=tfb-database

+ 6 - 0
frameworks/Java/pippo/src/main/resources/hikari-postgresql.properties

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

+ 22 - 0
frameworks/Java/pippo/src/main/resources/logback.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE configuration>
+
+<configuration>
+
+	<contextName>Pippo</contextName>
+
+	<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{20}:%line - %msg%n</pattern>
+		</encoder>
+	</appender>
+
+	<logger name="com.techempower.benchmark.pippo" additivity="false" level="INFO">
+		<appender-ref ref="STDOUT"/>
+	</logger>
+
+	<root level="WARN">
+		<appender-ref ref="STDOUT"/>
+	</root>
+
+</configuration>

+ 12 - 0
frameworks/Java/pippo/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>

+ 164 - 0
frameworks/Java/pippo/src/test/java/com/techempower/benchmark/pippo/BenchmarkTests.java

@@ -0,0 +1,164 @@
+package com.techempower.benchmark.pippo;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+
+import com.techempower.benchmark.pippo.benchmark.Benchmark;
+import com.techempower.benchmark.pippo.handler.Test6PlainTextHandler;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import ro.pippo.core.AbstractWebServer;
+import ro.pippo.core.HttpConstants;
+import ro.pippo.jetty.JettyServer;
+import ro.pippo.tomcat.TomcatServer;
+import ro.pippo.undertow.UndertowServer;
+
+import static ro.pippo.core.HttpConstants.Header.CONTENT_TYPE;
+
+@RunWith(Parameterized.class)
+public class BenchmarkTests {
+
+	@Parameterized.Parameters
+	public static Collection<Object[]> data() {
+		return Arrays.asList(new Object[][] {
+			{ new JettyServer() },
+			{ new UndertowServer() },
+			{ new TomcatServer() },	// keep it as last parameter, since Tomcat doesn't stop gracefully and locks port 8080
+		});
+	}
+
+	@Parameterized.BeforeParam
+	public static void beforeParameter(AbstractWebServer<?> server) throws InterruptedException {
+
+		benchmark = new Benchmark()
+						.server(server)
+						.serverName(server.getClass().getSimpleName())
+						.start();
+
+		client = new OkHttpClient();
+
+		Thread.sleep(2000);
+
+	}
+
+	@Parameterized.AfterParam
+	public static void afterParameter() {
+
+		client.connectionPool().evictAll();
+
+		benchmark.stop();
+
+	}
+
+	@Test
+	public void test1() throws IOException {
+		assertJson(execute("/json"));
+	}
+
+	@Test
+	public void test2Postgres() throws IOException { test2(DbPostgres); }
+	@Test
+	public void test2Mysql() throws IOException { test2(DbMysql); }
+	@Test
+	public void test2Mongo() throws IOException { test2(DbMongo); }
+
+	private void test2(String db) throws IOException {
+		assertJson(execute(String.format("/%s/db", db)));
+	}
+
+	@Test
+	public void test3Postgres() throws IOException { test3(DbPostgres); }
+	@Test
+	public void test3Mysql() throws IOException { test3(DbMysql); }
+	@Test
+	public void test3Mongo() throws IOException { test3(DbMongo); }
+
+	private void test3(String db) throws IOException {
+		assertJson(execute(String.format("/%s/queries", db)));
+		assertJson(execute(String.format("/%s/queries?queries=-1", db)));
+		assertJson(execute(String.format("/%s/queries?queries=ABC", db)));
+		assertJson(execute(String.format("/%s/queries?queries=600", db)));
+	}
+
+	@Test
+	public void test4Postgres() throws IOException { test4(DbPostgres); }
+	@Test
+	public void test4Mysql() throws IOException { test4(DbMysql); }
+	@Test
+	public void test4Mongo() throws IOException { test4(DbMongo); }
+
+	private void test4(String db) throws IOException {
+		assertHtml(execute(String.format("/%s/fortunes", db)));
+		assertHtml(execute(String.format("/%s/fortunes", db)));
+		assertHtml(execute(String.format("/%s/fortunes", db)));
+	}
+
+	@Test
+	public void test5Postgres() throws IOException { test5(DbPostgres); }
+	@Test
+	public void test5Mysql() throws IOException { test5(DbMysql); }
+	@Test
+	public void test5Mongo() throws IOException { test5(DbMongo); }
+
+	private void test5(String db) throws IOException {
+		assertJson(execute(String.format("/%s/updates", db)));
+		assertJson(execute(String.format("/%s/updates?queries=-1", db)));
+		assertJson(execute(String.format("/%s/updates?queries=ABC", db)));
+		assertJson(execute(String.format("/%s/updates?queries=600", db)));
+	}
+
+	@Test
+	public void test6() throws IOException {
+		assertHelloWorld(execute("/plaintext"));
+	}
+
+	private Response execute(String uri) throws IOException {
+		Request request = new Request.Builder()
+							  .url("http://localhost:8080" + uri)
+							  .build();
+		return client.newCall(request).execute();
+	}
+
+	private void assertHelloWorld(Response response) throws IOException {
+		Assert.assertEquals("HTTP response code error", 200, response.code());
+		Assert.assertTrue("Wrong content type: " + response.header(CONTENT_TYPE), response.header(CONTENT_TYPE).contains(HttpConstants.ContentType.TEXT_PLAIN));
+		Assert.assertFalse("'Server' HTTP response header missing", StringUtils.isBlank(response.header(BenchmarkUtils.Header.SERVER)));
+		Assert.assertFalse("'Date' HTTP response header missing", StringUtils.isBlank(response.header(HttpConstants.Header.DATE)));
+		Assert.assertEquals(Test6PlainTextHandler.Message, response.body().string());
+		response.close();
+	}
+
+	private void assertJson(Response response) {
+		Assert.assertEquals("HTTP response code error", 200, response.code());
+		Assert.assertTrue("Wrong content type: " + response.header(CONTENT_TYPE), response.header(CONTENT_TYPE).contains(HttpConstants.ContentType.APPLICATION_JSON));
+		Assert.assertFalse("'Server' HTTP response header missing", StringUtils.isBlank(response.header(BenchmarkUtils.Header.SERVER)));
+		Assert.assertFalse("'Date' HTTP response header missing", StringUtils.isBlank(response.header(HttpConstants.Header.DATE)));
+		response.close();
+	}
+
+	private void assertHtml(Response response) {
+		Assert.assertEquals("HTTP response code error", 200, response.code());
+		Assert.assertTrue("Wrong content type: " + response.header(CONTENT_TYPE), response.header(CONTENT_TYPE).contains(HttpConstants.ContentType.TEXT_HTML));
+		Assert.assertFalse("'Server' HTTP response header missing", StringUtils.isBlank(response.header(BenchmarkUtils.Header.SERVER)));
+		Assert.assertFalse("'Date' HTTP response header missing", StringUtils.isBlank(response.header(HttpConstants.Header.DATE)));
+		response.close();
+	}
+
+	private static final String DbPostgres = "postgres";
+	private static final String DbMysql = "mysql";
+	private static final String DbMongo = "mongo";
+
+	@Parameterized.Parameter()
+	public static AbstractWebServer<?> server;
+
+	private static Benchmark benchmark;
+	private static OkHttpClient client;
+
+}