projects/gke-optimization/binpacker/api/pkg/domain/model/node/nodes.go (149 lines of code) (raw):
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package node
import (
"errors"
"fmt"
"sort"
"github.com/golang/glog"
)
var (
ErrNumNodesNegative = errors.New("negative number of nodes")
ErrNumNodesOverLimit = errors.New("number of nodes over limit")
)
type Nodes struct {
nodes []Node
cpuPerNode CPU
memoryPerNode Memory
}
const MaxNumNodes = 15000
// NewNodes creates and initializes Nodes
func NewNodes(cpuPerNode CPU, memoryPerNode Memory, numNodes int) (*Nodes, error) {
if numNodes <= 0 {
return nil, fmt.Errorf("number of nodes must be positive, got %d: %w", numNodes, ErrNumNodesNegative)
}
if numNodes > MaxNumNodes {
return nil, fmt.Errorf("number of nodes must be under %d, got %d: %w", MaxNumNodes, numNodes, ErrNumNodesOverLimit)
}
nodes := make([]Node, 0, numNodes)
for i := 0; i < numNodes; i++ {
node, err := newNode(cpuPerNode, memoryPerNode)
if err != nil {
return nil, err
}
nodes = append(nodes, *node)
}
return &Nodes{nodes, cpuPerNode, memoryPerNode}, nil
}
// NewNodesByCPU creates and initializes Nodes by specifying CPU only
func NewNodesByCPU(cpuPerNode CPU, numNodes int) (*Nodes, error) {
if numNodes <= 0 {
return nil, fmt.Errorf("number of nodes must be positive, got %d: %w", numNodes, ErrNumNodesNegative)
}
if numNodes > MaxNumNodes {
return nil, fmt.Errorf("number of nodes must be under %d, got %d: %w", MaxNumNodes, numNodes, ErrNumNodesOverLimit)
}
nodes := make([]Node, 0, numNodes)
minMemoryInGiB, _ := CalculateMinMaxMemoryInGiBByCPU(cpuPerNode)
for i := 0; i < numNodes; i++ {
node, err := newNode(cpuPerNode, GiBToBytes(minMemoryInGiB))
if err != nil {
return nil, err
}
nodes = append(nodes, *node)
}
return &Nodes{nodes, cpuPerNode, GiBToBytes(minMemoryInGiB)}, nil
}
func (n *Nodes) SchedulableCPU(cpuUsage CPU) bool {
return n.nodes[0].SchedulableCPU(cpuUsage)
}
func (n *Nodes) SchedulableMemory(memoryUsage Memory) bool {
return n.nodes[0].SchedulableMemory(memoryUsage)
}
func (n *Nodes) CanScheduleAllMemoryUsages(memoryUsages []Memory) bool {
sort.SliceStable(memoryUsages, func(i, j int) bool {
return memoryUsages[j] < memoryUsages[i]
})
for _, memoryUsage := range memoryUsages {
foundAppropriateNode := false
for i := range n.nodes {
scheduled := n.nodes[i].ScheduleMemory(memoryUsage)
if scheduled {
foundAppropriateNode = true
break
}
}
if !foundAppropriateNode {
return false
}
}
return true
}
func (n *Nodes) FirstFitDecreasingByCPU(cpuUsages []CPU) (int, error) {
sort.SliceStable(cpuUsages, func(i, j int) bool {
return cpuUsages[j] < cpuUsages[i]
})
maxUsage := cpuUsages[0]
if !n.nodes[0].SchedulableCPU(maxUsage) {
return 0, errors.New("cannot find appropriate number of nodes")
}
for len(cpuUsages) > 0 {
foundAppropriateNode := false
for i := range n.nodes {
scheduled := n.nodes[i].ScheduleCPU(cpuUsages[0])
if scheduled {
cpuUsages = cpuUsages[1:]
foundAppropriateNode = true
break
}
}
if !foundAppropriateNode {
err := n.appendOneNode()
if err != nil {
glog.Error("Failed to append one node")
}
}
}
return len(n.nodes), nil
}
func MaxCPU(cpus []CPU) CPU {
var max CPU
for _, cpu := range cpus {
if cpu > max {
max = cpu
}
}
return max
}
func MaxMemory(memories []Memory) Memory {
var max Memory
for _, memory := range memories {
if memory > max {
max = memory
}
}
return max
}
func (n *Nodes) CPUCapacityForUserWorkloads() CPU {
return calculateRemainingCPU(n.nodes[0].cpuOnGCE)
}
func (n *Nodes) MemoryCapacityForUserWorkloads() Memory {
return calculateRemainingMemory(n.nodes[0].memoryOnGCE)
}
func (n *Nodes) MemoryKernelUsage() Memory {
return calculateKernelUsageMemory(n.nodes[0].memoryOnGCE)
}
func (n *Nodes) ReservedCPU() CPU {
reserved := reservedCPU(n.nodes[0].cpuOnGCE)
return reserved
}
func (n *Nodes) ReservedMemory() Memory {
return reservedMemory(n.nodes[0].memoryOnGCE)
}
func (n *Nodes) appendOneNode() error {
if len(n.nodes) >= MaxNumNodes {
return fmt.Errorf("can't append a node over %d nodes", MaxNumNodes)
}
// The error won't occur since error must have been captured in NewNodes
newNode, err := newNode(n.cpuPerNode, n.memoryPerNode)
if err != nil {
glog.Errorf("Failed to create a new node with %f cpus and %d GiB memory", n.cpuPerNode, n.memoryPerNode)
}
n.nodes = append(n.nodes, *newNode)
return nil
}