Merged in hybridSchedulerAndSortedOffers (pull request #8)

Hybrid Scheduler -- TopHeavy, BottomHeavy, FirstFit and BinPacked schedulers with sortedOffers.
This commit is contained in:
Pradyumna Kaushik 2017-02-10 20:21:04 +00:00 committed by Renan DelValle
commit a0a3e78041
29 changed files with 1826 additions and 436 deletions

View file

@ -8,12 +8,14 @@ To Do:
* Add ability to use constraints * Add ability to use constraints
* Running average calculations https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average * Running average calculations https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
* Make parameters corresponding to each scheduler configurable (possible to have a config template for each scheduler?) * Make parameters corresponding to each scheduler configurable (possible to have a config template for each scheduler?)
* TODO : Adding type of scheduler to be used, to be picked from a config file, along with it's configurable parameters.
* Write test code for each scheduler (This should be after the design change) * Write test code for each scheduler (This should be after the design change)
* Some of the constants in constants/constants.go can vary based on the environment. * Some of the constants in constants/constants.go can vary based on the environment.
Possible to setup the constants at runtime based on the environment? Possible to setup the constants at runtime based on the environment?
* Log fix for declining offer -- different reason when insufficient resources as compared to when there are no * Log fix for declining offer -- different reason when insufficient resources as compared to when there are no
longer any tasks to schedule. longer any tasks to schedule.
* Have a centralised logFile that can be filtered by identifier. All electron logs should go into this file. * Have a centralised logFile that can be filtered by identifier. All electron logs should go into this file.
* Make ClassMapWatts to commandLine arguments so Electron can be run with ClassMapWatts enabled/disabled.
**Requires [Performance Co-Pilot](http://pcp.io/) tool pmdumptext to be installed on the **Requires [Performance Co-Pilot](http://pcp.io/) tool pmdumptext to be installed on the

View file

@ -6,6 +6,8 @@ Constants that are used across scripts
5. window_size = number of tasks to consider for computation of the dynamic cap. 5. window_size = number of tasks to consider for computation of the dynamic cap.
Also, exposing functions to update or initialize some of the constants. Also, exposing functions to update or initialize some of the constants.
TODO: Clean this up and use Mesos Attributes instead.
*/ */
package constants package constants
@ -14,6 +16,24 @@ var Hosts = []string{"stratos-001.cs.binghamton.edu", "stratos-002.cs.binghamton
"stratos-005.cs.binghamton.edu", "stratos-006.cs.binghamton.edu", "stratos-005.cs.binghamton.edu", "stratos-006.cs.binghamton.edu",
"stratos-007.cs.binghamton.edu", "stratos-008.cs.binghamton.edu"} "stratos-007.cs.binghamton.edu", "stratos-008.cs.binghamton.edu"}
// Classification of the nodes in the cluster based on their power consumption.
var PowerClasses = map[string]map[string]bool{
"ClassA": map[string]bool{
"stratos-005.cs.binghamton.edu": true,
"stratos-006.cs.binghamton.edu": true,
},
"ClassB": map[string]bool{
"stratos-007.cs.binghamton.edu": true,
"stratos-008.cs.binghamton.edu": true,
},
"ClassC": map[string]bool{
"stratos-001.cs.binghamton.edu": true,
"stratos-002.cs.binghamton.edu": true,
"stratos-003.cs.binghamton.edu": true,
"stratos-004.cs.binghamton.edu": true,
},
}
// Add a new host to the slice of hosts. // Add a new host to the slice of hosts.
func AddNewHost(newHost string) bool { func AddNewHost(newHost string) bool {
// Validation // Validation
@ -68,7 +88,7 @@ func UpdateCapMargin(newCapMargin float64) bool {
var StarvationFactor = PowerThreshold / CapMargin var StarvationFactor = PowerThreshold / CapMargin
// Window size for running average // Window size for running average
var WindowSize = 20 var ConsiderationWindowSize = 20
// Update the window size. // Update the window size.
func UpdateWindowSize(newWindowSize int) bool { func UpdateWindowSize(newWindowSize int) bool {
@ -76,7 +96,7 @@ func UpdateWindowSize(newWindowSize int) bool {
if newWindowSize == 0 { if newWindowSize == 0 {
return false return false
} else { } else {
WindowSize = newWindowSize ConsiderationWindowSize = newWindowSize
return true return true
} }
} }

View file

@ -1,9 +1,9 @@
/* /*
Cluster wide dynamic capping Cluster wide dynamic capping
This is not a scheduler but a scheduling scheme that schedulers can use. This is a capping strategy that can be used with schedulers to improve the power consumption.
*/ */
package pcp package powerCapping
import ( import (
"bitbucket.org/sunybingcloud/electron/constants" "bitbucket.org/sunybingcloud/electron/constants"
@ -251,7 +251,7 @@ func (capper ClusterwideCapper) FCFSDeterminedCap(totalPower map[string]float64,
return 100, errors.New("Invalid argument: totalPower") return 100, errors.New("Invalid argument: totalPower")
} else { } else {
// Need to calculate the running average // Need to calculate the running average
runningAverage := runAvg.Calc(taskWrapper{task: *newTask}, constants.WindowSize) runningAverage := runAvg.Calc(taskWrapper{task: *newTask}, constants.ConsiderationWindowSize)
// For each node, calculate the percentage of the running average to the total power. // For each node, calculate the percentage of the running average to the total power.
ratios := make(map[string]float64) ratios := make(map[string]float64)
for host, tpower := range totalPower { for host, tpower := range totalPower {
@ -271,5 +271,5 @@ func (capper ClusterwideCapper) FCFSDeterminedCap(totalPower map[string]float64,
// Stringer for an instance of clusterwideCapper // Stringer for an instance of clusterwideCapper
func (capper ClusterwideCapper) String() string { func (capper ClusterwideCapper) String() string {
return "Cluster Capper -- Proactively cap the entire cluster." return "Cluster-wide Capper -- Proactively cap the entire cluster."
} }

View file

@ -58,7 +58,7 @@ func main() {
startTime := time.Now().Format("20060102150405") startTime := time.Now().Format("20060102150405")
logPrefix := *pcplogPrefix + "_" + startTime logPrefix := *pcplogPrefix + "_" + startTime
scheduler := schedulers.NewFirstFitSortedWattsReducedWAR(tasks, *ignoreWatts, logPrefix) scheduler := schedulers.NewBinPackSortedWatts(tasks, *ignoreWatts, logPrefix)
driver, err := sched.NewMesosSchedulerDriver(sched.DriverConfig{ driver, err := sched.NewMesosSchedulerDriver(sched.DriverConfig{
Master: *master, Master: *master,
Framework: &mesos.FrameworkInfo{ Framework: &mesos.FrameworkInfo{
@ -96,7 +96,7 @@ func main() {
// Signals we have scheduled every task we have // Signals we have scheduled every task we have
select { select {
case <-scheduler.Shutdown: case <-scheduler.Shutdown:
// case <-time.After(shutdownTimeout): //case <-time.After(shutdownTimeout):
} }
// All tasks have finished // All tasks have finished
@ -104,7 +104,7 @@ func main() {
case <-scheduler.Done: case <-scheduler.Done:
close(scheduler.PCPLog) close(scheduler.PCPLog)
time.Sleep(5 * time.Second) //Wait for PCP to log a few more seconds time.Sleep(5 * time.Second) //Wait for PCP to log a few more seconds
// case <-time.After(shutdownTimeout): //case <-time.After(shutdownTimeout):
} }
// Done shutting down // Done shutting down

View file

@ -8,12 +8,20 @@ To Do:
* Separate the capping strategies from the scheduling algorithms and make it possible to use any capping strategy with any scheduler. * Separate the capping strategies from the scheduling algorithms and make it possible to use any capping strategy with any scheduler.
* Make newTask(...) variadic where the newTaskClass argument can either be given or not. If not give, then pick task.Watts as the watts attribute, else pick task.ClassToWatts[newTaskClass]. * Make newTask(...) variadic where the newTaskClass argument can either be given or not. If not give, then pick task.Watts as the watts attribute, else pick task.ClassToWatts[newTaskClass].
* Retrofit pcp/proactiveclusterwidecappers.go to include the power capping go routines and to cap only when necessary. * Retrofit pcp/proactiveclusterwidecappers.go to include the power capping go routines and to cap only when necessary.
* Create a package that would contain routines to perform various logging and move helpers.coLocated(...) into that.
* Retrofit schedulers to be able to run either using ClassMapWatts enabled or disabled.
Scheduling Algorithms: Scheduling Algorithms:
* First Fit * First Fit
* First Fit with sorted watts * First Fit with sorted watts
* Bin-packing with sorted watts * Bin-packing with sorted watts
* FCFS Proactive Cluster-wide Capping * ClassMapWatts -- Bin-packing and First Fit that now use Watts per power class.
* Ranked Proactive Cluster-wide Capping * Top Heavy -- Hybrid scheduler that packs small tasks (less power intensive) using Bin-packing and spreads large tasks (power intensive) using First Fit.
* Piston Capping -- Works when scheduler is run with WAR * Bottom Heavy -- Hybrid scheduler that packs large tasks (power intensive) using Bin-packing and spreads small tasks (less power intensive) using First Fit.
Capping Strategies
* Extrema Dynamic Capping
* Proactive Cluster-wide Capping
* Piston Capping

View file

@ -0,0 +1,232 @@
package schedulers
import (
"bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt"
"github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto"
"github.com/mesos/mesos-go/mesosutil"
sched "github.com/mesos/mesos-go/scheduler"
"log"
"os"
"sort"
"time"
)
// Decides if to take an offer or not
func (s *BinPackSortedWattsSortedOffers) takeOffer(offer *mesos.Offer, totalCPU, totalRAM,
totalWatts float64, task def.Task) bool {
offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter
// Does the task fit
if (s.ignoreWatts || (offerWatts >= (totalWatts + task.Watts))) &&
(offerCPU >= (totalCPU + task.CPU)) &&
(offerRAM >= (totalRAM + task.RAM)) {
return true
}
return false
}
type BinPackSortedWattsSortedOffers struct {
base // Type embedded to inherit common functions
tasksCreated int
tasksRunning int
tasks []def.Task
metrics map[string]def.Metric
running map[string]map[string]bool
ignoreWatts bool
// First set of PCP values are garbage values, signal to logger to start recording when we're
// about to schedule a new task
RecordPCP bool
// This channel is closed when the program receives an interrupt,
// signalling that the program should shut down.
Shutdown chan struct{}
// This channel is closed after shutdown is closed, and only when all
// outstanding tasks have been cleaned up
Done chan struct{}
// Controls when to shutdown pcp logging
PCPLog chan struct{}
schedTrace *log.Logger
}
// New electron scheduler
func NewBinPackSortedWattsSortedOffers(tasks []def.Task, ignoreWatts bool, schedTracePrefix string) *BinPackSortedWattsSortedOffers {
sort.Sort(def.WattsSorter(tasks))
logFile, err := os.Create("./" + schedTracePrefix + "_schedTrace.log")
if err != nil {
log.Fatal(err)
}
s := &BinPackSortedWattsSortedOffers{
tasks: tasks,
ignoreWatts: ignoreWatts,
Shutdown: make(chan struct{}),
Done: make(chan struct{}),
PCPLog: make(chan struct{}),
running: make(map[string]map[string]bool),
RecordPCP: false,
schedTrace: log.New(logFile, "", log.LstdFlags),
}
return s
}
func (s *BinPackSortedWattsSortedOffers) newTask(offer *mesos.Offer, task def.Task) *mesos.TaskInfo {
taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances)
s.tasksCreated++
if !s.RecordPCP {
// Turn on logging
s.RecordPCP = true
time.Sleep(1 * time.Second) // Make sure we're recording by the time the first task starts
}
// If this is our first time running into this Agent
if _, ok := s.running[offer.GetSlaveId().GoString()]; !ok {
s.running[offer.GetSlaveId().GoString()] = make(map[string]bool)
}
// Add task to list of tasks running on node
s.running[offer.GetSlaveId().GoString()][taskName] = true
resources := []*mesos.Resource{
mesosutil.NewScalarResource("cpus", task.CPU),
mesosutil.NewScalarResource("mem", task.RAM),
}
if !s.ignoreWatts {
resources = append(resources, mesosutil.NewScalarResource("watts", task.Watts))
}
return &mesos.TaskInfo{
Name: proto.String(taskName),
TaskId: &mesos.TaskID{
Value: proto.String("electron-" + taskName),
},
SlaveId: offer.SlaveId,
Resources: resources,
Command: &mesos.CommandInfo{
Value: proto.String(task.CMD),
},
Container: &mesos.ContainerInfo{
Type: mesos.ContainerInfo_DOCKER.Enum(),
Docker: &mesos.ContainerInfo_DockerInfo{
Image: proto.String(task.Image),
Network: mesos.ContainerInfo_DockerInfo_BRIDGE.Enum(), // Run everything isolated
},
},
}
}
func (s *BinPackSortedWattsSortedOffers) ResourceOffers(driver sched.SchedulerDriver, offers []*mesos.Offer) {
log.Printf("Received %d resource offers", len(offers))
// Sorting the offers
sort.Sort(offerUtils.OffersSorter(offers))
// Printing the sorted offers and the corresponding CPU resource availability
log.Println("Sorted Offers:")
for i := 0; i < len(offers); i++ {
offer := offers[i]
offerCPU, _, _ := offerUtils.OfferAgg(offer)
log.Printf("Offer[%s].CPU = %f\n", offer.GetHostname(), offerCPU)
}
for _, offer := range offers {
select {
case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning)
continue
default:
}
tasks := []*mesos.TaskInfo{}
offerTaken := false
totalWatts := 0.0
totalCPU := 0.0
totalRAM := 0.0
for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i]
// Don't take offer if it doesn't match our task's host requirement
if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
continue
}
for *task.Instances > 0 {
// Does the task fit
if s.takeOffer(offer, totalCPU, totalRAM, totalWatts, task) {
offerTaken = true
totalWatts += task.Watts
totalCPU += task.CPU
totalRAM += task.RAM
log.Println("Co-Located with: ")
coLocated(s.running[offer.GetSlaveId().GoString()])
taskToSchedule := s.newTask(offer, task)
tasks = append(tasks, taskToSchedule)
fmt.Println("Inst: ", *task.Instances)
s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue())
*task.Instances--
if *task.Instances <= 0 {
// All instances of task have been scheduled, remove it
s.tasks = append(s.tasks[:i], s.tasks[i+1:]...)
if len(s.tasks) <= 0 {
log.Println("Done scheduling all tasks")
close(s.Shutdown)
}
}
} else {
break // Continue on to next offer
}
}
}
if offerTaken {
log.Printf("Starting on [%s]\n", offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
} else {
// If there was no match for the task
fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
}
}
}
func (s *BinPackSortedWattsSortedOffers) StatusUpdate(driver sched.SchedulerDriver, status *mesos.TaskStatus) {
log.Printf("Received task status [%s] for task [%s]", NameFor(status.State), *status.TaskId.Value)
if *status.State == mesos.TaskState_TASK_RUNNING {
s.tasksRunning++
} else if IsTerminal(status.State) {
delete(s.running[status.GetSlaveId().GoString()], *status.TaskId.Value)
s.tasksRunning--
if s.tasksRunning == 0 {
select {
case <-s.Shutdown:
close(s.Done)
default:
}
}
}
log.Printf("DONE: Task status [%s] for task [%s]", NameFor(status.State), *status.TaskId.Value)
}

View file

@ -4,6 +4,8 @@ import (
"bitbucket.org/sunybingcloud/electron/constants" "bitbucket.org/sunybingcloud/electron/constants"
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/rapl" "bitbucket.org/sunybingcloud/electron/rapl"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"errors" "errors"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
@ -13,7 +15,6 @@ import (
"log" "log"
"math" "math"
"os" "os"
"strings"
"sync" "sync"
"time" "time"
) )
@ -217,7 +218,7 @@ func (s *BinPackedPistonCapper) ResourceOffers(driver sched.SchedulerDriver, off
// retrieving the total power for each host in the offers // retrieving the total power for each host in the offers
for _, offer := range offers { for _, offer := range offers {
if _, ok := s.totalPower[*offer.Hostname]; !ok { if _, ok := s.totalPower[*offer.Hostname]; !ok {
_, _, offer_watts := OfferAgg(offer) _, _, offer_watts := offerUtils.OfferAgg(offer)
s.totalPower[*offer.Hostname] = offer_watts s.totalPower[*offer.Hostname] = offer_watts
} }
} }
@ -238,7 +239,7 @@ func (s *BinPackedPistonCapper) ResourceOffers(driver sched.SchedulerDriver, off
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
@ -246,8 +247,8 @@ func (s *BinPackedPistonCapper) ResourceOffers(driver sched.SchedulerDriver, off
} }
fitTasks := []*mesos.TaskInfo{} fitTasks := []*mesos.TaskInfo{}
offerCPU, offerRAM, offerWatts := OfferAgg(offer) offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
taken := false offerTaken := false
totalWatts := 0.0 totalWatts := 0.0
totalCPU := 0.0 totalCPU := 0.0
totalRAM := 0.0 totalRAM := 0.0
@ -256,13 +257,8 @@ func (s *BinPackedPistonCapper) ResourceOffers(driver sched.SchedulerDriver, off
partialLoad := 0.0 partialLoad := 0.0
for i := 0; i < len(s.tasks); i++ { for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i] task := s.tasks[i]
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {continue}
// Don't take offer if it doens't match our task's host requirement.
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
}
for *task.Instances > 0 { for *task.Instances > 0 {
// Does the task fit // Does the task fit
@ -274,7 +270,7 @@ func (s *BinPackedPistonCapper) ResourceOffers(driver sched.SchedulerDriver, off
s.startCapping() s.startCapping()
} }
taken = true offerTaken = true
totalWatts += task.Watts totalWatts += task.Watts
totalCPU += task.CPU totalCPU += task.CPU
totalRAM += task.RAM totalRAM += task.RAM
@ -303,20 +299,20 @@ func (s *BinPackedPistonCapper) ResourceOffers(driver sched.SchedulerDriver, off
} }
} }
if taken { if offerTaken {
// Updating the cap value for offer.Hostname // Updating the cap value for offer.Hostname
bpPistonMutex.Lock() bpPistonMutex.Lock()
bpPistonCapValues[*offer.Hostname] += partialLoad bpPistonCapValues[*offer.Hostname] += partialLoad
bpPistonMutex.Unlock() bpPistonMutex.Unlock()
log.Printf("Starting on [%s]\n", offer.GetHostname()) log.Printf("Starting on [%s]\n", offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, fitTasks, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, fitTasks, mesosUtils.DefaultFilter)
} else { } else {
// If there was no match for task // If there was no match for task
log.Println("There is not enough resources to launch task: ") log.Println("There is not enough resources to launch task: ")
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }
} }

View file

@ -2,6 +2,8 @@ package schedulers
import ( import (
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto" mesos "github.com/mesos/mesos-go/mesosproto"
@ -10,21 +12,19 @@ import (
"log" "log"
"os" "os"
"sort" "sort"
"strings"
"time" "time"
) )
// Decides if to take an offer or not // Decides if to take an offer or not
func (*BinPackSortedWatts) takeOffer(offer *mesos.Offer, task def.Task) bool { func (s *BinPackSortedWatts) takeOffer(offer *mesos.Offer, totalCPU, totalRAM, totalWatts float64, task def.Task) bool {
offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
cpus, mem, watts := OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter //TODO: Insert watts calculation here instead of taking them as a parameter
if (s.ignoreWatts || (offerWatts >= (totalWatts + task.Watts))) &&
if cpus >= task.CPU && mem >= task.RAM && watts >= task.Watts { (offerCPU >= (totalCPU + task.CPU)) &&
(offerRAM >= (totalRAM + task.RAM)) {
return true return true
} }
return false return false
} }
@ -130,7 +130,7 @@ func (s *BinPackSortedWatts) ResourceOffers(driver sched.SchedulerDriver, offers
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
@ -139,30 +139,23 @@ func (s *BinPackSortedWatts) ResourceOffers(driver sched.SchedulerDriver, offers
tasks := []*mesos.TaskInfo{} tasks := []*mesos.TaskInfo{}
offer_cpu, offer_ram, offer_watts := OfferAgg(offer) offerTaken := false
taken := false
totalWatts := 0.0 totalWatts := 0.0
totalCPU := 0.0 totalCPU := 0.0
totalRAM := 0.0 totalRAM := 0.0
for i := 0; i < len(s.tasks); i++ { for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i] task := s.tasks[i]
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doesn't match our task's host requirement continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
for *task.Instances > 0 { for *task.Instances > 0 {
// Does the task fit // Does the task fit
if (s.ignoreWatts || offer_watts >= (totalWatts+task.Watts)) && if s.takeOffer(offer, totalCPU, totalRAM, totalWatts, task) {
(offer_cpu >= (totalCPU + task.CPU)) &&
(offer_ram >= (totalRAM + task.RAM)) {
taken = true offerTaken = true
totalWatts += task.Watts totalWatts += task.Watts
totalCPU += task.CPU totalCPU += task.CPU
totalRAM += task.RAM totalRAM += task.RAM
@ -190,17 +183,17 @@ func (s *BinPackSortedWatts) ResourceOffers(driver sched.SchedulerDriver, offers
} }
} }
if taken { if offerTaken {
log.Printf("Starting on [%s]\n", offer.GetHostname()) log.Printf("Starting on [%s]\n", offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
} else { } else {
// If there was no match for the task // If there was no match for the task
fmt.Println("There is not enough resources to launch a task:") fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }
} }

340
schedulers/bottomHeavy.go Normal file
View file

@ -0,0 +1,340 @@
package schedulers
import (
"bitbucket.org/sunybingcloud/electron/constants"
"bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt"
"github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto"
"github.com/mesos/mesos-go/mesosutil"
sched "github.com/mesos/mesos-go/scheduler"
"log"
"math"
"os"
"sort"
"time"
)
/*
Tasks are categorized into small and large tasks based on the watts requirement.
All the small tasks are packed into offers from agents belonging to power class C, using BinPacking.
All the large tasks are spread among the offers from agents belonging to power class A and power class B, using FirstFit.
BinPacking has the most effect when co-scheduling of tasks is increased. Large tasks typically utilize more resources and hence,
co-scheduling them has a great impact on the total power utilization.
*/
func (s *BottomHeavy) takeOffer(offer *mesos.Offer, totalCPU, totalRAM, totalWatts,
wattsToConsider float64, task def.Task) bool {
offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter
if (s.ignoreWatts || (offerWatts >= (totalWatts + wattsToConsider))) &&
(offerCPU >= (totalCPU + task.CPU)) &&
(offerRAM >= (totalRAM + task.RAM)) {
return true
}
return false
}
// electronScheduler implements the Scheduler interface
type BottomHeavy struct {
base // Type embedded to inherit common functions
tasksCreated int
tasksRunning int
tasks []def.Task
metrics map[string]def.Metric
running map[string]map[string]bool
ignoreWatts bool
smallTasks, largeTasks []def.Task
// First set of PCP values are garbage values, signal to logger to start recording when we're
// about to schedule a new task
RecordPCP bool
// This channel is closed when the program receives an interrupt,
// signalling that the program should shut down.
Shutdown chan struct{}
// This channel is closed after shutdown is closed, and only when all
// outstanding tasks have been cleaned up
Done chan struct{}
// Controls when to shutdown pcp logging
PCPLog chan struct{}
schedTrace *log.Logger
}
// New electron scheduler
func NewBottomHeavy(tasks []def.Task, ignoreWatts bool, schedTracePrefix string) *BottomHeavy {
sort.Sort(def.WattsSorter(tasks))
logFile, err := os.Create("./" + schedTracePrefix + "_schedTrace.log")
if err != nil {
log.Fatal(err)
}
// Separating small tasks from large tasks.
// Classification done based on MMPU watts requirements.
mid := int(math.Floor((float64(len(tasks)) / 2.0) + 0.5))
s := &BottomHeavy{
smallTasks: tasks[:mid],
largeTasks: tasks[mid+1:],
ignoreWatts: ignoreWatts,
Shutdown: make(chan struct{}),
Done: make(chan struct{}),
PCPLog: make(chan struct{}),
running: make(map[string]map[string]bool),
RecordPCP: false,
schedTrace: log.New(logFile, "", log.LstdFlags),
}
return s
}
func (s *BottomHeavy) newTask(offer *mesos.Offer, task def.Task, newTaskClass string) *mesos.TaskInfo {
taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances)
s.tasksCreated++
if !s.RecordPCP {
// Turn on logging
s.RecordPCP = true
time.Sleep(1 * time.Second) // Make sure we're recording by the time the first task starts
}
// If this is our first time running into this Agent
if _, ok := s.running[offer.GetSlaveId().GoString()]; !ok {
s.running[offer.GetSlaveId().GoString()] = make(map[string]bool)
}
// Add task to list of tasks running on node
s.running[offer.GetSlaveId().GoString()][taskName] = true
resources := []*mesos.Resource{
mesosutil.NewScalarResource("cpus", task.CPU),
mesosutil.NewScalarResource("mem", task.RAM),
}
if !s.ignoreWatts {
resources = append(resources, mesosutil.NewScalarResource("watts", task.ClassToWatts[newTaskClass]))
}
return &mesos.TaskInfo{
Name: proto.String(taskName),
TaskId: &mesos.TaskID{
Value: proto.String("electron-" + taskName),
},
SlaveId: offer.SlaveId,
Resources: resources,
Command: &mesos.CommandInfo{
Value: proto.String(task.CMD),
},
Container: &mesos.ContainerInfo{
Type: mesos.ContainerInfo_DOCKER.Enum(),
Docker: &mesos.ContainerInfo_DockerInfo{
Image: proto.String(task.Image),
Network: mesos.ContainerInfo_DockerInfo_BRIDGE.Enum(), // Run everything isolated
},
},
}
}
// Shut down scheduler if no more tasks to schedule
func (s *BottomHeavy) shutDownIfNecessary() {
if len(s.smallTasks) <= 0 && len(s.largeTasks) <= 0 {
log.Println("Done scheduling all tasks")
close(s.Shutdown)
}
}
// create TaskInfo and log scheduling trace
func (s *BottomHeavy) createTaskInfoAndLogSchedTrace(offer *mesos.Offer,
powerClass string, task def.Task) *mesos.TaskInfo {
log.Println("Co-Located with:")
coLocated(s.running[offer.GetSlaveId().GoString()])
taskToSchedule := s.newTask(offer, task, powerClass)
fmt.Println("Inst: ", *task.Instances)
s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue())
*task.Instances--
return taskToSchedule
}
// Using BinPacking to pack small tasks into this offer.
func (s *BottomHeavy) pack(offers []*mesos.Offer, driver sched.SchedulerDriver) {
for _, offer := range offers {
select {
case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning)
continue
default:
}
tasks := []*mesos.TaskInfo{}
totalWatts := 0.0
totalCPU := 0.0
totalRAM := 0.0
offerTaken := false
for i := 0; i < len(s.largeTasks); i++ {
task := s.largeTasks[i]
for *task.Instances > 0 {
powerClass := offerUtils.PowerClass(offer)
// Does the task fit
// OR lazy evaluation. If ignore watts is set to true, second statement won't
// be evaluated.
wattsToConsider := task.Watts
if !s.ignoreWatts {
wattsToConsider = task.ClassToWatts[powerClass]
}
if s.takeOffer(offer, totalCPU, totalRAM, totalWatts, wattsToConsider, task) {
offerTaken = true
totalWatts += wattsToConsider
totalCPU += task.CPU
totalRAM += task.RAM
tasks = append(tasks, s.createTaskInfoAndLogSchedTrace(offer, powerClass, task))
if *task.Instances <= 0 {
// All instances of task have been scheduled, remove it
s.largeTasks = append(s.largeTasks[:i], s.largeTasks[i+1:]...)
s.shutDownIfNecessary()
}
} else {
break // Continue on to next task
}
}
}
if offerTaken {
log.Printf("Starting on [%s]\n", offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
} else {
// If there was no match for the task
fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
}
}
}
// Using first fit to spread large tasks into these offers.
func (s *BottomHeavy) spread(offers []*mesos.Offer, driver sched.SchedulerDriver) {
for _, offer := range offers {
select {
case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning)
continue
default:
}
tasks := []*mesos.TaskInfo{}
offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
taken := false
for i := 0; i < len(s.smallTasks); i++ {
task := s.smallTasks[i]
powerClass := offerUtils.PowerClass(offer)
// Decision to take the offer or not
wattsToConsider := task.Watts
if !s.ignoreWatts {
wattsToConsider = task.ClassToWatts[powerClass]
}
if (s.ignoreWatts || (offerWatts >= wattsToConsider)) &&
(offerCPU >= task.CPU) && (offerRAM >= task.RAM) {
taken = true
tasks = append(tasks, s.createTaskInfoAndLogSchedTrace(offer, powerClass, task))
log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
if *task.Instances <= 0 {
// All instances of task have been scheduled, remove it
s.smallTasks = append(s.smallTasks[:i], s.smallTasks[i+1:]...)
s.shutDownIfNecessary()
}
break // Offer taken, move on
}
}
if !taken {
// If there was no match for the task
fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
}
}
}
func (s *BottomHeavy) ResourceOffers(driver sched.SchedulerDriver, offers []*mesos.Offer) {
log.Printf("Received %d resource offers", len(offers))
// We need to separate the offers into
// offers from ClassA and ClassB and offers from ClassC.
// Nodes in ClassA and ClassB will be packed with the large tasks.
// Small tasks will be spread out among the nodes in ClassC.
offersClassAB := []*mesos.Offer{}
offersClassC := []*mesos.Offer{}
for _, offer := range offers {
select {
case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning)
continue
default:
}
if constants.PowerClasses["ClassA"][*offer.Hostname] ||
constants.PowerClasses["ClassB"][*offer.Hostname] {
offersClassAB = append(offersClassAB, offer)
} else if constants.PowerClasses["ClassC"][*offer.Hostname] {
offersClassC = append(offersClassC, offer)
}
}
log.Println("Packing Large tasks into ClassAB offers:")
for _, o := range offersClassAB {
log.Println(*o.Hostname)
}
// Packing tasks into offersClassAB
s.pack(offersClassAB, driver)
log.Println("Spreading Small tasks among ClassC offers:")
for _, o := range offersClassC {
log.Println(*o.Hostname)
}
// Spreading tasks among offersClassC
s.spread(offersClassC, driver)
}
func (s *BottomHeavy) StatusUpdate(driver sched.SchedulerDriver, status *mesos.TaskStatus) {
log.Printf("Received task status [%s] for task [%s]", NameFor(status.State), *status.TaskId.Value)
if *status.State == mesos.TaskState_TASK_RUNNING {
s.tasksRunning++
} else if IsTerminal(status.State) {
delete(s.running[status.GetSlaveId().GoString()], *status.TaskId.Value)
s.tasksRunning--
if s.tasksRunning == 0 {
select {
case <-s.Shutdown:
close(s.Done)
default:
}
}
}
log.Printf("DONE: Task status [%s] for task [%s]", NameFor(status.State), *status.TaskId.Value)
}

View file

@ -2,6 +2,8 @@ package schedulers
import ( import (
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto" mesos "github.com/mesos/mesos-go/mesosproto"
@ -10,21 +12,19 @@ import (
"log" "log"
"os" "os"
"sort" "sort"
"strings"
"time" "time"
) )
// Decides if to take an offer or not // Decides if to take an offer or not
func (*BPMaxMinWatts) takeOffer(offer *mesos.Offer, task def.Task) bool { func (s *BPMaxMinWatts) takeOffer(offer *mesos.Offer, totalCPU, totalRAM, totalWatts float64, task def.Task) bool {
offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
cpus, mem, watts := OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter //TODO: Insert watts calculation here instead of taking them as a parameter
if (s.ignoreWatts || (offerWatts >= (totalWatts + task.Watts))) &&
if cpus >= task.CPU && mem >= task.RAM && watts >= task.Watts { (offerCPU >= (totalCPU + task.CPU)) &&
(offerRAM >= (totalRAM + task.RAM)) {
return true return true
} }
return false return false
} }
@ -133,12 +133,8 @@ func (s *BPMaxMinWatts) CheckFit(i int,
totalRAM *float64, totalRAM *float64,
totalWatts *float64) (bool, *mesos.TaskInfo) { totalWatts *float64) (bool, *mesos.TaskInfo) {
offerCPU, offerRAM, offerWatts := OfferAgg(offer)
// Does the task fit // Does the task fit
if (s.ignoreWatts || (offerWatts >= (*totalWatts + task.Watts))) && if s.takeOffer(offer, *totalCPU, *totalRAM, *totalWatts, task) {
(offerCPU >= (*totalCPU + task.CPU)) &&
(offerRAM >= (*totalRAM + task.RAM)) {
*totalWatts += task.Watts *totalWatts += task.Watts
*totalCPU += task.CPU *totalCPU += task.CPU
@ -175,7 +171,7 @@ func (s *BPMaxMinWatts) ResourceOffers(driver sched.SchedulerDriver, offers []*m
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
@ -196,12 +192,9 @@ func (s *BPMaxMinWatts) ResourceOffers(driver sched.SchedulerDriver, offers []*m
for i := len(s.tasks) - 1; i >= 0; i-- { for i := len(s.tasks) - 1; i >= 0; i-- {
task := s.tasks[i] task := s.tasks[i]
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doesn't match our task's host requirement continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
// TODO: Fix this so index doesn't need to be passed // TODO: Fix this so index doesn't need to be passed
@ -217,12 +210,9 @@ func (s *BPMaxMinWatts) ResourceOffers(driver sched.SchedulerDriver, offers []*m
// Pack the rest of the offer with the smallest tasks // Pack the rest of the offer with the smallest tasks
for i, task := range s.tasks { for i, task := range s.tasks {
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doesn't match our task's host requirement continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
for *task.Instances > 0 { for *task.Instances > 0 {
@ -240,15 +230,15 @@ func (s *BPMaxMinWatts) ResourceOffers(driver sched.SchedulerDriver, offers []*m
if offerTaken { if offerTaken {
log.Printf("Starting on [%s]\n", offer.GetHostname()) log.Printf("Starting on [%s]\n", offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
} else { } else {
// If there was no match for the task // If there was no match for the task
fmt.Println("There is not enough resources to launch a task:") fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }
} }

View file

@ -4,6 +4,8 @@ import (
"bitbucket.org/sunybingcloud/electron/constants" "bitbucket.org/sunybingcloud/electron/constants"
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/rapl" "bitbucket.org/sunybingcloud/electron/rapl"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"errors" "errors"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
@ -14,22 +16,21 @@ import (
"math" "math"
"os" "os"
"sort" "sort"
"strings"
"sync" "sync"
"time" "time"
) )
// Decides if to take an offer or not // Decides if to take an offer or not
func (s *BPMaxMinPistonCapping) takeOffer(offer *mesos.Offer, task def.Task) bool { func (s *BPMaxMinPistonCapping) takeOffer(offer *mesos.Offer, totalCPU, totalRAM, totalWatts float64, task def.Task) bool {
offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
cpus, mem, watts := OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter //TODO: Insert watts calculation here instead of taking them as a parameter
// Does the task fit
if cpus >= task.CPU && mem >= task.RAM && watts >= task.Watts { if (s.ignoreWatts || (offerWatts >= (*totalWatts + task.Watts))) &&
(offerCPU >= (*totalCPU + task.CPU)) &&
(offerRAM >= (*totalRAM + task.RAM)) {
return true return true
} }
return false return false
} }
@ -222,12 +223,8 @@ func (s *BPMaxMinPistonCapping) CheckFit(i int,
totalWatts *float64, totalWatts *float64,
partialLoad *float64) (bool, *mesos.TaskInfo) { partialLoad *float64) (bool, *mesos.TaskInfo) {
offerCPU, offerRAM, offerWatts := OfferAgg(offer)
// Does the task fit // Does the task fit
if (s.ignoreWatts || (offerWatts >= (*totalWatts + task.Watts))) && if s.takeOffer(offer, *totalCPU, *totalRAM, *totalWatts, task) {
(offerCPU >= (*totalCPU + task.CPU)) &&
(offerRAM >= (*totalRAM + task.RAM)) {
// Start piston capping if haven't started yet // Start piston capping if haven't started yet
if !s.isCapping { if !s.isCapping {
@ -271,7 +268,7 @@ func (s *BPMaxMinPistonCapping) ResourceOffers(driver sched.SchedulerDriver, off
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
@ -295,12 +292,9 @@ func (s *BPMaxMinPistonCapping) ResourceOffers(driver sched.SchedulerDriver, off
for i := len(s.tasks) - 1; i >= 0; i-- { for i := len(s.tasks) - 1; i >= 0; i-- {
task := s.tasks[i] task := s.tasks[i]
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doesn't match our task's host requirement continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
// TODO: Fix this so index doesn't need to be passed // TODO: Fix this so index doesn't need to be passed
@ -316,12 +310,9 @@ func (s *BPMaxMinPistonCapping) ResourceOffers(driver sched.SchedulerDriver, off
// Pack the rest of the offer with the smallest tasks // Pack the rest of the offer with the smallest tasks
for i, task := range s.tasks { for i, task := range s.tasks {
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doesn't match our task's host requirement continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
for *task.Instances > 0 { for *task.Instances > 0 {
@ -343,15 +334,15 @@ func (s *BPMaxMinPistonCapping) ResourceOffers(driver sched.SchedulerDriver, off
bpMaxMinPistonCappingCapValues[*offer.Hostname] += partialLoad bpMaxMinPistonCappingCapValues[*offer.Hostname] += partialLoad
bpMaxMinPistonCappingMutex.Unlock() bpMaxMinPistonCappingMutex.Unlock()
log.Printf("Starting on [%s]\n", offer.GetHostname()) log.Printf("Starting on [%s]\n", offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
} else { } else {
// If there was no match for the task // If there was no match for the task
fmt.Println("There is not enough resources to launch a task:") fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }
} }

View file

@ -3,8 +3,10 @@ package schedulers
import ( import (
"bitbucket.org/sunybingcloud/electron/constants" "bitbucket.org/sunybingcloud/electron/constants"
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/pcp" powCap "bitbucket.org/sunybingcloud/electron/powerCapping"
"bitbucket.org/sunybingcloud/electron/rapl" "bitbucket.org/sunybingcloud/electron/rapl"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto" mesos "github.com/mesos/mesos-go/mesosproto"
@ -14,21 +16,21 @@ import (
"math" "math"
"os" "os"
"sort" "sort"
"strings"
"sync" "sync"
"time" "time"
) )
// Decides if to take an offer or not // Decides if to take an offer or not
func (s *BPMaxMinProacCC) takeOffer(offer *mesos.Offer, task def.Task) bool { func (s *BPMaxMinProacCC) takeOffer(offer *mesos.Offer, totalCPU, totalRAM, totalWatts float64, task def.Task) bool {
cpus, mem, watts := OfferAgg(offer) offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter //TODO: Insert watts calculation here instead of taking them as a parameter
// Does the task fit
if cpus >= task.CPU && mem >= task.RAM && watts >= task.Watts { if (s.ignoreWatts || (offerWatts >= (*totalWatts + task.Watts))) &&
(offerCPU >= (*totalCPU + task.CPU)) &&
(offerRAM >= (*totalRAM + task.RAM)) {
return true return true
} }
return false return false
} }
@ -43,7 +45,7 @@ type BPMaxMinProacCC struct {
availablePower map[string]float64 availablePower map[string]float64
totalPower map[string]float64 totalPower map[string]float64
ignoreWatts bool ignoreWatts bool
capper *pcp.ClusterwideCapper capper *powCap.ClusterwideCapper
ticker *time.Ticker ticker *time.Ticker
recapTicker *time.Ticker recapTicker *time.Ticker
isCapping bool // indicate whether we are currently performing cluster-wide capping. isCapping bool // indicate whether we are currently performing cluster-wide capping.
@ -86,7 +88,7 @@ func NewBPMaxMinProacCC(tasks []def.Task, ignoreWatts bool, schedTracePrefix str
availablePower: make(map[string]float64), availablePower: make(map[string]float64),
totalPower: make(map[string]float64), totalPower: make(map[string]float64),
RecordPCP: false, RecordPCP: false,
capper: pcp.GetClusterwideCapperInstance(), capper: powCap.GetClusterwideCapperInstance(),
ticker: time.NewTicker(10 * time.Second), ticker: time.NewTicker(10 * time.Second),
recapTicker: time.NewTicker(20 * time.Second), recapTicker: time.NewTicker(20 * time.Second),
isCapping: false, isCapping: false,
@ -246,12 +248,8 @@ func (s *BPMaxMinProacCC) CheckFit(i int,
totalRAM *float64, totalRAM *float64,
totalWatts *float64) (bool, *mesos.TaskInfo) { totalWatts *float64) (bool, *mesos.TaskInfo) {
offerCPU, offerRAM, offerWatts := OfferAgg(offer)
// Does the task fit // Does the task fit
if (s.ignoreWatts || (offerWatts >= (*totalWatts + task.Watts))) && if s.takeOffer(offer, *totalCPU, *totalRAM, *totalWatts, task) {
(offerCPU >= (*totalCPU + task.CPU)) &&
(offerRAM >= (*totalRAM + task.RAM)) {
// Capping the cluster if haven't yet started // Capping the cluster if haven't yet started
if !s.isCapping { if !s.isCapping {
@ -308,7 +306,7 @@ func (s *BPMaxMinProacCC) ResourceOffers(driver sched.SchedulerDriver, offers []
// retrieving the available power for all the hosts in the offers. // retrieving the available power for all the hosts in the offers.
for _, offer := range offers { for _, offer := range offers {
_, _, offerWatts := OfferAgg(offer) _, _, offerWatts := offerUtils.OfferAgg(offer)
s.availablePower[*offer.Hostname] = offerWatts s.availablePower[*offer.Hostname] = offerWatts
// setting total power if the first time // setting total power if the first time
if _, ok := s.totalPower[*offer.Hostname]; !ok { if _, ok := s.totalPower[*offer.Hostname]; !ok {
@ -324,7 +322,7 @@ func (s *BPMaxMinProacCC) ResourceOffers(driver sched.SchedulerDriver, offers []
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
@ -345,12 +343,9 @@ func (s *BPMaxMinProacCC) ResourceOffers(driver sched.SchedulerDriver, offers []
for i := len(s.tasks) - 1; i >= 0; i-- { for i := len(s.tasks) - 1; i >= 0; i-- {
task := s.tasks[i] task := s.tasks[i]
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doesn't match our task's host requirement continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
// TODO: Fix this so index doesn't need to be passed // TODO: Fix this so index doesn't need to be passed
@ -366,12 +361,9 @@ func (s *BPMaxMinProacCC) ResourceOffers(driver sched.SchedulerDriver, offers []
// Pack the rest of the offer with the smallest tasks // Pack the rest of the offer with the smallest tasks
for i, task := range s.tasks { for i, task := range s.tasks {
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doesn't match our task's host requirement continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
for *task.Instances > 0 { for *task.Instances > 0 {
@ -389,15 +381,15 @@ func (s *BPMaxMinProacCC) ResourceOffers(driver sched.SchedulerDriver, offers []
if offerTaken { if offerTaken {
log.Printf("Starting on [%s]\n", offer.GetHostname()) log.Printf("Starting on [%s]\n", offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
} else { } else {
// If there was no match for the task // If there was no match for the task
fmt.Println("There is not enough resources to launch a task:") fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }
} }

View file

@ -2,6 +2,8 @@ package schedulers
import ( import (
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto" mesos "github.com/mesos/mesos-go/mesosproto"
@ -10,21 +12,20 @@ import (
"log" "log"
"os" "os"
"sort" "sort"
"strings"
"time" "time"
) )
// Decides if to take an offer or not // Decides if to take an offer or not
func (*BPSWClassMapWatts) takeOffer(offer *mesos.Offer, task def.Task) bool { func (s *BPSWClassMapWatts) takeOffer(offer *mesos.Offer, totalCPU, totalRAM,
totalWatts float64, powerClass string, task def.Task) bool {
cpus, mem, watts := OfferAgg(offer) offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter //TODO: Insert watts calculation here instead of taking them as a parameter
if (s.ignoreWatts || (offerWatts >= (totalWatts + task.ClassToWatts[powerClass]))) &&
if cpus >= task.CPU && mem >= task.RAM && watts >= task.Watts { (offerCPU >= (totalCPU + task.CPU)) &&
(offerRAM >= (totalRAM + task.RAM)) {
return true return true
} }
return false return false
} }
@ -76,7 +77,7 @@ func NewBPSWClassMapWatts(tasks []def.Task, ignoreWatts bool, schedTracePrefix s
return s return s
} }
func (s *BPSWClassMapWatts) newTask(offer *mesos.Offer, task def.Task, newTaskClass string) *mesos.TaskInfo { func (s *BPSWClassMapWatts) newTask(offer *mesos.Offer, task def.Task, powerClass string) *mesos.TaskInfo {
taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances) taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances)
s.tasksCreated++ s.tasksCreated++
@ -100,7 +101,7 @@ func (s *BPSWClassMapWatts) newTask(offer *mesos.Offer, task def.Task, newTaskCl
} }
if !s.ignoreWatts { if !s.ignoreWatts {
resources = append(resources, mesosutil.NewScalarResource("watts", task.ClassToWatts[newTaskClass])) resources = append(resources, mesosutil.NewScalarResource("watts", task.ClassToWatts[powerClass]))
} }
return &mesos.TaskInfo{ return &mesos.TaskInfo{
@ -130,7 +131,7 @@ func (s *BPSWClassMapWatts) ResourceOffers(driver sched.SchedulerDriver, offers
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
@ -139,45 +140,33 @@ func (s *BPSWClassMapWatts) ResourceOffers(driver sched.SchedulerDriver, offers
tasks := []*mesos.TaskInfo{} tasks := []*mesos.TaskInfo{}
offerCPU, offerRAM, offerWatts := OfferAgg(offer) offerTaken := false
taken := false
totalWatts := 0.0 totalWatts := 0.0
totalCPU := 0.0 totalCPU := 0.0
totalRAM := 0.0 totalRAM := 0.0
for i := 0; i < len(s.tasks); i++ { for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i] task := s.tasks[i]
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doesn't match our task's host requirement continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
for *task.Instances > 0 { for *task.Instances > 0 {
var nodeClass string powerClass := offerUtils.PowerClass(offer)
for _, attr := range offer.GetAttributes() {
if attr.GetName() == "class" {
nodeClass = attr.GetText().GetValue()
}
}
// Does the task fit // Does the task fit
// OR lazy evaluation. If ignore watts is set to true, second statement won't // OR lazy evaluation. If ignore watts is set to true, second statement won't
// be evaluated. // be evaluated.
if (s.ignoreWatts || (offerWatts >= (totalWatts + task.ClassToWatts[nodeClass]))) && if s.takeOffer(offer, totalCPU, totalRAM, totalWatts, powerClass, task) {
(offerCPU >= (totalCPU + task.CPU)) &&
(offerRAM >= (totalRAM + task.RAM)) {
fmt.Println("Watts being used: ", task.ClassToWatts[nodeClass]) fmt.Println("Watts being used: ", task.ClassToWatts[powerClass])
taken = true offerTaken = true
totalWatts += task.ClassToWatts[nodeClass] totalWatts += task.ClassToWatts[powerClass]
totalCPU += task.CPU totalCPU += task.CPU
totalRAM += task.RAM totalRAM += task.RAM
log.Println("Co-Located with: ") log.Println("Co-Located with: ")
coLocated(s.running[offer.GetSlaveId().GoString()]) coLocated(s.running[offer.GetSlaveId().GoString()])
taskToSchedule := s.newTask(offer, task, nodeClass) taskToSchedule := s.newTask(offer, task, powerClass)
tasks = append(tasks, taskToSchedule) tasks = append(tasks, taskToSchedule)
fmt.Println("Inst: ", *task.Instances) fmt.Println("Inst: ", *task.Instances)
@ -199,17 +188,17 @@ func (s *BPSWClassMapWatts) ResourceOffers(driver sched.SchedulerDriver, offers
} }
} }
if taken { if offerTaken {
log.Printf("Starting on [%s]\n", offer.GetHostname()) log.Printf("Starting on [%s]\n", offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
} else { } else {
// If there was no match for the task // If there was no match for the task
fmt.Println("There is not enough resources to launch a task:") fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }
} }

View file

@ -4,6 +4,8 @@ import (
"bitbucket.org/sunybingcloud/electron/constants" "bitbucket.org/sunybingcloud/electron/constants"
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/rapl" "bitbucket.org/sunybingcloud/electron/rapl"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"errors" "errors"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
@ -14,21 +16,21 @@ import (
"math" "math"
"os" "os"
"sort" "sort"
"strings"
"sync" "sync"
"time" "time"
) )
// Decides if to take offer or not // Decides if to take an offer or not
func (s *BPSWClassMapWattsPistonCapping) takeOffer(offer *mesos.Offer, task def.Task) bool { func (s *BPSWClassMapWattsPistonCapping) takeOffer(offer *mesos.Offer, totalCPU, totalRAM,
cpus, mem, watts := OfferAgg(offer) totalWatts float64, powerClass string, task def.Task) bool {
offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter //TODO: Insert watts calculation here instead of taking them as a parameter
if (s.ignoreWatts || (offerWatts >= (totalWatts + task.ClassToWatts[powerClass]))) &&
if cpus >= task.CPU && mem >= task.RAM && watts >= task.Watts { (offerCPU >= (totalCPU + task.CPU)) &&
(offerRAM >= (totalRAM + task.RAM)) {
return true return true
} }
return false return false
} }
@ -89,7 +91,7 @@ func NewBPSWClassMapWattsPistonCapping(tasks []def.Task, ignoreWatts bool, sched
return s return s
} }
func (s *BPSWClassMapWattsPistonCapping) newTask(offer *mesos.Offer, task def.Task, newTaskClass string) *mesos.TaskInfo { func (s *BPSWClassMapWattsPistonCapping) newTask(offer *mesos.Offer, task def.Task, powerClass string) *mesos.TaskInfo {
taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances) taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances)
s.tasksCreated++ s.tasksCreated++
@ -123,7 +125,7 @@ func (s *BPSWClassMapWattsPistonCapping) newTask(offer *mesos.Offer, task def.Ta
} }
if !s.ignoreWatts { if !s.ignoreWatts {
resources = append(resources, mesosutil.NewScalarResource("watts", task.ClassToWatts[newTaskClass])) resources = append(resources, mesosutil.NewScalarResource("watts", task.ClassToWatts[powerClass]))
} }
return &mesos.TaskInfo{ return &mesos.TaskInfo{
@ -215,7 +217,7 @@ func (s *BPSWClassMapWattsPistonCapping) ResourceOffers(driver sched.SchedulerDr
// retrieving the total power for each host in the offers. // retrieving the total power for each host in the offers.
for _, offer := range offers { for _, offer := range offers {
if _, ok := s.totalPower[*offer.Hostname]; !ok { if _, ok := s.totalPower[*offer.Hostname]; !ok {
_, _, offerWatts := OfferAgg(offer) _, _, offerWatts := offerUtils.OfferAgg(offer)
s.totalPower[*offer.Hostname] = offerWatts s.totalPower[*offer.Hostname] = offerWatts
} }
} }
@ -229,7 +231,7 @@ func (s *BPSWClassMapWattsPistonCapping) ResourceOffers(driver sched.SchedulerDr
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
@ -238,9 +240,7 @@ func (s *BPSWClassMapWattsPistonCapping) ResourceOffers(driver sched.SchedulerDr
tasks := []*mesos.TaskInfo{} tasks := []*mesos.TaskInfo{}
offerCPU, offerRAM, offerWatts := OfferAgg(offer) offerTaken := false
taken := false
totalWatts := 0.0 totalWatts := 0.0
totalCPU := 0.0 totalCPU := 0.0
totalRAM := 0.0 totalRAM := 0.0
@ -249,27 +249,17 @@ func (s *BPSWClassMapWattsPistonCapping) ResourceOffers(driver sched.SchedulerDr
partialLoad := 0.0 partialLoad := 0.0
for i := 0; i < len(s.tasks); i++ { for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i] task := s.tasks[i]
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doesn't match our task's host requirement continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
for *task.Instances > 0 { for *task.Instances > 0 {
var nodeClass string powerClass := offerUtils.PowerClass(offer)
for _, attr := range offer.GetAttributes() {
if attr.GetName() == "class" {
nodeClass = attr.GetText().GetValue()
}
}
// Does the task fit // Does the task fit
// OR lazy evaluation. If ignoreWatts is set to true, second statement won't // OR lazy evaluation. If ignoreWatts is set to true, second statement won't
// be evaluated // be evaluated
if (s.ignoreWatts || (offerWatts >= (totalWatts + task.ClassToWatts[nodeClass]))) && if s.takeOffer(offer, totalCPU, totalRAM, totalWatts, powerClass, task) {
(offerCPU >= (totalCPU + task.CPU)) &&
(offerRAM >= (totalRAM + task.RAM)) {
// Start piston capping if haven't started yet // Start piston capping if haven't started yet
if !s.isCapping { if !s.isCapping {
@ -277,14 +267,14 @@ func (s *BPSWClassMapWattsPistonCapping) ResourceOffers(driver sched.SchedulerDr
s.startCapping() s.startCapping()
} }
fmt.Println("Watts being used: ", task.ClassToWatts[nodeClass]) fmt.Println("Watts being used: ", task.ClassToWatts[powerClass])
taken = true offerTaken = true
totalWatts += task.ClassToWatts[nodeClass] totalWatts += task.ClassToWatts[powerClass]
totalCPU += task.CPU totalCPU += task.CPU
totalRAM += task.RAM totalRAM += task.RAM
log.Println("Co-Located with: ") log.Println("Co-Located with: ")
coLocated(s.running[offer.GetSlaveId().GoString()]) coLocated(s.running[offer.GetSlaveId().GoString()])
taskToSchedule := s.newTask(offer, task, nodeClass) taskToSchedule := s.newTask(offer, task, powerClass)
tasks = append(tasks, taskToSchedule) tasks = append(tasks, taskToSchedule)
fmt.Println("Inst: ", *task.Instances) fmt.Println("Inst: ", *task.Instances)
@ -306,20 +296,20 @@ func (s *BPSWClassMapWattsPistonCapping) ResourceOffers(driver sched.SchedulerDr
} }
} }
if taken { if offerTaken {
// Updating the cap value for offer.Hostname // Updating the cap value for offer.Hostname
bpswClassMapWattsPistonMutex.Lock() bpswClassMapWattsPistonMutex.Lock()
bpswClassMapWattsPistonCapValues[*offer.Hostname] += partialLoad bpswClassMapWattsPistonCapValues[*offer.Hostname] += partialLoad
bpswClassMapWattsPistonMutex.Unlock() bpswClassMapWattsPistonMutex.Unlock()
log.Printf("Starting on [%s]\n", offer.GetHostname()) log.Printf("Starting on [%s]\n", offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
} else { } else {
// If there was no match for task // If there was no match for task
log.Println("There is not enough resources to launch task: ") log.Println("There is not enough resources to launch task: ")
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }
} }

View file

@ -3,8 +3,10 @@ package schedulers
import ( import (
"bitbucket.org/sunybingcloud/electron/constants" "bitbucket.org/sunybingcloud/electron/constants"
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/pcp" powCap "bitbucket.org/sunybingcloud/electron/powerCapping"
"bitbucket.org/sunybingcloud/electron/rapl" "bitbucket.org/sunybingcloud/electron/rapl"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto" mesos "github.com/mesos/mesos-go/mesosproto"
@ -14,21 +16,21 @@ import (
"math" "math"
"os" "os"
"sort" "sort"
"strings"
"sync" "sync"
"time" "time"
) )
// Decides if to take an offer or not // Decides if to take an offer or not
func (*BPSWClassMapWattsProacCC) takeOffer(offer *mesos.Offer, task def.Task) bool { func (s *BPSWClassMapWattsProacCC) takeOffer(offer *mesos.Offer, totalCPU, totalRAM,
cpus, mem, watts := OfferAgg(offer) totalWatts float64, powerClass string, task def.Task) bool {
offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
// TODO: Insert watts calculation here instead of taking them as parameter //TODO: Insert watts calculation here instead of taking them as a parameter
if (s.ignoreWatts || (offerWatts >= (totalWatts + task.ClassToWatts[powerClass]))) &&
if cpus >= task.CPU && mem >= task.RAM && watts >= task.Watts { (offerCPU >= (totalCPU + task.CPU)) &&
(offerRAM >= (totalRAM + task.RAM)) {
return true return true
} }
return false return false
} }
@ -43,7 +45,7 @@ type BPSWClassMapWattsProacCC struct {
availablePower map[string]float64 availablePower map[string]float64
totalPower map[string]float64 totalPower map[string]float64
ignoreWatts bool ignoreWatts bool
capper *pcp.ClusterwideCapper capper *powCap.ClusterwideCapper
ticker *time.Ticker ticker *time.Ticker
recapTicker *time.Ticker recapTicker *time.Ticker
isCapping bool // indicate whether we are currently performing cluster-wide capping. isCapping bool // indicate whether we are currently performing cluster-wide capping.
@ -86,7 +88,7 @@ func NewBPSWClassMapWattsProacCC(tasks []def.Task, ignoreWatts bool, schedTraceP
availablePower: make(map[string]float64), availablePower: make(map[string]float64),
totalPower: make(map[string]float64), totalPower: make(map[string]float64),
RecordPCP: false, RecordPCP: false,
capper: pcp.GetClusterwideCapperInstance(), capper: powCap.GetClusterwideCapperInstance(),
ticker: time.NewTicker(10 * time.Second), ticker: time.NewTicker(10 * time.Second),
recapTicker: time.NewTicker(20 * time.Second), recapTicker: time.NewTicker(20 * time.Second),
isCapping: false, isCapping: false,
@ -99,7 +101,7 @@ func NewBPSWClassMapWattsProacCC(tasks []def.Task, ignoreWatts bool, schedTraceP
// mutex // mutex
var bpswClassMapWattsProacCCMutex sync.Mutex var bpswClassMapWattsProacCCMutex sync.Mutex
func (s *BPSWClassMapWattsProacCC) newTask(offer *mesos.Offer, task def.Task, newTaskClass string) *mesos.TaskInfo { func (s *BPSWClassMapWattsProacCC) newTask(offer *mesos.Offer, task def.Task, powerClass string) *mesos.TaskInfo {
taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances) taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances)
s.tasksCreated++ s.tasksCreated++
@ -131,7 +133,7 @@ func (s *BPSWClassMapWattsProacCC) newTask(offer *mesos.Offer, task def.Task, ne
} }
if !s.ignoreWatts { if !s.ignoreWatts {
resources = append(resources, mesosutil.NewScalarResource("watts", task.ClassToWatts[newTaskClass])) resources = append(resources, mesosutil.NewScalarResource("watts", task.ClassToWatts[powerClass]))
} }
return &mesos.TaskInfo{ return &mesos.TaskInfo{
@ -251,7 +253,7 @@ func (s *BPSWClassMapWattsProacCC) ResourceOffers(driver sched.SchedulerDriver,
// retrieving the available power for all the hosts in the offers. // retrieving the available power for all the hosts in the offers.
for _, offer := range offers { for _, offer := range offers {
_, _, offerWatts := OfferAgg(offer) _, _, offerWatts := offerUtils.OfferAgg(offer)
s.availablePower[*offer.Hostname] = offerWatts s.availablePower[*offer.Hostname] = offerWatts
// setting total power if the first time // setting total power if the first time
if _, ok := s.totalPower[*offer.Hostname]; !ok { if _, ok := s.totalPower[*offer.Hostname]; !ok {
@ -267,7 +269,7 @@ func (s *BPSWClassMapWattsProacCC) ResourceOffers(driver sched.SchedulerDriver,
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
@ -276,35 +278,23 @@ func (s *BPSWClassMapWattsProacCC) ResourceOffers(driver sched.SchedulerDriver,
tasks := []*mesos.TaskInfo{} tasks := []*mesos.TaskInfo{}
offerCPU, offerRAM, offerWatts := OfferAgg(offer) offerTaken := false
taken := false
totalWatts := 0.0 totalWatts := 0.0
totalCPU := 0.0 totalCPU := 0.0
totalRAM := 0.0 totalRAM := 0.0
for i := 0; i < len(s.tasks); i++ { for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i] task := s.tasks[i]
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer it it doesn't match our task's host requirement. continue
if strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
for *task.Instances > 0 { for *task.Instances > 0 {
var nodeClass string powerClass := offerUtils.PowerClass(offer)
for _, attr := range offer.GetAttributes() {
if attr.GetName() == "class" {
nodeClass = attr.GetText().GetValue()
}
}
// Does the task fit // Does the task fit
// OR Lazy evaluation. If ignore watts is set to true, second statement won't // OR Lazy evaluation. If ignore watts is set to true, second statement won't
// be evaluated. // be evaluated.
if (s.ignoreWatts || (offerWatts >= (totalWatts + task.ClassToWatts[nodeClass]))) && if s.takeOffer(offer, totalCPU, totalRAM, totalWatts, powerClass, task) {
(offerCPU >= (totalCPU + task.CPU)) &&
(offerRAM >= (totalRAM + task.RAM)) {
// Capping the cluster if haven't yet started // Capping the cluster if haven't yet started
if !s.isCapping { if !s.isCapping {
@ -314,7 +304,7 @@ func (s *BPSWClassMapWattsProacCC) ResourceOffers(driver sched.SchedulerDriver,
s.startCapping() s.startCapping()
} }
fmt.Println("Watts being used: ", task.ClassToWatts[nodeClass]) fmt.Println("Watts being used: ", task.ClassToWatts[powerClass])
tempCap, err := s.capper.FCFSDeterminedCap(s.totalPower, &task) tempCap, err := s.capper.FCFSDeterminedCap(s.totalPower, &task)
if err == nil { if err == nil {
bpswClassMapWattsProacCCMutex.Lock() bpswClassMapWattsProacCCMutex.Lock()
@ -324,13 +314,13 @@ func (s *BPSWClassMapWattsProacCC) ResourceOffers(driver sched.SchedulerDriver,
log.Println("Failed to determine new cluster-wide cap:") log.Println("Failed to determine new cluster-wide cap:")
log.Println(err) log.Println(err)
} }
taken = true offerTaken = true
totalWatts += task.ClassToWatts[nodeClass] totalWatts += task.ClassToWatts[powerClass]
totalCPU += task.CPU totalCPU += task.CPU
totalRAM += task.RAM totalRAM += task.RAM
log.Println("Co-Located with: ") log.Println("Co-Located with: ")
coLocated(s.running[offer.GetSlaveId().GoString()]) coLocated(s.running[offer.GetSlaveId().GoString()])
taskToSchedule := s.newTask(offer, task, nodeClass) taskToSchedule := s.newTask(offer, task, powerClass)
tasks = append(tasks, taskToSchedule) tasks = append(tasks, taskToSchedule)
fmt.Println("Inst: ", *task.Instances) fmt.Println("Inst: ", *task.Instances)
@ -355,16 +345,16 @@ func (s *BPSWClassMapWattsProacCC) ResourceOffers(driver sched.SchedulerDriver,
} }
} }
if taken { if offerTaken {
log.Printf("Starting on [%s]\n", offer.GetHostname()) log.Printf("Starting on [%s]\n", offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
} else { } else {
// If there was no match for the task // If there was no match for the task
fmt.Println("There is not enough resources to launch a task:") fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }
} }

View file

@ -2,6 +2,8 @@ package schedulers
import ( import (
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto" mesos "github.com/mesos/mesos-go/mesosproto"
@ -9,14 +11,13 @@ import (
sched "github.com/mesos/mesos-go/scheduler" sched "github.com/mesos/mesos-go/scheduler"
"log" "log"
"os" "os"
"strings"
"time" "time"
) )
// Decides if to take an offer or not // Decides if to take an offer or not
func (s *FirstFit) takeOffer(offer *mesos.Offer, task def.Task) bool { func (s *FirstFit) takeOffer(offer *mesos.Offer, task def.Task) bool {
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter //TODO: Insert watts calculation here instead of taking them as a parameter
@ -129,7 +130,7 @@ func (s *FirstFit) ResourceOffers(driver sched.SchedulerDriver, offers []*mesos.
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
@ -140,16 +141,13 @@ func (s *FirstFit) ResourceOffers(driver sched.SchedulerDriver, offers []*mesos.
// First fit strategy // First fit strategy
taken := false offerTaken := false
for i := 0; i < len(s.tasks); i++ { for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i] task := s.tasks[i]
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doesn't match our task's host requirement continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
// Decision to take the offer or not // Decision to take the offer or not
@ -162,9 +160,9 @@ func (s *FirstFit) ResourceOffers(driver sched.SchedulerDriver, offers []*mesos.
tasks = append(tasks, taskToSchedule) tasks = append(tasks, taskToSchedule)
log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname()) log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
taken = true offerTaken = true
fmt.Println("Inst: ", *task.Instances) fmt.Println("Inst: ", *task.Instances)
s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue()) s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue())
@ -185,12 +183,12 @@ func (s *FirstFit) ResourceOffers(driver sched.SchedulerDriver, offers []*mesos.
} }
// If there was no match for the task // If there was no match for the task
if !taken { if !offerTaken {
fmt.Println("There is not enough resources to launch a task:") fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }

View file

@ -0,0 +1,226 @@
package schedulers
import (
"bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt"
"github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto"
"github.com/mesos/mesos-go/mesosutil"
sched "github.com/mesos/mesos-go/scheduler"
"log"
"os"
"sort"
"time"
)
// Decides if to take an offer or not
func (s *FirstFitSortedOffers) takeOffer(offer *mesos.Offer, task def.Task) bool {
cpus, mem, watts := offerUtils.OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter
if cpus >= task.CPU && mem >= task.RAM && (s.ignoreWatts || watts >= task.Watts) {
return true
}
return false
}
// electronScheduler implements the Scheduler interface
type FirstFitSortedOffers struct {
base // Type embedded to inherit common functions
tasksCreated int
tasksRunning int
tasks []def.Task
metrics map[string]def.Metric
running map[string]map[string]bool
ignoreWatts bool
// First set of PCP values are garbage values, signal to logger to start recording when we're
// about to schedule a new task
RecordPCP bool
// This channel is closed when the program receives an interrupt,
// signalling that the program should shut down.
Shutdown chan struct{}
// This channel is closed after shutdown is closed, and only when all
// outstanding tasks have been cleaned up
Done chan struct{}
// Controls when to shutdown pcp logging
PCPLog chan struct{}
schedTrace *log.Logger
}
// New electron scheduler
func NewFirstFitSortedOffers(tasks []def.Task, ignoreWatts bool, schedTracePrefix string) *FirstFitSortedOffers {
logFile, err := os.Create("./" + schedTracePrefix + "_schedTrace.log")
if err != nil {
log.Fatal(err)
}
s := &FirstFitSortedOffers{
tasks: tasks,
ignoreWatts: ignoreWatts,
Shutdown: make(chan struct{}),
Done: make(chan struct{}),
PCPLog: make(chan struct{}),
running: make(map[string]map[string]bool),
RecordPCP: false,
schedTrace: log.New(logFile, "", log.LstdFlags),
}
return s
}
func (s *FirstFitSortedOffers) newTask(offer *mesos.Offer, task def.Task) *mesos.TaskInfo {
taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances)
s.tasksCreated++
if !s.RecordPCP {
// Turn on logging
s.RecordPCP = true
time.Sleep(1 * time.Second) // Make sure we're recording by the time the first task starts
}
// If this is our first time running into this Agent
if _, ok := s.running[offer.GetSlaveId().GoString()]; !ok {
s.running[offer.GetSlaveId().GoString()] = make(map[string]bool)
}
// Add task to list of tasks running on node
s.running[offer.GetSlaveId().GoString()][taskName] = true
resources := []*mesos.Resource{
mesosutil.NewScalarResource("cpus", task.CPU),
mesosutil.NewScalarResource("mem", task.RAM),
}
if !s.ignoreWatts {
resources = append(resources, mesosutil.NewScalarResource("watts", task.Watts))
}
return &mesos.TaskInfo{
Name: proto.String(taskName),
TaskId: &mesos.TaskID{
Value: proto.String("electron-" + taskName),
},
SlaveId: offer.SlaveId,
Resources: resources,
Command: &mesos.CommandInfo{
Value: proto.String(task.CMD),
},
Container: &mesos.ContainerInfo{
Type: mesos.ContainerInfo_DOCKER.Enum(),
Docker: &mesos.ContainerInfo_DockerInfo{
Image: proto.String(task.Image),
Network: mesos.ContainerInfo_DockerInfo_BRIDGE.Enum(), // Run everything isolated
},
},
}
}
func (s *FirstFitSortedOffers) ResourceOffers(driver sched.SchedulerDriver, offers []*mesos.Offer) {
log.Printf("Received %d resource offers", len(offers))
// Sorting the offers
sort.Sort(offerUtils.OffersSorter(offers))
// Printing the sorted offers and the corresponding CPU resource availability
log.Println("Sorted Offers:")
for i := 0; i < len(offers); i++ {
offer := offers[i]
offerCPU, _, _ := offerUtils.OfferAgg(offer)
log.Printf("Offer[%s].CPU = %f\n", offer.GetHostname(), offerCPU)
}
for _, offer := range offers {
select {
case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning)
continue
default:
}
tasks := []*mesos.TaskInfo{}
// First fit strategy
offerTaken := false
for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i]
// Don't take offer if it doesn't match our task's host requirement
if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
continue
}
// Decision to take the offer or not
if s.takeOffer(offer, task) {
log.Println("Co-Located with: ")
coLocated(s.running[offer.GetSlaveId().GoString()])
taskToSchedule := s.newTask(offer, task)
tasks = append(tasks, taskToSchedule)
log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
offerTaken = true
fmt.Println("Inst: ", *task.Instances)
s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue())
*task.Instances--
if *task.Instances <= 0 {
// All instances of task have been scheduled, remove it
s.tasks[i] = s.tasks[len(s.tasks)-1]
s.tasks = s.tasks[:len(s.tasks)-1]
if len(s.tasks) <= 0 {
log.Println("Done scheduling all tasks")
close(s.Shutdown)
}
}
break // Offer taken, move on
}
}
// If there was no match for the task
if !offerTaken {
fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
}
}
}
func (s *FirstFitSortedOffers) StatusUpdate(driver sched.SchedulerDriver, status *mesos.TaskStatus) {
log.Printf("Received task status [%s] for task [%s]", NameFor(status.State), *status.TaskId.Value)
if *status.State == mesos.TaskState_TASK_RUNNING {
s.tasksRunning++
} else if IsTerminal(status.State) {
delete(s.running[status.GetSlaveId().GoString()], *status.TaskId.Value)
s.tasksRunning--
if s.tasksRunning == 0 {
select {
case <-s.Shutdown:
close(s.Done)
default:
}
}
}
log.Printf("DONE: Task status [%s] for task [%s]", NameFor(status.State), *status.TaskId.Value)
}

View file

@ -2,6 +2,8 @@ package schedulers
import ( import (
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto" mesos "github.com/mesos/mesos-go/mesosproto"
@ -10,10 +12,22 @@ import (
"log" "log"
"os" "os"
"sort" "sort"
"strings"
"time" "time"
) )
// Decides if to take an offer or not
func (s *FirstFitSortedWattsClassMapWatts) takeOffer(offer *mesos.Offer, powerClass string, task def.Task) bool {
offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter
// Decision to take the offer or not
if (s.ignoreWatts || (offerWatts >= task.ClassToWatts[powerClass])) &&
(offerCPU >= task.CPU) && (offerRAM >= task.RAM) {
return true
}
return false
}
// electron scheduler implements the Scheduler interface // electron scheduler implements the Scheduler interface
type FirstFitSortedWattsClassMapWatts struct { type FirstFitSortedWattsClassMapWatts struct {
base // Type embedded to inherit common features. base // Type embedded to inherit common features.
@ -63,7 +77,7 @@ func NewFirstFitSortedWattsClassMapWatts(tasks []def.Task, ignoreWatts bool, sch
return s return s
} }
func (s *FirstFitSortedWattsClassMapWatts) newTask(offer *mesos.Offer, task def.Task, newTaskClass string) *mesos.TaskInfo { func (s *FirstFitSortedWattsClassMapWatts) newTask(offer *mesos.Offer, task def.Task, powerClass string) *mesos.TaskInfo {
taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances) taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances)
s.tasksCreated++ s.tasksCreated++
@ -87,7 +101,7 @@ func (s *FirstFitSortedWattsClassMapWatts) newTask(offer *mesos.Offer, task def.
} }
if !s.ignoreWatts { if !s.ignoreWatts {
resources = append(resources, mesosutil.NewScalarResource("watts", task.ClassToWatts[newTaskClass])) resources = append(resources, mesosutil.NewScalarResource("watts", task.ClassToWatts[powerClass]))
} }
return &mesos.TaskInfo{ return &mesos.TaskInfo{
@ -117,47 +131,37 @@ func (s *FirstFitSortedWattsClassMapWatts) ResourceOffers(driver sched.Scheduler
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
default: default:
} }
offerCPU, offerRAM, offerWatts := OfferAgg(offer)
// First fit strategy // First fit strategy
taken := false offerTaken := false
for i := 0; i < len(s.tasks); i++ { for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i] task := s.tasks[i]
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doens't match our task's host requirement. continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
// retrieving the node class from the offer // retrieving the powerClass from the offer
var nodeClass string powerClass := offerUtils.PowerClass(offer)
for _, attr := range offer.GetAttributes() {
if attr.GetName() == "class" {
nodeClass = attr.GetText().GetValue()
}
}
// Decision to take the offer or not // Decision to take the offer or not
if (s.ignoreWatts || (offerWatts >= task.ClassToWatts[nodeClass])) && if s.takeOffer(offer, powerClass, task) {
(offerCPU >= task.CPU) && (offerRAM >= task.RAM) { fmt.Println("Watts being used: ", task.ClassToWatts[powerClass])
log.Println("Co-Located with: ") log.Println("Co-Located with: ")
coLocated(s.running[offer.GetSlaveId().GoString()]) coLocated(s.running[offer.GetSlaveId().GoString()])
taskToSchedule := s.newTask(offer, task, nodeClass) taskToSchedule := s.newTask(offer, task, powerClass)
s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue()) s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue())
log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname()) log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, []*mesos.TaskInfo{taskToSchedule}, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, []*mesos.TaskInfo{taskToSchedule}, mesosUtils.DefaultFilter)
taken = true offerTaken = true
fmt.Println("Inst: ", *task.Instances) fmt.Println("Inst: ", *task.Instances)
*task.Instances-- *task.Instances--
if *task.Instances <= 0 { if *task.Instances <= 0 {
@ -174,12 +178,12 @@ func (s *FirstFitSortedWattsClassMapWatts) ResourceOffers(driver sched.Scheduler
} }
// If there was no match for the task // If there was no match for the task
if !taken { if !offerTaken {
fmt.Println("There is not enough resources to launch a task:") fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }

View file

@ -3,8 +3,10 @@ package schedulers
import ( import (
"bitbucket.org/sunybingcloud/electron/constants" "bitbucket.org/sunybingcloud/electron/constants"
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/pcp" powCap "bitbucket.org/sunybingcloud/electron/powerCapping"
"bitbucket.org/sunybingcloud/electron/rapl" "bitbucket.org/sunybingcloud/electron/rapl"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto" mesos "github.com/mesos/mesos-go/mesosproto"
@ -14,11 +16,23 @@ import (
"math" "math"
"os" "os"
"sort" "sort"
"strings"
"sync" "sync"
"time" "time"
) )
// Decides if to take an offer or not
func (s *FirstFitSortedWattsClassMapWattsProacCC) takeOffer(offer *mesos.Offer, powerClass string, task def.Task) bool {
offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter
// Decision to take the offer or not
if (s.ignoreWatts || (offerWatts >= task.ClassToWatts[powerClass])) &&
(offerCPU >= task.CPU) && (offerRAM >= task.RAM) {
return true
}
return false
}
// electron scheduler implements the Scheduler interface // electron scheduler implements the Scheduler interface
type FirstFitSortedWattsClassMapWattsProacCC struct { type FirstFitSortedWattsClassMapWattsProacCC struct {
base // Type embedded to inherit common features. base // Type embedded to inherit common features.
@ -31,7 +45,7 @@ type FirstFitSortedWattsClassMapWattsProacCC struct {
availablePower map[string]float64 availablePower map[string]float64
totalPower map[string]float64 totalPower map[string]float64
ignoreWatts bool ignoreWatts bool
capper *pcp.ClusterwideCapper capper *powCap.ClusterwideCapper
ticker *time.Ticker ticker *time.Ticker
recapTicker *time.Ticker recapTicker *time.Ticker
isCapping bool // indicate whether we are currently performing cluster-wide capping. isCapping bool // indicate whether we are currently performing cluster-wide capping.
@ -74,7 +88,7 @@ func NewFirstFitSortedWattsClassMapWattsProacCC(tasks []def.Task, ignoreWatts bo
availablePower: make(map[string]float64), availablePower: make(map[string]float64),
totalPower: make(map[string]float64), totalPower: make(map[string]float64),
RecordPCP: false, RecordPCP: false,
capper: pcp.GetClusterwideCapperInstance(), capper: powCap.GetClusterwideCapperInstance(),
ticker: time.NewTicker(10 * time.Second), ticker: time.NewTicker(10 * time.Second),
recapTicker: time.NewTicker(20 * time.Second), recapTicker: time.NewTicker(20 * time.Second),
isCapping: false, isCapping: false,
@ -87,7 +101,7 @@ func NewFirstFitSortedWattsClassMapWattsProacCC(tasks []def.Task, ignoreWatts bo
// mutex // mutex
var ffswClassMapWattsProacCCMutex sync.Mutex var ffswClassMapWattsProacCCMutex sync.Mutex
func (s *FirstFitSortedWattsClassMapWattsProacCC) newTask(offer *mesos.Offer, task def.Task, newTaskClass string) *mesos.TaskInfo { func (s *FirstFitSortedWattsClassMapWattsProacCC) newTask(offer *mesos.Offer, task def.Task, powerClass string) *mesos.TaskInfo {
taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances) taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances)
s.tasksCreated++ s.tasksCreated++
@ -119,7 +133,7 @@ func (s *FirstFitSortedWattsClassMapWattsProacCC) newTask(offer *mesos.Offer, ta
} }
if !s.ignoreWatts { if !s.ignoreWatts {
resources = append(resources, mesosutil.NewScalarResource("watts", task.ClassToWatts[newTaskClass])) resources = append(resources, mesosutil.NewScalarResource("watts", task.ClassToWatts[powerClass]))
} }
return &mesos.TaskInfo{ return &mesos.TaskInfo{
@ -239,7 +253,7 @@ func (s *FirstFitSortedWattsClassMapWattsProacCC) ResourceOffers(driver sched.Sc
// retrieving the available power for all the hosts in the offers. // retrieving the available power for all the hosts in the offers.
for _, offer := range offers { for _, offer := range offers {
_, _, offerWatts := OfferAgg(offer) _, _, offerWatts := offerUtils.OfferAgg(offer)
s.availablePower[*offer.Hostname] = offerWatts s.availablePower[*offer.Hostname] = offerWatts
// setting total power if the first time // setting total power if the first time
if _, ok := s.totalPower[*offer.Hostname]; !ok { if _, ok := s.totalPower[*offer.Hostname]; !ok {
@ -255,38 +269,27 @@ func (s *FirstFitSortedWattsClassMapWattsProacCC) ResourceOffers(driver sched.Sc
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
default: default:
} }
offerCPU, offerRAM, offerWatts := OfferAgg(offer)
// First fit strategy // First fit strategy
taken := false offerTaken := false
for i := 0; i < len(s.tasks); i++ { for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i] task := s.tasks[i]
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doens't match our task's host requirement. continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
// Retrieving the node class from the offer // retrieving the powerClass for the offer
var nodeClass string powerClass := offerUtils.PowerClass(offer)
for _, attr := range offer.GetAttributes() {
if attr.GetName() == "class" {
nodeClass = attr.GetText().GetValue()
}
}
// Decision to take the offer or not // Decision to take the offer or not
if (s.ignoreWatts || (offerWatts >= task.ClassToWatts[nodeClass])) && if s.takeOffer(offer, powerClass, task) {
(offerCPU >= task.CPU) && (offerRAM >= task.RAM) {
// Capping the cluster if haven't yet started // Capping the cluster if haven't yet started
if !s.isCapping { if !s.isCapping {
@ -296,7 +299,7 @@ func (s *FirstFitSortedWattsClassMapWattsProacCC) ResourceOffers(driver sched.Sc
s.startCapping() s.startCapping()
} }
fmt.Println("Watts being used: ", task.ClassToWatts[nodeClass]) fmt.Println("Watts being used: ", task.ClassToWatts[powerClass])
tempCap, err := s.capper.FCFSDeterminedCap(s.totalPower, &task) tempCap, err := s.capper.FCFSDeterminedCap(s.totalPower, &task)
if err == nil { if err == nil {
ffswClassMapWattsProacCCMutex.Lock() ffswClassMapWattsProacCCMutex.Lock()
@ -310,12 +313,12 @@ func (s *FirstFitSortedWattsClassMapWattsProacCC) ResourceOffers(driver sched.Sc
log.Println("Co-Located with: ") log.Println("Co-Located with: ")
coLocated(s.running[offer.GetSlaveId().GoString()]) coLocated(s.running[offer.GetSlaveId().GoString()])
taskToSchedule := s.newTask(offer, task, nodeClass) taskToSchedule := s.newTask(offer, task, powerClass)
s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue()) s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue())
log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname()) log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, []*mesos.TaskInfo{taskToSchedule}, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, []*mesos.TaskInfo{taskToSchedule}, mesosUtils.DefaultFilter)
taken = true offerTaken = true
fmt.Println("Inst: ", *task.Instances) fmt.Println("Inst: ", *task.Instances)
*task.Instances-- *task.Instances--
if *task.Instances <= 0 { if *task.Instances <= 0 {
@ -335,12 +338,12 @@ func (s *FirstFitSortedWattsClassMapWattsProacCC) ResourceOffers(driver sched.Sc
} }
// If there was no match for the task // If there was no match for the task
if !taken { if !offerTaken {
fmt.Println("There is not enough resources to launch a task:") fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }
} }

View file

@ -0,0 +1,228 @@
package schedulers
import (
"bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt"
"github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto"
"github.com/mesos/mesos-go/mesosutil"
sched "github.com/mesos/mesos-go/scheduler"
"log"
"os"
"sort"
"time"
)
// Decides if to take an offer or not
func (s *FirstFitSortedWattsSortedOffers) takeOffer(offer *mesos.Offer, task def.Task) bool {
cpus, mem, watts := offerUtils.OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter
if cpus >= task.CPU && mem >= task.RAM && (s.ignoreWatts || watts >= task.Watts) {
return true
}
return false
}
// electronScheduler implements the Scheduler interface
type FirstFitSortedWattsSortedOffers struct {
base // Type embedded to inherit common functions
tasksCreated int
tasksRunning int
tasks []def.Task
metrics map[string]def.Metric
running map[string]map[string]bool
ignoreWatts bool
// First set of PCP values are garbage values, signal to logger to start recording when we're
// about to schedule a new task
RecordPCP bool
// This channel is closed when the program receives an interrupt,
// signalling that the program should shut down.
Shutdown chan struct{}
// This channel is closed after shutdown is closed, and only when all
// outstanding tasks have been cleaned up
Done chan struct{}
// Controls when to shutdown pcp logging
PCPLog chan struct{}
schedTrace *log.Logger
}
// New electron scheduler
func NewFirstFitSortedWattsSortedOffers(tasks []def.Task, ignoreWatts bool, schedTracePrefix string) *FirstFitSortedWattsSortedOffers {
// Sorting the tasks in increasing order of watts requirement.
sort.Sort(def.WattsSorter(tasks))
logFile, err := os.Create("./" + schedTracePrefix + "_schedTrace.log")
if err != nil {
log.Fatal(err)
}
s := &FirstFitSortedWattsSortedOffers{
tasks: tasks,
ignoreWatts: ignoreWatts,
Shutdown: make(chan struct{}),
Done: make(chan struct{}),
PCPLog: make(chan struct{}),
running: make(map[string]map[string]bool),
RecordPCP: false,
schedTrace: log.New(logFile, "", log.LstdFlags),
}
return s
}
func (s *FirstFitSortedWattsSortedOffers) newTask(offer *mesos.Offer, task def.Task) *mesos.TaskInfo {
taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances)
s.tasksCreated++
if !s.RecordPCP {
// Turn on logging
s.RecordPCP = true
time.Sleep(1 * time.Second) // Make sure we're recording by the time the first task starts
}
// If this is our first time running into this Agent
if _, ok := s.running[offer.GetSlaveId().GoString()]; !ok {
s.running[offer.GetSlaveId().GoString()] = make(map[string]bool)
}
// Add task to list of tasks running on node
s.running[offer.GetSlaveId().GoString()][taskName] = true
resources := []*mesos.Resource{
mesosutil.NewScalarResource("cpus", task.CPU),
mesosutil.NewScalarResource("mem", task.RAM),
}
if !s.ignoreWatts {
resources = append(resources, mesosutil.NewScalarResource("watts", task.Watts))
}
return &mesos.TaskInfo{
Name: proto.String(taskName),
TaskId: &mesos.TaskID{
Value: proto.String("electron-" + taskName),
},
SlaveId: offer.SlaveId,
Resources: resources,
Command: &mesos.CommandInfo{
Value: proto.String(task.CMD),
},
Container: &mesos.ContainerInfo{
Type: mesos.ContainerInfo_DOCKER.Enum(),
Docker: &mesos.ContainerInfo_DockerInfo{
Image: proto.String(task.Image),
Network: mesos.ContainerInfo_DockerInfo_BRIDGE.Enum(), // Run everything isolated
},
},
}
}
func (s *FirstFitSortedWattsSortedOffers) ResourceOffers(driver sched.SchedulerDriver, offers []*mesos.Offer) {
// Sorting the offers
sort.Sort(offerUtils.OffersSorter(offers))
// Printing the sorted offers and the corresponding CPU resource availability
log.Println("Sorted Offers:")
for i := 0; i < len(offers); i++ {
offer := offers[i]
offerCPU, _, _ := offerUtils.OfferAgg(offer)
log.Printf("Offer[%s].CPU = %f\n", offer.GetHostname(), offerCPU)
}
log.Printf("Received %d resource offers", len(offers))
for _, offer := range offers {
select {
case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning)
continue
default:
}
tasks := []*mesos.TaskInfo{}
// First fit strategy
offerTaken := false
for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i]
// Don't take offer if it doesn't match our task's host requirement
if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
continue
}
// Decision to take the offer or not
if s.takeOffer(offer, task) {
log.Println("Co-Located with: ")
coLocated(s.running[offer.GetSlaveId().GoString()])
taskToSchedule := s.newTask(offer, task)
tasks = append(tasks, taskToSchedule)
log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
offerTaken = true
fmt.Println("Inst: ", *task.Instances)
s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue())
*task.Instances--
if *task.Instances <= 0 {
// All instances of task have been scheduled, remove it
s.tasks = append(s.tasks[:i], s.tasks[i+1:]...)
if len(s.tasks) <= 0 {
log.Println("Done scheduling all tasks")
close(s.Shutdown)
}
}
break // Offer taken, move on
}
}
// If there was no match for the task
if !offerTaken {
fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
}
}
}
func (s *FirstFitSortedWattsSortedOffers) StatusUpdate(driver sched.SchedulerDriver, status *mesos.TaskStatus) {
log.Printf("Received task status [%s] for task [%s]", NameFor(status.State), *status.TaskId.Value)
if *status.State == mesos.TaskState_TASK_RUNNING {
s.tasksRunning++
} else if IsTerminal(status.State) {
delete(s.running[status.GetSlaveId().GoString()], *status.TaskId.Value)
s.tasksRunning--
if s.tasksRunning == 0 {
select {
case <-s.Shutdown:
close(s.Done)
default:
}
}
}
log.Printf("DONE: Task status [%s] for task [%s]", NameFor(status.State), *status.TaskId.Value)
}

View file

@ -2,6 +2,8 @@ package schedulers
import ( import (
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto" mesos "github.com/mesos/mesos-go/mesosproto"
@ -10,14 +12,13 @@ import (
"log" "log"
"os" "os"
"sort" "sort"
"strings"
"time" "time"
) )
// Decides if to take an offer or not // Decides if to take an offer or not
func (s *FirstFitSortedWatts) takeOffer(offer *mesos.Offer, task def.Task) bool { func (s *FirstFitSortedWatts) takeOffer(offer *mesos.Offer, task def.Task) bool {
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter //TODO: Insert watts calculation here instead of taking them as a parameter
@ -132,7 +133,7 @@ func (s *FirstFitSortedWatts) ResourceOffers(driver sched.SchedulerDriver, offer
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
@ -143,16 +144,13 @@ func (s *FirstFitSortedWatts) ResourceOffers(driver sched.SchedulerDriver, offer
// First fit strategy // First fit strategy
taken := false offerTaken := false
for i := 0; i < len(s.tasks); i++ { for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i] task := s.tasks[i]
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doesn't match our task's host requirement continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
// Decision to take the offer or not // Decision to take the offer or not
@ -165,9 +163,9 @@ func (s *FirstFitSortedWatts) ResourceOffers(driver sched.SchedulerDriver, offer
tasks = append(tasks, taskToSchedule) tasks = append(tasks, taskToSchedule)
log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname()) log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
taken = true offerTaken = true
fmt.Println("Inst: ", *task.Instances) fmt.Println("Inst: ", *task.Instances)
s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue()) s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue())
@ -187,12 +185,12 @@ func (s *FirstFitSortedWatts) ResourceOffers(driver sched.SchedulerDriver, offer
} }
// If there was no match for the task // If there was no match for the task
if !taken { if !offerTaken {
fmt.Println("There is not enough resources to launch a task:") fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }

View file

@ -2,6 +2,8 @@ package schedulers
import ( import (
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto" mesos "github.com/mesos/mesos-go/mesosproto"
@ -9,14 +11,13 @@ import (
sched "github.com/mesos/mesos-go/scheduler" sched "github.com/mesos/mesos-go/scheduler"
"log" "log"
"os" "os"
"strings"
"time" "time"
) )
// Decides if to take an offer or not // Decides if to take an offer or not
func (*FirstFitWattsOnly) takeOffer(offer *mesos.Offer, task def.Task) bool { func (*FirstFitWattsOnly) takeOffer(offer *mesos.Offer, task def.Task) bool {
_, _, watts := OfferAgg(offer) _, _, watts := offerUtils.OfferAgg(offer)
//TODO: Insert watts calculation here instead of taking them as a parameter //TODO: Insert watts calculation here instead of taking them as a parameter
@ -123,7 +124,7 @@ func (s *FirstFitWattsOnly) ResourceOffers(driver sched.SchedulerDriver, offers
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
@ -134,16 +135,13 @@ func (s *FirstFitWattsOnly) ResourceOffers(driver sched.SchedulerDriver, offers
// First fit strategy // First fit strategy
taken := false offerTaken := false
for i := 0; i < len(s.tasks); i++ { for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i] task := s.tasks[i]
// Check host if it exists // Don't take offer if it doesn't match our task's host requirement
if task.Host != "" { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
// Don't take offer if it doesn't match our task's host requirement continue
if !strings.HasPrefix(*offer.Hostname, task.Host) {
continue
}
} }
// Decision to take the offer or not // Decision to take the offer or not
@ -156,9 +154,9 @@ func (s *FirstFitWattsOnly) ResourceOffers(driver sched.SchedulerDriver, offers
tasks = append(tasks, taskToSchedule) tasks = append(tasks, taskToSchedule)
log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname()) log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
taken = true offerTaken = true
fmt.Println("Inst: ", *task.Instances) fmt.Println("Inst: ", *task.Instances)
s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue()) s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue())
@ -179,12 +177,12 @@ func (s *FirstFitWattsOnly) ResourceOffers(driver sched.SchedulerDriver, offers
} }
// If there was no match for the task // If there was no match for the task
if !taken { if !offerTaken {
fmt.Println("There is not enough resources to launch a task:") fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }

View file

@ -2,33 +2,12 @@ package schedulers
import ( import (
"fmt" "fmt"
"github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto"
"log" "log"
"bitbucket.org/sunybingcloud/electron/def"
mesos "github.com/mesos/mesos-go/mesosproto"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
) )
var (
defaultFilter = &mesos.Filters{RefuseSeconds: proto.Float64(1)}
longFilter = &mesos.Filters{RefuseSeconds: proto.Float64(1000)}
)
func OfferAgg(offer *mesos.Offer) (float64, float64, float64) {
var cpus, mem, watts float64
for _, resource := range offer.Resources {
switch resource.GetName() {
case "cpus":
cpus += *resource.GetScalar().Value
case "mem":
mem += *resource.GetScalar().Value
case "watts":
watts += *resource.GetScalar().Value
}
}
return cpus, mem, watts
}
func coLocated(tasks map[string]bool) { func coLocated(tasks map[string]bool) {
for task := range tasks { for task := range tasks {
@ -37,3 +16,22 @@ func coLocated(tasks map[string]bool) {
fmt.Println("---------------------") fmt.Println("---------------------")
} }
/*
Determine the watts value to consider for each task.
This value could either be task.Watts or task.ClassToWatts[<power class>]
If task.ClassToWatts is not present, then return task.Watts (this would be for workloads which don't have classMapWatts)
*/
func wattsToConsider(task def.Task, classMapWatts bool, offer *mesos.Offer) float64 {
if classMapWatts {
// checking if ClassToWatts was present in the workload.
if task.ClassToWatts != nil {
return task.ClassToWatts[offerUtils.PowerClass(offer)]
} else {
return task.Watts
}
} else {
return task.Watts
}
}

View file

@ -3,8 +3,10 @@ package schedulers
import ( import (
"bitbucket.org/sunybingcloud/electron/constants" "bitbucket.org/sunybingcloud/electron/constants"
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/pcp" powCap "bitbucket.org/sunybingcloud/electron/powerCapping"
"bitbucket.org/sunybingcloud/electron/rapl" "bitbucket.org/sunybingcloud/electron/rapl"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto" mesos "github.com/mesos/mesos-go/mesosproto"
@ -13,16 +15,15 @@ import (
"log" "log"
"math" "math"
"os" "os"
"strings"
"sync" "sync"
"time" "time"
) )
// Decides if to take an offer or not // Decides if to take an offer or not
func (_ *ProactiveClusterwideCapFCFS) takeOffer(offer *mesos.Offer, task def.Task) bool { func (s *ProactiveClusterwideCapFCFS) takeOffer(offer *mesos.Offer, task def.Task) bool {
offer_cpu, offer_mem, offer_watts := OfferAgg(offer) offer_cpu, offer_mem, offer_watts := offerUtils.OfferAgg(offer)
if offer_cpu >= task.CPU && offer_mem >= task.RAM && offer_watts >= task.Watts { if offer_cpu >= task.CPU && offer_mem >= task.RAM && (s.ignoreWatts || (offer_watts >= task.Watts)) {
return true return true
} }
return false return false
@ -40,7 +41,7 @@ type ProactiveClusterwideCapFCFS struct {
availablePower map[string]float64 // available power for each node in the cluster. availablePower map[string]float64 // available power for each node in the cluster.
totalPower map[string]float64 // total power for each node in the cluster. totalPower map[string]float64 // total power for each node in the cluster.
ignoreWatts bool ignoreWatts bool
capper *pcp.ClusterwideCapper capper *powCap.ClusterwideCapper
ticker *time.Ticker ticker *time.Ticker
recapTicker *time.Ticker recapTicker *time.Ticker
isCapping bool // indicate whether we are currently performing cluster wide capping. isCapping bool // indicate whether we are currently performing cluster wide capping.
@ -83,7 +84,7 @@ func NewProactiveClusterwideCapFCFS(tasks []def.Task, ignoreWatts bool, schedTra
availablePower: make(map[string]float64), availablePower: make(map[string]float64),
totalPower: make(map[string]float64), totalPower: make(map[string]float64),
RecordPCP: false, RecordPCP: false,
capper: pcp.GetClusterwideCapperInstance(), capper: powCap.GetClusterwideCapperInstance(),
ticker: time.NewTicker(10 * time.Second), ticker: time.NewTicker(10 * time.Second),
recapTicker: time.NewTicker(20 * time.Second), recapTicker: time.NewTicker(20 * time.Second),
isCapping: false, isCapping: false,
@ -240,7 +241,7 @@ func (s *ProactiveClusterwideCapFCFS) ResourceOffers(driver sched.SchedulerDrive
// retrieving the available power for all the hosts in the offers. // retrieving the available power for all the hosts in the offers.
for _, offer := range offers { for _, offer := range offers {
_, _, offer_watts := OfferAgg(offer) _, _, offer_watts := offerUtils.OfferAgg(offer)
s.availablePower[*offer.Hostname] = offer_watts s.availablePower[*offer.Hostname] = offer_watts
// setting total power if the first time. // setting total power if the first time.
if _, ok := s.totalPower[*offer.Hostname]; !ok { if _, ok := s.totalPower[*offer.Hostname]; !ok {
@ -256,7 +257,7 @@ func (s *ProactiveClusterwideCapFCFS) ResourceOffers(driver sched.SchedulerDrive
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
@ -273,12 +274,12 @@ func (s *ProactiveClusterwideCapFCFS) ResourceOffers(driver sched.SchedulerDrive
Cluster wide capping is currently performed at regular intervals of time. Cluster wide capping is currently performed at regular intervals of time.
*/ */
taken := false offerTaken := false
for i := 0; i < len(s.tasks); i++ { for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i] task := s.tasks[i]
// Don't take offer if it doesn't match our task's host requirement. // Don't take offer if it doesn't match our task's host requirement
if !strings.HasPrefix(*offer.Hostname, task.Host) { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
continue continue
} }
@ -291,7 +292,7 @@ func (s *ProactiveClusterwideCapFCFS) ResourceOffers(driver sched.SchedulerDrive
fcfsMutex.Unlock() fcfsMutex.Unlock()
s.startCapping() s.startCapping()
} }
taken = true offerTaken = true
tempCap, err := s.capper.FCFSDeterminedCap(s.totalPower, &task) tempCap, err := s.capper.FCFSDeterminedCap(s.totalPower, &task)
if err == nil { if err == nil {
@ -305,7 +306,7 @@ func (s *ProactiveClusterwideCapFCFS) ResourceOffers(driver sched.SchedulerDrive
log.Printf("Starting on [%s]\n", offer.GetHostname()) log.Printf("Starting on [%s]\n", offer.GetHostname())
taskToSchedule := s.newTask(offer, task) taskToSchedule := s.newTask(offer, task)
toSchedule := []*mesos.TaskInfo{taskToSchedule} toSchedule := []*mesos.TaskInfo{taskToSchedule}
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, toSchedule, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, toSchedule, mesosUtils.DefaultFilter)
log.Printf("Inst: %d", *task.Instances) log.Printf("Inst: %d", *task.Instances)
s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue()) s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue())
*task.Instances-- *task.Instances--
@ -329,12 +330,12 @@ func (s *ProactiveClusterwideCapFCFS) ResourceOffers(driver sched.SchedulerDrive
} }
// If no task fit the offer, then declining the offer. // If no task fit the offer, then declining the offer.
if !taken { if !offerTaken {
log.Printf("There is not enough resources to launch a task on Host: %s\n", offer.GetHostname()) log.Printf("There is not enough resources to launch a task on Host: %s\n", offer.GetHostname())
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }
} }

View file

@ -13,8 +13,10 @@ package schedulers
import ( import (
"bitbucket.org/sunybingcloud/electron/constants" "bitbucket.org/sunybingcloud/electron/constants"
"bitbucket.org/sunybingcloud/electron/def" "bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/pcp" powCap "bitbucket.org/sunybingcloud/electron/powerCapping"
"bitbucket.org/sunybingcloud/electron/rapl" "bitbucket.org/sunybingcloud/electron/rapl"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt" "fmt"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto" mesos "github.com/mesos/mesos-go/mesosproto"
@ -24,16 +26,15 @@ import (
"math" "math"
"os" "os"
"sort" "sort"
"strings"
"sync" "sync"
"time" "time"
) )
// Decides if to taken an offer or not // Decides if to taken an offer or not
func (_ *ProactiveClusterwideCapRanked) takeOffer(offer *mesos.Offer, task def.Task) bool { func (s *ProactiveClusterwideCapRanked) takeOffer(offer *mesos.Offer, task def.Task) bool {
offer_cpu, offer_mem, offer_watts := OfferAgg(offer) offer_cpu, offer_mem, offer_watts := offerUtils.OfferAgg(offer)
if offer_cpu >= task.CPU && offer_mem >= task.RAM && offer_watts >= task.Watts { if offer_cpu >= task.CPU && offer_mem >= task.RAM && (s.ignoreWatts || (offer_watts >= task.Watts)) {
return true return true
} }
return false return false
@ -51,7 +52,7 @@ type ProactiveClusterwideCapRanked struct {
availablePower map[string]float64 // available power for each node in the cluster. availablePower map[string]float64 // available power for each node in the cluster.
totalPower map[string]float64 // total power for each node in the cluster. totalPower map[string]float64 // total power for each node in the cluster.
ignoreWatts bool ignoreWatts bool
capper *pcp.ClusterwideCapper capper *powCap.ClusterwideCapper
ticker *time.Ticker ticker *time.Ticker
recapTicker *time.Ticker recapTicker *time.Ticker
isCapping bool // indicate whether we are currently performing cluster wide capping. isCapping bool // indicate whether we are currently performing cluster wide capping.
@ -94,7 +95,7 @@ func NewProactiveClusterwideCapRanked(tasks []def.Task, ignoreWatts bool, schedT
availablePower: make(map[string]float64), availablePower: make(map[string]float64),
totalPower: make(map[string]float64), totalPower: make(map[string]float64),
RecordPCP: false, RecordPCP: false,
capper: pcp.GetClusterwideCapperInstance(), capper: powCap.GetClusterwideCapperInstance(),
ticker: time.NewTicker(10 * time.Second), ticker: time.NewTicker(10 * time.Second),
recapTicker: time.NewTicker(20 * time.Second), recapTicker: time.NewTicker(20 * time.Second),
isCapping: false, isCapping: false,
@ -251,7 +252,7 @@ func (s *ProactiveClusterwideCapRanked) ResourceOffers(driver sched.SchedulerDri
// retrieving the available power for all the hosts in the offers. // retrieving the available power for all the hosts in the offers.
for _, offer := range offers { for _, offer := range offers {
_, _, offer_watts := OfferAgg(offer) _, _, offer_watts := offerUtils.OfferAgg(offer)
s.availablePower[*offer.Hostname] = offer_watts s.availablePower[*offer.Hostname] = offer_watts
// setting total power if the first time. // setting total power if the first time.
if _, ok := s.totalPower[*offer.Hostname]; !ok { if _, ok := s.totalPower[*offer.Hostname]; !ok {
@ -277,7 +278,7 @@ func (s *ProactiveClusterwideCapRanked) ResourceOffers(driver sched.SchedulerDri
select { select {
case <-s.Shutdown: case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]") log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, longFilter) driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning) log.Println("Number of tasks still running: ", s.tasksRunning)
continue continue
@ -297,12 +298,12 @@ func (s *ProactiveClusterwideCapRanked) ResourceOffers(driver sched.SchedulerDri
Cluster wide capping is currently performed at regular intervals of time. Cluster wide capping is currently performed at regular intervals of time.
*/ */
taken := false offerTaken := false
for i := 0; i < len(s.tasks); i++ { for i := 0; i < len(s.tasks); i++ {
task := s.tasks[i] task := s.tasks[i]
// Don't take offer if it doesn't match our task's host requirement. // Don't take offer if it doesn't match our task's host requirement
if !strings.HasPrefix(*offer.Hostname, task.Host) { if offerUtils.HostMismatch(*offer.Hostname, task.Host) {
continue continue
} }
@ -315,7 +316,7 @@ func (s *ProactiveClusterwideCapRanked) ResourceOffers(driver sched.SchedulerDri
rankedMutex.Unlock() rankedMutex.Unlock()
s.startCapping() s.startCapping()
} }
taken = true offerTaken = true
tempCap, err := s.capper.FCFSDeterminedCap(s.totalPower, &task) tempCap, err := s.capper.FCFSDeterminedCap(s.totalPower, &task)
if err == nil { if err == nil {
@ -328,7 +329,7 @@ func (s *ProactiveClusterwideCapRanked) ResourceOffers(driver sched.SchedulerDri
log.Printf("Starting on [%s]\n", offer.GetHostname()) log.Printf("Starting on [%s]\n", offer.GetHostname())
taskToSchedule := s.newTask(offer, task) taskToSchedule := s.newTask(offer, task)
to_schedule := []*mesos.TaskInfo{taskToSchedule} to_schedule := []*mesos.TaskInfo{taskToSchedule}
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, to_schedule, defaultFilter) driver.LaunchTasks([]*mesos.OfferID{offer.Id}, to_schedule, mesosUtils.DefaultFilter)
log.Printf("Inst: %d", *task.Instances) log.Printf("Inst: %d", *task.Instances)
s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue()) s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue())
*task.Instances-- *task.Instances--
@ -352,12 +353,12 @@ func (s *ProactiveClusterwideCapRanked) ResourceOffers(driver sched.SchedulerDri
} }
// If no tasks fit the offer, then declining the offer. // If no tasks fit the offer, then declining the offer.
if !taken { if !offerTaken {
log.Printf("There is not enough resources to launch a task on Host: %s\n", offer.GetHostname()) log.Printf("There is not enough resources to launch a task on Host: %s\n", offer.GetHostname())
cpus, mem, watts := OfferAgg(offer) cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts) log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, defaultFilter) driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
} }
} }
} }

329
schedulers/topHeavy.go Normal file
View file

@ -0,0 +1,329 @@
package schedulers
import (
"bitbucket.org/sunybingcloud/electron/constants"
"bitbucket.org/sunybingcloud/electron/def"
"bitbucket.org/sunybingcloud/electron/utilities/mesosUtils"
"bitbucket.org/sunybingcloud/electron/utilities/offerUtils"
"fmt"
"github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto"
"github.com/mesos/mesos-go/mesosutil"
sched "github.com/mesos/mesos-go/scheduler"
"log"
"math"
"os"
"sort"
"time"
)
/*
Tasks are categorized into small and large tasks based on the watts requirement.
All the large tasks are packed into offers from agents belonging to power class A and power class B, using BinPacking.
All the small tasks are spread among the offers from agents belonging to power class C, using FirstFit.
This was done to give a little more room for the large tasks (power intensive) for execution and reduce the possibility of
starvation of power intensive tasks.
*/
// electronScheduler implements the Scheduler interface
type TopHeavy struct {
base // Type embedded to inherit common functions
tasksCreated int
tasksRunning int
tasks []def.Task
metrics map[string]def.Metric
running map[string]map[string]bool
ignoreWatts bool
smallTasks, largeTasks []def.Task
// First set of PCP values are garbage values, signal to logger to start recording when we're
// about to schedule a new task
RecordPCP bool
// This channel is closed when the program receives an interrupt,
// signalling that the program should shut down.
Shutdown chan struct{}
// This channel is closed after shutdown is closed, and only when all
// outstanding tasks have been cleaned up
Done chan struct{}
// Controls when to shutdown pcp logging
PCPLog chan struct{}
schedTrace *log.Logger
}
// New electron scheduler
func NewPackSmallSpreadBig(tasks []def.Task, ignoreWatts bool, schedTracePrefix string) *TopHeavy {
sort.Sort(def.WattsSorter(tasks))
logFile, err := os.Create("./" + schedTracePrefix + "_schedTrace.log")
if err != nil {
log.Fatal(err)
}
// Separating small tasks from large tasks.
// Classification done based on MMPU watts requirements.
mid := int(math.Floor((float64(len(tasks)) / 2.0) + 0.5))
s := &TopHeavy{
smallTasks: tasks[:mid],
largeTasks: tasks[mid+1:],
ignoreWatts: ignoreWatts,
Shutdown: make(chan struct{}),
Done: make(chan struct{}),
PCPLog: make(chan struct{}),
running: make(map[string]map[string]bool),
RecordPCP: false,
schedTrace: log.New(logFile, "", log.LstdFlags),
}
return s
}
func (s *TopHeavy) newTask(offer *mesos.Offer, task def.Task, newTaskClass string) *mesos.TaskInfo {
taskName := fmt.Sprintf("%s-%d", task.Name, *task.Instances)
s.tasksCreated++
if !s.RecordPCP {
// Turn on logging
s.RecordPCP = true
time.Sleep(1 * time.Second) // Make sure we're recording by the time the first task starts
}
// If this is our first time running into this Agent
if _, ok := s.running[offer.GetSlaveId().GoString()]; !ok {
s.running[offer.GetSlaveId().GoString()] = make(map[string]bool)
}
// Add task to list of tasks running on node
s.running[offer.GetSlaveId().GoString()][taskName] = true
resources := []*mesos.Resource{
mesosutil.NewScalarResource("cpus", task.CPU),
mesosutil.NewScalarResource("mem", task.RAM),
}
if !s.ignoreWatts {
resources = append(resources, mesosutil.NewScalarResource("watts", task.ClassToWatts[newTaskClass]))
}
return &mesos.TaskInfo{
Name: proto.String(taskName),
TaskId: &mesos.TaskID{
Value: proto.String("electron-" + taskName),
},
SlaveId: offer.SlaveId,
Resources: resources,
Command: &mesos.CommandInfo{
Value: proto.String(task.CMD),
},
Container: &mesos.ContainerInfo{
Type: mesos.ContainerInfo_DOCKER.Enum(),
Docker: &mesos.ContainerInfo_DockerInfo{
Image: proto.String(task.Image),
Network: mesos.ContainerInfo_DockerInfo_BRIDGE.Enum(), // Run everything isolated
},
},
}
}
// Shut down scheduler if no more tasks to schedule
func (s *TopHeavy) shutDownIfNecessary() {
if len(s.smallTasks) <= 0 && len(s.largeTasks) <= 0 {
log.Println("Done scheduling all tasks")
close(s.Shutdown)
}
}
// create TaskInfo and log scheduling trace
func (s *TopHeavy) createTaskInfoAndLogSchedTrace(offer *mesos.Offer,
powerClass string, task def.Task) *mesos.TaskInfo {
log.Println("Co-Located with:")
coLocated(s.running[offer.GetSlaveId().GoString()])
taskToSchedule := s.newTask(offer, task, powerClass)
fmt.Println("Inst: ", *task.Instances)
s.schedTrace.Print(offer.GetHostname() + ":" + taskToSchedule.GetTaskId().GetValue())
*task.Instances--
return taskToSchedule
}
// Using BinPacking to pack small tasks into this offer.
func (s *TopHeavy) pack(offers []*mesos.Offer, driver sched.SchedulerDriver) {
for _, offer := range offers {
select {
case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning)
continue
default:
}
tasks := []*mesos.TaskInfo{}
offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
totalWatts := 0.0
totalCPU := 0.0
totalRAM := 0.0
taken := false
for i := 0; i < len(s.smallTasks); i++ {
task := s.smallTasks[i]
for *task.Instances > 0 {
powerClass := offerUtils.PowerClass(offer)
// Does the task fit
// OR lazy evaluation. If ignore watts is set to true, second statement won't
// be evaluated.
wattsToConsider := task.Watts
if !s.ignoreWatts {
wattsToConsider = task.ClassToWatts[powerClass]
}
if (s.ignoreWatts || (offerWatts >= (totalWatts + wattsToConsider))) &&
(offerCPU >= (totalCPU + task.CPU)) &&
(offerRAM >= (totalRAM + task.RAM)) {
taken = true
totalWatts += wattsToConsider
totalCPU += task.CPU
totalRAM += task.RAM
tasks = append(tasks, s.createTaskInfoAndLogSchedTrace(offer, powerClass, task))
if *task.Instances <= 0 {
// All instances of task have been scheduled, remove it
s.smallTasks = append(s.smallTasks[:i], s.smallTasks[i+1:]...)
s.shutDownIfNecessary()
}
} else {
break // Continue on to next task
}
}
}
if taken {
log.Printf("Starting on [%s]\n", offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
} else {
// If there was no match for the task
fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
}
}
}
// Using first fit to spread large tasks into these offers.
func (s *TopHeavy) spread(offers []*mesos.Offer, driver sched.SchedulerDriver) {
for _, offer := range offers {
select {
case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning)
continue
default:
}
tasks := []*mesos.TaskInfo{}
offerCPU, offerRAM, offerWatts := offerUtils.OfferAgg(offer)
offerTaken := false
for i := 0; i < len(s.largeTasks); i++ {
task := s.largeTasks[i]
powerClass := offerUtils.PowerClass(offer)
// Decision to take the offer or not
wattsToConsider := task.Watts
if !s.ignoreWatts {
wattsToConsider = task.ClassToWatts[powerClass]
}
if (s.ignoreWatts || (offerWatts >= wattsToConsider)) &&
(offerCPU >= task.CPU) && (offerRAM >= task.RAM) {
offerTaken = true
tasks = append(tasks, s.createTaskInfoAndLogSchedTrace(offer, powerClass, task))
log.Printf("Starting %s on [%s]\n", task.Name, offer.GetHostname())
driver.LaunchTasks([]*mesos.OfferID{offer.Id}, tasks, mesosUtils.DefaultFilter)
if *task.Instances <= 0 {
// All instances of task have been scheduled, remove it
s.largeTasks = append(s.largeTasks[:i], s.largeTasks[i+1:]...)
s.shutDownIfNecessary()
}
break // Offer taken, move on
}
}
if !offerTaken {
// If there was no match for the task
fmt.Println("There is not enough resources to launch a task:")
cpus, mem, watts := offerUtils.OfferAgg(offer)
log.Printf("<CPU: %f, RAM: %f, Watts: %f>\n", cpus, mem, watts)
driver.DeclineOffer(offer.Id, mesosUtils.DefaultFilter)
}
}
}
func (s *TopHeavy) ResourceOffers(driver sched.SchedulerDriver, offers []*mesos.Offer) {
log.Printf("Received %d resource offers", len(offers))
// We need to separate the offers into
// offers from ClassA and ClassB and offers from ClassC.
// Offers from ClassA and ClassB would execute the large tasks.
// Offers from ClassC would execute the small tasks.
offersClassAB := []*mesos.Offer{}
offersClassC := []*mesos.Offer{}
for _, offer := range offers {
select {
case <-s.Shutdown:
log.Println("Done scheduling tasks: declining offer on [", offer.GetHostname(), "]")
driver.DeclineOffer(offer.Id, mesosUtils.LongFilter)
log.Println("Number of tasks still running: ", s.tasksRunning)
continue
default:
}
if constants.PowerClasses["ClassA"][*offer.Hostname] ||
constants.PowerClasses["ClassB"][*offer.Hostname] {
offersClassAB = append(offersClassAB, offer)
} else if constants.PowerClasses["ClassC"][*offer.Hostname] {
offersClassC = append(offersClassC, offer)
}
}
log.Println("ClassAB Offers:")
for _, o := range offersClassAB {
log.Println(*o.Hostname)
}
log.Println("ClassC Offers:")
for _, o := range offersClassC {
log.Println(*o.Hostname)
}
// Packing tasks into offersClassC
s.pack(offersClassC, driver)
// Spreading tasks among offersClassAB
s.spread(offersClassAB, driver)
}
func (s *TopHeavy) StatusUpdate(driver sched.SchedulerDriver, status *mesos.TaskStatus) {
log.Printf("Received task status [%s] for task [%s]", NameFor(status.State), *status.TaskId.Value)
if *status.State == mesos.TaskState_TASK_RUNNING {
s.tasksRunning++
} else if IsTerminal(status.State) {
delete(s.running[status.GetSlaveId().GoString()], *status.TaskId.Value)
s.tasksRunning--
if s.tasksRunning == 0 {
select {
case <-s.Shutdown:
close(s.Done)
default:
}
}
}
log.Printf("DONE: Task status [%s] for task [%s]", NameFor(status.State), *status.TaskId.Value)
}

View file

@ -0,0 +1,11 @@
package mesosUtils
import (
"github.com/golang/protobuf/proto"
mesos "github.com/mesos/mesos-go/mesosproto"
)
var (
DefaultFilter = &mesos.Filters{RefuseSeconds: proto.Float64(1)}
LongFilter = &mesos.Filters{RefuseSeconds: proto.Float64(1000)}
)

View file

@ -0,0 +1,62 @@
package offerUtils
import (
mesos "github.com/mesos/mesos-go/mesosproto"
"strings"
)
func OfferAgg(offer *mesos.Offer) (float64, float64, float64) {
var cpus, mem, watts float64
for _, resource := range offer.Resources {
switch resource.GetName() {
case "cpus":
cpus += *resource.GetScalar().Value
case "mem":
mem += *resource.GetScalar().Value
case "watts":
watts += *resource.GetScalar().Value
}
}
return cpus, mem, watts
}
// Determine the power class of the host in the offer
func PowerClass(offer *mesos.Offer) string {
var powerClass string
for _, attr := range offer.GetAttributes() {
if attr.GetName() == "class" {
powerClass = attr.GetText().GetValue()
}
}
return powerClass
}
// Implements the sort.Sort interface to sort Offers based on CPU.
// TODO: Have a generic sorter that sorts based on a defined requirement (CPU, RAM, DISK or Watts)
type OffersSorter []*mesos.Offer
func (offersSorter OffersSorter) Len() int {
return len(offersSorter)
}
func (offersSorter OffersSorter) Swap(i, j int) {
offersSorter[i], offersSorter[j] = offersSorter[j], offersSorter[i]
}
func (offersSorter OffersSorter) Less(i, j int) bool {
// getting CPU resource availability of offersSorter[i]
cpu1, _, _ := OfferAgg(offersSorter[i])
// getting CPU resource availability of offersSorter[j]
cpu2, _, _ := OfferAgg(offersSorter[j])
return cpu1 <= cpu2
}
// Is there a mismatch between the task's host requirement and the host corresponding to the offer.
func HostMismatch(offerHost string, taskHost string) bool {
if taskHost != "" && !strings.HasPrefix(offerHost, taskHost) {
return true
}
return false
}

View file

@ -1,7 +1,7 @@
/* /*
A utility to calculate the running average. A utility to calculate the running average.
One should implement Val() to be able to use this utility. One should implement Val() and ID() to use this utility.
*/ */
package runAvg package runAvg
@ -19,9 +19,9 @@ type Interface interface {
} }
type runningAverageCalculator struct { type runningAverageCalculator struct {
window list.List considerationWindow list.List
windowSize int considerationWindowSize int
currentSum float64 currentSum float64
} }
// singleton instance // singleton instance
@ -31,14 +31,14 @@ var racSingleton *runningAverageCalculator
func getInstance(curSum float64, wSize int) *runningAverageCalculator { func getInstance(curSum float64, wSize int) *runningAverageCalculator {
if racSingleton == nil { if racSingleton == nil {
racSingleton = &runningAverageCalculator{ racSingleton = &runningAverageCalculator{
windowSize: wSize, considerationWindowSize: wSize,
currentSum: curSum, currentSum: curSum,
} }
return racSingleton return racSingleton
} else { } else {
// Updating window size if a new window size is given. // Updating window size if a new window size is given.
if wSize != racSingleton.windowSize { if wSize != racSingleton.considerationWindowSize {
racSingleton.windowSize = wSize racSingleton.considerationWindowSize = wSize
} }
return racSingleton return racSingleton
} }
@ -47,20 +47,20 @@ func getInstance(curSum float64, wSize int) *runningAverageCalculator {
// Compute the running average by adding 'data' to the window. // Compute the running average by adding 'data' to the window.
// Updating currentSum to get constant time complexity for every running average computation. // Updating currentSum to get constant time complexity for every running average computation.
func (rac *runningAverageCalculator) calculate(data Interface) float64 { func (rac *runningAverageCalculator) calculate(data Interface) float64 {
if rac.window.Len() < rac.windowSize { if rac.considerationWindow.Len() < rac.considerationWindowSize {
rac.window.PushBack(data) rac.considerationWindow.PushBack(data)
rac.currentSum += data.Val() rac.currentSum += data.Val()
} else { } else {
// removing the element at the front of the window. // removing the element at the front of the window.
elementToRemove := rac.window.Front() elementToRemove := rac.considerationWindow.Front()
rac.currentSum -= elementToRemove.Value.(Interface).Val() rac.currentSum -= elementToRemove.Value.(Interface).Val()
rac.window.Remove(elementToRemove) rac.considerationWindow.Remove(elementToRemove)
// adding new element to the window // adding new element to the window
rac.window.PushBack(data) rac.considerationWindow.PushBack(data)
rac.currentSum += data.Val() rac.currentSum += data.Val()
} }
return rac.currentSum / float64(rac.window.Len()) return rac.currentSum / float64(rac.considerationWindow.Len())
} }
/* /*
@ -68,9 +68,9 @@ If element with given ID present in the window, then remove it and return (remov
Else, return (nil, error) Else, return (nil, error)
*/ */
func (rac *runningAverageCalculator) removeFromWindow(id string) (interface{}, error) { func (rac *runningAverageCalculator) removeFromWindow(id string) (interface{}, error) {
for element := rac.window.Front(); element != nil; element = element.Next() { for element := rac.considerationWindow.Front(); element != nil; element = element.Next() {
if elementToRemove := element.Value.(Interface); elementToRemove.ID() == id { if elementToRemove := element.Value.(Interface); elementToRemove.ID() == id {
rac.window.Remove(element) rac.considerationWindow.Remove(element)
rac.currentSum -= elementToRemove.Val() rac.currentSum -= elementToRemove.Val()
return elementToRemove, nil return elementToRemove, nil
} }
@ -102,7 +102,7 @@ func Init() {
} }
// Setting parameters to default values. Could also set racSingleton to nil but this leads to unnecessary overhead of creating // Setting parameters to default values. Could also set racSingleton to nil but this leads to unnecessary overhead of creating
// another instance when Calc is called. // another instance when Calc is called.
racSingleton.window.Init() racSingleton.considerationWindow.Init()
racSingleton.windowSize = 0 racSingleton.considerationWindowSize = 0
racSingleton.currentSum = 0.0 racSingleton.currentSum = 0.0
} }