ソースを参照

Merge branch 'master' of github-msmith:TechEmpower/FrameworkBenchmarks

msmith-techempower 9 年 前
コミット
deaf3efd1d

+ 1 - 0
.travis.yml

@@ -62,6 +62,7 @@ env:
     - "TESTDIR=Go/goji"
     - "TESTDIR=Go/go-std-mongodb"
     - "TESTDIR=Go/go-std-mysql"
+    - "TESTDIR=Go/go-std-postgresql"
     - "TESTDIR=Go/revel"
     - "TESTDIR=Go/revel-jet"
     - "TESTDIR=Go/revel-qbs"

+ 26 - 0
frameworks/Go/go-std-postgresql/benchmark_config.json

@@ -0,0 +1,26 @@
+{
+  "framework": "go-std-postgresql",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortune",
+      "update_url": "/update?queries=",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "Postgres",
+      "framework": "go",
+      "language": "Go",
+      "orm": "Raw",
+      "platform": "Go",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "go-std-postgresql",
+      "notes": "",
+      "versus": "go"
+    }
+  }]
+}

+ 7 - 0
frameworks/Go/go-std-postgresql/setup.sh

@@ -0,0 +1,7 @@
+#!/bin/bash
+
+fw_depends go
+
+go get ./...
+
+go run src/hello/hello.go &

+ 3 - 0
frameworks/Go/go-std-postgresql/source_code

@@ -0,0 +1,3 @@
+./go/src/
+./go/src/hello
+./go/src/hello/hello.go

+ 219 - 0
frameworks/Go/go-std-postgresql/src/hello/hello.go

@@ -0,0 +1,219 @@
+package main
+
+import (
+	"database/sql"
+	"encoding/json"
+	"flag"
+	"fmt"
+	"html/template"
+	"log"
+	"math/rand"
+	"net/http"
+	"os"
+	"sort"
+	"strconv"
+
+	_ "github.com/lib/pq"
+)
+
+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"`
+}
+
+const (
+	// Content
+	fortuneHTML = `<!DOCTYPE html>
+<html>
+<head>
+<title>Fortunes</title>
+</head>
+<body>
+<table>
+<tr>
+<th>id</th>
+<th>message</th>
+</tr>
+{{range .}}
+<tr>
+<td>{{.Id}}</td>
+<td>{{.Message}}</td>
+</tr>
+{{end}}
+</table>
+</body>
+</html>`
+
+	// Database
+	connectionString = "postgres://benchmarkdbuser:benchmarkdbpass@%s/hello_world?sslmode=disable"
+	worldSelect      = "SELECT id, randomNumber FROM World WHERE id = $1"
+	worldUpdate      = "UPDATE World SET randomNumber = $1 WHERE id = $2"
+	fortuneSelect    = "SELECT id, message FROM Fortune"
+	worldRowCount    = 10000
+	maxConnections   = 256
+)
+
+var (
+	// Templates
+	tmpl = template.Must(template.New("fortune.html").Parse(fortuneHTML))
+
+	// Database
+	db                    *sql.DB
+	worldSelectPrepared   *sql.Stmt
+	worldUpdatePrepared   *sql.Stmt
+	fortuneSelectPrepared *sql.Stmt
+)
+
+func initDB() {
+	var err error
+	var dbhost = os.Getenv("DBHOST")
+	if dbhost == "" {
+		dbhost = "localhost"
+	}
+	db, err = sql.Open("postgres", fmt.Sprintf(connectionString, dbhost))
+	if err != nil {
+		log.Fatalf("Error opening database: %v", err)
+	}
+	db.SetMaxIdleConns(maxConnections)
+	db.SetMaxOpenConns(maxConnections)
+
+	worldSelectPrepared, err = db.Prepare(worldSelect)
+	if err != nil {
+		log.Fatal(err)
+	}
+	worldUpdatePrepared, err = db.Prepare(worldUpdate)
+	if err != nil {
+		log.Fatal(err)
+	}
+	fortuneSelectPrepared, err = db.Prepare(fortuneSelect)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+func main() {
+	flag.Parse()
+
+	initDB()
+
+	http.HandleFunc("/db", dbHandler)
+	http.HandleFunc("/queries", queriesHandler)
+	http.HandleFunc("/fortune", fortuneHandler)
+	http.HandleFunc("/update", updateHandler)
+
+	log.Fatal(http.ListenAndServe(":8080", nil))
+}
+
+func getQueriesParam(r *http.Request) int {
+	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
+	}
+	return n
+}
+
+// Test 2: Single database query
+func dbHandler(w http.ResponseWriter, r *http.Request) {
+	var world World
+	err := worldSelectPrepared.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world.Id, &world.RandomNumber)
+	if err != nil {
+		log.Fatalf("Error scanning world row: %s", err.Error())
+	}
+
+	w.Header().Set("Server", "Go")
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(&world)
+}
+
+// Test 3: Multiple database queries
+func queriesHandler(w http.ResponseWriter, r *http.Request) {
+	n := getQueriesParam(r)
+
+	world := make([]World, n)
+	for i := 0; i < n; i++ {
+		err := worldSelectPrepared.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber)
+		if err != nil {
+			log.Fatalf("Error scanning world row: %v", err)
+		}
+	}
+
+	w.Header().Set("Server", "Go")
+	w.Header().Set("Content-Type", "application/json")
+	json.NewEncoder(w).Encode(world)
+}
+
+// Test 4: Fortunes
+func fortuneHandler(w http.ResponseWriter, r *http.Request) {
+	rows, err := fortuneSelectPrepared.Query()
+	if err != nil {
+		log.Fatalf("Error preparing statement: %v", err)
+	}
+	defer rows.Close()
+
+	fortunes := fetchFortunes(rows)
+	fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
+
+	sort.Sort(ByMessage{fortunes})
+	w.Header().Set("Server", "Go")
+	w.Header().Set("Content-Type", "text/html; charset=utf-8")
+	if err := tmpl.Execute(w, fortunes); err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+	}
+}
+
+func fetchFortunes(rows *sql.Rows) Fortunes {
+	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)
+	}
+	return fortunes
+}
+
+// Test 5: Database updates
+func updateHandler(w http.ResponseWriter, r *http.Request) {
+	n := getQueriesParam(r)
+
+	world := make([]World, n)
+	for i := 0; i < n; i++ {
+		if err := worldSelectPrepared.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber); err != nil {
+			log.Fatalf("Error scanning world row: %v", err)
+		}
+		world[i].RandomNumber = uint16(rand.Intn(worldRowCount) + 1)
+		if _, err := worldUpdatePrepared.Exec(world[i].RandomNumber, world[i].Id); err != nil {
+			log.Fatalf("Error updating world row: %v", err)
+		}
+	}
+
+	w.Header().Set("Server", "Go")
+	w.Header().Set("Content-Type", "application/json")
+	encoder := json.NewEncoder(w)
+	encoder.Encode(world)
+}
+
+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 }

BIN
frameworks/Go/revel-jet/bin/revel


+ 3 - 4
frameworks/Go/revel-jet/setup.sh

@@ -1,9 +1,8 @@
 #!/bin/bash
 
-sed -i 's|tcp(.*:3306)|tcp('"${DBHOST}"':3306)|g' src/benchmark/conf/app.conf
-
 fw_depends go
 
-go get -u github.com/robfig/revel/revel github.com/eaigner/jet
-go build -o bin/revel github.com/robfig/revel/revel
+sed -i 's|tcp(.*:3306)|tcp('"${DBHOST}"':3306)|g' src/benchmark/conf/app.conf
+
+go get -u github.com/revel/cmd/revel github.com/eaigner/jet
 bin/revel run benchmark prod &

+ 3 - 2
frameworks/Go/revel-jet/src/benchmark/app/controllers/app.go

@@ -1,11 +1,12 @@
 package controllers
 
 import (
-	"benchmark/app/db"
-	"github.com/robfig/revel"
 	"math/rand"
 	"runtime"
 	"sort"
+
+	"benchmark/app/db"
+	"github.com/revel/revel"
 )
 
 type MessageStruct struct {

+ 1 - 2
frameworks/Go/revel-jet/src/benchmark/app/db/jet.go

@@ -2,8 +2,7 @@ package db
 
 import (
 	"github.com/eaigner/jet"
-	"github.com/robfig/revel"
-	// "os"
+	"github.com/revel/revel"
 )
 
 var (

BIN
frameworks/Go/revel-qbs/bin/revel


+ 3 - 4
frameworks/Go/revel-qbs/setup.sh

@@ -1,9 +1,8 @@
 #!/bin/bash
 
-sed -i 's|tcp(.*:3306)|tcp('"${DBHOST}"':3306)|g' src/benchmark/conf/app.conf
-
 fw_depends go
 
-go get -u github.com/robfig/revel/revel github.com/coocood/qbs
-go build -o bin/revel github.com/robfig/revel/revel
+sed -i 's|tcp(.*:3306)|tcp('"${DBHOST}"':3306)|g' src/benchmark/conf/app.conf
+
+go get -u github.com/revel/cmd/revel github.com/coocood/qbs
 bin/revel run benchmark prod &

+ 5 - 3
frameworks/Go/revel-qbs/src/benchmark/app/controllers/app.go

@@ -1,12 +1,14 @@
 package controllers
 
 import (
-	"benchmark/app/db"
-	"github.com/coocood/qbs"
-	"github.com/robfig/revel"
 	"math/rand"
 	"runtime"
 	"sort"
+
+	"benchmark/app/db"
+
+	"github.com/coocood/qbs"
+	"github.com/revel/revel"
 )
 
 type MessageStruct struct {

+ 1 - 1
frameworks/Go/revel-qbs/src/benchmark/app/db/qbs.go

@@ -2,7 +2,7 @@ package db
 
 import (
 	"github.com/coocood/qbs"
-	"github.com/robfig/revel"
+	"github.com/revel/revel"
 )
 
 var (

+ 12 - 1
frameworks/Java/jooby/conf/application.conf

@@ -1,6 +1,17 @@
+# add or override properties
+# See https://github.com/typesafehub/config/blob/master/HOCON.md for more details
+
 application.env = prod
 
+## server threads
+server.threads.Min = ${runtime.processors}
+server.threads.Max = ${runtime.processors-x2}
+
+# default DBHOST
+DBHOST = localhost
+
+## database
 db.url = "jdbc:mysql://"${DBHOST}":3306/hello_world?jdbcCompliantTruncation=false&elideSetAutoCommits=true&useLocalSessionState=true&cachePrepStmts=true&cacheCallableStmts=true&alwaysSendSetIsolation=false&prepStmtCacheSize=4096&cacheServerConfiguration=true&prepStmtCacheSqlLimit=2048&zeroDateTimeBehavior=convertToNull&traceProtocol=false&useUnbufferedInput=false&useReadAheadInput=false&maintainTimeStats=false&useServerPrepStmts&cacheRSMetadata=true"
 db.user = benchmarkdbuser
 db.password = benchmarkdbpass
-hikari.maximumPoolSize = 100
+hikari.maximumPoolSize = ${server.threads.Max}

+ 3 - 3
frameworks/Java/jooby/pom.xml

@@ -7,7 +7,7 @@
   <parent>
     <groupId>org.jooby</groupId>
     <artifactId>jooby-project</artifactId>
-    <version>0.14.0</version>
+    <version>0.16.0</version>
   </parent>
 
   <artifactId>jooby</artifactId>
@@ -15,10 +15,9 @@
   <version>1.0</version>
 
   <name>jooby</name>
-  <description>generated by Jooby archetype</description>
 
   <properties>
-    <jooby.version>0.14.0</jooby.version>
+    <jooby.version>0.16.0</jooby.version>
 
     <!-- Startup class -->
     <application.class>com.techempower.App</application.class>
@@ -37,6 +36,7 @@
       <artifactId>jooby-jackson</artifactId>
     </dependency>
 
+    <!-- jdbi -->
     <dependency>
       <groupId>org.jooby</groupId>
       <artifactId>jooby-jdbi</artifactId>

+ 38 - 50
frameworks/Java/jooby/src/main/java/com/techempower/App.java

@@ -1,20 +1,20 @@
 package com.techempower;
 
-import org.jooby.Jooby;
-import org.jooby.MediaType;
-import org.jooby.Results;
-import org.jooby.Result;
-import org.jooby.json.Jackson;
-import java.util.Map;
-import java.util.HashMap;
-import java.util.Locale;
-import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+import java.util.concurrent.ThreadLocalRandom;
+
+import org.jooby.Jooby;
+import org.jooby.MediaType;
+import org.jooby.Result;
+import org.jooby.Results;
 import org.jooby.jdbi.Jdbi;
+import org.jooby.json.Jackson;
 import org.skife.jdbi.v2.Handle;
-import java.util.Random;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
 
 /**
  * @author jooby generator
@@ -28,56 +28,44 @@ public class App extends Jooby {
   static final String H_SERVER = "Server";
   static final String SERVER = "Netty";
 
-  static final String H_DATE= "Date";
-
-  static final String helloWorld = "Hello, World!";
+  static final String H_DATE = "Date";
 
   static final int DB_ROWS = 10000;
 
+  static final String HELLO_WORLD = "Hello, World!";
+
   {
-    use(new Jackson());
+    /** json via jackson .*/
+    ObjectMapper mapper = new ObjectMapper();
+    use(new Jackson(mapper));
+
+    /** database via jdbi .*/
     use(new Jdbi());
 
-    get("*", (req, rsp) -> {
-      rsp.header(H_SERVER, SERVER)
-         .header(H_DATE, fmt.format(Instant.ofEpochMilli(System.currentTimeMillis())));
-    });
-
-    /**
-     * Plain text response. Please note all these lines can be just:
-     *
-     *  get("/plaintext", () -> "Hello, World!");
-     *
-     *  This way we will get just a few extra ms.
-     */
-    byte[] plaintextBytes = helloWorld.getBytes(StandardCharsets.UTF_8);
-    Result plaintext = Results
-        .with(plaintextBytes)
-        .type(MediaType.plain);
-
-    get("/plaintext", () -> plaintext);
-
-    /**
-     * json response.
-     */
-    Map<String, Object> hash = new HashMap<>();
-    hash.put("message", helloWorld);
-    Result json = Results
-        .with(hash)
-        .type(MediaType.json);
-
-    get("/json", () -> json);
+    get("/plaintext", () -> result(HELLO_WORLD, MediaType.text))
+        .renderer("text");
+
+    get("/json", () -> result(mapper.createObjectNode().put("message", HELLO_WORLD), MediaType.json))
+        .renderer("json");
 
     get("/db", req -> {
       try (Handle handle = req.require(Handle.class)) {
-        Random rnd = new Random();
-        int id = rnd.nextInt(DB_ROWS);
-        return handle.createQuery("select * from World where id = :id")
-          .bind("id", id)
-          .map((idx, rs, ctx) -> new World(rs.getInt("id"), rs.getInt("randomNumber")))
-          .first();
+        int id = ThreadLocalRandom.current().nextInt(DB_ROWS + 1);
+        return result(
+            handle.createQuery("select * from World where id = :id")
+                .bind("id", id)
+                .map((idx, rs, ctx) -> new World(rs.getInt("id"), rs.getInt("randomNumber")))
+                .first(),
+            MediaType.json);
       }
-  });
+    }).renderer("json");
+
+  }
+
+  private Result result(final Object value, final MediaType type) {
+    return Results.ok(value).type(type)
+        .header(H_SERVER, SERVER)
+        .header(H_DATE, fmt.format(Instant.ofEpochMilli(System.currentTimeMillis())));
   }
 
   public static void main(final String[] args) throws Exception {