hello_mysql.go 9.3 KB

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