Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance tests part 1 #3434

Merged
merged 60 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
767d9c2
wip
kciesielski Dec 15, 2023
1998a34
Dynamic perf test runner
kciesielski Jan 2, 2024
32ba29e
Merge branch 'master' into perf-tests-2023
kciesielski Jan 2, 2024
f2c5d79
Restore netty server dependency
kciesielski Jan 2, 2024
7347583
Remove unneeded files
kciesielski Jan 2, 2024
67973b1
Remove unneded build utils
kciesielski Jan 2, 2024
917dfeb
Apply base simulation to all sims
kciesielski Jan 2, 2024
4695510
Remove old way of running perf tests
kciesielski Jan 2, 2024
7f8110b
Fix file size to 5MB
kciesielski Jan 2, 2024
4342d97
Explain why a command is used
kciesielski Jan 8, 2024
6a9bc15
Parameterize active user count
kciesielski Jan 8, 2024
5bcc177
Switch from Akka to Pekko
kciesielski Jan 8, 2024
fc414b2
Add Netty Cats server
kciesielski Jan 8, 2024
881f2c3
Add Play server
kciesielski Jan 8, 2024
01536c0
Merge branch 'master' into perf-tests-2023
kciesielski Jan 8, 2024
5ec7484
Vanilla Play server skeleton
kciesielski Jan 8, 2024
987ccd1
Finish Play
kciesielski Jan 8, 2024
dc71db9
Refator vanilla Play
kciesielski Jan 9, 2024
01f91bc
Use new ActorSystem for each Pekko/Play server run
kciesielski Jan 10, 2024
2aa228a
Initial suite runner and log processor
kciesielski Jan 11, 2024
eda178b
Save HTML report
kciesielski Jan 11, 2024
13291a0
CSV reported and inmprovements to HTML reporter
kciesielski Jan 11, 2024
50a191d
Ensure same filename for all reports
kciesielski Jan 11, 2024
1449769
Allow running all servers or all simulations with '*'
kciesielski Jan 11, 2024
9f44abf
Parse arguments
kciesielski Jan 15, 2024
55d3daa
Remove sbt command
kciesielski Jan 15, 2024
97093a2
Tweak HTML report style
kciesielski Jan 16, 2024
3fbe5a5
Allow skipping Gatling reports
kciesielski Jan 16, 2024
cfd243c
Add Vert.x Tapir server
kciesielski Jan 16, 2024
d677a0e
Skip duplicates
kciesielski Jan 17, 2024
4311fb6
Finish Vertx vanilla server
kciesielski Jan 17, 2024
73e0506
Display servers as rows in HTML
kciesielski Jan 17, 2024
3b0969c
Disable Gatling reports by defaults, improve flag
kciesielski Jan 17, 2024
af44696
Add missing endpoints to http4s Vanilla
kciesielski Jan 17, 2024
42df183
Add missing vanilla endpoints for Pekko
kciesielski Jan 17, 2024
90fe6e6
Refactoring and cleanup
kciesielski Jan 17, 2024
a5146c0
Fix netty-cats server shutdown
kciesielski Jan 17, 2024
3ffd82e
Use constant
kciesielski Jan 17, 2024
0b86d1d
Merge branch 'master' into perf-tests-2023
kciesielski Jan 17, 2024
8732f0d
Minor tweaks in Vertx vanilla file endpoint
kciesielski Jan 18, 2024
33763ee
Use text/plain because otherwise Play complains
kciesielski Jan 19, 2024
3505216
Sort rows in HTML report
kciesielski Jan 19, 2024
5fa603f
Fixes
kciesielski Jan 19, 2024
f303791
Add Vertx Cats Effect server
kciesielski Jan 19, 2024
1bfc354
Many improvements
kciesielski Jan 19, 2024
0188b7b
Fix warmup
kciesielski Jan 22, 2024
ce012a2
Merge branch 'master' into perf-tests-2023
kciesielski Jan 22, 2024
818c953
Include warmup in total duration estimations
kciesielski Jan 22, 2024
a4ceeb9
Restore
kciesielski Jan 22, 2024
cb814cb
Display default values of optional parameters
kciesielski Jan 22, 2024
f8dbbc1
Disable logging
kciesielski Jan 22, 2024
1bf6442
List available servers and sims in the help view
kciesielski Jan 22, 2024
76a5e4b
Documentation
kciesielski Jan 22, 2024
c40e455
Merge branch 'master' into perf-tests-2023
kciesielski Jan 22, 2024
6aca7f8
Merge branch 'master' into perf-tests-2023
kciesielski Jan 22, 2024
bc9bad5
Merge remote-tracking branch 'origin/perf-tests-2023' into perf-tests…
kciesielski Jan 22, 2024
6afe52f
Remove accidental double release after merge
kciesielski Jan 22, 2024
f0f9f85
Less personal example ;)
kciesielski Jan 22, 2024
18c11df
Restore ideSkipProject
kciesielski Jan 23, 2024
8904ef2
Add warmup to the info screen
kciesielski Jan 23, 2024
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
44 changes: 13 additions & 31 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq(
}.value,
mimaPreviousArtifacts := Set.empty, // we only use MiMa for `core` for now, using enableMimaSettings
ideSkipProject := (scalaVersion.value == scala2_12) ||
(scalaVersion.value == scala2_13) ||
(scalaVersion.value == scala3) ||
thisProjectRef.value.project.contains("Native") ||
thisProjectRef.value.project.contains("JS"),
bspEnabled := !ideSkipProject.value,
Expand Down Expand Up @@ -496,35 +496,25 @@ lazy val tests: ProjectMatrix = (projectMatrix in file("tests"))
)
.dependsOn(core, files, circeJson, cats)

val akkaHttpVanilla = taskKey[Unit]("akka-http-vanilla")
val akkaHttpTapir = taskKey[Unit]("akka-http-tapir")
val akkaHttpVanillaMulti = taskKey[Unit]("akka-http-vanilla-multi")
val akkaHttpTapirMulti = taskKey[Unit]("akka-http-tapir-multi")
val http4sVanilla = taskKey[Unit]("http4s-vanilla")
val http4sTapir = taskKey[Unit]("http4s-tapir")
val http4sVanillaMulti = taskKey[Unit]("http4s-vanilla-multi")
val http4sTapirMulti = taskKey[Unit]("http4s-tapir-multi")
def genPerfTestTask(servName: String, simName: String) = Def.taskDyn {
Def.task {
(Compile / runMain).toTask(s" sttp.tapir.perf.${servName}Server").value
(Gatling / testOnly).toTask(s" sttp.tapir.perf.${simName}Simulation").value
}
}

lazy val perfTests: ProjectMatrix = (projectMatrix in file("perf-tests"))
.enablePlugins(GatlingPlugin)
.settings(commonJvmSettings)
.settings(
name := "tapir-perf-tests",
libraryDependencies ++= Seq(
"io.gatling.highcharts" % "gatling-charts-highcharts" % "3.10.3" % "test",
"io.gatling" % "gatling-test-framework" % "3.10.3" % "test",
"com.typesafe.akka" %% "akka-http" % Versions.akkaHttp,
"com.typesafe.akka" %% "akka-stream" % Versions.akkaStreams,
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer,
"org.http4s" %% "http4s-server" % Versions.http4s,
// Required to force newer jackson in Pekko, a version that is compatible with Gatling's Jackson dependency
"io.gatling.highcharts" % "gatling-charts-highcharts" % "3.10.3" % "test" exclude (
"com.fasterxml.jackson.core", "jackson-databind"
),
"io.gatling" % "gatling-test-framework" % "3.10.3" % "test" exclude ("com.fasterxml.jackson.core", "jackson-databind"),
"com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.15.1",
"nl.grons" %% "metrics4-scala" % Versions.metrics4Scala % Test,
"com.lihaoyi" %% "scalatags" % Versions.scalaTags % Test,
"com.github.scopt" %% "scopt" % "4.1.0",
"io.github.classgraph" % "classgraph" % "4.8.165" % Test,
"org.http4s" %% "http4s-core" % Versions.http4s,
"org.http4s" %% "http4s-dsl" % Versions.http4s,
"org.http4s" %% "http4s-blaze-server" % Versions.http4sBlazeServer,
"org.typelevel" %%% "cats-effect" % Versions.catsEffect
) ++ loggerDependencies,
publishArtifact := false
Expand All @@ -534,16 +524,8 @@ lazy val perfTests: ProjectMatrix = (projectMatrix in file("perf-tests"))
fork := true,
connectInput := true
)
.settings(akkaHttpVanilla := { (genPerfTestTask("akka.Vanilla", "OneRoute")).value })
.settings(akkaHttpTapir := { (genPerfTestTask("akka.Tapir", "OneRoute")).value })
.settings(akkaHttpVanillaMulti := { (genPerfTestTask("akka.VanillaMulti", "MultiRoute")).value })
.settings(akkaHttpTapirMulti := { (genPerfTestTask("akka.TapirMulti", "MultiRoute")).value })
.settings(http4sVanilla := { (genPerfTestTask("http4s.Vanilla", "OneRoute")).value })
.settings(http4sTapir := { (genPerfTestTask("http4s.Tapir", "OneRoute")).value })
.settings(http4sVanillaMulti := { (genPerfTestTask("http4s.VanillaMulti", "MultiRoute")).value })
.settings(http4sTapirMulti := { (genPerfTestTask("http4s.TapirMulti", "MultiRoute")).value })
.jvmPlatform(scalaVersions = List(scala2_13))
.dependsOn(core, akkaHttpServer, http4sServer)
.dependsOn(core, pekkoHttpServer, http4sServer, nettyServer, nettyServerCats, playServer, vertxServer, vertxServerCats)

// integrations

Expand Down
22 changes: 9 additions & 13 deletions perf-tests/src/main/scala/sttp/tapir/perf/Common.scala
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
package sttp.tapir.perf

import sttp.tapir.{PublicEndpoint, endpoint, path, stringBody}

import scala.io.StdIn
import java.io.File
import java.nio.file.Path
import java.util.Date
import scala.util.Random

object Common {
def genTapirEndpoint(n: Int): PublicEndpoint[Int, String, String, Any] = endpoint.get
.in("path" + n.toString)
.in(path[Int]("id"))
.errorOut(stringBody)
.out(stringBody)
val rootPackage = "sttp.tapir.perf"
val LargeInputSize = 5 * 1024 * 1024
val Port = 8080
val TmpDir: File = new java.io.File(System.getProperty("java.io.tmpdir")).getAbsoluteFile
def tempFilePath(): Path = TmpDir.toPath.resolve(s"tapir-${new Date().getTime}-${Random.nextLong()}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's a path to a new temp file?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'll rename this function to make it clearer.


def blockServer(): Unit = {
println(Console.BLUE + "Server now online. Please navigate to http://localhost:8080/path0/1\nPress RETURN to stop..." + Console.RESET)
StdIn.readLine()
println("Server terminated")
}
}
55 changes: 0 additions & 55 deletions perf-tests/src/main/scala/sttp/tapir/perf/akka/AkkaHttp.scala

This file was deleted.

67 changes: 67 additions & 0 deletions perf-tests/src/main/scala/sttp/tapir/perf/apis/Endpoints.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package sttp.tapir.perf.apis

import cats.effect.IO
import sttp.tapir._
import sttp.tapir.perf.Common._
import sttp.tapir.server.ServerEndpoint
import sttp.tapir.server.model.EndpointExtensions._

import java.io.File
import scala.concurrent.Future

trait Endpoints {
type EndpointGen = Int => PublicEndpoint[_, String, String, Any]
type ServerEndpointGen[F[_]] = Int => ServerEndpoint[Any, F]

def serverEndpoints[F[_]](reply: String => F[String]): List[ServerEndpointGen[F]] = {
List(
{ (n: Int) =>
endpoint.get
.in("path" + n.toString)
.in(path[Int]("id"))
.out(stringBody)
.serverLogicSuccess { id =>
reply((id + n).toString)
}
},
{ (n: Int) =>
endpoint.post
.in("path" + n.toString)
.in(stringBody)
.maxRequestBodyLength(LargeInputSize + 1024L)
.out(stringBody)
.serverLogicSuccess {
body: String =>
reply(s"Ok [$n], string length = ${body.length}")
}
},
{ (n: Int) =>
endpoint.post
.in("pathBytes" + n.toString)
.in(byteArrayBody)
.maxRequestBodyLength(LargeInputSize + 1024L)
.out(stringBody)
.serverLogicSuccess { body: Array[Byte] =>
reply(s"Ok [$n], bytes length = ${body.length}")
}
},
{ (n: Int) =>
endpoint.post
.in("pathFile" + n.toString)
.in(fileBody)
.maxRequestBodyLength(LargeInputSize + 1024L)
.out(stringBody)
.serverLogicSuccess {
body: File =>
reply(s"Ok [$n], file saved to ${body.toPath}")
}
}
)
}

def genServerEndpoints[F[_]](routeCount: Int)(reply: String => F[String]): List[ServerEndpoint[Any, F]] =
serverEndpoints[F](reply).flatMap(gen => (0 to routeCount).map(i => gen(i)))

def genEndpointsFuture(count: Int): List[ServerEndpoint[Any, Future]] = genServerEndpoints(count)(Future.successful)
def genEndpointsIO(count: Int): List[ServerEndpoint[Any, IO]] = genServerEndpoints(count)(IO.pure)
}
11 changes: 11 additions & 0 deletions perf-tests/src/main/scala/sttp/tapir/perf/apis/ServerRunner.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package sttp.tapir.perf.apis

import cats.effect.IO

trait ServerRunner {
def start: IO[ServerRunner.KillSwitch]
}

object ServerRunner {
type KillSwitch = IO[Unit]
}
63 changes: 43 additions & 20 deletions perf-tests/src/main/scala/sttp/tapir/perf/http4s/Http4s.scala
Original file line number Diff line number Diff line change
@@ -1,54 +1,77 @@
package sttp.tapir.perf.http4s

import cats.effect._
import cats.effect.unsafe.implicits.global
import cats.syntax.all._
import fs2.io.file.{Files, Path => Fs2Path}
import org.http4s._
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.dsl._
import org.http4s.implicits._
import org.http4s.server.Router
import sttp.tapir.perf
import sttp.tapir.perf.Common
import sttp.monad.MonadError
import sttp.tapir.integ.cats.effect.CatsMonadError
import sttp.tapir.perf.Common._
import sttp.tapir.perf.apis._
import sttp.tapir.server.http4s.Http4sServerInterpreter

object Vanilla {
val router: Int => HttpRoutes[IO] = (nRoutes: Int) =>
Router(
(0 to nRoutes).map((n: Int) =>
("/path" + n.toString) -> {
("/") -> {
val dsl = Http4sDsl[IO]
import dsl._
HttpRoutes.of[IO] { case GET -> Root / IntVar(id) =>
Ok((id + n).toString)
HttpRoutes.of[IO] {
case GET -> Root / s"path$n" / IntVar(id) =>
Ok((id + n.toInt).toString)
case req @ POST -> Root / s"path$n" =>
req.as[String].flatMap { str =>
Ok(s"Ok [$n], string length = ${str.length}")
}
case req @ POST -> Root / s"pathBytes$n" =>
req.as[Array[Byte]].flatMap { bytes =>
Ok(s"Ok [$n], bytes length = ${bytes.length}")
}
case req @ POST -> Root / s"pathFile$n" =>
val filePath = tempFilePath()
val sink = Files[IO].writeAll(Fs2Path.fromNioPath(filePath))
req.body
.through(sink)
.compile
.drain
.flatMap(_ => Ok(s"Ok [$n], file saved to ${filePath.toAbsolutePath.toString}"))
}
}
): _*
)
}

object Tapir {
object Tapir extends Endpoints {

implicit val mErr: MonadError[IO] = new CatsMonadError[IO]


val router: Int => HttpRoutes[IO] = (nRoutes: Int) =>
Router("/" -> {
Http4sServerInterpreter[IO]().toRoutes(
(0 to nRoutes)
.map((n: Int) => Common.genTapirEndpoint(n).serverLogic(id => IO(((id + n).toString).asRight[String])))
.toList
genEndpointsIO(nRoutes)
)
})
}

object Http4s {
def runServer(router: HttpRoutes[IO]): IO[ExitCode] = {
object server {
def runServer(router: HttpRoutes[IO]): IO[ServerRunner.KillSwitch] =
BlazeServerBuilder[IO]
.bindHttp(8080, "localhost")
.bindHttp(Port, "localhost")
.withHttpApp(router.orNotFound)
.resource
.use(_ => { perf.Common.blockServer(); IO.pure(ExitCode.Success) })
}
.allocated
.map(_._2)
.map(_.flatTap { _ =>
IO.println("Http4s server closed.")
})
}

object TapirServer extends App { Http4s.runServer(Tapir.router(1)).unsafeRunSync() }
object TapirMultiServer extends App { Http4s.runServer(Tapir.router(128)).unsafeRunSync() }
object VanillaServer extends App { Http4s.runServer(Vanilla.router(1)).unsafeRunSync() }
object VanillaMultiServer extends App { Http4s.runServer(Vanilla.router(128)).unsafeRunSync() }
object TapirServer extends ServerRunner { override def start = server.runServer(Tapir.router(1)) }
object TapirMultiServer extends ServerRunner { override def start = server.runServer(Tapir.router(128)) }
object VanillaServer extends ServerRunner { override def start = server.runServer(Vanilla.router(1)) }
object VanillaMultiServer extends ServerRunner { override def start = server.runServer(Vanilla.router(128)) }
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package sttp.tapir.perf.netty.cats

import cats.effect.IO
import cats.effect.kernel.Resource
import sttp.tapir.perf.Common._
import sttp.tapir.perf.apis._
import sttp.tapir.server.ServerEndpoint
import sttp.tapir.server.netty.cats.NettyCatsServer

object Tapir extends Endpoints

object NettyCats {

def runServer(endpoints: List[ServerEndpoint[Any, IO]]): IO[ServerRunner.KillSwitch] = {
val declaredPort = Port
val declaredHost = "0.0.0.0"
// Starting netty server
NettyCatsServer
.io()
.flatMap { server =>
Resource.make(
server
.port(declaredPort)
.host(declaredHost)
.addEndpoints(endpoints)
.start()
)(binding => binding.stop())
}
.allocated
.map(_._2)
}
}

object TapirServer extends ServerRunner { override def start = NettyCats.runServer(Tapir.genEndpointsIO(1)) }
object TapirMultiServer extends ServerRunner { override def start = NettyCats.runServer(Tapir.genEndpointsIO(128)) }
Loading
Loading