github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/storage/segment/fuzz_test.go (about)

     1  package segment
     2  
     3  import (
     4  	"log"
     5  	"math/big"
     6  	"math/rand"
     7  	"sync"
     8  	"time"
     9  
    10  	. "github.com/onsi/ginkgo/v2"
    11  	. "github.com/onsi/gomega"
    12  
    13  	"github.com/pyroscope-io/pyroscope/pkg/testing"
    14  )
    15  
    16  type datapoint struct {
    17  	t       time.Time
    18  	samples uint64
    19  	r       *big.Rat
    20  }
    21  
    22  type storageMock struct {
    23  	resolution time.Duration
    24  	data       []datapoint
    25  }
    26  
    27  func newMock(resolution time.Duration) *storageMock {
    28  	return &storageMock{
    29  		resolution: resolution,
    30  		data:       []datapoint{},
    31  	}
    32  }
    33  
    34  func (sm *storageMock) Put(st, et time.Time, samples uint64) {
    35  	st, et = normalize(st, et)
    36  	fullDur := et.Sub(st) / sm.resolution
    37  	for t := st; t.Before(et); t = t.Add(sm.resolution) {
    38  		d := datapoint{
    39  			t:       t,
    40  			samples: samples,
    41  			r:       big.NewRat(int64(samples), int64(fullDur)),
    42  		}
    43  
    44  		sm.data = append(sm.data, d)
    45  	}
    46  }
    47  
    48  func (sm *storageMock) Get(st, et time.Time, cb func(depth int, samples, writes uint64, t time.Time, r *big.Rat)) {
    49  	st, et = normalize(st, et)
    50  	for _, d := range sm.data {
    51  		if !d.t.Before(st) && !d.t.Add(sm.resolution).After(et) {
    52  			cb(0, 1, 1, d.t, d.r)
    53  		}
    54  	}
    55  }
    56  
    57  // if you change something in this test make sure it doesn't change test coverage.
    58  func fuzzTest(testWrites bool, writeSize func() int) {
    59  	s := New()
    60  	m := newMock(10 * time.Second)
    61  
    62  	r := rand.New(rand.NewSource(1213))
    63  
    64  	for k := 0; k < 20; k++ {
    65  		maxStartTime := r.Intn(5000)
    66  		// for i := 0; i < 10; i++ {
    67  		for i := 0; i < r.Intn(200); i++ {
    68  			sti := r.Intn(maxStartTime) * 10
    69  			st := testing.SimpleTime(sti)
    70  			et := testing.SimpleTime(sti + writeSize())
    71  			dur := et.Sub(st)
    72  
    73  			// samples := uint64(1+r.Intn(10)) * uint64(dur/(10*time.Second))
    74  			samples := uint64(20)
    75  
    76  			m.Put(st, et, samples)
    77  			s.Put(st, et, samples, func(depth int, t time.Time, r *big.Rat, addons []Addon) {
    78  				log.Println(depth, r, dur)
    79  			})
    80  		}
    81  		mSum := big.NewRat(0, 1)
    82  		mWrites := big.NewRat(0, 1)
    83  		sSum := big.NewRat(0, 1)
    84  		sWrites := big.NewRat(0, 1)
    85  		for i := 0; i < r.Intn(100); i++ {
    86  			sti := r.Intn(100) * 10
    87  			st := testing.SimpleTime(sti)
    88  			et := testing.SimpleTime(sti + r.Intn(100)*10)
    89  
    90  			m.Get(st, et, func(depth int, samples, writes uint64, t time.Time, r *big.Rat) {
    91  				rClone := big.NewRat(r.Num().Int64(), r.Denom().Int64())
    92  				mSum.Add(mSum, rClone.Mul(rClone, big.NewRat(int64(samples), 1)))
    93  				log.Println("mWrites", samples, writes, r)
    94  				// if r.Num().Int64() > 0 {
    95  				// r = r.Inv(r)
    96  				w := big.NewRat(int64(writes), 1)
    97  				// mWrites.Add(mWrites, r.Mul(r, w))
    98  				mWrites.Add(mWrites, w)
    99  				// }
   100  			})
   101  
   102  			s.Get(st, et, func(depth int, samples, writes uint64, t time.Time, r *big.Rat) {
   103  				rClone := big.NewRat(r.Num().Int64(), r.Denom().Int64())
   104  				sSum.Add(sSum, rClone.Mul(rClone, big.NewRat(int64(samples), 1)))
   105  				log.Println("sWrites", samples, writes, r)
   106  				// if r.Num().Int64() > 0 {
   107  				// r = r.Inv(r)
   108  				w := big.NewRat(int64(writes), 1)
   109  				// sWrites.Add(sWrites, r.Mul(r, w))
   110  				sWrites.Add(sWrites, w)
   111  				// }
   112  			})
   113  		}
   114  		mSumF, _ := mSum.Float64()
   115  		mWritesF, _ := mWrites.Float64()
   116  		log.Println("m:", mSum, mSumF, mWrites, mWritesF)
   117  
   118  		sSumF, _ := sSum.Float64()
   119  		sWritesF, _ := sWrites.Float64()
   120  		log.Println("s:", sSum, sSumF, sWrites, sWritesF)
   121  
   122  		Expect(mSum.Cmp(sSum)).To(Equal(0))
   123  		if testWrites {
   124  			Expect(mWrites.Cmp(sWrites)).To(Equal(0))
   125  		}
   126  	}
   127  }
   128  
   129  // See https://github.com/pyroscope-io/pyroscope/issues/28 for more context
   130  var _ = Describe("segment", func() {
   131  	Context("fuzz tests", func() {
   132  		Context("writes are 10 second long", func() {
   133  			It("works as expected", func() {
   134  				done := make(chan interface{})
   135  				go func() {
   136  					fuzzTest(true, func() int {
   137  						return 10
   138  					})
   139  					close(done)
   140  				}()
   141  				Eventually(done, 5).Should(BeClosed())
   142  			})
   143  		})
   144  		Context("writes are different lengths", func() {
   145  			It("works as expected", func() {
   146  				done := make(chan interface{})
   147  				go func() {
   148  					fuzzTest(false, func() int {
   149  						return 20
   150  						// return 1 + rand.Intn(10)*10
   151  					})
   152  					close(done)
   153  				}()
   154  				Eventually(done, 5).Should(BeClosed())
   155  			})
   156  		})
   157  		Context("retention and sampling randomized test", func() {
   158  			It("works as expected", func() {
   159  				var (
   160  					seed = 7332
   161  					n    = 1
   162  					wg   sync.WaitGroup
   163  				)
   164  				wg.Add(n)
   165  				for i := 0; i < n; i++ {
   166  					go func(i int) {
   167  						fuzzDeleteNodesBefore(seed + i)
   168  						wg.Done()
   169  					}(i)
   170  				}
   171  				wg.Wait()
   172  			})
   173  		})
   174  	})
   175  })
   176  
   177  func fuzzDeleteNodesBefore(seed int) {
   178  	defer GinkgoRecover()
   179  
   180  	s := New()
   181  	r := rand.New(rand.NewSource(int64(seed)))
   182  	w := testSegWriter{
   183  		n: 10e3, // Number of writes
   184  		r: r,
   185  
   186  		samplesPerWrite:  100,
   187  		writeTimeSpanSec: 10,
   188  		startTimeMin:     randInt(1000, 3000),
   189  		startTimeMax:     randInt(7000, 100000),
   190  
   191  		buckets: make([]*bucket, 10),
   192  	}
   193  
   194  	w.write(s)
   195  
   196  	for _, b := range w.buckets {
   197  		// Delete samples that fall within the time span of the bucket.
   198  		removed, err := s.DeleteNodesBefore(&RetentionPolicy{AbsoluteTime: b.time})
   199  		Expect(err).ToNot(HaveOccurred())
   200  		Expect(removed).To(BeFalse())
   201  		// Ensure we have removed expected number of samples from the segment.
   202  		samples, writes := totalSamplesWrites(s, time.Time{}, testing.SimpleTime(w.startTimeMax*10))
   203  		Expect(samples).To(Equal(b.samples))
   204  		Expect(writes).To(Equal(b.writes))
   205  		// Ensure no samples left outside the retention period.
   206  		samples, writes = totalSamplesWrites(s, b.time, testing.SimpleTime(w.startTimeMax*10))
   207  		Expect(samples).To(Equal(b.samples))
   208  		Expect(writes).To(Equal(b.writes))
   209  	}
   210  
   211  	st := testing.SimpleTime(w.startTimeMax * 10)
   212  	samples, writes := totalSamplesWrites(s, st, st.Add(time.Hour))
   213  	Expect(samples).To(BeZero())
   214  	Expect(writes).To(BeZero())
   215  }
   216  
   217  // testSegWriter inserts randomized data into the segment recording the
   218  // samples distribution by time. Every bucket indicates the number of
   219  // writes and samples that had been written before the bucket time mark.
   220  type testSegWriter struct {
   221  	r *rand.Rand
   222  	n int
   223  
   224  	samplesPerWrite  int
   225  	writeTimeSpanSec int
   226  	expectedWrites   int
   227  
   228  	startTimeMin int
   229  	startTimeMax int
   230  
   231  	buckets []*bucket
   232  }
   233  
   234  type bucket struct {
   235  	time    time.Time
   236  	samples int
   237  	writes  int
   238  }
   239  
   240  func (f testSegWriter) putStartEndTime() (st time.Time, et time.Time) {
   241  	st = testing.SimpleTime(randInt(f.startTimeMin, f.startTimeMax) * 10)
   242  	et = st.Add(time.Second * time.Duration(f.writeTimeSpanSec))
   243  	return st, et
   244  }
   245  
   246  func randInt(min, max int) int { return rand.Intn(max-min) + min }
   247  
   248  func (f testSegWriter) expectedSamples() int { return f.n * f.samplesPerWrite }
   249  
   250  func (f testSegWriter) write(s *Segment) {
   251  	// Initialize time buckets, if required: the whole time
   252  	// span is divided proportionally to the number of buckets.
   253  	if len(f.buckets) > 0 {
   254  		step := (f.startTimeMax - f.startTimeMin) / len(f.buckets) * 10
   255  		for i := 0; i < len(f.buckets); i++ {
   256  			f.buckets[i] = &bucket{time: testing.SimpleTime(f.startTimeMin + step*i)}
   257  		}
   258  	}
   259  	for i := 0; i < f.n; i++ {
   260  		st, et := f.putStartEndTime()
   261  		err := s.Put(st, et, uint64(f.samplesPerWrite), putNoOp)
   262  		Expect(err).ToNot(HaveOccurred())
   263  		for _, b := range f.buckets {
   264  			if et.After(b.time) {
   265  				b.samples += f.samplesPerWrite
   266  				b.writes++
   267  			}
   268  		}
   269  	}
   270  }
   271  
   272  func totalSamplesWrites(s *Segment, st, et time.Time) (samples, writes int) {
   273  	v := big.NewRat(0, 1)
   274  	s.Get(st, et, func(depth int, s, w uint64, t time.Time, r *big.Rat) {
   275  		x := big.NewRat(r.Num().Int64(), r.Denom().Int64())
   276  		v.Add(v, x.Mul(x, big.NewRat(int64(s), 1)))
   277  		writes += int(w)
   278  	})
   279  	return int(v.Num().Int64()), writes
   280  }