github.com/zeebo/mon@v0.0.0-20211012163247-13d39bdb54fa/inthist/histogram_test.go (about)

     1  package inthist
     2  
     3  import (
     4  	"encoding/binary"
     5  	"encoding/hex"
     6  	"sync/atomic"
     7  	"testing"
     8  
     9  	"github.com/zeebo/assert"
    10  	"github.com/zeebo/pcg"
    11  )
    12  
    13  func TestHistogram(t *testing.T) {
    14  	t.Run("Walk", func(t *testing.T) {
    15  		type key = [2]uint64
    16  
    17  		var (
    18  			bucket uint64
    19  			entry  uint64
    20  			value  int64
    21  
    22  			bucketEntries = map[key]bool{}
    23  		)
    24  
    25  		for bucket < histBuckets && entry < histEntries {
    26  			// we must be on a new bucket/entry combination
    27  			assert.That(t, !bucketEntries[key{bucket, entry}])
    28  			bucketEntries[key{bucket, entry}] = true
    29  
    30  			// value is always lowerValue(bucket, entry)
    31  			assert.Equal(t, value, lowerValue(bucket, entry))
    32  
    33  			// bucketEntry(lowerValue(bucket, entry)) == bucket, entry
    34  			lbucket, lentry := bucketEntry(lowerValue(bucket, entry))
    35  			assert.Equal(t, bucket, uint(lbucket))
    36  			assert.Equal(t, entry, int(lentry))
    37  
    38  			// bucketEntry(upperValue(bucket, entry)) == bucket, entry
    39  			ubucket, uentry := bucketEntry(upperValue(bucket, entry))
    40  			assert.Equal(t, bucket, uint(ubucket))
    41  			assert.Equal(t, entry, int(uentry))
    42  
    43  			// upperValue(bucket, entry) + 1 is in the next bucket/entry
    44  			value = upperValue(bucket, entry) + 1
    45  			bucket, entry = bucketEntry(value)
    46  		}
    47  
    48  		// we must have hit every bucket/entry
    49  		assert.Equal(t, len(bucketEntries), histBuckets*histEntries)
    50  	})
    51  
    52  	t.Run("Zero", func(t *testing.T) {
    53  		h := new(Histogram)
    54  		sum, average, variance := h.Variance()
    55  		assert.Equal(t, sum, 0.0)
    56  		assert.Equal(t, average, 0.0)
    57  		assert.Equal(t, variance, 0.0)
    58  	})
    59  
    60  	t.Run("Boundaries", func(t *testing.T) {
    61  		h := new(Histogram)
    62  
    63  		h.Observe(0)
    64  		assert.Equal(t, h.Total(), 1)
    65  
    66  		h.Observe(-1)
    67  		assert.Equal(t, h.Total(), 1)
    68  
    69  		upper := upperValue(histBuckets-1, histEntries-1)
    70  
    71  		h.Observe(upper)
    72  		assert.Equal(t, h.Total(), 2)
    73  
    74  		for upper++; upper > 0; upper++ {
    75  			h.Observe(upper)
    76  			assert.Equal(t, h.Total(), 2)
    77  		}
    78  	})
    79  
    80  	t.Run("Basic", func(t *testing.T) {
    81  		h := new(Histogram)
    82  
    83  		for i := int64(0); i < 1000; i++ {
    84  			h.Observe(i)
    85  		}
    86  	})
    87  
    88  	t.Run("Quantile", func(t *testing.T) {
    89  		h := new(Histogram)
    90  		for i := int64(0); i < 1000; i++ {
    91  			h.Observe(i)
    92  		}
    93  
    94  		assert.Equal(t, h.Quantile(0), 0)
    95  		assert.Equal(t, h.Quantile(.25), 250)
    96  		assert.Equal(t, h.Quantile(.5), 500)
    97  		assert.Equal(t, h.Quantile(1), 1000)
    98  	})
    99  
   100  	t.Run("CDF", func(t *testing.T) {
   101  		h := new(Histogram)
   102  		for i := int64(0); i < 1000; i++ {
   103  			h.Observe(i)
   104  		}
   105  
   106  		assert.Equal(t, h.CDF(0), 0.001)
   107  		assert.Equal(t, h.CDF(250), 0.252)
   108  		assert.Equal(t, h.CDF(500), 0.504)
   109  		assert.Equal(t, h.CDF(1000), 1.0)
   110  	})
   111  
   112  	t.Run("Variance", func(t *testing.T) {
   113  		h := new(Histogram)
   114  		rsum := int64(0)
   115  		for i := int64(0); i < 1000; i++ {
   116  			h.Observe(i)
   117  			rsum += i
   118  		}
   119  
   120  		sum, average, variance := h.Variance()
   121  
   122  		assert.Equal(t, sum, 499532.0)
   123  		assert.Equal(t, average, 499.532)
   124  		assert.Equal(t, variance, 83361.358976)
   125  	})
   126  
   127  	t.Run("Percentiles", func(t *testing.T) {
   128  		h := new(Histogram)
   129  		for i := int64(0); i < 1000; i++ {
   130  			r := int64(pcg.Uint32n(1000))
   131  			h.Observe(r * r)
   132  		}
   133  
   134  		h.Percentiles(func(value, count, total int64) {
   135  			t.Log(value, count, total)
   136  		})
   137  	})
   138  
   139  	t.Run("Serialize", func(t *testing.T) {
   140  		h := new(Histogram)
   141  		for i := int64(0); i < 10000; i++ {
   142  			r := int64(pcg.Uint32n(100) + 500)
   143  			h.Observe(r)
   144  		}
   145  		h.Observe(1)
   146  		h.Observe(3)
   147  		h.Observe(5)
   148  
   149  		data := h.Serialize(nil)
   150  		t.Logf("%d\n%s", len(data), hex.Dump(data))
   151  		t.Logf("%064b\n", binary.LittleEndian.Uint64(data[:8]))
   152  	})
   153  
   154  	t.Run("Load", func(t *testing.T) {
   155  		h := new(Histogram)
   156  		for i := int64(0); i < 10000; i++ {
   157  			r := int64(pcg.Uint32n(1000) + 500)
   158  			h.Observe(r)
   159  		}
   160  
   161  		h2 := new(Histogram)
   162  		assert.NoError(t, h2.Load(h.Serialize(nil)))
   163  
   164  		assert.Equal(t, h.Total(), h2.Total())
   165  		assert.Equal(t, h.Sum(), h2.Sum())
   166  		t.Log(h.Average())
   167  		t.Log(h2.Average())
   168  	})
   169  }
   170  
   171  func BenchmarkHistogram(b *testing.B) {
   172  	b.Run("SumHistogramSlow", func(b *testing.B) {
   173  		var buf [64]uint32
   174  
   175  		for i := 0; i < b.N; i++ {
   176  			_ = sumHistogramSlow(&buf)
   177  		}
   178  	})
   179  
   180  	b.Run("SumHistogram", func(b *testing.B) {
   181  		var buf [64]uint32
   182  
   183  		for i := 0; i < b.N; i++ {
   184  			_ = sumHistogram(&buf)
   185  		}
   186  	})
   187  
   188  	b.Run("Observe", func(b *testing.B) {
   189  		his := new(Histogram)
   190  
   191  		for i := 0; i < b.N; i++ {
   192  			his.Observe(1)
   193  		}
   194  	})
   195  
   196  	b.Run("Observe_Parallel", func(b *testing.B) {
   197  		his := new(Histogram)
   198  		n := int64(0)
   199  		b.RunParallel(func(pb *testing.PB) {
   200  			i := int64(1024) << uint64(atomic.AddInt64(&n, 1))
   201  			for pb.Next() {
   202  				his.Observe(i)
   203  			}
   204  		})
   205  	})
   206  
   207  	b.Run("Total", func(b *testing.B) {
   208  		his := new(Histogram)
   209  		for i := 0; i < 1000000; i++ {
   210  			his.Observe(int64(pcg.Uint64() >> histEntriesBits))
   211  		}
   212  		b.ReportAllocs()
   213  		b.ResetTimer()
   214  
   215  		for i := 0; i < b.N; i++ {
   216  			his.Total()
   217  		}
   218  	})
   219  
   220  	b.Run("Total_Easy", func(b *testing.B) {
   221  		his := new(Histogram)
   222  		for i := 0; i < 1000000; i++ {
   223  			his.Observe(int64(pcg.Uint32n(64)))
   224  		}
   225  		b.ReportAllocs()
   226  		b.ResetTimer()
   227  
   228  		for i := 0; i < b.N; i++ {
   229  			his.Total()
   230  		}
   231  	})
   232  
   233  	b.Run("Quantile", func(b *testing.B) {
   234  		his := new(Histogram)
   235  		for i := 0; i < 1000000; i++ {
   236  			his.Observe(int64(pcg.Uint64() >> histEntriesBits))
   237  		}
   238  		assert.Equal(b, his.Total(), 1000000)
   239  		b.ReportAllocs()
   240  		b.ResetTimer()
   241  
   242  		for i := 0; i < b.N; i++ {
   243  			his.Quantile(pcg.Float64())
   244  		}
   245  	})
   246  
   247  	b.Run("Quantile_Easy", func(b *testing.B) {
   248  		his := new(Histogram)
   249  		for i := 0; i < 1000000; i++ {
   250  			his.Observe(int64(pcg.Uint32n(64)))
   251  		}
   252  		assert.Equal(b, his.Total(), 1000000)
   253  		b.ReportAllocs()
   254  		b.ResetTimer()
   255  
   256  		for i := 0; i < b.N; i++ {
   257  			his.Quantile(pcg.Float64())
   258  		}
   259  	})
   260  
   261  	b.Run("CDF", func(b *testing.B) {
   262  		his := new(Histogram)
   263  		for i := 0; i < 1000000; i++ {
   264  			his.Observe(int64(pcg.Uint64() >> histEntriesBits))
   265  		}
   266  		assert.Equal(b, his.Total(), 1000000)
   267  		b.ReportAllocs()
   268  		b.ResetTimer()
   269  
   270  		for i := 0; i < b.N; i++ {
   271  			his.CDF(int64(pcg.Uint64() >> histEntriesBits))
   272  		}
   273  	})
   274  
   275  	b.Run("CDF_Easy", func(b *testing.B) {
   276  		his := new(Histogram)
   277  		for i := 0; i < 1000000; i++ {
   278  			his.Observe(int64(pcg.Uint32n(64)))
   279  		}
   280  		assert.Equal(b, his.Total(), 1000000)
   281  		b.ReportAllocs()
   282  		b.ResetTimer()
   283  
   284  		for i := 0; i < b.N; i++ {
   285  			his.CDF(int64(pcg.Uint32n(64)))
   286  		}
   287  	})
   288  
   289  	b.Run("Sum", func(b *testing.B) {
   290  		his := new(Histogram)
   291  		for i := 0; i < 1000; i++ {
   292  			his.Observe(int64(pcg.Uint32n(64)))
   293  		}
   294  		assert.Equal(b, his.Total(), 1000)
   295  		b.ReportAllocs()
   296  		b.ResetTimer()
   297  
   298  		for i := 0; i < b.N; i++ {
   299  			_ = his.Sum()
   300  		}
   301  	})
   302  
   303  	b.Run("Average", func(b *testing.B) {
   304  		his := new(Histogram)
   305  		for i := 0; i < 1000; i++ {
   306  			his.Observe(int64(pcg.Uint32n(64)))
   307  		}
   308  		assert.Equal(b, his.Total(), 1000)
   309  		b.ReportAllocs()
   310  		b.ResetTimer()
   311  
   312  		for i := 0; i < b.N; i++ {
   313  			_, _ = his.Average()
   314  		}
   315  	})
   316  
   317  	b.Run("Variance", func(b *testing.B) {
   318  		his := new(Histogram)
   319  		for i := 0; i < 1000; i++ {
   320  			his.Observe(int64(pcg.Uint32n(64)))
   321  		}
   322  		assert.Equal(b, his.Total(), 1000)
   323  		b.ReportAllocs()
   324  		b.ResetTimer()
   325  
   326  		for i := 0; i < b.N; i++ {
   327  			_, _, _ = his.Variance()
   328  		}
   329  	})
   330  
   331  	b.Run("Serialize", func(b *testing.B) {
   332  		h := new(Histogram)
   333  		for i := int64(0); i < 10000000; i++ {
   334  			r := int64(pcg.Uint32n(1000) + 500)
   335  			h.Observe(r)
   336  		}
   337  		buf := h.Serialize(nil)
   338  
   339  		b.SetBytes(int64(len(buf)))
   340  		b.ResetTimer()
   341  		b.ReportAllocs()
   342  
   343  		for i := 0; i < b.N; i++ {
   344  			h.Serialize(buf[:0])
   345  		}
   346  
   347  		b.ReportMetric(float64(len(buf)), "bytes")
   348  	})
   349  
   350  	b.Run("Load", func(b *testing.B) {
   351  		h := new(Histogram)
   352  		for i := int64(0); i < 10000000; i++ {
   353  			r := int64(pcg.Uint32n(1000) + 500)
   354  			h.Observe(r)
   355  		}
   356  		buf := h.Serialize(nil)
   357  
   358  		b.SetBytes(int64(len(buf)))
   359  		b.ResetTimer()
   360  		b.ReportAllocs()
   361  
   362  		for i := 0; i < b.N; i++ {
   363  			var h Histogram
   364  			_ = h.Load(buf)
   365  		}
   366  
   367  		b.ReportMetric(float64(len(buf)), "bytes")
   368  	})
   369  }