123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- package main
- import (
- "database/sql"
- "encoding/json"
- "flag"
- "fmt"
- "html/template"
- "log"
- "math/rand"
- "net"
- "net/http"
- "os"
- "os/exec"
- "runtime"
- "sort"
- "strconv"
- _ "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 (
- // Content
- helloWorldString = "Hello, World!"
- 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>`
- // Databases
- //
- // `interpolateParams=true` enables client side parameter interpolation.
- // It reduces round trips without prepared statement.
- //
- // We can see difference between prepared statement and interpolation by comparing go-std-mysql and go-std-mysql-interpolate
- connectionString = "benchmarkdbuser:benchmarkdbpass@tcp(%s: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
- maxConnections = 256
- )
- var (
- helloWorldBytes = []byte(helloWorldString)
- // Templates
- tmpl = template.Must(template.New("fortune.html").Parse(fortuneHTML))
- // Database
- db *sql.DB
- worldSelectPrepared *sql.Stmt
- worldUpdatePrepared *sql.Stmt
- fortuneSelectPrepared *sql.Stmt
- )
- var prefork = flag.Bool("prefork", false, "use prefork")
- var child = flag.Bool("child", false, "is child proc")
- func initDB() {
- var err error
- var dbhost = os.Getenv("DBHOST")
- if dbhost == "" {
- dbhost = "localhost"
- }
- db, err = sql.Open("mysql", 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() {
- var listener net.Listener
- flag.Parse()
- if !*prefork {
- runtime.GOMAXPROCS(runtime.NumCPU())
- } else {
- listener = doPrefork()
- }
- initDB()
- http.HandleFunc("/json", jsonHandler)
- http.HandleFunc("/db", dbHandler)
- http.HandleFunc("/dbInterpolate", dbInterpolateHandler)
- http.HandleFunc("/queries", queriesHandler)
- http.HandleFunc("/queriesInterpolate", queriesInterpolateHandler)
- http.HandleFunc("/fortune", fortuneHandler)
- http.HandleFunc("/fortuneInterpolate", fortuneInterpolateHandler)
- http.HandleFunc("/update", updateHandler)
- http.HandleFunc("/updateInterpolate", updateInterpolateHandler)
- http.HandleFunc("/plaintext", plaintextHandler)
- if !*prefork {
- http.ListenAndServe(":8080", nil)
- } else {
- http.Serve(listener, nil)
- }
- }
- 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()/2)
- 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(2)
- }
- return listener
- }
- 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 1: JSON serialization
- func jsonHandler(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Server", "Go")
- w.Header().Set("Content-Type", "application/json")
- json.NewEncoder(w).Encode(&Message{helloWorldString})
- }
- // 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)
- }
- func dbInterpolateHandler(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("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)
- }
- func queriesInterpolateHandler(w http.ResponseWriter, r *http.Request) {
- n := getQueriesParam(r)
- 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: %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)
- }
- 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 fortuneInterpolateHandler(w http.ResponseWriter, r *http.Request) {
- rows, err := db.Query(fortuneSelect)
- if err != nil {
- log.Fatalf("Error preparing statement: %v", err)
- }
- 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 {
- defer rows.Close()
- 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)
- }
- func updateInterpolateHandler(w http.ResponseWriter, r *http.Request) {
- n := getQueriesParam(r)
- 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: %v", 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: %v", err)
- }
- }
- w.Header().Set("Server", "Go")
- w.Header().Set("Content-Type", "application/json")
- encoder := json.NewEncoder(w)
- encoder.Encode(world)
- }
- // Test 6: Plaintext
- func plaintextHandler(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Server", "Go")
- 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 }
|