Skip to content

Instantly share code, notes, and snippets.

@jordanorelli
Created August 12, 2013 01:10
Show Gist options
  • Save jordanorelli/6207633 to your computer and use it in GitHub Desktop.
Save jordanorelli/6207633 to your computer and use it in GitHub Desktop.
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