diff --git a/scheduler/rank.go b/scheduler/rank.go index 70a6208ef..86106cf8a 100644 --- a/scheduler/rank.go +++ b/scheduler/rank.go @@ -1,6 +1,10 @@ package scheduler -import "github.com/hashicorp/nomad/nomad/structs" +import ( + "math" + + "github.com/hashicorp/nomad/nomad/structs" +) // Rank is used to provide a score and various ranking metadata // along with a node when iterating. This state can be modified as @@ -108,3 +112,40 @@ func (iter *BinPackIterator) Next() *RankedNode { return option } } + +// scoreFit is used to score the fit based on the Google work published here: +// http://www.columbia.edu/~cs2035/courses/ieor4405.S13/datacenter_scheduling.ppt +// This is equivalent to their BestFit v3 +func scoreFit(node *structs.Node, util *structs.Resources) float64 { + // Determine the node availability + nodeCpu := node.Resources.CPU + if node.Reserved != nil { + nodeCpu -= node.Reserved.CPU + } + nodeMem := float64(node.Resources.MemoryMB) + if node.Reserved != nil { + nodeMem -= float64(node.Reserved.MemoryMB) + } + + // Compute the free percentage + freePctCpu := 1 - (util.CPU / nodeCpu) + freePctRam := 1 - (float64(util.MemoryMB) / nodeMem) + + // Total will be "maximized" the smaller the value is. + // At 100% utilization, the total is 2, while at 0% util it is 20. + total := math.Pow(10, freePctCpu) + math.Pow(10, freePctRam) + + // Invert so that the "maximized" total represents a high-value + // score. Because the floor is 20, we simply use that as an anchor. + // This means at a perfect fit, we return 18 as the score. + score := 20.0 - total + + // Bound the score, just in case + // If the score is over 18, that means we've overfit the node. + if score > 18.0 { + score = 18.0 + } else if score < 0 { + score = 0 + } + return score +} diff --git a/scheduler/rank_test.go b/scheduler/rank_test.go index 16a75b992..b2c7775e3 100644 --- a/scheduler/rank_test.go +++ b/scheduler/rank_test.go @@ -30,3 +30,45 @@ func TestFeasibleRankIterator(t *testing.T) { t.Fatalf("bad: %v", out) } } + +func TestScoreFit(t *testing.T) { + node := mock.Node() + node.Resources = &structs.Resources{ + CPU: 4096, + MemoryMB: 8192, + } + node.Reserved = &structs.Resources{ + CPU: 2048, + MemoryMB: 4096, + } + + // Test a perfect fit + util := &structs.Resources{ + CPU: 2048, + MemoryMB: 4096, + } + score := scoreFit(node, util) + if score != 18.0 { + t.Fatalf("bad: %v", score) + } + + // Test the worst fit + util = &structs.Resources{ + CPU: 0, + MemoryMB: 0, + } + score = scoreFit(node, util) + if score != 0.0 { + t.Fatalf("bad: %v", score) + } + + // Test a mid-case scenario + util = &structs.Resources{ + CPU: 1024, + MemoryMB: 2048, + } + score = scoreFit(node, util) + if score < 10.0 || score > 16.0 { + t.Fatalf("bad: %v", score) + } +}