report.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. // Copyright 2014 The etcd Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // the file is borrowed from github.com/rakyll/boom/boomer/print.go
  15. package cmd
  16. import (
  17. "fmt"
  18. "math"
  19. "sort"
  20. "strings"
  21. "time"
  22. )
  23. const (
  24. barChar = "∎"
  25. )
  26. type result struct {
  27. errStr string
  28. duration time.Duration
  29. happened time.Time
  30. }
  31. type report struct {
  32. avgTotal float64
  33. fastest float64
  34. slowest float64
  35. average float64
  36. stddev float64
  37. rps float64
  38. results chan result
  39. total time.Duration
  40. errorDist map[string]int
  41. lats []float64
  42. sps *secondPoints
  43. }
  44. func printReport(results chan result) <-chan struct{} {
  45. return wrapReport(func() {
  46. r := &report{
  47. results: results,
  48. errorDist: make(map[string]int),
  49. sps: newSecondPoints(),
  50. }
  51. r.finalize()
  52. r.print()
  53. })
  54. }
  55. func printRate(results chan result) <-chan struct{} {
  56. return wrapReport(func() {
  57. r := &report{
  58. results: results,
  59. errorDist: make(map[string]int),
  60. sps: newSecondPoints(),
  61. }
  62. r.finalize()
  63. fmt.Printf(" Requests/sec:\t%4.4f\n", r.rps)
  64. })
  65. }
  66. func wrapReport(f func()) <-chan struct{} {
  67. donec := make(chan struct{})
  68. go func() {
  69. defer close(donec)
  70. f()
  71. }()
  72. return donec
  73. }
  74. func (r *report) finalize() {
  75. st := time.Now()
  76. for res := range r.results {
  77. if res.errStr != "" {
  78. r.errorDist[res.errStr]++
  79. } else {
  80. r.sps.Add(res.happened, res.duration)
  81. r.lats = append(r.lats, res.duration.Seconds())
  82. r.avgTotal += res.duration.Seconds()
  83. }
  84. }
  85. r.total = time.Since(st)
  86. r.rps = float64(len(r.lats)) / r.total.Seconds()
  87. r.average = r.avgTotal / float64(len(r.lats))
  88. for i := range r.lats {
  89. dev := r.lats[i] - r.average
  90. r.stddev += dev * dev
  91. }
  92. r.stddev = math.Sqrt(r.stddev / float64(len(r.lats)))
  93. }
  94. func (r *report) print() {
  95. sort.Float64s(r.lats)
  96. if len(r.lats) > 0 {
  97. r.fastest = r.lats[0]
  98. r.slowest = r.lats[len(r.lats)-1]
  99. fmt.Printf("\nSummary:\n")
  100. fmt.Printf(" Total:\t%4.4f secs.\n", r.total.Seconds())
  101. fmt.Printf(" Slowest:\t%4.4f secs.\n", r.slowest)
  102. fmt.Printf(" Fastest:\t%4.4f secs.\n", r.fastest)
  103. fmt.Printf(" Average:\t%4.4f secs.\n", r.average)
  104. fmt.Printf(" Stddev:\t%4.4f secs.\n", r.stddev)
  105. fmt.Printf(" Requests/sec:\t%4.4f\n", r.rps)
  106. r.printHistogram()
  107. r.printLatencies()
  108. if sample {
  109. r.printSecondSample()
  110. }
  111. }
  112. if len(r.errorDist) > 0 {
  113. r.printErrors()
  114. }
  115. }
  116. // Prints percentile latencies.
  117. func (r *report) printLatencies() {
  118. pctls := []int{10, 25, 50, 75, 90, 95, 99}
  119. data := make([]float64, len(pctls))
  120. j := 0
  121. for i := 0; i < len(r.lats) && j < len(pctls); i++ {
  122. current := i * 100 / len(r.lats)
  123. if current >= pctls[j] {
  124. data[j] = r.lats[i]
  125. j++
  126. }
  127. }
  128. fmt.Printf("\nLatency distribution:\n")
  129. for i := 0; i < len(pctls); i++ {
  130. if data[i] > 0 {
  131. fmt.Printf(" %v%% in %4.4f secs.\n", pctls[i], data[i])
  132. }
  133. }
  134. }
  135. func (r *report) printSecondSample() {
  136. fmt.Println(r.sps.getTimeSeries())
  137. }
  138. func (r *report) printHistogram() {
  139. bc := 10
  140. buckets := make([]float64, bc+1)
  141. counts := make([]int, bc+1)
  142. bs := (r.slowest - r.fastest) / float64(bc)
  143. for i := 0; i < bc; i++ {
  144. buckets[i] = r.fastest + bs*float64(i)
  145. }
  146. buckets[bc] = r.slowest
  147. var bi int
  148. var max int
  149. for i := 0; i < len(r.lats); {
  150. if r.lats[i] <= buckets[bi] {
  151. i++
  152. counts[bi]++
  153. if max < counts[bi] {
  154. max = counts[bi]
  155. }
  156. } else if bi < len(buckets)-1 {
  157. bi++
  158. }
  159. }
  160. fmt.Printf("\nResponse time histogram:\n")
  161. for i := 0; i < len(buckets); i++ {
  162. // Normalize bar lengths.
  163. var barLen int
  164. if max > 0 {
  165. barLen = counts[i] * 40 / max
  166. }
  167. fmt.Printf(" %4.3f [%v]\t|%v\n", buckets[i], counts[i], strings.Repeat(barChar, barLen))
  168. }
  169. }
  170. func (r *report) printErrors() {
  171. fmt.Printf("\nError distribution:\n")
  172. for err, num := range r.errorDist {
  173. fmt.Printf(" [%d]\t%s\n", num, err)
  174. }
  175. }