OSDN Git Service

new repo
[bytom/vapor.git] / vendor / google.golang.org / grpc / benchmark / stats / stats.go
1 /*
2  *
3  * Copyright 2017 gRPC authors.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */
18
19 package stats
20
21 import (
22         "bytes"
23         "fmt"
24         "io"
25         "math"
26         "sort"
27         "strconv"
28         "time"
29 )
30
31 // Features contains most fields for a benchmark
32 type Features struct {
33         NetworkMode        string
34         EnableTrace        bool
35         Latency            time.Duration
36         Kbps               int
37         Mtu                int
38         MaxConcurrentCalls int
39         ReqSizeBytes       int
40         RespSizeBytes      int
41         EnableCompressor   bool
42 }
43
44 // String returns the textual output of the Features as string.
45 func (f Features) String() string {
46         return fmt.Sprintf("traceMode_%t-latency_%s-kbps_%#v-MTU_%#v-maxConcurrentCalls_"+
47                 "%#v-reqSize_%#vB-respSize_%#vB-Compressor_%t", f.EnableTrace,
48                 f.Latency.String(), f.Kbps, f.Mtu, f.MaxConcurrentCalls, f.ReqSizeBytes, f.RespSizeBytes, f.EnableCompressor)
49 }
50
51 // PartialPrintString can print certain features with different format.
52 func PartialPrintString(noneEmptyPos []bool, f Features, shared bool) string {
53         s := ""
54         var (
55                 prefix, suffix, linker string
56                 isNetwork              bool
57         )
58         if shared {
59                 suffix = "\n"
60                 linker = ": "
61         } else {
62                 prefix = "-"
63                 linker = "_"
64         }
65         if noneEmptyPos[0] {
66                 s += fmt.Sprintf("%sTrace%s%t%s", prefix, linker, f.EnableCompressor, suffix)
67         }
68         if shared && f.NetworkMode != "" {
69                 s += fmt.Sprintf("Network: %s \n", f.NetworkMode)
70                 isNetwork = true
71         }
72         if !isNetwork {
73                 if noneEmptyPos[1] {
74                         s += fmt.Sprintf("%slatency%s%s%s", prefix, linker, f.Latency.String(), suffix)
75                 }
76                 if noneEmptyPos[2] {
77                         s += fmt.Sprintf("%skbps%s%#v%s", prefix, linker, f.Kbps, suffix)
78                 }
79                 if noneEmptyPos[3] {
80                         s += fmt.Sprintf("%sMTU%s%#v%s", prefix, linker, f.Mtu, suffix)
81                 }
82         }
83         if noneEmptyPos[4] {
84                 s += fmt.Sprintf("%sCallers%s%#v%s", prefix, linker, f.MaxConcurrentCalls, suffix)
85         }
86         if noneEmptyPos[5] {
87                 s += fmt.Sprintf("%sreqSize%s%#vB%s", prefix, linker, f.ReqSizeBytes, suffix)
88         }
89         if noneEmptyPos[6] {
90                 s += fmt.Sprintf("%srespSize%s%#vB%s", prefix, linker, f.RespSizeBytes, suffix)
91         }
92         if noneEmptyPos[7] {
93                 s += fmt.Sprintf("%sCompressor%s%t%s", prefix, linker, f.EnableCompressor, suffix)
94         }
95         return s
96 }
97
98 type percentLatency struct {
99         Percent int
100         Value   time.Duration
101 }
102
103 // BenchResults records features and result of a benchmark.
104 type BenchResults struct {
105         RunMode           string
106         Features          Features
107         Latency           []percentLatency
108         Operations        int
109         NsPerOp           int64
110         AllocedBytesPerOp int64
111         AllocsPerOp       int64
112         SharedPosion      []bool
113 }
114
115 // SetBenchmarkResult sets features of benchmark and basic results.
116 func (stats *Stats) SetBenchmarkResult(mode string, features Features, o int, allocdBytes, allocs int64, sharedPos []bool) {
117         stats.result.RunMode = mode
118         stats.result.Features = features
119         stats.result.Operations = o
120         stats.result.AllocedBytesPerOp = allocdBytes
121         stats.result.AllocsPerOp = allocs
122         stats.result.SharedPosion = sharedPos
123 }
124
125 // GetBenchmarkResults returns the result of the benchmark including features and result.
126 func (stats *Stats) GetBenchmarkResults() BenchResults {
127         return stats.result
128 }
129
130 // BenchString output latency stats as the format as time + unit.
131 func (stats *Stats) BenchString() string {
132         stats.maybeUpdate()
133         s := stats.result
134         res := s.RunMode + "-" + s.Features.String() + ": \n"
135         if len(s.Latency) != 0 {
136                 var statsUnit = s.Latency[0].Value
137                 var timeUnit = fmt.Sprintf("%v", statsUnit)[1:]
138                 for i := 1; i < len(s.Latency)-1; i++ {
139                         res += fmt.Sprintf("%d_Latency: %s %s \t", s.Latency[i].Percent,
140                                 strconv.FormatFloat(float64(s.Latency[i].Value)/float64(statsUnit), 'f', 4, 64), timeUnit)
141                 }
142                 res += fmt.Sprintf("Avg latency: %s %s \t",
143                         strconv.FormatFloat(float64(s.Latency[len(s.Latency)-1].Value)/float64(statsUnit), 'f', 4, 64), timeUnit)
144         }
145         res += fmt.Sprintf("Count: %v \t", s.Operations)
146         res += fmt.Sprintf("%v Bytes/op\t", s.AllocedBytesPerOp)
147         res += fmt.Sprintf("%v Allocs/op\t", s.AllocsPerOp)
148
149         return res
150 }
151
152 // Stats is a simple helper for gathering additional statistics like histogram
153 // during benchmarks. This is not thread safe.
154 type Stats struct {
155         numBuckets int
156         unit       time.Duration
157         min, max   int64
158         histogram  *Histogram
159
160         durations durationSlice
161         dirty     bool
162
163         sortLatency bool
164         result      BenchResults
165 }
166
167 type durationSlice []time.Duration
168
169 // NewStats creates a new Stats instance. If numBuckets is not positive,
170 // the default value (16) will be used.
171 func NewStats(numBuckets int) *Stats {
172         if numBuckets <= 0 {
173                 numBuckets = 16
174         }
175         return &Stats{
176                 // Use one more bucket for the last unbounded bucket.
177                 numBuckets: numBuckets + 1,
178                 durations:  make(durationSlice, 0, 100000),
179         }
180 }
181
182 // Add adds an elapsed time per operation to the stats.
183 func (stats *Stats) Add(d time.Duration) {
184         stats.durations = append(stats.durations, d)
185         stats.dirty = true
186 }
187
188 // Clear resets the stats, removing all values.
189 func (stats *Stats) Clear() {
190         stats.durations = stats.durations[:0]
191         stats.histogram = nil
192         stats.dirty = false
193         stats.result = BenchResults{}
194 }
195
196 //Sort method for durations
197 func (a durationSlice) Len() int           { return len(a) }
198 func (a durationSlice) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
199 func (a durationSlice) Less(i, j int) bool { return a[i] < a[j] }
200 func max(a, b int64) int64 {
201         if a > b {
202                 return a
203         }
204         return b
205 }
206
207 // maybeUpdate updates internal stat data if there was any newly added
208 // stats since this was updated.
209 func (stats *Stats) maybeUpdate() {
210         if !stats.dirty {
211                 return
212         }
213
214         if stats.sortLatency {
215                 sort.Sort(stats.durations)
216                 stats.min = int64(stats.durations[0])
217                 stats.max = int64(stats.durations[len(stats.durations)-1])
218         }
219
220         stats.min = math.MaxInt64
221         stats.max = 0
222         for _, d := range stats.durations {
223                 if stats.min > int64(d) {
224                         stats.min = int64(d)
225                 }
226                 if stats.max < int64(d) {
227                         stats.max = int64(d)
228                 }
229         }
230
231         // Use the largest unit that can represent the minimum time duration.
232         stats.unit = time.Nanosecond
233         for _, u := range []time.Duration{time.Microsecond, time.Millisecond, time.Second} {
234                 if stats.min <= int64(u) {
235                         break
236                 }
237                 stats.unit = u
238         }
239
240         numBuckets := stats.numBuckets
241         if n := int(stats.max - stats.min + 1); n < numBuckets {
242                 numBuckets = n
243         }
244         stats.histogram = NewHistogram(HistogramOptions{
245                 NumBuckets: numBuckets,
246                 // max-min(lower bound of last bucket) = (1 + growthFactor)^(numBuckets-2) * baseBucketSize.
247                 GrowthFactor:   math.Pow(float64(stats.max-stats.min), 1/float64(numBuckets-2)) - 1,
248                 BaseBucketSize: 1.0,
249                 MinValue:       stats.min})
250
251         for _, d := range stats.durations {
252                 stats.histogram.Add(int64(d))
253         }
254
255         stats.dirty = false
256
257         if stats.durations.Len() != 0 {
258                 var percentToObserve = []int{50, 90}
259                 // First data record min unit from the latency result.
260                 stats.result.Latency = append(stats.result.Latency, percentLatency{Percent: -1, Value: stats.unit})
261                 for _, position := range percentToObserve {
262                         stats.result.Latency = append(stats.result.Latency, percentLatency{Percent: position, Value: stats.durations[max(stats.histogram.Count*int64(position)/100-1, 0)]})
263                 }
264                 // Last data record the average latency.
265                 avg := float64(stats.histogram.Sum) / float64(stats.histogram.Count)
266                 stats.result.Latency = append(stats.result.Latency, percentLatency{Percent: -1, Value: time.Duration(avg)})
267         }
268 }
269
270 // SortLatency blocks the output
271 func (stats *Stats) SortLatency() {
272         stats.sortLatency = true
273 }
274
275 // Print writes textual output of the Stats.
276 func (stats *Stats) Print(w io.Writer) {
277         stats.maybeUpdate()
278         if stats.histogram == nil {
279                 fmt.Fprint(w, "Histogram (empty)\n")
280         } else {
281                 fmt.Fprintf(w, "Histogram (unit: %s)\n", fmt.Sprintf("%v", stats.unit)[1:])
282                 stats.histogram.PrintWithUnit(w, float64(stats.unit))
283         }
284 }
285
286 // String returns the textual output of the Stats as string.
287 func (stats *Stats) String() string {
288         var b bytes.Buffer
289         stats.Print(&b)
290         return b.String()
291 }