Skip to content

Commit

Permalink
optimize global strategy (#545)
Browse files Browse the repository at this point in the history
  • Loading branch information
DuodenumL authored Feb 7, 2022
1 parent ba5e49e commit b152f31
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 56 deletions.
91 changes: 56 additions & 35 deletions strategy/global.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
package strategy

import (
"container/heap"
"context"
"fmt"
"sort"

"github.com/pkg/errors"

"github.com/projecteru2/core/log"
"github.com/projecteru2/core/types"
"github.com/projecteru2/core/utils"

"github.com/pkg/errors"
)

type infoHeapForGlobalStrategy []Info

// Len .
func (r infoHeapForGlobalStrategy) Len() int {
return len(r)
}

// Less .
func (r infoHeapForGlobalStrategy) Less(i, j int) bool {
return (r[i].Usage + r[i].Rate) < (r[j].Usage + r[j].Rate)
}

// Swap .
func (r infoHeapForGlobalStrategy) Swap(i, j int) {
r[i], r[j] = r[j], r[i]
}

// Push .
func (r *infoHeapForGlobalStrategy) Push(x interface{}) {
*r = append(*r, x.(Info))
}

// Pop .
func (r *infoHeapForGlobalStrategy) Pop() interface{} {
old := *r
n := len(old)
x := old[n-1]
*r = old[:n-1]
return x
}

// GlobalPlan 基于全局资源配额
// 尽量使得资源消耗平均
func GlobalPlan(ctx context.Context, infos []Info, need, total, _ int) (map[string]int, error) {
Expand All @@ -21,40 +51,31 @@ func GlobalPlan(ctx context.Context, infos []Info, need, total, _ int) (map[stri
}
strategyInfos := make([]Info, len(infos))
copy(strategyInfos, infos)
sort.Slice(infos, func(i, j int) bool { return infos[i].Capacity > infos[j].Capacity })
length := len(strategyInfos)
i := 0

deployMap := make(map[string]int)
for need > 0 {
p := i
deploy := 0
delta := 0.0
if i < length-1 {
delta = utils.Round(strategyInfos[i+1].Usage - strategyInfos[i].Usage)
i++
deployMap := map[string]int{}

infoHeap := &infoHeapForGlobalStrategy{}
for _, info := range strategyInfos {
if info.Capacity > 0 {
infoHeap.Push(info)
}
for j := 0; j <= p && need > 0 && delta >= 0; j++ {
// 减枝
if strategyInfos[j].Capacity == 0 {
continue
}
cost := utils.Round(strategyInfos[j].Rate)
deploy = int(delta / cost)
if deploy == 0 {
deploy = 1
}
if deploy > strategyInfos[j].Capacity {
deploy = strategyInfos[j].Capacity
}
if deploy > need {
deploy = need
}
strategyInfos[j].Capacity -= deploy
deployMap[strategyInfos[j].Nodename] += deploy
need -= deploy
}
heap.Init(infoHeap)

for i := 0; i < need; i++ {
if infoHeap.Len() == 0 {
return nil, errors.WithStack(types.NewDetailedErr(types.ErrInsufficientRes,
fmt.Sprintf("need: %d, available: %d", need, i)))
}
infoWithMinUsage := heap.Pop(infoHeap).(Info)
deployMap[infoWithMinUsage.Nodename]++
infoWithMinUsage.Usage += infoWithMinUsage.Rate
infoWithMinUsage.Capacity--

if infoWithMinUsage.Capacity > 0 {
heap.Push(infoHeap, infoWithMinUsage)
}
}

// 这里 need 一定会为 0 出来,因为 volTotal 保证了一定大于 need
// 这里并不需要再次排序了,理论上的排序是基于资源使用率得到的 Deploy 最终方案
log.Debugf(ctx, "[GlobalPlan] strategyInfos: %v", strategyInfos)
Expand Down
133 changes: 112 additions & 21 deletions strategy/global_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/projecteru2/core/types"
)

func TestGlobalPlan1(t *testing.T) {
// normal case
n1 := Info{
Nodename: "n1",
Usage: 0.8,
Expand All @@ -17,64 +20,152 @@ func TestGlobalPlan1(t *testing.T) {
n2 := Info{
Nodename: "n2",
Usage: 0.5,
Rate: 0.11,
Capacity: 1,
Rate: 0.12,
Capacity: 2,
}
n3 := Info{
Nodename: "n3",
Usage: 2.2,
Rate: 0.05,
Capacity: 1,
}
arg := []Info{n3, n2, n1}
arg := []Info{n1, n2, n3}
r, err := GlobalPlan(context.TODO(), arg, 3, 100, 0)
assert.NoError(t, err)
assert.Equal(t, r[arg[0].Nodename], 1)
}
assert.Equal(t, r, map[string]int{"n1": 1, "n2": 2})

func TestGlobalPlan2(t *testing.T) {
n1 := Info{
// normal case 2
n1 = Info{
Nodename: "n1",
Usage: 0.8,
Rate: 0.05,
Capacity: 4,
}
n2 = Info{
Nodename: "n2",
Usage: 0.5,
Rate: 0.35,
Capacity: 1,
}
n3 = Info{
Nodename: "n3",
Usage: 2.2,
Rate: 0.05,
Capacity: 1,
}
arg = []Info{n1, n2, n3}
r, err = GlobalPlan(context.TODO(), arg, 3, 100, 0)
assert.Equal(t, r, map[string]int{"n1": 2, "n2": 1})

// insufficient total
n1 = Info{
Nodename: "n1",
Usage: 0.8,
Rate: 0.05,
Capacity: 4,
}
n2 = Info{
Nodename: "n2",
Usage: 0.5,
Rate: 0.35,
Capacity: 1,
}
n3 = Info{
Nodename: "n3",
Usage: 2.2,
Rate: 0.05,
Capacity: 1,
}
arg = []Info{n1, n2, n3}
r, err = GlobalPlan(context.TODO(), arg, 100, 6, 0)
assert.ErrorIs(t, err, types.ErrInsufficientRes)

// fake total
n1 = Info{
Nodename: "n1",
Usage: 0.8,
Rate: 0.05,
Capacity: 4,
}
n2 = Info{
Nodename: "n2",
Usage: 0.5,
Rate: 0.35,
Capacity: 1,
}
n3 = Info{
Nodename: "n3",
Usage: 2.2,
Rate: 0.05,
Capacity: 1,
}
arg = []Info{n1, n2, n3}
r, err = GlobalPlan(context.TODO(), arg, 10, 100, 0)
assert.ErrorIs(t, err, types.ErrInsufficientRes)

// small rate
n1 = Info{
Nodename: "n1",
Usage: 0.8,
Rate: 0,
Capacity: 1e10,
}
n2 = Info{
Nodename: "n2",
Usage: 0.5,
Rate: 0,
Capacity: 1e10,
}
n3 = Info{
Nodename: "n3",
Usage: 2.2,
Rate: 0,
Capacity: 1e10,
}
arg = []Info{n1, n2, n3}
r, err = GlobalPlan(context.TODO(), arg, 10, 100, 0)
assert.NoError(t, err)
assert.Equal(t, r, map[string]int{"n2": 10})

// old test case 2
n1 = Info{
Nodename: "n1",
Usage: 1.6,
Rate: 0.05,
Capacity: 100,
}
n2 := Info{
n2 = Info{
Nodename: "n2",
Usage: 0.5,
Rate: 0.11,
Capacity: 100,
}
arg := []Info{n2, n1}
r, err := GlobalPlan(context.TODO(), arg, 2, 100, 0)
arg = []Info{n2, n1}
r, err = GlobalPlan(context.TODO(), arg, 2, 100, 0)
assert.NoError(t, err)
assert.Equal(t, r[arg[0].Nodename], 2)
}
assert.Equal(t, r, map[string]int{"n2": 2})

func TestGlobalPlan3(t *testing.T) {
n1 := Info{
// old test case 3
n1 = Info{
Nodename: "n1",
Usage: 0.5259232954545454,
Rate: 0.0000712,
Capacity: 100,
}

r, err := GlobalPlan(context.TODO(), []Info{n1}, 1, 100, 0)
r, err = GlobalPlan(context.TODO(), []Info{n1}, 1, 100, 0)
assert.NoError(t, err)
assert.Equal(t, r["n1"], 1)
}

func TestGlobal3(t *testing.T) {
_, err := GlobalPlan(context.TODO(), []Info{}, 10, 1, 0)
assert.Error(t, err)
nodeInfo := Info{
// old test case 4
n1 = Info{
Nodename: "n1",
Usage: 1,
Rate: 0.3,
Capacity: 100,
Count: 21,
}
r, err := GlobalPlan(context.TODO(), []Info{nodeInfo}, 10, 100, 0)
r, err = GlobalPlan(context.TODO(), []Info{n1}, 10, 100, 0)
assert.NoError(t, err)
assert.Equal(t, r["n1"], 10)
}
Expand Down

0 comments on commit b152f31

Please sign in to comment.