func (lbs *LoadBasedAlg) getRebalanceThreshold() int {
	lbs.config.rebalanceThresholdMu.RLock()
	defer lbs.config.rebalanceThresholdMu.RUnlock()
	return lbs.config.rebalanceThreshold
}
getRebalanceThreshold get the rebalance threshold
package server
import (
	"fmt"
	"math"
	"regexp"
	"sort"
	"sync"
	"time"
	log "github.com/sirupsen/logrus"
	"github.com/twitter/scoot/common/stats"
	"github.com/twitter/scoot/scheduler/domain"
)
const (
	under int = iota
	over
)
var (
	DefaultLoadBasedSchedulerClassPercents = map[string]int32{
		"land":       40,
		"diff":       25,
		"sandbox":    10,
		"regression": 17,
		"ktf":        3,
		"coverage":   2,
		"tryout":     2,
		"unknown":    1,
	}
	DefaultRequestorToClassMap = map[string]string{
		"land.*":       "land",
		"diff.*":       "diff",
		"sandbox.*":    "sandbox",
		"regression.*": "regression",
		"CI.*":         "regression",
		"jenkins.*":    "ktf",
		"ktf.*":        "ktf",
		"coverage.*":   "coverage",
		"tryout.*":     "tryout",
	}
	DefaultMinRebalanceTime = time.Duration(4 * time.Minute)
	MaxTaskDuration         = time.Duration(4 * time.Hour)
)
type LoadBasedAlgConfig struct {
	classLoadPercents       map[string]int
	classLoadPercentsMu     sync.RWMutex
	requestorReToClassMap   map[string]string
	requestorReToClassMapMU sync.RWMutex
	rebalanceThreshold     int
	rebalanceThresholdMu   sync.RWMutex
	rebalanceMinDuration   time.Duration
	rebalanceMinDurationMu sync.RWMutex
	classByDescLoadPct []string
	stat stats.StatsReceiver
}
type LoadBasedAlg struct {
	config     *LoadBasedAlgConfig
	jobClasses map[string]*jobClass
	totalUnusedEntitlement          int
	exceededRebalanceThresholdStart time.Time
	classLoadPercents     map[string]int
	requestorReToClassMap map[string]string
	classByDescLoadPct             []string
	tasksByJobClassAndStartTimeSec map[taskClassAndStartKey]taskStateByJobIDTaskID
	nopAllocationsCnt int  
}
func NewLoadBasedAlg(config *LoadBasedAlgConfig, tasksByJobClassAndStartTimeSec map[taskClassAndStartKey]taskStateByJobIDTaskID) *LoadBasedAlg {
	lbs := &LoadBasedAlg{
		config:                          config,
		jobClasses:                      map[string]*jobClass{},
		exceededRebalanceThresholdStart: time.Time{},
		tasksByJobClassAndStartTimeSec:  tasksByJobClassAndStartTimeSec,
	}
	return lbs
}
type jobWaitingTasks struct {
	jobState     *jobState
	waitingTasks []*taskState
}
type jobClass struct {
	className string
	jobsByNumRunningTasks map[int][]jobWaitingTasks
	maxTaskRunningMapIndex int
	origNumWaitingTasks int
	origNumRunningTasks int
	origTargetLoadPct int
	origNumTargetedWorkers int
	numTasksToStart int
	numWaitingTasks int
	tempEntitlement int
	tempNormalizedPct int
}
func (jc *jobClass) String() string {
	return fmt.Sprintf("%s:TargetLoadPct:%d, origTasksWaiting:%d, origTasksRunning:%d, origTargetWorkers:%d, TasksToStart:%d, remainingWaitingTasks:%d, tempEntitlement:%d, tempNormalizedPct:%d",
		jc.className,
		jc.origTargetLoadPct,
		jc.origNumWaitingTasks,
		jc.origNumRunningTasks,
		jc.origNumTargetedWorkers,
		jc.numTasksToStart,
		jc.numWaitingTasks,
		jc.tempEntitlement,
		jc.tempNormalizedPct)
}
func (lbs *LoadBasedAlg) GetTasksToBeAssigned(jobsNotUsed []*jobState, stat stats.StatsReceiver, cs *clusterState,
	jobsByRequestor map[string][]*jobState) ([]*taskState, []*taskState) {
	lbs.classLoadPercents = lbs.LocalCopyClassLoadPercents()
	lbs.requestorReToClassMap = lbs.getRequestorToClassMap()
	lbs.classByDescLoadPct = lbs.getClassByDescLoadPct()
	numWorkers := len(cs.nodes)
	lbs.initOrigNumTargetedWorkers(numWorkers)
	lbs.initJobClassesMap(jobsByRequestor)
	rebalanced := false
	var tasksToStop []*taskState
	if lbs.getRebalanceMinimumDuration() > 0 && lbs.getRebalanceThreshold() > 0 {
		currentPctSpread := lbs.getCurrentPercentsSpread(numWorkers)
		if currentPctSpread > lbs.getRebalanceThreshold() {
			nilTime := time.Time{}
			if lbs.exceededRebalanceThresholdStart == nilTime {
				lbs.exceededRebalanceThresholdStart = time.Now()
			} else if time.Now().Sub(lbs.exceededRebalanceThresholdStart) > lbs.config.rebalanceMinDuration {
				tasksToStop = lbs.rebalanceClassTasks(jobsByRequestor, numWorkers)
				lbs.exceededRebalanceThresholdStart = time.Time{}
				rebalanced = true
			}
		}
	}
	if !rebalanced {
		lbs.computeNumTasksToStart(cs.numFree())
	}
	tasksToStart := lbs.buildTaskStartList()
	for _, jc := range lbs.jobClasses {
		stat.Gauge(fmt.Sprintf("%s_%s", stats.SchedJobClassTasksStarting, jc.className)).Update(int64(jc.numTasksToStart))
		stat.Gauge(fmt.Sprintf("%s_%s", stats.SchedJobClassTasksWaiting, jc.className)).Update(int64(jc.origNumWaitingTasks - jc.numTasksToStart))
		stat.Gauge(fmt.Sprintf("%s_%s", stats.SchedJobClassTasksRunning, jc.className)).Update(int64(jc.origNumRunningTasks))
		stat.Gauge(fmt.Sprintf("%s_%s", stats.SchedJobClassDefinedPct, jc.className)).Update(int64(jc.origTargetLoadPct))
		finalPct := int(math.Round(float64(jc.origNumRunningTasks+jc.numTasksToStart) / float64(int64(numWorkers)*100.0)))
		stat.Gauge(fmt.Sprintf("%s_%s", stats.SchedJobClassActualPct, jc.className)).Update(int64(finalPct))
	}
	if len(tasksToStart) > 0 || len(tasksToStop) > 0 {
		log.Debugf("Returning %d start tasks, %d stop tasks, for %d free nodes out of %d total nodes.\n"+
			"(%d calls to GetTasksToBeAssigned() with no tasks assigned)\n",
			len(tasksToStart), len(tasksToStop), cs.numFree(), len(cs.nodes), lbs.nopAllocationsCnt)
		lbs.nopAllocationsCnt = 0
	} else {
		lbs.nopAllocationsCnt++
	}
	return tasksToStart, tasksToStop
}
func (lbs *LoadBasedAlg) initOrigNumTargetedWorkers(numWorkers int) {
	lbs.jobClasses = map[string]*jobClass{}
	totalWorkers := 0
	firstClass := true
	for _, className := range lbs.classByDescLoadPct {
		jc := &jobClass{className: className, origTargetLoadPct: lbs.classLoadPercents[className], jobsByNumRunningTasks: map[int][]jobWaitingTasks{}}
		lbs.jobClasses[className] = jc
		if firstClass {
			firstClass = false
			continue
		}
		targetNumWorkers := int(math.Floor(float64(numWorkers) * float64(jc.origTargetLoadPct) / 100.0))
		jc.origNumTargetedWorkers = targetNumWorkers
		totalWorkers += targetNumWorkers
	}
	lbs.jobClasses[lbs.classByDescLoadPct[0]].origNumTargetedWorkers = numWorkers - totalWorkers
}
func (lbs *LoadBasedAlg) initJobClassesMap(jobsByRequestor map[string][]*jobState) {
	classNameWithLeastWorkers := lbs.classByDescLoadPct[len(lbs.classByDescLoadPct)-1]
	for requestor, jobs := range jobsByRequestor {
		var jc *jobClass
		var ok bool
		className := GetRequestorClass(requestor, lbs.requestorReToClassMap)
		jc, ok = lbs.jobClasses[className]
		if !ok {
			lbs.config.stat.Counter(stats.SchedLBSUnknownJobCounter).Inc(1)
			jc = lbs.jobClasses[classNameWithLeastWorkers]
			log.Errorf("%s is not a recognized job class assigning to class (%s)", className, classNameWithLeastWorkers)
		}
		if jc.origTargetLoadPct == 0 {
			log.Errorf("%s worker allocation (load %% is 0), ignoring %d jobs", requestor, len(jobs))
			lbs.config.stat.Counter(stats.SchedLBSIgnoredJobCounter).Inc(1)
			continue
		}
		for _, job := range jobs {
			_, ok := jc.jobsByNumRunningTasks[job.TasksRunning]
			if !ok {
				jc.jobsByNumRunningTasks[job.TasksRunning] = []jobWaitingTasks{}
			}
			waitingTasks := []*taskState{}
			for _, taskState := range job.Tasks {
				if taskState.Status == domain.NotStarted {
					waitingTasks = append(waitingTasks, taskState)
					jc.origNumWaitingTasks++
				}
			}
			jc.jobsByNumRunningTasks[job.TasksRunning] = append(jc.jobsByNumRunningTasks[job.TasksRunning], jobWaitingTasks{jobState: job, waitingTasks: waitingTasks})
			if job.TasksRunning > jc.maxTaskRunningMapIndex {
				jc.maxTaskRunningMapIndex = job.TasksRunning
			}
			jc.origNumRunningTasks += job.TasksRunning
		}
		jc.numWaitingTasks = jc.origNumWaitingTasks
	}
}
func GetRequestorClass(requestor string, requestorToClassMap map[string]string) string {
	for reqRe, className := range requestorToClassMap {
		if m, _ := regexp.Match(reqRe, []byte(requestor)); m {
			return className
		}
	}
	return ""
}
func (lbs *LoadBasedAlg) computeNumTasksToStart(numIdleWorkers int) {
	var haveUnallocatedTasks bool
	numIdleWorkers, haveUnallocatedTasks = lbs.entitlementTasksToStart(numIdleWorkers)
	if numIdleWorkers > 0 && haveUnallocatedTasks {
		lbs.workerLoanAllocation(numIdleWorkers, false)
	}
}
func (lbs *LoadBasedAlg) entitlementTasksToStart(numIdleWorkers int) (int, bool) {
	i := 0
	haveWaitingTasks := true
	for ; i < len(lbs.jobClasses); i++ {
		totalEntitlements := 0
		for _, jc := range lbs.jobClasses {
			if (jc.origNumRunningTasks+jc.numTasksToStart) <= jc.origNumTargetedWorkers && jc.numWaitingTasks > 0 {
				jc.tempEntitlement = jc.origNumTargetedWorkers - (jc.origNumRunningTasks + jc.numTasksToStart)
			} else {
				jc.tempEntitlement = 0
			}
			totalEntitlements += jc.tempEntitlement
		}
		if totalEntitlements == 0 {
			break
		}
		lbs.computeEntitlementPercents()
		numTasksAllocated := 0
		tasksToStart := min(numIdleWorkers, totalEntitlements)
		numTasksAllocated, haveWaitingTasks = lbs.getTaskAllocations(tasksToStart)
		numIdleWorkers -= numTasksAllocated
		if !haveWaitingTasks {
			break
		}
		if numIdleWorkers <= 0 {
			break
		}
	}
	return numIdleWorkers, haveWaitingTasks
}
func (lbs *LoadBasedAlg) workerLoanAllocation(numIdleWorkers int, haveRebalanced bool) {
	i := 0
	for ; i < len(lbs.jobClasses); i++ {
		lbs.computeLoanPercents(numIdleWorkers, haveRebalanced)
		numTasksToStart, haveWaitingTasks := lbs.getTaskAllocations(numIdleWorkers)
		numIdleWorkers -= numTasksToStart
		if !haveWaitingTasks {
			break
		}
		if numIdleWorkers <= 0 {
			break
		}
	}
}
func (lbs *LoadBasedAlg) getTaskAllocations(numIdleWorkers int) (int, bool) {
	totalTasksToStart := 0
	haveWaitingTasks := false
	for _, className := range lbs.classByDescLoadPct {
		jc := lbs.jobClasses[className]
		numTasksToStart := min(jc.numWaitingTasks, ceil(float32(numIdleWorkers)*(float32(jc.tempNormalizedPct)/100.0)))
		if jc.numTasksToStart < 0 {
			jc.tempNormalizedPct = 0.0
		}
		if (totalTasksToStart + numTasksToStart) > numIdleWorkers {
			numTasksToStart = numIdleWorkers - totalTasksToStart
		}
		jc.numTasksToStart += numTasksToStart
		jc.numWaitingTasks -= numTasksToStart
		if jc.numWaitingTasks > 0 {
			haveWaitingTasks = true
		}
		totalTasksToStart += numTasksToStart
	}
	return totalTasksToStart, haveWaitingTasks
}
func (lbs *LoadBasedAlg) computeEntitlementPercents() {
	entitlementTotal := 0
	for _, jc := range lbs.jobClasses {
		entitlementTotal += jc.tempEntitlement
	}
	totalPercents := 0
	firstClass := true
	for _, className := range lbs.classByDescLoadPct {
		if firstClass {
			firstClass = false
			continue
		}
		jc := lbs.jobClasses[className]
		jc.tempNormalizedPct = int(math.Floor(float64(jc.tempEntitlement) * 100.0 / float64(entitlementTotal)))
		totalPercents += jc.tempNormalizedPct
	}
	lbs.jobClasses[lbs.classByDescLoadPct[0]].tempNormalizedPct = 100 - totalPercents
}
func (lbs *LoadBasedAlg) computeLoanPercents(numWorkersAvailableForLoan int, haveRebalanced bool) {
	pctsTotal := 0
	for _, jc := range lbs.jobClasses {
		if jc.numWaitingTasks > 0 {
			pctsTotal += jc.origTargetLoadPct
		}
	}
	if pctsTotal == 0 {
		return
	}
	normalizedLoanPcts := map[string]float64{}
	totalLoaners := 0
	for _, className := range lbs.classByDescLoadPct {
		jc := lbs.jobClasses[className]
		if jc.numWaitingTasks > 0 {
			normalizedLoanPcts[className] = float64(jc.origTargetLoadPct) / float64(pctsTotal)
		} else {
			normalizedLoanPcts[className] = 0.0
		}
		if !haveRebalanced {
			totalLoaners += int(math.Max(0.0, float64(jc.origNumRunningTasks-jc.origNumTargetedWorkers)))
		}
	}
	totalLoaners += numWorkersAvailableForLoan
	loanEntitlements := map[string]int{}
	totalEntitlements := 0
	for className, jc := range lbs.jobClasses {
		entitlement := int(math.Floor(normalizedLoanPcts[className] * float64(totalLoaners)))
		currentLoaned := int(math.Max(0, float64(jc.origNumRunningTasks-jc.origNumTargetedWorkers)))
		if haveRebalanced && jc.numTasksToStart < 0 {
			currentLoaned += jc.numTasksToStart
		}
		loanEntitlements[className] = int(math.Max(0, float64(entitlement-currentLoaned)))
		totalEntitlements += loanEntitlements[className]
	}
	for className, jc := range lbs.jobClasses {
		jc.tempNormalizedPct = int(float64(loanEntitlements[className]) / float64(totalEntitlements) * 100.0)
	}
}
func (lbs *LoadBasedAlg) buildTaskStartList() []*taskState {
	tasks := []*taskState{}
	for _, jc := range lbs.jobClasses {
		if jc.numTasksToStart <= 0 {
			continue
		}
		classTasks := lbs.getTasksToStartForJobClass(jc)
		tasks = append(tasks, classTasks...)
	}
	return tasks
}
func (lbs *LoadBasedAlg) getTasksToStartForJobClass(jc *jobClass) []*taskState {
	tasks := []*taskState{}
	startingTaskCnt := 0
	for numRunningTasks := 0; numRunningTasks <= jc.maxTaskRunningMapIndex; numRunningTasks++ {
		var jobs []jobWaitingTasks
		var ok bool
		if jobs, ok = jc.jobsByNumRunningTasks[numRunningTasks]; !ok {
			continue
		}
		for _, job := range jobs {
			if job.waitingTasks != nil && len(job.waitingTasks) > 0 {
				tasks = append(tasks, job.waitingTasks[0])
				if len(job.waitingTasks) > 1 {
					job.waitingTasks = job.waitingTasks[1:]
					jc.jobsByNumRunningTasks[numRunningTasks+1] = append(jc.jobsByNumRunningTasks[numRunningTasks+1], job)
					if numRunningTasks == jc.maxTaskRunningMapIndex {
						jc.maxTaskRunningMapIndex++
					}
				} else {
					job.waitingTasks = []*taskState{}
				}
				startingTaskCnt++
				if startingTaskCnt == jc.numTasksToStart {
					return tasks
				}
			}
		}
	}
	msg := "getTasksToStartForJobClass() fell out of for range jobs loop.  We expected it to return because startingTaskCnt == jc.numTasksToStart above " +
		"before falling out of the loop.  Workers may be left idle till the next scheduling iteration."
	log.Warn(msg)
	return tasks
}
func (lbs *LoadBasedAlg) buildTaskStopList() []*taskState {
	tasks := []*taskState{}
	for _, jc := range lbs.jobClasses {
		if jc.numTasksToStart >= 0 {
			continue
		}
		classTasks := lbs.getTasksToStopForJobClass(jc)
		tasks = append(tasks, classTasks...)
	}
	return tasks
}
func (lbs *LoadBasedAlg) getTasksToStopForJobClass(jobClass *jobClass) []*taskState {
	earliest := time.Now().Add(-1 * MaxTaskDuration)
	startTimeSec := time.Now().Truncate(time.Second)
	numTasksToStop := jobClass.numTasksToStart * -1
	tasksToStop := []*taskState{}
	for len(tasksToStop) < numTasksToStop {
		key := taskClassAndStartKey{class: jobClass.className, start: startTimeSec}
		tasks := lbs.tasksByJobClassAndStartTimeSec[key]
		for _, task := range tasks {
			tasksToStop = append(tasksToStop, task)
			if len(tasksToStop) == numTasksToStop {
				break
			}
		}
		startTimeSec = startTimeSec.Add(-1 * time.Second).Truncate(time.Second)
		if startTimeSec.Before(earliest) {
			break
		}
	}
	lbs.config.stat.Gauge(fmt.Sprintf("%s%s", stats.SchedStoppingTasks, jobClass.className)).Update(int64(numTasksToStop))
	return tasksToStop
}
func (lbs *LoadBasedAlg) getNumTasksToStart(requestor string) int {
	return lbs.jobClasses[requestor].numTasksToStart
}
func (lbs *LoadBasedAlg) rebalanceClassTasks(jobsByRequestor map[string][]*jobState, totalWorkers int) []*taskState {
	log.Info("Rebalancing")
	totalTasks := 0
	for _, jc := range lbs.jobClasses {
		if jc.origNumRunningTasks > jc.origNumTargetedWorkers {
			jc.numTasksToStart = jc.origNumTargetedWorkers - jc.origNumRunningTasks
		} else if jc.origNumRunningTasks+jc.origNumWaitingTasks < jc.origNumTargetedWorkers {
			jc.numTasksToStart = jc.origNumWaitingTasks
		} else {
			jc.numTasksToStart = jc.origNumTargetedWorkers - jc.origNumRunningTasks
		}
		totalTasks += jc.origNumRunningTasks + jc.numTasksToStart
	}
	if totalTasks < totalWorkers {
		lbs.computeLoanPercents(totalWorkers-totalTasks, true)
		lbs.getTaskAllocations(totalWorkers - totalTasks)
	}
	tasksToStop := lbs.buildTaskStopList()
	return tasksToStop
}
func (lbs *LoadBasedAlg) getCurrentPercentsSpread(totalWorkers int) int {
	if len(lbs.jobClasses) < 2 {
		return 0
	}
	minPct := 0
	maxPct := 0
	for _, className := range lbs.classByDescLoadPct {
		jc := lbs.jobClasses[className]
		currPct := int(math.Floor(float64(jc.origNumRunningTasks) * 100.0 / float64(totalWorkers)))
		pctDiff := jc.origTargetLoadPct - currPct
		if pctDiff < 0 || jc.numWaitingTasks > 0 {  
			minPct = min(minPct, pctDiff)
			maxPct = max(maxPct, pctDiff)
		}
	}
	return maxPct - minPct
}
func (lbs *LoadBasedAlg) getClassByDescLoadPct() []string {
	lbs.config.classLoadPercentsMu.RLock()
	defer lbs.config.classLoadPercentsMu.RUnlock()
	copy := []string{}
	for _, v := range lbs.config.classByDescLoadPct {
		copy = append(copy, v)
	}
	return copy
}
func (lbs *LoadBasedAlg) getClassLoadPercents() map[string]int32 {
	lbs.config.classLoadPercentsMu.RLock()
	defer lbs.config.classLoadPercentsMu.RUnlock()
	copy := map[string]int32{}
	for k, v := range lbs.config.classLoadPercents {
		copy[k] = int32(v)
	}
	return copy
}
func (lbs *LoadBasedAlg) LocalCopyClassLoadPercents() map[string]int {
	lbs.config.classLoadPercentsMu.RLock()
	defer lbs.config.classLoadPercentsMu.RUnlock()
	copy := map[string]int{}
	for k, v := range lbs.config.classLoadPercents {
		copy[k] = v
	}
	return copy
}
func (lbs *LoadBasedAlg) setClassLoadPercents(classLoadPercents map[string]int32) {
	lbs.config.classLoadPercentsMu.Lock()
	defer lbs.config.classLoadPercentsMu.Unlock()
	keys := []string{}
	for key := range classLoadPercents {
		keys = append(keys, key)
	}
	sort.Slice(keys, func(i, j int) bool {
		return classLoadPercents[keys[i]] > classLoadPercents[keys[j]]
	})
	lbs.config.classByDescLoadPct = keys
	lbs.config.classLoadPercents = map[string]int{}
	pctTotal := 0
	for k, val := range classLoadPercents {
		lbs.config.classLoadPercents[k] = int(val)
		pctTotal += int(val)
	}
	if pctTotal != 100 {
		log.Errorf("LoadBalanced scheduling %%'s don't add up to 100, normalizing them")
		lbs.normalizePercents(pctTotal)
	}
	log.Infof("classLoadPercents are %v", lbs.config.classLoadPercents)
}
func (lbs *LoadBasedAlg) normalizePercents(percentsSum int) {
	totalNormalizedPercents := 0
	firstClass := true
	normalizedPercents := map[string]int{}
	for _, className := range lbs.config.classByDescLoadPct {
		if firstClass {
			firstClass = false
			continue  
		}
		classPercent := lbs.config.classLoadPercents[className]
		normalizedPercents[className] = int(math.Floor(float64(classPercent) / float64(percentsSum) * 100))
		totalNormalizedPercents += normalizedPercents[className]
	}
	normalizedPercents[lbs.config.classByDescLoadPct[0]] = 100 - totalNormalizedPercents
	lbs.config.classLoadPercents = normalizedPercents
}
func (lbs *LoadBasedAlg) getRequestorToClassMap() map[string]string {
	lbs.config.requestorReToClassMapMU.RLock()
	defer lbs.config.requestorReToClassMapMU.RUnlock()
	copy := map[string]string{}
	for k, v := range lbs.config.requestorReToClassMap {
		copy[k] = v
	}
	return copy
}
func (lbs *LoadBasedAlg) setRequestorToClassMap(requestorToClassMap map[string]string) {
	lbs.config.requestorReToClassMapMU.Lock()
	defer lbs.config.requestorReToClassMapMU.Unlock()
	lbs.config.requestorReToClassMap = map[string]string{}
	for k, v := range requestorToClassMap {
		lbs.config.requestorReToClassMap[k] = v
	}
	log.Infof("set requestorToClassMap to %v", requestorToClassMap)
}
func (lbs *LoadBasedAlg) getRebalanceMinimumDuration() time.Duration {
	lbs.config.rebalanceMinDurationMu.RLock()
	defer lbs.config.rebalanceMinDurationMu.RUnlock()
	return lbs.config.rebalanceMinDuration
}
func (lbs *LoadBasedAlg) setRebalanceMinimumDuration(rebalanceMinDuration time.Duration) {
	lbs.config.rebalanceMinDurationMu.Lock()
	defer lbs.config.rebalanceMinDurationMu.Unlock()
	lbs.config.rebalanceMinDuration = rebalanceMinDuration
	log.Infof("set rebalanceMinDuration to %s", rebalanceMinDuration)
}