github.com/insolar/vanilla@v0.0.0-20201023172447-248fdf805322/ratelimiter/calc.go (about)

     1  // Copyright 2020 Insolar Network Ltd.
     2  // All rights reserved.
     3  // This material is licensed under the Insolar License version 1.0,
     4  // available at https://github.com/insolar/assured-ledger/blob/master/LICENSE.md.
     5  
     6  package ratelimiter
     7  
     8  import (
     9  	"math"
    10  	"math/bits"
    11  
    12  	"github.com/insolar/vanilla/args"
    13  	"github.com/insolar/vanilla/throw"
    14  )
    15  
    16  func BucketConfigByThroughput(bytePerS int, refillUnit uint64, burst int, quantum uint32) BucketConfig {
    17  	if bytePerS <= 0 {
    18  		return BucketConfig{
    19  			RefillAmount: math.MaxUint32,
    20  			Quantum:      quantum,
    21  			MaxAmount:    math.MaxUint32,
    22  		}
    23  	}
    24  
    25  	refillAmount := uint64(bytePerS) / refillUnit
    26  	if refillAmount == 0 {
    27  		refillAmount = 1
    28  	}
    29  	if burst <= 0 {
    30  		burst = 1
    31  	}
    32  	maxAmount := (refillAmount*uint64(burst) + uint64(quantum) - 1) / uint64(quantum)
    33  	maxAmount *= uint64(quantum)
    34  	if maxAmount > math.MaxUint32 {
    35  		maxAmount = math.MaxUint32
    36  	}
    37  
    38  	return BucketConfig{
    39  		RefillAmount: uint32(refillAmount),
    40  		Quantum:      quantum,
    41  		MaxAmount:    uint32(maxAmount),
    42  	}
    43  }
    44  
    45  func ThroughputQuantizer(quantum uint32, samplesPerSecond uint64, limits ...int) (increment uint32, refillUnit uint64, scaleUnit uint32) {
    46  	switch max := maxLimit(0, limits...); {
    47  	case max == 0:
    48  		return 0, 1, 1 // no limits
    49  	case max > math.MaxUint32:
    50  		scaleUnit = 1 << bits.Len64(uint64(max)/math.MaxUint32)
    51  	default:
    52  		scaleUnit = 1
    53  	}
    54  
    55  	min := minLimit(math.MaxInt64, int64(quantum), limits...)
    56  	switch {
    57  	case min == math.MaxInt64:
    58  		return 0, 1, 1 // no limits
    59  	case min <= 0:
    60  		panic(throw.Impossible())
    61  	case len(limits) > 1:
    62  		if gcd := uint64(args.GCDListInt(int(samplesPerSecond)<<2, limits[0], limits[1:]...)); gcd >= samplesPerSecond {
    63  			min = int64(gcd)
    64  		}
    65  	}
    66  
    67  	minSamples := uint64(min) / uint64(scaleUnit)
    68  	minSamples /= samplesPerSecond
    69  
    70  	bitExtra := 0
    71  	if minSamples > 1 {
    72  		bitExtra = bits.Len64(minSamples - 1)
    73  	}
    74  	if bitExtra < 3 {
    75  		return 1, samplesPerSecond * uint64(scaleUnit), scaleUnit
    76  	}
    77  	bitExtra -= 2
    78  	if bitExtra > 8 {
    79  		bitExtra = 8
    80  	}
    81  	return 1 << bitExtra, (samplesPerSecond << bitExtra) * uint64(scaleUnit), scaleUnit
    82  }
    83  
    84  func minLimit(v, lowCut int64, vv ...int) int64 {
    85  	for i := range vv {
    86  		vvv := int64(vv[i])
    87  		switch {
    88  		case vvv <= 0:
    89  			continue
    90  		case vvv < lowCut:
    91  			vvv = lowCut
    92  		}
    93  		if v > vvv {
    94  			v = vvv
    95  		}
    96  	}
    97  	return v
    98  }
    99  
   100  func maxLimit(v int64, vv ...int) int64 {
   101  	for i := range vv {
   102  		vvv := int64(vv[i])
   103  		switch {
   104  		case vvv <= 0:
   105  			continue
   106  		case vvv > v:
   107  			v = vvv
   108  		}
   109  	}
   110  	return v
   111  }
   112  
   113  func quantizeCeiling(v, quantum uint32) uint32 {
   114  	if v < quantum {
   115  		return quantum
   116  	}
   117  	a := (uint64(v) + uint64(quantum) - 1) / uint64(quantum)
   118  	a *= uint64(quantum)
   119  	if a > math.MaxUint32 {
   120  		return uint32(a - uint64(quantum))
   121  	}
   122  	return uint32(a)
   123  }
   124  
   125  func quantizeFloor(v, quantum uint32) uint32 {
   126  	if v < quantum {
   127  		return 0
   128  	}
   129  	a := uint64(v) / uint64(quantum)
   130  	return uint32(a * uint64(quantum))
   131  }