mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
This change isolates all the code that deals with node selection in the scheduler into its own package called feasible. --------- Co-authored-by: Tim Gross <tgross@hashicorp.com>
226 lines
5.8 KiB
Go
226 lines
5.8 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package benchmarks
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/shoenig/test/must"
|
|
|
|
"github.com/hashicorp/nomad/helper/uuid"
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/nomad/scheduler"
|
|
"github.com/hashicorp/nomad/scheduler/tests"
|
|
)
|
|
|
|
// BenchmarkSchedulerExample is an example of how to write a one-off
|
|
// benchmark for the Nomad scheduler. The starting state for your
|
|
// implementation will depend on the following environment variables:
|
|
//
|
|
// - NOMAD_BENCHMARK_DATADIR: path to data directory
|
|
// - NOMAD_BENCHMARK_SNAPSHOT: path to raft snapshot
|
|
// - neither: empty starting state
|
|
//
|
|
// You can run a profile for this benchmark with the usual -cpuprofile
|
|
// -memprofile flags.
|
|
func BenchmarkSchedulerExample(b *testing.B) {
|
|
|
|
h := NewBenchmarkingHarness(b)
|
|
var eval *structs.Evaluation
|
|
|
|
// (implement me!) this is your setup for the state and the eval
|
|
// you're going to process, all of which happens before benchmarking
|
|
// starts. If you're benchmarking a real world datadir or snapshot,
|
|
// you should assert your assumptions about the contents here.
|
|
{
|
|
upsertNodes(h, 5000, 100)
|
|
|
|
iter, err := h.State.Nodes(nil)
|
|
must.NoError(b, err)
|
|
nodes := 0
|
|
for {
|
|
raw := iter.Next()
|
|
if raw == nil {
|
|
break
|
|
}
|
|
nodes++
|
|
}
|
|
must.Eq(b, 5000, nodes)
|
|
job := generateJob(true, 600, 100)
|
|
eval = upsertJob(h, job)
|
|
}
|
|
|
|
// (implement me!) Note that h.Process doesn't return errors for
|
|
// most states that result in blocked plans, so it's recommended
|
|
// you write an assertion section here so that you're sure you're
|
|
// benchmarking a successful run and not a failed plan.
|
|
{
|
|
err := h.Process(scheduler.NewServiceScheduler, eval)
|
|
must.NoError(b, err)
|
|
must.Len(b, 1, h.Plans)
|
|
must.False(b, h.Plans[0].IsNoOp())
|
|
}
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
err := h.Process(scheduler.NewServiceScheduler, eval)
|
|
must.NoError(b, err)
|
|
}
|
|
}
|
|
|
|
// BenchmarkServiceScheduler exercises the service scheduler at a
|
|
// variety of cluster sizes, with both spread and non-spread jobs
|
|
func BenchmarkServiceScheduler(b *testing.B) {
|
|
|
|
clusterSizes := []int{500, 1000, 5000, 10000}
|
|
rackSets := []int{25, 50, 75}
|
|
jobSizes := []int{50, 300, 600, 900, 1200}
|
|
|
|
type benchmark struct {
|
|
name string
|
|
clusterSize int
|
|
racks int
|
|
jobSize int
|
|
withSpread bool
|
|
}
|
|
|
|
benchmarks := []benchmark{}
|
|
for _, clusterSize := range clusterSizes {
|
|
for _, racks := range rackSets {
|
|
for _, jobSize := range jobSizes {
|
|
benchmarks = append(benchmarks,
|
|
benchmark{
|
|
name: fmt.Sprintf("%d nodes %d racks %d allocs spread",
|
|
clusterSize, racks, jobSize,
|
|
),
|
|
clusterSize: clusterSize, racks: racks, jobSize: jobSize,
|
|
withSpread: true,
|
|
},
|
|
)
|
|
benchmarks = append(benchmarks,
|
|
benchmark{
|
|
name: fmt.Sprintf("%d nodes %d racks %d allocs no spread",
|
|
clusterSize, racks, jobSize,
|
|
),
|
|
clusterSize: clusterSize, racks: racks, jobSize: jobSize,
|
|
withSpread: false,
|
|
},
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, bm := range benchmarks {
|
|
job := generateJob(bm.withSpread, bm.jobSize, bm.racks)
|
|
h := tests.NewHarness(b)
|
|
h.SetNoSubmit()
|
|
upsertNodes(h, bm.clusterSize, bm.racks)
|
|
eval := upsertJob(h, job)
|
|
b.ResetTimer()
|
|
|
|
b.Run(bm.name, func(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
err := h.Process(scheduler.NewServiceScheduler, eval)
|
|
must.NoError(b, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func upsertJob(h *tests.Harness, job *structs.Job) *structs.Evaluation {
|
|
err := h.State.UpsertJob(structs.MsgTypeTestSetup, h.NextIndex(), nil, job)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
eval := &structs.Evaluation{
|
|
Namespace: structs.DefaultNamespace,
|
|
ID: uuid.Generate(),
|
|
Priority: job.Priority,
|
|
TriggeredBy: structs.EvalTriggerJobRegister,
|
|
JobID: job.ID,
|
|
Status: structs.EvalStatusPending,
|
|
}
|
|
err = h.State.UpsertEvals(structs.MsgTypeTestSetup,
|
|
h.NextIndex(), []*structs.Evaluation{eval})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return eval
|
|
}
|
|
|
|
func generateJob(withSpread bool, jobSize int, racks int) *structs.Job {
|
|
job := mock.Job()
|
|
job.Datacenters = []string{"dc-1", "dc-2"}
|
|
if withSpread {
|
|
job.Spreads = []*structs.Spread{{Attribute: "${meta.rack}"}}
|
|
}
|
|
|
|
// only half the racks will be considered eligibble
|
|
rackTargets := []string{}
|
|
for i := range racks / 2 {
|
|
rackTargets = append(rackTargets, fmt.Sprintf("r%d", i))
|
|
}
|
|
rackTarget := strings.Join(rackTargets, ",")
|
|
job.Constraints = []*structs.Constraint{
|
|
{
|
|
LTarget: "${meta.rack}",
|
|
RTarget: rackTarget,
|
|
Operand: "set_contains_any",
|
|
},
|
|
}
|
|
job.TaskGroups[0].Count = jobSize
|
|
job.TaskGroups[0].Networks = nil
|
|
job.TaskGroups[0].Services = []*structs.Service{}
|
|
job.TaskGroups[0].Tasks[0].Resources = &structs.Resources{
|
|
CPU: 6000,
|
|
MemoryMB: 6000,
|
|
}
|
|
return job
|
|
}
|
|
|
|
func upsertNodes(h *tests.Harness, count, racks int) {
|
|
|
|
datacenters := []string{"dc-1", "dc-2"}
|
|
|
|
for i := 0; i < count; i++ {
|
|
node := mock.Node()
|
|
node.Datacenter = datacenters[i%2]
|
|
node.Meta = map[string]string{}
|
|
node.Meta["rack"] = fmt.Sprintf("r%d", i%racks)
|
|
node.Attributes["unique.advertise.address"] = fmt.Sprintf("192.168.%d.%d", i%10, i%120)
|
|
memoryMB := 32000
|
|
diskMB := 100 * 1024
|
|
|
|
node.NodeResources = &structs.NodeResources{
|
|
Processors: structs.NodeProcessorResources{
|
|
Topology: structs.MockBasicTopology(),
|
|
},
|
|
Memory: structs.NodeMemoryResources{
|
|
MemoryMB: int64(memoryMB),
|
|
},
|
|
Disk: structs.NodeDiskResources{
|
|
DiskMB: int64(diskMB),
|
|
},
|
|
Networks: []*structs.NetworkResource{
|
|
{
|
|
Mode: "host",
|
|
Device: "eth0",
|
|
CIDR: "192.168.0.100/32",
|
|
MBits: 1000,
|
|
},
|
|
},
|
|
}
|
|
node.NodeResources.Compatibility()
|
|
node.ComputeClass()
|
|
|
|
err := h.State.UpsertNode(structs.MsgTypeTestSetup, h.NextIndex(), node)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|