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