diff --git a/cmix/attempts/histrogram.go b/cmix/attempts/histrogram.go index 52bf207a48345818f0f1900d5c69b45e0259d7d8..0b6955dea3239edecd9f598191751edb9c315c63 100644 --- a/cmix/attempts/histrogram.go +++ b/cmix/attempts/histrogram.go @@ -1,16 +1,22 @@ package attempts import ( + "fmt" "sort" + "strconv" + "strings" "sync" "sync/atomic" ) -const maxHistogramSize = 100 -const minElements = 3 -const percentileNumerator = 66 -const percentileDenominator = 99 -const percentileDenominatorOffset = 49 +const ( + maxHistogramSize = 100 + minElements = 3 + percentileNumerator = 66 + percentileDenominator = 99 + percentileDenominatorOffset = 49 + optimalAttemptsInitValue = -1 +) type SendAttemptTracker interface { SubmitProbeAttempt(numAttemptsUntilSuccessful int) @@ -18,32 +24,32 @@ type SendAttemptTracker interface { } type sendAttempts struct { - lock sync.Mutex - numAttempts []int - currentIndex int - isFull bool - optimalAttempts *int32 + isFull bool + currentIndex int + numAttempts []int + lock sync.Mutex } func NewSendAttempts() SendAttemptTracker { - optimalAttempts := int32(-1) - + optimalAttempts := int32(optimalAttemptsInitValue) sa := &sendAttempts{ - numAttempts: make([]int, maxHistogramSize), - currentIndex: 0, - isFull: false, optimalAttempts: &optimalAttempts, + isFull: false, + currentIndex: 0, + numAttempts: make([]int, maxHistogramSize), } + return sa } -func (sa *sendAttempts) SubmitProbeAttempt(a int) { +func (sa *sendAttempts) SubmitProbeAttempt(numAttemptsUntilSuccessful int) { sa.lock.Lock() defer sa.lock.Unlock() - sa.numAttempts[sa.currentIndex] = a - sa.currentIndex += 1 + sa.numAttempts[sa.currentIndex] = numAttemptsUntilSuccessful + sa.currentIndex++ + if sa.currentIndex == len(sa.numAttempts) { sa.currentIndex = 0 sa.isFull = true @@ -55,7 +61,7 @@ func (sa *sendAttempts) SubmitProbeAttempt(a int) { func (sa *sendAttempts) GetOptimalNumAttempts() (attempts int, ready bool) { optimalAttempts := atomic.LoadInt32(sa.optimalAttempts) - if optimalAttempts == -1 { + if optimalAttempts == optimalAttemptsInitValue { return 0, false } @@ -69,16 +75,28 @@ func (sa *sendAttempts) computeOptimalUnsafe() { return } toCopy = sa.currentIndex - } - histoCopy := make([]int, toCopy) - copy(histoCopy, sa.numAttempts[:toCopy]) - - sort.Slice(histoCopy, func(i, j int) bool { - return histoCopy[i] < histoCopy[j] - }) + histogramCopy := make([]int, toCopy) + copy(histogramCopy, sa.numAttempts[:toCopy]) + sort.Ints(histogramCopy) - optimal := histoCopy[((toCopy*percentileNumerator)+percentileDenominatorOffset)/percentileDenominator] + i := ((toCopy * percentileNumerator) + percentileDenominatorOffset) / + percentileDenominator + optimal := histogramCopy[i] atomic.StoreInt32(sa.optimalAttempts, int32(optimal)) } + +// String prints the values in the sendAttempts in a human-readable form for +// debugging and logging purposes. This function adheres to the fmt.Stringer +// interface. +func (sa *sendAttempts) String() string { + fields := []string{ + "optimalAttempts:" + strconv.Itoa(int(atomic.LoadInt32(sa.optimalAttempts))), + "isFull:" + strconv.FormatBool(sa.isFull), + "currentIndex:" + strconv.Itoa(sa.currentIndex), + "numAttempts:" + fmt.Sprintf("%d", sa.numAttempts), + } + + return "{" + strings.Join(fields, " ") + "}" +}