Skip to content

Commit 505ac99

Browse files
committed
scheduler: Fixed a bug where the bandwidth of reserved cores was not taken into account
1 parent 10be73c commit 505ac99

File tree

6 files changed

+106
-46
lines changed

6 files changed

+106
-46
lines changed

client/client.go

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3323,8 +3323,7 @@ func (c *Client) setGaugeForDiskStats(hStats *hoststats.HostStats, baseLabels []
33233323
// setGaugeForAllocationStats proxies metrics for allocation specific statistics
33243324
func (c *Client) setGaugeForAllocationStats(baseLabels []metrics.Label) {
33253325
node := c.GetConfig().Node
3326-
total := node.NodeResources
3327-
res := node.ReservedResources
3326+
total := node.AvailableResources()
33283327
allocated := c.getAllocatedResources(node)
33293328

33303329
// Emit allocated
@@ -3342,20 +3341,13 @@ func (c *Client) setGaugeForAllocationStats(baseLabels []metrics.Label) {
33423341
}
33433342

33443343
// Emit unallocated
3345-
unallocatedMem := total.Memory.MemoryMB - res.Memory.MemoryMB - allocated.Flattened.Memory.MemoryMB
3346-
unallocatedDisk := total.Disk.DiskMB - res.Disk.DiskMB - allocated.Shared.DiskMB
3344+
unallocated := total.Copy()
3345+
unallocated.Subtract(allocated)
3346+
metrics.SetGaugeWithLabels([]string{"client", "unallocated", "memory"}, float32(unallocated.Flattened.Memory.MemoryMB), baseLabels)
3347+
metrics.SetGaugeWithLabels([]string{"client", "unallocated", "disk"}, float32(unallocated.Shared.DiskMB), baseLabels)
3348+
metrics.SetGaugeWithLabels([]string{"client", "unallocated", "cpu"}, float32(unallocated.Flattened.Cpu.CpuShares), baseLabels)
33473349

3348-
// The UsableCompute function call already subtracts and accounts for any
3349-
// reserved CPU within the client configuration. Therefore, we do not need
3350-
// to subtract that here.
3351-
unallocatedCpu := int64(total.Processors.Topology.UsableCompute()) - allocated.Flattened.Cpu.CpuShares
3352-
3353-
metrics.SetGaugeWithLabels([]string{"client", "unallocated", "memory"}, float32(unallocatedMem), baseLabels)
3354-
metrics.SetGaugeWithLabels([]string{"client", "unallocated", "disk"}, float32(unallocatedDisk), baseLabels)
3355-
metrics.SetGaugeWithLabels([]string{"client", "unallocated", "cpu"}, float32(unallocatedCpu), baseLabels)
3356-
3357-
totalComparable := total.Comparable()
3358-
for _, n := range totalComparable.Flattened.Networks {
3350+
for _, n := range total.Flattened.Networks {
33593351
// Determined the used resources
33603352
var usedMbits int
33613353
totalIdx := allocated.Flattened.Networks.NetIndex(n)

client/lib/numalib/topology.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,16 @@ func (st *Topology) NumECores() int {
282282
return total
283283
}
284284

285+
// TotalCores returns the number of logical cores detected. The UsableCores will
286+
// be equal to or less than this value.
287+
func (st *Topology) TotalCores() *idset.Set[hw.CoreID] {
288+
result := idset.Empty[hw.CoreID]()
289+
for _, cpu := range st.Cores {
290+
result.Insert(cpu.ID)
291+
}
292+
return result
293+
}
294+
285295
// UsableCores returns the number of logical cores usable by the Nomad client
286296
// for running tasks. Nomad must subtract off any reserved cores (reserved.cores)
287297
// and/or must mask the cpuset to the one set in config (config.reservable_cores).

nomad/structs/funcs.go

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -189,10 +189,8 @@ func AllocsFit(node *Node, allocs []*Allocation, netIdx *NetworkIndex, checkDevi
189189
return false, "cores", used, nil
190190
}
191191

192-
// Check that the node resources (after subtracting reserved) are a
193-
// super set of those that are being allocated
194-
available := node.NodeResources.Comparable()
195-
available.Subtract(node.ReservedResources.Comparable())
192+
// Check that the available node resources are a super set of those that are being allocated
193+
available := node.AvailableResources()
196194
if superset, dimension := available.Superset(used); !superset {
197195
return false, dimension, used, nil
198196
}
@@ -232,20 +230,9 @@ func AllocsFit(node *Node, allocs []*Allocation, netIdx *NetworkIndex, checkDevi
232230
}
233231

234232
func computeFreePercentage(node *Node, util *ComparableResources) (freePctCpu, freePctRam float64) {
235-
reserved := node.ReservedResources.Comparable()
236-
res := node.NodeResources.Comparable()
237-
238-
// Determine the node availability
239-
nodeCpu := float64(res.Flattened.Cpu.CpuShares)
240-
nodeMem := float64(res.Flattened.Memory.MemoryMB)
241-
if reserved != nil {
242-
nodeCpu -= float64(reserved.Flattened.Cpu.CpuShares)
243-
nodeMem -= float64(reserved.Flattened.Memory.MemoryMB)
244-
}
245-
246-
// Compute the free percentage
247-
freePctCpu = 1 - (float64(util.Flattened.Cpu.CpuShares) / nodeCpu)
248-
freePctRam = 1 - (float64(util.Flattened.Memory.MemoryMB) / nodeMem)
233+
resources := node.AvailableResources()
234+
freePctCpu = 1 - (float64(util.Flattened.Cpu.CpuShares) / float64(resources.Flattened.Cpu.CpuShares))
235+
freePctRam = 1 - (float64(util.Flattened.Memory.MemoryMB) / float64(resources.Flattened.Memory.MemoryMB))
249236
return freePctCpu, freePctRam
250237
}
251238

nomad/structs/funcs_test.go

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,16 @@ func node2k() *Node {
106106
ID: 1,
107107
Grade: numalib.Performance,
108108
BaseSpeed: 1000,
109+
}, {
110+
ID: 2,
111+
Grade: numalib.Performance,
112+
BaseSpeed: 1000,
113+
Disable: true,
114+
}, {
115+
ID: 3,
116+
Grade: numalib.Performance,
117+
BaseSpeed: 1000,
118+
Disable: true,
109119
}},
110120
OverrideWitholdCompute: 1000, // set by client reserved field
111121
},
@@ -137,7 +147,8 @@ func node2k() *Node {
137147
},
138148
ReservedResources: &NodeReservedResources{
139149
Cpu: NodeReservedCpuResources{
140-
CpuShares: 1000,
150+
CpuShares: 1000,
151+
ReservedCpuCores: []uint16{2, 3},
141152
},
142153
Memory: NodeReservedMemoryResources{
143154
MemoryMB: 1024,
@@ -248,6 +259,38 @@ func TestAllocsFit(t *testing.T) {
248259
must.Eq(t, 1000, used.Flattened.Cpu.CpuShares)
249260
must.Eq(t, []uint16{0}, used.Flattened.Cpu.ReservedCores)
250261
must.Eq(t, 1024, used.Flattened.Memory.MemoryMB)
262+
263+
a3 := &Allocation{
264+
AllocatedResources: &AllocatedResources{
265+
Tasks: map[string]*AllocatedTaskResources{
266+
"web": {
267+
Cpu: AllocatedCpuResources{
268+
CpuShares: 1000,
269+
},
270+
Memory: AllocatedMemoryResources{
271+
MemoryMB: 512,
272+
},
273+
},
274+
},
275+
},
276+
}
277+
278+
// Should fit one allocation
279+
fit, dim, used, err = AllocsFit(n, []*Allocation{a3}, nil, false)
280+
must.NoError(t, err)
281+
must.True(t, fit, must.Sprintf("failed for dimension %q", dim))
282+
must.Eq(t, 1000, used.Flattened.Cpu.CpuShares)
283+
must.Eq(t, []uint16{}, used.Flattened.Cpu.ReservedCores)
284+
must.Eq(t, 512, used.Flattened.Memory.MemoryMB)
285+
286+
// Should not fit second allocation
287+
fit, dim, used, err = AllocsFit(n, []*Allocation{a3, a3}, nil, false)
288+
must.NoError(t, err)
289+
must.False(t, fit)
290+
must.Eq(t, "cpu", dim)
291+
must.Eq(t, 2000, used.Flattened.Cpu.CpuShares)
292+
must.Eq(t, []uint16{}, used.Flattened.Cpu.ReservedCores)
293+
must.Eq(t, 1024, used.Flattened.Memory.MemoryMB)
251294
}
252295

253296
func TestAllocsFit_Cores(t *testing.T) {
@@ -649,8 +692,23 @@ func TestScoreFitBinPack(t *testing.T) {
649692
Cores: []numalib.Core{{
650693
ID: 0,
651694
Grade: numalib.Performance,
652-
BaseSpeed: 4096,
695+
BaseSpeed: 2048,
696+
}, {
697+
ID: 1,
698+
Grade: numalib.Performance,
699+
BaseSpeed: 2048,
700+
}, {
701+
ID: 2,
702+
Grade: numalib.Performance,
703+
BaseSpeed: 2048,
704+
Disable: true,
705+
}, {
706+
ID: 3,
707+
Grade: numalib.Performance,
708+
BaseSpeed: 2048,
709+
Disable: true,
653710
}},
711+
OverrideWitholdCompute: 2048, // set by client reserved field
654712
},
655713
},
656714
Memory: NodeMemoryResources{
@@ -661,7 +719,8 @@ func TestScoreFitBinPack(t *testing.T) {
661719
node.NodeResources.Compatibility()
662720
node.ReservedResources = &NodeReservedResources{
663721
Cpu: NodeReservedCpuResources{
664-
CpuShares: 2048,
722+
CpuShares: 2048,
723+
ReservedCpuCores: []uint16{2, 3},
665724
},
666725
Memory: NodeReservedMemoryResources{
667726
MemoryMB: 4096,

nomad/structs/structs.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2352,6 +2352,24 @@ func (n *Node) Stub(fields *NodeStubFields) *NodeListStub {
23522352
return s
23532353
}
23542354

2355+
// AvailableResources returns a comparable version of the resources available
2356+
// for scheduling workloads.
2357+
func (n *Node) AvailableResources() *ComparableResources {
2358+
if n == nil {
2359+
return nil
2360+
}
2361+
2362+
available := n.NodeResources.Comparable()
2363+
reserved := n.ReservedResources.Comparable()
2364+
2365+
// ReservedResources is a simple representation of the excluded resources. It is not aware of the hardware topology,
2366+
// hence we need to manually account for the reserved cpu and the cpu of the reserved cores.
2367+
available.Subtract(reserved)
2368+
available.Flattened.Cpu.CpuShares = int64(n.NodeResources.Processors.Topology.UsableCompute())
2369+
2370+
return available
2371+
}
2372+
23552373
// NodeListStub is used to return a subset of job information
23562374
// for the job list
23572375
type NodeListStub struct {
@@ -3239,16 +3257,16 @@ func (n *NodeResources) Comparable() *ComparableResources {
32393257
return nil
32403258
}
32413259

3242-
usableCores := n.Processors.Topology.UsableCores().Slice()
3243-
reservableCores := helper.ConvertSlice(usableCores, func(id hw.CoreID) uint16 {
3260+
totalCores := n.Processors.Topology.TotalCores().Slice()
3261+
cores := helper.ConvertSlice(totalCores, func(id hw.CoreID) uint16 {
32443262
return uint16(id)
32453263
})
32463264

32473265
c := &ComparableResources{
32483266
Flattened: AllocatedTaskResources{
32493267
Cpu: AllocatedCpuResources{
32503268
CpuShares: int64(n.Processors.Topology.TotalCompute()),
3251-
ReservedCores: reservableCores,
3269+
ReservedCores: cores,
32523270
},
32533271
Memory: AllocatedMemoryResources{
32543272
MemoryMB: n.Memory.MemoryMB,

scheduler/feasible/preemption.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -162,13 +162,7 @@ func (p *Preemptor) Copy() *Preemptor {
162162

163163
// SetNode sets the node
164164
func (p *Preemptor) SetNode(node *structs.Node) {
165-
nodeRemainingResources := node.NodeResources.Comparable()
166-
167-
// Subtract the reserved resources of the node
168-
if c := node.ReservedResources.Comparable(); c != nil {
169-
nodeRemainingResources.Subtract(c)
170-
}
171-
p.nodeRemainingResources = nodeRemainingResources
165+
p.nodeRemainingResources = node.AvailableResources()
172166
}
173167

174168
// SetCandidates initializes the candidate set from which preemptions are chosen

0 commit comments

Comments
 (0)