Browse Source

Merge remote-tracking branch 'TechEmpower/master'

Jesterovskiy 9 years ago
parent
commit
fee074007c
36 changed files with 791 additions and 90 deletions
  1. 1 0
      .travis.yml
  2. 19 0
      frameworks/Go/kami/README.md
  3. 28 0
      frameworks/Go/kami/benchmark_config.json
  4. 10 0
      frameworks/Go/kami/setup.sh
  5. 191 0
      frameworks/Go/kami/src/kami/server.go
  6. 14 0
      frameworks/Go/kami/templates/fortune.html
  7. 9 0
      frameworks/Go/kami/templates/layout.html
  8. 33 0
      frameworks/Java/bayou/README.md
  9. 24 0
      frameworks/Java/bayou/benchmark_config.json
  10. 81 0
      frameworks/Java/bayou/pom.xml
  11. 7 0
      frameworks/Java/bayou/setup.sh
  12. 1 0
      frameworks/Java/bayou/source_code
  13. 59 0
      frameworks/Java/bayou/src/main/java/bayou/BayouServer.java
  14. 1 1
      frameworks/Java/vertx-web/Readme.md
  15. 26 5
      frameworks/Java/vertx-web/benchmark_config.json
  16. 2 8
      frameworks/Java/vertx-web/pom.xml
  17. 1 1
      frameworks/Java/vertx-web/setup.sh
  18. 5 4
      frameworks/Java/vertx-web/src/main/conf/config.json
  19. 99 32
      frameworks/Java/vertx-web/src/main/java/io/vertx/benchmark/App.java
  20. 1 1
      frameworks/Java/vertx/pom.xml
  21. 8 0
      frameworks/Python/asyncio/aiohttp.web/etc/hello/main/main.yaml
  22. 23 0
      frameworks/Python/asyncio/aiohttp.web/hello/__init__.py
  23. 33 1
      frameworks/Python/asyncio/aiohttp.web/hello/endpoints/world.py
  24. 56 0
      frameworks/Python/asyncio/aiohttp.web/hello/services/mysql.py
  25. 20 0
      frameworks/Python/asyncio/benchmark_config.json
  26. 2 0
      frameworks/Python/asyncio/requirements.txt
  27. 1 2
      frameworks/Rust/hyper/Cargo.toml
  28. 12 12
      frameworks/Rust/hyper/src/main.rs
  29. 3 3
      frameworks/Rust/iron/Cargo.toml
  30. 1 1
      frameworks/Rust/iron/setup.sh
  31. 6 8
      frameworks/Rust/iron/src/main.rs
  32. 2 2
      frameworks/Rust/nickel/Cargo.toml
  33. 1 1
      frameworks/Rust/nickel/setup.sh
  34. 7 4
      frameworks/Rust/nickel/src/main.rs
  35. 1 1
      frameworks/Ur/urweb/setup.sh
  36. 3 3
      toolset/setup/linux/languages/rust.sh

+ 1 - 0
.travis.yml

@@ -69,6 +69,7 @@ env:
     - "TESTDIR=Haskell/wai"
     - "TESTDIR=Haskell/yesod"
     - "TESTDIR=Java/activeweb"
+    - "TESTDIR=Java/bayou"
     - "TESTDIR=Java/comsat-servlet"
     - "TESTDIR=Java/comsat-webactors"
     - "TESTDIR=Java/curacao"

+ 19 - 0
frameworks/Go/kami/README.md

@@ -0,0 +1,19 @@
+# [kame](https://github.com/guregu/kami) (Go) Benchmarking Test
+
+This is the go portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+
+"kami (神) is a tiny web framework using [x/net/context](https://blog.golang.org/context) for request context and [HttpRouter](https://github.com/julienschmidt/httprouter) for routing. It includes a simple system for running hierarchical middleware before and after requests, in addition to log and panic hooks."
+
+
+### Source
+* [All test source](src/kami/server.go)
+
+## Test URLs
+
+    http://localhost:8080/json
+    http://localhost:8080/db
+    http://localhost:8080/queries?queries=[1-500]
+    http://localhost:8080/fortunes
+    http://localhost:8080/updates?queries=[1-500]
+    http://localhost:8080/plaintext

+ 28 - 0
frameworks/Go/kami/benchmark_config.json

@@ -0,0 +1,28 @@
+{
+  "framework": "kami",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "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": "Platform",
+      "database": "MySQL",
+      "framework": "kami",
+      "language": "Go",
+      "orm": "Raw",
+      "platform": "Go",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "kami",
+      "notes": "",
+      "versus": "go"
+    }
+  }]
+}

+ 10 - 0
frameworks/Go/kami/setup.sh

@@ -0,0 +1,10 @@
+#!/bin/bash
+
+sed -i 's|tcp(.*:3306)|tcp('"${DBHOST}"':3306)|g' src/kami/server.go
+
+fw_depends go
+
+go get github.com/go-sql-driver/mysql
+go get github.com/guregu/kami
+
+go run src/kami/server.go &

+ 191 - 0
frameworks/Go/kami/src/kami/server.go

@@ -0,0 +1,191 @@
+package main
+
+import (
+	"database/sql"
+	"encoding/json"
+	"flag"
+	"html/template"
+	"log"
+	"math/rand"
+	"net/http"
+	"sort"
+	"strconv"
+
+	"golang.org/x/net/context"
+
+	_ "github.com/go-sql-driver/mysql"
+	"github.com/guregu/kami"
+)
+
+type Message struct {
+	Message string `json:"message"`
+}
+
+type World struct {
+	Id           uint16 `json:"id"`
+	RandomNumber uint16 `json:"randomNumber"`
+}
+
+type Fortune struct {
+	Id      uint16 `json:"id"`
+	Message string `json:"message"`
+}
+
+// Databases
+const (
+	connectionString   = "benchmarkdbuser:benchmarkdbpass@tcp(localhost:3306)/hello_world?interpolateParams=true"
+	worldSelect        = "SELECT id, randomNumber FROM World WHERE id = ?"
+	worldUpdate        = "UPDATE World SET randomNumber = ? WHERE id = ?"
+	fortuneSelect      = "SELECT id, message FROM Fortune;"
+	worldRowCount      = 10000
+	maxConnectionCount = 256
+)
+
+const helloWorldString = "Hello, World!"
+
+var (
+	// Templates
+	tmpl = template.Must(template.ParseFiles("templates/layout.html", "templates/fortune.html"))
+
+	// Database
+	db *sql.DB
+
+	helloWorldBytes = []byte(helloWorldString)
+)
+
+func main() {
+	var err error
+	db, err = sql.Open("mysql", connectionString)
+	if err != nil {
+		log.Fatalf("Error opening database: %v", err)
+	}
+	db.SetMaxIdleConns(maxConnectionCount)
+
+	flag.Set("bind", ":8080")
+
+	kami.Use("/", serverMiddleware)
+	kami.Get("/json", jsonHandler)
+	kami.Get("/db", dbHandler)
+	kami.Get("/queries", queriesHandler)
+	kami.Get("/fortunes", fortuneHandler)
+	kami.Get("/updates", updateHandler)
+	kami.Get("/plaintext", plaintextHandler)
+	kami.Serve()
+}
+
+// serverMiddleware will set the Server header on all outgoing requests
+func serverMiddleware(ctx context.Context, w http.ResponseWriter, _ *http.Request) context.Context {
+	w.Header().Set("Server", "kami")
+	return ctx
+}
+
+// jsonHandler implements Test 1: JSON Serializer
+func jsonHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(&Message{helloWorldString})
+}
+
+// Test 2: Single database query
+func dbHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+	var world World
+	err := db.QueryRow(worldSelect, rand.Intn(worldRowCount)+1).Scan(&world.Id, &world.RandomNumber)
+	if err != nil {
+		log.Fatalf("Error scanning world row: %s", err.Error())
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(&world)
+}
+
+// Test 3: Multiple database queries
+func queriesHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+	n := 1
+	if nStr := r.URL.Query().Get("queries"); len(nStr) > 0 {
+		n, _ = strconv.Atoi(nStr)
+	}
+
+	if n < 1 {
+		n = 1
+	} else if n > 500 {
+		n = 500
+	}
+
+	world := make([]World, n)
+	for i := 0; i < n; i++ {
+		err := db.QueryRow(worldSelect, rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber)
+		if err != nil {
+			log.Fatalf("Error scanning world row: %s", err.Error())
+		}
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(world)
+}
+
+// Test 4: Fortunes
+func fortuneHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+	rows, err := db.Query(fortuneSelect)
+	if err != nil {
+		log.Fatalf("Error preparing statement: %v", err)
+	}
+
+	fortunes := make(Fortunes, 0, 16)
+	for rows.Next() { //Fetch rows
+		fortune := Fortune{}
+		if err := rows.Scan(&fortune.Id, &fortune.Message); err != nil {
+			log.Fatalf("Error scanning fortune row: %s", err.Error())
+		}
+		fortunes = append(fortunes, &fortune)
+	}
+	rows.Close()
+	fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
+
+	sort.Sort(ByMessage{fortunes})
+	w.Header().Set("Content-Type", "text/html")
+	if err := tmpl.Execute(w, fortunes); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+	}
+}
+
+// Test 5: Database updates
+func updateHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+	n := 1
+	if nStr := r.URL.Query().Get("queries"); len(nStr) > 0 {
+		n, _ = strconv.Atoi(nStr)
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	encoder := json.NewEncoder(w)
+
+	if n < 1 {
+		n = 1
+	} else if n > 500 {
+		n = 500
+	}
+	world := make([]World, n)
+	for i := 0; i < n; i++ {
+		if err := db.QueryRow(worldSelect, rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber); err != nil {
+			log.Fatalf("Error scanning world row: %s", err.Error())
+		}
+		world[i].RandomNumber = uint16(rand.Intn(worldRowCount) + 1)
+		if _, err := db.Exec(worldUpdate, world[i].RandomNumber, world[i].Id); err != nil {
+			log.Fatalf("Error updating world row: %s", err.Error())
+		}
+	}
+	encoder.Encode(world)
+}
+
+// Test 6: Plaintext
+func plaintextHandler(ctx context.Context, w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-Type", "text/plain")
+	w.Write(helloWorldBytes)
+}
+
+type Fortunes []*Fortune
+
+func (s Fortunes) Len() int      { return len(s) }
+func (s Fortunes) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
+type ByMessage struct{ Fortunes }
+
+func (s ByMessage) Less(i, j int) bool { return s.Fortunes[i].Message < s.Fortunes[j].Message }

+ 14 - 0
frameworks/Go/kami/templates/fortune.html

@@ -0,0 +1,14 @@
+{{define "content"}}
+<table>
+<tr>
+<th>id</th>
+<th>message</th>
+</tr>
+{{range .}}
+<tr>
+<td>{{.Id}}</td>
+<td>{{.Message}}</td>
+</tr>
+{{end}}
+</table>
+{{end}}

+ 9 - 0
frameworks/Go/kami/templates/layout.html

@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Fortunes</title>
+</head>
+<body>
+{{template "content" .}}
+</body>
+</html>

+ 33 - 0
frameworks/Java/bayou/README.md

@@ -0,0 +1,33 @@
+# bayou.io Benchmarking Test
+
+This is the `bayou.io` portion of [TechEmpower/FrameworkBenchmarks](https://github.com/TechEmpower/FrameworkBenchmarks).
+
+
+
+## About bayou.io
+
+`bayou.io` is an async http server/client library for Java.
+
+Version: [bayou-1.0.0](http://bayou.io/code.html)
+
+GitHub: <https://github.com/zhong-j-yu/bayou>
+
+More Info: <http://bayou.io/info.html>
+
+
+
+
+## Source for Tests
+
+Json and Plaintext test source:
+
+* [BayouServer.java](src/main/java/bayou/BayouServer.java)
+
+
+
+## Test URLs
+
+* json: <http://localhost:8080/json>
+
+* plaintext: <http://localhost:8080/plaintext>
+

+ 24 - 0
frameworks/Java/bayou/benchmark_config.json

@@ -0,0 +1,24 @@
+{
+    "framework": "bayou",
+    "tests": [{
+        "default": {
+            "setup_file": "setup",
+            "json_url": "/json",
+            "plaintext_url": "/plaintext",
+            "port": 8080,
+            "approach": "Realistic",
+            "classification": "Platform",
+            "database": "None",
+            "framework": "bayou.io",
+            "language": "Java",
+            "orm": "Raw",
+            "platform": "bayou.io",
+            "webserver": "None",
+            "os": "Linux",
+            "database_os": "Linux",
+            "display_name": "bayou.io",
+            "notes": "",
+            "versus": ""
+        }
+    }]
+}

+ 81 - 0
frameworks/Java/bayou/pom.xml

@@ -0,0 +1,81 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>io.bayou</groupId>
+    <artifactId>bayou_TFB</artifactId>
+    <version>0.1</version>
+
+
+
+    <!-- see https://jitpack.io/ -->
+    <repositories>
+        <repository>
+            <id>jitpack.io</id>
+            <url>https://jitpack.io</url>
+        </repository>
+    </repositories>
+
+    <dependencies>
+
+        <!-- see https://jitpack.io/ -->
+        <dependency>
+            <groupId>com.github.zhong-j-yu</groupId>
+            <artifactId>bayou</artifactId>
+            <version>1.0.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>2.6.0</version>
+        </dependency>
+
+    </dependencies>
+
+
+
+    <build>
+        <plugins>
+
+            <!-- require Java 8 -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.3</version>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifest>
+                            <mainClass>bayou.BayouServer</mainClass>
+                        </manifest>
+                    </archive>
+                    <descriptorRefs>
+                        <descriptorRef>jar-with-dependencies</descriptorRef>
+                    </descriptorRefs>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>make-assembly</id> <!-- this is used for inheritance merges -->
+                        <phase>package</phase> <!-- bind to the packaging phase -->
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+
+        </plugins>
+    </build>
+
+
+
+</project>

+ 7 - 0
frameworks/Java/bayou/setup.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+fw_depends java maven
+
+mvn clean compile assembly:single
+cd target
+java -jar bayou_TFB-0.1-jar-with-dependencies.jar &

+ 1 - 0
frameworks/Java/bayou/source_code

@@ -0,0 +1 @@
+./bayou/src/main/java/bayou/BayouServer.java

+ 59 - 0
frameworks/Java/bayou/src/main/java/bayou/BayouServer.java

@@ -0,0 +1,59 @@
+package bayou;
+
+import bayou.http.*;
+import bayou.mime.ContentType;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.util.Collections;
+
+/**
+ *  bayou.io HTTP Server for TechEmpower/FrameworkBenchmarks
+ */
+public class BayouServer
+{
+    public static void main(String[] args) throws Exception
+    {
+        System.setProperty("bayou.http.server.pipeline", "true" ); // favor pipelined requests
+        System.setProperty("bayou.http.server.fiber",    "false"); // fiber not needed in this app
+
+        byte[] bytesHelloWorld = "Hello, World!".getBytes();
+
+        HttpServer server = new HttpServer(request->
+        {
+            switch(request.uri())
+            {
+                case "/json" :
+                    Object obj = Collections.singletonMap("message", "Hello, World!");
+                    return new SimpleHttpResponse(HttpStatus.c200_OK, ContentType.json, toJson(obj));
+
+                case "/plaintext" :
+                    return new SimpleHttpResponse(HttpStatus.c200_OK, ContentType.text_plain, bytesHelloWorld);
+
+                default :
+                    return HttpResponse.text(404, "Not Found -- ", request.uri() );
+            }
+        });
+
+        server.conf().setProxyDefaults();  // disable some non-essential features
+        //server.conf().trafficDump(System.out::print);
+
+        server.start();
+    }
+
+
+    // json - jackson -------------------------------------------------------------------------------------
+    // pretty slow; don't care much.
+
+    static final ObjectMapper objectMapper = new ObjectMapper();
+    static byte[] toJson(Object obj)
+    {
+        try{
+            return objectMapper.writeValueAsBytes(obj);
+        }catch (JsonProcessingException e){
+            throw new RuntimeException(e);  // HTTP 500 Internal Error
+        }
+    }
+
+}

+ 1 - 1
frameworks/Java/vertx-web/Readme.md

@@ -56,7 +56,7 @@ repo and wrk is on your path. The final argument after the `--` is the desired p
 plaintext scenario at a pipeline depth of 16, [just like the Techempower Benchmarks](https://github.com/TechEmpower/FrameworkBenchmarks/blob/6594d32db618c6ca65e0106c5adf2671f7b63654/toolset/benchmark/framework_test.py#L640).
 
 ```
-wrk -c 256 -t 32 -d 10 -s ./scripts/pipeline.lua http://127.0.0.1:8080/plaintext -- 16
+wrk -c 256 -t 32 -d 15 -s ./scripts/pipeline.lua http://localhost:8080/psql/update?queries=20 -- 16
 ```
 
 *Note you may want to tweak the number of client threads (the `-t` arg) being used based on the specs of your load

+ 26 - 5
frameworks/Java/vertx-web/benchmark_config.json

@@ -41,12 +41,33 @@
       "notes": "",
       "versus": ""
     },
+    "mysql": {
+      "setup_file": "setup",
+      "db_url": "/mysql/db",
+      "query_url": "/mysql/queries?queries=",
+      "fortune_url": "/mysql/fortunes",
+      "update_url": "/mysql/update?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "vertx-web",
+      "language": "Java",
+      "orm": "Raw",
+      "platform": "vertx",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "vertx-web-mysql",
+      "notes": "",
+      "versus": ""
+    },
     "postgres": {
       "setup_file": "setup",
-      "db_url": "/jdbc/db",
-      "query_url": "/jdbc/queries?queries=",
-      "fortune_url": "/jdbc/fortunes",
-      "update_url": "/jdbc/update?queries=",
+      "db_url": "/psql/db",
+      "query_url": "/psql/queries?queries=",
+      "fortune_url": "/psql/fortunes",
+      "update_url": "/psql/update?queries=",
       "port": 8080,
       "approach": "Realistic",
       "classification": "Micro",
@@ -58,7 +79,7 @@
       "webserver": "None",
       "os": "Linux",
       "database_os": "Linux",
-      "display_name": "vertx-web-jdbc",
+      "display_name": "vertx-web-postgres",
       "notes": "",
       "versus": ""
     }

+ 2 - 8
frameworks/Java/vertx-web/pom.xml

@@ -12,7 +12,7 @@
   <properties>
     <!-- the main class -->
     <main.verticle>io.vertx.benchmark.App</main.verticle>
-    <vertx.version>3.2.0</vertx.version>
+    <vertx.version>3.2.1</vertx.version>
   </properties>
 
   <dependencies>
@@ -36,7 +36,7 @@
 
     <dependency>
       <groupId>io.vertx</groupId>
-      <artifactId>vertx-jdbc-client</artifactId>
+      <artifactId>vertx-mysql-postgresql-client</artifactId>
       <version>${vertx.version}</version>
     </dependency>
 
@@ -46,12 +46,6 @@
       <version>${vertx.version}</version>
     </dependency>
 
-    <dependency>
-      <groupId>org.postgresql</groupId>
-      <artifactId>postgresql</artifactId>
-      <version>9.4-1206-jdbc42</version>
-    </dependency>
-
   </dependencies>
 
   <build>

+ 1 - 1
frameworks/Java/vertx-web/setup.sh

@@ -6,4 +6,4 @@ fw_depends java maven
 
 mvn clean package
 
-java -Xms2G -Xmx2G -server -XX:+UseNUMA -XX:+UseParallelGC -XX:+AggressiveOpts -Dvertx.disableWebsockets=true -Dvertx.flashPolicyHandler=false -Dvertx.threadChecks=false -Dvertx.disableContextTimings=true -Dvertx.disableTCCL=true -jar target/vertx-benchmark-1.0.0-SNAPSHOT-fat.jar --conf src/main/conf/config.json &
+java -Xms2G -Xmx2G -server -XX:+UseNUMA -XX:+UseParallelGC -XX:+AggressiveOpts -Dvertx.disableWebsockets=true -Dvertx.flashPolicyHandler=false -Dvertx.threadChecks=false -Dvertx.disableContextTimings=true -Dvertx.disableTCCL=true -jar target/vertx-benchmark-1.0.0-SNAPSHOT-fat.jar --instances `grep --count ^processor /proc/cpuinfo` --conf src/main/conf/config.json &

+ 5 - 4
frameworks/Java/vertx-web/src/main/conf/config.json

@@ -2,8 +2,9 @@
   "connection_string": "mongodb://localhost:27017",
   "db_name": "hello_world",
 
-  "url": "jdbc:postgresql://localhost:5432/hello_world",
-  "driver_class": "org.postgresql.Driver",
-  "user": "benchmarkdbuser",
-  "password": "benchmarkdbpass"
+  "host": "localhost",
+  "username": "benchmarkdbuser",
+  "password": "benchmarkdbpass",
+  "database": "hello_world",
+  "maxPoolSize": 32
 }

+ 99 - 32
frameworks/Java/vertx-web/src/main/java/io/vertx/benchmark/App.java

@@ -7,7 +7,9 @@ import io.vertx.core.*;
 import io.vertx.core.http.HttpHeaders;
 import io.vertx.core.json.JsonArray;
 import io.vertx.core.json.JsonObject;
-import io.vertx.ext.jdbc.JDBCClient;
+import io.vertx.ext.asyncsql.AsyncSQLClient;
+import io.vertx.ext.asyncsql.MySQLClient;
+import io.vertx.ext.asyncsql.PostgreSQLClient;
 import io.vertx.ext.mongo.MongoClient;
 import io.vertx.ext.sql.SQLConnection;
 import io.vertx.ext.web.Router;
@@ -168,13 +170,30 @@ public class App extends AbstractVerticle {
   /**
    * JDBC implementation
    */
-  private final class JDBC {
-    private final JDBCClient database;
+  private final class AsyncSQL {
+
+    public static final int MYSQL = 0;
+    public static final int POSTGRES = 1;
+
+    private final AsyncSQLClient database;
+    private final int dbms;
+
     // In order to use a template we first need to create an engine
     private final HandlebarsTemplateEngine engine;
 
-    public JDBC(Vertx vertx, JsonObject config) {
-      this.database = JDBCClient.createShared(vertx, config);
+    public AsyncSQL(Vertx vertx, int driver, JsonObject config) {
+      switch (driver) {
+        case MYSQL:
+          this.database = MySQLClient.createNonShared(vertx, config);
+          this.dbms = MYSQL;
+          break;
+        case POSTGRES:
+          this.database = PostgreSQLClient.createNonShared(vertx, config);
+          this.dbms = POSTGRES;
+          break;
+        default:
+          throw new RuntimeException("Unsupported DB");
+      }
       this.engine = HandlebarsTemplateEngine.create();
     }
 
@@ -216,7 +235,7 @@ public class App extends AbstractVerticle {
 
     public final void queriesHandler(final RoutingContext ctx) {
       final int queries = Helper.getQueries(ctx.request());
-      final World[] worlds = new World[queries];
+      final JsonArray worlds = new JsonArray();
 
       database.getConnection(getConnection -> {
         if (getConnection.failed()) {
@@ -235,7 +254,7 @@ public class App extends AbstractVerticle {
                   .putHeader(HttpHeaders.SERVER, SERVER)
                   .putHeader(HttpHeaders.DATE, date)
                   .putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
-                  .end(new JsonArray(Arrays.asList(worlds)).encode());
+                  .end(worlds.encode());
 
               conn.close();
             } else {
@@ -259,7 +278,7 @@ public class App extends AbstractVerticle {
 
                 final JsonArray row = resultSet.get(0);
 
-                worlds[idx] = new World(row.getInteger(0), row.getInteger(1));
+                worlds.add(new World(row.getInteger(0), row.getInteger(1)));
                 self.handle(idx + 1);
               });
             }
@@ -322,7 +341,16 @@ public class App extends AbstractVerticle {
 
     public final void updateHandler(final RoutingContext ctx) {
       final int queries = Helper.getQueries(ctx.request());
-      final World[] worlds = new World[queries];
+      final JsonArray worlds = new JsonArray();
+
+      final StringBuffer batch;
+
+      if (dbms == POSTGRES) {
+        // Postgres can batch queries
+        batch = new StringBuffer();
+      } else {
+        batch = null;
+      }
 
       database.getConnection(getConnection -> {
         if (getConnection.failed()) {
@@ -336,14 +364,34 @@ public class App extends AbstractVerticle {
           @Override
           public void handle(Integer idx) {
             if (idx == queries) {
-              // stop condition
-              ctx.response()
-                  .putHeader(HttpHeaders.SERVER, SERVER)
-                  .putHeader(HttpHeaders.DATE, date)
-                  .putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
-                  .end(new JsonArray(Arrays.asList(worlds)).encode());
+              switch (dbms) {
+                case MYSQL:
+                  ctx.response()
+                      .putHeader(HttpHeaders.SERVER, SERVER)
+                      .putHeader(HttpHeaders.DATE, date)
+                      .putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
+                      .end(worlds.encode());
 
-              conn.close();
+                  conn.close();
+                  break;
+                case POSTGRES:
+                  // stop condition, first run the batch update
+                  conn.update(batch.toString(), update -> {
+                    if (update.failed()) {
+                      ctx.fail(update.cause());
+                      conn.close();
+                      return;
+                    }
+                    ctx.response()
+                        .putHeader(HttpHeaders.SERVER, SERVER)
+                        .putHeader(HttpHeaders.DATE, date)
+                        .putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
+                        .end(worlds.encode());
+
+                    conn.close();
+                  });
+                  break;
+              }
             } else {
 
               final Handler<Integer> self = this;
@@ -365,17 +413,31 @@ public class App extends AbstractVerticle {
                 }
 
                 final int newRandomNumber = Helper.randomWorld();
-
-                conn.update("UPDATE WORLD SET randomnumber = " + newRandomNumber + " WHERE id = " + id, update -> {
-                  if (update.failed()) {
-                    ctx.fail(update.cause());
-                    conn.close();
-                    return;
-                  }
-
-                  worlds[idx] = new World(id, newRandomNumber);
-                  self.handle(idx + 1);
-                });
+                worlds.add(new World(id, newRandomNumber));
+
+                switch (dbms) {
+                  case MYSQL:
+                    conn.update("UPDATE WORLD SET randomnumber = " + newRandomNumber + " WHERE id = " + id, update -> {
+                      if (update.failed()) {
+                        ctx.fail(update.cause());
+                        conn.close();
+                        return;
+                      }
+
+                      self.handle(idx + 1);
+                    });
+                    break;
+                  case POSTGRES:
+                    batch
+                        .append("UPDATE WORLD SET randomnumber = ")
+                        .append(newRandomNumber)
+                        .append(" WHERE id = ")
+                        .append(id)
+                        .append("; ");
+
+                    self.handle(idx + 1);
+                    break;
+                }
               });
             }
           }
@@ -394,7 +456,8 @@ public class App extends AbstractVerticle {
     vertx.setPeriodic(1000, handler -> date = DateTimeFormatter.RFC_1123_DATE_TIME.format(ZonedDateTime.now()));
 
     final MongoDB mongoDB = new MongoDB(vertx, config());
-    final JDBC jdbc = new JDBC(vertx, config());
+    final AsyncSQL psql = new AsyncSQL(vertx, AsyncSQL.POSTGRES, config());
+    final AsyncSQL mysql = new AsyncSQL(vertx, AsyncSQL.MYSQL, config());
 
     /**
      * This test exercises the framework fundamentals including keep-alive support, request routing, request header
@@ -413,7 +476,8 @@ public class App extends AbstractVerticle {
      * and database connection pool.
      */
     app.get("/mongo/db").handler(mongoDB::dbHandler);
-    app.get("/jdbc/db").handler(jdbc::dbHandler);
+    app.get("/psql/db").handler(psql::dbHandler);
+    app.get("/mysql/db").handler(mysql::dbHandler);
 
     /**
      * This test is a variation of Test #2 and also uses the World table. Multiple rows are fetched to more dramatically
@@ -421,14 +485,16 @@ public class App extends AbstractVerticle {
      * demonstrates all frameworks' convergence toward zero requests-per-second as database activity increases.
      */
     app.get("/mongo/queries").handler(mongoDB::queriesHandler);
-    app.get("/jdbc/queries").handler(jdbc::queriesHandler);
+    app.get("/psql/queries").handler(psql::queriesHandler);
+    app.get("/mysql/queries").handler(mysql::queriesHandler);
 
     /**
      * This test exercises the ORM, database connectivity, dynamic-size collections, sorting, server-side templates,
      * XSS countermeasures, and character encoding.
      */
     app.get("/mongo/fortunes").handler(mongoDB::fortunesHandler);
-    app.get("/jdbc/fortunes").handler(jdbc::fortunesHandler);
+    app.get("/psql/fortunes").handler(psql::fortunesHandler);
+    app.get("/mysql/fortunes").handler(mysql::fortunesHandler);
 
     /**
      * This test is a variation of Test #3 that exercises the ORM's persistence of objects and the database driver's
@@ -436,7 +502,8 @@ public class App extends AbstractVerticle {
      * read-then-write style database operations.
      */
     app.route("/mongo/update").handler(mongoDB::updateHandler);
-    app.route("/jdbc/update").handler(jdbc::updateHandler);
+    app.route("/psql/update").handler(psql::updateHandler);
+    app.route("/mysql/update").handler(mysql::updateHandler);
 
     /**
      * This test is an exercise of the request-routing fundamentals only, designed to demonstrate the capacity of

+ 1 - 1
frameworks/Java/vertx/pom.xml

@@ -8,7 +8,7 @@
 	<properties>
 		<!-- the main class -->
 		<main.class>vertx.WebServer</main.class>
-		<stack.version>3.2.0</stack.version>
+		<stack.version>3.2.1</stack.version>
 	</properties>
 
 	<repositories>

+ 8 - 0
frameworks/Python/asyncio/aiohttp.web/etc/hello/main/main.yaml

@@ -8,6 +8,14 @@ engines:
     password: benchmarkdbpass
     minsize: 22
     maxsize: 22
+  mysql:
+    host: 127.0.0.1
+    port: 3306
+    db: hello_world
+    user: benchmarkdbuser
+    pwd: benchmarkdbpass
+    minsize: 22
+    maxsize: 22
   redis:
     host: 127.0.0.1
     port: 6379

+ 23 - 0
frameworks/Python/asyncio/aiohttp.web/hello/__init__.py

@@ -9,6 +9,7 @@ import asyncio_redis
 from asyncio_redis.protocol import HiRedisProtocol
 import aiohttp.web
 import aiohttp_jinja2
+import aiomysql
 import api_hour
 
 from . import endpoints
@@ -27,12 +28,16 @@ class Container(api_hour.Container):
         self.servers['http'].router.add_route('GET', '/json', endpoints.world.json)
         self.servers['http'].router.add_route('GET', '/db', endpoints.world.db)
         self.servers['http'].router.add_route('GET', '/db_redis', endpoints.world.db_redis)
+        self.servers['http'].router.add_route('GET', '/db_mysql', endpoints.world.db_mysql)
         self.servers['http'].router.add_route('GET', '/queries', endpoints.world.queries)
         self.servers['http'].router.add_route('GET', '/queries_redis', endpoints.world.queries_redis)
+        self.servers['http'].router.add_route('GET', '/queries_mysql', endpoints.world.queries_mysql)
         self.servers['http'].router.add_route('GET', '/fortunes', endpoints.world.fortunes)
         self.servers['http'].router.add_route('GET', '/fortunes_redis', endpoints.world.fortunes_redis)
+        self.servers['http'].router.add_route('GET', '/fortunes_mysql', endpoints.world.fortunes_mysql)
         self.servers['http'].router.add_route('GET', '/updates', endpoints.world.updates)
         self.servers['http'].router.add_route('GET', '/updates_redis', endpoints.world.updates_redis)
+        self.servers['http'].router.add_route('GET', '/updates_mysql', endpoints.world.updates_mysql)
         self.servers['http'].router.add_route('GET', '/plaintext', endpoints.world.plaintext)
 
     def make_servers(self):
@@ -56,6 +61,18 @@ class Container(api_hour.Container):
                                                                      minsize=int(self.config['engines']['pg']['minsize']),
                                                                      maxsize=int(self.config['engines']['pg']['maxsize']),
                                                                      loop=self.loop))
+        self.engines['mysql'] = self.loop.create_task(aiomysql.create_pool(
+                host=self.config['engines']['mysql']['host'],
+                port=self.config['engines']['mysql']['port'],
+                user=self.config['engines']['mysql']['user'],
+                password=self.config['engines']['mysql']['pwd'],
+                db=self.config['engines']['mysql']['db'],
+                minsize=int(self.config['engines']['mysql']['minsize']),
+                maxsize=int(self.config['engines']['mysql']['maxsize']),
+                cursorclass=aiomysql.DictCursor,
+                charset='utf8',
+                use_unicode=True,
+                loop=self.loop))
         yield from asyncio.wait([self.engines['pg']], return_when=asyncio.ALL_COMPLETED)
         self.engines['redis'] = yield from asyncio_redis.Pool.create(host=self.config['engines']['redis']['host'],
                                                                      port=self.config['engines']['redis']['port'],
@@ -74,6 +91,12 @@ class Container(api_hour.Container):
                 yield from self.engines['pg'].result().wait_closed()
             else:
                 yield from self.engines['pg'].cancel()
+        if 'mysql' in self.engines:
+            if self.engines['mysql'].done():
+                self.engines['mysql'].result().close()
+                yield from self.engines['mysql'].result().wait_closed()
+            else:
+                yield from self.engines['mysql'].cancel()
         if 'redis' in self.engines:
             self.engines['redis'].close()
             yield from asyncio.sleep(1) # wait redis close connection

+ 33 - 1
frameworks/Python/asyncio/aiohttp.web/hello/endpoints/world.py

@@ -7,7 +7,7 @@ import aiohttp_jinja2
 
 from ..services import queries_number
 from ..services.world import get_random_record, get_random_records, update_random_records, get_fortunes
-from ..services import redis
+from ..services import redis, mysql
 
 LOG = logging.getLogger(__name__)
 
@@ -30,6 +30,13 @@ def db_redis(request):
 
     return JSON((yield from redis.get_random_record(container)))
 
[email protected]
+def db_mysql(request):
+    """Test type 2: Single database query"""
+    container = request.app.ah_container
+
+    return JSON((yield from mysql.get_random_record(container)))
+
 @asyncio.coroutine
 def queries(request):
     """Test type 3: Multiple database queries"""
@@ -46,6 +53,14 @@ def queries_redis(request):
 
     return JSON((yield from redis.get_random_records(container, limit)))
 
[email protected]
+def queries_mysql(request):
+    """Test type 3: Multiple database queries"""
+    container = request.app.ah_container
+    limit = queries_number(request.GET.get('queries', 1))
+
+    return JSON((yield from mysql.get_random_records(container, limit)))
+
 @asyncio.coroutine
 def fortunes(request):
     """Test type 4: Fortunes"""
@@ -64,6 +79,15 @@ def fortunes_redis(request):
                                           request,
                                           {'fortunes': (yield from redis.get_fortunes(container))})
 
[email protected]
+def fortunes_mysql(request):
+    """Test type 4: Fortunes"""
+    container = request.app.ah_container
+
+    return aiohttp_jinja2.render_template('fortunes.html.j2',
+                                          request,
+                                          {'fortunes': (yield from mysql.get_fortunes(container))})
+
 @asyncio.coroutine
 def updates(request):
     """Test type 5: Database updates"""
@@ -80,6 +104,14 @@ def updates_redis(request):
 
     return JSON((yield from redis.update_random_records(container, limit)))
 
[email protected]
+def updates_mysql(request):
+    """Test type 5: Database updates"""
+    container = request.app.ah_container
+    limit = queries_number(request.GET.get('queries', 1))
+
+    return JSON((yield from mysql.update_random_records(container, limit)))
+
 @asyncio.coroutine
 def plaintext(request):
     """Test type 6: Plaintext"""

+ 56 - 0
frameworks/Python/asyncio/aiohttp.web/hello/services/mysql.py

@@ -0,0 +1,56 @@
+import asyncio
+from random import randint
+from operator import itemgetter
+
+
[email protected]
+def get_random_record(container):
+    with (yield from container.engines['mysql'].result()) as mysql_conn:
+        cur = yield from mysql_conn.cursor()
+        yield from cur.execute('SELECT id AS "Id", randomnumber AS "RandomNumber" FROM world WHERE id=%(idx)s LIMIT 1',
+                               {'idx': randint(1, 10000)})
+        world = yield from cur.fetchone()
+    return world
+
+
[email protected]
+def get_random_records(container, limit):
+    results = []
+    with (yield from container.engines['mysql'].result()) as mysql_conn:
+        cur = yield from mysql_conn.cursor()
+        for i in range(limit):
+            yield from cur.execute('SELECT id AS "Id", randomnumber AS "RandomNumber" FROM world WHERE id=%(idx)s LIMIT 1',
+                                   {'idx': randint(1, 10000)})
+            results.append((yield from cur.fetchone()))
+
+    return results
+
+
[email protected]
+def update_random_records(container, limit):
+    results = []
+
+    with (yield from container.engines['mysql'].result()) as mysql_conn:
+        cur = yield from mysql_conn.cursor()
+        for i in range(limit):
+            yield from cur.execute('SELECT id AS "Id", randomnumber AS "RandomNumber" FROM world WHERE id=%(idx)s LIMIT 1',
+                                   {'idx': randint(1, 10000)})
+            world = yield from cur.fetchone()
+            yield from cur.execute('UPDATE world SET randomnumber=%(random_number)s WHERE id=%(idx)s',
+                                   {'random_number': randint(1, 10000), 'idx': world['Id']})
+            results.append(world)
+    return results
+
+
[email protected]
+def get_fortunes(container):
+    with (yield from container.engines['mysql'].result()) as mysql_conn:
+        cur = yield from mysql_conn.cursor()
+        yield from cur.execute('SELECT * FROM fortune')
+        fortunes = yield from cur.fetchall()
+
+    fortunes.append({'id': 0, 'message': 'Additional fortune added at request time.'})
+
+    fortunes.sort(key=itemgetter('message'))
+
+    return fortunes

+ 20 - 0
frameworks/Python/asyncio/benchmark_config.json

@@ -43,6 +43,26 @@
       "display_name": "API-Hour+aiohttp.web+redis",
       "notes": "Python 3 + API-Hour + AsyncIO + aiohttp.web + Redis"
     },
+    "mysql": {
+      "setup_file": "aiohttp.web/setup",
+      "db_url": "/db_mysql",
+      "query_url": "/queries_mysql?queries=",
+      "fortune_url": "/fortunes_mysql",
+      "update_url": "/updates_mysql?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "MySQL",
+      "framework": "aiohttp.web",
+      "language": "Python",
+      "orm": "Raw",
+      "platform": "API-Hour",
+      "webserver": "Gunicorn",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "API-Hour+aiohttp.web+mysql",
+      "notes": "Python 3 + API-Hour + AsyncIO + aiohttp.web + MySQL"
+    },
     "json": {
       "setup_file": "yocto_http/setup",
       "json_url": "/json",

+ 2 - 0
frameworks/Python/asyncio/requirements.txt

@@ -16,3 +16,5 @@ requests-futures==0.9.5
 setproctitle==1.1.8
 six==1.9.0
 ujson==1.33
+aiomysql==0.0.7
+PyMySQL==0.6.7

+ 1 - 2
frameworks/Rust/hyper/Cargo.toml

@@ -4,5 +4,4 @@ version = "0.1.0"
 authors = ["Steve Klabnik <[email protected]>"]
 
 [dependencies]
-
-hyper="0.5.2"
+hyper="0.7.2"

+ 12 - 12
frameworks/Rust/hyper/src/main.rs

@@ -1,24 +1,24 @@
 extern crate hyper;
 
 use hyper::server::{Server, Request, Response};
-use hyper::status::StatusCode;
 use hyper::uri::RequestUri;
 use hyper::header::ContentType;
-use hyper::header::ContentLength;
 use hyper::header;
 
 const HELLO_WORLD: &'static [u8; 14] = b"Hello, World!\n";
 
 fn main() {
-    Server::http(|req: Request, mut res: Response| {
-        match (req.method, req.uri) {
-            (hyper::Get, RequestUri::AbsolutePath(ref path)) if path == "/plaintext" => {
-                res.headers_mut().set(ContentType::plaintext());
-                res.headers_mut().set(header::Server("Hyper".to_owned()));
+    Server::http("0.0.0.0:8080").unwrap().handle(handler).unwrap();
+}
+
+fn handler(req: Request, mut res: Response) {
+    match (req.method, req.uri) {
+        (hyper::Get, RequestUri::AbsolutePath(ref path)) if path == "/plaintext" => {
+            res.headers_mut().set(ContentType::plaintext());
+            res.headers_mut().set(header::Server("Hyper".to_owned()));
 
-                res.send(HELLO_WORLD).unwrap();
-            }
-            _ => (),
-        };
-    }).listen("0.0.0.0:8080").unwrap();
+            res.send(HELLO_WORLD).unwrap();
+        }
+        _ => (),
+    }
 }

+ 3 - 3
frameworks/Rust/iron/Cargo.toml

@@ -4,6 +4,6 @@ name = "iron"
 version = "0.0.1"
 
 [dependencies]
-rustc-serialize = "0.3"
-iron = "0.1.18"
-router = "0.0.10"
+rustc-serialize = "0.3.18"
+iron = "0.2.6"
+router = "0.1.0"

+ 1 - 1
frameworks/Rust/iron/setup.sh

@@ -2,6 +2,6 @@
 
 fw_depends rust
 
-rm -rf target/
+cargo clean
 cargo build --release
 ./target/release/iron &

+ 6 - 8
frameworks/Rust/iron/src/main.rs

@@ -4,7 +4,7 @@ extern crate rustc_serialize;
 
 use iron::{Iron, Request, Response, IronResult};
 use iron::status;
-use router::{Router};
+use router::Router;
 use rustc_serialize::json;
 
 #[derive(RustcDecodable, RustcEncodable)]
@@ -14,19 +14,17 @@ struct Message {
 
 fn main() {
     let mut router = Router::new();
-    router.get("/json", jsonHandler);
-    router.get("/plaintext", plaintextHandler);
+    router.get("/json", json_handler);
+    router.get("/plaintext", plaintext_handler);
 
     Iron::new(router).http("0.0.0.0:8080").unwrap();
 }
 
-fn jsonHandler(req: &mut Request) -> IronResult<Response> {
-    let message: Message = Message{
-        message: "Hello, World!".to_string(),
-    };
+fn json_handler(_: &mut Request) -> IronResult<Response> {
+    let message: Message = Message { message: "Hello, World!".to_string() };
     Ok(Response::with((status::Ok, json::encode(&message).unwrap())))
 }
 
-fn plaintextHandler(req: &mut Request) -> IronResult<Response> {
+fn plaintext_handler(_: &mut Request) -> IronResult<Response> {
     Ok(Response::with((status::Ok, "Hello, World!")))
 }

+ 2 - 2
frameworks/Rust/nickel/Cargo.toml

@@ -4,6 +4,6 @@ name = "nickel"
 version = "0.0.1"
 
 [dependencies]
-rustc-serialize = "0.3"
-nickel = "0.5.0"
+rustc-serialize = "0.3.18"
+nickel = "0.7.3"
 nickel_macros = "0.1.0"

+ 1 - 1
frameworks/Rust/nickel/setup.sh

@@ -2,6 +2,6 @@
 
 fw_depends rust
 
-rm -rf target/
+cargo clean
 cargo build --release
 ./target/release/nickel &

+ 7 - 4
frameworks/Rust/nickel/src/main.rs

@@ -1,7 +1,8 @@
-#[macro_use] extern crate nickel;
+#[macro_use]
+extern crate nickel;
 extern crate rustc_serialize;
 
-use nickel::{ Nickel, HttpRouter, MediaType };
+use nickel::{Nickel, HttpRouter, MediaType};
 use rustc_serialize::json;
 
 #[derive(RustcDecodable, RustcEncodable)]
@@ -13,7 +14,8 @@ fn main() {
     let mut server = Nickel::new();
     let mut router = Nickel::router();
 
-    router.get("/json", middleware!{ |_, mut response|
+    router.get("/json",
+               middleware!{ |_, mut response|
         response.set(MediaType::Json);
         let message: Message = Message{
             message: "Hello, World!".to_string(),
@@ -21,7 +23,8 @@ fn main() {
         json::encode(&message).unwrap()
     });
 
-    router.get("/plaintext", middleware! { |_, mut response|
+    router.get("/plaintext",
+               middleware! { |_, mut response|
         response.set(MediaType::Txt);
         "Hello, World!"
     });

+ 1 - 1
frameworks/Ur/urweb/setup.sh

@@ -1,6 +1,6 @@
 #!/bin/bash
 
-VERSION=20150520
+VERSION=20160213
 COMPILER=${IROOT}/urweb
 
 RETCODE=$(fw_exists ${COMPILER}.installed)

+ 3 - 3
toolset/setup/linux/languages/rust.sh

@@ -5,10 +5,10 @@ RETCODE=$(fw_exists $IROOT/rust.installed)
   source $IROOT/rust.installed;
   return 0; }
 
-fw_get -O https://static.rust-lang.org/dist/rust-1.0.0-x86_64-unknown-linux-gnu.tar.gz
-fw_untar rust-1.0.0-x86_64-unknown-linux-gnu.tar.gz
+fw_get -O https://static.rust-lang.org/dist/rust-1.6.0-x86_64-unknown-linux-gnu.tar.gz
+fw_untar rust-1.6.0-x86_64-unknown-linux-gnu.tar.gz
 (
-	cd rust-1.0.0-x86_64-unknown-linux-gnu
+	cd rust-1.6.0-x86_64-unknown-linux-gnu
 	./install.sh --prefix=$IROOT/rust
 )