Skip to content

Commit

Permalink
Add some microbenchmarks
Browse files Browse the repository at this point in the history
The microbenchmarks have been added in a new Play subproject called
Play-Microbenchmark. The microbenchmarks are executed using JMH via
the sbt-jmh plugin. They can be run with the `jmh:run` command. I've
added a few simple benchmarks to show how they are implemented.
  • Loading branch information
richdougherty committed Jul 4, 2016
1 parent 650f3fd commit 36f183c
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 1 deletion.
9 changes: 9 additions & 0 deletions framework/bin/test
Expand Up @@ -27,6 +27,15 @@ echo "[info]"

build "$@" "${CROSSBUILD} test"

echo "[info]"
echo "[info] ---- RUNNING MICROBENCHMARKS"
echo "[info]"

# Just run single iteration of microbenchmark to make sure that they
# run properly. The results will be inaccurate, but this ensures that
# the microbenchmarks at least compile and run.
build "$@" "${CROSSBUILD} Play-Microbenchmark/jmh:run -i 1 -wi 0 -f 1 -t 1"

echo "[info]"
echo "[info] ALL TESTS PASSED"
echo "[info]"
15 changes: 14 additions & 1 deletion framework/project/Build.scala
Expand Up @@ -12,7 +12,7 @@ import com.typesafe.tools.mima.plugin.MimaKeys.{
import com.typesafe.tools.mima.core._

import com.typesafe.sbt.SbtScalariform.scalariformSettings

import pl.project13.scala.sbt.JmhPlugin
import play.twirl.sbt.SbtTwirl
import play.twirl.sbt.Import.TwirlKeys

Expand Down Expand Up @@ -422,6 +422,18 @@ object PlayBuild extends Build {
.dependsOn(PlayJavaProject)
.dependsOn(PlayAkkaHttpServerProject)

// This project is just for microbenchmarking Play, not really a public artifact
lazy val PlayMicrobenchmarkProject = PlayCrossBuiltProject("Play-Microbenchmark", "play-microbenchmark")
.enablePlugins(JmhPlugin)
.settings(
parallelExecution in Test := false,
previousArtifacts := Set.empty
)
.dependsOn(PlayProject % "test->test", PlayLogback % "test->test", PlayWsProject, PlayWsJavaProject, PlaySpecs2Project)
.dependsOn(PlayFiltersHelpersProject)
.dependsOn(PlayJavaProject)
.dependsOn(PlayAkkaHttpServerProject)

lazy val PlayCacheProject = PlayCrossBuiltProject("Play-Cache", "play-cache")
.settings(
libraryDependencies ++= playCacheDeps,
Expand Down Expand Up @@ -471,6 +483,7 @@ object PlayBuild extends Build {
PlayDocsProject,
PlayFiltersHelpersProject,
PlayIntegrationTestProject,
PlayMicrobenchmarkProject,
PlayDocsSbtPlugin,
StreamsProject
)
Expand Down
1 change: 1 addition & 0 deletions framework/project/plugins.sbt
Expand Up @@ -20,6 +20,7 @@ addSbtPlugin("com.typesafe.play" % "interplay" % sys.props.getOrElse("interplay.
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % sbtTwirlVersion)
addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "0.1.8")
addSbtPlugin("com.typesafe.sbt" % "sbt-scalariform" % "1.3.0")
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.2.7")

libraryDependencies ++= Seq(
"org.scala-sbt" % "scripted-plugin" % sbtVersion.value,
Expand Down
@@ -0,0 +1,40 @@
package play.api.mvc

import org.openjdk.jmh.annotations._

/**
* This benchmark reads a cookie value from a RequestHeader.
*/
@State(Scope.Benchmark)
class Cookies_01_ReadCookieFromHeader {

var requestHeader: RequestHeader = null
var result: String = null

@Setup(Level.Iteration)
def setup(): Unit = {
requestHeader = MvcHelpers.requestHeader(List(
"Accept-Encoding" -> "gzip, deflate, sdch, br",
"Host" -> "www.playframework.com",
"Accept-Language" -> "en-US,en;q=0.8",
"Upgrade-Insecure-Requests" -> "1",
"User-Agent" -> "Mozilla/9.9 (Macintosh; Intel Mac OS X 10_99_9) AppleWebKit/999.99 (KHTML, like Gecko) Chrome/99.9.9999.999 Safari/999.999",
"Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Cache-Control" -> "max-age=0",
"Cookie" -> "__utma=99999999999999999999999999999999999999999999999999999; __utmz=999999999999999999999999999999999999999999999999999999999999999999999; _mkto_trk=999999999999999999999999999999999999999999999999999999999999999",
"Connection" -> "keep-alive"
))
result = null
}

@TearDown(Level.Iteration)
def tearDown(): Unit = {
// Check the benchmark got the correct result
assert(result == "99999999999999999999999999999999999999999999999999999")
}

@Benchmark
def getSomeCookie(): Unit = {
result = requestHeader.cookies.get("__utma").get.value
}
}
@@ -0,0 +1,11 @@
package play.api.mvc

import org.openjdk.jmh.annotations.{ Level, Setup }
import play.core.server.netty.NettyHelpers

object MvcHelpers {
def requestHeader(headerList: List[(String, String)]): RequestHeader = {
val rawRequest = NettyHelpers.nettyRequest(headers = headerList)
NettyHelpers.conversion.convertRequest(1L, NettyHelpers.localhost, None, rawRequest).get
}
}
@@ -0,0 +1,40 @@
package play.api.mvc

import org.openjdk.jmh.annotations._

/**
* This benchmark reads a header from a RequestHeader object.
*/
@State(Scope.Benchmark)
class RequestHeader_01_ReadHeaderValue {

var requestHeader: RequestHeader = null
var result: String = null

@Setup(Level.Iteration)
def setup(): Unit = {
requestHeader = MvcHelpers.requestHeader(List(
"Accept-Encoding" -> "gzip, deflate, sdch, br",
"Host" -> "www.playframework.com",
"Accept-Language" -> "en-US,en;q=0.8",
"Upgrade-Insecure-Requests" -> "1",
"User-Agent" -> "Mozilla/9.9 (Macintosh; Intel Mac OS X 10_99_9) AppleWebKit/999.99 (KHTML, like Gecko) Chrome/99.9.9999.999 Safari/999.999",
"Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Cache-Control" -> "max-age=0",
"Cookie" -> "__utma=99999999999999999999999999999999999999999999999999999; __utmz=999999999999999999999999999999999999999999999999999999999999999999999; _mkto_trk=999999999999999999999999999999999999999999999999999999999999999",
"Connection" -> "keep-alive"
))
result = null
}

@TearDown(Level.Iteration)
def tearDown(): Unit = {
// Check the benchmark got the correct result
assert(result == "max-age=0")
}

@Benchmark
def getCacheControlHeader(): Unit = {
result = requestHeader.headers("Cache-Control")
}
}
@@ -0,0 +1,26 @@
package play.core.server.netty

import java.net.InetSocketAddress

import io.netty.handler.codec.http.{ DefaultHttpRequest, HttpMethod, HttpRequest, HttpVersion }
import play.core.server.common.ForwardedHeaderHandler

object NettyHelpers {

val conversion: NettyModelConversion = new NettyModelConversion(
new ForwardedHeaderHandler(ForwardedHeaderHandler.ForwardedHeaderHandlerConfig(None))
)

val localhost: InetSocketAddress = new InetSocketAddress("127.0.0.1", 9999)

def nettyRequest(
method: String = "GET",
target: String = "/",
headers: List[(String, String)] = Nil): HttpRequest = {
val r = new DefaultHttpRequest(HttpVersion.valueOf("HTTP/1.1"), HttpMethod.valueOf(method), target)
for ((name, value) <- headers) {
r.headers().add(name, value)
}
r
}
}
@@ -0,0 +1,40 @@
package play.core.server.netty

import io.netty.handler.codec.http.HttpRequest
import org.openjdk.jmh.annotations.{ TearDown, _ }
import play.api.mvc.RequestHeader

import scala.util.Try

@State(Scope.Benchmark)
class NettyModelConversion_01_ConvertMinimalRequest {

// Cache some values that will be used in the benchmark
private val conversion = NettyHelpers.conversion
private val remoteAddress = NettyHelpers.localhost

// Benchmark state
private var request: HttpRequest = null
private var result: RequestHeader = null

@Setup(Level.Iteration)
def setup(): Unit = {
request = NettyHelpers.nettyRequest(
method = "GET",
target = "/",
headers = Nil
)
result = null
}

@TearDown(Level.Iteration)
def tearDown(): Unit = {
// Sanity check the benchmark result
assert(result.path == "/")
}

@Benchmark
def convertRequest(): Unit = {
result = conversion.convertRequest(1L, remoteAddress, None, request).get
}
}
@@ -0,0 +1,48 @@
package play.core.server.netty

import io.netty.handler.codec.http.HttpRequest
import org.openjdk.jmh.annotations.{ TearDown, _ }
import play.api.mvc.RequestHeader

@State(Scope.Benchmark)
class NettyModelConversion_02_ConvertNormalRequest {

// Cache some values that will be used in the benchmark
private val conversion = NettyHelpers.conversion
private val remoteAddress = NettyHelpers.localhost

// Benchmark state
private var request: HttpRequest = null
private var result: RequestHeader = null

@Setup(Level.Iteration)
def setup(): Unit = {
request = NettyHelpers.nettyRequest(
method = "GET",
target = "/x/y/z",
headers = List(
"Accept-Encoding" -> "gzip, deflate, sdch, br",
"Host" -> "www.playframework.com",
"Accept-Language" -> "en-US,en;q=0.8",
"Upgrade-Insecure-Requests" -> "1",
"User-Agent" -> "Mozilla/9.9 (Macintosh; Intel Mac OS X 10_99_9) AppleWebKit/999.99 (KHTML, like Gecko) Chrome/99.9.9999.999 Safari/999.999",
"Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Cache-Control" -> "max-age=0",
"Cookie" -> "__utma=99999999999999999999999999999999999999999999999999999; __utmz=999999999999999999999999999999999999999999999999999999999999999999999; _mkto_trk=999999999999999999999999999999999999999999999999999999999999999",
"Connection" -> "keep-alive"
)
)
result = null
}

@TearDown(Level.Iteration)
def tearDown(): Unit = {
// Sanity check the benchmark result
assert(result.path == "/x/y/z")
}

@Benchmark
def convertRequest(): Unit = {
result = conversion.convertRequest(1L, remoteAddress, None, request).get
}
}

0 comments on commit 36f183c

Please sign in to comment.