A process that yields is written at idle priority under its share of the machine (issue 1806)
A build seat's runtime took 1650% of shanks' 24 processors and slowed the operator's desktop. The unit of a process declared with yields now carries CPUWeight=idle, IOWeight=1, Nice=19, the lowest best-effort IO level, and CPUQuota for its share of the processors this machine has.
This commit is contained in:
@@ -3,7 +3,7 @@ module github.com/novox/mesh-host
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026112527-c2a410bb6376
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026123700-04902ff24a0e
|
||||
github.com/nats-io/nats-server/v2 v2.11.17
|
||||
github.com/nats-io/nats.go v1.54.0
|
||||
golang.org/x/crypto v0.57.0
|
||||
|
||||
@@ -1,17 +1,5 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024234636-10b6e202320f h1:lUEyNPPF3w4Q0LIgcUUD6ofkKVhCF0n7dVjMoixQmXo=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261024234636-10b6e202320f/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025204213-f63fa86c7456 h1:He2o9vSKjgKbWOCOgOJY0WlaDh1n8RcGhvGY/dwXsBQ=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025204213-f63fa86c7456/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025204730-0be0ffd6159e h1:k/1NjjW5HuuuHW2ldyWTvjp+1u3fN0RO18Q57Iup6Dk=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025204730-0be0ffd6159e/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026105311-175a5567b8bd h1:ef6y09csev6xY/suFrIFGUXbgvDA29kummFtHcIb3B8=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026105311-175a5567b8bd/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026110029-83ee7769d8ee h1:ZdBMkF4WYJi82jb3xYi9Z2lXN/iMTn9r3SFC122ROY4=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026110029-83ee7769d8ee/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026112307-af66adc180cd h1:DV4xfPRqmh6p20D2QNN+9EGFAyvpGOZPTRsSbpT7oww=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026112307-af66adc180cd/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026112527-c2a410bb6376 h1:by2zBASLb8h5jA1IiYJWykXwLnHYpuZh/zFNa4NsbOU=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026112527-c2a410bb6376/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026123700-04902ff24a0e h1:9Kwe61NVrT4NcNr6PZoupzsGuZCQ5WV+luqrxDV9frs=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026123700-04902ff24a0e/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
||||
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -277,6 +278,7 @@ func unitFor(r *declaration.Process) string {
|
||||
if r.User != "" {
|
||||
fmt.Fprintf(&b, "User=%s\n", r.User)
|
||||
}
|
||||
writeYields(&b, r.Yields)
|
||||
fmt.Fprintf(&b, "ExecStart=%s\n", strings.Join(runFrom(r), " "))
|
||||
if r.Schedule != "" || r.RunOnce {
|
||||
// Started by its timer, or once by the host, and expected to finish. Restarting it would have it run
|
||||
@@ -293,6 +295,36 @@ func unitFor(r *declaration.Process) string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// machineCPUs is how many processors this machine has, for a share of them in a quota. A variable only so a test can
|
||||
// fix it.
|
||||
var machineCPUs = runtime.NumCPU
|
||||
|
||||
// writeYields writes how a process gives way to everything else on its machine (novox/hq issue 1806): a build seat's
|
||||
// holder, whose builds must never slow the operator's desktop.
|
||||
//
|
||||
// **Idle priority** is four lines, because one alone leaves a door open: CPUWeight=idle runs its cgroup only on cycles
|
||||
// its siblings leave, Nice=19 holds every process it starts below the machine's own within the group, IOWeight=1 and
|
||||
// the lowest best-effort IO level put its disk work behind everyone's. Not the idle IO class: a disk that is never idle
|
||||
// would starve a build for good, and a build that never ends holds a check for ever.
|
||||
//
|
||||
// **The share** is a hard bound: CPUQuota counts in percent of one processor, so a share of the machine is written for
|
||||
// the processors it has (half of 24 is 1200%), at least one processor.
|
||||
func writeYields(b *strings.Builder, y *declaration.Yields) {
|
||||
if y == nil {
|
||||
return
|
||||
}
|
||||
if y.Priority == declaration.YieldIdle {
|
||||
b.WriteString("CPUWeight=idle\nIOWeight=1\nNice=19\nIOSchedulingClass=best-effort\nIOSchedulingPriority=7\n")
|
||||
}
|
||||
if y.CPUShare > 0 {
|
||||
quota := y.CPUShare * machineCPUs()
|
||||
if quota < 100 {
|
||||
quota = 100
|
||||
}
|
||||
fmt.Fprintf(b, "CPUQuota=%d%%\n", quota)
|
||||
}
|
||||
}
|
||||
|
||||
// timerFor is the cadence a scheduled process runs on.
|
||||
//
|
||||
// **The mesh's cron expression, handed to the machine's own timer.** The host already parses and
|
||||
|
||||
@@ -83,6 +83,36 @@ func TestAProcessRunsAsWhoItSaid(t *testing.T) {
|
||||
if strings.Contains(unitFor(aProcess()), "User=") {
|
||||
t.Fatalf("a process that named no user had one written for it:\n%s", unitFor(aProcess()))
|
||||
}
|
||||
|
||||
// A process that yields (issue 1806) is written at idle priority under its share of the machine's processors; one
|
||||
// that does not has none of it.
|
||||
defer func(n func() int) { machineCPUs = n }(machineCPUs)
|
||||
for _, c := range []struct {
|
||||
cpus int
|
||||
y *declaration.Yields
|
||||
want []string
|
||||
never []string
|
||||
}{
|
||||
{24, &declaration.Yields{Priority: declaration.YieldIdle, CPUShare: 50},
|
||||
[]string{"CPUWeight=idle\n", "IOWeight=1\n", "Nice=19\n", "IOSchedulingPriority=7\n", "CPUQuota=1200%\n"}, nil},
|
||||
{1, &declaration.Yields{CPUShare: 25}, []string{"CPUQuota=100%\n"}, []string{"Nice="}},
|
||||
{24, nil, nil, []string{"CPUWeight=", "Nice=", "CPUQuota=", "IOWeight="}},
|
||||
} {
|
||||
machineCPUs = func() int { return c.cpus }
|
||||
p := aProcess()
|
||||
p.Yields = c.y
|
||||
unit := unitFor(p)
|
||||
for _, w := range c.want {
|
||||
if !strings.Contains(unit, w) {
|
||||
t.Errorf("yields %+v on %d processors: the unit does not say %q:\n%s", c.y, c.cpus, w, unit)
|
||||
}
|
||||
}
|
||||
for _, n := range c.never {
|
||||
if strings.Contains(unit, n) {
|
||||
t.Errorf("yields %+v: the unit says %q:\n%s", c.y, n, unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// **Three modes, one kind.** A scheduled process is a timer plus a unit that finishes, not a unit
|
||||
|
||||
Reference in New Issue
Block a user