Browse Source

Merge pull request #2103 from vishr/master

Upgraded Go/Echo to v2.
Nate 9 years ago
parent
commit
481b65de89

+ 1 - 0
.travis.yml

@@ -54,6 +54,7 @@ env:
     - "TESTDIR=Erlang/mochiweb"
     - "TESTDIR=Erlang/mochiweb"
     - "TESTDIR=Erlang/misultin"
     - "TESTDIR=Erlang/misultin"
     - "TESTDIR=Go/beego"
     - "TESTDIR=Go/beego"
+    - "TESTDIR=Go/echo"
     - "TESTDIR=Go/falcore"
     - "TESTDIR=Go/falcore"
     - "TESTDIR=Go/fasthttp"
     - "TESTDIR=Go/fasthttp"
     - "TESTDIR=Go/gin"
     - "TESTDIR=Go/gin"

+ 8 - 8
frameworks/Go/echo/README.md

@@ -1,14 +1,14 @@
-# [ECHO](http://echo.labstack.com) (GoLang) Benchmarking Test
+# [Echo](https://github.com/labstack/echo) (Go) Benchmarking Test
 
 
 This is the go portion of a [benchmarking test suite](https://www.techempower.com/benchmarks/) comparing a variety of web development platforms.
 This is the go portion of a [benchmarking test suite](https://www.techempower.com/benchmarks/) comparing a variety of web development platforms.
 
 
-"Echo is a fast and unfancy micro web framework for Go."
+> Echo is a fast and unfancy micro web framework for Go
 
 
 ## Test URLs
 ## 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
+- http://localhost:8080/json
+- http://localhost:8080/db
+- http://localhost:8080/queries/:n[1-500]
+- http://localhost:8080/fortunes
+- http://localhost:8080/updates/:n[1-500]
+- http://localhost:8080/plaintext

+ 50 - 4
frameworks/Go/echo/benchmark_config.json

@@ -11,16 +11,62 @@
       "plaintext_url": "/plaintext",
       "plaintext_url": "/plaintext",
       "port": 8080,
       "port": 8080,
       "approach": "Realistic",
       "approach": "Realistic",
-      "classification": "Platform",
-      "database": "MySQL",
-      "framework": "ECHO",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "echo",
       "language": "Go",
       "language": "Go",
       "orm": "Raw",
       "orm": "Raw",
       "platform": "Go",
       "platform": "Go",
       "webserver": "None",
       "webserver": "None",
       "os": "Linux",
       "os": "Linux",
       "database_os": "Linux",
       "database_os": "Linux",
-      "display_name": "ECHO",
+      "display_name": "echo",
+      "notes": "",
+      "versus": "go"
+    },
+    "std": {
+      "setup_file": "setup_std",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries/",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates/",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "echo",
+      "language": "Go",
+      "orm": "Raw",
+      "platform": "Go",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "echo-std",
+      "notes": "",
+      "versus": "go"
+    },
+    "prefork": {
+      "setup_file": "setup_prefork",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries/",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates/",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Micro",
+      "database": "Postgres",
+      "framework": "echo",
+      "language": "Go",
+      "orm": "Raw",
+      "platform": "Go",
+      "webserver": "None",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "echo-prefork",
       "notes": "",
       "notes": "",
       "versus": "go"
       "versus": "go"
     }
     }

+ 0 - 20
frameworks/Go/echo/fortune.html

@@ -1,20 +0,0 @@
-<!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>

+ 0 - 0
frameworks/Go/echo/layout.html


+ 0 - 204
frameworks/Go/echo/server.go

@@ -1,204 +0,0 @@
-package main
-
-import (
-	"database/sql"
-	"fmt"
-	"html/template"
-	"io"
-	"log"
-	"math/rand"
-	"net/http"
-	"sort"
-	"strconv"
-
-	_ "github.com/go-sql-driver/mysql"
-	"github.com/labstack/echo"
-)
-
-const (
-	// Database
-	connectionString   = "benchmarkdbuser:benchmarkdbpass@tcp(localhost:3306)/hello_world"
-	worldRowCount      = 10000
-	macIdleConnection  = 30
-	maxConnectionCount = 256
-
-	helloWorldString = "Hello, World!"
-	worldSelect      = "SELECT id, randomNumber FROM World WHERE id = ?"
-	worldUpdate      = "UPDATE World SET randomNumber = ? WHERE id = ?"
-	fortuneSelect    = "SELECT id, message FROM Fortune;"
-)
-
-var (
-	// Database
-	worldStatement   *sql.Stmt
-	fortuneStatement *sql.Stmt
-	updateStatement  *sql.Stmt
-)
-
-type Template struct {
-	templates *template.Template
-}
-
-type MessageStruct struct {
-	Message string `json:"message"`
-}
-
-type World struct {
-	Id           uint16 `json:"id"`
-	RandomNumber uint16 `json:"randomNumber"`
-}
-
-func randomRow() *sql.Row {
-	return worldStatement.QueryRow(rand.Intn(worldRowCount) + 1)
-}
-
-type Fortune struct {
-	Id      uint16 `json:"id"`
-	Message string `json:"message"`
-}
-
-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 }
-
-// Render HTML
-func (t *Template) Render(w io.Writer, name string, data interface{}) error {
-	return t.templates.ExecuteTemplate(w, name, data)
-}
-
-// queries parameter between 1 and 500.
-func sanitizeQueryParam(param string) int {
-	queries, err := strconv.Atoi(param)
-	if err != nil || queries < 1 {
-		return 1
-	}
-	if queries > 500 {
-		return 500
-	}
-	return queries
-}
-
-func json(c *echo.Context) error {
-	return c.JSON(http.StatusOK, MessageStruct{"Hello, World!"})
-}
-
-func plaintext(c *echo.Context) error {
-	return c.String(http.StatusOK, "Hello, World!")
-}
-
-func fortunes(c *echo.Context) error {
-	rows, err := fortuneStatement.Query()
-	if err != nil {
-		log.Fatalf("Error preparing statement: %v", err)
-	}
-
-	fortunes := make(Fortunes, 0, 16)
-
-	for rows.Next() {
-		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)
-	}
-	fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
-
-	sort.Sort(ByMessage{fortunes})
-	return c.Render(http.StatusOK, "fortune.html", fortunes)
-}
-
-func singleQuery(c *echo.Context) error {
-	world := World{}
-	if err := randomRow().Scan(&world.Id, &world.RandomNumber); err != nil {
-		log.Fatalf("Error scanning world row: %s", err.Error())
-	}
-	return c.JSON(http.StatusOK, world)
-}
-
-func multipleQueries(c *echo.Context) error {
-	// Get Param
-	queries := sanitizeQueryParam(c.Param("queries"))
-	worlds := make([]World, queries)
-	for i := 0; i < queries; i++ {
-		if err := randomRow().Scan(&worlds[i].Id, &worlds[i].RandomNumber); err != nil {
-			log.Fatalf("Error scanning world row: %s", err.Error())
-		}
-	}
-	return c.JSON(http.StatusOK, worlds)
-}
-
-func updates(c *echo.Context) error {
-	// Get Param
-	queries := sanitizeQueryParam(c.Param("queries"))
-	worlds := make([]World, queries)
-
-	for i := 0; i < queries; i++ {
-		if err := randomRow().Scan(&worlds[i].Id, &worlds[i].RandomNumber); err != nil {
-			log.Fatalf("Error scanning world row: %s", err.Error())
-		}
-		worlds[i].RandomNumber = uint16(rand.Intn(worldRowCount) + 1)
-		if _, err := updateStatement.Exec(worlds[i].RandomNumber, worlds[i].Id); err != nil {
-			log.Fatalf("Error updating world row: %s", err.Error())
-		}
-	}
-	return c.JSON(http.StatusOK, worlds)
-}
-
-func main() {
-	e := echo.New()
-
-	// Set Templates
-	tmpl := &Template{
-		templates: template.Must(template.ParseFiles("fortune.html", "layout.html")),
-	}
-	e.SetRenderer(tmpl)
-
-	// Middleware
-	e.Use(ServerHeader())
-
-	// Routes
-	e.Get("/json", json)
-	e.Get("/db", singleQuery)
-	e.Get("/queries/:queries", multipleQueries)
-	e.Get("/fortunes", fortunes)
-	e.Get("/updates/:queries", updates)
-	e.Get("/plaintext", plaintext)
-
-	// Start server
-	e.Run(":8080")
-}
-
-func ServerHeader() echo.MiddlewareFunc {
-	return func(h echo.HandlerFunc) echo.HandlerFunc {
-		return func(c *echo.Context) error {
-			c.Response().Header().Add("Server", "ECHO")
-			return h(c)
-		}
-	}
-}
-
-func init() {
-	db, err := sql.Open("mysql", fmt.Sprintf(connectionString))
-	if err != nil {
-		log.Fatalf("Error opening database: %v", err)
-	}
-	db.SetMaxIdleConns(maxConnectionCount)
-
-	worldStatement, err = db.Prepare(worldSelect)
-	if err != nil {
-		log.Fatal(err)
-	}
-	fortuneStatement, err = db.Prepare(fortuneSelect)
-	if err != nil {
-		log.Fatal(err)
-	}
-	updateStatement, err = db.Prepare(worldUpdate)
-	if err != nil {
-		log.Fatal(err)
-	}
-}

+ 4 - 5
frameworks/Go/echo/setup.sh

@@ -1,10 +1,9 @@
 #!/bin/bash
 #!/bin/bash
 
 
-sed -i 's|tcp(.*:3306)|tcp('"${DBHOST}"':3306)|g' server.go
-
 fw_depends go
 fw_depends go
 
 
-go get github.com/go-sql-driver/mysql
-go get github.com/labstack/echo
+go get github.com/labstack/echo/...
+go get github.com/lib/pq
+go install standard fasthttp
 
 
-go run server.go &
+fasthttp &

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

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+fw_depends go
+
+go get github.com/labstack/echo/...
+go get github.com/lib/pq
+go install standard fasthttp
+
+fasthttp -prefork &

+ 9 - 0
frameworks/Go/echo/setup_std.sh

@@ -0,0 +1,9 @@
+#!/bin/bash
+
+fw_depends go
+
+go get github.com/labstack/echo/...
+go get github.com/lib/pq
+go install standard fasthttp
+
+standard &

+ 3 - 0
frameworks/Go/echo/source_code

@@ -0,0 +1,3 @@
+./echo/src/common/common.go
+./echo/src/standard/main.go
+./echo/src/fasthttp/main.go

+ 293 - 0
frameworks/Go/echo/src/common/common.go

@@ -0,0 +1,293 @@
+package common
+
+import (
+	"database/sql"
+	"encoding/json"
+	"fmt"
+	"html/template"
+	"io"
+	"log"
+	"math/rand"
+	"net/http"
+	"os"
+	"sort"
+	"strconv"
+
+	"github.com/labstack/echo"
+	_ "github.com/lib/pq"
+)
+
+type (
+	handler struct{}
+
+	StdTemplate struct {
+		templates *template.Template
+	}
+
+	Message struct {
+		Message string `json:"message"`
+	}
+
+	World struct {
+		ID           uint16 `json:"id"`
+		RandomNumber uint16 `json:"randomNumber"`
+	}
+
+	Fortune struct {
+		ID      uint16 `json:"id"`
+		Message string `json:"message"`
+	}
+
+	Fortunes []*Fortune
+
+	FortunesByMessage struct {
+		Fortunes
+	}
+)
+
+const (
+	// Template
+	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 (
+	// Database
+	db                *sql.DB
+	worldSelectStmt   *sql.Stmt
+	worldUpdateStmt   *sql.Stmt
+	fortuneSelectStmt *sql.Stmt
+
+	// Template
+	Template = &StdTemplate{
+		templates: template.Must(template.New("fortune").Parse(fortuneHTML)),
+	}
+
+	helloWorld = []byte("Hello, World!")
+)
+
+func (t *StdTemplate) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
+	return t.templates.ExecuteTemplate(w, name, data)
+}
+
+func (f Fortunes) Len() int {
+	return len(f)
+}
+
+func (f Fortunes) Swap(i, j int) {
+	f[i], f[j] = f[j], f[i]
+}
+
+func (f FortunesByMessage) Less(i, j int) bool {
+	return f.Fortunes[i].Message < f.Fortunes[j].Message
+}
+
+// Test 1: JSON serialization
+func (h *handler) json() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		c.Response().Header().Set("Server", "Echo")
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+		return json.NewEncoder(c.Response()).Encode(Message{"Hello, World!"})
+	}
+}
+
+// Test 2: Single database query
+func (h *handler) db() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		world := new(World)
+		if err := fetchRandomWorld(world); err != nil {
+			return err
+		}
+
+		c.Response().Header().Set("Server", "Echo")
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+		return json.NewEncoder(c.Response()).Encode(world)
+	}
+}
+
+// Test 3: Multiple database queries
+func (h *handler) queries() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		n := getQueryCount(c.P(0))
+		worlds := make([]World, n)
+		for i := 0; i < n; i++ {
+			if err := fetchRandomWorld(&worlds[i]); err != nil {
+				return err
+			}
+		}
+
+		c.Response().Header().Set("Server", "Echo")
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+		return json.NewEncoder(c.Response()).Encode(worlds)
+	}
+}
+
+// Test 4: Fortunes
+func (h *handler) fortunes() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		rows, err := fortuneSelectStmt.Query()
+		if err != nil {
+			return fmt.Errorf("Error preparing statement: %v", err)
+		}
+		defer rows.Close()
+
+		fortunes, err := fetchFortunes(rows)
+		if err != nil {
+			return err
+		}
+		fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
+		sort.Sort(FortunesByMessage{fortunes})
+
+		c.Response().Header().Set("Server", "Echo")
+		return c.Render(http.StatusOK, "fortune", fortunes)
+	}
+}
+
+// Test 5: Database updates
+func (h *handler) updates() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		n := getQueryCount(c.P(0))
+		worlds := make([]World, n)
+		for i := 0; i < n; i++ {
+			// Fetch and modify
+			w := &worlds[i]
+			if err := fetchRandomWorld(&worlds[i]); err != nil {
+				return err
+			}
+			w.RandomNumber = uint16(randomWorldNum())
+
+			// Update
+			if _, err := worldUpdateStmt.Exec(w.RandomNumber, w.ID); err != nil {
+				return fmt.Errorf("Error updating world row: %v", err)
+			}
+		}
+
+		// sorting is required for insert deadlock prevention.
+		// sort.Sort(WorldsByID(worlds))
+		// Update
+		// tx, err := db.Begin()
+		// if err != nil {
+		// 	return fmt.Errorf("Error starting transaction: %s", err)
+		// }
+		// for i := 0; i < n; i++ {
+		// 	w := &worlds[i]
+		// 	if _, err = tx.Stmt(worldUpdateStmt).Exec(w.RandomNumber, w.ID); err != nil {
+		// 		return fmt.Errorf("Error updating world row %d: %s", i, err)
+		// 	}
+		// }
+		// if err = tx.Commit(); err != nil {
+		// 	return fmt.Errorf("Error when commiting world rows: %s", err)
+		// }
+
+		c.Response().Header().Set("Server", "Echo")
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
+		return json.NewEncoder(c.Response()).Encode(worlds)
+	}
+}
+
+// Test 6: Plaintext
+func (h *handler) plaintext() echo.HandlerFunc {
+	return func(c echo.Context) error {
+		c.Response().Header().Set("Server", "Echo")
+		c.Response().Header().Set(echo.HeaderContentType, echo.MIMETextPlain)
+		_, err := c.Response().Write(helloWorld)
+		return err
+	}
+}
+
+func fetchRandomWorld(w *World) error {
+	n := randomWorldNum()
+	return worldSelectStmt.QueryRow(n).Scan(&w.ID, &w.RandomNumber)
+}
+
+func randomWorldNum() int {
+	return rand.Intn(worldRowCount) + 1
+}
+
+func getQueryCount(q string) int {
+	n, err := strconv.Atoi(q)
+	if err != nil || n < 1 {
+		return 1
+	}
+	if n > 500 {
+		return 500
+	}
+	return n
+}
+
+func fetchFortunes(rows *sql.Rows) (Fortunes, error) {
+	fortunes := make(Fortunes, 0, 16)
+	for rows.Next() { // Fetch rows
+		f := new(Fortune)
+		if err := rows.Scan(&f.ID, &f.Message); err != nil {
+			return nil, fmt.Errorf("Error scanning fortune row: %s", err.Error())
+		}
+		fortunes = append(fortunes, f)
+	}
+	return fortunes, nil
+}
+
+func InitRoutes(e *echo.Echo) {
+	h := new(handler)
+	e.Get("/json", h.json())
+	e.Get("/db", h.db())
+	e.Get("/queries/*", h.queries())
+	e.Get("/fortunes", h.fortunes())
+	e.Get("/updates/*", h.updates())
+	e.Get("/plaintext", h.plaintext())
+}
+
+func InitPostgres() {
+	host := os.Getenv("DBHOST")
+	if host == "" {
+		host = "localhost"
+	}
+
+	var err error
+	db, err = sql.Open("postgres", fmt.Sprintf(connectionString, host))
+	if err != nil {
+		log.Fatalf("Error opening database: %v", err)
+	}
+	db.SetMaxIdleConns(maxConnections)
+	db.SetMaxOpenConns(maxConnections)
+
+	worldSelectStmt, err = db.Prepare(worldSelect)
+	if err != nil {
+		log.Fatal(err)
+	}
+	worldUpdateStmt, err = db.Prepare(worldUpdate)
+	if err != nil {
+		log.Fatal(err)
+	}
+	fortuneSelectStmt, err = db.Prepare(fortuneSelect)
+	if err != nil {
+		log.Fatal(err)
+	}
+}

+ 69 - 0
frameworks/Go/echo/src/fasthttp/main.go

@@ -0,0 +1,69 @@
+package main
+
+import (
+	"common"
+	"flag"
+	"log"
+	"net"
+	"os"
+	"os/exec"
+	"runtime"
+
+	"github.com/labstack/echo"
+	"github.com/labstack/echo/engine"
+	"github.com/labstack/echo/engine/fasthttp"
+	"github.com/valyala/fasthttp/reuseport"
+)
+
+var (
+	prefork = flag.Bool("prefork", false, "use prefork")
+	child   = flag.Bool("child", false, "is child proc")
+)
+
+func getListener() net.Listener {
+	if !*prefork {
+		runtime.GOMAXPROCS(runtime.NumCPU())
+		ln, err := net.Listen("tcp4", ":8080")
+		if err != nil {
+			log.Fatal(err)
+		}
+		return ln
+	}
+
+	if !*child {
+		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
+			if err := children[i].Start(); err != nil {
+				log.Fatal(err)
+			}
+		}
+		for _, ch := range children {
+			if err := ch.Wait(); err != nil {
+				log.Print(err)
+			}
+		}
+		os.Exit(0)
+		panic("unreachable")
+	}
+
+	runtime.GOMAXPROCS(1)
+	ln, err := reuseport.Listen("tcp4", ":8080")
+	if err != nil {
+		log.Fatal(err)
+	}
+	return ln
+}
+
+func main() {
+	flag.Parse()
+	e := echo.New()
+	e.SetRenderer(common.Template)
+	common.InitRoutes(e)
+	common.InitPostgres()
+	e.Run(fasthttp.WithConfig(engine.Config{
+		Listener: getListener(),
+	}))
+}

+ 16 - 0
frameworks/Go/echo/src/standard/main.go

@@ -0,0 +1,16 @@
+package main
+
+import (
+	"common"
+
+	"github.com/labstack/echo"
+	"github.com/labstack/echo/engine/standard"
+)
+
+func main() {
+	e := echo.New()
+	e.SetRenderer(common.Template)
+	common.InitRoutes(e)
+	common.InitPostgres()
+	e.Run(standard.New(":8080"))
+}