report.go 6.4 KB

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