diff --git a/gmeasure/experiment.go b/gmeasure/experiment.go index 6081ca331..eddb58ce0 100644 --- a/gmeasure/experiment.go +++ b/gmeasure/experiment.go @@ -53,14 +53,16 @@ import ( SamplingConfig configures the Sample family of experiment methods. These methods invoke passed-in functions repeatedly to sample and record a given measurement. SamplingConfig is used to control the maximum number of samples or time spent sampling (or both). When both are specified sampling ends as soon as one of the conditions is met. -SamplingConfig can also enable concurrent sampling. +SamplingConfig can also ensure a minimum interval between samples and can enable concurrent sampling. */ type SamplingConfig struct { // N - the maximum number of samples to record N int // Duration - the maximum amount of time to spend recording samples Duration time.Duration - // NumParallel - the number of parallel workers to spin up to record samples. + // MinSamplingInterval - the minimum time that must elapse between samplings. It is an error to specify both MinSamplingInterval and NumParallel. + MinSamplingInterval time.Duration + // NumParallel - the number of parallel workers to spin up to record samples. It is an error to specify both MinSamplingInterval and NumParallel. NumParallel int } @@ -445,6 +447,9 @@ func (e *Experiment) Sample(callback func(idx int), samplingConfig SamplingConfi if samplingConfig.N == 0 && samplingConfig.Duration == 0 { panic("you must specify at least one of SamplingConfig.N and SamplingConfig.Duration") } + if samplingConfig.MinSamplingInterval > 0 && samplingConfig.NumParallel > 1 { + panic("you cannot specify both SamplingConfig.MinSamplingInterval and SamplingConfig.NumParallel") + } maxTime := time.Now().Add(100000 * time.Hour) if samplingConfig.Duration > 0 { maxTime = time.Now().Add(samplingConfig.Duration) @@ -457,6 +462,7 @@ func (e *Experiment) Sample(callback func(idx int), samplingConfig SamplingConfi if samplingConfig.NumParallel > numParallel { numParallel = samplingConfig.NumParallel } + minSamplingInterval := samplingConfig.MinSamplingInterval work := make(chan int) if numParallel > 1 { @@ -479,6 +485,10 @@ func (e *Experiment) Sample(callback func(idx int), samplingConfig SamplingConfi callback(idx) } dt := time.Since(t) + if numParallel == 1 && dt < minSamplingInterval { + time.Sleep(minSamplingInterval - dt) + dt = time.Since(t) + } if idx >= numParallel { avgDt = (avgDt*time.Duration(idx-numParallel) + dt) / time.Duration(idx-numParallel+1) } diff --git a/gmeasure/experiment_test.go b/gmeasure/experiment_test.go index 13014ef6c..96e60249d 100644 --- a/gmeasure/experiment_test.go +++ b/gmeasure/experiment_test.go @@ -206,12 +206,23 @@ var _ = Describe("Experiment", func() { e.Sample(func(idx int) { indices = append(indices, idx) time.Sleep(10 * time.Millisecond) - }, gmeasure.SamplingConfig{N: 100, Duration: 100 * time.Millisecond}) + }, gmeasure.SamplingConfig{N: 100, Duration: 100 * time.Millisecond, MinSamplingInterval: 5 * time.Millisecond}) Ω(len(indices)).Should(BeNumerically("~", 10, 3)) Ω(indices).Should(Equal(ints(len(indices)))) }) + It("can ensure a minimum interval between samples", func() { + times := map[int]time.Time{} + e.Sample(func(idx int) { + times[idx] = time.Now() + }, gmeasure.SamplingConfig{N: 10, Duration: 200 * time.Millisecond, MinSamplingInterval: 50 * time.Millisecond, NumParallel: 1}) + + Ω(len(times)).Should(BeNumerically("~", 4, 2)) + Ω(times[1]).Should(BeTemporally(">", times[0], 50*time.Millisecond)) + Ω(times[2]).Should(BeTemporally(">", times[1], 50*time.Millisecond)) + }) + It("can run samples in parallel", func() { lock := &sync.Mutex{} @@ -228,11 +239,17 @@ var _ = Describe("Experiment", func() { Ω(indices).Should(ConsistOf(ints(len(indices)))) }) - It("panics if the SamplingConfig is misconfigured", func() { + It("panics if the SamplingConfig does not specify a ceiling", func() { Expect(func() { - e.Sample(func(_ int) {}, gmeasure.SamplingConfig{}) + e.Sample(func(_ int) {}, gmeasure.SamplingConfig{MinSamplingInterval: time.Second}) }).To(PanicWith("you must specify at least one of SamplingConfig.N and SamplingConfig.Duration")) }) + + It("panics if the SamplingConfig includes both a minimum interval and a directive to run in parallel", func() { + Expect(func() { + e.Sample(func(_ int) {}, gmeasure.SamplingConfig{N: 10, MinSamplingInterval: time.Second, NumParallel: 2}) + }).To(PanicWith("you cannot specify both SamplingConfig.MinSamplingInterval and SamplingConfig.NumParallel")) + }) }) Describe("recording multiple entries", func() {