Jiri Simsa | d7616c9 | 2015-03-24 23:44:30 -0700 | [diff] [blame] | 1 | // Copyright 2015 The Vanadium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
Todd Wang | 8c4e5cc | 2015-04-09 11:30:52 -0700 | [diff] [blame] | 5 | // Package benchmark implements utilities to augment the standard Go |
| 6 | // testing.Benchmark functionality. |
Jungho Ahn | fa7a31f | 2015-01-07 17:34:12 -0800 | [diff] [blame] | 7 | package benchmark |
Jungho Ahn | 711a985 | 2014-12-04 10:43:20 -0800 | [diff] [blame] | 8 | |
| 9 | import ( |
| 10 | "bytes" |
| 11 | "fmt" |
| 12 | "io" |
| 13 | "math" |
| 14 | "time" |
| 15 | |
Jiri Simsa | ffceefa | 2015-02-28 11:03:34 -0800 | [diff] [blame] | 16 | "v.io/x/ref/lib/stats/histogram" |
Jungho Ahn | 711a985 | 2014-12-04 10:43:20 -0800 | [diff] [blame] | 17 | ) |
| 18 | |
Jungho Ahn | fa7a31f | 2015-01-07 17:34:12 -0800 | [diff] [blame] | 19 | // Stats is a simple helper for gathering additional statistics like histogram |
| 20 | // during benchmarks. This is not thread safe. |
| 21 | type Stats struct { |
Jungho Ahn | 711a985 | 2014-12-04 10:43:20 -0800 | [diff] [blame] | 22 | numBuckets int |
| 23 | unit time.Duration |
| 24 | min, max int64 |
| 25 | histogram *histogram.Histogram |
| 26 | |
| 27 | durations durationSlice |
| 28 | dirty bool |
| 29 | } |
| 30 | |
| 31 | type durationSlice []time.Duration |
| 32 | |
Jungho Ahn | fa7a31f | 2015-01-07 17:34:12 -0800 | [diff] [blame] | 33 | // NewStats creates a new Stats instance. If numBuckets is not positive, |
| 34 | // the default value (16) will be used. |
| 35 | func NewStats(numBuckets int) *Stats { |
Jungho Ahn | 711a985 | 2014-12-04 10:43:20 -0800 | [diff] [blame] | 36 | if numBuckets <= 0 { |
| 37 | numBuckets = 16 |
| 38 | } |
Jungho Ahn | fa7a31f | 2015-01-07 17:34:12 -0800 | [diff] [blame] | 39 | return &Stats{ |
Jungho Ahn | 711a985 | 2014-12-04 10:43:20 -0800 | [diff] [blame] | 40 | // Use one more bucket for the last unbounded bucket. |
| 41 | numBuckets: numBuckets + 1, |
| 42 | durations: make(durationSlice, 0, 100000), |
| 43 | } |
| 44 | } |
| 45 | |
Jungho Ahn | fa7a31f | 2015-01-07 17:34:12 -0800 | [diff] [blame] | 46 | // Add adds an elapsed time per operation to the stats. |
| 47 | func (stats *Stats) Add(d time.Duration) { |
Jungho Ahn | 711a985 | 2014-12-04 10:43:20 -0800 | [diff] [blame] | 48 | stats.durations = append(stats.durations, d) |
| 49 | stats.dirty = true |
| 50 | } |
| 51 | |
Jungho Ahn | bc6ffa2 | 2014-12-10 17:55:35 -0800 | [diff] [blame] | 52 | // Clear resets the stats, removing all values. |
Jungho Ahn | fa7a31f | 2015-01-07 17:34:12 -0800 | [diff] [blame] | 53 | func (stats *Stats) Clear() { |
Jungho Ahn | bc6ffa2 | 2014-12-10 17:55:35 -0800 | [diff] [blame] | 54 | stats.durations = stats.durations[:0] |
Jungho Ahn | fa7a31f | 2015-01-07 17:34:12 -0800 | [diff] [blame] | 55 | stats.histogram = nil |
| 56 | stats.dirty = false |
Jungho Ahn | bc6ffa2 | 2014-12-10 17:55:35 -0800 | [diff] [blame] | 57 | } |
| 58 | |
Jungho Ahn | 711a985 | 2014-12-04 10:43:20 -0800 | [diff] [blame] | 59 | // maybeUpdate updates internal stat data if there was any newly added |
| 60 | // stats since this was updated. |
Jungho Ahn | fa7a31f | 2015-01-07 17:34:12 -0800 | [diff] [blame] | 61 | func (stats *Stats) maybeUpdate() { |
| 62 | if !stats.dirty { |
Jungho Ahn | 711a985 | 2014-12-04 10:43:20 -0800 | [diff] [blame] | 63 | return |
| 64 | } |
| 65 | |
| 66 | stats.min = math.MaxInt64 |
| 67 | stats.max = 0 |
| 68 | for _, d := range stats.durations { |
| 69 | if stats.min > int64(d) { |
| 70 | stats.min = int64(d) |
| 71 | } |
| 72 | if stats.max < int64(d) { |
| 73 | stats.max = int64(d) |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | // Use the largest unit that can represent the minimum time duration. |
| 78 | stats.unit = time.Nanosecond |
| 79 | for _, u := range []time.Duration{time.Microsecond, time.Millisecond, time.Second} { |
| 80 | if stats.min <= int64(u) { |
| 81 | break |
| 82 | } |
| 83 | stats.unit = u |
| 84 | } |
| 85 | |
| 86 | // Adjust the min/max according to the new unit. |
| 87 | stats.min /= int64(stats.unit) |
| 88 | stats.max /= int64(stats.unit) |
Jungho Ahn | fa7a31f | 2015-01-07 17:34:12 -0800 | [diff] [blame] | 89 | numBuckets := stats.numBuckets |
| 90 | if n := int(stats.max - stats.min + 1); n < numBuckets { |
| 91 | numBuckets = n |
| 92 | } |
Jungho Ahn | 711a985 | 2014-12-04 10:43:20 -0800 | [diff] [blame] | 93 | stats.histogram = histogram.New(histogram.Options{ |
Jungho Ahn | fa7a31f | 2015-01-07 17:34:12 -0800 | [diff] [blame] | 94 | NumBuckets: numBuckets, |
Jungho Ahn | 711a985 | 2014-12-04 10:43:20 -0800 | [diff] [blame] | 95 | // max(i.e., Nth lower bound) = min + (1 + growthFactor)^(numBuckets-2). |
| 96 | GrowthFactor: math.Pow(float64(stats.max-stats.min), 1/float64(stats.numBuckets-2)) - 1, |
| 97 | SmallestBucketSize: 1.0, |
| 98 | MinValue: stats.min}) |
| 99 | |
| 100 | for _, d := range stats.durations { |
| 101 | stats.histogram.Add(int64(d / stats.unit)) |
| 102 | } |
| 103 | |
| 104 | stats.dirty = false |
| 105 | } |
| 106 | |
Jungho Ahn | fa7a31f | 2015-01-07 17:34:12 -0800 | [diff] [blame] | 107 | // Print writes textual output of the Stats. |
| 108 | func (stats *Stats) Print(w io.Writer) { |
Jungho Ahn | 711a985 | 2014-12-04 10:43:20 -0800 | [diff] [blame] | 109 | stats.maybeUpdate() |
| 110 | |
Jungho Ahn | fa7a31f | 2015-01-07 17:34:12 -0800 | [diff] [blame] | 111 | if stats.histogram == nil { |
| 112 | fmt.Fprint(w, "Histogram (empty)\n") |
| 113 | } else { |
| 114 | fmt.Fprintf(w, "Histogram (unit: %s)\n", fmt.Sprintf("%v", stats.unit)[1:]) |
| 115 | stats.histogram.Value().Print(w) |
| 116 | } |
Jungho Ahn | 711a985 | 2014-12-04 10:43:20 -0800 | [diff] [blame] | 117 | } |
| 118 | |
Jungho Ahn | fa7a31f | 2015-01-07 17:34:12 -0800 | [diff] [blame] | 119 | // String returns the textual output of the Stats as string. |
| 120 | func (stats *Stats) String() string { |
Jungho Ahn | 711a985 | 2014-12-04 10:43:20 -0800 | [diff] [blame] | 121 | var b bytes.Buffer |
| 122 | stats.Print(&b) |
| 123 | return b.String() |
| 124 | } |