Browse Source

Initial Kitura 2.3 implementation (#3639)

David Jones 7 years ago
parent
commit
2093bde2bf
26 changed files with 1786 additions and 0 deletions
  1. 6 0
      frameworks/Swift/kitura/.gitignore
  2. 1 0
      frameworks/Swift/kitura/.swift-version
  3. 277 0
      frameworks/Swift/kitura/Package.resolved
  4. 28 0
      frameworks/Swift/kitura/Package.swift
  5. 50 0
      frameworks/Swift/kitura/README.md
  6. 55 0
      frameworks/Swift/kitura/Sources/TechEmpowerKuery/Fortune.swift
  7. 203 0
      frameworks/Swift/kitura/Sources/TechEmpowerKuery/Postgres.swift
  8. 22 0
      frameworks/Swift/kitura/Sources/TechEmpowerKuery/error.swift
  9. 182 0
      frameworks/Swift/kitura/Sources/TechEmpowerKuery/main.swift
  10. 23 0
      frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/Fortune+Mustache.swift
  11. 55 0
      frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/Fortune.swift
  12. 203 0
      frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/Postgres.swift
  13. 22 0
      frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/error.swift
  14. 163 0
      frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/main.swift
  15. 55 0
      frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/Fortune.swift
  16. 127 0
      frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/Mongo.swift
  17. 22 0
      frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/error.swift
  18. 137 0
      frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/main.swift
  19. 12 0
      frameworks/Swift/kitura/Views/fortunes.mustache
  20. 10 0
      frameworks/Swift/kitura/Views/fortunes.stencil
  21. 89 0
      frameworks/Swift/kitura/benchmark_config.json
  22. 16 0
      frameworks/Swift/kitura/config.json
  23. 7 0
      frameworks/Swift/kitura/kitura-gcd-mongodb.dockerfile
  24. 7 0
      frameworks/Swift/kitura/kitura-gcd.dockerfile
  25. 7 0
      frameworks/Swift/kitura/kitura-mongodb.dockerfile
  26. 7 0
      frameworks/Swift/kitura/kitura.dockerfile

+ 6 - 0
frameworks/Swift/kitura/.gitignore

@@ -0,0 +1,6 @@
+.DS_Store
+.build
+build_gcd
+.swiftenv
+Packages
+*.xcodeproj

+ 1 - 0
frameworks/Swift/kitura/.swift-version

@@ -0,0 +1 @@
+4.1

+ 277 - 0
frameworks/Swift/kitura/Package.resolved

@@ -0,0 +1,277 @@
+{
+  "object": {
+    "pins": [
+      {
+        "package": "Signals",
+        "repositoryURL": "https://github.com/IBM-Swift/BlueSignals.git",
+        "state": {
+          "branch": null,
+          "revision": "acd6304348ebeb181c0952ee91aa2c72a7826a12",
+          "version": "1.0.1"
+        }
+      },
+      {
+        "package": "Socket",
+        "repositoryURL": "https://github.com/IBM-Swift/BlueSocket.git",
+        "state": {
+          "branch": null,
+          "revision": "36561e7722f26151b8a6ba4b4bb96fd9b3f36980",
+          "version": "1.0.4"
+        }
+      },
+      {
+        "package": "SSLService",
+        "repositoryURL": "https://github.com/IBM-Swift/BlueSSLService.git",
+        "state": {
+          "branch": null,
+          "revision": "4759df40007b50d087c6513f844c36b243415296",
+          "version": "1.0.4"
+        }
+      },
+      {
+        "package": "BSON",
+        "repositoryURL": "https://github.com/OpenKitten/BSON.git",
+        "state": {
+          "branch": null,
+          "revision": "3edad325da31095c9d54d04c6125f1fec1f3c5bf",
+          "version": "5.2.3"
+        }
+      },
+      {
+        "package": "CCurl",
+        "repositoryURL": "https://github.com/IBM-Swift/CCurl.git",
+        "state": {
+          "branch": null,
+          "revision": "2a431e3a5fb8d004aa7d4d1bebb5d511f568c43e",
+          "version": "1.0.0"
+        }
+      },
+      {
+        "package": "CEpoll",
+        "repositoryURL": "https://github.com/IBM-Swift/CEpoll.git",
+        "state": {
+          "branch": null,
+          "revision": "4e56acbea4d452110236dcbfcee0045a697c0e9f",
+          "version": "1.0.0"
+        }
+      },
+      {
+        "package": "Cheetah",
+        "repositoryURL": "https://github.com/OpenKitten/Cheetah.git",
+        "state": {
+          "branch": null,
+          "revision": "801f4fa278670719bec074b708241878ad5fb25e",
+          "version": "2.0.2"
+        }
+      },
+      {
+        "package": "CLibpq",
+        "repositoryURL": "https://github.com/IBM-Swift/CLibpq.git",
+        "state": {
+          "branch": null,
+          "revision": "20421e0c7890a2a9b1515eb1321ebcd76ad7231f",
+          "version": "0.1.2"
+        }
+      },
+      {
+        "package": "Configuration",
+        "repositoryURL": "https://github.com/IBM-Swift/Configuration.git",
+        "state": {
+          "branch": null,
+          "revision": "a5e3ce4619089069697bbef5abfe3687f6830b9d",
+          "version": "3.0.1"
+        }
+      },
+      {
+        "package": "CryptoKitten",
+        "repositoryURL": "https://github.com/OpenKitten/CryptoKitten.git",
+        "state": {
+          "branch": null,
+          "revision": "bf7a4d6dbd6f4bbd96ae620101239446d2cdd3f9",
+          "version": "0.2.3"
+        }
+      },
+      {
+        "package": "FileKit",
+        "repositoryURL": "https://github.com/IBM-Swift/FileKit.git",
+        "state": {
+          "branch": null,
+          "revision": "319016dbf4b3ce04589265c23c9cac0016fe8096",
+          "version": "0.0.1"
+        }
+      },
+      {
+        "package": "Mustache",
+        "repositoryURL": "https://github.com/IBM-Swift/GRMustache.swift.git",
+        "state": {
+          "branch": null,
+          "revision": "f27856d0e2182d41a56c8c6be115baadf87857d0",
+          "version": "1.7.4"
+        }
+      },
+      {
+        "package": "HeliumLogger",
+        "repositoryURL": "https://github.com/IBM-Swift/HeliumLogger.git",
+        "state": {
+          "branch": null,
+          "revision": "2afeb4c02f5a48a7fbd081fe8607b91eeebd7ecb",
+          "version": "1.7.1"
+        }
+      },
+      {
+        "package": "KittenCTLS",
+        "repositoryURL": "https://github.com/OpenKitten/KittenCTLS.git",
+        "state": {
+          "branch": null,
+          "revision": "be8c42f22975e8033c6f342f42b8999407672bb3",
+          "version": "1.0.1"
+        }
+      },
+      {
+        "package": "Kitura",
+        "repositoryURL": "https://github.com/IBM-Swift/Kitura.git",
+        "state": {
+          "branch": null,
+          "revision": "55c2937de7d2d766082a5af63f28325dea6473fa",
+          "version": "2.3.0"
+        }
+      },
+      {
+        "package": "KituraMustache",
+        "repositoryURL": "https://github.com/IBM-Swift/Kitura-MustacheTemplateEngine.git",
+        "state": {
+          "branch": null,
+          "revision": "54e4929c4bab6b5c44a11ef68bb04894245a8370",
+          "version": "1.7.3"
+        }
+      },
+      {
+        "package": "Kitura-net",
+        "repositoryURL": "https://github.com/IBM-Swift/Kitura-net.git",
+        "state": {
+          "branch": null,
+          "revision": "755c39febb661d5da85e1ec9fcf98721c420af0a",
+          "version": "2.1.0"
+        }
+      },
+      {
+        "package": "KituraStencil",
+        "repositoryURL": "https://github.com/IBM-Swift/Kitura-StencilTemplateEngine.git",
+        "state": {
+          "branch": null,
+          "revision": "535d9e1a3def131386cca1e26cfb5ef0477d4c65",
+          "version": "1.9.1"
+        }
+      },
+      {
+        "package": "Kitura-TemplateEngine",
+        "repositoryURL": "https://github.com/IBM-Swift/Kitura-TemplateEngine.git",
+        "state": {
+          "branch": null,
+          "revision": "d43155da4dadd91c1d34d76e317571dc54655594",
+          "version": "1.7.4"
+        }
+      },
+      {
+        "package": "KituraContracts",
+        "repositoryURL": "https://github.com/IBM-Swift/KituraContracts.git",
+        "state": {
+          "branch": null,
+          "revision": "de85c1a28bb379cd423f5a17eb651222112d2c3e",
+          "version": "0.0.22"
+        }
+      },
+      {
+        "package": "LoggerAPI",
+        "repositoryURL": "https://github.com/IBM-Swift/LoggerAPI.git",
+        "state": {
+          "branch": null,
+          "revision": "5041f2673aa75d6e973d9b6bd3956bc5068387c8",
+          "version": "1.7.3"
+        }
+      },
+      {
+        "package": "MongoKitten",
+        "repositoryURL": "https://github.com/OpenKitten/MongoKitten.git",
+        "state": {
+          "branch": null,
+          "revision": "f564cc39c6088c23bbafd7ea8910dc270517b77e",
+          "version": "4.1.3"
+        }
+      },
+      {
+        "package": "OpenSSL",
+        "repositoryURL": "https://github.com/IBM-Swift/OpenSSL.git",
+        "state": {
+          "branch": null,
+          "revision": "95c405c18a071a1c13616e984ba1b3c6091ca6ca",
+          "version": "1.0.0"
+        }
+      },
+      {
+        "package": "PathKit",
+        "repositoryURL": "https://github.com/kylef/PathKit.git",
+        "state": {
+          "branch": null,
+          "revision": "fa81fa9e3a9f59645159c4ea45c0c46ee6558f71",
+          "version": "0.9.1"
+        }
+      },
+      {
+        "package": "Schrodinger",
+        "repositoryURL": "https://github.com/OpenKitten/Schrodinger.git",
+        "state": {
+          "branch": null,
+          "revision": "d7030fe0a4a6eeddc02044650a8b409a4f57365a",
+          "version": "1.0.1"
+        }
+      },
+      {
+        "package": "Spectre",
+        "repositoryURL": "https://github.com/kylef/Spectre.git",
+        "state": {
+          "branch": null,
+          "revision": "e34d5687e1e9d865e3527dd58bc2f7464ef6d936",
+          "version": "0.8.0"
+        }
+      },
+      {
+        "package": "Stencil",
+        "repositoryURL": "https://github.com/kylef/Stencil.git",
+        "state": {
+          "branch": null,
+          "revision": "c2e25f25acfbe24442809c055141d8323af8f6cc",
+          "version": "0.11.0"
+        }
+      },
+      {
+        "package": "SwiftKuery",
+        "repositoryURL": "https://github.com/IBM-Swift/Swift-Kuery.git",
+        "state": {
+          "branch": null,
+          "revision": "a95ae0131c6742e8ff1575db8ed7fda1bf4b93ee",
+          "version": "1.3.0"
+        }
+      },
+      {
+        "package": "SwiftKueryPostgreSQL",
+        "repositoryURL": "https://github.com/IBM-Swift/Swift-Kuery-PostgreSQL",
+        "state": {
+          "branch": null,
+          "revision": "4177014a8e85da24afbcdbbb7baaac070d549fac",
+          "version": "1.1.4"
+        }
+      },
+      {
+        "package": "SwiftyJSON",
+        "repositoryURL": "https://github.com/IBM-Swift/SwiftyJSON.git",
+        "state": {
+          "branch": null,
+          "revision": "7eb6c83f91c519c19e6a507d604c9176d5ae5b15",
+          "version": "17.0.1"
+        }
+      }
+    ]
+  },
+  "version": 1
+}

+ 28 - 0
frameworks/Swift/kitura/Package.swift

@@ -0,0 +1,28 @@
+// swift-tools-version:4.0
+// The swift-tools-version declares the minimum version of Swift required to build this package.
+
+import PackageDescription
+
+let package = Package(
+    name: "Kitura-TechEmpower",
+    dependencies: [
+        .package(url: "https://github.com/IBM-Swift/Kitura.git", from: "2.3.0"),
+        .package(url: "https://github.com/IBM-Swift/HeliumLogger.git", from: "1.7.0"),
+        .package(url: "https://github.com/IBM-Swift/Configuration.git", from: "3.0.0"),
+        .package(url: "https://github.com/IBM-Swift/Swift-Kuery-PostgreSQL", from: "1.0.0"),
+        .package(url: "https://github.com/IBM-Swift/Kitura-StencilTemplateEngine.git", from: "1.9.0"),
+        .package(url: "https://github.com/IBM-Swift/Kitura-MustacheTemplateEngine.git", from: "1.7.2"),
+        .package(url: "https://github.com/OpenKitten/MongoKitten.git", from: "4.1.3"),
+    ],
+    targets: [
+        .target(
+            name: "TechEmpowerKuery",
+            dependencies: ["Kitura", "HeliumLogger", "Configuration", "SwiftKueryPostgreSQL", "KituraStencil"]),
+        .target(
+            name: "TechEmpowerKueryMustache",
+            dependencies: ["Kitura", "HeliumLogger", "Configuration", "SwiftKueryPostgreSQL", "KituraMustache"]),
+        .target(
+            name: "TechEmpowerMongoKitten",
+            dependencies: ["Kitura", "HeliumLogger", "Configuration", "MongoKitten", "KituraStencil"]),
+    ]
+)

+ 50 - 0
frameworks/Swift/kitura/README.md

@@ -0,0 +1,50 @@
+# [Kitura](https://kitura.io) Benchmark Test
+
+This is the [Kitura](https://kitura.io) portion of a [benchmarking test suite](https://www.techempower.com/benchmarks/) comparing a variety of web development platforms.
+
+## Variants
+
+There are two versions of the benchmark, using different database backends:
+- `kitura`: Kitura with Postgres
+- `kitura-mongokitten`: Kitura with MongoDB
+
+There are two additional variants: `kitura-gcd` and `kitura-gcd-mongokitten`. These are compiled from the same source, but use the Grand Central Dispatch threading model (used by default on macOS) instead of the default epoll implementation on Linux.
+
+Each listens on port 8080, and use common URLs described below.
+
+## Versions and Dependencies
+
+This version of the benchmark requires Swift 4.0.3 or Swift 4.1, and uses the following versions of Kitura and dependencies:
+
+- [Kitura 2.3](https://github.com/IBM-Swift/Kitura)
+- [HeliumLogger 1.7](https://github.com/IBM-Swift/HeliumLogger)
+- [Configuration 3.0](https://github.com/IBM-Swift/Configuration)
+- [SwiftKueryPostgreSQL 1.1](https://github.com/IBM-Swift/Swift-Kuery-PostgreSQL)
+- [KituraStencil 1.9](https://github.com/IBM-Swift/Kitura-StencilTemplateEngine)
+- [KituraMustache 1.7](https://github.com/IBM-Swift/Kitura-MustacheTemplateEngine)
+- [MongoKitten 4.1.3](https://github.com/OpenKitten/MongoKitten)
+
+## Test URLs
+### JSON serialization
+
+http://localhost:8080/json
+
+### Single database query
+
+http://localhost:8080/db
+
+### Multiple database queries
+
+http://localhost:8080/queries?queries=n
+
+### Fortunes
+
+http://localhost:8080/fortunes
+
+### Database updates
+
+http://localhost:8080/updates?queries=n
+
+### Plaintext
+
+http://localhost:8080/plaintext

+ 55 - 0
frameworks/Swift/kitura/Sources/TechEmpowerKuery/Fortune.swift

@@ -0,0 +1,55 @@
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+struct Fortune {
+
+  let id: Int
+  let message: String
+
+  public init(id: Int, message: String) {
+    self.id = id
+    self.message = message
+  }
+
+  init(row: [String:Any?]) throws {
+    guard let idField = row["id"] else {
+      throw AppError.DataFormatError("Missing 'id' field")
+    }
+    guard let msgField = row["message"] else {
+      throw AppError.DataFormatError("Missing 'message' field")
+    }
+    guard let message = msgField as? String else {
+      throw AppError.DataFormatError("'message' field not a String")
+    }
+    guard let id = idField as? Int32 else {
+      throw AppError.DataFormatError("'id' field not an Int32")
+    }
+    self.init(id: Int(id), message: message)
+  }
+
+}
+
+extension Fortune: Comparable {
+
+  static func == (lhs: Fortune, rhs: Fortune) -> Bool {
+    return lhs.id == rhs.id && lhs.message == rhs.message
+  }
+
+  static func < (lhs: Fortune, rhs: Fortune) -> Bool {
+    return lhs.message < rhs.message || (lhs.message == rhs.message && lhs.id < rhs.id)
+  }
+
+}

+ 203 - 0
frameworks/Swift/kitura/Sources/TechEmpowerKuery/Postgres.swift

@@ -0,0 +1,203 @@
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Foundation
+import LoggerAPI
+import SwiftKuery
+import SwiftKueryPostgreSQL
+import Configuration
+
+#if os(Linux)
+    import Glibc
+#else
+    import Darwin
+#endif
+
+// We will load our database configuration from config.json, but this can be
+// overridden with the TFB_DB_CONFIG environment variable.
+let configurationFilename: String = ProcessInfo.processInfo.environment["TFB_DB_CONFIG"] ?? "config.json"
+let manager = ConfigurationManager().load(file: configurationFilename, relativeFrom: .pwd).load(.environmentVariables)
+
+let dbHost = manager["DB_HOST"] as? String ?? manager["db:host"] as? String ?? "localhost"
+let dbPort = Int32(manager["DB_PORT"] as? String != nil ? Int(manager["DB_PORT"] as! String) ?? 5432 : manager["db:port"] as? Int ?? 5432)
+let dbName = manager["db:name"] as? String ?? "hello_world"
+let dbUser = manager["db:user"] as? String ?? "benchmarkdbuser"
+let dbPass = manager["db:password"] as? String ?? "benchmarkdbpass"
+
+let dbRows = 10000
+let maxValue = 10000
+
+class World: Table {
+    let tableName = "World"
+    
+    let id = Column("id")
+    let randomNumber = Column("randomNumber")
+}
+
+class Fortunes: Table {
+    let tableName = "Fortune"
+
+    let id = Column("id")
+    let message = Column("message")
+}
+
+let world = World()
+let fortunes = Fortunes()
+
+var update = Update(world, set: [(world.randomNumber, randomNumberGenerator(maxValue))])
+    .where(world.id == randomNumberGenerator(dbRows))
+
+
+let dbConnPoolOpts = ConnectionPoolOptions(initialCapacity: 20, maxCapacity: 50, timeout:10000)
+
+func releaseConnection(connection: Connection) {
+    connection.closeConnection()
+}
+
+func generateConnection() -> Connection? {
+    var dbConn: Connection
+    dbConn = PostgreSQLConnection(host: dbHost, port: dbPort,
+                                  options: [.databaseName(dbName),
+                                            .userName(dbUser), .password(dbPass) ])
+
+    
+    dbConn.connect() { error in
+        if let error = error {
+            print(error)
+            return
+        }
+    }
+    return dbConn
+}
+
+let dbConnPool = ConnectionPool(options: dbConnPoolOpts, connectionGenerator: generateConnection, connectionReleaser:releaseConnection)
+
+// Return a random number within the range of rows in the database
+func randomNumberGenerator(_ maxVal: Int) -> Int {
+    #if os(Linux)
+        return Int(random() % maxVal) + 1
+    #else
+        return Int(arc4random_uniform(UInt32(maxVal))) + 1
+    #endif
+}
+
+func getFortunes() -> ([Fortune]?, AppError?) {
+    var resultFortunes: [Fortune]? = nil
+    var errRes: AppError? = nil
+    
+    // Get a dedicated connection object for this transaction from the pool
+    guard let dbConn = dbConnPool.getConnection() else {
+        errRes = AppError.OtherError("Timed out waiting for a DB connection from the pool")
+        return (nil, errRes)
+    }
+    // Ensure that when we complete, the connection is returned to the pool
+    defer {
+        releaseConnection(connection: dbConn)
+    }
+    
+    let query = Select(from: fortunes)
+
+    dbConn.execute(query: query) { result in
+        guard let rows = result.asRows, result.success else {
+            errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
+            return
+        }
+        do {
+            resultFortunes = try rows.map { try Fortune.init(row: $0) }
+        } catch {
+            errRes = AppError.DataFormatError("\(error)")
+        }
+    }
+
+    return (resultFortunes, errRes)
+}
+
+// Get a random row (range 1 to 10,000) from DB: id(int),randomNumber(int)
+// Convert to object using object-relational mapping (ORM) tool
+// Serialize object to JSON - example: {"id":3217,"randomNumber":2149}
+func getRandomRow() -> ([String:Int]?, AppError?) {
+    var resultDict: [String:Int]? = nil
+    var errRes: AppError? = nil
+    // Get a dedicated connection object for this transaction from the pool
+    guard let dbConn = dbConnPool.getConnection() else {
+        errRes = AppError.OtherError("Timed out waiting for a DB connection from the pool")
+        return (resultDict, errRes)
+    }
+    // Ensure that when we complete, the connection is returned to the pool
+    defer {
+        releaseConnection(connection: dbConn)
+    }
+    let rnd = randomNumberGenerator(dbRows)
+    
+    let query = Select(world.randomNumber, from: world)
+        .where(world.id == rnd)
+    
+    dbConn.execute(query: query) { result in
+        if let resultSet = result.asResultSet {
+            guard result.success else {
+                errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
+                return
+            }
+            
+            for row in resultSet.rows {
+                for value in row {
+                    if let unwrapped = value {
+                        guard let randomNumber = unwrapped as? Int32 else {
+                        errRes = AppError.DBKueryError("Error: could not get field as an Int")
+                        return
+                    }
+                        resultDict = ["id":rnd, "randomNumber":Int(randomNumber)]
+                    } else {
+                        errRes = AppError.DBKueryError("Error: randomNumber value is nil")
+                    }
+
+                }
+                
+            }
+        }
+    }
+    return (resultDict, errRes)
+}
+
+// Updates a row of World to a new value.
+func updateRow(id: Int) throws  -> AppError? {
+    // Get a dedicated connection object for this transaction from the pool
+    guard let dbConn = dbConnPool.getConnection() else {
+        throw AppError.OtherError("Timed out waiting for a DB connection from the pool")
+    }
+    // Ensure that when we complete, the connection is returned to the pool
+    defer {
+        releaseConnection(connection: dbConn)
+    }
+    let rndValue = randomNumberGenerator(maxValue)
+    let query = Update(world, set: [(world.randomNumber, rndValue)])
+        .where(world.id == id)
+    var errRes: AppError? = nil
+    dbConn.execute(query: query) { result in
+        if result.asResultSet != nil {
+            guard result.success else {
+                errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
+                return
+            }
+            
+        }
+        
+    }
+    return errRes
+}
+
+
+

+ 22 - 0
frameworks/Swift/kitura/Sources/TechEmpowerKuery/error.swift

@@ -0,0 +1,22 @@
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public enum AppError: Error {
+    case DBError(String, query: String)
+    case DBKueryError(String)
+    case DataFormatError(String)
+    case OtherError(String)
+}

+ 182 - 0
frameworks/Swift/kitura/Sources/TechEmpowerKuery/main.swift

@@ -0,0 +1,182 @@
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Kitura
+import LoggerAPI
+import HeliumLogger
+import SwiftKuery
+import SwiftKueryPostgreSQL
+import KituraStencil
+import Stencil
+
+Log.logger = HeliumLogger(.info)
+
+// Stencil stuff
+let ext = Extension()
+
+// Stencil does not yet support automatic HTML escaping:
+// https://github.com/kylef/Stencil/pull/80
+//
+ext.registerFilter("htmlencode") { (value: Any?) in
+    if let value = value as? String {
+        return value
+            .replacingOccurrences(of: "&", with: "&amp;")
+            .replacingOccurrences(of: "<", with: "&lt;")
+            .replacingOccurrences(of: ">", with: "&gt;")
+            .replacingOccurrences(of: "'", with: "&apos;")
+            .replacingOccurrences(of: "\"", with: "&quot;")
+    }
+    return value
+}
+
+let router = Router()
+router.add(templateEngine: StencilTemplateEngine(extension: ext))
+
+//
+// TechEmpower test 6: plaintext
+//
+router.get("/plaintext") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    response.headers["Content-Type"] = "text/plain"
+    try response.status(.OK).send("Hello, world!").end()
+}
+
+//
+// TechEmpower test 1: JSON serialization
+//
+router.get("/json") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let result = ["message":"Hello, World!"]
+    try response.status(.OK).send(json: result).end()
+}
+
+//
+// TechEmpower test 2: Single database query (raw, no ORM)
+//
+router.get("/db") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let result = getRandomRow()
+    guard let dict = result.0 else {
+        guard let err = result.1 else {
+            Log.error("Unknown Error")
+            try response.status(.badRequest).send("Unknown error").end()
+            return
+        }
+        Log.error("\(err)")
+        try response.status(.badRequest).send("Error: \(err)").end()
+        return
+    }
+    try response.status(.OK).send(json: dict).end()
+}
+
+//
+// TechEmpower test 3: Multiple database queries (raw, no ORM)
+// Get param provides number of queries: /queries?queries=N
+//
+router.get("/queries") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    var results: [[String:Int]] = []
+    for _ in 1...numQueries {
+        let result = getRandomRow()
+        guard let dict = result.0 else {
+            guard let err = result.1 else {
+                Log.error("Unknown Error")
+                try response.status(.badRequest).send("Unknown error").end()
+                return
+            }
+            Log.error("\(err)")
+            try response.status(.badRequest).send("Error: \(err)").end()
+            return
+        }
+        results.append(dict)
+    }
+    // Return JSON representation of array of results
+    try response.status(.OK).send(json: results).end()
+}
+
+//
+// TechEmpower test 4: fortunes (raw, no ORM)
+//
+router.get("/fortunes") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    response.headers["Content-Type"] = "text/html; charset=UTF-8"
+    let result = getFortunes()
+    guard var fortunes = result.0 else {
+        guard let err = result.1 else {
+            Log.error("Unknown Error")
+            try response.status(.badRequest).send("Unknown error").end()
+            return
+        }
+        Log.error("\(err)")
+        try response.status(.badRequest).send("Error: \(err)").end()
+        return
+    }
+    fortunes.append(Fortune(id: 0, message: "Additional fortune added at request time."))
+    do {
+      try response.render("fortunes.stencil", context: ["fortunes": fortunes.sorted()]).end()
+    } catch {
+      print("Error: \(error)")
+    }
+}
+
+//
+// TechEmpower test 5: updates (raw, no ORM)
+//
+router.get("/updates") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    var results: [[String:Int]] = []
+    for _ in 1...numQueries {
+        let result = getRandomRow()
+        guard let dict = result.0 else {
+            guard let err = result.1 else {
+                Log.error("Unknown Error")
+                try response.status(.badRequest).send("Unknown error").end()
+                return
+            }
+            Log.error("\(err)")
+            try response.status(.badRequest).send("Error: \(err)").end()
+            return
+        }
+        do {
+            var error: AppError?
+            try error = updateRow(id: dict["id"]!)
+            if let appError = error {
+                throw appError
+            }
+        } catch let err as AppError {
+            try response.status(.badRequest).send("Error: \(err)").end()
+            return
+        }
+        results.append(dict)
+    }
+    
+    // Return JSON representation of array of results
+    try response.status(.OK).send(json: results).end()
+}
+
+
+Kitura.addHTTPServer(onPort: 8080, with: router)
+Kitura.run()

+ 23 - 0
frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/Fortune+Mustache.swift

@@ -0,0 +1,23 @@
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Mustache
+
+extension Fortune: MustacheBoxable {
+  var mustacheBox: MustacheBox {
+    return Box(["id": self.id, "message": self.message])
+  }
+}

+ 55 - 0
frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/Fortune.swift

@@ -0,0 +1,55 @@
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+struct Fortune {
+
+  let id: Int
+  let message: String
+
+  public init(id: Int, message: String) {
+    self.id = id
+    self.message = message
+  }
+
+  init(row: [String:Any?]) throws {
+    guard let idField = row["id"] else {
+      throw AppError.DataFormatError("Missing 'id' field")
+    }
+    guard let msgField = row["message"] else {
+      throw AppError.DataFormatError("Missing 'message' field")
+    }
+    guard let message = msgField as? String else {
+      throw AppError.DataFormatError("'message' field not a String")
+    }
+    guard let id = idField as? Int32 else {
+      throw AppError.DataFormatError("'id' field not an Int32")
+    }
+    self.init(id: Int(id), message: message)
+  }
+
+}
+
+extension Fortune: Comparable {
+
+  static func == (lhs: Fortune, rhs: Fortune) -> Bool {
+    return lhs.id == rhs.id && lhs.message == rhs.message
+  }
+
+  static func < (lhs: Fortune, rhs: Fortune) -> Bool {
+    return lhs.message < rhs.message || (lhs.message == rhs.message && lhs.id < rhs.id)
+  }
+
+}

+ 203 - 0
frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/Postgres.swift

@@ -0,0 +1,203 @@
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Foundation
+import LoggerAPI
+import SwiftKuery
+import SwiftKueryPostgreSQL
+import Configuration
+
+#if os(Linux)
+    import Glibc
+#else
+    import Darwin
+#endif
+
+// We will load our database configuration from config.json, but this can be
+// overridden with the TFB_DB_CONFIG environment variable.
+let configurationFilename: String = ProcessInfo.processInfo.environment["TFB_DB_CONFIG"] ?? "config.json"
+let manager = ConfigurationManager().load(file: configurationFilename, relativeFrom: .pwd).load(.environmentVariables)
+
+let dbHost = manager["DB_HOST"] as? String ?? manager["db:host"] as? String ?? "localhost"
+let dbPort = Int32(manager["DB_PORT"] as? String != nil ? Int(manager["DB_PORT"] as! String) ?? 5432 : manager["db:port"] as? Int ?? 5432)
+let dbName = manager["db:name"] as? String ?? "hello_world"
+let dbUser = manager["db:user"] as? String ?? "benchmarkdbuser"
+let dbPass = manager["db:password"] as? String ?? "benchmarkdbpass"
+
+let dbRows = 10000
+let maxValue = 10000
+
+class World: Table {
+    let tableName = "World"
+    
+    let id = Column("id")
+    let randomNumber = Column("randomNumber")
+}
+
+class Fortunes: Table {
+    let tableName = "Fortune"
+
+    let id = Column("id")
+    let message = Column("message")
+}
+
+let world = World()
+let fortunes = Fortunes()
+
+var update = Update(world, set: [(world.randomNumber, randomNumberGenerator(maxValue))])
+    .where(world.id == randomNumberGenerator(dbRows))
+
+
+let dbConnPoolOpts = ConnectionPoolOptions(initialCapacity: 20, maxCapacity: 50, timeout:10000)
+
+func releaseConnection(connection: Connection) {
+    connection.closeConnection()
+}
+
+func generateConnection() -> Connection? {
+    var dbConn: Connection
+    dbConn = PostgreSQLConnection(host: dbHost, port: dbPort,
+                                  options: [.databaseName(dbName),
+                                            .userName(dbUser), .password(dbPass) ])
+
+    
+    dbConn.connect() { error in
+        if let error = error {
+            print(error)
+            return
+        }
+    }
+    return dbConn
+}
+
+let dbConnPool = ConnectionPool(options: dbConnPoolOpts, connectionGenerator: generateConnection, connectionReleaser:releaseConnection)
+
+// Return a random number within the range of rows in the database
+func randomNumberGenerator(_ maxVal: Int) -> Int {
+    #if os(Linux)
+        return Int(random() % maxVal) + 1
+    #else
+        return Int(arc4random_uniform(UInt32(maxVal))) + 1
+    #endif
+}
+
+func getFortunes() -> ([Fortune]?, AppError?) {
+    var resultFortunes: [Fortune]? = nil
+    var errRes: AppError? = nil
+    
+    // Get a dedicated connection object for this transaction from the pool
+    guard let dbConn = dbConnPool.getConnection() else {
+        errRes = AppError.OtherError("Timed out waiting for a DB connection from the pool")
+        return (nil, errRes)
+    }
+    // Ensure that when we complete, the connection is returned to the pool
+    defer {
+        releaseConnection(connection: dbConn)
+    }
+    
+    let query = Select(from: fortunes)
+
+    dbConn.execute(query: query) { result in
+        guard let rows = result.asRows, result.success else {
+            errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
+            return
+        }
+        do {
+            resultFortunes = try rows.map { try Fortune.init(row: $0) }
+        } catch {
+            errRes = AppError.DataFormatError("\(error)")
+        }
+    }
+
+    return (resultFortunes, errRes)
+}
+
+// Get a random row (range 1 to 10,000) from DB: id(int),randomNumber(int)
+// Convert to object using object-relational mapping (ORM) tool
+// Serialize object to JSON - example: {"id":3217,"randomNumber":2149}
+func getRandomRow() -> ([String:Int]?, AppError?) {
+    var resultDict: [String:Int]? = nil
+    var errRes: AppError? = nil
+    // Get a dedicated connection object for this transaction from the pool
+    guard let dbConn = dbConnPool.getConnection() else {
+        errRes = AppError.OtherError("Timed out waiting for a DB connection from the pool")
+        return (resultDict, errRes)
+    }
+    // Ensure that when we complete, the connection is returned to the pool
+    defer {
+        releaseConnection(connection: dbConn)
+    }
+    let rnd = randomNumberGenerator(dbRows)
+    
+    let query = Select(world.randomNumber, from: world)
+        .where(world.id == rnd)
+    
+    dbConn.execute(query: query) { result in
+        if let resultSet = result.asResultSet {
+            guard result.success else {
+                errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
+                return
+            }
+            
+            for row in resultSet.rows {
+                for value in row {
+                    if let unwrapped = value {
+                        guard let randomNumber = unwrapped as? Int32 else {
+                        errRes = AppError.DBKueryError("Error: could not get field as an Int")
+                        return
+                    }
+                        resultDict = ["id":rnd, "randomNumber":Int(randomNumber)]
+                    } else {
+                        errRes = AppError.DBKueryError("Error: randomNumber value is nil")
+                    }
+
+                }
+                
+            }
+        }
+    }
+    return (resultDict, errRes)
+}
+
+// Updates a row of World to a new value.
+func updateRow(id: Int) throws  -> AppError? {
+    // Get a dedicated connection object for this transaction from the pool
+    guard let dbConn = dbConnPool.getConnection() else {
+        throw AppError.OtherError("Timed out waiting for a DB connection from the pool")
+    }
+    // Ensure that when we complete, the connection is returned to the pool
+    defer {
+        releaseConnection(connection: dbConn)
+    }
+    let rndValue = randomNumberGenerator(maxValue)
+    let query = Update(world, set: [(world.randomNumber, rndValue)])
+        .where(world.id == id)
+    var errRes: AppError? = nil
+    dbConn.execute(query: query) { result in
+        if result.asResultSet != nil {
+            guard result.success else {
+                errRes = AppError.DBKueryError("Query failed - status \(String(describing: result.asError))")
+                return
+            }
+            
+        }
+        
+    }
+    return errRes
+}
+
+
+

+ 22 - 0
frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/error.swift

@@ -0,0 +1,22 @@
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public enum AppError: Error {
+    case DBError(String, query: String)
+    case DBKueryError(String)
+    case DataFormatError(String)
+    case OtherError(String)
+}

+ 163 - 0
frameworks/Swift/kitura/Sources/TechEmpowerKueryMustache/main.swift

@@ -0,0 +1,163 @@
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Kitura
+import LoggerAPI
+import HeliumLogger
+import SwiftKuery
+import SwiftKueryPostgreSQL
+import KituraMustache
+
+Log.logger = HeliumLogger(.info)
+
+let router = Router()
+router.add(templateEngine: MustacheTemplateEngine())
+
+//
+// TechEmpower test 6: plaintext
+//
+router.get("/plaintext") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    response.headers["Content-Type"] = "text/plain"
+    try response.status(.OK).send("Hello, world!").end()
+}
+
+//
+// TechEmpower test 1: JSON serialization
+//
+router.get("/json") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let result = ["message":"Hello, World!"]
+    try response.status(.OK).send(json: result).end()
+}
+
+//
+// TechEmpower test 2: Single database query (raw, no ORM)
+//
+router.get("/db") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let result = getRandomRow()
+    guard let dict = result.0 else {
+        guard let err = result.1 else {
+            Log.error("Unknown Error")
+            try response.status(.badRequest).send("Unknown error").end()
+            return
+        }
+        Log.error("\(err)")
+        try response.status(.badRequest).send("Error: \(err)").end()
+        return
+    }
+    try response.status(.OK).send(json: dict).end()
+}
+
+//
+// TechEmpower test 3: Multiple database queries (raw, no ORM)
+// Get param provides number of queries: /queries?queries=N
+//
+router.get("/queries") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    var results: [[String:Int]] = []
+    for _ in 1...numQueries {
+        let result = getRandomRow()
+        guard let dict = result.0 else {
+            guard let err = result.1 else {
+                Log.error("Unknown Error")
+                try response.status(.badRequest).send("Unknown error").end()
+                return
+            }
+            Log.error("\(err)")
+            try response.status(.badRequest).send("Error: \(err)").end()
+            return
+        }
+        results.append(dict)
+    }
+    // Return JSON representation of array of results
+    try response.status(.OK).send(json: results).end()
+}
+
+//
+// TechEmpower test 4: fortunes (raw, no ORM)
+//
+router.get("/fortunes") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    response.headers["Content-Type"] = "text/html; charset=UTF-8"
+    let result = getFortunes()
+    guard var fortunes = result.0 else {
+        guard let err = result.1 else {
+            Log.error("Unknown Error")
+            try response.status(.badRequest).send("Unknown error").end()
+            return
+        }
+        Log.error("\(err)")
+        try response.status(.badRequest).send("Error: \(err)").end()
+        return
+    }
+    fortunes.append(Fortune(id: 0, message: "Additional fortune added at request time."))
+    do {
+      try response.render("fortunes.mustache", context: ["fortunes": fortunes.sorted()]).end()
+    } catch {
+      print("Error: \(error)")
+    }
+}
+
+//
+// TechEmpower test 5: updates (raw, no ORM)
+//
+router.get("/updates") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    var results: [[String:Int]] = []
+    for _ in 1...numQueries {
+        let result = getRandomRow()
+        guard let dict = result.0 else {
+            guard let err = result.1 else {
+                Log.error("Unknown Error")
+                try response.status(.badRequest).send("Unknown error").end()
+                return
+            }
+            Log.error("\(err)")
+            try response.status(.badRequest).send("Error: \(err)").end()
+            return
+        }
+        do {
+            var error: AppError?
+            try error = updateRow(id: dict["id"]!)
+            if let appError = error {
+                throw appError
+            }
+        } catch let err as AppError {
+            try response.status(.badRequest).send("Error: \(err)").end()
+            return
+        }
+        results.append(dict)
+    }
+    
+    // Return JSON representation of array of results
+    try response.status(.OK).send(json: results).end()
+}
+
+
+Kitura.addHTTPServer(onPort: 8080, with: router)
+Kitura.run()

+ 55 - 0
frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/Fortune.swift

@@ -0,0 +1,55 @@
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+struct Fortune {
+
+  let id: Int
+  let message: String
+
+  public init(id: Int, message: String) {
+    self.id = id
+    self.message = message
+  }
+
+  init(row: [String:Any?]) throws {
+    guard let idField = row["id"] else {
+      throw AppError.DataFormatError("Missing 'id' field")
+    }
+    guard let msgField = row["message"] else {
+      throw AppError.DataFormatError("Missing 'message' field")
+    }
+    guard let message = msgField as? String else {
+      throw AppError.DataFormatError("'message' field not a String")
+    }
+    guard let id = idField as? Int32 else {
+      throw AppError.DataFormatError("'id' field not an Int32")
+    }
+    self.init(id: Int(id), message: message)
+  }
+
+}
+
+extension Fortune: Comparable {
+
+  static func == (lhs: Fortune, rhs: Fortune) -> Bool {
+    return lhs.id == rhs.id && lhs.message == rhs.message
+  }
+
+  static func < (lhs: Fortune, rhs: Fortune) -> Bool {
+    return lhs.message < rhs.message || (lhs.message == rhs.message && lhs.id < rhs.id)
+  }
+
+}

+ 127 - 0
frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/Mongo.swift

@@ -0,0 +1,127 @@
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Foundation
+import LoggerAPI
+import Configuration
+import MongoKitten
+
+#if os(Linux)
+    import Glibc
+#else
+    import Darwin
+#endif
+
+// We will load our database configuration from config.json, but this can be
+// overridden with the TFB_DB_CONFIG environment variable.
+let configurationFilename: String = ProcessInfo.processInfo.environment["TFB_DB_CONFIG"] ?? "config.json"
+let manager = ConfigurationManager().load(file: configurationFilename, relativeFrom: .pwd).load(.environmentVariables)
+
+let dbHost = manager["DB_HOST"] as? String ?? manager["mongodb:host"] as? String ?? "localhost"
+let dbPort = Int32(manager["DB_PORT"] as? String != nil ? Int(manager["DB_PORT"] as! String) ?? 27017 : manager["mongodb:port"] as? Int ?? 27017)
+let dbName = manager["mongodb:name"] as? String ?? "hello_world"
+let dbUser = manager["mongodb:user"] as? String ?? "benchmarkdbuser"
+let dbPass = manager["mongodb:password"] as? String ?? "benchmarkdbpass"
+
+let dbRows = 10000
+let maxValue = 10000
+
+var myDatabase: MongoKitten.Database?
+var world: MongoKitten.Collection?
+var fortune: MongoKitten.Collection?
+
+func connectToDB() throws {
+    let connectString = "mongodb://\(dbHost):\(dbPort)/\(dbName)"
+    Log.info("Connect string = \(connectString)")
+    //myDatabase = try MongoKitten.Database("mongodb://\(dbUser):\(dbPass)@\(dbHost):\(dbPort)/\(dbName)")
+    myDatabase = try MongoKitten.Database(connectString)
+    guard let myDatabase = myDatabase else {
+        throw AppError.MongoError("Nil MongoDB connection to \(connectString)")
+    }
+    guard myDatabase.server.isConnected else {
+        throw AppError.MongoError("Not connected to \(connectString)")
+    }
+    world = myDatabase["world"]
+    fortune = myDatabase["fortune"]
+}
+
+// Return a random number within the range of rows in the database
+func randomNumberGenerator(_ maxVal: Int) -> Int {
+    #if os(Linux)
+        return Int(random() % maxVal) + 1
+    #else
+        return Int(arc4random_uniform(UInt32(maxVal))) + 1
+    #endif
+}
+
+// Allow construction of a Fortune from a MongoKitten Document
+extension Fortune {
+    init(document: Document) throws {
+        if let id = Int(document["_id"]), let message = String(document["message"]) {
+            self.init(id: id, message: message)
+        } else {
+            throw AppError.DataFormatError("Expected fields of Fortune document could not be retreived")
+        }
+    }
+}
+
+func getFortunes() throws -> [Fortune] {
+    guard let fortune = fortune else {
+        throw AppError.MongoError("Fortune collection not initialized")
+    }
+    
+//    let allFortunes: [Document] = Array(try fortune.find())
+    let allFortunes = try fortune.find()
+    let resultFortunes: [Fortune] = try allFortunes.map { try Fortune.init(document: $0) }
+    return resultFortunes
+}
+
+// Get a random row (range 1 to 10,000) from DB: id(int),randomNumber(int)
+// Convert to object using object-relational mapping (ORM) tool
+// Serialize object to JSON - example: {"id":3217,"randomNumber":2149}
+func getRandomRow() throws -> [String:Int] {
+    guard let world = world else {
+        throw AppError.MongoError("World collection not initialized")
+    }
+
+    let rnd = randomNumberGenerator(dbRows)
+    let result = try world.findOne("_id" == rnd)
+    guard let document = result else {
+        throw AppError.DataFormatError("World entry id=\(rnd) not found")
+    }
+    guard let id = Int(document["_id"]), let randomNumber = Int(document["randomNumber"]) else {
+        throw AppError.DataFormatError("Expected fields of World document could not be retreived")
+    }
+    return ["id":id, "randomNumber":Int(randomNumber)]
+}
+
+// Updates a row of World to a new value.
+func updateRandomRow() throws -> [String:Int] {
+    guard let world = world else {
+        throw AppError.MongoError("World collection not initialized")
+    }
+    
+    let rnd = randomNumberGenerator(dbRows)
+    let rndValue = randomNumberGenerator(maxValue)
+    let document = try world.findAndUpdate("_id" == rnd, with: ["randomNumber": rndValue])
+    guard let id = Int(document["_id"]), let randomNumber = Int(document["randomNumber"]) else {
+        throw AppError.DataFormatError("Expected fields of World document could not be retreived")
+    }
+    return ["id":id, "randomNumber":Int(randomNumber)]
+}
+
+
+

+ 22 - 0
frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/error.swift

@@ -0,0 +1,22 @@
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public enum AppError: Error {
+    case DBError(String, query: String)
+    case MongoError(String)
+    case DataFormatError(String)
+    case OtherError(String)
+}

+ 137 - 0
frameworks/Swift/kitura/Sources/TechEmpowerMongoKitten/main.swift

@@ -0,0 +1,137 @@
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Kitura
+import LoggerAPI
+import HeliumLogger
+import MongoKitten
+import KituraStencil
+import Stencil
+//import KituraMustache
+
+Log.logger = HeliumLogger(.info)
+
+// Stencil stuff
+let ext = Extension()
+
+// Stencil does not yet support automatic HTML escaping:
+// https://github.com/kylef/Stencil/pull/80
+//
+ext.registerFilter("htmlencode") { (value: Any?) in
+    if let value = value as? String {
+        return value
+            .replacingOccurrences(of: "&", with: "&amp;")
+            .replacingOccurrences(of: "<", with: "&lt;")
+            .replacingOccurrences(of: ">", with: "&gt;")
+            .replacingOccurrences(of: "'", with: "&apos;")
+            .replacingOccurrences(of: "\"", with: "&quot;")
+    }
+    return value
+}
+
+let router = Router()
+router.add(templateEngine: StencilTemplateEngine(extension: ext))
+//router.add(templateEngine: MustacheTemplateEngine())
+
+//
+// TechEmpower test 6: plaintext
+//
+router.get("/plaintext") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    response.headers["Content-Type"] = "text/plain"
+    try response.status(.OK).send("Hello, world!").end()
+}
+
+//
+// TechEmpower test 1: JSON serialization
+//
+router.get("/json") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let result = ["message":"Hello, World!"]
+    try response.status(.OK).send(json: result).end()
+}
+
+//
+// TechEmpower test 2: Single database query (raw, no ORM)
+//
+router.get("/db") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let dict = try getRandomRow()
+    try response.status(.OK).send(json: dict).end()
+}
+
+//
+// TechEmpower test 3: Multiple database queries (raw, no ORM)
+// Get param provides number of queries: /queries?queries=N
+//
+router.get("/queries") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    var results: [[String:Int]] = []
+    for _ in 1...numQueries {
+        let dict = try getRandomRow()
+        results.append(dict)
+    }
+    // Return JSON representation of array of results
+    try response.status(.OK).send(json: results).end()
+}
+
+//
+// TechEmpower test 4: fortunes (raw, no ORM)
+//
+router.get("/fortunes") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    response.headers["Content-Type"] = "text/html; charset=UTF-8"
+    var fortunes = try getFortunes()
+    fortunes.append(Fortune(id: 0, message: "Additional fortune added at request time."))
+
+    try response.render("fortunes.stencil", context: ["fortunes": fortunes.sorted()]).end()
+    //try response.render("fortunes.mustache", context: ["fortunes": fortunes.sorted()]).end()
+}
+
+//
+// TechEmpower test 5: updates (raw, no ORM)
+//
+router.get("/updates") {
+    request, response, next in
+    response.headers["Server"] = "Kitura"
+    let queriesParam = request.queryParameters["queries"] ?? "1"
+    let numQueries = max(1, min(Int(queriesParam) ?? 1, 500))      // Snap to range of 1-500 as per test spec
+    var results: [[String:Int]] = []
+    for _ in 1...numQueries {
+        let dict = try updateRandomRow()
+        results.append(dict)
+    }
+    
+    // Return JSON representation of array of results
+    try response.status(.OK).send(json: results).end()
+}
+
+// Initialize MongoDB connection
+do {
+    try connectToDB()
+} catch {
+    print("Error connecting to database: \(error)")
+}
+
+Kitura.addHTTPServer(onPort: 8080, with: router)
+Kitura.run()

+ 12 - 0
frameworks/Swift/kitura/Views/fortunes.mustache

@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+<tr><th>id</th><th>message</th></tr>
+{{#fortunes}}
+<tr><td>{{id}}</td><td>{{message}}</td></tr>
+{{/fortunes}}
+</table>
+</body>
+</html>

+ 10 - 0
frameworks/Swift/kitura/Views/fortunes.stencil

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+<tr><th>id</th><th>message</th></tr>{% for fortune in fortunes %}
+<tr><td>{{ fortune.id }}</td><td>{{ fortune.message | htmlencode }}</td></tr>{% endfor %}
+</table>
+</body>
+</html>

+ 89 - 0
frameworks/Swift/kitura/benchmark_config.json

@@ -0,0 +1,89 @@
+{
+  "framework": "kitura",
+  "tests": [{
+    "default": {
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "Kitura",
+      "language": "Swift",
+      "orm": "Raw",
+      "platform": "Kitura",
+      "webserver": "Kitura",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Kitura",
+      "notes": ""
+    },
+    "mongodb": {
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "MongoDB",
+      "framework": "Kitura",
+      "language": "Swift",
+      "orm": "Raw",
+      "platform": "Kitura",
+      "webserver": "Kitura",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Kitura",
+      "notes": ""
+    },
+    "gcd": {
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "Kitura",
+      "language": "Swift",
+      "orm": "Raw",
+      "platform": "Kitura",
+      "webserver": "Kitura",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Kitura",
+      "notes": ""
+    },
+    "gcd-mongodb": {
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "MongoDB",
+      "framework": "Kitura",
+      "language": "Swift",
+      "orm": "Raw",
+      "platform": "Kitura",
+      "webserver": "Kitura",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "Kitura",
+      "notes": ""
+    }
+  }]
+}

+ 16 - 0
frameworks/Swift/kitura/config.json

@@ -0,0 +1,16 @@
+{
+  "db": {
+    "host":     "tfb-database",
+    "port":     5432,
+    "name":     "hello_world",
+    "user":     "benchmarkdbuser",
+    "password": "benchmarkdbpass"
+  },
+  "mongodb": {
+    "host":     "tfb-database",
+    "port":     27017,
+    "name":     "hello_world",
+    "user":     "benchmarkdbuser",
+    "password": "benchmarkdbpass"
+  }
+}

+ 7 - 0
frameworks/Swift/kitura/kitura-gcd-mongodb.dockerfile

@@ -0,0 +1,7 @@
+FROM swift:4.1
+
+ADD ./ /kitura
+WORKDIR /kitura
+RUN apt update -yqq && apt install -yqq libpq-dev
+RUN swift build -c release -Xswiftc -DGCD_ASYNCH
+CMD .build/release/TechEmpowerMongoKitten

+ 7 - 0
frameworks/Swift/kitura/kitura-gcd.dockerfile

@@ -0,0 +1,7 @@
+FROM swift:4.1
+
+ADD ./ /kitura
+WORKDIR /kitura
+RUN apt update -yqq && apt install -yqq libpq-dev
+RUN swift build -c release -Xswiftc -DGCD_ASYNCH
+CMD .build/release/TechEmpowerKuery

+ 7 - 0
frameworks/Swift/kitura/kitura-mongodb.dockerfile

@@ -0,0 +1,7 @@
+FROM swift:4.1
+
+ADD ./ /kitura
+WORKDIR /kitura
+RUN apt update -yqq && apt install -yqq libpq-dev
+RUN swift build -c release
+CMD .build/release/TechEmpowerMongoKitten

+ 7 - 0
frameworks/Swift/kitura/kitura.dockerfile

@@ -0,0 +1,7 @@
+FROM swift:4.1
+
+ADD ./ /kitura
+WORKDIR /kitura
+RUN apt update -yqq && apt install -yqq libpq-dev
+RUN swift build -c release
+CMD .build/release/TechEmpowerKuery