Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scheduler: more resource list fraction functions #4034

Merged
merged 2 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 24 additions & 6 deletions internal/scheduler/internaltypes/resource_fraction_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package internaltypes

import (
"fmt"
"math"
)

type ResourceFractionList struct {
Expand All @@ -13,6 +14,29 @@ func (rfl ResourceFractionList) IsEmpty() bool {
return rfl.factory == nil
}

func (rfl ResourceFractionList) Multiply(other ResourceFractionList) ResourceFractionList {
assertSameResourceListFactory(rfl.factory, other.factory)
if rfl.IsEmpty() || other.IsEmpty() {
return ResourceFractionList{}
}

result := make([]float64, len(rfl.fractions))
for i, r := range rfl.fractions {
result[i] = r * other.fractions[i]
}
return ResourceFractionList{factory: rfl.factory, fractions: result}
}

func (rfl ResourceFractionList) Max() float64 {
result := math.Inf(-1)
for _, val := range rfl.fractions {
if val > result {
result = val
}
}
return result
}

func (rfl ResourceFractionList) GetByName(name string) (float64, error) {
if rfl.IsEmpty() {
return 0, fmt.Errorf("resource type %s not found as resource fraction list is empty", name)
Expand All @@ -23,9 +47,3 @@ func (rfl ResourceFractionList) GetByName(name string) (float64, error) {
}
return rfl.fractions[index], nil
}

func (rfl ResourceFractionList) assertSameResourceListFactory(other ResourceList) {
if rfl.factory != nil && other.factory != nil && rfl.factory != other.factory {
panic("mismatched ResourceListFactory")
}
}
35 changes: 32 additions & 3 deletions internal/scheduler/internaltypes/resource_fraction_list_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package internaltypes

import (
"math"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -10,13 +11,41 @@ func TestResourceFractionList_IsEmpty(t *testing.T) {
factory := testFactory()

assert.True(t, ResourceFractionList{}.IsEmpty())
assert.False(t, testResourceFractionList(factory, 0, 0).IsEmpty())
assert.False(t, testResourceFractionList(factory, 1, 1).IsEmpty())
assert.False(t, testResourceFractionList(factory, 0, 0, 0).IsEmpty())
assert.False(t, testResourceFractionList(factory, 1, 1, 1).IsEmpty())
}

func TestMax(t *testing.T) {
factory := testFactory()
assert.Equal(t, 0.0, testResourceFractionList(factory, -0.1, 0.0, 0.0).Max())
assert.Equal(t, 0.0, testResourceFractionList(factory, 0.0, 0.0, 0.0).Max())
assert.Equal(t, 0.9, testResourceFractionList(factory, 0.1, 0.9, 0.7).Max())
}

func TestMax_HandlesEmptyCorrectly(t *testing.T) {
assert.Equal(t, math.Inf(-1), ResourceFractionList{}.Max())
}

func TestResourceFractionList_Multiply(t *testing.T) {
factory := testFactory()

assert.Equal(t,
testResourceFractionList(factory, 0.125, 0.25, 1),
testResourceFractionList(factory, 0.25, 0.5, 1).Multiply(
testResourceFractionList(factory, 0.5, 0.5, 1)))
}

func TestResourceFractionList_Multiply_HandlesEmptyCorrectly(t *testing.T) {
factory := testFactory()

assert.Equal(t, ResourceFractionList{}, ResourceFractionList{}.Multiply(ResourceFractionList{}))
assert.Equal(t, ResourceFractionList{}, ResourceFractionList{}.Multiply(testResourceFractionList(factory, 1, 1, 1)))
assert.Equal(t, ResourceFractionList{}, testResourceFractionList(factory, 1, 1, 1).Multiply(ResourceFractionList{}))
}

func TestResourceFractionList_GetByName(t *testing.T) {
factory := testFactory()
a := testResourceFractionList(factory, 0.1, 0.2)
a := testResourceFractionList(factory, 0.1, 0.2, 1)

cpu, err := a.GetByName("cpu")
assert.Nil(t, err)
Expand Down
35 changes: 28 additions & 7 deletions internal/scheduler/internaltypes/resource_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type Resource struct {
}

func (rl ResourceList) Equal(other ResourceList) bool {
rl.assertSameResourceListFactory(other)
assertSameResourceListFactory(rl.factory, other.factory)
if rl.IsEmpty() && other.IsEmpty() {
return true
}
Expand Down Expand Up @@ -157,7 +157,7 @@ func (rl ResourceList) IsEmpty() bool {
// - if no resources in this ResourceList exceed available, the last return value is false.
// - empty resource lists are considered equivalent to all zero.
func (rl ResourceList) ExceedsAvailable(available ResourceList) (string, k8sResource.Quantity, k8sResource.Quantity, bool) {
rl.assertSameResourceListFactory(available)
assertSameResourceListFactory(rl.factory, available.factory)

if rl.IsEmpty() && available.IsEmpty() {
return "", k8sResource.Quantity{}, k8sResource.Quantity{}, false
Expand Down Expand Up @@ -196,7 +196,7 @@ func (rl ResourceList) OfType(t ResourceType) ResourceList {
}

func (rl ResourceList) Add(other ResourceList) ResourceList {
rl.assertSameResourceListFactory(other)
assertSameResourceListFactory(rl.factory, other.factory)
if rl.IsEmpty() {
return other
}
Expand All @@ -211,7 +211,7 @@ func (rl ResourceList) Add(other ResourceList) ResourceList {
}

func (rl ResourceList) Subtract(other ResourceList) ResourceList {
rl.assertSameResourceListFactory(other)
assertSameResourceListFactory(rl.factory, other.factory)
if other.IsEmpty() {
return rl
}
Expand All @@ -226,7 +226,7 @@ func (rl ResourceList) Subtract(other ResourceList) ResourceList {
}

func (rl ResourceList) Multiply(multipliers ResourceFractionList) ResourceList {
multipliers.assertSameResourceListFactory(rl)
assertSameResourceListFactory(rl.factory, multipliers.factory)
if rl.IsEmpty() || multipliers.IsEmpty() {
return ResourceList{}
}
Expand All @@ -238,6 +238,23 @@ func (rl ResourceList) Multiply(multipliers ResourceFractionList) ResourceList {
return ResourceList{factory: rl.factory, resources: result}
}

// Divide, return 0 on attempt to divide by 0
func (rl ResourceList) DivideZeroOnError(other ResourceList) ResourceFractionList {
assertSameResourceListFactory(rl.factory, other.factory)
if rl.IsEmpty() || other.IsEmpty() {
return ResourceFractionList{}
}

result := make([]float64, len(rl.resources))
for i, r := range rl.resources {
denom := other.resources[i]
if denom != 0 {
result[i] = float64(r) / float64(denom)
}
}
return ResourceFractionList{factory: rl.factory, fractions: result}
}

func (rl ResourceList) Negate() ResourceList {
if rl.IsEmpty() {
return rl
Expand Down Expand Up @@ -274,12 +291,16 @@ func resourcesZeroIfEmpty(resources []int64, factory *ResourceListFactory) []int
return resources
}

func (rl ResourceList) assertSameResourceListFactory(other ResourceList) {
if rl.factory != nil && other.factory != nil && rl.factory != other.factory {
func assertSameResourceListFactory(a, b *ResourceListFactory) {
if a != nil && b != nil && a != b {
panic("mismatched ResourceListFactory")
}
}

func multiplyResource(res int64, multiplier float64) int64 {
if multiplier == 1.0 {
// avoid rounding error in the simple case
return res
}
return int64(float64(res) * multiplier)
}
40 changes: 34 additions & 6 deletions internal/scheduler/internaltypes/resource_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,25 +258,53 @@ func TestMultiply(t *testing.T) {
assert.Equal(t,
testResourceList(factory, "100", "150Ki"),
testResourceList(factory, "400", "200Ki").Multiply(
testResourceFractionList(factory, 0.25, 0.75)))
testResourceFractionList(factory, 0.25, 0.75, 1)))
assert.Equal(t,
testResourceList(factory, "0", "0"),
testResourceList(factory, "0", "200Ki").Multiply(
testResourceFractionList(factory, 0.25, 0)))
testResourceFractionList(factory, 0.25, 0, 1)))
assert.Equal(t,
testResourceList(factory, "2", "100Ki"),
testResourceList(factory, "2", "100Ki").Multiply(
testResourceFractionList(factory, 1, 1, 1)))
assert.Equal(t,
testResourceList(factory, "-100", "150Ki"),
testResourceList(factory, "400", "-200Ki").Multiply(
testResourceFractionList(factory, -0.25, -0.75)))
testResourceFractionList(factory, -0.25, -0.75, 1)))
}

func TestMultiply_HandlesEmptyCorrectly(t *testing.T) {
factory := testFactory()

assert.Equal(t, ResourceList{}, ResourceList{}.Multiply(ResourceFractionList{}))
assert.Equal(t, ResourceList{}, ResourceList{}.Multiply(testResourceFractionList(factory, 1, 1)))
assert.Equal(t, ResourceList{}, ResourceList{}.Multiply(testResourceFractionList(factory, 1, 1, 1)))
assert.Equal(t, ResourceList{}, testResourceList(factory, "1", "1Ki").Multiply(ResourceFractionList{}))
}

func TestDivideZeroOnError(t *testing.T) {
factory := testFactory()

expected := testResourceFractionList(factory, 0.5, 0.25, 0)
actual := testResourceList(factory, "2", "2Ki").DivideZeroOnError(testResourceList(factory, "4", "8Ki"))
assert.Equal(t, expected, actual)
}

func TestDDivideZeroOnError_HandlesZeroDenominatorCorrectly(t *testing.T) {
factory := testFactory()

expected := testResourceFractionList(factory, 2, 0, 0)
actual := testResourceList(factory, "2", "2Ki").DivideZeroOnError(testResourceList(factory, "1", "0Ki"))
assert.Equal(t, expected, actual)
}

func TestDivideZeroOnError_HandlesEmptyCorrectly(t *testing.T) {
factory := testFactory()

assert.Equal(t, ResourceFractionList{}, testResourceList(factory, "1", "1Ki").DivideZeroOnError(ResourceList{}))
assert.Equal(t, ResourceFractionList{}, ResourceList{}.DivideZeroOnError(testResourceList(factory, "1", "1Ki")))
assert.Equal(t, ResourceFractionList{}, ResourceList{}.DivideZeroOnError(ResourceList{}))
}

func TestNegate(t *testing.T) {
factory := testFactory()

Expand Down Expand Up @@ -308,9 +336,9 @@ func testResourceList(factory *ResourceListFactory, cpu string, memory string) R
})
}

func testResourceFractionList(factory *ResourceListFactory, cpu float64, memory float64) ResourceFractionList {
func testResourceFractionList(factory *ResourceListFactory, cpu float64, memory float64, defaultValue float64) ResourceFractionList {
return factory.MakeResourceFractionList(map[string]float64{
"cpu": cpu,
"memory": memory,
}, 1)
}, defaultValue)
}