Browse Source

Added initial fasthttp benchmark

Aliaksandr Valialkin 10 years ago
parent
commit
c00129bfb5

+ 1 - 0
.travis.yml

@@ -49,6 +49,7 @@ env:
     - "TESTDIR=Erlang/elli"
     - "TESTDIR=Erlang/elli"
     - "TESTDIR=Go/beego"
     - "TESTDIR=Go/beego"
     - "TESTDIR=Go/falcore"
     - "TESTDIR=Go/falcore"
+    - "TESTDIR=Go/fasthttp"
     - "TESTDIR=Go/gin"
     - "TESTDIR=Go/gin"
     - "TESTDIR=Go/go-raw"
     - "TESTDIR=Go/go-raw"
     - "TESTDIR=Go/goji"
     - "TESTDIR=Go/goji"

+ 16 - 0
frameworks/Go/fasthttp/README.md

@@ -0,0 +1,16 @@
+# Go Benchmarking Test
+
+This is the go portion of a [benchmarking test suite](../) comparing a variety of web development platforms.
+
+### JSON Encoding Test
+* [JSON test source](src/hello/hello.go)
+
+## Versions
+
+* [fasthttp](https://github.com/valyala/fasthttp)
+
+## Test URLs
+
+### JSON Encoding Test
+
+http://localhost:8080/json

+ 51 - 0
frameworks/Go/fasthttp/benchmark_config.json

@@ -0,0 +1,51 @@
+{
+  "framework": "go",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortune",
+      "update_url": "/update?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "MySQL",
+      "framework": "go",
+      "language": "Go",
+      "orm": "Raw",
+      "platform": "Go",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "fasthttp",
+      "notes": "",
+      "versus": "go"
+    },
+    "prefork": {
+      "setup_file": "setup_prefork",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortune",
+      "update_url": "/update?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "MySQL",
+      "framework": "go",
+      "language": "Go",
+      "orm": "Raw",
+      "platform": "Go",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "fasthttp",
+      "notes": "",
+      "versus": "go"
+    }
+  }]
+}

+ 2 - 0
frameworks/Go/fasthttp/setup.bat

@@ -0,0 +1,2 @@
+set GOPATH=C:\FrameworkBenchmarks\go
+go run src\hello\hello.go

+ 9 - 0
frameworks/Go/fasthttp/setup.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+sed -i 's|tcp(.*:3306)|tcp('"${DBHOST}"':3306)|g' src/hello/hello.go
+
+fw_depends go
+
+go get ./...
+
+go run src/hello/hello.go &

+ 9 - 0
frameworks/Go/fasthttp/setup_prefork.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+sed -i 's|tcp(.*:3306)|tcp('"${DBHOST}"':3306)|g' src/hello/hello.go
+
+fw_depends go
+
+go get ./...
+
+go run src/hello/hello.go -prefork &

+ 6 - 0
frameworks/Go/fasthttp/source_code

@@ -0,0 +1,6 @@
+./go/src/
+./go/src/hello
+./go/src/hello/hello.go
+./go/templates/
+./go/templates/fortune.html
+./go/templates/layout.html

+ 266 - 0
frameworks/Go/fasthttp/src/hello/hello.go

@@ -0,0 +1,266 @@
+package main
+
+import (
+	"bytes"
+	"database/sql"
+	"encoding/json"
+	"flag"
+	"html/template"
+	"log"
+	"math/rand"
+	"net"
+	"os"
+	"os/exec"
+	"runtime"
+	"sort"
+
+	_ "github.com/go-sql-driver/mysql"
+	"github.com/valyala/fasthttp"
+)
+
+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 (
+	// Go 1.4's sql.DB has scalability problem when using (explicitly reused) prepared statement.
+	// https://github.com/golang/go/issues/9484
+	//
+	// Using db.Query() instead of stmt.Query() avoid the issue.
+	// But it makes 3 round trips per query: prepare, execute and close.
+	// `interpolateParams=true` enables client side parameter interpolation.
+	// It reduces round trips without prepared statement.
+	//
+	// Before Go 1.5 is released, we can see real power of Go with this benchmark.
+	// After Go 1.5 is released, we can see prepared statement vs interpolation by comparing
+	// this and another lightweight Go framework.
+	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)
+)
+
+var prefork = flag.Bool("prefork", false, "use prefork")
+var child = flag.Bool("child", false, "is child proc")
+
+func main() {
+	var listener net.Listener
+	flag.Parse()
+	if !*prefork {
+		runtime.GOMAXPROCS(runtime.NumCPU())
+	} else {
+		listener = doPrefork()
+	}
+
+	var err error
+	db, err = sql.Open("mysql", connectionString)
+	if err != nil {
+		log.Fatalf("Error opening database: %v", err)
+	}
+	db.SetMaxIdleConns(maxConnectionCount)
+
+	s := &fasthttp.Server{
+		Handler: func(ctx *fasthttp.RequestCtx) {
+			path := ctx.Path()
+			switch {
+			case fasthttp.EqualBytesStr(path, "/plaintext"):
+				plaintextHandler(ctx)
+			case fasthttp.EqualBytesStr(path, "/json"):
+				jsonHandler(ctx)
+			case fasthttp.EqualBytesStr(path, "/db"):
+				dbHandler(ctx)
+			case fasthttp.EqualBytesStr(path, "/queries"):
+				dbHandler(ctx)
+			case fasthttp.EqualBytesStr(path, "/fortune"):
+				fortuneHandler(ctx)
+			default:
+				ctx.Error("unexpected path", fasthttp.StatusBadRequest)
+			}
+		},
+		Name: "fasthttp",
+	}
+	if !*prefork {
+		s.ListenAndServe(":8080")
+	} else {
+		s.Serve(listener)
+	}
+}
+
+func doPrefork() (listener net.Listener) {
+	var err error
+	var fl *os.File
+	var tcplistener *net.TCPListener
+	if !*child {
+		var addr *net.TCPAddr
+		addr, err = net.ResolveTCPAddr("tcp", ":8080")
+		if err != nil {
+			log.Fatal(err)
+		}
+		tcplistener, err = net.ListenTCP("tcp", addr)
+		if err != nil {
+			log.Fatal(err)
+		}
+		fl, err = tcplistener.File()
+		if err != nil {
+			log.Fatal(err)
+		}
+		children := make([]*exec.Cmd, runtime.NumCPU())
+		for i := range children {
+			children[i] = exec.Command(os.Args[0], "-prefork", "-child")
+			children[i].Stdout = os.Stdout
+			children[i].Stderr = os.Stderr
+			children[i].ExtraFiles = []*os.File{fl}
+			err = children[i].Start()
+			if err != nil {
+				log.Fatal(err)
+			}
+		}
+		for _, ch := range children {
+			var err error = ch.Wait()
+			if err != nil {
+				log.Print(err)
+			}
+		}
+		os.Exit(0)
+	} else {
+		fl = os.NewFile(3, "")
+		listener, err = net.FileListener(fl)
+		if err != nil {
+			log.Fatal(err)
+		}
+		runtime.GOMAXPROCS(1)
+	}
+	return listener
+}
+
+func jsonMarshal(ctx *fasthttp.RequestCtx, v interface{}) {
+	body, err := json.Marshal(v)
+	if err != nil {
+		log.Fatalf("Error in json.Marshal: %s", err)
+	}
+	ctx.Success("application/json", body)
+}
+
+// Test 1: JSON serialization
+func jsonHandler(ctx *fasthttp.RequestCtx) {
+	jsonMarshal(ctx, &Message{helloWorldString})
+}
+
+// Test 2: Single database query
+func dbHandler(ctx *fasthttp.RequestCtx) {
+	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)
+	}
+
+	jsonMarshal(ctx, &world)
+}
+
+// Test 3: Multiple database queries
+func queriesHandler(ctx *fasthttp.RequestCtx) {
+	n := ctx.QueryArgs().GetUintOrZero("queries")
+	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)
+		}
+	}
+
+	jsonMarshal(ctx, world)
+}
+
+// Test 4: Fortunes
+func fortuneHandler(ctx *fasthttp.RequestCtx) {
+	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)
+		}
+		fortunes = append(fortunes, &fortune)
+	}
+	rows.Close()
+	fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
+
+	sort.Sort(ByMessage{fortunes})
+
+	var w bytes.Buffer
+	if err := tmpl.Execute(&w, fortunes); err != nil {
+		log.Fatalf("Error executing fortune: %s", err)
+	}
+	ctx.Success("text/html", w.Bytes())
+}
+
+// Test 5: Database updates
+func updateHandler(ctx *fasthttp.RequestCtx) {
+	n := ctx.QueryArgs().GetUintOrZero("queries")
+	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)
+		}
+		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)
+		}
+	}
+
+	jsonMarshal(ctx, world)
+}
+
+// Test 6: Plaintext
+func plaintextHandler(ctx *fasthttp.RequestCtx) {
+	ctx.Success("text/plain", 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/fasthttp/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/fasthttp/templates/layout.html

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