forked from TrueCloudLab/frostfs-node
[#9999] Add mClock implementation
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
This commit is contained in:
parent
10f683a2f5
commit
fb15809357
2 changed files with 290 additions and 0 deletions
231
pkg/core/quota/mclock.go
Normal file
231
pkg/core/quota/mclock.go
Normal file
|
@ -0,0 +1,231 @@
|
|||
package quota
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"math"
|
||||
)
|
||||
|
||||
const (
|
||||
invalidIndex = -1
|
||||
zeroReservation = math.SmallestNonzeroFloat64
|
||||
maxLimit = math.MaxFloat64
|
||||
)
|
||||
|
||||
type mQueueItem interface {
|
||||
tag() float64
|
||||
setIndex(idx int)
|
||||
}
|
||||
|
||||
type mQueue struct {
|
||||
items []mQueueItem
|
||||
}
|
||||
|
||||
type request struct {
|
||||
vm string
|
||||
seqNumber uint64
|
||||
|
||||
reservation float64
|
||||
limit float64
|
||||
shares float64
|
||||
|
||||
reservationIdx int
|
||||
limitIdx int
|
||||
sharesIdx int
|
||||
}
|
||||
|
||||
type vmData struct {
|
||||
reservation float64
|
||||
limit float64
|
||||
shares float64
|
||||
}
|
||||
|
||||
type clock interface {
|
||||
now() float64
|
||||
}
|
||||
|
||||
type worker interface {
|
||||
do(vm string, seqNumber uint64)
|
||||
}
|
||||
|
||||
type mClockQueue struct {
|
||||
current uint64
|
||||
limit uint64
|
||||
clock clock
|
||||
vmData map[string]vmData
|
||||
previous map[string]*request
|
||||
|
||||
reservationQueue *mQueue
|
||||
limitQueue *mQueue
|
||||
sharesQueue *mQueue
|
||||
|
||||
index uint64
|
||||
worker worker
|
||||
}
|
||||
|
||||
func (q *mClockQueue) RequestArrival(vm string) {
|
||||
now := q.clock.now()
|
||||
vmData, ok := q.vmData[vm]
|
||||
if !ok {
|
||||
panic("unknown vm: " + vm)
|
||||
}
|
||||
prev, ok := q.previous[vm]
|
||||
if !ok {
|
||||
panic("unknown previous: " + vm)
|
||||
}
|
||||
r := request{
|
||||
vm: vm,
|
||||
reservation: max(prev.reservation+1.0/vmData.reservation, now),
|
||||
limit: max(prev.limit+1.0/vmData.limit, now),
|
||||
shares: max(prev.shares+1.0/vmData.shares, now),
|
||||
seqNumber: q.index,
|
||||
reservationIdx: invalidIndex,
|
||||
limitIdx: invalidIndex,
|
||||
sharesIdx: invalidIndex,
|
||||
}
|
||||
q.index++
|
||||
q.previous[vm] = &r
|
||||
heap.Push(q.reservationQueue, &reservationMQueueItem{r: &r})
|
||||
heap.Push(q.limitQueue, &limitMQueueItem{r: &r})
|
||||
q.scheduleRequest()
|
||||
}
|
||||
|
||||
func (q *mClockQueue) scheduleRequest() {
|
||||
if q.current >= q.limit {
|
||||
return
|
||||
}
|
||||
now := q.clock.now()
|
||||
if q.reservationQueue.Len() > 0 && q.reservationQueue.items[0].tag() <= now {
|
||||
it := heap.Pop(q.reservationQueue).(*reservationMQueueItem)
|
||||
if it.r.limitIdx != invalidIndex {
|
||||
heap.Remove(q.limitQueue, it.r.limitIdx)
|
||||
}
|
||||
if it.r.sharesIdx != invalidIndex {
|
||||
heap.Remove(q.sharesQueue, it.r.sharesIdx)
|
||||
}
|
||||
assertIndexInvalid(it.r)
|
||||
q.current++
|
||||
q.worker.do(it.r.vm, it.r.seqNumber)
|
||||
return
|
||||
}
|
||||
|
||||
for q.limitQueue.Len() > 0 && q.limitQueue.items[0].tag() <= now {
|
||||
it := heap.Pop(q.limitQueue).(*limitMQueueItem)
|
||||
heap.Push(q.sharesQueue, &sharesMQueueItem{r: it.r})
|
||||
}
|
||||
|
||||
if q.sharesQueue.Len() > 0 {
|
||||
it := heap.Pop(q.sharesQueue).(*sharesMQueueItem)
|
||||
if it.r.reservationIdx != invalidIndex {
|
||||
heap.Remove(q.reservationQueue, it.r.reservationIdx)
|
||||
}
|
||||
var updated bool
|
||||
vmData, ok := q.vmData[it.r.vm]
|
||||
if !ok {
|
||||
panic("unknown vm: " + it.r.vm)
|
||||
}
|
||||
for _, i := range q.reservationQueue.items {
|
||||
ri := i.(*reservationMQueueItem)
|
||||
if ri.r.vm == it.r.vm {
|
||||
ri.r.reservation -= 1.0 / vmData.reservation
|
||||
updated = true
|
||||
}
|
||||
}
|
||||
if updated {
|
||||
heap.Init(q.reservationQueue)
|
||||
}
|
||||
assertIndexInvalid(it.r)
|
||||
q.current++
|
||||
q.worker.do(it.r.vm, it.r.seqNumber)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (q *mClockQueue) requestCompleted() {
|
||||
if q.current == 0 {
|
||||
panic("invalid requets count")
|
||||
}
|
||||
q.current--
|
||||
q.scheduleRequest()
|
||||
}
|
||||
|
||||
func assertIndexInvalid(r *request) {
|
||||
if r.limitIdx != invalidIndex {
|
||||
panic("limitIdx is not -1")
|
||||
}
|
||||
if r.sharesIdx != invalidIndex {
|
||||
panic("sharesIdx is not -1")
|
||||
}
|
||||
if r.reservationIdx != invalidIndex {
|
||||
panic("reservationIdx is not -1")
|
||||
}
|
||||
}
|
||||
|
||||
// Len implements heap.Interface.
|
||||
func (q *mQueue) Len() int {
|
||||
return len(q.items)
|
||||
}
|
||||
|
||||
// Less implements heap.Interface.
|
||||
func (q *mQueue) Less(i int, j int) bool {
|
||||
return q.items[i].tag() < q.items[j].tag()
|
||||
}
|
||||
|
||||
// Pop implements heap.Interface.
|
||||
func (q *mQueue) Pop() any {
|
||||
n := len(q.items)
|
||||
item := q.items[n-1]
|
||||
q.items[n-1] = nil
|
||||
q.items = q.items[0 : n-1]
|
||||
item.setIndex(-1)
|
||||
return item
|
||||
}
|
||||
|
||||
// Push implements heap.Interface.
|
||||
func (q *mQueue) Push(x any) {
|
||||
it := x.(mQueueItem)
|
||||
it.setIndex(q.Len())
|
||||
q.items = append(q.items, it)
|
||||
}
|
||||
|
||||
// Swap implements heap.Interface.
|
||||
func (q *mQueue) Swap(i int, j int) {
|
||||
q.items[i], q.items[j] = q.items[j], q.items[i]
|
||||
q.items[i].setIndex(i)
|
||||
q.items[j].setIndex(j)
|
||||
}
|
||||
|
||||
type reservationMQueueItem struct {
|
||||
r *request
|
||||
}
|
||||
|
||||
func (i *reservationMQueueItem) tag() float64 {
|
||||
return i.r.reservation
|
||||
}
|
||||
|
||||
func (i *reservationMQueueItem) setIndex(idx int) {
|
||||
i.r.reservationIdx = idx
|
||||
}
|
||||
|
||||
type limitMQueueItem struct {
|
||||
r *request
|
||||
}
|
||||
|
||||
func (i *limitMQueueItem) tag() float64 {
|
||||
return i.r.limit
|
||||
}
|
||||
|
||||
func (i *limitMQueueItem) setIndex(idx int) {
|
||||
i.r.limitIdx = idx
|
||||
}
|
||||
|
||||
type sharesMQueueItem struct {
|
||||
r *request
|
||||
}
|
||||
|
||||
func (i *sharesMQueueItem) tag() float64 {
|
||||
return i.r.shares
|
||||
}
|
||||
|
||||
func (i *sharesMQueueItem) setIndex(idx int) {
|
||||
i.r.sharesIdx = idx
|
||||
}
|
59
pkg/core/quota/mclock_test.go
Normal file
59
pkg/core/quota/mclock_test.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package quota
|
||||
|
||||
import (
|
||||
"math/rand/v2"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMClockSimulation(t *testing.T) {
|
||||
const maxIter = 100
|
||||
q := &mClockQueue{
|
||||
limit: 1,
|
||||
clock: &systemClock{},
|
||||
vmData: map[string]vmData{
|
||||
"class1": {reservation: zeroReservation, limit: maxLimit, shares: 60},
|
||||
"class2": {reservation: zeroReservation, limit: maxLimit, shares: 30},
|
||||
},
|
||||
previous: map[string]*request{
|
||||
"class1": {reservation: 0.0, limit: 0.0, shares: 0.0},
|
||||
"class2": {reservation: 0.0, limit: 0.0, shares: 0.0},
|
||||
},
|
||||
reservationQueue: &mQueue{
|
||||
items: make([]mQueueItem, 0),
|
||||
},
|
||||
limitQueue: &mQueue{
|
||||
items: make([]mQueueItem, 0),
|
||||
},
|
||||
sharesQueue: &mQueue{
|
||||
items: make([]mQueueItem, 0),
|
||||
},
|
||||
worker: &consoleWorker{t: t},
|
||||
}
|
||||
for i := 0; i < maxIter; i++ {
|
||||
vmNumber := rand.Int64N(2) + 1
|
||||
q.RequestArrival("class" + strconv.FormatInt(vmNumber, 10))
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
for i := 0; i < maxIter; i++ {
|
||||
q.requestCompleted()
|
||||
}
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
type systemClock struct {
|
||||
start time.Time
|
||||
}
|
||||
|
||||
func (c *systemClock) now() float64 {
|
||||
return time.Since(c.start).Seconds()
|
||||
}
|
||||
|
||||
type consoleWorker struct {
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (w *consoleWorker) do(vm string, seqNumber uint64) {
|
||||
w.t.Logf("request for vm %s with seq number %d scheduled\n", vm, seqNumber)
|
||||
}
|
Loading…
Add table
Reference in a new issue