2019-10-31 14:32:46 -04:00
// Copyright (C) 2018 spdfg
2018-10-06 20:03:14 -07:00
//
// This file is part of Elektron.
//
// Elektron is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Elektron is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Elektron. If not, see <http://www.gnu.org/licenses/>.
//
2018-01-19 21:20:43 +00:00
package schedulers
import (
2018-04-17 23:44:36 +00:00
"fmt"
2018-04-17 20:12:33 +00:00
"log"
2018-04-17 20:10:36 +00:00
"time"
2018-09-30 18:23:38 -07:00
mesos "github.com/mesos/mesos-go/api/v0/mesosproto"
sched "github.com/mesos/mesos-go/api/v0/scheduler"
2019-10-31 14:32:46 -04:00
"github.com/spdfg/elektron/def"
elekLogDef "github.com/spdfg/elektron/logging/def"
2018-01-19 21:20:43 +00:00
)
type SchedPolicyContext interface {
// Change the state of scheduling.
SwitchSchedPol ( s SchedPolicyState )
}
type SchedPolicyState interface {
// Define the particular scheduling policy's methodology of resource offer consumption.
ConsumeOffers ( SchedPolicyContext , sched . SchedulerDriver , [ ] * mesos . Offer )
2018-04-17 20:09:35 +00:00
// Get information about the scheduling policy.
GetInfo ( ) ( info struct {
2018-04-17 23:44:36 +00:00
taskDist float64
varCpuShare float64
nextPolicyName string
prevPolicyName string
} )
// Update links to next and previous scheduling policy.
UpdateLinks ( info struct {
nextPolicyName string
prevPolicyName string
2018-04-17 20:09:35 +00:00
} )
// Switch scheduling policy if necessary.
SwitchIfNecessary ( SchedPolicyContext )
2018-01-19 21:20:43 +00:00
}
2018-02-06 16:50:16 -05:00
type baseSchedPolicyState struct {
SchedPolicyState
// Keep track of the number of tasks that have been scheduled.
numTasksScheduled int
2018-02-26 01:59:09 +00:00
// Distribution of tasks that the scheduling policy is most appropriate for.
// This distribution corresponds to the ratio of low power consuming tasks to
// high power consuming tasks.
TaskDistribution float64 ` json:"taskDist" `
// The average variance in cpu-share per task that this scheduling policy can cause.
// Note: This number corresponds to a given workload.
VarianceCpuSharePerTask float64 ` json:"varCpuShare" `
2018-04-17 23:44:36 +00:00
// Next and Previous scheduling policy in round-robin order.
// This order is determined by the sorted order (non-decreasing or non-increasing) of taskDistribution.
nextPolicyName string
prevPolicyName string
}
// Scheduling policy switching criteria.
// Takes a pointer to the BaseScheduler and returns the name of the scheduling policy to switch to.
type switchBy func ( * BaseScheduler ) string
var switchBasedOn map [ string ] switchBy = map [ string ] switchBy {
"taskDist" : switchTaskDistBased ,
"round-robin" : switchRoundRobinBased ,
"rev-round-robin" : switchRevRoundRobinBased ,
2018-02-06 16:50:16 -05:00
}
2018-04-17 23:44:36 +00:00
func switchTaskDistBased ( baseSchedRef * BaseScheduler ) string {
2018-04-17 20:12:33 +00:00
// Name of the scheduling policy to switch to.
switchToPolicyName := ""
// Record overhead to classify the tasks in the scheduling window and using the classification results
// to determine the distribution of low power consuming and high power consuming tasks.
startTime := time . Now ( )
// Determine the distribution of tasks in the new scheduling window.
taskDist , err := def . GetTaskDistributionInWindow ( baseSchedRef . schedWindowSize , baseSchedRef . tasks )
baseSchedRef . LogClsfnAndTaskDistOverhead ( time . Now ( ) . Sub ( startTime ) )
2018-10-04 13:45:31 -04:00
baseSchedRef . Log ( elekLogDef . GENERAL , fmt . Sprintf ( "Switching... TaskDistribution[%f]" , taskDist ) )
2018-04-17 20:12:33 +00:00
if err != nil {
// All the tasks in the window were only classified into 1 cluster.
// Max-Min and Max-GreedyMins would work the same way as Bin-Packing for this situation.
// So, we have 2 choices to make. First-Fit or Bin-Packing.
// If choose Bin-Packing, then there might be a performance degradation due to increase in
// resource contention. So, First-Fit might be a better option to cater to the worst case
// where all the tasks are power intensive tasks.
// TODO: Another possibility is to do the exact opposite and choose Bin-Packing.
// TODO[2]: Determine scheduling policy based on the distribution of tasks in the whole queue.
switchToPolicyName = bp
} else {
// The tasks in the scheduling window were classified into 2 clusters, meaning that there is
// some variety in the kind of tasks.
// We now select the scheduling policy which is most appropriate for this distribution of tasks.
first := schedPoliciesToSwitch [ 0 ]
last := schedPoliciesToSwitch [ len ( schedPoliciesToSwitch ) - 1 ]
if taskDist < first . sp . GetInfo ( ) . taskDist {
switchToPolicyName = first . spName
} else if taskDist > last . sp . GetInfo ( ) . taskDist {
switchToPolicyName = last . spName
} else {
low := 0
high := len ( schedPoliciesToSwitch ) - 1
for low <= high {
mid := ( low + high ) / 2
if taskDist < schedPoliciesToSwitch [ mid ] . sp . GetInfo ( ) . taskDist {
high = mid - 1
} else if taskDist > schedPoliciesToSwitch [ mid ] . sp . GetInfo ( ) . taskDist {
low = mid + 1
} else {
switchToPolicyName = schedPoliciesToSwitch [ mid ] . spName
break
}
}
// We're here if low == high+1.
// If haven't yet found the closest match.
if switchToPolicyName == "" {
lowDiff := schedPoliciesToSwitch [ low ] . sp . GetInfo ( ) . taskDist - taskDist
highDiff := taskDist - schedPoliciesToSwitch [ high ] . sp . GetInfo ( ) . taskDist
if lowDiff > highDiff {
switchToPolicyName = schedPoliciesToSwitch [ high ] . spName
} else if highDiff > lowDiff {
switchToPolicyName = schedPoliciesToSwitch [ low ] . spName
} else {
// index doens't matter as the values at high and low are equidistant
// from taskDist.
switchToPolicyName = schedPoliciesToSwitch [ high ] . spName
}
}
}
}
2018-04-17 23:44:36 +00:00
return switchToPolicyName
}
// Switching based on a round-robin approach.
// Not being considerate to the state of TaskQueue or the state of the cluster.
func switchRoundRobinBased ( baseSchedRef * BaseScheduler ) string {
// If haven't received any resource offers.
if ! baseSchedRef . hasReceivedResourceOffers {
return schedPoliciesToSwitch [ 0 ] . spName
}
return baseSchedRef . curSchedPolicy . GetInfo ( ) . nextPolicyName
}
// Switching based on a round-robin approach, but in the reverse order.
// Not being considerate to the state of the TaskQueue or the state of the cluster.
func switchRevRoundRobinBased ( baseSchedRef * BaseScheduler ) string {
// If haven't received any resource offers.
if ! baseSchedRef . hasReceivedResourceOffers {
return schedPoliciesToSwitch [ len ( schedPoliciesToSwitch ) - 1 ] . spName
}
return baseSchedRef . curSchedPolicy . GetInfo ( ) . prevPolicyName
2018-04-17 20:12:33 +00:00
}
2018-04-17 20:09:35 +00:00
func ( bsps * baseSchedPolicyState ) SwitchIfNecessary ( spc SchedPolicyContext ) {
2018-02-06 16:50:16 -05:00
baseSchedRef := spc . ( * BaseScheduler )
2018-04-17 20:12:33 +00:00
// Switching scheduling policy only if feature enabled from CLI.
2018-02-06 16:50:16 -05:00
if baseSchedRef . schedPolSwitchEnabled {
2018-04-17 20:12:33 +00:00
// Name of scheduling policy to switch to.
2018-04-17 20:09:35 +00:00
switchToPolicyName := ""
2018-04-17 23:44:36 +00:00
// Size of the new scheduling window.
newSchedWindowSize := 0
2018-04-17 20:12:33 +00:00
// If scheduling window has not been fixed, then determine the scheduling window based on the current
// availability of resources on the cluster (Mesos perspective).
if ! baseSchedRef . toFixSchedWindow {
// Need to compute the size of the scheduling window.
// The next scheduling policy will schedule at max schedWindowSize number of tasks.
2018-04-17 23:44:36 +00:00
newSchedWindowSize , baseSchedRef . numTasksInSchedWindow =
2018-04-17 20:12:33 +00:00
baseSchedRef . schedWindowResStrategy . Apply ( func ( ) interface { } { return baseSchedRef . tasks } )
}
2018-04-17 23:44:36 +00:00
// Now, we need to switch if the new scheduling window is > 0.
if ( ! baseSchedRef . toFixSchedWindow && ( newSchedWindowSize > 0 ) ) ||
( baseSchedRef . toFixSchedWindow && ( baseSchedRef . schedWindowSize > 0 ) ) {
2018-04-17 20:12:33 +00:00
// If we haven't received any resource offers, then
// check whether we need to fix the first scheduling policy to deploy.
// If not, then determine the first scheduling policy based on the distribution of tasks
// in the scheduling window.
// Else,
// Check whether the currently deployed scheduling policy has already scheduled the
// schedWindowSize number of tasks.
// If yes, then we switch to the scheduling policy based on the distribution of tasks in
// the scheduling window.
// If not, then we continue to use the currently deployed scheduling policy.
if ! baseSchedRef . hasReceivedResourceOffers {
if baseSchedRef . nameOfFstSchedPolToDeploy != "" {
switchToPolicyName = baseSchedRef . nameOfFstSchedPolToDeploy
2018-04-17 23:44:36 +00:00
if ! baseSchedRef . toFixSchedWindow {
baseSchedRef . schedWindowSize = newSchedWindowSize
}
2018-04-17 20:12:33 +00:00
} else {
2018-04-17 23:44:36 +00:00
// Decided to switch, so updating the window size.
if ! baseSchedRef . toFixSchedWindow {
baseSchedRef . schedWindowSize = newSchedWindowSize
}
switchToPolicyName = switchBasedOn [ baseSchedRef . schedPolSwitchCriteria ] ( baseSchedRef )
2018-04-17 20:12:33 +00:00
}
2018-04-17 20:09:35 +00:00
} else {
2018-04-17 23:44:36 +00:00
// Checking if the currently deployed scheduling policy has scheduled all the tasks in the window.
2018-04-17 20:12:33 +00:00
if bsps . numTasksScheduled >= baseSchedRef . schedWindowSize {
2018-04-17 23:44:36 +00:00
// Decided to switch, so updating the window size.
if ! baseSchedRef . toFixSchedWindow {
baseSchedRef . schedWindowSize = newSchedWindowSize
}
switchToPolicyName = switchBasedOn [ baseSchedRef . schedPolSwitchCriteria ] ( baseSchedRef )
2018-04-17 20:10:36 +00:00
} else {
2018-04-17 20:12:33 +00:00
// We continue working with the currently deployed scheduling policy.
log . Println ( "Continuing with the current scheduling policy..." )
log . Printf ( "TasksScheduled[%d], SchedWindowSize[%d]" , bsps . numTasksScheduled ,
baseSchedRef . schedWindowSize )
return
2018-04-17 20:09:35 +00:00
}
2018-02-06 16:50:16 -05:00
}
2018-04-17 23:44:36 +00:00
// Switching scheduling policy.
baseSchedRef . LogSchedPolicySwitch ( switchToPolicyName , SchedPolicies [ switchToPolicyName ] )
baseSchedRef . SwitchSchedPol ( SchedPolicies [ switchToPolicyName ] )
// Resetting the number of tasks scheduled as this is a new scheduling policy that has been
// deployed.
bsps . numTasksScheduled = 0
2018-04-17 20:10:36 +00:00
} else {
2018-04-17 20:12:33 +00:00
// We continue working with the currently deployed scheduling policy.
log . Println ( "Continuing with the current scheduling policy..." )
log . Printf ( "TasksScheduled[%d], SchedWindowSize[%d]" , bsps . numTasksScheduled ,
baseSchedRef . schedWindowSize )
return
2018-02-06 16:50:16 -05:00
}
}
}
2018-04-17 20:09:35 +00:00
func ( bsps * baseSchedPolicyState ) GetInfo ( ) ( info struct {
2018-04-17 23:44:36 +00:00
taskDist float64
varCpuShare float64
nextPolicyName string
prevPolicyName string
2018-04-17 20:09:35 +00:00
} ) {
info . taskDist = bsps . TaskDistribution
info . varCpuShare = bsps . VarianceCpuSharePerTask
2018-04-17 23:44:36 +00:00
info . nextPolicyName = bsps . nextPolicyName
info . prevPolicyName = bsps . prevPolicyName
2018-04-17 20:09:35 +00:00
return info
}
2018-04-17 23:44:36 +00:00
func ( bsps * baseSchedPolicyState ) UpdateLinks ( info struct {
nextPolicyName string
prevPolicyName string
} ) {
bsps . nextPolicyName = info . nextPolicyName
bsps . prevPolicyName = info . prevPolicyName
}