diff --git a/frameworks/Scala/kyo-scheduler/README.md b/frameworks/Scala/kyo-scheduler/README.md new file mode 100644 index 00000000000..38244346a1f --- /dev/null +++ b/frameworks/Scala/kyo-scheduler/README.md @@ -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 diff --git a/frameworks/Scala/kyo-scheduler/benchmark_config.json b/frameworks/Scala/kyo-scheduler/benchmark_config.json new file mode 100644 index 00000000000..e665c94a2fe --- /dev/null +++ b/frameworks/Scala/kyo-scheduler/benchmark_config.json @@ -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" + } + } + ] +} \ No newline at end of file diff --git a/frameworks/Scala/kyo-scheduler/build.sbt b/frameworks/Scala/kyo-scheduler/build.sbt new file mode 100644 index 00000000000..22db16be04d --- /dev/null +++ b/frameworks/Scala/kyo-scheduler/build.sbt @@ -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) \ No newline at end of file diff --git a/frameworks/Scala/kyo-scheduler/config.toml b/frameworks/Scala/kyo-scheduler/config.toml new file mode 100644 index 00000000000..515c42b6557 --- /dev/null +++ b/frameworks/Scala/kyo-scheduler/config.toml @@ -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" diff --git a/frameworks/Scala/kyo-scheduler/http4s/src/main/resources/application.properties b/frameworks/Scala/kyo-scheduler/http4s/src/main/resources/application.properties new file mode 100644 index 00000000000..614284866be --- /dev/null +++ b/frameworks/Scala/kyo-scheduler/http4s/src/main/resources/application.properties @@ -0,0 +1,4 @@ +ctx.port=5432 +ctx.username=benchmarkdbuser +ctx.password=benchmarkdbpass +ctx.database=hello_world diff --git a/frameworks/Scala/kyo-scheduler/http4s/src/main/resources/logback.xml b/frameworks/Scala/kyo-scheduler/http4s/src/main/resources/logback.xml new file mode 100644 index 00000000000..378a2fc929b --- /dev/null +++ b/frameworks/Scala/kyo-scheduler/http4s/src/main/resources/logback.xml @@ -0,0 +1,14 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + diff --git a/frameworks/Scala/kyo-scheduler/http4s/src/main/scala/http4s/techempower/benchmark/DatabaseService.scala b/frameworks/Scala/kyo-scheduler/http4s/src/main/scala/http4s/techempower/benchmark/DatabaseService.scala new file mode 100644 index 00000000000..a08883458b9 --- /dev/null +++ b/frameworks/Scala/kyo-scheduler/http4s/src/main/scala/http4s/techempower/benchmark/DatabaseService.scala @@ -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))) +} diff --git a/frameworks/Scala/kyo-scheduler/http4s/src/main/scala/http4s/techempower/benchmark/WebServer.scala b/frameworks/Scala/kyo-scheduler/http4s/src/main/scala/http4s/techempower/benchmark/WebServer.scala new file mode 100644 index 00000000000..ffabe43f354 --- /dev/null +++ b/frameworks/Scala/kyo-scheduler/http4s/src/main/scala/http4s/techempower/benchmark/WebServer.scala @@ -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) +} diff --git a/frameworks/Scala/kyo-scheduler/http4s/src/main/twirl/index.scala.html b/frameworks/Scala/kyo-scheduler/http4s/src/main/twirl/index.scala.html new file mode 100644 index 00000000000..70dfcc42ca1 --- /dev/null +++ b/frameworks/Scala/kyo-scheduler/http4s/src/main/twirl/index.scala.html @@ -0,0 +1,14 @@ +@import http4s.techempower.benchmark.Fortune +@(fortunes: Seq[Fortune]) + + +Fortunes + + + + @for(fortune <- fortunes) { + + } +
idmessage
@fortune.id@fortune.message
+ + \ No newline at end of file diff --git a/frameworks/Scala/kyo-scheduler/kyo-scheduler-http4s.dockerfile b/frameworks/Scala/kyo-scheduler/kyo-scheduler-http4s.dockerfile new file mode 100644 index 00000000000..6ecf32cc149 --- /dev/null +++ b/frameworks/Scala/kyo-scheduler/kyo-scheduler-http4s.dockerfile @@ -0,0 +1,24 @@ +FROM sbtscala/scala-sbt:eclipse-temurin-21.0.6_7_1.10.11_2.13.16 + +WORKDIR /kyo-scheduler-benchmark +COPY http4s http4s +COPY project project +COPY build.sbt build.sbt +RUN sbt http4s/assembly + +EXPOSE 8080 + +CMD java \ + -server \ + -Xms2g \ + -Xmx2g \ + -XX:NewSize=1g \ + -XX:MaxNewSize=1g \ + -XX:InitialCodeCacheSize=256m \ + -XX:ReservedCodeCacheSize=256m \ + -XX:+UseParallelGC \ + -XX:+AlwaysPreTouch \ + -Dcats.effect.stackTracingMode=disabled \ + -jar \ + /kyo-scheduler-benchmark/http4s/target/scala-2.13/http4s-kyo-scheduler-benchmark-assembly-1.0.0.jar \ + tfb-database diff --git a/frameworks/Scala/kyo-scheduler/kyo-scheduler.dockerfile b/frameworks/Scala/kyo-scheduler/kyo-scheduler.dockerfile new file mode 100644 index 00000000000..3b7645aeda6 --- /dev/null +++ b/frameworks/Scala/kyo-scheduler/kyo-scheduler.dockerfile @@ -0,0 +1,10 @@ +FROM sbtscala/scala-sbt:eclipse-temurin-21.0.6_7_1.10.11_3.6.4 + +WORKDIR /kyo-scheduler-benchmark +COPY zio-http zio-http +COPY project project +COPY build.sbt build.sbt +RUN sbt zio-http/assembly + +EXPOSE 8080 +CMD ["java", "-Xms2G", "-Xmx2G", "-server", "-Dio.netty.leakDetection.level=disabled", "-Dio.netty.recycler.maxCapacityPerThread=0", "-jar", "/kyo-scheduler-benchmark/zio-http/target/scala-3.6.4/zio-http-kyo-scheduler-benchmark-assembly-1.0.0.jar"] \ No newline at end of file diff --git a/frameworks/Scala/kyo-tapir/project/build.properties b/frameworks/Scala/kyo-scheduler/project/build.properties similarity index 100% rename from frameworks/Scala/kyo-tapir/project/build.properties rename to frameworks/Scala/kyo-scheduler/project/build.properties diff --git a/frameworks/Scala/kyo-scheduler/project/plugins.sbt b/frameworks/Scala/kyo-scheduler/project/plugins.sbt new file mode 100644 index 00000000000..5bd83c79d15 --- /dev/null +++ b/frameworks/Scala/kyo-scheduler/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.1") +addSbtPlugin("com.typesafe.play" % "sbt-twirl" % "1.6.1") \ No newline at end of file diff --git a/frameworks/Scala/kyo-tapir/src/main/scala/Payload.scala b/frameworks/Scala/kyo-scheduler/zio-http/src/main/scala/Payload.scala similarity index 54% rename from frameworks/Scala/kyo-tapir/src/main/scala/Payload.scala rename to frameworks/Scala/kyo-scheduler/zio-http/src/main/scala/Payload.scala index 53b27e476c2..4806bf3380d 100644 --- a/frameworks/Scala/kyo-tapir/src/main/scala/Payload.scala +++ b/frameworks/Scala/kyo-scheduler/zio-http/src/main/scala/Payload.scala @@ -1,8 +1,6 @@ -import sttp.tapir.Schema -import zio.json.* +import zio.json.{DeriveJsonCodec, JsonCodec} case class Payload(message: String) object Payload { given JsonCodec[Payload] = DeriveJsonCodec.gen - given Schema[Payload] = Schema.derived -} \ No newline at end of file +} diff --git a/frameworks/Scala/kyo-scheduler/zio-http/src/main/scala/ZioHttp.scala b/frameworks/Scala/kyo-scheduler/zio-http/src/main/scala/ZioHttp.scala new file mode 100644 index 00000000000..448fd878f56 --- /dev/null +++ b/frameworks/Scala/kyo-scheduler/zio-http/src/main/scala/ZioHttp.scala @@ -0,0 +1,45 @@ +import zio._ +import zio.http._ +import zio.http.netty.NettyConfig +import zio.http.netty.NettyConfig.LeakDetectionLevel +import zio.json.* + +import java.lang.{Runtime => JRuntime} + +// based on the framework/Scala/zio-http implementation +object ZioHttp extends kyo.KyoSchedulerZIOAppDefault { + + private val plainTextMessage: String = "hello, world!" + + private val STATIC_SERVER_NAME = "zio-http" + private val NUM_PROCESSORS = JRuntime.getRuntime.availableProcessors() + + val app: Routes[Any, Response] = Routes( + Method.GET / "/plaintext" -> + Handler.fromResponse( + Response + .text(plainTextMessage) + .addHeader(Header.Server(STATIC_SERVER_NAME)), + ), + Method.GET / "/json" -> + Handler.fromResponse( + Response + .json(Payload(plainTextMessage).toJson) + .addHeader(Header.Server(STATIC_SERVER_NAME)), + ), + ) + + private val config = Server.Config.default + .port(8080) + .enableRequestStreaming + + private val nettyConfig = NettyConfig.default + .leakDetection(LeakDetectionLevel.DISABLED) + .maxThreads(NUM_PROCESSORS) + + private val configLayer = ZLayer.succeed(config) + private val nettyConfigLayer = ZLayer.succeed(nettyConfig) + + val run: UIO[ExitCode] = + Server.serve(app).provide(configLayer, nettyConfigLayer, Server.customized).exitCode +} \ No newline at end of file diff --git a/frameworks/Scala/kyo-tapir/README.md b/frameworks/Scala/kyo-tapir/README.md deleted file mode 100644 index 4fcc3d2c677..00000000000 --- a/frameworks/Scala/kyo-tapir/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Kyo Tapir Benchmarking Test - -This is a simple test to benchmark the performance of the Kyo and Tapir libraries in Scala. - -### Test Type Implementation Source Code - -* [JSON](src/main/scala/Main.scala) -* [PLAINTEXT](src/main/scala/Main.scala) - -## Software Versions - -* [Java OpenJDK 21](https://adoptium.net/temurin/releases/) -* [Scala 3.6.3](https://www.scala-lang.org/) -* [Kyo 0.16.2](https://github.com/getkyo/kyo) -* [Tapir 1.11.15](https://tapir.softwaremill.com) -* [ZIO Json 0.7.32](https://zio.dev/zio-json/) - -## Test URLs - -* JSON - http://localhost:9999/json -* PLAINTEXT - http://localhost:9999/plaintext diff --git a/frameworks/Scala/kyo-tapir/benchmark_config.json b/frameworks/Scala/kyo-tapir/benchmark_config.json deleted file mode 100644 index 3b005996a8f..00000000000 --- a/frameworks/Scala/kyo-tapir/benchmark_config.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "framework": "kyo-tapir", - "tests": [ - { - "default": { - "plaintext_url": "/plaintext", - "json_url": "/json", - "port": 9999, - "database": "None", - "approach": "Realistic", - "classification": "Micro", - "framework": "kyo-tapir", - "language": "Scala", - "flavor": "None", - "orm": "Raw", - "platform": "Netty", - "webserver": "None", - "database_os": "Linux", - "os": "Linux", - "display_name": "kyo-tapir", - "notes": "", - "versus": "None" - } - } - ] -} \ No newline at end of file diff --git a/frameworks/Scala/kyo-tapir/build.sbt b/frameworks/Scala/kyo-tapir/build.sbt deleted file mode 100644 index c7ee01108fc..00000000000 --- a/frameworks/Scala/kyo-tapir/build.sbt +++ /dev/null @@ -1,17 +0,0 @@ -name := "kyo-tapir" -version := "1.0.0" -scalaVersion := "3.6.3" -lazy val root = (project in file(".")) - .settings( - libraryDependencies ++= Seq( - "io.getkyo" %% "kyo-tapir" % "0.16.2", - "com.softwaremill.sttp.tapir" %% "tapir-json-zio" % "1.11.15", - "dev.zio" %% "zio-json" % "0.7.32" - ), - assembly / assemblyMergeStrategy := { - case x if x.contains("io.netty.versions.properties") => MergeStrategy.discard - case x => - val oldStrategy = (assembly / assemblyMergeStrategy).value - oldStrategy(x) - } - ) diff --git a/frameworks/Scala/kyo-tapir/config.toml b/frameworks/Scala/kyo-tapir/config.toml deleted file mode 100644 index f210a34bf48..00000000000 --- a/frameworks/Scala/kyo-tapir/config.toml +++ /dev/null @@ -1,15 +0,0 @@ -[framework] -name = "kyo-tapir" - -[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" \ No newline at end of file diff --git a/frameworks/Scala/kyo-tapir/kyo-tapir.dockerfile b/frameworks/Scala/kyo-tapir/kyo-tapir.dockerfile deleted file mode 100644 index 4dc5370acf2..00000000000 --- a/frameworks/Scala/kyo-tapir/kyo-tapir.dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM sbtscala/scala-sbt:eclipse-temurin-21.0.6_7_1.10.7_3.6.3 - -WORKDIR /kyo-tapir -COPY src src -COPY project project -COPY build.sbt build.sbt -RUN sbt assembly - -EXPOSE 9999 -CMD ["java", "-Xms2G", "-Xmx2G", "-server", "-Dio.netty.leakDetection.level=disabled", "-Dio.netty.recycler.maxCapacityPerThread=0", "-jar", "/kyo-tapir/target/scala-3.6.3/kyo-tapir-assembly-1.0.0.jar"] \ No newline at end of file diff --git a/frameworks/Scala/tapir/README.md b/frameworks/Scala/tapir/README.md new file mode 100644 index 00000000000..8375201828c --- /dev/null +++ b/frameworks/Scala/tapir/README.md @@ -0,0 +1,28 @@ +# Tapir Benchmarking Test + +This is a simple test to benchmark the performance of the Tapir libraries along with different backends in Scala. + +### Test Type Implementation Source Code + +* JSON +* PLAINTEXT + +## Software Versions + +* [Java OpenJDK 21](https://adoptium.net/temurin/releases/) +* [Scala 3.6.4](https://www.scala-lang.org/) +* [Tapir 1.11.24](https://tapir.softwaremill.com) + +### Backend Implementations +* [Tapir running as zio-http server](https://tapir.softwaremill.com/en/latest/server/ziohttp.html) +* [Tapir running as http4s server](https://tapir.softwaremill.com/en/latest/server/http4s.html) +* [as an http4s server using ZIO](https://tapir.softwaremill.com/en/latest/server/zio-http4s.html) +* [Tapir running as Netty-based server(Cats)](https://tapir.softwaremill.com/en/latest/server/netty.html) +* [Tapir running as Netty-based server(Kyo)](https://getkyo.io/#/?id=routes-http-server-via-tapir) +* [Tapir running as Netty-based server(ZIO)](https://tapir.softwaremill.com/en/latest/server/netty.html) +* [Tapir running as pekko-http server](https://tapir.softwaremill.com/en/latest/server/pekkohttp.html) + +## Test URLs + +* JSON - http://localhost:8080/json +* PLAINTEXT - http://localhost:8080/plaintext diff --git a/frameworks/Scala/tapir/benchmark_config.json b/frameworks/Scala/tapir/benchmark_config.json new file mode 100644 index 00000000000..96c27368335 --- /dev/null +++ b/frameworks/Scala/tapir/benchmark_config.json @@ -0,0 +1,140 @@ +{ + "framework": "tapir", + "tests": [ + { + "default": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "port": 8080, + "database": "None", + "approach": "Realistic", + "classification": "Micro", + "framework": "tapir", + "language": "Scala", + "flavor": "None", + "orm": "Raw", + "platform": "Netty", + "webserver": "None", + "database_os": "Linux", + "os": "Linux", + "display_name": "tapir as zio-http server", + "notes": "https://tapir.softwaremill.com/en/latest/server/ziohttp.html", + "versus": "None" + }, + "http4s": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "port": 8080, + "database": "None", + "approach": "Realistic", + "classification": "Micro", + "framework": "tapir", + "language": "Scala", + "flavor": "None", + "orm": "Raw", + "platform": "Netty", + "webserver": "None", + "database_os": "Linux", + "os": "Linux", + "display_name": "tapir as http4s server", + "notes": "https://tapir.softwaremill.com/en/latest/server/http4s.html", + "versus": "None" + }, + "http4s-zio": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "port": 8080, + "database": "None", + "approach": "Realistic", + "classification": "Micro", + "framework": "tapir", + "language": "Scala", + "flavor": "None", + "orm": "Raw", + "platform": "Netty", + "webserver": "None", + "database_os": "Linux", + "os": "Linux", + "display_name": "tapir as http4s server using ZIO", + "notes": "https://tapir.softwaremill.com/en/latest/server/zio-http4s.html", + "versus": "None" + }, + "netty-cats": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "port": 8080, + "database": "None", + "approach": "Realistic", + "classification": "Micro", + "framework": "tapir", + "language": "Scala", + "flavor": "None", + "orm": "Raw", + "platform": "Netty", + "webserver": "None", + "database_os": "Linux", + "os": "Linux", + "display_name": "tapir as netty(cats) server", + "notes": "https://tapir.softwaremill.com/en/latest/server/netty.html", + "versus": "None" + }, + "netty-kyo": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "port": 8080, + "database": "None", + "approach": "Realistic", + "classification": "Micro", + "framework": "tapir", + "language": "Scala", + "flavor": "None", + "orm": "Raw", + "platform": "Netty", + "webserver": "None", + "database_os": "Linux", + "os": "Linux", + "display_name": "tapir as netty(kyo) server", + "notes": "https://getkyo.io/#/?id=routes-http-server-via-tapir", + "versus": "None" + }, + "netty-zio": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "port": 8080, + "database": "None", + "approach": "Realistic", + "classification": "Micro", + "framework": "tapir", + "language": "Scala", + "flavor": "None", + "orm": "Raw", + "platform": "Netty", + "webserver": "None", + "database_os": "Linux", + "os": "Linux", + "display_name": "tapir as netty(zio) server", + "notes": "https://tapir.softwaremill.com/en/latest/server/netty.html", + "versus": "None" + }, + "pekko-http": { + "plaintext_url": "/plaintext", + "json_url": "/json", + "port": 8080, + "database": "None", + "approach": "Realistic", + "classification": "Micro", + "framework": "tapir", + "language": "Scala", + "flavor": "None", + "orm": "Raw", + "platform": "Netty", + "webserver": "None", + "database_os": "Linux", + "os": "Linux", + "display_name": "tapir as pekko-http server", + "notes": "https://tapir.softwaremill.com/en/latest/server/pekkohttp.html", + "versus": "None" + } + } + ] +} \ No newline at end of file diff --git a/frameworks/Scala/tapir/build.sbt b/frameworks/Scala/tapir/build.sbt new file mode 100644 index 00000000000..99947e9a16c --- /dev/null +++ b/frameworks/Scala/tapir/build.sbt @@ -0,0 +1,100 @@ +name := "tapir-benchmark" + +ThisBuild / version := "1.0.0" +ThisBuild / scalaVersion := "3.6.4" + +val tapirVersion = "1.11.24" + +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) +} + +lazy val common = (project in file("common")) + .settings( + name := "tapir-benchmark-common" + ) + +lazy val `zio-http-server` = (project in file("zio-http-server")) + .dependsOn(common) + .settings( + name := "tapir-zio-http-server", + libraryDependencies ++= Seq( + "com.softwaremill.sttp.tapir" %% "tapir-zio-http-server" % tapirVersion, + "com.softwaremill.sttp.tapir" %% "tapir-json-zio" % tapirVersion + ), + commonAssemblySettings + ) + +lazy val `http4s-server` = (project in file("http4s-server")) + .dependsOn(common) + .settings( + name := "tapir-http4s-server", + libraryDependencies ++= Seq( + "com.softwaremill.sttp.tapir" %% "tapir-http4s-server" % tapirVersion, + "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % tapirVersion, + "org.http4s" %% "http4s-blaze-server" % "0.23.17", + ), + commonAssemblySettings + ) + +lazy val `http4s-server-zio` = (project in file("http4s-server-zio")) + .dependsOn(common) + .settings( + name := "tapir-http4s-server-zio", + libraryDependencies ++= Seq( + "com.softwaremill.sttp.tapir" %% "tapir-http4s-server-zio" % tapirVersion, + "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % tapirVersion, + "org.http4s" %% "http4s-blaze-server" % "0.23.17", + "dev.zio" %% "zio-interop-cats" % "23.1.0.5" + ), + commonAssemblySettings + ) + +lazy val `netty-kyo-server` = (project in file("netty-kyo-server")) + .dependsOn(common) + .settings( + name := "tapir-netty-kyo-server", + libraryDependencies ++= Seq( + "io.getkyo" %% "kyo-tapir" % "0.18.0", + "com.softwaremill.sttp.tapir" %% "tapir-json-zio" % tapirVersion + ), + commonAssemblySettings + ) + +lazy val `netty-zio-server` = (project in file("netty-zio-server")) + .dependsOn(common) + .settings( + name := "tapir-netty-zio-server", + libraryDependencies ++= Seq( + "com.softwaremill.sttp.tapir" %% "tapir-netty-server-zio" % tapirVersion, + "com.softwaremill.sttp.tapir" %% "tapir-json-zio" % tapirVersion, + ), + commonAssemblySettings + ) + +lazy val `netty-cats-server` = (project in file("netty-cats-server")) + .dependsOn(common) + .settings( + name := "tapir-netty-cats-server", + libraryDependencies ++= Seq( + "com.softwaremill.sttp.tapir" %% "tapir-netty-server-cats" % tapirVersion, + "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % tapirVersion, + "org.http4s" %% "http4s-blaze-server" % "0.23.17", + ), + commonAssemblySettings + ) + +lazy val `pekko-http-server` = (project in file("pekko-http-server")) + .dependsOn(common) + .settings( + name := "tapir-pekko-http-server", + libraryDependencies ++= Seq( + "com.softwaremill.sttp.tapir" %% "tapir-pekko-http-server" % tapirVersion, + "com.softwaremill.sttp.tapir" %% "tapir-json-circe" % tapirVersion, + ), + commonAssemblySettings + ) diff --git a/frameworks/Scala/tapir/common/src/main/scala/Payload.scala b/frameworks/Scala/tapir/common/src/main/scala/Payload.scala new file mode 100644 index 00000000000..2b886ef91b8 --- /dev/null +++ b/frameworks/Scala/tapir/common/src/main/scala/Payload.scala @@ -0,0 +1 @@ +case class Payload(message: String) diff --git a/frameworks/Scala/tapir/config.toml b/frameworks/Scala/tapir/config.toml new file mode 100644 index 00000000000..895f262659b --- /dev/null +++ b/frameworks/Scala/tapir/config.toml @@ -0,0 +1,93 @@ +[framework] +name = "tapir" + +[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" +approach = "Realistic" +classification = "Micro" +database = "None" +database_os = "Linux" +os = "Linux" +orm = "Raw" +platform = "Netty" +webserver = "None" +versus = "None" + +[http4s-zio] +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" + +[netty-cats] +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" + +[netty-kyo] +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" + +[netty-zio] +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" + +[pekko-http] +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" \ No newline at end of file diff --git a/frameworks/Scala/tapir/http4s-server-zio/src/main/scala/TapirHttp4sServerZioBenchmark.scala b/frameworks/Scala/tapir/http4s-server-zio/src/main/scala/TapirHttp4sServerZioBenchmark.scala new file mode 100644 index 00000000000..8e93bd7445c --- /dev/null +++ b/frameworks/Scala/tapir/http4s-server-zio/src/main/scala/TapirHttp4sServerZioBenchmark.scala @@ -0,0 +1,55 @@ +import io.circe.generic.auto.* +import org.http4s.HttpRoutes +import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.server.Router +import sttp.model.{Header, HeaderNames, StatusCode} +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* +import sttp.tapir.server.http4s.ztapir.ZHttp4sServerInterpreter +import sttp.tapir.ztapir.* +import zio.* + +import java.time.format.DateTimeFormatter +import java.time.{Clock, LocalDateTime, ZoneOffset, ZonedDateTime} +import java.util.Date +import java.util.concurrent.locks.LockSupport +import scala.concurrent.ExecutionContext +import zio.interop.catz.* + +object TapirHttp4sServerZioBenchmark extends ZIOAppDefault: + private val STATIC_SERVER_NAME = "tapir-http4s-server-zio" + + private val plainTextMessage: String = "Hello, World!" + + private val plaintextEndpoint: ZServerEndpoint[Any, Any] = + endpoint.get.in("plaintext") + .out(header(HeaderNames.Server, STATIC_SERVER_NAME)) + .out(stringBody) + .zServerLogic(_ => ZIO.succeed(plainTextMessage)) + + private val jsonEndpoint: ZServerEndpoint[Any, Any] = + endpoint.get.in("json") + .out(header(HeaderNames.Server, STATIC_SERVER_NAME)) + .out(jsonBody[Payload]) + .zServerLogic(_ => ZIO.succeed(Payload(plainTextMessage))) + + + private val declaredPort = 8080 + private val declaredHost = "0.0.0.0" + + private val routes: HttpRoutes[Task] = ZHttp4sServerInterpreter() + .from(List(plaintextEndpoint, jsonEndpoint)) + .toRoutes + + implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global + + override def run: URIO[Any, ExitCode] = + ZIO.executor.flatMap(executor => + BlazeServerBuilder[Task] + .withExecutionContext(executor.asExecutionContext) + .bindHttp(8080, declaredHost) + .withHttpApp(Router("/" -> routes).orNotFound) + .serve + .compile + .drain + ).exitCode diff --git a/frameworks/Scala/tapir/http4s-server/src/main/scala/TapirHttp4sServerBenchmark.scala b/frameworks/Scala/tapir/http4s-server/src/main/scala/TapirHttp4sServerBenchmark.scala new file mode 100644 index 00000000000..f8dadd8d446 --- /dev/null +++ b/frameworks/Scala/tapir/http4s-server/src/main/scala/TapirHttp4sServerBenchmark.scala @@ -0,0 +1,50 @@ +import cats.effect.{ExitCode, IO, IOApp} +import io.circe.generic.auto.* +import org.http4s.HttpRoutes +import org.http4s.blaze.server.BlazeServerBuilder +import org.http4s.server.Router +import sttp.model.{Header, HeaderNames, StatusCode} +import sttp.tapir.* +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* +import sttp.tapir.server.http4s.Http4sServerInterpreter + +import java.time.format.DateTimeFormatter +import java.time.{Clock, LocalDateTime, ZoneOffset, ZonedDateTime} +import java.util.Date +import java.util.concurrent.locks.LockSupport +import scala.concurrent.ExecutionContext + +object TapirHttp4sServerBenchmark extends IOApp: + private val STATIC_SERVER_NAME = "tapir-http4s-server" + + private val plainTextMessage: String = "Hello, World!" + + val plaintextEndpoint = + endpoint.get.in("plaintext") + .out(header(HeaderNames.Server, STATIC_SERVER_NAME)) + .out(stringBody) + .serverLogic(_ => IO.pure[Either[Unit, String]](Right(plainTextMessage))) + + val jsonEndpoint = + endpoint.get.in("json") + .out(header(HeaderNames.Server, STATIC_SERVER_NAME)) + .out(jsonBody[Payload]) + .serverLogic(_ => IO.pure[Either[Unit, Payload]](Right(Payload(plainTextMessage)))) + + + private val declaredPort = 8080 + private val declaredHost = "0.0.0.0" + + val routes = Http4sServerInterpreter[IO]().toRoutes(List(plaintextEndpoint, jsonEndpoint)) + + implicit val ec: ExecutionContext = scala.concurrent.ExecutionContext.Implicits.global + + override def run(args: List[String]): IO[ExitCode] = + BlazeServerBuilder[IO] + .withExecutionContext(ec) + .bindHttp(declaredPort, declaredHost) + .withHttpApp(Router("/" -> routes).orNotFound) + .resource + .useForever + .as(ExitCode.Success) diff --git a/frameworks/Scala/tapir/netty-cats-server/src/main/scala/TapirNettyCatsServerBenchmark.scala b/frameworks/Scala/tapir/netty-cats-server/src/main/scala/TapirNettyCatsServerBenchmark.scala new file mode 100644 index 00000000000..824b538a079 --- /dev/null +++ b/frameworks/Scala/tapir/netty-cats-server/src/main/scala/TapirNettyCatsServerBenchmark.scala @@ -0,0 +1,54 @@ +import cats.effect.{IO, IOApp} +import io.circe.generic.auto.* +import sttp.model.{Header, HeaderNames} +import sttp.tapir.* +import sttp.tapir.generic.auto.* +import sttp.tapir.json.circe.* +import sttp.tapir.server.netty.cats.NettyCatsServer + +import java.time.Instant + +object TapirNettyCatsServerBenchmark extends IOApp.Simple: + private val STATIC_SERVER_NAME = "tapir-netty-cats-server" + + private val plainTextMessage: String = "Hello, World!" + + val plaintextEndpoint = + endpoint.get.in("plaintext") + .out(header(HeaderNames.Server, STATIC_SERVER_NAME)) + .out(header[String](HeaderNames.Date)) + .out(stringBody) + .serverLogic(_ => + for { + now <- IO.realTime.map(time => Instant.ofEpochMilli(time.toMillis)) + } yield Right(Header.toHttpDateString(now) -> plainTextMessage) + ) + + val jsonEndpoint = + endpoint.get.in("json") + .out(header(HeaderNames.Server, STATIC_SERVER_NAME)) + .out(header[String](HeaderNames.Date)) + .out(jsonBody[Payload]) + .serverLogic(_ => + for { + now <- IO.realTime.map(time => Instant.ofEpochMilli(time.toMillis)) + } yield Right(Header.toHttpDateString(now) -> Payload(plainTextMessage)) + ) + + + private val declaredPort = 8080 + private val declaredHost = "0.0.0.0" + + override def run = NettyCatsServer + .io() + .use { server => + for { + _ <- server + .port(declaredPort) + .host(declaredHost) + .addEndpoint(plaintextEndpoint) + .addEndpoint(jsonEndpoint) + .start() + _ <- IO.never + } yield () + } diff --git a/frameworks/Scala/kyo-tapir/src/main/scala/Main.scala b/frameworks/Scala/tapir/netty-kyo-server/src/main/scala/TapirNettyKyoServerBenchmark.scala similarity index 83% rename from frameworks/Scala/kyo-tapir/src/main/scala/Main.scala rename to frameworks/Scala/tapir/netty-kyo-server/src/main/scala/TapirNettyKyoServerBenchmark.scala index 6c543de17e2..f8cbdb4618c 100644 --- a/frameworks/Scala/kyo-tapir/src/main/scala/Main.scala +++ b/frameworks/Scala/tapir/netty-kyo-server/src/main/scala/TapirNettyKyoServerBenchmark.scala @@ -3,12 +3,16 @@ import sttp.model.{Header, HeaderNames} import sttp.tapir.* import sttp.tapir.json.zio.* import sttp.tapir.server.netty.* +import zio.json.* -object Main extends KyoApp { +object TapirNettyKyoServerBenchmark extends KyoApp { private val STATIC_SERVER_NAME = "kyo-tapir" private val plainTextMessage: String = "Hello, World!" + private given JsonCodec[Payload] = DeriveJsonCodec.gen + private given Schema[Payload] = Schema.derived + run { val plaintextRoute: Unit < Routes = Routes.add( @@ -38,7 +42,7 @@ object Main extends KyoApp { .withSocketKeepAlive .copy(lingerTimeout = None) - val server = NettyKyoServer(config).host("0.0.0.0").port(9999) + val server = NettyKyoServer(config).host("0.0.0.0").port(8080) val binding: NettyKyoServerBinding < Async = Routes.run(server)(plaintextRoute.andThen(jsonRoute)) diff --git a/frameworks/Scala/tapir/netty-zio-server/src/main/scala/TapirNettyZioServerBenchmark.scala b/frameworks/Scala/tapir/netty-zio-server/src/main/scala/TapirNettyZioServerBenchmark.scala new file mode 100644 index 00000000000..fbd2fa34477 --- /dev/null +++ b/frameworks/Scala/tapir/netty-zio-server/src/main/scala/TapirNettyZioServerBenchmark.scala @@ -0,0 +1,57 @@ +import sttp.model.{Header, HeaderNames} +import sttp.tapir.Schema +import sttp.tapir.json.zio.* +import sttp.tapir.server.netty.zio.NettyZioServer +import sttp.tapir.ztapir.* +import zio.* +import zio.json.* +import sttp.tapir.server.netty.* + +object TapirNettyZioServerBenchmark extends ZIOAppDefault { + private val STATIC_SERVER_NAME = "tapir-netty-zio-server" + + private val plainTextMessage: String = "Hello, World!" + + given JsonCodec[Payload] = DeriveJsonCodec.gen + given Schema[Payload] = Schema.derived + + override def run = { + val plaintextRoute: ZServerEndpoint[Any, Any] = + endpoint.get.in("plaintext") + .out(header(HeaderNames.Server, STATIC_SERVER_NAME)) + .out(header[String](HeaderNames.Date)) + .out(stringBody) + .zServerLogic { _ => + for { + now <- Clock.currentDateTime + } yield Header.toHttpDateString(now.toInstant) -> plainTextMessage + } + + val jsonRoute: ZServerEndpoint[Any, Any] = + endpoint.get.in("json") + .out(header(HeaderNames.Server, STATIC_SERVER_NAME)) + .out(header[String](HeaderNames.Date)) + .out(jsonBody[Payload]) + .zServerLogic { _ => + for { + now <- Clock.currentDateTime + } yield Header.toHttpDateString(now.toInstant) -> Payload(plainTextMessage) + } + + val config = NettyConfig.default + .withSocketKeepAlive + .copy(lingerTimeout = None) + + + + val server = NettyZioServer[Any](config) + .addEndpoint(plaintextRoute) + .addEndpoint(jsonRoute) + .host("0.0.0.0") + .port(8080) + + server.start().flatMap { _ => + ZIO.never + } + } +} \ No newline at end of file diff --git a/frameworks/Scala/tapir/pekko-http-server/src/main/scala/Main.scala b/frameworks/Scala/tapir/pekko-http-server/src/main/scala/Main.scala new file mode 100644 index 00000000000..ebcdd901368 --- /dev/null +++ b/frameworks/Scala/tapir/pekko-http-server/src/main/scala/Main.scala @@ -0,0 +1,42 @@ +import io.circe.generic.auto.* +import org.apache.pekko.actor.ActorSystem +import org.apache.pekko.http.scaladsl.Http +import sttp.model.HeaderNames +import sttp.tapir.* +import sttp.tapir.json.circe.* +import sttp.tapir.server.pekkohttp.PekkoHttpServerInterpreter + +import scala.concurrent.Future + +@main def tapirPekkoServerBenchmark(): Unit = { + implicit val actorSystem: ActorSystem = ActorSystem() + import actorSystem.dispatcher + + val STATIC_SERVER_NAME = "tapir-pekko-http-server" + + val plainTextMessage: String = "Hello, World!" + + given Schema[Payload] = Schema.derived + + val plaintextRoute = + endpoint.get + .in("plaintext") + .out(header(HeaderNames.Server, STATIC_SERVER_NAME)) + .out(stringBody) + .serverLogic[Future] { _ => + Future.successful(Right(plainTextMessage)) + } + + val jsonRoute = + endpoint.get + .in("json") + .out(header(HeaderNames.Server, STATIC_SERVER_NAME)) + .out(jsonBody[Payload]) + .serverLogic[Future] { _ => + Future.successful(Right(Payload(plainTextMessage))) + } + + val route = PekkoHttpServerInterpreter().toRoute(List(plaintextRoute, jsonRoute)) + + Http().newServerAt("0.0.0.0", 8080).bindFlow(route) +} \ No newline at end of file diff --git a/frameworks/Scala/tapir/project/build.properties b/frameworks/Scala/tapir/project/build.properties new file mode 100644 index 00000000000..00a19d65b65 --- /dev/null +++ b/frameworks/Scala/tapir/project/build.properties @@ -0,0 +1 @@ +sbt.version = 1.10.11 \ No newline at end of file diff --git a/frameworks/Scala/kyo-tapir/project/plugins.sbt b/frameworks/Scala/tapir/project/plugins.sbt similarity index 100% rename from frameworks/Scala/kyo-tapir/project/plugins.sbt rename to frameworks/Scala/tapir/project/plugins.sbt diff --git a/frameworks/Scala/tapir/tapir-http4s-zio.dockerfile b/frameworks/Scala/tapir/tapir-http4s-zio.dockerfile new file mode 100644 index 00000000000..7ef30135459 --- /dev/null +++ b/frameworks/Scala/tapir/tapir-http4s-zio.dockerfile @@ -0,0 +1,11 @@ +FROM sbtscala/scala-sbt:eclipse-temurin-21.0.6_7_1.10.11_3.6.4 + +WORKDIR /tapir-benchmark +COPY http4s-server-zio http4s-server-zio +COPY common common +COPY project project +COPY build.sbt build.sbt +RUN sbt http4s-server-zio/assembly + +EXPOSE 8080 +CMD ["java", "-Xms2G", "-Xmx2G", "-server", "-jar", "/tapir-benchmark/http4s-server-zio/target/scala-3.6.4/tapir-http4s-server-zio-assembly-1.0.0.jar"] \ No newline at end of file diff --git a/frameworks/Scala/tapir/tapir-http4s.dockerfile b/frameworks/Scala/tapir/tapir-http4s.dockerfile new file mode 100644 index 00000000000..892210bc692 --- /dev/null +++ b/frameworks/Scala/tapir/tapir-http4s.dockerfile @@ -0,0 +1,11 @@ +FROM sbtscala/scala-sbt:eclipse-temurin-21.0.6_7_1.10.11_3.6.4 + +WORKDIR /tapir-benchmark +COPY http4s-server http4s-server +COPY common common +COPY project project +COPY build.sbt build.sbt +RUN sbt http4s-server/assembly + +EXPOSE 8080 +CMD ["java", "-Xms2G", "-Xmx2G", "-server", "-jar", "/tapir-benchmark/http4s-server/target/scala-3.6.4/tapir-http4s-server-assembly-1.0.0.jar"] \ No newline at end of file diff --git a/frameworks/Scala/tapir/tapir-netty-cats.dockerfile b/frameworks/Scala/tapir/tapir-netty-cats.dockerfile new file mode 100644 index 00000000000..5dec450e3d5 --- /dev/null +++ b/frameworks/Scala/tapir/tapir-netty-cats.dockerfile @@ -0,0 +1,11 @@ +FROM sbtscala/scala-sbt:eclipse-temurin-21.0.6_7_1.10.11_3.6.4 + +WORKDIR /tapir-benchmark +COPY netty-cats-server netty-cats-server +COPY common common +COPY project project +COPY build.sbt build.sbt +RUN sbt netty-cats-server/assembly + +EXPOSE 8080 +CMD ["java", "-Xms2G", "-Xmx2G", "-server", "-Dio.netty.leakDetection.level=disabled", "-Dio.netty.recycler.maxCapacityPerThread=0", "-jar", "/tapir-benchmark/netty-cats-server/target/scala-3.6.4/tapir-netty-cats-server-assembly-1.0.0.jar"] \ No newline at end of file diff --git a/frameworks/Scala/tapir/tapir-netty-kyo.dockerfile b/frameworks/Scala/tapir/tapir-netty-kyo.dockerfile new file mode 100644 index 00000000000..ed7a5991624 --- /dev/null +++ b/frameworks/Scala/tapir/tapir-netty-kyo.dockerfile @@ -0,0 +1,11 @@ +FROM sbtscala/scala-sbt:eclipse-temurin-21.0.6_7_1.10.11_3.6.4 + +WORKDIR /tapir-benchmark +COPY netty-kyo-server netty-kyo-server +COPY common common +COPY project project +COPY build.sbt build.sbt +RUN sbt netty-kyo-server/assembly + +EXPOSE 8080 +CMD ["java", "-Xms2G", "-Xmx2G", "-server", "-Dio.netty.leakDetection.level=disabled", "-Dio.netty.recycler.maxCapacityPerThread=0", "-jar", "/tapir-benchmark/netty-kyo-server/target/scala-3.6.4/tapir-netty-kyo-server-assembly-1.0.0.jar"] \ No newline at end of file diff --git a/frameworks/Scala/tapir/tapir-netty-zio.dockerfile b/frameworks/Scala/tapir/tapir-netty-zio.dockerfile new file mode 100644 index 00000000000..aa9c3607080 --- /dev/null +++ b/frameworks/Scala/tapir/tapir-netty-zio.dockerfile @@ -0,0 +1,11 @@ +FROM sbtscala/scala-sbt:eclipse-temurin-21.0.6_7_1.10.11_3.6.4 + +WORKDIR /tapir-benchmark +COPY netty-zio-server netty-zio-server +COPY common common +COPY project project +COPY build.sbt build.sbt +RUN sbt netty-zio-server/assembly + +EXPOSE 8080 +CMD ["java", "-Xms2G", "-Xmx2G", "-server", "-Dio.netty.leakDetection.level=disabled", "-Dio.netty.recycler.maxCapacityPerThread=0", "-jar", "/tapir-benchmark/netty-zio-server/target/scala-3.6.4/tapir-netty-zio-server-assembly-1.0.0.jar"] \ No newline at end of file diff --git a/frameworks/Scala/tapir/tapir-pekko-http.dockerfile b/frameworks/Scala/tapir/tapir-pekko-http.dockerfile new file mode 100644 index 00000000000..f21480ae5be --- /dev/null +++ b/frameworks/Scala/tapir/tapir-pekko-http.dockerfile @@ -0,0 +1,11 @@ +FROM sbtscala/scala-sbt:eclipse-temurin-21.0.6_7_1.10.11_3.6.4 + +WORKDIR /tapir-benchmark +COPY pekko-http-server pekko-http-server +COPY common common +COPY project project +COPY build.sbt build.sbt +RUN sbt pekko-http-server/assembly + +EXPOSE 8080 +CMD ["java", "-Xms2G", "-Xmx2G", "-server", "-jar", "/tapir-benchmark/pekko-http-server/target/scala-3.6.4/tapir-pekko-http-server-assembly-1.0.0.jar"] \ No newline at end of file diff --git a/frameworks/Scala/tapir/tapir.dockerfile b/frameworks/Scala/tapir/tapir.dockerfile new file mode 100644 index 00000000000..33e2438eea7 --- /dev/null +++ b/frameworks/Scala/tapir/tapir.dockerfile @@ -0,0 +1,11 @@ +FROM sbtscala/scala-sbt:eclipse-temurin-21.0.6_7_1.10.11_3.6.4 + +WORKDIR /tapir-benchmark +COPY zio-http-server zio-http-server +COPY common common +COPY project project +COPY build.sbt build.sbt +RUN sbt zio-http-server/assembly + +EXPOSE 8080 +CMD ["java", "-Xms2G", "-Xmx2G", "-server", "-Dio.netty.leakDetection.level=disabled", "-Dio.netty.recycler.maxCapacityPerThread=0", "-jar", "/tapir-benchmark/zio-http-server/target/scala-3.6.4/tapir-zio-http-server-assembly-1.0.0.jar"] \ No newline at end of file diff --git a/frameworks/Scala/tapir/zio-http-server/src/main/scala/TapirZioHttpServerBenchmark.scala b/frameworks/Scala/tapir/zio-http-server/src/main/scala/TapirZioHttpServerBenchmark.scala new file mode 100644 index 00000000000..59e189b01bd --- /dev/null +++ b/frameworks/Scala/tapir/zio-http-server/src/main/scala/TapirZioHttpServerBenchmark.scala @@ -0,0 +1,56 @@ +import sttp.model.{Header, HeaderNames} +import sttp.tapir.Schema +import sttp.tapir.json.zio.* +import sttp.tapir.server.ziohttp.ZioHttpInterpreter +import sttp.tapir.ztapir.* +import zio.* +import zio.http.netty.NettyConfig +import zio.http.netty.NettyConfig.LeakDetectionLevel +import zio.http.{Request, Response, Routes, Server} +import zio.json.* + +import java.lang.Runtime as JRuntime + +object TapirZioHttpServerBenchmark extends ZIOAppDefault { + private val STATIC_SERVER_NAME = "zio-http-tapir" + + private val plainTextMessage: String = "Hello, World!" + private val NUM_PROCESSORS = JRuntime.getRuntime.availableProcessors() + + given JsonCodec[Payload] = DeriveJsonCodec.gen + given Schema[Payload] = Schema.derived + + override def run = { + val plaintextRoute: ZServerEndpoint[Any, Any] = + endpoint.get.in("plaintext") + .out(header(HeaderNames.Server, STATIC_SERVER_NAME)) + .out(stringBody) + .zServerLogic { _ => + ZIO.succeed(plainTextMessage) + } + + val jsonRoute: ZServerEndpoint[Any, Any] = + endpoint.get.in("json") + .out(header(HeaderNames.Server, STATIC_SERVER_NAME)) + .out(jsonBody[Payload]) + .zServerLogic { _ => + ZIO.succeed(Payload(plainTextMessage)) + } + + val app = ZioHttpInterpreter().toHttp(List(plaintextRoute, jsonRoute)) + + + val config = Server.Config.default + .port(8080) + .enableRequestStreaming + + val nettyConfig = NettyConfig.default + .leakDetection(LeakDetectionLevel.DISABLED) + .maxThreads(NUM_PROCESSORS) + + val configLayer = ZLayer.succeed(config) + val nettyConfigLayer = ZLayer.succeed(nettyConfig) + + Server.serve(app).provide(configLayer, nettyConfigLayer, Server.customized).exitCode + } +} \ No newline at end of file diff --git a/frameworks/Scala/zio-http/README.md b/frameworks/Scala/zio-http/README.md index b5ea957391f..e9eb2759004 100644 --- a/frameworks/Scala/zio-http/README.md +++ b/frameworks/Scala/zio-http/README.md @@ -9,7 +9,7 @@ This is the ZIO Http portion of a [benchmarking test suite](../) comparing a var ## Versions -* [Java OpenJDK 11](https://openjdk.java.net/) +* [Java OpenJDK 21](https://openjdk.java.net/) ## Test URLs diff --git a/frameworks/Scala/zio-http/build.sbt b/frameworks/Scala/zio-http/build.sbt index a985d9b5dc9..4c0157b1354 100644 --- a/frameworks/Scala/zio-http/build.sbt +++ b/frameworks/Scala/zio-http/build.sbt @@ -1,9 +1,9 @@ name := "zio-http" version := "1.0.0" -scalaVersion := "2.13.14" +scalaVersion := "2.13.16" lazy val root = (project in file(".")) .settings( - libraryDependencies += "dev.zio" %% "zio-http" % "3.0.0-RC10", + libraryDependencies += "dev.zio" %% "zio-http" % "3.2.0", testFrameworks += new TestFramework("zio.test.sbt.ZTestFramework"), assembly / assemblyMergeStrategy := { case x if x.contains("io.netty.versions.properties") => MergeStrategy.discard diff --git a/frameworks/Scala/zio-http/src/main/scala/Main.scala b/frameworks/Scala/zio-http/src/main/scala/Main.scala index 8af102fd7f6..652a4a43357 100644 --- a/frameworks/Scala/zio-http/src/main/scala/Main.scala +++ b/frameworks/Scala/zio-http/src/main/scala/Main.scala @@ -2,12 +2,13 @@ import zio._ import zio.http._ import zio.http.netty.NettyConfig import zio.http.netty.NettyConfig.LeakDetectionLevel +import zio.json.EncoderOps + import java.lang.{Runtime => JRuntime} object Main extends ZIOAppDefault { private val plainTextMessage: String = "hello, world!" - private val jsonMessage: String = """{"message": "hello, world!"}""" private val STATIC_SERVER_NAME = "zio-http" private val NUM_PROCESSORS = JRuntime.getRuntime.availableProcessors() @@ -22,7 +23,7 @@ object Main extends ZIOAppDefault { Method.GET / "/json" -> Handler.fromResponse( Response - .json(jsonMessage) + .json(Payload(plainTextMessage).toJson) .addHeader(Header.Server(STATIC_SERVER_NAME)), ), ) diff --git a/frameworks/Scala/zio-http/src/main/scala/Payload.scala b/frameworks/Scala/zio-http/src/main/scala/Payload.scala new file mode 100644 index 00000000000..86b4fc705d7 --- /dev/null +++ b/frameworks/Scala/zio-http/src/main/scala/Payload.scala @@ -0,0 +1,6 @@ +import zio.json.{DeriveJsonCodec, JsonCodec} + +case class Payload(message: String) +object Payload { + implicit val codec: JsonCodec[Payload] = DeriveJsonCodec.gen +} \ No newline at end of file diff --git a/frameworks/Scala/zio-http/zio-http.dockerfile b/frameworks/Scala/zio-http/zio-http.dockerfile index f05ead620fe..5c814e5887a 100644 --- a/frameworks/Scala/zio-http/zio-http.dockerfile +++ b/frameworks/Scala/zio-http/zio-http.dockerfile @@ -1,4 +1,4 @@ -FROM hseeberger/scala-sbt:11.0.12_1.5.5_2.13.6 +FROM sbtscala/scala-sbt:eclipse-temurin-21.0.6_7_1.10.11_2.13.16 WORKDIR /zhttp COPY src src