hello_mysql.go 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. package main
  2. import (
  3. "database/sql"
  4. "encoding/json"
  5. "flag"
  6. "fmt"
  7. "html/template"
  8. "log"
  9. "math/rand"
  10. "net"
  11. "net/http"
  12. "os"
  13. "os/exec"
  14. "runtime"
  15. "sort"
  16. "strconv"
  17. _ "github.com/go-sql-driver/mysql"
  18. )
  19. type Message struct {
  20. Message string `json:"message"`
  21. }
  22. type World struct {
  23. Id uint16 `json:"id"`
  24. RandomNumber uint16 `json:"randomNumber"`
  25. }
  26. type Fortune struct {
  27. Id uint16 `json:"id"`
  28. Message string `json:"message"`
  29. }
  30. const (
  31. // Content
  32. helloWorldString = "Hello, World!"
  33. fortuneHTML = `<!DOCTYPE html>
  34. <html>
  35. <head>
  36. <title>Fortunes</title>
  37. </head>
  38. <body>
  39. <table>
  40. <tr>
  41. <th>id</th>
  42. <th>message</th>
  43. </tr>
  44. {{range .}}
  45. <tr>
  46. <td>{{.Id}}</td>
  47. <td>{{.Message}}</td>
  48. </tr>
  49. {{end}}
  50. </table>
  51. </body>
  52. </html>`
  53. // Databases
  54. //
  55. // `interpolateParams=true` enables client side parameter interpolation.
  56. // It reduces round trips without prepared statement.
  57. //
  58. // We can see difference between prepared statement and interpolation by comparing go-std-mysql and go-std-mysql-interpolate
  59. connectionString = "benchmarkdbuser:benchmarkdbpass@tcp(%s:3306)/hello_world?interpolateParams=true"
  60. worldSelect = "SELECT id, randomNumber FROM World WHERE id = ?"
  61. worldUpdate = "UPDATE World SET randomNumber = ? WHERE id = ?"
  62. fortuneSelect = "SELECT id, message FROM Fortune"
  63. worldRowCount = 10000
  64. maxConnections = 256
  65. )
  66. var (
  67. helloWorldBytes = []byte(helloWorldString)
  68. // Templates
  69. tmpl = template.Must(template.New("fortune.html").Parse(fortuneHTML))
  70. // Database
  71. db *sql.DB
  72. worldSelectPrepared *sql.Stmt
  73. worldUpdatePrepared *sql.Stmt
  74. fortuneSelectPrepared *sql.Stmt
  75. )
  76. var prefork = flag.Bool("prefork", false, "use prefork")
  77. var child = flag.Bool("child", false, "is child proc")
  78. func initDB() {
  79. var err error
  80. var dbhost = os.Getenv("DBHOST")
  81. if dbhost == "" {
  82. dbhost = "localhost"
  83. }
  84. db, err = sql.Open("mysql", fmt.Sprintf(connectionString, dbhost))
  85. if err != nil {
  86. log.Fatalf("Error opening database: %v", err)
  87. }
  88. db.SetMaxIdleConns(maxConnections)
  89. db.SetMaxOpenConns(maxConnections)
  90. worldSelectPrepared, err = db.Prepare(worldSelect)
  91. if err != nil {
  92. log.Fatal(err)
  93. }
  94. worldUpdatePrepared, err = db.Prepare(worldUpdate)
  95. if err != nil {
  96. log.Fatal(err)
  97. }
  98. fortuneSelectPrepared, err = db.Prepare(fortuneSelect)
  99. if err != nil {
  100. log.Fatal(err)
  101. }
  102. }
  103. func main() {
  104. var listener net.Listener
  105. flag.Parse()
  106. if !*prefork {
  107. runtime.GOMAXPROCS(runtime.NumCPU())
  108. } else {
  109. listener = doPrefork()
  110. }
  111. initDB()
  112. http.HandleFunc("/json", jsonHandler)
  113. http.HandleFunc("/db", dbHandler)
  114. http.HandleFunc("/dbInterpolate", dbInterpolateHandler)
  115. http.HandleFunc("/queries", queriesHandler)
  116. http.HandleFunc("/queriesInterpolate", queriesInterpolateHandler)
  117. http.HandleFunc("/fortune", fortuneHandler)
  118. http.HandleFunc("/fortuneInterpolate", fortuneInterpolateHandler)
  119. http.HandleFunc("/update", updateHandler)
  120. http.HandleFunc("/updateInterpolate", updateInterpolateHandler)
  121. http.HandleFunc("/plaintext", plaintextHandler)
  122. if !*prefork {
  123. http.ListenAndServe(":8080", nil)
  124. } else {
  125. http.Serve(listener, nil)
  126. }
  127. }
  128. func doPrefork() (listener net.Listener) {
  129. var err error
  130. var fl *os.File
  131. var tcplistener *net.TCPListener
  132. if !*child {
  133. var addr *net.TCPAddr
  134. addr, err = net.ResolveTCPAddr("tcp", ":8080")
  135. if err != nil {
  136. log.Fatal(err)
  137. }
  138. tcplistener, err = net.ListenTCP("tcp", addr)
  139. if err != nil {
  140. log.Fatal(err)
  141. }
  142. fl, err = tcplistener.File()
  143. if err != nil {
  144. log.Fatal(err)
  145. }
  146. children := make([]*exec.Cmd, runtime.NumCPU()/2)
  147. for i := range children {
  148. children[i] = exec.Command(os.Args[0], "-prefork", "-child")
  149. children[i].Stdout = os.Stdout
  150. children[i].Stderr = os.Stderr
  151. children[i].ExtraFiles = []*os.File{fl}
  152. err = children[i].Start()
  153. if err != nil {
  154. log.Fatal(err)
  155. }
  156. }
  157. for _, ch := range children {
  158. var err error = ch.Wait()
  159. if err != nil {
  160. log.Print(err)
  161. }
  162. }
  163. os.Exit(0)
  164. } else {
  165. fl = os.NewFile(3, "")
  166. listener, err = net.FileListener(fl)
  167. if err != nil {
  168. log.Fatal(err)
  169. }
  170. runtime.GOMAXPROCS(2)
  171. }
  172. return listener
  173. }
  174. func getQueriesParam(r *http.Request) int {
  175. n := 1
  176. if nStr := r.URL.Query().Get("queries"); len(nStr) > 0 {
  177. n, _ = strconv.Atoi(nStr)
  178. }
  179. if n < 1 {
  180. n = 1
  181. } else if n > 500 {
  182. n = 500
  183. }
  184. return n
  185. }
  186. // Test 1: JSON serialization
  187. func jsonHandler(w http.ResponseWriter, r *http.Request) {
  188. w.Header().Set("Server", "Go")
  189. w.Header().Set("Content-Type", "application/json")
  190. json.NewEncoder(w).Encode(&Message{helloWorldString})
  191. }
  192. // Test 2: Single database query
  193. func dbHandler(w http.ResponseWriter, r *http.Request) {
  194. var world World
  195. err := worldSelectPrepared.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world.Id, &world.RandomNumber)
  196. if err != nil {
  197. log.Fatalf("Error scanning world row: %s", err.Error())
  198. }
  199. w.Header().Set("Server", "Go")
  200. w.Header().Set("Content-Type", "application/json")
  201. json.NewEncoder(w).Encode(&world)
  202. }
  203. func dbInterpolateHandler(w http.ResponseWriter, r *http.Request) {
  204. var world World
  205. err := db.QueryRow(worldSelect, rand.Intn(worldRowCount)+1).Scan(&world.Id, &world.RandomNumber)
  206. if err != nil {
  207. log.Fatalf("Error scanning world row: %s", err.Error())
  208. }
  209. w.Header().Set("Server", "Go")
  210. w.Header().Set("Content-Type", "application/json")
  211. json.NewEncoder(w).Encode(&world)
  212. }
  213. // Test 3: Multiple database queries
  214. func queriesHandler(w http.ResponseWriter, r *http.Request) {
  215. n := getQueriesParam(r)
  216. world := make([]World, n)
  217. for i := 0; i < n; i++ {
  218. err := worldSelectPrepared.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber)
  219. if err != nil {
  220. log.Fatalf("Error scanning world row: %v", err)
  221. }
  222. }
  223. w.Header().Set("Server", "Go")
  224. w.Header().Set("Content-Type", "application/json")
  225. json.NewEncoder(w).Encode(world)
  226. }
  227. func queriesInterpolateHandler(w http.ResponseWriter, r *http.Request) {
  228. n := getQueriesParam(r)
  229. world := make([]World, n)
  230. for i := 0; i < n; i++ {
  231. err := db.QueryRow(worldSelect, rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber)
  232. if err != nil {
  233. log.Fatalf("Error scanning world row: %v", err)
  234. }
  235. }
  236. w.Header().Set("Server", "Go")
  237. w.Header().Set("Content-Type", "application/json")
  238. json.NewEncoder(w).Encode(world)
  239. }
  240. // Test 4: Fortunes
  241. func fortuneHandler(w http.ResponseWriter, r *http.Request) {
  242. rows, err := fortuneSelectPrepared.Query()
  243. if err != nil {
  244. log.Fatalf("Error preparing statement: %v", err)
  245. }
  246. fortunes := fetchFortunes(rows)
  247. fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
  248. sort.Sort(ByMessage{fortunes})
  249. w.Header().Set("Server", "Go")
  250. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  251. if err := tmpl.Execute(w, fortunes); err != nil {
  252. http.Error(w, err.Error(), http.StatusInternalServerError)
  253. }
  254. }
  255. func fortuneInterpolateHandler(w http.ResponseWriter, r *http.Request) {
  256. rows, err := db.Query(fortuneSelect)
  257. if err != nil {
  258. log.Fatalf("Error preparing statement: %v", err)
  259. }
  260. fortunes := fetchFortunes(rows)
  261. fortunes = append(fortunes, &Fortune{Message: "Additional fortune added at request time."})
  262. sort.Sort(ByMessage{fortunes})
  263. w.Header().Set("Server", "Go")
  264. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  265. if err := tmpl.Execute(w, fortunes); err != nil {
  266. http.Error(w, err.Error(), http.StatusInternalServerError)
  267. }
  268. }
  269. func fetchFortunes(rows *sql.Rows) Fortunes {
  270. defer rows.Close()
  271. fortunes := make(Fortunes, 0, 16)
  272. for rows.Next() { //Fetch rows
  273. fortune := Fortune{}
  274. if err := rows.Scan(&fortune.Id, &fortune.Message); err != nil {
  275. log.Fatalf("Error scanning fortune row: %s", err.Error())
  276. }
  277. fortunes = append(fortunes, &fortune)
  278. }
  279. return fortunes
  280. }
  281. // Test 5: Database updates
  282. func updateHandler(w http.ResponseWriter, r *http.Request) {
  283. n := getQueriesParam(r)
  284. world := make([]World, n)
  285. for i := 0; i < n; i++ {
  286. if err := worldSelectPrepared.QueryRow(rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber); err != nil {
  287. log.Fatalf("Error scanning world row: %v", err)
  288. }
  289. world[i].RandomNumber = uint16(rand.Intn(worldRowCount) + 1)
  290. if _, err := worldUpdatePrepared.Exec(world[i].RandomNumber, world[i].Id); err != nil {
  291. log.Fatalf("Error updating world row: %v", err)
  292. }
  293. }
  294. w.Header().Set("Server", "Go")
  295. w.Header().Set("Content-Type", "application/json")
  296. encoder := json.NewEncoder(w)
  297. encoder.Encode(world)
  298. }
  299. func updateInterpolateHandler(w http.ResponseWriter, r *http.Request) {
  300. n := getQueriesParam(r)
  301. world := make([]World, n)
  302. for i := 0; i < n; i++ {
  303. if err := db.QueryRow(worldSelect, rand.Intn(worldRowCount)+1).Scan(&world[i].Id, &world[i].RandomNumber); err != nil {
  304. log.Fatalf("Error scanning world row: %v", err)
  305. }
  306. world[i].RandomNumber = uint16(rand.Intn(worldRowCount) + 1)
  307. if _, err := db.Exec(worldUpdate, world[i].RandomNumber, world[i].Id); err != nil {
  308. log.Fatalf("Error updating world row: %v", err)
  309. }
  310. }
  311. w.Header().Set("Server", "Go")
  312. w.Header().Set("Content-Type", "application/json")
  313. encoder := json.NewEncoder(w)
  314. encoder.Encode(world)
  315. }
  316. // Test 6: Plaintext
  317. func plaintextHandler(w http.ResponseWriter, r *http.Request) {
  318. w.Header().Set("Server", "Go")
  319. w.Header().Set("Content-Type", "text/plain")
  320. w.Write(helloWorldBytes)
  321. }
  322. type Fortunes []*Fortune
  323. func (s Fortunes) Len() int { return len(s) }
  324. func (s Fortunes) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  325. type ByMessage struct{ Fortunes }
  326. func (s ByMessage) Less(i, j int) bool { return s.Fortunes[i].Message < s.Fortunes[j].Message }