report.go 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  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 report
  16. import (
  17. "fmt"
  18. "math"
  19. "sort"
  20. "strings"
  21. "time"
  22. )
  23. const (
  24. barChar = "∎"
  25. )
  26. // Result describes the timings for an operation.
  27. type Result struct {
  28. Start time.Time
  29. End time.Time
  30. Err error
  31. }
  32. func (res *Result) Duration() time.Duration { return res.End.Sub(res.Start) }
  33. type report struct {
  34. results chan Result
  35. precision string
  36. avgTotal float64
  37. fastest float64
  38. slowest float64
  39. average float64
  40. stddev float64
  41. rps float64
  42. total time.Duration
  43. errorDist map[string]int
  44. lats []float64
  45. sps *secondPoints
  46. }
  47. // Stats exposes results raw data.
  48. type Stats struct {
  49. AvgTotal float64
  50. Fastest float64
  51. Slowest float64
  52. Average float64
  53. Stddev float64
  54. RPS float64
  55. Total time.Duration
  56. ErrorDist map[string]int
  57. Lats []float64
  58. TimeSeries TimeSeries
  59. }
  60. // Report processes a result stream until it is closed, then produces a
  61. // string with information about the consumed result data.
  62. type Report interface {
  63. Results() chan<- Result
  64. // Run returns results in print-friendly format.
  65. Run() <-chan string
  66. // Stats returns results in raw data.
  67. Stats() <-chan Stats
  68. }
  69. func NewReport(precision string) Report {
  70. return &report{
  71. results: make(chan Result, 16),
  72. precision: precision,
  73. errorDist: make(map[string]int),
  74. }
  75. }
  76. func NewReportSample(precision string) Report {
  77. r := NewReport(precision).(*report)
  78. r.sps = newSecondPoints()
  79. return r
  80. }
  81. func (r *report) Results() chan<- Result { return r.results }
  82. func (r *report) Run() <-chan string {
  83. donec := make(chan string, 1)
  84. go func() {
  85. defer close(donec)
  86. r.processResults()
  87. donec <- r.String()
  88. }()
  89. return donec
  90. }
  91. func (r *report) Stats() <-chan Stats {
  92. donec := make(chan Stats, 1)
  93. go func() {
  94. defer close(donec)
  95. r.processResults()
  96. var ts TimeSeries
  97. if r.sps != nil {
  98. ts = r.sps.getTimeSeries()
  99. }
  100. donec <- Stats{
  101. AvgTotal: r.avgTotal,
  102. Fastest: r.fastest,
  103. Slowest: r.slowest,
  104. Average: r.average,
  105. Stddev: r.stddev,
  106. RPS: r.rps,
  107. Total: r.total,
  108. ErrorDist: copyMap(r.errorDist),
  109. Lats: copyFloats(r.lats),
  110. TimeSeries: ts,
  111. }
  112. }()
  113. return donec
  114. }
  115. func copyMap(m map[string]int) (c map[string]int) {
  116. c = make(map[string]int, len(m))
  117. for k, v := range m {
  118. c[k] = v
  119. }
  120. return
  121. }
  122. func copyFloats(s []float64) (c []float64) {
  123. c = make([]float64, len(s))
  124. copy(c, s)
  125. return
  126. }
  127. func (r *report) String() (s string) {
  128. if len(r.lats) > 0 {
  129. s += fmt.Sprintf("\nSummary:\n")
  130. s += fmt.Sprintf(" Total:\t%s.\n", r.sec2str(r.total.Seconds()))
  131. s += fmt.Sprintf(" Slowest:\t%s.\n", r.sec2str(r.slowest))
  132. s += fmt.Sprintf(" Fastest:\t%s.\n", r.sec2str(r.fastest))
  133. s += fmt.Sprintf(" Average:\t%s.\n", r.sec2str(r.average))
  134. s += fmt.Sprintf(" Stddev:\t%s.\n", r.sec2str(r.stddev))
  135. s += fmt.Sprintf(" Requests/sec:\t"+r.precision+"\n", r.rps)
  136. s += r.histogram()
  137. s += r.sprintLatencies()
  138. if r.sps != nil {
  139. s += fmt.Sprintf("%v\n", r.sps.getTimeSeries())
  140. }
  141. }
  142. if len(r.errorDist) > 0 {
  143. s += r.errors()
  144. }
  145. return s
  146. }
  147. func (r *report) sec2str(sec float64) string { return fmt.Sprintf(r.precision+" secs", sec) }
  148. type reportRate struct{ *report }
  149. func NewReportRate(precision string) Report {
  150. return &reportRate{NewReport(precision).(*report)}
  151. }
  152. func (r *reportRate) String() string {
  153. return fmt.Sprintf(" Requests/sec:\t"+r.precision+"\n", r.rps)
  154. }
  155. func (r *report) processResult(res *Result) {
  156. if res.Err != nil {
  157. r.errorDist[res.Err.Error()]++
  158. return
  159. }
  160. dur := res.Duration()
  161. r.lats = append(r.lats, dur.Seconds())
  162. r.avgTotal += dur.Seconds()
  163. if r.sps != nil {
  164. r.sps.Add(res.Start, dur)
  165. }
  166. }
  167. func (r *report) processResults() {
  168. st := time.Now()
  169. for res := range r.results {
  170. r.processResult(&res)
  171. }
  172. r.total = time.Since(st)
  173. r.rps = float64(len(r.lats)) / r.total.Seconds()
  174. r.average = r.avgTotal / float64(len(r.lats))
  175. for i := range r.lats {
  176. dev := r.lats[i] - r.average
  177. r.stddev += dev * dev
  178. }
  179. r.stddev = math.Sqrt(r.stddev / float64(len(r.lats)))
  180. sort.Float64s(r.lats)
  181. if len(r.lats) > 0 {
  182. r.fastest = r.lats[0]
  183. r.slowest = r.lats[len(r.lats)-1]
  184. }
  185. }
  186. var pctls = []float64{10, 25, 50, 75, 90, 95, 99, 99.9}
  187. // Percentiles returns percentile distribution of float64 slice.
  188. func Percentiles(nums []float64) (pcs []float64, data []float64) {
  189. return pctls, percentiles(nums)
  190. }
  191. func percentiles(nums []float64) (data []float64) {
  192. data = make([]float64, len(pctls))
  193. j := 0
  194. n := len(nums)
  195. for i := 0; i < n && j < len(pctls); i++ {
  196. current := float64(i) * 100.0 / float64(n)
  197. if current >= pctls[j] {
  198. data[j] = nums[i]
  199. j++
  200. }
  201. }
  202. return
  203. }
  204. func (r *report) sprintLatencies() string {
  205. data := percentiles(r.lats)
  206. s := fmt.Sprintf("\nLatency distribution:\n")
  207. for i := 0; i < len(pctls); i++ {
  208. if data[i] > 0 {
  209. s += fmt.Sprintf(" %v%% in %s.\n", pctls[i], r.sec2str(data[i]))
  210. }
  211. }
  212. return s
  213. }
  214. func (r *report) histogram() string {
  215. bc := 10
  216. buckets := make([]float64, bc+1)
  217. counts := make([]int, bc+1)
  218. bs := (r.slowest - r.fastest) / float64(bc)
  219. for i := 0; i < bc; i++ {
  220. buckets[i] = r.fastest + bs*float64(i)
  221. }
  222. buckets[bc] = r.slowest
  223. var bi int
  224. var max int
  225. for i := 0; i < len(r.lats); {
  226. if r.lats[i] <= buckets[bi] {
  227. i++
  228. counts[bi]++
  229. if max < counts[bi] {
  230. max = counts[bi]
  231. }
  232. } else if bi < len(buckets)-1 {
  233. bi++
  234. }
  235. }
  236. s := fmt.Sprintf("\nResponse time histogram:\n")
  237. for i := 0; i < len(buckets); i++ {
  238. // Normalize bar lengths.
  239. var barLen int
  240. if max > 0 {
  241. barLen = counts[i] * 40 / max
  242. }
  243. s += fmt.Sprintf(" "+r.precision+" [%v]\t|%v\n", buckets[i], counts[i], strings.Repeat(barChar, barLen))
  244. }
  245. return s
  246. }
  247. func (r *report) errors() string {
  248. s := fmt.Sprintf("\nError distribution:\n")
  249. for err, num := range r.errorDist {
  250. s += fmt.Sprintf(" [%d]\t%s\n", num, err)
  251. }
  252. return s
  253. }