|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- // Copyright 2016 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
-
- package stat
-
- import (
- "bytes"
- "encoding/csv"
- "fmt"
- "io"
- "math"
- "sort"
- "strconv"
- "text/tabwriter"
- "time"
- )
-
- type byDuration []time.Duration
-
- func (data byDuration) Len() int { return len(data) }
- func (data byDuration) Swap(i, j int) { data[i], data[j] = data[j], data[i] }
- func (data byDuration) Less(i, j int) bool { return data[i] < data[j] }
-
- // quantile returns a value representing the kth of q quantiles.
- // May alter the order of data.
- func quantile(data []time.Duration, k, q int) (quantile time.Duration, ok bool) {
- if len(data) < 1 {
- return 0, false
- }
- if k > q {
- return 0, false
- }
- if k < 0 || q < 1 {
- return 0, false
- }
-
- sort.Sort(byDuration(data))
-
- if k == 0 {
- return data[0], true
- }
- if k == q {
- return data[len(data)-1], true
- }
-
- bucketSize := float64(len(data)-1) / float64(q)
- i := float64(k) * bucketSize
-
- lower := int(math.Trunc(i))
- var upper int
- if i > float64(lower) && lower+1 < len(data) {
- // If the quantile lies between two elements
- upper = lower + 1
- } else {
- upper = lower
- }
- weightUpper := i - float64(lower)
- weightLower := 1 - weightUpper
- return time.Duration(weightLower*float64(data[lower]) + weightUpper*float64(data[upper])), true
- }
-
- // Aggregate is an aggregate of latencies.
- type Aggregate struct {
- Name string
- Count, Errors int
- Min, Median, Max time.Duration
- P75, P90, P95, P99 time.Duration // percentiles
- }
-
- // NewAggregate constructs an aggregate from latencies. Returns nil if latencies does not contain aggregateable data.
- func NewAggregate(name string, latencies []time.Duration, errorCount int) *Aggregate {
- agg := Aggregate{Name: name, Count: len(latencies), Errors: errorCount}
-
- if len(latencies) == 0 {
- return nil
- }
- var ok bool
- if agg.Min, ok = quantile(latencies, 0, 2); !ok {
- return nil
- }
- if agg.Median, ok = quantile(latencies, 1, 2); !ok {
- return nil
- }
- if agg.Max, ok = quantile(latencies, 2, 2); !ok {
- return nil
- }
- if agg.P75, ok = quantile(latencies, 75, 100); !ok {
- return nil
- }
- if agg.P90, ok = quantile(latencies, 90, 100); !ok {
- return nil
- }
- if agg.P95, ok = quantile(latencies, 95, 100); !ok {
- return nil
- }
- if agg.P99, ok = quantile(latencies, 99, 100); !ok {
- return nil
- }
- return &agg
- }
-
- func (agg *Aggregate) String() string {
- if agg == nil {
- return "no data"
- }
- var buf bytes.Buffer
- tw := tabwriter.NewWriter(&buf, 0, 0, 1, ' ', 0) // one-space padding
- fmt.Fprintf(tw, "min:\t%v\nmedian:\t%v\nmax:\t%v\n95th percentile:\t%v\n99th percentile:\t%v\n",
- agg.Min, agg.Median, agg.Max, agg.P95, agg.P99)
- tw.Flush()
- return buf.String()
- }
-
- // WriteCSV writes a csv file to the given Writer,
- // with a header row and one row per aggregate.
- func WriteCSV(aggs []*Aggregate, iow io.Writer) (err error) {
- w := csv.NewWriter(iow)
- defer func() {
- w.Flush()
- if err == nil {
- err = w.Error()
- }
- }()
- err = w.Write([]string{"name", "count", "errors", "min", "median", "max", "p75", "p90", "p95", "p99"})
- if err != nil {
- return err
- }
- for _, agg := range aggs {
- err = w.Write([]string{
- agg.Name, strconv.Itoa(agg.Count), strconv.Itoa(agg.Errors),
- agg.Min.String(), agg.Median.String(), agg.Max.String(),
- agg.P75.String(), agg.P90.String(), agg.P95.String(), agg.P99.String(),
- })
- if err != nil {
- return err
- }
- }
- return nil
- }
|