blob: 3589162bf0bd52e11f1adc2584c38998bce5ae09 [file]
package dfbuilder
import (
"math"
"go.skia.org/infra/perf/go/types"
)
const (
maxGrowthExponent = 10
// windowCalculatorSafetyMultiplier is the safety buffer multiplier applied to the
// estimated commits needed to find the remaining data points in the calculator.
windowCalculatorSafetyMultiplier = 1.5
)
// commitWindowCalculator manages sliding commit window boundaries, exponential growth, and step-size density estimation.
type commitWindowCalculator struct {
tileSize int32
maxSearchSteps int
endIndex types.CommitNumber
beginIndex types.CommitNumber
prevScannedRange int32
consecutiveZeroSteps int
steps int
numStepsNoData int
targetN int32
totalFound int32
}
func newCommitWindowCalculator(endIndex types.CommitNumber, targetN int32, tileSize int32, maxSearchSteps int) *commitWindowCalculator {
beginIndex := endIndex.Add(-(tileSize - 1))
if beginIndex < 0 {
beginIndex = 0
}
return &commitWindowCalculator{
tileSize: tileSize,
maxSearchSteps: maxSearchSteps,
endIndex: endIndex,
beginIndex: beginIndex,
prevScannedRange: int32(endIndex - beginIndex + 1),
targetN: targetN,
steps: 1,
}
}
// CurrentBounds returns the current beginIndex and endIndex.
func (c *commitWindowCalculator) CurrentBounds() (types.CommitNumber, types.CommitNumber) {
return c.beginIndex, c.endIndex
}
// ShouldStop checks if the search should terminate (target achieved, out of bounds, or too many empty steps).
func (c *commitWindowCalculator) ShouldStop() bool {
if c.totalFound >= c.targetN {
return true
}
if c.endIndex < 0 {
return true
}
if c.numStepsNoData > c.maxSearchSteps {
return true
}
return false
}
// RecordEmptyStep grows the window exponentially when no commits/traces are found.
func (c *commitWindowCalculator) RecordEmptyStep() {
c.numStepsNoData++
c.consecutiveZeroSteps++
if c.consecutiveZeroSteps > maxGrowthExponent {
c.consecutiveZeroSteps = maxGrowthExponent
}
growthFactor := int(math.Pow(2, float64(c.consecutiveZeroSteps)))
nextScanSize := float64(c.tileSize) * float64(growthFactor)
searchTiles := (int(nextScanSize) + int(c.tileSize-1)) / int(c.tileSize)
nextScanSize = float64(searchTiles * int(c.tileSize))
c.steps++
c.endIndex = c.beginIndex - 1
c.beginIndex = c.endIndex - types.CommitNumber(nextScanSize) + 1
if c.beginIndex < 0 {
c.beginIndex = 0
}
c.prevScannedRange = int32(c.endIndex - c.beginIndex + 1)
}
// RecordSuccessStep dynamically adjusts the next scan size based on observed density.
func (c *commitWindowCalculator) RecordSuccessStep(commitsWithData int) {
c.totalFound += int32(commitsWithData)
c.consecutiveZeroSteps = 0
c.steps++
if c.totalFound >= c.targetN {
return
}
// Calculate density of commits with actual data points
density := float64(commitsWithData) / float64(c.prevScannedRange)
if density < 0.01 {
density = 0.01
}
neededCommits := c.targetN - c.totalFound
estimatedCommits := float64(neededCommits) / density
nextScanSize := estimatedCommits * windowCalculatorSafetyMultiplier
searchTiles := (int(nextScanSize) + int(c.tileSize-1)) / int(c.tileSize)
nextScanSize = float64(searchTiles * int(c.tileSize))
c.endIndex = c.beginIndex - 1
c.beginIndex = c.endIndex - types.CommitNumber(nextScanSize) + 1
if c.beginIndex < 0 {
c.beginIndex = 0
}
c.prevScannedRange = int32(c.endIndex - c.beginIndex + 1)
}