core/flow/tc_warm_up.go (92 lines of code) (raw):
// Copyright 1999-2020 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package flow
import (
"math"
"sync/atomic"
"github.com/alibaba/sentinel-golang/core/base"
"github.com/alibaba/sentinel-golang/core/config"
"github.com/alibaba/sentinel-golang/logging"
"github.com/alibaba/sentinel-golang/util"
)
type WarmUpTrafficShapingCalculator struct {
owner *TrafficShapingController
threshold float64
warmUpPeriodInSec uint32
coldFactor uint32
warningToken uint64
maxToken uint64
slope float64
storedTokens int64
lastFilledTime uint64
}
func (c *WarmUpTrafficShapingCalculator) BoundOwner() *TrafficShapingController {
return c.owner
}
func NewWarmUpTrafficShapingCalculator(owner *TrafficShapingController, rule *Rule) TrafficShapingCalculator {
if rule.WarmUpColdFactor <= 1 {
rule.WarmUpColdFactor = config.DefaultWarmUpColdFactor
logging.Warn("[NewWarmUpTrafficShapingCalculator] No set WarmUpColdFactor,use default warm up cold factor value", "defaultWarmUpColdFactor", config.DefaultWarmUpColdFactor)
}
warningToken := uint64((float64(rule.WarmUpPeriodSec) * rule.Threshold) / float64(rule.WarmUpColdFactor-1))
maxToken := warningToken + uint64(2*float64(rule.WarmUpPeriodSec)*rule.Threshold/float64(1.0+rule.WarmUpColdFactor))
slope := float64(rule.WarmUpColdFactor-1.0) / rule.Threshold / float64(maxToken-warningToken)
warmUpTrafficShapingCalculator := &WarmUpTrafficShapingCalculator{
owner: owner,
warmUpPeriodInSec: rule.WarmUpPeriodSec,
coldFactor: rule.WarmUpColdFactor,
warningToken: warningToken,
maxToken: maxToken,
slope: slope,
threshold: rule.Threshold,
storedTokens: 0,
lastFilledTime: 0,
}
return warmUpTrafficShapingCalculator
}
func (c *WarmUpTrafficShapingCalculator) CalculateAllowedTokens(_ uint32, _ int32) float64 {
metricReadonlyStat := c.BoundOwner().boundStat.readOnlyMetric
previousQps := metricReadonlyStat.GetPreviousQPS(base.MetricEventPass)
c.syncToken(previousQps)
restToken := atomic.LoadInt64(&c.storedTokens)
if restToken < 0 {
restToken = 0
}
if restToken >= int64(c.warningToken) {
aboveToken := restToken - int64(c.warningToken)
warningQps := math.Nextafter(1.0/(float64(aboveToken)*c.slope+1.0/c.threshold), math.MaxFloat64)
return warningQps
} else {
return c.threshold
}
}
func (c *WarmUpTrafficShapingCalculator) syncToken(passQps float64) {
currentTime := util.CurrentTimeMillis()
currentTime = currentTime - currentTime%1000
oldLastFillTime := atomic.LoadUint64(&c.lastFilledTime)
if currentTime <= oldLastFillTime {
return
}
oldValue := atomic.LoadInt64(&c.storedTokens)
newValue := c.coolDownTokens(currentTime, passQps)
if atomic.CompareAndSwapInt64(&c.storedTokens, oldValue, newValue) {
if currentValue := atomic.AddInt64(&c.storedTokens, int64(-passQps)); currentValue < 0 {
atomic.StoreInt64(&c.storedTokens, 0)
}
atomic.StoreUint64(&c.lastFilledTime, currentTime)
}
}
func (c *WarmUpTrafficShapingCalculator) coolDownTokens(currentTime uint64, passQps float64) int64 {
oldValue := atomic.LoadInt64(&c.storedTokens)
newValue := oldValue
// Prerequisites for adding a token:
// When token consumption is much lower than the warning line
if oldValue < int64(c.warningToken) {
newValue = int64(float64(oldValue) + (float64(currentTime)-float64(atomic.LoadUint64(&c.lastFilledTime)))*c.threshold/1000.0)
} else if oldValue > int64(c.warningToken) {
if passQps < float64(uint32(c.threshold)/c.coldFactor) {
newValue = int64(float64(oldValue) + float64(currentTime-atomic.LoadUint64(&c.lastFilledTime))*c.threshold/1000.0)
}
}
if newValue <= int64(c.maxToken) {
return newValue
} else {
return int64(c.maxToken)
}
}