Skip to content

Commit

Permalink
gmeasure.SamplingConfig now suppers a MinSamplingInterval
Browse files Browse the repository at this point in the history
When specified, gmeasure will ensure that samples are separated by a duration of at least MinSamplingInterval.
  • Loading branch information
onsi committed Jul 4, 2021
1 parent 2f04e6e commit e94dbca
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 5 deletions.
14 changes: 12 additions & 2 deletions gmeasure/experiment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand All @@ -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)
}
Expand Down
23 changes: 20 additions & 3 deletions gmeasure/experiment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}

Expand All @@ -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() {
Expand Down

0 comments on commit e94dbca

Please sign in to comment.