bench.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. "sort"
  6. "strings"
  7. "sync"
  8. "time"
  9. "github.com/astaxie/bat/httplib"
  10. )
  11. type result struct {
  12. err error
  13. statusCode int
  14. duration time.Duration
  15. contentLength int64
  16. }
  17. func RunBench(b *httplib.BeegoHttpRequest) {
  18. runtime.GOMAXPROCS(runtime.NumCPU())
  19. start := time.Now()
  20. results := make(chan *result, benchN)
  21. var wg sync.WaitGroup
  22. wg.Add(benchN)
  23. jobs := make(chan int, benchN)
  24. for i := 0; i < benchC; i++ {
  25. go func() {
  26. worker(&wg, jobs, results, b)
  27. }()
  28. }
  29. for i := 0; i < benchN; i++ {
  30. jobs <- i
  31. }
  32. close(jobs)
  33. wg.Wait()
  34. printReport(benchN, results, "", time.Now().Sub(start))
  35. close(results)
  36. }
  37. func worker(wg *sync.WaitGroup, ch chan int, results chan *result, b *httplib.BeegoHttpRequest) {
  38. for _ = range ch {
  39. s := time.Now()
  40. code := 0
  41. size := int64(0)
  42. resp, err := b.SendOut()
  43. if err == nil {
  44. size = resp.ContentLength
  45. code = resp.StatusCode
  46. resp.Body.Close()
  47. }
  48. wg.Done()
  49. results <- &result{
  50. statusCode: code,
  51. duration: time.Now().Sub(s),
  52. err: err,
  53. contentLength: size,
  54. }
  55. }
  56. }
  57. const (
  58. barChar = "∎"
  59. )
  60. type report struct {
  61. avgTotal float64
  62. fastest float64
  63. slowest float64
  64. average float64
  65. rps float64
  66. results chan *result
  67. total time.Duration
  68. errorDist map[string]int
  69. statusCodeDist map[int]int
  70. lats []float64
  71. sizeTotal int64
  72. output string
  73. }
  74. func printReport(size int, results chan *result, output string, total time.Duration) {
  75. r := &report{
  76. output: output,
  77. results: results,
  78. total: total,
  79. statusCodeDist: make(map[int]int),
  80. errorDist: make(map[string]int),
  81. }
  82. r.finalize()
  83. }
  84. func (r *report) finalize() {
  85. for {
  86. select {
  87. case res := <-r.results:
  88. if res.err != nil {
  89. r.errorDist[res.err.Error()]++
  90. } else {
  91. r.lats = append(r.lats, res.duration.Seconds())
  92. r.avgTotal += res.duration.Seconds()
  93. r.statusCodeDist[res.statusCode]++
  94. if res.contentLength > 0 {
  95. r.sizeTotal += res.contentLength
  96. }
  97. }
  98. default:
  99. r.rps = float64(len(r.lats)) / r.total.Seconds()
  100. r.average = r.avgTotal / float64(len(r.lats))
  101. r.print()
  102. return
  103. }
  104. }
  105. }
  106. func (r *report) print() {
  107. sort.Float64s(r.lats)
  108. if r.output == "csv" {
  109. r.printCSV()
  110. return
  111. }
  112. if len(r.lats) > 0 {
  113. r.fastest = r.lats[0]
  114. r.slowest = r.lats[len(r.lats)-1]
  115. fmt.Printf("\nSummary:\n")
  116. fmt.Printf(" Total:\t%4.4f secs.\n", r.total.Seconds())
  117. fmt.Printf(" Slowest:\t%4.4f secs.\n", r.slowest)
  118. fmt.Printf(" Fastest:\t%4.4f secs.\n", r.fastest)
  119. fmt.Printf(" Average:\t%4.4f secs.\n", r.average)
  120. fmt.Printf(" Requests/sec:\t%4.4f\n", r.rps)
  121. if r.sizeTotal > 0 {
  122. fmt.Printf(" Total Data Received:\t%d bytes.\n", r.sizeTotal)
  123. fmt.Printf(" Response Size per Request:\t%d bytes.\n", r.sizeTotal/int64(len(r.lats)))
  124. }
  125. r.printStatusCodes()
  126. r.printHistogram()
  127. r.printLatencies()
  128. }
  129. if len(r.errorDist) > 0 {
  130. r.printErrors()
  131. }
  132. }
  133. func (r *report) printCSV() {
  134. for i, val := range r.lats {
  135. fmt.Printf("%v,%4.4f\n", i+1, val)
  136. }
  137. }
  138. // Prints percentile latencies.
  139. func (r *report) printLatencies() {
  140. pctls := []int{10, 25, 50, 75, 90, 95, 99}
  141. data := make([]float64, len(pctls))
  142. j := 0
  143. for i := 0; i < len(r.lats) && j < len(pctls); i++ {
  144. current := i * 100 / len(r.lats)
  145. if current >= pctls[j] {
  146. data[j] = r.lats[i]
  147. j++
  148. }
  149. }
  150. fmt.Printf("\nLatency distribution:\n")
  151. for i := 0; i < len(pctls); i++ {
  152. if data[i] > 0 {
  153. fmt.Printf(" %v%% in %4.4f secs.\n", pctls[i], data[i])
  154. }
  155. }
  156. }
  157. func (r *report) printHistogram() {
  158. bc := 10
  159. buckets := make([]float64, bc+1)
  160. counts := make([]int, bc+1)
  161. bs := (r.slowest - r.fastest) / float64(bc)
  162. for i := 0; i < bc; i++ {
  163. buckets[i] = r.fastest + bs*float64(i)
  164. }
  165. buckets[bc] = r.slowest
  166. var bi int
  167. var max int
  168. for i := 0; i < len(r.lats); {
  169. if r.lats[i] <= buckets[bi] {
  170. i++
  171. counts[bi]++
  172. if max < counts[bi] {
  173. max = counts[bi]
  174. }
  175. } else if bi < len(buckets)-1 {
  176. bi++
  177. }
  178. }
  179. fmt.Printf("\nResponse time histogram:\n")
  180. for i := 0; i < len(buckets); i++ {
  181. // Normalize bar lengths.
  182. var barLen int
  183. if max > 0 {
  184. barLen = counts[i] * 40 / max
  185. }
  186. fmt.Printf(" %4.3f [%v]\t|%v\n", buckets[i], counts[i], strings.Repeat(barChar, barLen))
  187. }
  188. }
  189. // Prints status code distribution.
  190. func (r *report) printStatusCodes() {
  191. fmt.Printf("\nStatus code distribution:\n")
  192. for code, num := range r.statusCodeDist {
  193. fmt.Printf(" [%d]\t%d responses\n", code, num)
  194. }
  195. }
  196. func (r *report) printErrors() {
  197. fmt.Printf("\nError distribution:\n")
  198. for err, num := range r.errorDist {
  199. fmt.Printf(" [%d]\t%s\n", num, err)
  200. }
  201. }