Browse Source

Merge branch 'unfiltered' of https://github.com/pakunoda/FrameworkBenchmarks into pakunoda-unfiltered

Patrick Falls 12 years ago
parent
commit
83d1e3521e

+ 26 - 0
unfiltered/README.md

@@ -0,0 +1,26 @@
+#Unfiltered Benchmarking Test
+
+This is the Unfiltered portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+### JSON Encoding Test
+
+* [JSON test source](src/main/scala/Plans.scala)
+
+### Data-Store/Database Mapping Test
+
+* [Database test controller and model](src/main/scala/Plans.scala)
+
+## Infrastructure Software Versions
+The tests were run with:
+
+* [Java OpenJDK 1.7.0_09](http://openjdk.java.net/)
+* [Unfiltered 0.6.8](http://unfiltered.databinder.net/Unfiltered.html)
+
+## Test URLs
+### JSON Encoding Test
+
+http://localhost/json
+
+### Data-Store/Database Mapping Test
+
+http://localhost/db?queries=5

+ 13 - 0
unfiltered/benchmark_config

@@ -0,0 +1,13 @@
+{
+  "framework": "unfiltered",
+  "tests": [{
+    "default": {
+      "setup_file": "setup_unfiltered",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/db?queries=",
+      "port": 9000,
+      "sort": 16
+    }
+  }]
+}

+ 28 - 0
unfiltered/project/Build.scala

@@ -0,0 +1,28 @@
+import sbt._
+import Keys._
+import sbtassembly.Plugin._
+import AssemblyKeys._
+
+object Bench extends Build {
+  lazy val project = Project(
+    "bench", 
+    file("."),
+    settings = Defaults.defaultSettings ++ assemblySettings ++ Seq(
+      scalaVersion := "2.10.1",
+      version := "1.0.0",
+      name := "bench",
+      libraryDependencies ++= Seq(
+        "net.databinder" %% "unfiltered-netty-server" % "0.6.8",
+        "net.databinder.dispatch" %% "dispatch-core" % "0.9.5",
+        "net.databinder" %% "unfiltered-json4s" % "0.6.8",
+        "net.databinder" %% "unfiltered-spec" % "0.6.8" % "test",
+        "org.clapper" %% "avsl" % "1.0.1",
+        "org.json4s" %% "json4s-jackson" % "3.2.3",
+        "com.typesafe.slick" %% "slick" % "1.0.0",
+        "mysql" % "mysql-connector-java" % "5.1.24",
+        "com.jolbox" % "bonecp" % "0.7.1.RELEASE",
+        "com.typesafe" % "config" % "1.0.0"
+      )
+    )
+  )
+}

+ 1 - 0
unfiltered/project/plugins.sbt

@@ -0,0 +1 @@
+addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.8.7")

+ 1 - 0
unfiltered/sbt

@@ -0,0 +1 @@
+java -Xms512M -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=384M -jar `dirname $0`/sbt-launch.jar "$@"

+ 159 - 0
unfiltered/sbt-launch-lib.bash

@@ -0,0 +1,159 @@
+#!/usr/bin/env bash
+#
+
+# A library to simplify using the SBT launcher from other packages.
+# Note: This should be used by tools like giter8/conscript etc.
+
+# TODO - Should we merge the main SBT script with this library?
+
+if test -z "$HOME"; then
+  declare -r script_dir="$(dirname $script_path)"
+else
+  declare -r script_dir="$HOME/.sbt"
+fi
+
+declare -a residual_args
+declare -a java_args
+declare -a scalac_args
+declare -a sbt_commands
+declare java_cmd=java
+
+echoerr () {
+  echo 1>&2 "$@"
+}
+vlog () {
+  [[ $verbose || $debug ]] && echoerr "$@"
+}
+dlog () {
+  [[ $debug ]] && echoerr "$@"
+}
+
+jar_file () {
+  echo "$(dirname $(realpath $0))/sbt-launch.jar"
+}
+
+acquire_sbt_jar () {
+  sbt_jar="$(jar_file)"
+
+  if [[ ! -f "$sbt_jar" ]]; then
+    echoerr "Could not find launcher jar: $sbt_jar"
+    exit 2
+  fi
+}
+
+execRunner () {
+  # print the arguments one to a line, quoting any containing spaces
+  [[ $verbose || $debug ]] && echo "# Executing command line:" && {
+    for arg; do
+      if printf "%s\n" "$arg" | grep -q ' '; then
+        printf "\"%s\"\n" "$arg"
+      else
+        printf "%s\n" "$arg"
+      fi
+    done
+    echo ""
+  }
+
+  exec "$@"
+}
+
+addJava () {
+  dlog "[addJava] arg = '$1'"
+  java_args=( "${java_args[@]}" "$1" )
+}
+addSbt () {
+  dlog "[addSbt] arg = '$1'"
+  sbt_commands=( "${sbt_commands[@]}" "$1" )
+}
+addResidual () {
+  dlog "[residual] arg = '$1'"
+  residual_args=( "${residual_args[@]}" "$1" )
+}
+addDebugger () {
+  addJava "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1"
+}
+
+# a ham-fisted attempt to move some memory settings in concert
+# so they need not be dicked around with individually.
+get_mem_opts () {
+  local mem=${1:-1536}
+  local perm=$(( $mem / 4 ))
+  (( $perm > 256 )) || perm=256
+  (( $perm < 1024 )) || perm=1024
+  local codecache=$(( $perm / 2 ))
+
+  echo "-Xms${mem}m -Xmx${mem}m -XX:MaxPermSize=${perm}m -XX:ReservedCodeCacheSize=${codecache}m"
+}
+
+require_arg () {
+  local type="$1"
+  local opt="$2"
+  local arg="$3"
+  if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then
+    die "$opt requires <$type> argument"
+  fi
+}
+
+is_function_defined() {
+  declare -f "$1" > /dev/null
+}
+
+process_args () {
+  while [[ $# -gt 0 ]]; do
+    case "$1" in
+       -h|-help) usage; exit 1 ;;
+    -v|-verbose) verbose=1 && shift ;;
+      -d|-debug) debug=1 && shift ;;
+
+           -ivy) require_arg path "$1" "$2" && addJava "-Dsbt.ivy.home=$2" && shift 2 ;;
+           -mem) require_arg integer "$1" "$2" && sbt_mem="$2" && shift 2 ;;
+     -jvm-debug) require_arg port "$1" "$2" && addDebugger $2 && shift 2 ;;
+         -batch) exec </dev/null && shift ;;
+
+       -sbt-jar) require_arg path "$1" "$2" && sbt_jar="$2" && shift 2 ;;
+   -sbt-version) require_arg version "$1" "$2" && sbt_version="$2" && shift 2 ;;
+     -java-home) require_arg path "$1" "$2" && java_cmd="$2/bin/java" && shift 2 ;;
+
+            -D*) addJava "$1" && shift ;;
+            -J*) addJava "${1:2}" && shift ;;
+              *) addResidual "$1" && shift ;;
+    esac
+  done
+  
+  is_function_defined process_my_args && {
+    myargs=("${residual_args[@]}")
+    residual_args=()
+    process_my_args "${myargs[@]}"
+  }
+}
+
+run() {
+  # no jar? download it.
+  [[ -f "$sbt_jar" ]] || acquire_sbt_jar "$sbt_version" || {
+    # still no jar? uh-oh.
+    echo "Download failed. Obtain the sbt-launch.jar manually and place it at $sbt_jar"
+    exit 1
+  }
+
+  # process the combined args, then reset "$@" to the residuals
+  process_args "$@"
+  set -- "${residual_args[@]}"
+  argumentCount=$#
+
+  # run sbt
+  execRunner "$java_cmd" \
+    ${SBT_OPTS:-$default_sbt_opts} \
+    $(get_mem_opts $sbt_mem) \
+    ${java_opts} \
+    ${java_args[@]} \
+    -jar "$sbt_jar" \
+    "${sbt_commands[@]}" \
+    "${residual_args[@]}"
+}
+
+runAlternateBoot() {
+  local bootpropsfile="$1"
+  shift
+  addJava "-Dsbt.boot.properties=$bootpropsfile"
+  run $@
+}

BIN
unfiltered/sbt-launch.jar


+ 31 - 0
unfiltered/setup_unfiltered.py

@@ -0,0 +1,31 @@
+
+import subprocess
+import sys
+import setup_util
+import os
+
+def start(args):
+  setup_util.replace_text("unfiltered/src/main/resources/application.conf", "jdbc:mysql:\/\/.*:3306", "jdbc:mysql://" + args.database_host + ":3306")
+  setup_util.replace_text("unfiltered/src/main/resources/application.conf", "maxThreads = \\d+", "maxThreads = " + str(args.max_threads))
+
+  # Shamelessly stolen from stack overflow
+  try:
+    from subprocess import DEVNULL
+  except ImportError:
+    import os
+    DEVNULL = open(os.devnull, 'wb')
+
+  subprocess.check_call("chmod u+x sbt", shell=True, cwd="unfiltered")
+  subprocess.check_call("./sbt assembly", shell=True, cwd="unfiltered")
+  subprocess.Popen("java -jar bench-assembly-1.0.0.jar", stderr=DEVNULL, shell=True, cwd="unfiltered/target/scala-2.10")
+
+  return 0
+def stop():
+  p = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
+  out, err = p.communicate()
+  for line in out.splitlines():
+    if 'bench-assembly' in line or 'java' in line:
+      pid = int(line.split(None, 2)[1])
+      os.kill(pid, 9)
+
+  return 0

+ 8 - 0
unfiltered/src/main/resources/application.conf

@@ -0,0 +1,8 @@
+db.default.driver=""com.mysql.jdbc.Driver""
+db.default.url = "jdbc:mysql://localhost:3306/hello_world"
+db.default.user = "benchmarkdbuser"
+db.default.password = "benchmarkdbpass"
+db.default.minConnections = 4
+db.default.maxConnections = 125
+
+unfiltered.maxThreads = 16

+ 46 - 0
unfiltered/src/main/scala/DatabaseAccess.scala

@@ -0,0 +1,46 @@
+package bench
+
+import scala.collection.immutable.Map
+import scala.util.Try
+import scala.slick.driver.SQLServerDriver.simple._
+import java.util.Map.Entry
+import com.typesafe.config._
+import com.jolbox.bonecp.{ BoneCP, BoneCPConfig, BoneCPDataSource }
+
+object DatabaseAccess {
+	var configs: Map[String, BoneCPConfig] = Map[String, BoneCPConfig]()
+	var dataSources: Map[String, BoneCPDataSource] = Map[String, BoneCPDataSource]()
+	var databases: Map[String, Database] = dataSources.map { case(key, value) => (key, Database.forDataSource(value)) }
+  var maxThreads: Int = 16 // shoehorning this in
+
+  /** Loads the configuration given.  Usually loaded from application.conf in class path.
+   *
+   * @param config the configuration to use
+   */
+  def loadConfiguration(config: Config) {
+    val keys = Seq(config.getObject("db").toConfig.entrySet.toArray: _*)
+    val entries = keys.map("db." + _.asInstanceOf[Entry[String, Object]].getKey)
+    val driverStrings = entries.filter(_.contains(".driver")).distinct
+    val urls = entries.filter(_.contains(".url"))
+
+    /* Load class drivers */
+    for (driverString <- driverStrings) Class.forName(config.getString(driverString))
+
+    /* Load config */
+    DatabaseAccess.configs = (for (url <- urls) yield {
+      /* Keys should be in the format db.key.url */
+      val key = url.split('.').init.mkString(".") // db.key.url becomes db.key
+      val boneCPConfig = new BoneCPConfig()
+      boneCPConfig.setJdbcUrl(config.getString(url))
+      boneCPConfig.setMinConnectionsPerPartition(config.getInt(key + ".minConnections"))
+      boneCPConfig.setMaxConnectionsPerPartition(config.getInt(key + ".maxConnections"))
+      boneCPConfig.setUsername(config.getString(key + ".user"))
+      boneCPConfig.setPassword(config.getString(key + ".password"))
+      boneCPConfig.setPartitionCount(2)
+      (key, boneCPConfig)
+    }).toMap
+    DatabaseAccess.dataSources = DatabaseAccess.configs.map { case(key, value) => (key, new BoneCPDataSource(value)) }
+    databases = dataSources.map { case(key, value) => (key, Database.forDataSource(value)) }
+    maxThreads = Try { config.getString("unfiltered.maxThreads").toInt }.getOrElse(16) * 2
+  }
+}

+ 50 - 0
unfiltered/src/main/scala/Plans.scala

@@ -0,0 +1,50 @@
+package bench
+
+import unfiltered.request._
+import unfiltered.response._
+import unfiltered.netty._
+import org.json4s._
+import org.json4s.jackson.JsonMethods._
+import org.json4s.jackson.Serialization
+import org.json4s.jackson.Serialization.{read, write}
+import org.json4s.JsonDSL.WithBigDecimal._
+import scala.util.Random
+import scala.slick.driver.MySQLDriver.simple._
+import scala.slick.jdbc.{ GetResult, StaticQuery => Q }
+import java.util.concurrent.ThreadLocalRandom
+
+case class World(id: Long, randomNumber: Long)
+
+/** unfiltered plan */
+object Plans extends cycle.Plan
+  with cycle.DeferralExecutor with cycle.DeferredIntent with ServerErrorResponse {
+
+  private val TEST_DATABASE_ROWS = 9999
+  implicit val getWorld = GetResult(r => World(r.<<, r.<<))
+  implicit val formats = Serialization.formats(NoTypeHints)
+  val db = DatabaseAccess.databases("db.default")
+
+  def intent = {
+    case GET(Path("/json")) => JsonContent ~> ResponseString(compact(render("message" -> "Hello world")))
+    case GET(Path("/db") & Params(params)) =>
+      val random = ThreadLocalRandom.current()
+      val queries = params.get("queries").flatMap(_.headOption).getOrElse("1").toInt
+      JsonContent ~> ResponseString(
+        write(
+          db.withSession { implicit session: Session =>
+            (0 until queries).map { _ =>
+              (Q[Int, World] + "select id, randomNumber from World where id = ?")(random.nextInt(TEST_DATABASE_ROWS) + 1).first
+            }
+          }
+        )
+      )
+  }
+  def underlying = CustomExecutor.underlying
+}
+
+object CustomExecutor {
+  import org.jboss.netty.handler.execution._
+  lazy val underlying = new MemoryAwareThreadPoolExecutor(
+    DatabaseAccess.maxThreads, 536870912, 536870912
+  )
+}

+ 20 - 0
unfiltered/src/main/scala/Server.scala

@@ -0,0 +1,20 @@
+package bench
+
+import com.typesafe.config.{ ConfigFactory, Config }
+
+object Server {
+  val logger = org.clapper.avsl.Logger(Server.getClass)
+
+  def main(args: Array[String]) {
+    val config = ConfigFactory.load()
+    DatabaseAccess.loadConfiguration(config)
+
+    unfiltered.netty.Http(9000)
+      .handler(Plans)
+      .run { s =>
+        logger.info("starting unfiltered app at localhost on port %s".format(s.port))
+      }
+
+    dispatch.Http.shutdown()
+  }
+}