in sort/pid_controller.go [269:386]
func (p *PIDController) Compute(itemOrExpId string, ctx *context.RecommendContext) (float64, float64) {
if !p.online {
ctx.LogDebug(fmt.Sprintf("module=PIDController\t<taskId:%s/targetId:%s>[targetName:%s] offline",
p.task.TrafficControlTaskId, p.target.TrafficControlTargetId, p.target.Name))
return 0, 0
}
var status = p.getPIDStatus(itemOrExpId)
status.mu.Lock()
defer status.mu.Unlock()
isPercentageTask := p.task.ControlType == constants.TrafficControlTaskControlTypePercent
measure := status.GetMeasurement()
if isPercentageTask && measure > 1.0 {
ctx.LogError(fmt.Sprintf("module=PIDController\tinvalid traffic percentage <taskId:%s/targetId:%s>[targetName:%s] value=%f",
p.task.TrafficControlTaskId, p.target.TrafficControlTargetId, p.target.Name, measure))
return 0, 0
}
if measure == 0 && !p.runWithZeroInput {
ctx.LogDebug(fmt.Sprintf("module=PIDController\t<taskId:%s/targetId:%s>[targetName:%s] input value is 0",
p.task.TrafficControlTaskId, p.target.TrafficControlTargetId, p.target.Name))
return 0, 0
}
var setValue float64
if isPercentageTask || time.Now().Sub(status.lastTime) < time.Duration(30)*time.Second {
setValue = status.setValue
} else {
value, enabled := p.getTargetSetValue()
if !enabled {
ctx.LogDebug(fmt.Sprintf("module=PIDController\t<taskId:%s/targetId:%s>[targetName:%s] disable",
p.task.TrafficControlTaskId, p.target.TrafficControlTargetId, p.target.Name))
return 0, value
}
if value < 1 {
setValue = 1
} else {
setValue = value
}
}
if p.task.ControlLogic == constants.TrafficControlTaskControlLogicGuaranteed {
// 调控类型为保量,并且当前时刻目标已达成的情况下,直接返回 0
if isPercentageTask {
if measure >= (setValue / 100) {
ctx.LogDebug(fmt.Sprintf("module=PIDController\t<taskId:%s/targetId:%s>[targetName:%s] item_or_exp=%s, measure=%.6f, achieved",
p.task.TrafficControlTaskId, p.target.TrafficControlTargetId, p.target.Name, itemOrExpId, measure))
return 0, setValue
}
} else {
if measure >= setValue {
ctx.LogDebug(fmt.Sprintf("module=PIDController\t<taskId:%s/targetId:%s>[targetName:%s] item_or_exp=%s, measure=%.2f, achieved",
p.task.TrafficControlTaskId, p.target.TrafficControlTargetId, p.target.Name, itemOrExpId, measure))
return 0, setValue
}
}
}
// 处理死控区域
if isPercentageTask {
delta := float64(p.target.ToleranceValue) / 100
if delta == 0 { // 添加死区控制:微小误差时不调整
delta = 0.001
}
if BetweenSlackInterval(measure, setValue/100, delta) {
// when current input is between `setValue` and `setValue+SetVale Range`, turn off controller
ctx.LogDebug(fmt.Sprintf("module=PIDController\t<taskId:%s/targetId:%s>[targetName:%s] item_or_exp=%s, between slack interval",
p.task.TrafficControlTaskId, p.target.TrafficControlTargetId, p.target.Name, itemOrExpId))
return 0, setValue
}
} else {
delta := float64(p.target.ToleranceValue)
if delta == 0 { // 添加死区控制:微小误差时不调整
delta = setValue * 0.01
}
if BetweenSlackInterval(measure, setValue, delta) {
ctx.LogDebug(fmt.Sprintf("module=PIDController\t<taskId:%s/targetId:%s>[targetName:%s] item_or_exp=%s, between slack interval",
p.task.TrafficControlTaskId, p.target.TrafficControlTargetId, p.target.Name, itemOrExpId))
return 0, setValue
}
}
if p.freezeMinutes > 0 {
// 流量占比型任务凌晨刚开始的时候流量占比统计值不置信,直接输出前一天最后一次的调控信号
location, err := time.LoadLocation("Asia/Shanghai") // 北京采用Asia/Shanghai时区
if err != nil { // 如果无法加载时区,默认使用本地时区
location = time.Local
}
// 获取当前时间
now := time.Now().In(location)
// 获取当天的零点时间
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, location)
// 计算当前时间与零点的差值
durationSinceStartOfDay := int(now.Sub(startOfDay).Minutes())
if durationSinceStartOfDay < p.freezeMinutes {
ctx.LogDebug(fmt.Sprintf("module=PIDController\titemIdOrExpId=%s\texit within the freezing time", itemOrExpId))
return status.lastOutput, setValue
}
}
var currentError float64
if isPercentageTask {
currentError = setValue/100.0 - measure
} else {
currentError = 1.0 - measure/setValue
}
pTerm := p.kp * currentError
dTerm := p.kd * status.derivative
var iTerm float64
if status.integralActive { // 积分项(根据激活状态决定是否使用)
iTerm = p.ki * status.integral
}
status.lastOutput = pTerm + iTerm + dTerm
if status.lastOutput == 0 {
ctx.LogDebug(fmt.Sprintf("module=PIDController\t<taskId:%s/targetId:%s>[targetName:%s] item_or_exp=%s, measure=%f, err=%.6f, output=0",
p.task.TrafficControlTaskId, p.target.TrafficControlTargetId, p.target.Name, itemOrExpId, measure, currentError))
}
return status.lastOutput, setValue
}