Browse Source

Add MySQL support (Hexagon) (#2690)

* Update Hexagon benchmark

* Update Hexagon benchmark

* Remove features not used in tests

* Remove MySql storage

* Enable Resin test

* Upgrade Hexagon to v0.11

* Disable Resin

* Fix dependencies

* Improve template rendering

* Test in travis

* Fix build script

* Restore Travis script

* Restore Travis script

* MySQL store support and MongoDB fix

* Fix MySQL test

* Fix MySQL test

* Fix MySQL test

* Travis testing

* Fix MySQL test

* Restore Travis

* Take MySQL URL from other test
Juanjo Aguililla 8 years ago
parent
commit
dba933fb43

+ 1 - 0
frameworks/Kotlin/hexagon/.gitignore

@@ -1,4 +1,5 @@
 
 build/
+log/
 .gradle/
 

+ 27 - 1
frameworks/Kotlin/hexagon/benchmark_config.json

@@ -27,7 +27,33 @@
 
                 "setup_file" : "setup",
                 "versus" : "servlet"
-            }              
+            },
+            "mysql" : {
+                "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" : "MySQL",
+                "framework" : "Hexagon",
+                "language" : "Kotlin",
+                "orm" : "Raw",
+                "platform" : "Servlet",
+                "webserver" : "None",
+                "os" : "Linux",
+                "database_os" : "Linux",
+                "display_name" : "Hexagon Jetty MySQL",
+                "notes" : "http://there4.co/hexagon",
+
+                "setup_file" : "setup_mysql",
+                "versus" : "servlet"
+            }
         }
     ]
 }

+ 13 - 10
frameworks/Kotlin/hexagon/build.gradle

@@ -1,19 +1,16 @@
 
-buildscript {
-    repositories {
-        jcenter ()
-    }
-
-    dependencies {
-        classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion"
-        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
-    }
+plugins {
+    id 'org.jetbrains.kotlin.jvm' version '1.1.1'
 }
 
 apply from: "$gradleScripts/kotlin.gradle"
 apply from: "$gradleScripts/service.gradle"
+apply from: "$gradleScripts/deploy.gradle"
+
 apply plugin: 'war'
 
+defaultTasks 'systemd'
+
 mainClassName = "co.there4.hexagon.BenchmarkKt"
 applicationDefaultJvmArgs = [
     '-Xms64M',
@@ -31,9 +28,11 @@ installDist.dependsOn 'war'
 
 dependencies {
     compile ("co.there4:hexagon:$hexagonVersion")
-    compile ("com.mitchellbosecke:pebble:$pebbleVersion")
 
+    compile ("com.mitchellbosecke:pebble:$pebbleVersion")
     compile ("org.mongodb:mongodb-driver:$mongodbVersion")
+    compile ("com.zaxxer:HikariCP:$hikariVersion")
+    compile ("mysql:mysql-connector-java:$mysqlConnectorVersion")
 
     // providedCompile excludes the dependency only in the WAR, not in the distribution
     providedCompile ("org.eclipse.jetty:jetty-webapp:$jettyVersion") { exclude module: "slf4j-api" }
@@ -41,3 +40,7 @@ dependencies {
     testCompile ("org.testng:testng:$testngVersion")
     testCompile ("co.there4:hexagon:$hexagonVersion:test")
 }
+
+test {
+    useTestNG ()
+}

+ 3 - 2
frameworks/Kotlin/hexagon/gradle.properties

@@ -1,14 +1,15 @@
+# suppress inspection "UnusedProperty" for whole file
 
 description=Hexagon web framework's benchmark
 
 gradleScripts=https://raw.githubusercontent.com/jaguililla/hexagon/0.11.1/gradle
 
-dokkaVersion=0.9.13
 kotlinVersion=1.1.1
-
 hexagonVersion=0.11.1
 pebbleVersion=2.3.0
 jettyVersion=9.4.2.v20170220
 mongodbVersion=3.4.2
+hikariVersion=2.6.1
+mysqlConnectorVersion=5.1.41
 
 testngVersion=6.10

+ 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.4.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.5-all.zip

+ 7 - 0
frameworks/Kotlin/hexagon/setup_mysql.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+fw_depends mysql java
+
+gradle/wrapper
+
+nohup build/install/hexagon/bin/hexagon mysql &

+ 112 - 2
frameworks/Kotlin/hexagon/src/main/kotlin/co/there4/hexagon/BenchmarkStorage.kt

@@ -7,7 +7,15 @@ 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 com.zaxxer.hikari.HikariConfig
+import com.zaxxer.hikari.HikariDataSource
 import java.io.Closeable
+import java.sql.Connection
+import java.sql.ResultSet.CONCUR_READ_ONLY
+import java.sql.ResultSet.TYPE_FORWARD_ONLY
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import javax.sql.DataSource
 import kotlin.reflect.KProperty1
 
 internal val DB_ROWS = 10000
@@ -17,8 +25,11 @@ 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 executor: ExecutorService = Executors.newFixedThreadPool(8)
+
 internal fun createStore(engine: String): Repository = when (engine) {
     "mongodb" -> MongoDbRepository()
+    "mysql" -> MySqlRepository()
     else -> error("Unsupported database")
 }
 
@@ -45,8 +56,107 @@ internal class MongoDbRepository : Repository {
 
     override fun replaceWorlds(queries: Int) = (1..queries).map {
         val id = rnd()
-        val newWorld = World(id, id)
-        worldRepository.replaceObject(newWorld)
+        val newWorld = worldRepository.find(id)?.copy(randomNumber = rnd()) ?: err
+        executor.execute { worldRepository.replaceObject(newWorld) }
         newWorld
     }
 }
+
+internal class MySqlRepository : Repository {
+    private val SELECT_WORLD = "select * from world where id = ?"
+    private val UPDATE_WORLD = "update world set randomNumber = ? where id = ?"
+    private val SELECT_FORTUNES = "select * from fortune"
+
+    private val DATA_SOURCE: DataSource
+
+    init {
+        val config = HikariConfig()
+        config.jdbcUrl = "jdbc:mysql://$DB_HOST/$DB?" +
+            "useSSL=false&" +
+            "rewriteBatchedStatements=true&" +
+            "jdbcCompliantTruncation=false&" +
+            "elideSetAutoCommits=true&" +
+            "useLocalSessionState=true&" +
+            "cachePrepStmts=true&" +
+            "cacheCallableStmts=true&" +
+            "alwaysSendSetIsolation=false&" +
+            "prepStmtCacheSize=4096&" +
+            "cacheServerConfiguration=true&" +
+            "prepStmtCacheSqlLimit=2048&" +
+            "traceProtocol=false&" +
+            "useUnbufferedInput=false&" +
+            "useReadAheadInput=false&" +
+            "maintainTimeStats=false&" +
+            "useServerPrepStmts=true&" +
+            "cacheRSMetadata=true"
+        config.maximumPoolSize = 256
+        config.username = "benchmarkdbuser"
+        config.password = "benchmarkdbpass"
+        DATA_SOURCE = HikariDataSource(config)
+    }
+
+    override fun findFortunes(): List<Fortune> {
+        var fortunes = listOf<Fortune>()
+
+        val connection = KConnection(DATA_SOURCE.connection ?: err)
+        connection.use { con: Connection ->
+            val rs = con.prepareStatement(SELECT_FORTUNES).executeQuery()
+            while (rs.next())
+                fortunes += Fortune(rs.getInt(1), rs.getString(2))
+        }
+
+        return fortunes
+    }
+
+    class KConnection(conn: Connection) : Connection by conn, Closeable
+
+    override fun findWorlds(queries: Int): List<World> {
+        var worlds: List<World> = listOf()
+
+        KConnection(DATA_SOURCE.connection).use { con: Connection ->
+            val stmtSelect = con.prepareStatement(SELECT_WORLD)
+
+            for (ii in 0..queries - 1) {
+                stmtSelect.setInt(1, rnd())
+                val rs = stmtSelect.executeQuery()
+                rs.next()
+                worlds += World(rs.getInt(1), rs.getInt(2))
+            }
+        }
+
+        return worlds
+    }
+
+    override fun replaceWorlds(queries: Int): List<World> {
+        var worlds: List<World> = listOf()
+
+        KConnection(DATA_SOURCE.connection).use { con: Connection ->
+            val stmtSelect = con.prepareStatement(SELECT_WORLD, TYPE_FORWARD_ONLY, CONCUR_READ_ONLY)
+
+            for (ii in 0..queries - 1) {
+                stmtSelect.setInt(1, rnd())
+                val rs = stmtSelect.executeQuery()
+                rs.next()
+
+                val world = World(rs.getInt(1), rs.getInt(2)).copy(randomNumber = rnd())
+                worlds += world
+            }
+        }
+
+        executor.execute {
+            KConnection(DATA_SOURCE.connection).use { con: Connection ->
+                val stmtUpdate = con.prepareStatement(UPDATE_WORLD)
+
+                for ((_, id, randomNumber) in worlds) {
+                    stmtUpdate.setInt(1, randomNumber)
+                    stmtUpdate.setInt(2, id)
+                    stmtUpdate.addBatch()
+                }
+
+                stmtUpdate.executeBatch()
+            }
+        }
+
+        return worlds
+    }
+}

+ 13 - 0
frameworks/Kotlin/hexagon/src/main/resources/logback.xml

@@ -8,7 +8,20 @@
     </encoder>
   </appender>
 
+  <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
+    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+      <FileNamePattern>log/%d{yyyy-MM-dd}.log</FileNamePattern>
+      <MaxHistory>5</MaxHistory>
+    </rollingPolicy>
+    <encoder>
+      <Pattern>%d{HH:mm:ss.SSS} %-5p [%-15.15thread] %-30logger{30} %X{jvmId} | %m%n</Pattern>
+    </encoder>
+  </appender>
+
   <root level="off">
     <appender-ref ref="console" />
+    <appender-ref ref="file" />
   </root>
+
+  <logger name="co.there4.hexagon" level="off" />
 </configuration>

+ 3 - 2
frameworks/Kotlin/hexagon/src/test/kotlin/co/there4/hexagon/BenchmarkTest.kt

@@ -14,9 +14,10 @@ import kotlin.test.assertFailsWith
 internal const val THREADS = 4
 internal const val TIMES = 4
 
-//class BenchmarkMongoDbTest : BenchmarkTest("mongodb")
+class BenchmarkMongoDbTest : BenchmarkTest("mongodb")
+class BenchmarkMySqlTest : BenchmarkTest("mysql")
 
-@Test(threadPoolSize = THREADS, invocationCount = TIMES)
+//@Test(threadPoolSize = THREADS, invocationCount = TIMES)
 abstract class BenchmarkTest(val databaseEngine: String) {
     private val client by lazy { Client("http://localhost:${server.runtimePort}") }