Browse Source

Merge pull request #2386 from daviddenton/round-14

Round 14: Fintrospect updates including re-adding missing benchmark tests
knewmanTE 8 years ago
parent
commit
37b460923c

+ 3 - 0
frameworks/Scala/fintrospect/Dockerfile

@@ -0,0 +1,3 @@
+FROM mysql
+
+ADD create.sql /docker-entrypoint-initdb.d/

+ 7 - 7
frameworks/Scala/fintrospect/README.md

@@ -4,16 +4,16 @@
 The tests were run with:
 
 * [Java Oracle 1.8.0_25](http://www.oracle.com/technetwork/java/javase)
-* [fintrospect 13.5.2](https://github.com/daviddenton/fintrospect)
-* [finagle 6.36.0](https://github.com/twitter/finagle)
+* [fintrospect 13.13.0](https://github.com/daviddenton/fintrospect)
 
 ## Test URLs
 
-### JSON Encoding Test
-http://localhost:9000/json
-
-### Plaintext Test
-http://localhost:9000/plaintext
+- JSON Encoding: http://localhost:9000/json
+- Plaintext: http://localhost:9000/plaintext
+- Fortunes: http://localhost:9000/fortunes
+- Single Query: http://localhost:9000/db
+- Multi Query: http://localhost:9000/queries?queries=1
+- Update Query: http://localhost:9000/updates?queries=1
 
 ## How to run
 sbt 'oneJar'

+ 5 - 1
frameworks/Scala/fintrospect/benchmark_config.json

@@ -6,12 +6,16 @@
         "orm": "Raw",
         "database_os": "Linux",
         "setup_file": "setup",
+        "db_url": "/db",
+        "fortune_url": "/fortunes",
         "json_url": "/json",
+        "query_url": "/queries?queries=",
         "plaintext_url": "/plaintext",
+        "update_url": "/updates?queries=",
         "port": 9000,
         "approach": "Realistic",
         "classification": "Micro",
-        "database": "None",
+        "database": "MySQL",
         "framework": "fintrospect",
         "language": "Scala",
         "platform": "Netty",

+ 6 - 4
frameworks/Scala/fintrospect/build.sbt

@@ -15,8 +15,10 @@ com.github.retronym.SbtOneJar.oneJarSettings
 mainClass in(Compile, run) := Some("FintrospectBenchmarkServer")
 
 libraryDependencies ++= Seq(
-  "io.fintrospect" %% "fintrospect-core" % "13.11.0",
-  "io.fintrospect" %% "fintrospect-json4s" % "13.11.0"
-  )
+  "io.fintrospect" %% "fintrospect-core" % "13.13.0",
+  "io.fintrospect" %% "fintrospect-json4s" % "13.13.0",
+  "io.fintrospect" %% "fintrospect-mustache" % "13.13.0",
+  "com.twitter" %% "finagle-mysql" % "6.40.0"
+)
 
-resolvers += Resolver.sonatypeRepo("snapshots")
+resolvers += Resolver.sonatypeRepo("snapshots")

+ 11 - 0
frameworks/Scala/fintrospect/run_db.sh

@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+cp ../../../config/create.sql .
+
+docker build -t dbi .
+
+rm create.sql
+
+docker run --name db -d \
+  -e MYSQL_ROOT_PASSWORD=123 \
+  -p 3306:3306 dbi

+ 2 - 0
frameworks/Scala/fintrospect/setup.sh

@@ -2,6 +2,8 @@
 
 fw_depends java sbt
 
+sbt clean
+
 sbt 'oneJar' -batch
 
 java -jar target/scala-2.11/*fintrospect*one-jar.jar &

+ 5 - 0
frameworks/Scala/fintrospect/source_code

@@ -1,2 +1,7 @@
 fintrospect/src/main/scala/
+fintrospect/src/main/scala/Database.scala
+fintrospect/src/main/scala/FortunesRoute.scala
+fintrospect/src/main/scala/JsonRoute.scala
+fintrospect/src/main/scala/PlainTextRoute.scala
+fintrospect/src/main/scala/DatabaseRoutes.scala
 fintrospect/src/main/scala/FintrospectBenchmarkServer.scala

+ 22 - 0
frameworks/Scala/fintrospect/src/main/scala/Database.scala

@@ -0,0 +1,22 @@
+import com.twitter.finagle.Mysql
+import com.twitter.finagle.client.DefaultPool.Param
+import com.twitter.finagle.mysql.Client
+import com.twitter.finagle.stats.NullStatsReceiver
+import com.twitter.finagle.tracing.NullTracer
+import com.twitter.util.Duration.fromSeconds
+import com.twitter.util.NullMonitor
+import io.fintrospect.configuration.Host
+
+object Database {
+  def apply(dbHost: Host): Client = {
+    Mysql.client
+      .withCredentials("benchmarkdbuser", "benchmarkdbpass")
+      .withDatabase("hello_world")
+      .configured(Param(low = 0, high = 10, idleTime = fromSeconds(5 * 60), bufferSize = 0, maxWaiters = Int.MaxValue))
+      .withStatsReceiver(NullStatsReceiver)
+      .withMonitor(NullMonitor)
+      .withTracer(NullTracer)
+      .withMaxConcurrentPrepareStatements(256)
+      .newRichClient(dbHost.value + ":3306")
+  }
+}

+ 79 - 0
frameworks/Scala/fintrospect/src/main/scala/DatabaseRoutes.scala

@@ -0,0 +1,79 @@
+import com.twitter.finagle.Service
+import com.twitter.finagle.http.Method.Get
+import com.twitter.finagle.http.Status.{NotFound, Ok}
+import com.twitter.finagle.http.{Request, Response}
+import com.twitter.finagle.mysql.Parameter.wrap
+import com.twitter.finagle.mysql.{Client, IntValue, Result, ResultSet}
+import com.twitter.util.Future.collect
+import io.fintrospect.RouteSpec.RequestValidation
+import io.fintrospect.RouteSpec.RequestValidation.none
+import io.fintrospect.formats.Json4sJackson.JsonFormat.{array, number, obj}
+import io.fintrospect.formats.Json4sJackson.ResponseBuilder.implicits._
+import io.fintrospect.parameters.ParameterSpec.int
+import io.fintrospect.parameters.Query
+import io.fintrospect.{RouteSpec, ServerRoutes}
+import org.json4s.JValue
+
+import scala.language.reflectiveCalls
+import scala.util.Random
+
+object DatabaseRoutes {
+
+  private val toJson: PartialFunction[Result, Option[JValue]] = {
+    case rs: ResultSet => rs.rows.headOption
+      .map(row => {
+        val IntValue(id) = row("id").get
+        val IntValue(random) = row("randomNumber").get
+        obj("id" -> number(id), "randomNumber" -> number(random))
+      })
+    case _ => None
+  }
+
+  private def generateRandomNumber = Random.nextInt(9999) + 1
+
+  def apply(database: Client) = {
+    val getStatement = database.prepare("SELECT id, randomNumber FROM world WHERE id = ?")
+    val updateStatement = database.prepare("UPDATE world SET randomNumber = ? WHERE id = ?")
+
+    val queryRoute = RouteSpec(validation = none).at(Get) / "db" bindTo Service.mk {
+      r: Request => getStatement(generateRandomNumber)
+        .map(toJson)
+        .map(_.map(Ok(_)).getOrElse(NotFound()).build())
+    }
+
+    val numberOfQueries = Query.optional(int("queries").map(_.max(1).min(500)))
+
+    val multipleRoute = RouteSpec()
+      .taking(numberOfQueries)
+      .at(Get) / "queries" bindTo Service.mk {
+      r: Request => {
+        collect(1.to((numberOfQueries <-- r).getOrElse(1))
+          .map(i => getStatement(generateRandomNumber).map(toJson)))
+          .map(f => f.flatMap(_.toSeq))
+          .flatMap(c => Ok(array(c)))
+      }
+    }
+
+    val updateRoute = RouteSpec()
+      .taking(numberOfQueries)
+      .at(Get) / "updates" bindTo Service.mk {
+      r: Request => {
+        collect(1.to((numberOfQueries <-- r).getOrElse(1))
+          .map(i => {
+            val id = generateRandomNumber
+            updateStatement(generateRandomNumber, id)
+              .flatMap(_ => getStatement(id))
+              .map(toJson)
+          }))
+          .map(f => f.flatMap(_.toSeq))
+          .flatMap(c => Ok(array(c)))
+      }
+    }
+
+    new ServerRoutes[Request, Response] {
+      add(queryRoute)
+      add(multipleRoute)
+      add(updateRoute)
+    }
+  }
+}

+ 23 - 25
frameworks/Scala/fintrospect/src/main/scala/FintrospectBenchmarkServer.scala

@@ -1,40 +1,38 @@
-import java.time.ZonedDateTime._
-import java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME
+import java.util.TimeZone.getTimeZone
 
-import com.twitter.finagle.http.Method.Get
-import com.twitter.finagle.http.Request
-import com.twitter.finagle.http.Status._
 import com.twitter.finagle.http.path.Root
+import com.twitter.finagle.http.{Request, Response}
 import com.twitter.finagle.stats.NullStatsReceiver
 import com.twitter.finagle.tracing.NullTracer
-import com.twitter.finagle.{Http, Service}
-import com.twitter.io.Buf
+import com.twitter.finagle.{Filter, Http}
 import com.twitter.util.{Await, NullMonitor}
-import io.fintrospect.formats.Json4sJackson.JsonFormat._
-import io.fintrospect.{ModuleSpec, RouteSpec}
+import io.fintrospect.ModuleSpec
+import io.fintrospect.configuration.Host
+import io.fintrospect.renderers.simplejson.SimpleJson
+import org.apache.commons.lang.time.FastDateFormat.getInstance
+
+import scala.util.Properties
 
 object FintrospectBenchmarkServer extends App {
 
-  val preAllocatedHelloWorldText = Buf.Utf8("Hello, World!")
+  val dateFormat = getInstance("EEE, dd MMM yyyy HH:mm:ss 'GMT'", getTimeZone("GMT"))
 
-  val plainTextHelloWorld = {
-    import io.fintrospect.formats.PlainText.ResponseBuilder.implicits._
-    Service.mk { r: Request =>
-      Ok(preAllocatedHelloWorldText)
-        .withHeaders("Server" -> "Example", "Date" -> RFC_1123_DATE_TIME.format(now()))
-    }
+  val addServerAndDate = Filter.mk[Request, Response, Request, Response] { (req, svc) =>
+    svc(req).map(resp => {
+      resp.headerMap("Server") = "Example"
+      resp.headerMap("Date") = dateFormat.format(System.currentTimeMillis())
+      resp
+    })
   }
 
-  val jsonHelloWorld = {
-    import io.fintrospect.formats.Json4sJackson.ResponseBuilder.implicits._
-    Service.mk { r: Request => Ok(obj("message" -> string("Hello, World!")))
-      .withHeaders("Server" -> "Example", "Date" -> RFC_1123_DATE_TIME.format(now()))
-    }
-  }
+  val dbHost = Properties.envOrNone("DBHOST").map(Host(_)).getOrElse(Host.localhost)
+  val database = Database(dbHost)
 
-  val module = ModuleSpec(Root)
-    .withRoute(RouteSpec().at(Get) / "plaintext" bindTo plainTextHelloWorld)
-    .withRoute(RouteSpec().at(Get) / "json" bindTo jsonHelloWorld)
+  val module = ModuleSpec(Root, SimpleJson(), addServerAndDate)
+    .withRoute(JsonRoute())
+    .withRoute(PlainTextRoute())
+    .withRoute(FortunesRoute(database))
+    .withRoutes(DatabaseRoutes(database))
 
   Await.ready(
     Http.server

+ 42 - 0
frameworks/Scala/fintrospect/src/main/scala/FortunesRoute.scala

@@ -0,0 +1,42 @@
+import com.twitter.finagle.Service
+import com.twitter.finagle.http.Method.Get
+import com.twitter.finagle.http.Request
+import com.twitter.finagle.mysql.{Client, IntValue, Result, ResultSet, StringValue}
+import io.fintrospect.RouteSpec
+import io.fintrospect.RouteSpec.RequestValidation.none
+import io.fintrospect.formats.Html
+import io.fintrospect.templating.MustacheTemplates.CachingClasspath
+import io.fintrospect.templating.{RenderView, View}
+
+case class Fortune(id: Int, message: String)
+
+case class FortunesList(items: Seq[Fortune]) extends View
+
+object FortunesRoute {
+
+  private val toFortunes: PartialFunction[Result, Seq[Fortune]] = {
+    case rs: ResultSet => rs.rows
+      .map(row => {
+        val IntValue(id) = row("id").get
+        val StringValue(message) = row("message").get
+        Fortune(id, message)
+      })
+    case _ => Seq.empty
+  }
+
+  def apply(database: Client) = {
+
+    val statement = database.prepare("SELECT * FROM fortune")
+
+    val service = new RenderView(Html.ResponseBuilder, CachingClasspath()).andThen(
+      Service.mk {
+        r: Request =>
+          statement().map(toFortunes).map(f => {
+            val sortedFortunes = (Fortune(0, "Additional fortune added at request time.") +: f).sortBy(_.message)
+            FortunesList(sortedFortunes)
+          })
+      })
+
+    RouteSpec(validation = none).at(Get) / "fortunes" bindTo service
+  }
+}

+ 15 - 0
frameworks/Scala/fintrospect/src/main/scala/JsonRoute.scala

@@ -0,0 +1,15 @@
+import com.twitter.finagle.Service
+import com.twitter.finagle.http.Method.Get
+import com.twitter.finagle.http.Request
+import com.twitter.finagle.http.Status.Ok
+import io.fintrospect.RouteSpec
+import io.fintrospect.RouteSpec.RequestValidation.none
+import io.fintrospect.formats.Json4sJackson.JsonFormat.{obj, string}
+import io.fintrospect.formats.Json4sJackson.ResponseBuilder.implicits._
+
+object JsonRoute {
+
+  private val service = Service.mk { r: Request => Ok(obj("message" -> string("Hello, World!"))) }
+
+  def apply() = RouteSpec(validation = none).at(Get) / "json" bindTo service
+}

+ 17 - 0
frameworks/Scala/fintrospect/src/main/scala/PlainTextRoute.scala

@@ -0,0 +1,17 @@
+import com.twitter.finagle.Service
+import com.twitter.finagle.http.Method.Get
+import com.twitter.finagle.http.Request
+import com.twitter.finagle.http.Status.Ok
+import com.twitter.io.Buf
+import io.fintrospect.RouteSpec
+import io.fintrospect.RouteSpec.RequestValidation.none
+import io.fintrospect.formats.PlainText.ResponseBuilder.implicits._
+
+object PlainTextRoute {
+
+  private val preallocatedMsgForPlainText = Buf.Utf8("Hello, World!")
+
+  private val service = Service.mk { r: Request => Ok(preallocatedMsgForPlainText) }
+
+  def apply() = RouteSpec(validation = none).at(Get) / "plaintext" bindTo service
+}