Created
August 12, 2013 01:10
-
-
Save jordanorelli/6207633 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"database/sql" | |
"encoding/json" | |
"html/template" | |
"log" | |
"math/rand" | |
"net/http" | |
"runtime" | |
"sort" | |
"strconv" | |
"sync" | |
_ "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"` | |
} | |
// TODO: remove ?charset=utf8 from DSN after the next Go-MySQL-Driver release | |
// https://github.com/go-sql-driver/mysql#unicode-support | |
const ( | |
// Database | |
connectionString = "benchmarkdbuser:benchmarkdbpass@tcp(localhost:3306)/hello_world?charset=utf8" | |
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) | |
db *sql.DB | |
queryChan chan (*queryRequest) | |
) | |
func main() { | |
runtime.GOMAXPROCS(runtime.NumCPU()) | |
var err error | |
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) | |
} | |
queryChan = make(chan *queryRequest) | |
numQueryRunners := runtime.NumCPU() * 2 | |
for i := 0; i < numQueryRunners; i++ { | |
go queryRunner(queryChan) | |
} | |
http.HandleFunc("/db", dbHandler) | |
http.HandleFunc("/queries", queriesHandler) | |
http.HandleFunc("/json", jsonHandler) | |
http.HandleFunc("/fortune", fortuneHandler) | |
http.HandleFunc("/update", updateHandler) | |
http.HandleFunc("/plaintext", plaintextHandler) | |
http.ListenAndServe(":8080", nil) | |
} | |
// Test 1: JSON serialization | |
func jsonHandler(w http.ResponseWriter, r *http.Request) { | |
w.Header().Set("Content-Type", "application/javascript") | |
json.NewEncoder(w).Encode(&Message{helloWorldString}) | |
} | |
// Test 2: Single database query | |
func dbHandler(w http.ResponseWriter, r *http.Request) { | |
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()) | |
} | |
w.Header().Set("Content-Type", "application/json") | |
json.NewEncoder(w).Encode(&world) | |
} | |
type queryRequest struct { | |
n int | |
wg *sync.WaitGroup | |
result World | |
err error | |
} | |
func setupQueryRequest(r *queryRequest, wg *sync.WaitGroup) { | |
r.n = rand.Intn(worldRowCount) + 1 | |
r.wg = wg | |
} | |
func (q *queryRequest) MarshalJSON() ([]byte, error) { | |
return json.Marshal(q.result) | |
} | |
func (r *queryRequest) runQuery(stmt *sql.Stmt) { | |
defer r.wg.Done() | |
r.err = stmt.QueryRow(r.n).Scan(&r.result.Id, &r.result.RandomNumber) | |
} | |
func queryRunner(c chan *queryRequest) { | |
stmt, err := db.Prepare(worldSelect) | |
if err != nil { | |
log.Fatal(err) | |
} | |
defer stmt.Close() | |
for r := range c { | |
r.runQuery(stmt) | |
} | |
} | |
// Test 3: Multiple database queries | |
func queriesHandler(w http.ResponseWriter, r *http.Request) { | |
n := 1 | |
if nStr := r.URL.Query().Get("queries"); len(nStr) > 0 { | |
n, _ = strconv.Atoi(nStr) | |
} | |
if n <= 1 { | |
dbHandler(w, r) | |
return | |
} | |
var wg sync.WaitGroup | |
queries := make([]queryRequest, n) | |
wg.Add(n) | |
for i := 0; i < n; i++ { | |
setupQueryRequest(&queries[i], &wg) | |
queryChan <- &queries[i] | |
} | |
wg.Wait() | |
for i := range queries { | |
if queries[i].err != nil { | |
log.Fatalf("Error scanning world row: %d %s", queries[i].n, queries[i].err.Error()) | |
} | |
} | |
w.Header().Set("Content-Type", "application/json") | |
json.NewEncoder(w).Encode(queries) | |
} | |
// Test 4: Fortunes | |
func fortuneHandler(w http.ResponseWriter, r *http.Request) { | |
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}) | |
w.Header().Set("Content-Type", "text/html") | |
if err := tmpl.Execute(w, fortunes); err != nil { | |
http.Error(w, err.Error(), http.StatusInternalServerError) | |
} | |
} | |
// Test 5: Database updates | |
func updateHandler(w http.ResponseWriter, r *http.Request) { | |
n := 1 | |
if nStr := r.URL.Query().Get("queries"); len(nStr) > 0 { | |
n, _ = strconv.Atoi(nStr) | |
} | |
w.Header().Set("Content-Type", "application/json") | |
encoder := json.NewEncoder(w) | |
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) | |
encoder.Encode(&world) | |
} 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()) | |
} | |
} | |
encoder.Encode(world) | |
} | |
} | |
// Test 6: Plaintext | |
func plaintextHandler(w http.ResponseWriter, r *http.Request) { | |
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 } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment