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