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  }