64 lines
2.1 KiB
Go
64 lines
2.1 KiB
Go
// Package choose picks the host for a new lease: among the healthy, non-draining, known hosts it
|
|
// prefers those that have the model loaded over those that would only be able to serve it, then the
|
|
// one with the most free slots times weight, breaking ties by shortest queue and finally list
|
|
// order.
|
|
package choose
|
|
|
|
// Info is one candidate host's view of itself for a single model.
|
|
type Info struct {
|
|
Healthy bool // answered its last poll
|
|
Draining bool // operator is draining it; no new leases
|
|
Loaded bool // the model is resident here
|
|
CanServe bool // config lists the model, so we may load it
|
|
Free int
|
|
Queued int
|
|
Weight float64
|
|
}
|
|
|
|
// Best returns the best host for a new lease, or ok=false when nothing is eligible. It runs two
|
|
// passes over the candidates in order: the first over those that have the model loaded, the second,
|
|
// only if the first found nothing, over those that can serve it. A host must be healthy, not
|
|
// draining, and known (info reported ok) to be eligible in either pass; a host with zero free slots
|
|
// is still eligible, since it will queue. Among the eligible ones the highest Free*Weight wins, ties
|
|
// go to the lowest queue, and a remaining tie keeps the earlier candidate.
|
|
func Best(candidates []string, info func(host string) (Info, bool)) (string, bool) {
|
|
const (
|
|
passLoaded = 0
|
|
passCanServe = 1
|
|
)
|
|
best := ""
|
|
var (
|
|
bestScore float64
|
|
bestQueued int
|
|
found bool
|
|
)
|
|
for pass := passLoaded; pass <= passCanServe; pass++ {
|
|
for _, host := range candidates {
|
|
v, ok := info(host)
|
|
if !ok || !v.Healthy || v.Draining {
|
|
continue
|
|
}
|
|
switch pass {
|
|
case passLoaded:
|
|
if !v.Loaded {
|
|
continue
|
|
}
|
|
case passCanServe:
|
|
if !v.CanServe {
|
|
continue
|
|
}
|
|
}
|
|
score := float64(v.Free) * v.Weight
|
|
// Replace only when strictly better on score, or equal score with a shorter queue; a
|
|
// further tie keeps the earlier candidate because we scan in order and use "<".
|
|
if !found || score > bestScore || (score == bestScore && v.Queued < bestQueued) {
|
|
best, bestScore, bestQueued, found = host, score, v.Queued, true
|
|
}
|
|
}
|
|
if found {
|
|
break
|
|
}
|
|
}
|
|
return best, found
|
|
}
|