Browse Source

Merge branch 'adding_missing_slickness' of https://github.com/nraychaudhuri/FrameworkBenchmarks into nraychaudhuri-adding_missing_slickness

Patrick Falls 12 years ago
parent
commit
6e1cfc704e

+ 33 - 0
play-slick/.gitignore

@@ -0,0 +1,33 @@
+logs
+project/project
+project/target
+public
+target
+test
+tmp
+.history
+dist
+conf/evolutions
+
+# Ignore all dotfiles...
+.*
+# except for .gitignore
+!.gitignore
+
+# Ignore Play! working directory #
+db
+eclipse
+lib
+log
+logs
+modules
+precompiled
+project/project
+project/target
+target
+tmp
+test-result
+server.pid
+*.iml
+*.eml
+

+ 27 - 0
play-slick/README.md

@@ -0,0 +1,27 @@
+#Play Benchmarking Test
+
+This is the Play portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+### JSON Encoding Test
+
+* [JSON test source](app/controllers/Application.scala)
+
+### Data-Store/Database Mapping Test
+
+* [Database test controller](app/controllers/Application.scala)
+* [Database test model](app/models/World.scala)
+
+## Infrastructure Software Versions
+The tests were run with:
+
+* [Java OpenJDK 1.7.0_09](http://openjdk.java.net/)
+* [Play 2.1.0](http://http://www.playframework.com/)
+
+## Test URLs
+### JSON Encoding Test
+
+http://localhost/json
+
+### Data-Store/Database Mapping Test
+
+http://localhost/db?queries=5

+ 0 - 0
play-slick/__init__.py


+ 100 - 0
play-slick/app/controllers/Application.scala

@@ -0,0 +1,100 @@
+package controllers
+
+import play.api.Play.current
+import play.api.mvc._
+import play.api.libs.json.Json
+import java.util.concurrent._
+import scala.concurrent._
+import models.{Worlds, World, Fortunes, Fortune}
+import utils._
+import scala.concurrent.Future
+
+import play.api.libs.concurrent.Execution.Implicits._
+import play.core.NamedThreadFactory
+
+object Application extends Controller {
+
+  private val MaxQueriesPerRequest = 20
+  private val TestDatabaseRows = 10000
+
+  private val partitionCount = current.configuration.getInt("db.default.partitionCount").getOrElse(2)
+  private val maxConnections =
+    partitionCount * current.configuration.getInt("db.default.maxConnectionsPerPartition").getOrElse(5)
+  private val minConnections =
+    partitionCount * current.configuration.getInt("db.default.minConnectionsPerPartition").getOrElse(5)
+
+  private val tpe = new ThreadPoolExecutor(minConnections, maxConnections,
+    0L, TimeUnit.MILLISECONDS,
+    new LinkedBlockingQueue[Runnable](),
+    new NamedThreadFactory("dbEc"))
+  private val dbEc = ExecutionContext.fromExecutorService(tpe)
+
+  private val worldsTable = new Worlds
+  private val fortunesTable = new Fortunes
+
+  // A predicate for checking our ability to service database requests is determined by ensuring that the request
+  // queue doesn't fill up beyond a certain threshold. For convenience we use the max number of connections * the max
+  // # of db requests per web request to determine this threshold. It is a rough check as we don't know how many
+  // queries we're going to make or what other threads are running in parallel etc. Nevertheless, the check is
+  // adequate in order to throttle the acceptance of requests to the size of the pool.
+  def isDbAvailable: Boolean = (tpe.getQueue.size() < maxConnections * MaxQueriesPerRequest)
+
+  def db(queries: Int) = PredicatedAction(isDbAvailable, ServiceUnavailable) {
+    Action {
+      Async {
+        val random = ThreadLocalRandom.current()
+
+        val _worlds = Future.sequence((for {
+          _ <- 1 to queries
+        } yield Future(worldsTable.findById(random.nextInt(TestDatabaseRows) + 1))(dbEc)
+          ).toList)
+
+        _worlds map {
+          w => Ok(Json.toJson(w))
+        }
+      }
+    }
+  }
+
+  def fortunes() = PredicatedAction(isDbAvailable, ServiceUnavailable) {
+    Action {
+      Async {
+        Future(fortunesTable.getAll())(dbEc).map { fs =>
+          val fortunes =  Fortune(-1, "Additional fortune added at request time.") +: fs
+          Ok(views.html.fortune(fortunes))
+        }
+      }
+    }
+  }
+
+  def update(queries: Int) = PredicatedAction(isDbAvailable, ServiceUnavailable) {
+    Action {
+      Async {
+        val random = ThreadLocalRandom.current()
+
+        val boundsCheckedQueries = queries match {
+          case q if q > 500 => 500
+          case q if q <   1 => 1
+          case _ => queries
+        }
+
+        val worlds = Future.sequence((for {
+          _ <- 1 to boundsCheckedQueries
+        } yield Future {
+            for {
+              world <- worldsTable.findById(random.nextInt(TestDatabaseRows) + 1) 
+            } yield {
+              val updatedWorld = world.copy(randomNumber = random.nextInt(TestDatabaseRows) + 1)
+              worldsTable.updateRandom(updatedWorld)
+              updatedWorld
+            }
+          }(dbEc)
+        ).toList)
+
+        worlds.map {
+          w => Ok(Json.toJson(w)).withHeaders("Server" -> "Netty")
+        }
+      }
+    }
+  }
+}

+ 20 - 0
play-slick/app/models/Fortune.scala

@@ -0,0 +1,20 @@
+package models
+
+import play.api.Play.current
+import play.api.db.slick.Config.driver.simple._
+import play.api.db.slick.DB
+
+
+class Fortunes extends Table[Fortune]("Fortune") {
+  def id = column[Long]("id", O.PrimaryKey)
+  def message = column[String]("message")
+  def * = id ~ message <> (Fortune.apply _, Fortune.unapply _)
+
+  val byId = createFinderBy(_.id)
+
+  def getAll(): List[Fortune] = DB.withSession { implicit session =>
+    Query(this).list
+  }
+}
+
+case class Fortune(id: Long, message: String)

+ 39 - 0
play-slick/app/models/World.scala

@@ -0,0 +1,39 @@
+package models
+
+import play.api.Play.current
+import play.api.db.slick.Config.driver.simple._
+import play.api.db.slick.DB
+import play.api.libs.json._
+import play.api.libs.functional.syntax._
+
+class Worlds extends Table[World]("World") {
+  def id = column[Int]("id", O.PrimaryKey)
+  def randomNumber = column[Long]("randomNumber")
+  def * = id ~ randomNumber <> (World.apply _, World.unapply _)
+
+  val byId = createFinderBy(_.id)
+
+  def findById(id: Int): Option[World] = DB.withSession { implicit session =>
+      byId(id).firstOption
+  }
+
+  def updateRandom(world: World) {
+    DB.withSession { implicit session =>
+      this.where(_.id === world.id.bind).update(world)
+    }
+  }
+
+}
+
+case class World(id: Int, randomNumber: Long)
+
+object World {
+  implicit val toJson = new Writes[World] {
+    def writes(w: World): JsValue = {
+      Json.obj(
+        "id" -> w.id,
+        "randomNumber" -> w.randomNumber
+      )
+    }
+  }
+}

+ 19 - 0
play-slick/app/utils/PredicatedAction.scala

@@ -0,0 +1,19 @@
+package utils
+
+import play.api.mvc._
+
+/**
+ * A predicated action is one where a condition must be satisfied in order to proceed with the request. If the
+ * condition is not satisfied then a supplied status result is yielded.
+ */
+class PredicatedActionBuilder {
+  def apply[A](p: => Boolean, failed: => Result)(action: Action[A]): Action[A] = new Action[A] {
+    def apply(request: Request[A]): Result = {
+      if (p) action(request) else failed
+    }
+
+    lazy val parser = action.parser
+  }
+}
+
+object PredicatedAction extends PredicatedActionBuilder

+ 18 - 0
play-slick/app/views/fortune.scala.html

@@ -0,0 +1,18 @@
+@(fortunes: List[Fortune])
+
+@main("Testcase 4 :: Fortune :: FrameworkBenchmark") {
+    <table>
+        <tr>
+            <th>id</th>
+            <th>message</th>
+        </tr>
+
+        @fortunes.sortBy(_.message).map { case Fortune(id, message) =>
+            <tr>
+                <td>@id</td>
+                <td>@message</td>
+            </tr>
+        }
+
+    </table>
+}

+ 13 - 0
play-slick/app/views/main.scala.html

@@ -0,0 +1,13 @@
+@(title: String)(content: Html)
+
+<!DOCTYPE html>
+
+<html>
+<head>
+    <title>@title</title>
+    <meta charset=utf-8>
+</head>
+<body>
+    @content
+</body>
+</html>

+ 15 - 0
play-slick/benchmark_config

@@ -0,0 +1,15 @@
+{
+  "framework": "play-scala",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/db?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/update?queries=",
+      "port": 9000,
+      "sort": 32
+    }
+  }]
+}

+ 88 - 0
play-slick/conf/application.conf

@@ -0,0 +1,88 @@
+# This is the main configuration file for the application.
+# ~~~~~
+
+# Secret key
+# ~~~~~
+# The secret key is used to secure cryptographics functions.
+# If you deploy your application to several instances be sure to use the same key!
+application.secret="RItx1I:80?W@]8GAtPDuF8Ydd3mXM85p/<7og]Q;uBOdijQAauRDgu73B6`wQP59"
+
+# The application languages
+# ~~~~~
+application.langs="en"
+
+# Global object class
+# ~~~~~
+# Define the Global object class for this application.
+# Default to Global in the root package.
+# global=Global
+
+# Database configuration
+# ~~~~~ 
+# You can declare as many datasources as you want.
+# By convention, the default datasource is named `default`
+#
+#db.default.driver=org.h2.Driver
+#db.default.url="jdbc:h2:mem:play"
+#db.default.user=sa
+# db.default.password=
+#
+# You can expose this datasource via JNDI if needed (Useful for JPA)
+# db.default.jndiName=DefaultDS
+db.default.driver= com.mysql.jdbc.Driver
+db.default.url="jdbc:mysql://localhost:3306/hello_world?jdbcCompliantTruncation=false&elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&prepStmtCacheSize=4096&cacheServerConfiguration=true&prepStmtCacheSqlLimit=2048&zeroDateTimeBehavior=convertToNull&traceProtocol=false&useUnbufferedInput=false&useReadAheadInput=false&maintainTimeStats=false&useServerPrepStmts&cacheRSMetadata=true"
+db.default.user=benchmarkdbuser
+db.default.password=benchmarkdbpass
+db.default.jndiName=DefaultDS
+
+db.default.partitionCount=4
+
+# The number of connections to create per partition. Setting this to 
+# 5 with 3 partitions means you will have 15 unique connections to the 
+# database. Note that BoneCP will not create all these connections in 
+# one go but rather start off with minConnectionsPerPartition and 
+# gradually increase connections as required.
+db.default.maxConnectionsPerPartition=64
+
+# The number of initial connections, per partition.
+db.default.minConnectionsPerPartition=64
+
+slick.default="models.*"
+
+# Evolutions
+# ~~~~~
+# You can disable evolutions if needed
+evolutionplugin=disabled
+
+# Logger
+# ~~~~~
+# You can also configure logback (http://logback.qos.ch/), by providing a logger.xml file in the conf directory .
+
+# Root logger:
+logger.root=ERROR
+
+# Logger used by the framework:
+logger.play=ERROR
+
+# Logger provided to your application:
+logger.application=ERROR
+
+play {
+  akka {
+    event-handlers = ["akka.event.slf4j.Slf4jEventHandler"]
+    loglevel = WARNING
+    actor {
+      default-dispatcher = {
+        fork-join-executor {
+          parallelism-factor = 1.0
+          parallelism-max = 50
+        }
+      }
+      application = {
+        fork-join-executor {
+          parallelism-max = 300
+        }
+      }
+    }
+  }
+}

+ 11 - 0
play-slick/conf/routes

@@ -0,0 +1,11 @@
+# Routes
+# This file defines all application routes (Higher priority routes first)
+# ~~~~
+
+# Home page
+GET     /db                             controllers.Application.db(queries: Int ?= 1)
+GET     /fortunes                       controllers.Application.fortunes
+GET     /update                         controllers.Application.update(queries: Int ?= 1)
+
+# Map static resources from the /public folder to the /assets URL path
+GET     /assets/*file                   controllers.Assets.at(path="/public", file)

+ 20 - 0
play-slick/project/Build.scala

@@ -0,0 +1,20 @@
+import sbt._
+import Keys._
+import PlayProject._
+
+object ApplicationBuild extends Build {
+
+  val appName         = "play-slick"
+  val appVersion      = "1.0-SNAPSHOT"
+
+  val appDependencies = Seq(
+    jdbc,
+    anorm,
+    "mysql" % "mysql-connector-java" % "5.1.22",
+    "com.typesafe.play" %% "play-slick" % "0.3.2" 
+  )
+
+  val main = play.Project(appName, appVersion, appDependencies).settings(
+  )
+
+}

+ 1 - 0
play-slick/project/build.properties

@@ -0,0 +1 @@
+sbt.version=0.12.2

+ 8 - 0
play-slick/project/plugins.sbt

@@ -0,0 +1,8 @@
+// Comment to get more information during initialization
+logLevel := Level.Warn
+
+// The Typesafe repository 
+resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
+
+// Use the Play sbt plugin for Play projects
+addSbtPlugin("play" % "sbt-plugin" % "2.1.1")

+ 28 - 0
play-slick/setup.py

@@ -0,0 +1,28 @@
+
+import subprocess
+import sys
+import setup_util
+import os
+
+def start(args):
+  setup_util.replace_text("play-scala/conf/application.conf", "jdbc:mysql:\/\/.*:3306", "jdbc:mysql://" + args.database_host + ":3306")
+
+  subprocess.check_call("play dist", shell=True, cwd="play-scala")
+  subprocess.check_call("unzip play-scala-1.0-SNAPSHOT.zip", shell=True, cwd="play-scala/dist")
+  subprocess.check_call("chmod +x start", shell=True, cwd="play-scala/dist/play-scala-1.0-SNAPSHOT")
+  subprocess.Popen("./start", shell=True, cwd="play-scala/dist/play-scala-1.0-SNAPSHOT")
+
+  return 0
+def stop():
+  p = subprocess.Popen(['ps', 'aux'], stdout=subprocess.PIPE)
+  out, err = p.communicate()
+  for line in out.splitlines():
+    if './start' in line or ('play' in line and 'java' in line):
+      pid = int(line.split(None, 2)[1])
+      os.kill(pid, 9)
+  try:
+    os.remove("play-scala/RUNNING_PID")
+  except OSError:
+    pass
+
+  return 0