|
@@ -1,291 +0,0 @@
|
|
|
-package main
|
|
|
-
|
|
|
-import (
|
|
|
- "database/sql"
|
|
|
- "html/template"
|
|
|
- "io"
|
|
|
- "log"
|
|
|
- "math/rand"
|
|
|
- "net/http"
|
|
|
- "sort"
|
|
|
- "strconv"
|
|
|
- "sync"
|
|
|
- "time"
|
|
|
-
|
|
|
- "github.com/fitstar/falcore"
|
|
|
- "github.com/fitstar/falcore/responder"
|
|
|
- _ "github.com/go-sql-driver/mysql"
|
|
|
-)
|
|
|
-
|
|
|
-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 (
|
|
|
- // Database
|
|
|
- connectionString = "benchmarkdbuser:benchmarkdbpass@tcp(tfb-database:3306)/hello_world?collation=utf8mb4_bin"
|
|
|
- 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
|
|
|
-
|
|
|
- helloWorldString = "Hello, World!"
|
|
|
-)
|
|
|
-
|
|
|
-var (
|
|
|
- // Templates
|
|
|
- tmpl = template.Must(template.ParseFiles("templates/layout.html", "templates/fortune.html"))
|
|
|
-
|
|
|
- // Database
|
|
|
- worldStatement *sql.Stmt
|
|
|
- fortuneStatement *sql.Stmt
|
|
|
- updateStatement *sql.Stmt
|
|
|
-
|
|
|
- helloWorldBytes = []byte(helloWorldString)
|
|
|
-)
|
|
|
-
|
|
|
-type stats struct {
|
|
|
- Count time.Duration
|
|
|
- Sum time.Duration
|
|
|
-}
|
|
|
-
|
|
|
-// for profiling
|
|
|
-var reqCount = 0
|
|
|
-var statMap = make(map[string]*stats)
|
|
|
-var compCallbackMutex = &sync.Mutex{}
|
|
|
-
|
|
|
-func CompletionCallback(req *falcore.Request, res *http.Response) {
|
|
|
- l := req.PipelineStageStats
|
|
|
- compCallbackMutex.Lock()
|
|
|
- incrStat(statMap, "overhead", req.Overhead)
|
|
|
- incrStat(statMap, "tots", req.EndTime.Sub(req.StartTime))
|
|
|
- for e := l.Front(); e != nil; e = e.Next() {
|
|
|
- pss, _ := e.Value.(*falcore.PipelineStageStat)
|
|
|
- dur := pss.EndTime.Sub(pss.StartTime)
|
|
|
- incrStat(statMap, pss.Name, dur)
|
|
|
- }
|
|
|
- reqCount++
|
|
|
- if reqCount%10000 == 0 {
|
|
|
- for k, v := range statMap {
|
|
|
- log.Printf("%v: %v\n", k, v.Sum/v.Count)
|
|
|
- }
|
|
|
- log.Println("")
|
|
|
- }
|
|
|
- compCallbackMutex.Unlock()
|
|
|
-}
|
|
|
-
|
|
|
-func incrStat(statMap map[string]*stats, name string, dur time.Duration) {
|
|
|
- if s, ok := statMap[name]; ok {
|
|
|
- s.Count++
|
|
|
- s.Sum += dur
|
|
|
- } else {
|
|
|
- statMap[name] = &stats{1, dur}
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-func main() {
|
|
|
- db, err := sql.Open("mysql", 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)
|
|
|
- }
|
|
|
- pipeline := falcore.NewPipeline()
|
|
|
-
|
|
|
- pipeline.Upstream.PushBack(dbFilter)
|
|
|
- pipeline.Upstream.PushBack(queriesFilter)
|
|
|
- pipeline.Upstream.PushBack(jsonFilter)
|
|
|
- pipeline.Upstream.PushBack(fortuneFilter)
|
|
|
- pipeline.Upstream.PushBack(updateFilter)
|
|
|
- pipeline.Upstream.PushBack(plaintextFilter)
|
|
|
-
|
|
|
- pipeline.Downstream.PushBack(requiredHeaders)
|
|
|
-
|
|
|
- /*
|
|
|
- http.HandleFunc("/db", dbHandler)
|
|
|
- http.HandleFunc("/queries", queriesHandler)
|
|
|
- http.HandleFunc("/json", jsonHandler)
|
|
|
- http.HandleFunc("/fortune", fortuneHandler)
|
|
|
- http.HandleFunc("/update", updateHandler)
|
|
|
- http.HandleFunc("/plaintext", plaintextHandler)
|
|
|
- */
|
|
|
- server := falcore.NewServer(8080, pipeline)
|
|
|
- // uncomment for printing internal peromance stats
|
|
|
- //server.CompletionCallback = CompletionCallback
|
|
|
-
|
|
|
- if err := server.ListenAndServe(); err != nil {
|
|
|
- log.Println("Could not start server:", err)
|
|
|
- }
|
|
|
- //if err := http.ListenAndServe(":8080", server); err != nil {
|
|
|
- // log.Println("Could not start server:", err)
|
|
|
- //}
|
|
|
-}
|
|
|
-
|
|
|
-var requiredHeaders = falcore.NewResponseFilter(func(req *falcore.Request, res *http.Response) {
|
|
|
- res.Header.Set("Server", "falcore")
|
|
|
- res.Header.Set("Date", time.Now().Format(time.RFC1123))
|
|
|
-})
|
|
|
-
|
|
|
-// Test 1: JSON serialization
|
|
|
-var jsonFilter = falcore.NewRequestFilter(func(req *falcore.Request) *http.Response {
|
|
|
- if req.HttpRequest.URL.Path == "/json" {
|
|
|
- resp, _ := responder.JSONResponse(req.HttpRequest, 200, nil, &Message{helloWorldString})
|
|
|
- return resp
|
|
|
- }
|
|
|
- return nil
|
|
|
-})
|
|
|
-
|
|
|
-// Test 2: Single database query
|
|
|
-var dbFilter = falcore.NewRequestFilter(func(req *falcore.Request) *http.Response {
|
|
|
- if req.HttpRequest.URL.Path == "/db" {
|
|
|
- var world World
|
|
|
- err := worldStatement.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world.Id, &world.RandomNumber)
|
|
|
- if err != nil {
|
|
|
- log.Fatalf("Error scanning world row: %s", err.Error())
|
|
|
- }
|
|
|
-
|
|
|
- resp, _ := responder.JSONResponse(req.HttpRequest, 200, nil, &world)
|
|
|
- return resp
|
|
|
- }
|
|
|
- return nil
|
|
|
-})
|
|
|
-
|
|
|
-// Test 3: Multiple database queries
|
|
|
-var queriesFilter = falcore.NewRequestFilter(func(req *falcore.Request) *http.Response {
|
|
|
-
|
|
|
- if req.HttpRequest.URL.Path == "/queries" {
|
|
|
-
|
|
|
- n := 1
|
|
|
-
|
|
|
- if nStr := req.HttpRequest.URL.Query().Get("queries"); len(nStr) > 0 {
|
|
|
- n, _ = strconv.Atoi(nStr) // rvalue is 0 if nStr is non-number.
|
|
|
- }
|
|
|
-
|
|
|
- // In the case of nStr is non-number string, n will be 0. So need to change n to 1.
|
|
|
- if n == 0 {
|
|
|
- n = 1
|
|
|
- }
|
|
|
-
|
|
|
- // In the case of nStr is number and its value is higher than 500, change n to 500
|
|
|
- if n > 500 {
|
|
|
- n = 500
|
|
|
- }
|
|
|
-
|
|
|
- world := make([]World, n)
|
|
|
- for i := 0; i < n; i++ {
|
|
|
- err := worldStatement.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber)
|
|
|
- if err != nil {
|
|
|
- log.Fatalf("Error scanning world row: %s", err.Error())
|
|
|
- }
|
|
|
- }
|
|
|
- resp, _ := responder.JSONResponse(req.HttpRequest, 200, nil, &world)
|
|
|
- return resp
|
|
|
- }
|
|
|
- return nil
|
|
|
-})
|
|
|
-
|
|
|
-// Test 4: Fortunes
|
|
|
-var fortuneFilter = falcore.NewRequestFilter(func(req *falcore.Request) *http.Response {
|
|
|
- if req.HttpRequest.URL.Path == "/fortune" {
|
|
|
- rows, err := fortuneStatement.Query()
|
|
|
- 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)
|
|
|
- }
|
|
|
- fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
|
|
|
-
|
|
|
- sort.Sort(ByMessage{fortunes})
|
|
|
- pipeReader, pipeWriter := io.Pipe()
|
|
|
- // TODO maybe figure out err handling
|
|
|
- go func() {
|
|
|
- tmpl.Execute(pipeWriter, fortunes)
|
|
|
- pipeWriter.Close()
|
|
|
- }()
|
|
|
-
|
|
|
- textHtml := http.Header{"Content-Type": []string{"text/html;charset=utf-8"}}
|
|
|
- return falcore.SimpleResponse(req.HttpRequest, 200, textHtml, -1, pipeReader)
|
|
|
- }
|
|
|
- return nil
|
|
|
-})
|
|
|
-
|
|
|
-// Test 5: Database updates
|
|
|
-var updateFilter = falcore.NewRequestFilter(func(req *falcore.Request) *http.Response {
|
|
|
- if req.HttpRequest.URL.Path == "/update" {
|
|
|
- n := 1
|
|
|
- if nStr := req.HttpRequest.URL.Query().Get("queries"); len(nStr) > 0 {
|
|
|
- n, _ = strconv.Atoi(nStr)
|
|
|
- }
|
|
|
-
|
|
|
- if n <= 1 {
|
|
|
- var world World
|
|
|
- worldStatement.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world.Id, &world.RandomNumber)
|
|
|
- world.RandomNumber = uint16(rand.Intn(worldRowCount) + 1)
|
|
|
- updateStatement.Exec(world.RandomNumber, world.Id)
|
|
|
- resp, _ := responder.JSONResponse(req.HttpRequest, 200, nil, &world)
|
|
|
- return resp
|
|
|
- } else {
|
|
|
- world := make([]World, n)
|
|
|
- for i := 0; i < n; i++ {
|
|
|
- if err := worldStatement.QueryRow(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 := updateStatement.Exec(world[i].RandomNumber, world[i].Id); err != nil {
|
|
|
- log.Fatalf("Error updating world row: %s", err.Error())
|
|
|
- }
|
|
|
- }
|
|
|
- resp, _ := responder.JSONResponse(req.HttpRequest, 200, nil, world)
|
|
|
- return resp
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
- return nil
|
|
|
-})
|
|
|
-
|
|
|
-// Test 6: Plaintext
|
|
|
-var plaintextFilter = falcore.NewRequestFilter(func(req *falcore.Request) *http.Response {
|
|
|
- if req.HttpRequest.URL.Path == "/plaintext" {
|
|
|
- textPlain := http.Header{"Content-Type": []string{"text/plain"}}
|
|
|
- return falcore.ByteResponse(req.HttpRequest, 200, textPlain, helloWorldBytes)
|
|
|
- }
|
|
|
- return nil
|
|
|
-})
|
|
|
-
|
|
|
-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 }
|