Browse Source

[Scala] Add Apache Pekko (#8375)

* Pekko implementation

- start with pekko-http only
- remove deprecations

* Fix readme reference to plaintext implementation

* Improve docker build
Oliver Trosien 1 year ago
parent
commit
ab55fccc33
29 changed files with 609 additions and 0 deletions
  1. 36 0
      frameworks/Scala/pekko-http/.dockerignore
  2. 6 0
      frameworks/Scala/pekko-http/README.md
  3. 28 0
      frameworks/Scala/pekko-http/benchmark_config.json
  4. 20 0
      frameworks/Scala/pekko-http/config.toml
  5. 18 0
      frameworks/Scala/pekko-http/pekko-http.dockerfile
  6. 34 0
      frameworks/Scala/pekko-http/pekko-http/.gitignore
  7. 5 0
      frameworks/Scala/pekko-http/pekko-http/.sbtopts
  8. 50 0
      frameworks/Scala/pekko-http/pekko-http/README.md
  9. 25 0
      frameworks/Scala/pekko-http/pekko-http/build.sbt
  10. 1 0
      frameworks/Scala/pekko-http/pekko-http/project/build.properties
  11. 2 0
      frameworks/Scala/pekko-http/pekko-http/project/plugins.sbt
  12. 33 0
      frameworks/Scala/pekko-http/pekko-http/src/main/resources/application.conf
  13. 12 0
      frameworks/Scala/pekko-http/pekko-http/src/main/resources/templates/fortunes.mustache
  14. 21 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/App.scala
  15. 27 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/Bootstrap.scala
  16. 14 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/Infrastructure.scala
  17. 9 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/Main.scala
  18. 10 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/RequestMapping.scala
  19. 7 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/Templating.scala
  20. 16 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/datastore/DataStore.scala
  21. 78 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/datastore/MySqlDataStore.scala
  22. 3 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/entity/Fortune.scala
  23. 11 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/entity/World.scala
  24. 18 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/handlers/DbHandler.scala
  25. 31 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/handlers/FortunesHandler.scala
  26. 21 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/handlers/JsonHandler.scala
  27. 30 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/handlers/QueriesHandler.scala
  28. 35 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/handlers/UpdatesHandler.scala
  29. 8 0
      frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/util/RandomGenerator.scala

+ 36 - 0
frameworks/Scala/pekko-http/.dockerignore

@@ -0,0 +1,36 @@
+*/.idea/
+*/*.iml
+
+# Created by .ignore support plugin (hsz.mobi)
+### SBT template
+# Simple Build Tool
+# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control
+
+*/target/
+lib_managed/
+src_managed/
+project/boot/
+.history
+.cache
+### Scala template
+**/*.class
+**/*.log
+
+# sbt specific
+.lib/
+dist/*
+**/target*
+project/target
+project/project/target
+project/plugins/project/
+
+# Scala-IDE specific
+.scala_dependencies
+.worksheet
+
+# ENSIME specific
+.ensime_cache/
+.ensime
+
+# our own buffer file
+compare.txt

+ 6 - 0
frameworks/Scala/pekko-http/README.md

@@ -0,0 +1,6 @@
+# [Apache Pekko](https://pekko.apache.org/) Benchmarking Tests
+
+
+See the README.md files of the individual permutations:
+
+* pekko-http

+ 28 - 0
frameworks/Scala/pekko-http/benchmark_config.json

@@ -0,0 +1,28 @@
+{
+  "framework": "pekko-http",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "plaintext_url": "/plaintext",
+        "db_url": "/db",
+        "query_url": "/queries?queries=",
+        "fortune_url": "/fortunes",
+        "update_url": "/updates?queries=",
+        "port": 9000,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "MySQL",
+        "framework": "pekko-http",
+        "language": "Scala",
+        "platform": "Pekko",
+        "webserver": "None",
+        "os": "Linux",
+        "orm": "Raw",
+        "database_os": "Linux",
+        "display_name": "pekko-http",
+        "notes": ""
+      }
+    }
+  ]
+}

+ 20 - 0
frameworks/Scala/pekko-http/config.toml

@@ -0,0 +1,20 @@
+[framework]
+name = "pekko-http"
+
+[main]
+urls.plaintext = "/plaintext"
+urls.json = "/json"
+urls.db = "/db"
+urls.query = "/queries?queries="
+urls.update = "/updates?queries="
+urls.fortune = "/fortunes"
+approach = "Realistic"
+classification = "Micro"
+database = "MySQL"
+database_os = "Linux"
+os = "Linux"
+orm = "Raw"
+platform = "Pekko"
+webserver = "None"
+versus = "None"
+

+ 18 - 0
frameworks/Scala/pekko-http/pekko-http.dockerfile

@@ -0,0 +1,18 @@
+FROM sbtscala/scala-sbt:eclipse-temurin-jammy-17.0.5_8_1.9.3_2.13.11 as build
+
+WORKDIR /pekko-http
+COPY pekko-http ./
+RUN \
+  which sbt && \
+  pwd && \
+  ls -la . && \
+  ls -la project && \
+  sbt sbtVersion
+RUN sbt clean compile stage
+
+FROM eclipse-temurin:17-jre-jammy
+COPY --from=build /pekko-http/target/universal/stage /pekko-http
+
+EXPOSE 9000
+
+CMD ["/pekko-http/bin/pekko-http-benchmark", "-Dpekko.http.benchmark.mysql.dbhost=tfb-database", "-J-server", "-J-Xms2g", "-J-Xmx2g", "-J-XX:NewSize=1g", "-J-XX:MaxNewSize=1g", "-J-XX:InitialCodeCacheSize=256m", "-J-XX:ReservedCodeCacheSize=256m", "-J-XX:+UseParallelGC", "-J-XX:+AlwaysPreTouch", "-J-XX:+UseNUMA"]

+ 34 - 0
frameworks/Scala/pekko-http/pekko-http/.gitignore

@@ -0,0 +1,34 @@
+.idea/
+*/*.iml
+
+# Created by .ignore support plugin (hsz.mobi)
+### SBT template
+# Simple Build Tool
+# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control
+
+target/
+lib_managed/
+src_managed/
+project/boot/
+.history
+.cache
+### Scala template
+*.class
+*.log
+
+# sbt specific
+.lib/
+dist/*
+project/project/
+project/plugins/project/
+
+# Scala-IDE specific
+.scala_dependencies
+.worksheet
+
+# ENSIME specific
+.ensime_cache/
+.ensime
+
+# our own buffer file
+compare.txt

+ 5 - 0
frameworks/Scala/pekko-http/pekko-http/.sbtopts

@@ -0,0 +1,5 @@
+-J-Xms256M
+-J-Xmx768M
+-J-XX:MaxMetaspaceSize=256M
+-Djava.awt.headless=true
+-Djava.net.preferIPv4Stack=true

+ 50 - 0
frameworks/Scala/pekko-http/pekko-http/README.md

@@ -0,0 +1,50 @@
+# pekko-http Benchmarking Test
+
+### Test Type Implementation Source Code
+
+* [JSON](src/main/scala/pekko/http/benchmark/handlers/JsonHandler.scala)
+* [PLAINTEXT](src/main/scala/pekko/http/benchmark/Bootstrap.scala)
+* [DB](src/main/scala/pekko/http/benchmark/handlers/DbHandler.scala)
+* [QUERY](src/main/scala/pekko/http/benchmark/handlers/QueriesHandler.scala)
+* [UPDATE](src/main/scala/pekko/http/benchmark/handlers/UpdatesHandler.scala)
+* [FORTUNES](src/main/scala/pekko/http/benchmark/handlers/FortunesHandler.scala)
+
+## Important Libraries
+
+The tests were run with:
+
+* [Pekko HTTP](https://pekko.apache.org/docs/pekko-http/current/)
+* [Pekko Streams](https://pekko.apache.org/docs/pekko/current/)
+* [HikariCP](https://brettwooldridge.github.io/HikariCP/)
+* [jsoniter-scala](https://github.com/plokhotnyuk/jsoniter-scala)
+* [Scalate](https://scalate.github.io/scalate/)
+
+## Test URLs
+
+### JSON
+
+http://localhost:9000/json
+
+### PLAINTEXT
+
+http://localhost:9000/plaintext
+
+### DB
+
+http://localhost:9000/db
+
+### QUERY
+
+http://localhost:9000/query?queries=
+
+### CACHED QUERY
+
+http://localhost:9000/cached_query?queries=
+
+### UPDATE
+
+http://localhost:9000/update?queries=
+
+### FORTUNES
+
+http://localhost:9000/fortunes

+ 25 - 0
frameworks/Scala/pekko-http/pekko-http/build.sbt

@@ -0,0 +1,25 @@
+enablePlugins(JavaAppPackaging)
+
+organization := "org.apache.pekko"
+
+name := "pekko-http-benchmark"
+
+version := "0.1.0-SNAPSHOT"
+
+scalaVersion := "2.13.8"
+
+val pekkoV = "1.0.1"
+val pekkoHttpV = "1.0.0"
+
+libraryDependencies ++= Seq(
+  "org.apache.pekko" %% "pekko-http" % pekkoHttpV,
+  "org.apache.pekko" %% "pekko-stream" % pekkoV,
+  "com.github.pjfanning" %% "pekko-http-jsoniter-scala" % "2.1.0",
+  "com.github.plokhotnyuk.jsoniter-scala" %% "jsoniter-scala-macros" % "2.6.0",
+  "mysql" % "mysql-connector-java" % "8.0.21",
+  "com.zaxxer" % "HikariCP" % "3.4.5",
+  "org.scalatra.scalate" %% "scalate-core" % "1.9.6",
+  "org.scalatest" %% "scalatest" % "3.0.8" % "test"
+)
+
+Compile / mainClass := Some("pekko.http.benchmark.Main")

+ 1 - 0
frameworks/Scala/pekko-http/pekko-http/project/build.properties

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

+ 2 - 0
frameworks/Scala/pekko-http/pekko-http/project/plugins.sbt

@@ -0,0 +1,2 @@
+addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.16")
+

+ 33 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/resources/application.conf

@@ -0,0 +1,33 @@
+pekko {
+  actor {
+    default-dispatcher {
+      fork-join-executor {
+        parallelism-min = 1    # don't constrain parallelism statically
+        parallelism-max = 64   # --
+        parallelism-factor = 1 # one thread per core is enough
+      }
+    }
+    internal-dispatcher = "pekko.actor.default-dispatcher"
+  }
+  stream.materializer.io.tcp.write-buffer-size = 128k
+  stream.materializer.io.tcp.coalesce-writes = 1
+  http {
+    benchmark {
+      host: 0.0.0.0
+      port: 9000
+      mysql {
+        dbhost: 0.0.0.0
+        dbport: 3306
+        dbuser: "benchmarkdbuser"
+        dbpass: "benchmarkdbpass"
+        jdbc-url: "jdbc:mysql://"${pekko.http.benchmark.mysql.dbhost}":"${pekko.http.benchmark.mysql.dbport}"/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=true&cacheRSMetadata=true&useSSL=false"
+        connection-pool-size: 512
+        thread-pool-size: 512
+      }
+    }
+    server {
+      backlog = 1024
+      request-timeout = off
+    }
+  }
+}

+ 12 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/resources/templates/fortunes.mustache

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+    <head><title>Fortunes</title></head>
+    <body>
+        <table>
+            <tr><th>id</th><th>message</th></tr>
+                {{#fortunes}}
+            <tr><td>{{id}}</td><td>{{message}}</td></tr>
+                {{/fortunes}}
+        </table>
+    </body>
+</html>

+ 21 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/App.scala

@@ -0,0 +1,21 @@
+package pekko.http.benchmark
+
+import org.apache.pekko.actor.ActorSystem
+import com.typesafe.config.Config
+import com.typesafe.config.ConfigFactory
+import org.fusesource.scalate.TemplateEngine
+import pekko.http.benchmark.datastore.MySqlDataStore
+import pekko.http.benchmark.handlers.{DbHandler, FortunesHandler, JsonHandler, QueriesHandler, UpdatesHandler}
+import pekko.http.benchmark.util.RandomGenerator
+
+import scala.concurrent.ExecutionContext
+
+class App extends Infrastructure with RandomGenerator with MySqlDataStore with JsonHandler with DbHandler
+  with QueriesHandler with FortunesHandler with UpdatesHandler with RequestMapping with BenchmarkBootstrap with Templating {
+
+  val templateEngine = new TemplateEngine()
+  implicit val system: ActorSystem = ActorSystem("pekko-http-benchmark")
+  val executionContext: ExecutionContext = system.dispatcher
+  val appConfig: Config = ConfigFactory.load
+}
+

+ 27 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/Bootstrap.scala

@@ -0,0 +1,27 @@
+package pekko.http.benchmark
+
+import org.apache.pekko.http.scaladsl.Http
+import org.apache.pekko.http.scaladsl.model._
+import org.apache.pekko.http.scaladsl.util.FastFuture
+
+import scala.concurrent.Future
+
+trait Bootstrap {
+  def run(): Unit
+}
+
+trait BenchmarkBootstrap extends Bootstrap { _: Infrastructure with RequestMapping =>
+  override def run(): Unit =
+    Http().newServerAt(
+      appConfig.getString("pekko.http.benchmark.host"),
+      appConfig.getInt("pekko.http.benchmark.port"))
+      .adaptSettings(settings => settings.mapHttp2Settings(_.withMaxConcurrentStreams(16)))
+      .bind(handler)
+
+  val plainTextResponse = FastFuture.successful(HttpResponse(entity = HttpEntity("Hello, World!")))
+  lazy val mainHandler: HttpRequest => Future[HttpResponse] = asRoute
+  lazy val handler: HttpRequest => Future[HttpResponse] = {
+    case HttpRequest(HttpMethods.GET, Uri.Path("/plaintext"), _, _, _) => plainTextResponse
+    case x => mainHandler(x)
+  }
+}

+ 14 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/Infrastructure.scala

@@ -0,0 +1,14 @@
+package pekko.http.benchmark
+
+import org.apache.pekko.actor.ActorSystem
+import com.typesafe.config.Config
+
+import scala.concurrent.ExecutionContext
+
+trait Infrastructure {
+  implicit def system: ActorSystem
+
+  implicit def executionContext: ExecutionContext
+
+  def appConfig: Config
+}

+ 9 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/Main.scala

@@ -0,0 +1,9 @@
+package pekko.http.benchmark
+
+import pekko.http.benchmark
+
+
+object Main {
+  def main(args: Array[String]): Unit =
+    (new benchmark.App).run()
+}

+ 10 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/RequestMapping.scala

@@ -0,0 +1,10 @@
+package pekko.http.benchmark
+
+import org.apache.pekko.http.scaladsl.server.Directives._
+import org.apache.pekko.http.scaladsl.server.Route
+import pekko.http.benchmark.handlers.{DbHandler, FortunesHandler, JsonHandler, QueriesHandler, UpdatesHandler}
+
+trait RequestMapping { _: JsonHandler with DbHandler with QueriesHandler with FortunesHandler with UpdatesHandler =>
+  def asRoute: Route =
+    jsonEndpoint ~ dbEndpoint ~ queriesEndpoint ~ fortunesEndpoint ~ updatesEndpoint
+}

+ 7 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/Templating.scala

@@ -0,0 +1,7 @@
+package pekko.http.benchmark
+
+import org.fusesource.scalate.TemplateEngine
+
+trait Templating {
+  def templateEngine: TemplateEngine
+}

+ 16 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/datastore/DataStore.scala

@@ -0,0 +1,16 @@
+package pekko.http.benchmark.datastore
+
+import pekko.http.benchmark.entity.{Fortune, World}
+
+import scala.collection.immutable
+import scala.concurrent.Future
+
+trait DataStore {
+  def findWorldById(id: Int): Future[Option[World]]
+
+  def requireWorldById(id: Int): Future[World]
+
+  def updateWorld(world: World): Future[Boolean]
+
+  def getFortunes: Future[Seq[Fortune]]
+}

+ 78 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/datastore/MySqlDataStore.scala

@@ -0,0 +1,78 @@
+package pekko.http.benchmark.datastore
+
+import java.sql.PreparedStatement
+import java.sql.ResultSet
+import java.util
+import java.util.Comparator
+import java.util.concurrent.Executors
+import com.typesafe.config.Config
+import com.zaxxer.hikari._
+import pekko.http.benchmark.Infrastructure
+import pekko.http.benchmark.entity.{Fortune, World}
+
+import scala.concurrent.ExecutionContext
+import scala.concurrent.Future
+
+// TODO: use slick or similar here instead for more idiomatic usage
+trait MySqlDataStore extends DataStore { _: Infrastructure =>
+  lazy val config: Config = appConfig.getConfig("pekko.http.benchmark.mysql")
+
+  private lazy val dataSource = new HikariDataSource {
+    setJdbcUrl(config.getString("jdbc-url"))
+    setUsername(config.getString("dbuser"))
+    setPassword(config.getString("dbpass"))
+    setMaximumPoolSize(config.getInt("connection-pool-size"))
+  }
+
+  private implicit lazy val dbExecutionContext: ExecutionContext = {
+    val size = config.getInt("thread-pool-size")
+    val threadPool = Executors.newFixedThreadPool(size)
+    ExecutionContext.fromExecutor(threadPool)
+  }
+
+  def requireWorldById(id: Int): Future[World] =
+    findWorldById(id).map(_.getOrElse(throw new RuntimeException(s"Element with id $id was not found.")))(executionContext)
+
+  override def findWorldById(id: Int): Future[Option[World]] =
+    withStatement("select id, randomNumber from World where id = ?") { stmt =>
+      stmt.setInt(1, id)
+      val rs = stmt.executeQuery()
+      if (rs.next()) Some(World(rs.getInt(1), rs.getInt(2)))
+      else None
+    }
+
+  override def updateWorld(world: World): Future[Boolean] =
+    withStatement("update World set randomNumber = ? where id = ?") { stmt =>
+      stmt.setInt(1, world.randomNumber)
+      stmt.setInt(2, world.id)
+      stmt.executeUpdate() > 0
+    }
+
+  override def getFortunes: Future[Seq[Fortune]] =
+    withStatement("select id, message from Fortune") { stmt =>
+      val rs = stmt.executeQuery()
+      val fortunes = (
+        Iterator.single(Fortune(0, "Additional fortune added at request time."))
+        ++ rs.map(r => Fortune(r.getInt(1), r.getString(2)))
+      ).toArray
+
+      util.Arrays.sort(fortunes, Ordering.by((f: Fortune) => f.message): Comparator[Fortune])
+      fortunes
+    }
+
+  private def withStatement[T](statement: String)(f: PreparedStatement => T): Future[T] =
+    Future {
+      val conn = dataSource.getConnection
+      val stmt = conn.prepareStatement(statement)
+      try f(stmt)
+      finally {
+        stmt.close()
+        conn.close()
+      }
+    }(dbExecutionContext)
+
+  implicit class RsIterator(rs: ResultSet) extends Iterator[ResultSet] {
+    def hasNext: Boolean = rs.next()
+    def next(): ResultSet = rs
+  }
+}

+ 3 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/entity/Fortune.scala

@@ -0,0 +1,3 @@
+package pekko.http.benchmark.entity
+
+case class Fortune(id: Int, message: String)

+ 11 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/entity/World.scala

@@ -0,0 +1,11 @@
+package pekko.http.benchmark.entity
+
+import com.github.plokhotnyuk.jsoniter_scala.core._
+import com.github.plokhotnyuk.jsoniter_scala.macros._
+
+case class World(id: Int, randomNumber: Int)
+
+object World {
+  implicit val codec: JsonValueCodec[World] = JsonCodecMaker.make[World](CodecMakerConfig)
+  implicit val seqCodec: JsonValueCodec[Seq[World]] = JsonCodecMaker.make[Seq[World]](CodecMakerConfig)
+}

+ 18 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/handlers/DbHandler.scala

@@ -0,0 +1,18 @@
+package pekko.http.benchmark.handlers
+
+import org.apache.pekko.http.scaladsl.server.Directives._
+import org.apache.pekko.http.scaladsl.server.Route
+import pekko.http.benchmark.Infrastructure
+import pekko.http.benchmark.datastore.DataStore
+import pekko.http.benchmark.util.RandomGenerator
+
+trait DbHandler { _: Infrastructure with DataStore with RandomGenerator =>
+  import com.github.pjfanning.pekkohttpjsoniterscala.JsoniterScalaSupport._
+
+  def dbEndpoint: Route =
+    get {
+      path("db") {
+        complete(requireWorldById(nextRandomIntBetween1And10000))
+      }
+    }
+}

+ 31 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/handlers/FortunesHandler.scala

@@ -0,0 +1,31 @@
+package pekko.http.benchmark.handlers
+
+import org.apache.pekko.http.scaladsl.marshalling.{Marshaller, ToEntityMarshaller}
+import org.apache.pekko.http.scaladsl.model.HttpCharsets._
+import org.apache.pekko.http.scaladsl.model.MediaTypes._
+import org.apache.pekko.http.scaladsl.model._
+import org.apache.pekko.http.scaladsl.server.Directives._
+import org.apache.pekko.http.scaladsl.server.Route
+import pekko.http.benchmark.{Infrastructure, Templating}
+import pekko.http.benchmark.datastore.DataStore
+import pekko.http.benchmark.entity.Fortune
+
+trait FortunesHandler { _: Infrastructure with DataStore with Templating =>
+
+  def fortunesEndpoint: Route =
+    get {
+      path("fortunes") {
+        complete(getFortunes)
+      }
+    }
+
+  private implicit lazy val fortunesMarshaller: ToEntityMarshaller[Seq[Fortune]] = {
+    val fortunesTemplate = templateEngine.load("/templates/fortunes.mustache")
+    Marshaller.opaque { fortunes =>
+      HttpEntity(
+        contentType = `text/html`.withCharset(`UTF-8`),
+        string = templateEngine.layout("", fortunesTemplate, Map("fortunes" -> fortunes))
+      )
+    }
+  }
+}

+ 21 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/handlers/JsonHandler.scala

@@ -0,0 +1,21 @@
+package pekko.http.benchmark.handlers
+
+import org.apache.pekko.http.scaladsl.server.Directives._
+import org.apache.pekko.http.scaladsl.server.Route
+import com.github.plokhotnyuk.jsoniter_scala.core._
+import com.github.plokhotnyuk.jsoniter_scala.macros._
+
+case class JsonResponse(message: String)
+
+object JsonResponse {
+  implicit val codec: JsonValueCodec[JsonResponse] = JsonCodecMaker.make[JsonResponse](CodecMakerConfig)
+}
+
+trait JsonHandler {
+  import com.github.pjfanning.pekkohttpjsoniterscala.JsoniterScalaSupport._
+
+  def jsonEndpoint: Route =
+    (get & path("json")) {
+      complete(JsonResponse("Hello, World!"))
+    }
+}

+ 30 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/handlers/QueriesHandler.scala

@@ -0,0 +1,30 @@
+package pekko.http.benchmark.handlers
+
+import org.apache.pekko.http.scaladsl.server.Directives._
+import org.apache.pekko.http.scaladsl.server.Route
+import pekko.http.benchmark.Infrastructure
+import pekko.http.benchmark.datastore.DataStore
+import pekko.http.benchmark.util.RandomGenerator
+
+import scala.concurrent.Future
+import scala.util.Try
+
+trait QueriesHandler {
+  _: Infrastructure with DataStore with RandomGenerator =>
+  import com.github.pjfanning.pekkohttpjsoniterscala.JsoniterScalaSupport._
+
+  def queriesEndpoint: Route =
+    get {
+      path("queries") {
+        parameter("queries".?) { numQueries =>
+          // The queries parameter must be bounded to between 1 and 500. If the parameter is missing,
+          // is not an integer, or is an integer less than 1, the value should be interpreted as 1;
+          // if greater than 500, the value should be interpreted as 500.
+          val realNumQueries = Try(numQueries.getOrElse("1").toInt).getOrElse(1).min(500).max(1)
+          complete {
+            Future.traverse(Seq.fill(realNumQueries)(nextRandomIntBetween1And10000))(requireWorldById)
+          }
+        }
+      }
+    }
+}

+ 35 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/handlers/UpdatesHandler.scala

@@ -0,0 +1,35 @@
+package pekko.http.benchmark.handlers
+
+import org.apache.pekko.http.scaladsl.server.Directives._
+import org.apache.pekko.http.scaladsl.server.Route
+import pekko.http.benchmark.Infrastructure
+import pekko.http.benchmark.datastore.DataStore
+import pekko.http.benchmark.entity.World
+import pekko.http.benchmark.util.RandomGenerator
+
+import scala.concurrent.Future
+import scala.util.Try
+
+trait UpdatesHandler { _: Infrastructure with DataStore with RandomGenerator =>
+  import com.github.pjfanning.pekkohttpjsoniterscala.JsoniterScalaSupport._
+
+  def updatesEndpoint: Route =
+    get {
+      path("updates") {
+        parameter("queries".?) { numQueries =>
+          val realNumQueries = Try(numQueries.getOrElse("1").toInt).getOrElse(1).min(500).max(1)
+
+          def mutateOne(id: Int): Future[World] =
+            for {
+              world <- requireWorldById(id)
+              newWorld = world.copy(randomNumber = nextRandomIntBetween1And10000)
+              _ <- updateWorld(newWorld) // ignore `wasUpdated`
+            } yield newWorld
+
+          complete {
+            Future.traverse(Seq.fill(realNumQueries)(nextRandomIntBetween1And10000))(mutateOne)
+          }
+        }
+      }
+    }
+}

+ 8 - 0
frameworks/Scala/pekko-http/pekko-http/src/main/scala/pekko/http/benchmark/util/RandomGenerator.scala

@@ -0,0 +1,8 @@
+package pekko.http.benchmark.util
+
+import java.util.concurrent.ThreadLocalRandom
+
+trait RandomGenerator {
+  def nextRandomIntBetween1And10000: Int =
+    ThreadLocalRandom.current().nextInt(10000) + 1
+}