Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions frameworks/Scala/kyo-scheduler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# kyo-scheduler Benchmarking Test

This is a simple test to benchmark the performance of the kyo-scheduler libraries along with different backends in Scala.

### Test Type Implementation Source Code

* JSON
* PLAINTEXT

## Software Versions

* [Java OpenJDK 21](https://adoptium.net/temurin/releases/)
* [Kyo 0.17.0](https://github.com/getkyo/kyo)
* [Scala 3.6.4 and Scala 2.13.16](https://www.scala-lang.org/)

### Server Implementations

* [ZIO Http](https://zio.dev/zio-http/)
* [http4s](https://http4s.org/)

## Test URLs

* JSON - http://localhost:8080/json
* PLAINTEXT - http://localhost:8080/plaintext
49 changes: 49 additions & 0 deletions frameworks/Scala/kyo-scheduler/benchmark_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"framework": "kyo-scheduler",
"tests": [
{
"default": {
"plaintext_url": "/plaintext",
"json_url": "/json",
"port": 8080,
"database": "None",
"approach": "Realistic",
"classification": "Micro",
"framework": "zio-http",
"language": "Scala",
"flavor": "None",
"orm": "Raw",
"platform": "Netty",
"webserver": "None",
"database_os": "Linux",
"os": "Linux",
"display_name": "zio-http with kyo-scheduler",
"notes": "https://zio.dev/zio-http/",
"versus": "None"
},
"http4s": {
"orm": "Raw",
"database_os": "Linux",
"json_url": "/json",
"plaintext_url": "/plaintext",
"query_url": "/queries?queries=",
"update_url": "/updates?queries=",
"fortune_url": "/fortunes",
"port": 8080,
"approach": "Realistic",
"classification": "Micro",
"database": "Postgres",
"db_url": "/db",
"framework": "http4s",
"language": "Scala",
"platform": "NIO2",
"webserver": "blaze",
"os": "Linux",
"display_name": "http4s with kyo-scheduler",
"notes": "https://http4s.org/",
"flavor": "None",
"versus": "None"
}
}
]
}
54 changes: 54 additions & 0 deletions frameworks/Scala/kyo-scheduler/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name := "kyo-scheduler-benchmark"

ThisBuild / version := "1.0.0"

val kyoVersion = "0.18.0"

val commonAssemblySettings = assembly / assemblyMergeStrategy := {
case x if x.contains("io.netty.versions.properties") => MergeStrategy.discard
case x if x.contains("module-info.class") => MergeStrategy.discard
case x =>
val oldStrategy = (assembly / assemblyMergeStrategy).value
oldStrategy(x)
}

// based on the framework/Scala/zio-http implementation
lazy val `zio-http` = (project in file("zio-http"))
.settings(
scalaVersion := "3.6.4",
name := "zio-http-kyo-scheduler-benchmark",
libraryDependencies ++= Seq(
"dev.zio" %% "zio-http" % "3.2.0",
"io.getkyo" %% "kyo-scheduler-zio" % kyoVersion,
),
commonAssemblySettings
)

val http4sVersion = "0.23.22"
val http4sBlazeVersion = "0.23.15"
val http4sTwirlVersion = "0.23.17"

// based on the framework/Scala/http4s implementation
lazy val http4s = (project in file("http4s"))
.settings(
scalaVersion := "2.13.16",
name := "http4s-kyo-scheduler-benchmark",
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-blaze-server" % http4sBlazeVersion,
"org.http4s" %% "http4s-dsl" % http4sVersion,
"org.http4s" %% "http4s-twirl" % http4sTwirlVersion,
"org.http4s" %% "http4s-circe" % http4sVersion,
// Optional for auto-derivation of JSON codecs
"io.circe" %% "circe-generic" % "0.14.5",
"org.typelevel" %% "cats-effect" % "3.5.1",
"co.fs2" %% "fs2-core" % "3.7.0",
"co.fs2" %% "fs2-io" % "3.7.0",
"io.getquill" %% "quill-jasync-postgres" % "3.19.0",
"io.getquill" %% "quill-jasync" % "3.19.0",
"ch.qos.logback" % "logback-classic" % "1.4.8",
"io.getkyo" %% "kyo-scheduler-cats" % kyoVersion,
),
commonAssemblySettings,
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
)
.enablePlugins(SbtTwirl)
32 changes: 32 additions & 0 deletions frameworks/Scala/kyo-scheduler/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[framework]
name = "kyo-scheduler"

[main]
urls.plaintext = "/plaintext"
urls.json = "/json"
approach = "Realistic"
classification = "Micro"
database = "None"
database_os = "Linux"
os = "Linux"
orm = "Raw"
platform = "Netty"
webserver = "None"
versus = "None"

[http4s]
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 = "Postgres"
database_os = "Linux"
os = "Linux"
orm = "Raw"
platform = "NIO2"
webserver = "blaze"
versus = "None"
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ctx.port=5432
ctx.username=benchmarkdbuser
ctx.password=benchmarkdbpass
ctx.database=hello_world
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<root level="error">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package http4s.techempower.benchmark

import java.util.concurrent.{Executor, ThreadLocalRandom}

import scala.concurrent.{ExecutionContext, ExecutionContextExecutor}
import cats.effect.{IO => CatsIO}
import cats.syntax.all._
import io.getquill._

class DatabaseService(ctx: PostgresJAsyncContext[LowerCase.type], executor: Executor) {
implicit val dbExecutionContext: ExecutionContextExecutor = ExecutionContext.fromExecutor(executor)
import ctx._

def close(): CatsIO[Unit] = {
CatsIO(ctx.close())
}

// Provide a random number between 1 and 10000 (inclusive)
private def randomWorldId() =
CatsIO(ThreadLocalRandom.current().nextInt(1, 10001))

// Update the randomNumber field with a random number
def updateRandomNumber(world: World): CatsIO[World] =
for {
randomId <- randomWorldId()
} yield world.copy(randomNumber = randomId)

// Select a World object from the database by ID
def selectWorld(id: Int): CatsIO[World] =
CatsIO.fromFuture(
CatsIO.delay(
ctx
.run(quote {
query[World].filter(_.id == lift(id))
})
.map(rq => rq.head)
)
)

// Select a random World object from the database
def selectRandomWorld(): CatsIO[World] =
for {
randomId <- randomWorldId()
world <- selectWorld(randomId)
} yield world

// Select a specified number of random World objects from the database
def getWorlds(numQueries: Int): CatsIO[List[World]] =
(0 until numQueries).toList.traverse(_ => selectRandomWorld())

// Update the randomNumber field with a new random number, for a list of World objects
def getNewWorlds(worlds: List[World]): CatsIO[List[World]] =
worlds.map(updateRandomNumber).sequence

// Update the randomNumber column in the database for a specified set of World objects,
// this uses a batch update SQL call.
def updateWorlds(newWorlds: List[World]): CatsIO[Int] = {
val u = quote {
liftQuery(newWorlds).foreach { world =>
query[World]
.filter(_.id == world.id)
.update(_.randomNumber -> world.randomNumber)
}
}
CatsIO.fromFuture(CatsIO.delay(ctx.run(u).map(_.length)))
}

// Retrieve all fortunes from the database
def getFortunes(): CatsIO[List[Fortune]] =
CatsIO.fromFuture(CatsIO.delay(ctx.run(query[Fortune]).map(_.toList)))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package http4s.techempower.benchmark


import java.util.concurrent.Executors
import cats.effect.{ExitCode, IO, Resource}
import com.typesafe.config.ConfigValueFactory
import io.circe.generic.auto._
import io.circe.syntax._
import io.getquill.util.LoadConfig
import io.getquill.LowerCase
import io.getquill.PostgresJAsyncContext
import org.http4s._
import org.http4s.dsl._
import org.http4s.circe._
import org.http4s.implicits._
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.headers.Server
import org.http4s.twirl._

final case class Message(message: String)
final case class World(id: Int, randomNumber: Int)
final case class Fortune(id: Int, message: String)

// Extract queries parameter (with default and min/maxed)
object Queries {
def unapply(params: Map[String, Seq[String]]): Option[Int] =
Some(params.getOrElse("queries", Nil).headOption match {
case None => 1
case Some(x) =>
Math.max(1, Math.min(500, scala.util.Try(x.toInt).getOrElse(1)))
})
}

// based on the framework/Scala/http4s implementation
object WebServer extends kyo.KyoSchedulerIOApp with Http4sDsl[IO] {
def makeDatabaseService(
host: String,
poolSize: Int
): Resource[IO, DatabaseService] = {
for {
executor <- Resource(IO {
val pool = Executors.newFixedThreadPool(poolSize)
(pool, IO(pool.shutdown()))
})
ctx <- Resource.fromAutoCloseable(IO(new PostgresJAsyncContext(
LowerCase,
LoadConfig("ctx")
.withValue("host", ConfigValueFactory.fromAnyRef(host))
.withValue(
"maxActiveConnections",
ConfigValueFactory.fromAnyRef(poolSize)
)
)))
} yield new DatabaseService(ctx, executor)
}

// Add a new fortune to an existing list, and sort by message.
def getSortedFortunes(old: List[Fortune]): List[Fortune] = {
val newFortune = Fortune(0, "Additional fortune added at request time.")
(newFortune :: old).sortBy(_.message)
}

// Add Server header container server address
def addServerHeader(service: HttpRoutes[IO]): HttpRoutes[IO] =
cats.data.Kleisli { req: Request[IO] =>
service.run(req).map(_.putHeaders(server))
}

val server = Server(ProductId("http4s", None))

// HTTP service definition
def service(db: DatabaseService) =
addServerHeader(HttpRoutes.of[IO] {
case GET -> Root / "plaintext" =>
Ok("Hello, World!")

case GET -> Root / "json" =>
Ok(Message("Hello, World!").asJson)

case GET -> Root / "db" =>
Ok(db.selectRandomWorld().map(_.asJson))

case GET -> Root / "queries" :? Queries(numQueries) =>
Ok(db.getWorlds(numQueries).map(_.asJson))

case GET -> Root / "fortunes" =>
Ok(for {
oldFortunes <- db.getFortunes()
newFortunes = getSortedFortunes(oldFortunes)
} yield html.index(newFortunes))

case GET -> Root / "updates" :? Queries(numQueries) =>
Ok(for {
worlds <- db.getWorlds(numQueries)
newWorlds <- db.getNewWorlds(worlds)
_ <- db.updateWorlds(newWorlds)
} yield newWorlds.asJson)
})

// Given a fully constructed HttpService, start the server and wait for completion
def startServer(service: HttpRoutes[IO]) =
BlazeServerBuilder[IO]
.bindHttp(8080, "0.0.0.0")
.withHttpApp(service.orNotFound)
.withSocketKeepAlive(true)
.resource

// Entry point when starting service
override def run(args: List[String]): IO[ExitCode] =
(for {
db <- makeDatabaseService(
args.headOption.getOrElse("localhost"),
sys.env.get("DB_POOL_SIZE").map(_.toInt).getOrElse(64)
)
server <- startServer(service(db))
} yield server)
.use(_ => IO.never)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@import http4s.techempower.benchmark.Fortune
@(fortunes: Seq[Fortune])
<!DOCTYPE html>
<html>
<head><title>Fortunes</title></head>
<body>
<table>
<tr><th>id</th><th>message</th></tr>
@for(fortune <- fortunes) {
<tr><td>@fortune.id</td><td>@fortune.message</td></tr>
}
</table>
</body>
</html>
Loading
Loading