1// Copyright 2014 Google Inc. All Rights Reserved. 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 17package cmd 18 19import ( 20 "fmt" 21 "math" 22 "sort" 23 "strings" 24 "time" 25) 26 27const ( 28 barChar = "∎" 29) 30 31type result struct { 32 errStr string 33 duration time.Duration 34 happened time.Time 35} 36 37type report struct { 38 avgTotal float64 39 fastest float64 40 slowest float64 41 average float64 42 stddev float64 43 rps float64 44 45 results chan result 46 total time.Duration 47 48 errorDist map[string]int 49 lats []float64 50 51 sps *secondPoints 52} 53 54func printReport(results chan result) <-chan struct{} { 55 return wrapReport(func() { 56 r := &report{ 57 results: results, 58 errorDist: make(map[string]int), 59 sps: newSecondPoints(), 60 } 61 r.finalize() 62 r.print() 63 }) 64} 65 66func printRate(results chan result) <-chan struct{} { 67 return wrapReport(func() { 68 r := &report{ 69 results: results, 70 errorDist: make(map[string]int), 71 sps: newSecondPoints(), 72 } 73 r.finalize() 74 fmt.Printf(" Requests/sec:\t%4.4f\n", r.rps) 75 }) 76} 77 78func wrapReport(f func()) <-chan struct{} { 79 donec := make(chan struct{}) 80 go func() { 81 defer close(donec) 82 f() 83 }() 84 return donec 85} 86 87func (r *report) finalize() { 88 st := time.Now() 89 for res := range r.results { 90 if res.errStr != "" { 91 r.errorDist[res.errStr]++ 92 } else { 93 r.sps.Add(res.happened, res.duration) 94 r.lats = append(r.lats, res.duration.Seconds()) 95 r.avgTotal += res.duration.Seconds() 96 } 97 } 98 r.total = time.Since(st) 99 100 r.rps = float64(len(r.lats)) / r.total.Seconds() 101 r.average = r.avgTotal / float64(len(r.lats)) 102 for i := range r.lats { 103 dev := r.lats[i] - r.average 104 r.stddev += dev * dev 105 } 106 r.stddev = math.Sqrt(r.stddev / float64(len(r.lats))) 107} 108 109func (r *report) print() { 110 sort.Float64s(r.lats) 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(" Stddev:\t%4.4f secs.\n", r.stddev) 121 fmt.Printf(" Requests/sec:\t%4.4f\n", r.rps) 122 r.printHistogram() 123 r.printLatencies() 124 if sample { 125 r.printSecondSample() 126 } 127 } 128 129 if len(r.errorDist) > 0 { 130 r.printErrors() 131 } 132} 133 134// Prints percentile latencies. 135func (r *report) printLatencies() { 136 pctls := []int{10, 25, 50, 75, 90, 95, 99} 137 data := make([]float64, len(pctls)) 138 j := 0 139 for i := 0; i < len(r.lats) && j < len(pctls); i++ { 140 current := i * 100 / len(r.lats) 141 if current >= pctls[j] { 142 data[j] = r.lats[i] 143 j++ 144 } 145 } 146 fmt.Printf("\nLatency distribution:\n") 147 for i := 0; i < len(pctls); i++ { 148 if data[i] > 0 { 149 fmt.Printf(" %v%% in %4.4f secs.\n", pctls[i], data[i]) 150 } 151 } 152} 153 154func (r *report) printSecondSample() { 155 fmt.Println(r.sps.getTimeSeries()) 156} 157 158func (r *report) printHistogram() { 159 bc := 10 160 buckets := make([]float64, bc+1) 161 counts := make([]int, bc+1) 162 bs := (r.slowest - r.fastest) / float64(bc) 163 for i := 0; i < bc; i++ { 164 buckets[i] = r.fastest + bs*float64(i) 165 } 166 buckets[bc] = r.slowest 167 var bi int 168 var max int 169 for i := 0; i < len(r.lats); { 170 if r.lats[i] <= buckets[bi] { 171 i++ 172 counts[bi]++ 173 if max < counts[bi] { 174 max = counts[bi] 175 } 176 } else if bi < len(buckets)-1 { 177 bi++ 178 } 179 } 180 fmt.Printf("\nResponse time histogram:\n") 181 for i := 0; i < len(buckets); i++ { 182 // Normalize bar lengths. 183 var barLen int 184 if max > 0 { 185 barLen = counts[i] * 40 / max 186 } 187 fmt.Printf(" %4.3f [%v]\t|%v\n", buckets[i], counts[i], strings.Repeat(barChar, barLen)) 188 } 189} 190 191func (r *report) printErrors() { 192 fmt.Printf("\nError distribution:\n") 193 for err, num := range r.errorDist { 194 fmt.Printf(" [%d]\t%s\n", num, err) 195 } 196} 197