Browse Source

Upgrade Hexagon framework (#2488)

* Update Hexagon benchmark

* Update Hexagon benchmark

* Remove features not used in tests

* Remove MySql storage
Juanjo Aguililla 8 years ago
parent
commit
30497d5c49

+ 31 - 26
frameworks/Kotlin/hexagon/benchmark_config.json

@@ -1,28 +1,33 @@
 {
-  "framework": "hexagon",
-  "tests": [{
-    "default": {
-      "json_url" : "/json",
-      "db_url" : "/db",
-      "query_url" : "/query?queries=",
-      "fortune_url" : "/fortunes",
-      "update_url" : "/update?queries=",
-      "plaintext_url" : "/plaintext",
-      "port" : 9090,
-      "approach" : "Realistic",
-      "classification" : "Micro",
-      "database" : "MongoDB",
-      "framework" : "Hexagon",
-      "language" : "Kotlin",
-      "orm" : "Raw",
-      "platform" : "Servlet",
-      "webserver" : "None",
-      "os" : "Linux",
-      "database_os" : "Linux",
-      "display_name" : "Hexagon MongoDB",
-      "notes" : "http://there4.co/hexagon",
-      "setup_file" : "setup",
-      "versus" : "servlet"
-    }
-  }]
+    "framework" : "hexagon",
+    "tests" : [
+        {
+            "default" : {
+                "json_url" : "/json",
+                "db_url" : "/db",
+                "query_url" : "/query?queries=",
+                "fortune_url" : "/fortunes",
+                "update_url" : "/update?queries=",
+                "plaintext_url" : "/plaintext",
+
+                "port" : 9090,
+
+                "approach" : "Realistic",
+                "classification" : "Micro",
+                "database" : "MongoDB",
+                "framework" : "Hexagon",
+                "language" : "Kotlin",
+                "orm" : "Raw",
+                "platform" : "Servlet",
+                "webserver" : "None",
+                "os" : "Linux",
+                "database_os" : "Linux",
+                "display_name" : "Hexagon Jetty MongoDB",
+                "notes" : "http://there4.co/hexagon",
+
+                "setup_file" : "setup",
+                "versus" : "servlet"
+            }
+        }
+    ]
 }

+ 14 - 9
frameworks/Kotlin/hexagon/build.gradle

@@ -10,26 +10,31 @@ buildscript {
     }
 }
 
-apply from: "$gradleScripts/hexagon.gradle"
+apply from: "$gradleScripts/kotlin.gradle"
 apply from: "$gradleScripts/service.gradle"
 apply plugin: 'war'
 
 mainClassName = "co.there4.hexagon.BenchmarkKt"
 applicationDefaultJvmArgs = [
-    '-Xms512M',
-    '-Xmx1024M',
-    '-server',
+    '-Xms64M',
+    '-Xmx2G',
     '-XX:+UseNUMA',
     '-XX:+UseParallelGC',
     '-XX:+AggressiveOpts'
 ]
 
-clean {
-    delete "systems/load_test.jtl", "systems/jmeter.log"
-}
-
 war {
     archiveName = "ROOT.war"
 }
 
-installDist.dependsOn 'war'
+install.dependsOn 'war'
+
+dependencies {
+    compile ("co.there4:hexagon:$hexagonVersion")
+    compile ("com.mitchellbosecke:pebble:$pebbleVersion")
+
+    // providedCompile excludes the dependency only in the WAR, not in the distribution
+    providedCompile ("org.eclipse.jetty:jetty-webapp:$jettyVersion") { exclude module: "slf4j-api" }
+
+    testCompile ("co.there4:hexagon:$hexagonVersion:test")
+}

+ 6 - 6
frameworks/Kotlin/hexagon/gradle.properties

@@ -1,11 +1,11 @@
 
-version=1.0.0
-group=co.there4.hexagon
 description=Hexagon web framework's benchmark
 
-wrapperGradleVersion=3.2.1
-gradleScripts=https://raw.githubusercontent.com/jaguililla/hexagon/a1a489ba2e0a512c7012028a5e7b079f1aea345f/gradle
+gradleScripts=https://raw.githubusercontent.com/jaguililla/hexagon/0.10.3/gradle
 
 dokkaVersion=0.9.11
-kotlinVersion=1.0.5-2
-hexagonVersion=0.9.11
+kotlinVersion=1.0.6
+
+hexagonVersion=0.10.3
+pebbleVersion=2.3.0
+jettyVersion=9.3.14.v20161028

+ 1 - 1
frameworks/Kotlin/hexagon/gradle/wrapper.properties

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-3.2.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip

+ 49 - 35
frameworks/Kotlin/hexagon/readme.md

@@ -4,52 +4,66 @@
 This is the Hexagon portion of a [benchmarking test suite](../) comparing a variety of web
 development platforms. The test utilizes Hexagon routes, serialization and database access.
 
-
-## Local setup
-
-    tar -Jxvf db.txz && \
-    mongorestore dump/ && \
-    rm -rf dump
-
-
 ## Tests
 
-* [Hexagon application](/src/main/java/co/there4/hexagon/Benchmark.kt)
-
+* [Hexagon Web](/src/main/java/co/there4/hexagon/Benchmark.kt)
+* [Hexagon Storage](/src/main/java/co/there4/hexagon/BenchmarkStorage.kt)
 
 ## Infrastructure Software Versions
 
-* [Hexagon 0.3.2](http://there4.co/hexagon)
-
+* [Hexagon 0.10.x](http://there4.co/hexagon)
 
 ## Test URLs
 
-* JSON Encoding Test: http://localhost:5050/json
-* Data-Store/Database Mapping Test: http://localhost:5050/db?queries=5 
-* Plain Text Test: http://localhost:5050/plaintext 
-* Fortunes: http://localhost:5050/fortune 
-* Database updates: http://localhost:5050/update
-
-## Run on OpenShift
-
-https://blog.openshift.com/run-gradle-builds-on-openshift/
+### Jetty
 
+* JSON Encoding Test: http://localhost:9090/json
+* Data-Store/Database Mapping Test: http://localhost:9090/db?queries=5 
+* Plain Text Test: http://localhost:9090/plaintext 
+* Fortunes: http://localhost:9090/fortunes
+* Database updates: http://localhost:9090/update
+* Database queries: http://localhost:9090/query
 
-## Copy to TFB
+### Resin
 
-    rm -f db.txz
-    
-## Run inside vagrant
-
-    toolset/run-tests.py --install server --mode verify --test hexagon
-    
-## Clear
+* JSON Encoding Test: http://localhost:8080/json
+* Data-Store/Database Mapping Test: http://localhost:8080/db?queries=5 
+* Plain Text Test: http://localhost:8080/plaintext 
+* Fortunes: http://localhost:8080/fortunes
+* Database updates: http://localhost:8080/update
+* Database queries: http://localhost:8080/query
     
+#### Resin configuration
+
+    "resin" : { 
+      "json_url" : "/json",
+      "db_url" : "/db",
+      "query_url" : "/query?queries=",
+      "fortune_url" : "/fortunes",
+      "update_url" : "/update?queries=",
+      "plaintext_url" : "/plaintext",
+ 
+      "port" : 8080,
+ 
+      "approach" : "Realistic",
+      "classification" : "Micro",
+      "database" : "MongoDB",
+      "framework" : "Hexagon",
+      "language" : "Kotlin",
+      "orm" : "Raw",
+      "platform" : "Servlet",
+      "webserver" : "Resin",
+      "os" : "Linux",
+      "database_os" : "Linux",
+      "display_name" : "Hexagon Resin MongoDB",
+      "notes" : "http://there4.co/hexagon",
+ 
+      "setup_file" : "setup_resin",
+      "versus" : "servlet"
+    }                                   
 
-## TODO
-
-* Remove `benchmark_config` optional parameters. Check:
-  //frameworkbenchmarks.readthedocs.io/en/latest/Codebase/Framework-Files/#benchmark-config-file
-
-* Document common commands to test the framework inside Vagrant's development machine
+## Run inside vagrant
 
+    rm -rf ~/FrameworkBenchmarks/results
+    ~/FrameworkBenchmarks/toolset/run-tests.py --mode verify --test hexagon
+    ~/FrameworkBenchmarks/toolset/run-tests.py --mode verify --test hexagon-resin

+ 1 - 1
frameworks/Kotlin/hexagon/setup.sh

@@ -4,4 +4,4 @@ fw_depends mongodb java
 
 gradle/wrapper
 
-nohup build/hexagon/bin/hexagon &
+nohup build/install/hexagon/bin/hexagon &

+ 2 - 1
frameworks/Kotlin/hexagon/setup_resin.sh

@@ -4,5 +4,6 @@ fw_depends mongodb java resin
 
 gradle/wrapper
 
-cp -f build/libs/ROOT.war $RESIN_HOME/webapps
+rm -rf $RESIN_HOME/webapps/*
+cp build/libs/ROOT.war $RESIN_HOME/webapps
 resinctl start

+ 0 - 1
frameworks/Kotlin/hexagon/source_code

@@ -4,7 +4,6 @@
 ./hexagon/src/main/resources/service.properties
 ./hexagon/src/main/resources/logback.xml
 ./hexagon/src/test/kotlin/co/there4/hexagon/BenchmarkTest.kt
-./hexagon/src/test/resources/logback-test.xml
 ./hexagon/src/test/resources/service.yaml
 ./hexagon/build.gradle
 ./hexagon/gradle.properties

+ 20 - 33
frameworks/Kotlin/hexagon/src/main/kotlin/co/there4/hexagon/Benchmark.kt

@@ -1,11 +1,9 @@
 package co.there4.hexagon
 
-import co.there4.hexagon.rest.crud
 import co.there4.hexagon.serialization.convertToMap
 import co.there4.hexagon.serialization.serialize
 import co.there4.hexagon.web.*
 import co.there4.hexagon.web.servlet.ServletServer
-import kotlinx.html.*
 
 import java.net.InetAddress.getByName as address
 import java.time.LocalDateTime.now
@@ -24,32 +22,14 @@ private val QUERIES_PARAM = "queries"
 // UTILITIES
 internal fun rnd() = ThreadLocalRandom.current().nextInt(DB_ROWS) + 1
 
-private fun World.strip(): Map<*, *> = this.convertToMap().filterKeys { it != "_id" }
-private fun World.toJson(): String = this.strip().serialize()
-private fun List<World>.toJson(): String = this.map(World::strip).serialize()
+private fun Exchange.returnWorlds(worlds: List<World>) {
+    fun World.strip(): Map<*, *> = this.convertToMap().filterKeys { it != "_id" }
 
-private fun Exchange.hasQueryCount() = request[QUERIES_PARAM] == null
+    val result =
+        if (request[QUERIES_PARAM] == null) worlds[0].strip().serialize()
+        else worlds.map(World::strip).serialize()
 
-private fun Exchange.getDb() {
-    val worlds = (1..getQueries()).map { findWorld() }.filterNotNull()
-
-    ok(if (hasQueryCount()) worlds[0].toJson() else worlds.toJson(), CONTENT_TYPE_JSON)
-}
-
-private fun listFortunes() =
-    (findFortunes() + Fortune(0, "Additional fortune added at request time."))
-        .sortedBy { it.message }
-
-// HANDLERS
-private fun Exchange.getUpdates() {
-    val worlds = (1..getQueries()).map {
-        val id = rnd()
-        val newWorld = World(id, id)
-        replaceWorld(newWorld)
-        newWorld
-    }
-
-    ok(if (hasQueryCount()) worlds[0].toJson() else worlds.toJson(), CONTENT_TYPE_JSON)
+    ok(result, CONTENT_TYPE_JSON)
 }
 
 private fun Exchange.getQueries() =
@@ -65,7 +45,13 @@ private fun Exchange.getQueries() =
         1
     }
 
-fun benchmarkRoutes(srv: Router = server) {
+// HANDLERS
+private fun Exchange.listFortunes(store: Repository) {
+    val fortunes = store.findFortunes() + Fortune(0, "Additional fortune added at request time.")
+    template("fortunes.html", "fortunes" to fortunes.sortedBy { it.message })
+}
+
+private fun benchmarkRoutes(store: Repository, srv: Router = server) {
     srv.before {
         response.addHeader("Server", "Servlet/3.1")
         response.addHeader("Transfer-Encoding", "chunked")
@@ -74,19 +60,20 @@ fun benchmarkRoutes(srv: Router = server) {
 
     srv.get("/plaintext") { ok("Hello, World!", "text/plain") }
     srv.get("/json") { ok(Message().serialize(), CONTENT_TYPE_JSON) }
-    srv.get("/fortunes") { template("fortunes.html", "fortunes" to listFortunes()) }
-    srv.get("/db") { getDb() }
-    srv.get("/query") { getDb() }
-    srv.get("/update") { getUpdates() }
+    srv.get("/fortunes") { listFortunes(store) }
+    srv.get("/db") { returnWorlds(store.findWorlds(getQueries())) }
+    srv.get("/query") { returnWorlds(store.findWorlds(getQueries())) }
+    srv.get("/update") { returnWorlds(store.replaceWorlds(getQueries())) }
 }
 
 @WebListener class Web : ServletServer () {
     override fun init() {
-        benchmarkRoutes(this)
+        benchmarkRoutes(createStore("mongodb"), this)
     }
 }
 
 fun main(args: Array<String>) {
-    benchmarkRoutes()
+    val store = createStore(if (args.isEmpty()) "mongodb" else args[0])
+    benchmarkRoutes(store)
     run()
 }

+ 27 - 35
frameworks/Kotlin/hexagon/src/main/kotlin/co/there4/hexagon/BenchmarkStorage.kt

@@ -6,23 +6,10 @@ import java.lang.System.getenv
 
 import co.there4.hexagon.settings.SettingsManager.setting
 import co.there4.hexagon.repository.mongoDatabase
+import co.there4.hexagon.util.err
+import java.io.Closeable
 import kotlin.reflect.KProperty1
 
-internal val FORTUNE_MESSAGES = setOf(
-    "fortune: No such file or directory",
-    "A computer scientist is someone who fixes things that aren't broken.",
-    "After enough decimal places, nobody gives a damn.",
-    "A bad random number generator: 1, 1, 1, 1, 1, 4.33e+67, 1, 1, 1",
-    "A computer program does what you tell it to do, not what you want it to do.",
-    "Emacs is a nice operating system, but I prefer UNIX. — Tom Christaensen",
-    "Any program that runs right is obsolete.",
-    "A list is only as strong as its weakest link. — Donald Knuth",
-    "Feature: A bug with seniority.",
-    "Computers make very fast, very accurate mistakes.",
-    "<script>alert(\"This should not be displayed in a browser alert box.\");</script>",
-    "フレームワークのベンチマーク"
-)
-
 internal val DB_ROWS = 10000
 
 private val DB_HOST = getenv("DBHOST") ?: "localhost"
@@ -30,31 +17,36 @@ private val DB = setting<String>("database") ?: "hello_world"
 private val WORLD: String = setting<String>("worldCollection") ?: "world"
 private val FORTUNE: String = setting<String>("fortuneCollection") ?: "fortune"
 
-private val database = mongoDatabase("mongodb://$DB_HOST/$DB")
+internal fun createStore(engine: String): Repository = when (engine) {
+    "mongodb" -> MongoDbRepository()
+    else -> error("Unsupported database")
+}
 
-internal val worldRepository = repository(WORLD, World::_id)
-internal val fortuneRepository = repository(FORTUNE, Fortune::_id)
+internal interface Repository {
+    fun findFortunes(): List<Fortune>
+    fun findWorlds(queries: Int): List<World>
+    fun replaceWorlds(queries: Int): List<World>
+}
 
-// TODO Find out why it fails when creating index '_id' with background: true
-private inline fun <reified T : Any> repository(name: String, key: KProperty1<T, Int>) =
-    MongoIdRepository(T::class, mongoCollection(name, database), key, indexOrder = null)
+internal class MongoDbRepository : Repository {
+    private val database = mongoDatabase("mongodb://$DB_HOST/$DB")
 
-internal fun initialize() {
-    if (fortuneRepository.isEmpty()) {
-        val fortunes = FORTUNE_MESSAGES.mapIndexed { ii, fortune -> Fortune(ii + 1, fortune) }
-        fortuneRepository.insertManyObjects(fortunes)
-    }
+    internal val worldRepository = repository(WORLD, World::_id)
+    internal val fortuneRepository = repository(FORTUNE, Fortune::_id)
 
-    if (worldRepository.isEmpty()) {
-        val world = (1..DB_ROWS).map { World(it, it) }
-        worldRepository.insertManyObjects(world)
-    }
-}
+    // TODO Find out why it fails when creating index '_id' with background: true
+    private inline fun <reified T : Any> repository(name: String, key: KProperty1<T, Int>) =
+        MongoIdRepository(T::class, mongoCollection(name, database), key, indexOrder = null)
 
-internal fun findFortunes() = fortuneRepository.findObjects().toList()
+    override fun findFortunes() = fortuneRepository.findObjects().toList()
 
-internal fun findWorld() = worldRepository.find(rnd())
+    override fun findWorlds(queries: Int) =
+        (1..queries).map { worldRepository.find(rnd()) }.filterNotNull()
 
-internal fun replaceWorld(newWorld: World) {
-    worldRepository.replaceObject(newWorld)
+    override fun replaceWorlds(queries: Int) = (1..queries).map {
+        val id = rnd()
+        val newWorld = World(id, id)
+        worldRepository.replaceObject(newWorld)
+        newWorld
+    }
 }

+ 1 - 5
frameworks/Kotlin/hexagon/src/main/resources/logback.xml

@@ -1,5 +1,5 @@
 <!--
- | Logback configuration for tests
+ | Logback configuration
  !-->
 <configuration>
   <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
@@ -14,8 +14,4 @@
     <level value="off" />
     <appender-ref ref="console" />
   </root>
-
-  <logger name="co.there4.hexagon">
-    <level value="off" />
-  </logger>
 </configuration>

+ 2 - 0
frameworks/Kotlin/hexagon/src/main/resources/service.yaml

@@ -2,6 +2,8 @@
 bindPort : 9090
 bindAddress : 0.0.0.0
 
+ignoreResources : true
+
 database : hello_world
 worldCollection : world
 fortuneCollection : fortune

+ 42 - 14
frameworks/Kotlin/hexagon/src/test/kotlin/co/there4/hexagon/BenchmarkTest.kt

@@ -2,22 +2,28 @@ package co.there4.hexagon
 
 import co.there4.hexagon.serialization.parse
 import co.there4.hexagon.web.Client
+import co.there4.hexagon.web.HttpMethod.GET
+import co.there4.hexagon.web.reset
 import co.there4.hexagon.web.server
+import co.there4.hexagon.web.stop
 import org.asynchttpclient.Response
 import org.testng.annotations.BeforeClass
 import org.testng.annotations.Test
+import kotlin.test.assertFailsWith
 
 internal const val THREADS = 4
 internal const val TIMES = 4
 
+//class BenchmarkMongoDbTest : BenchmarkTest("mongodb")
+
 @Test(threadPoolSize = THREADS, invocationCount = TIMES)
-class BenchmarkTest {
+abstract class BenchmarkTest(val databaseEngine: String) {
     private val client by lazy { Client("http://localhost:${server.runtimePort}") }
 
     @BeforeClass fun warmup() {
-        initialize()
-
-        main(arrayOf())
+        stop()
+        reset()
+        main(arrayOf(databaseEngine))
 
         val warmupRounds = if (THREADS > 1) 2 else 0
         (1..warmupRounds).forEach {
@@ -45,6 +51,29 @@ class BenchmarkTest {
         }
     }
 
+    fun store() {
+        assertFailsWith<IllegalStateException> {
+            createStore("invalid")
+        }
+    }
+
+    fun web() {
+        val web = Web()
+        web.init()
+
+        val webRoutes = web.routes.map { it.key.method to it.key.path.path }
+        val benchmarkRoutes = listOf(
+            GET to "/plaintext",
+            GET to "/json",
+            GET to "/fortunes",
+            GET to "/db",
+            GET to "/query",
+            GET to "/update"
+        )
+
+        assert(webRoutes.containsAll(benchmarkRoutes))
+    }
+
     fun json() {
         val response = client.get("/json")
         val content = response.responseBody
@@ -61,7 +90,15 @@ class BenchmarkTest {
         assert("Hello, World!" == content)
     }
 
-    fun fortunes() = fortunesCheck("/fortunes")
+    fun fortunes() {
+        val response = client.get("/fortunes")
+        val content = response.responseBody
+
+        checkResponse(response, "text/html;charset=utf-8")
+        assert(content.contains("<td>&lt;script&gt;alert(&quot;This should not be "))
+        assert(content.contains(" displayed in a browser alert box.&quot;);&lt;/script&gt;</td>"))
+        assert(content.contains("<td>フレームワークのベンチマーク</td>"))
+    }
 
     fun no_query_parameter() {
         val response = client.get("/db")
@@ -118,15 +155,6 @@ class BenchmarkTest {
         }
     }
 
-    private fun fortunesCheck(url: String) {
-        val response = client.get(url)
-        val content = response.responseBody
-
-        checkResponse(response, "text/html;charset=utf-8")
-        assert(content.contains("<td>&lt;script&gt;alert(&quot;This should not be displayed in a browser alert box.&quot;);&lt;/script&gt;</td>"))
-        assert(content.contains("<td>フレームワークのベンチマーク</td>"))
-    }
-
     private fun checkResponse(res: Response, contentType: String) {
         assert(res.headers ["Date"] != null)
         assert(res.headers ["Server"] != null)

+ 0 - 21
frameworks/Kotlin/hexagon/src/test/resources/logback-test.xml

@@ -1,21 +0,0 @@
-<!--
- | Logback configuration for tests
- !-->
-<configuration>
-  <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
-    <encoder>
-      <Pattern>
-%d{HH:mm:ss.SSS} %highlight(%-5p) %magenta([%-15.15thread]) %-30logger{30} %cyan(%X{jvmId}) | %m%n
-      </Pattern>
-    </encoder>
-  </appender>
-
-  <root>
-    <level value="off" />
-    <appender-ref ref="console" />
-  </root>
-
-  <logger name="co.there4.hexagon">
-    <level value="off" />
-  </logger>
-</configuration>