diff --git a/modules/mesh-build-go/cmd/mesh-build-go/cpu.go b/modules/mesh-build-go/cmd/mesh-build-go/cpu.go index 73322827..dc7c2a55 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/cpu.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/cpu.go @@ -5,15 +5,15 @@ import ( "git.novox.be/novox/mesh-sdk/go/buildstep" ) -// The processors a step may take (novox/hq issue 1806). The kernel holds the module's runtime to idle priority and to -// its node's share of the processors (the setting cpu-cap, written into its unit by the node-engine); inside that -// bound a step starts no more threads than its part of it, so the steps running at once do not wait in the kernel's -// throttle: GOMAXPROCS, which sets every go command's -p, the compiler's concurrency, a test binary's processors and -// -parallel. +// The processors a step may take (novox/hq ADR 0553, issue 1806). The node-engine holds this module's runtime, with +// every other build module's of the node, in one slice at idle priority under the node's one share of its processors; +// inside that bound a step starts no more threads than its part of it: the share divided by every build slot of the +// node, from the words the composer gives (buildstep.NodeLimits), so the steps running at once do not wait in the +// kernel's throttle. -// cpuLimits is what one step is held to now: the cap from the settings file, the machine's processors and the node's -// capacity, read at every call like the capacity, so a change takes effect at the next step. -func (m *Module) cpuLimits(say func(string, ...any)) buildcpu.Limits { +// cpuLimits is what one step is held to now. Without the composer's words, it assumes every build seat of the node +// beside it at this module's own capacity. +func (m *Module) cpuLimits() buildcpu.Limits { slots := 1 switch { case m.Capacity != nil: @@ -21,19 +21,17 @@ func (m *Module) cpuLimits(say func(string, ...any)) buildcpu.Limits { case m.Settings != "": slots = buildstep.NodeCapacity(m.Settings, buildstep.DefaultCapacities, m.Node, nil)() } - if slots < 1 { - slots = 1 - } - return buildstep.NodeLimits(m.Settings, m.Node, slots, say) + return buildstep.NodeLimits(slots) } -// threadEnv is the environment every go command of a step is given for its threads. +// threadEnv is the environment every go command of a step is given for its threads: GOMAXPROCS sets its -p, the compiler's +// concurrency, a test binary's processors and -parallel. func (m *Module) threadEnv() []string { - return buildcpu.Env(m.cpuLimits(nil).Threads) + return buildcpu.Env(m.cpuLimits().Threads) } // cpuStatus is what the status tool says of it: the limits a step is given, and what the kernel holds this runtime -// to, read from its cgroup rather than assumed. +// to (its unit and its slice), read from its cgroup rather than assumed. func (m *Module) cpuStatus() map[string]any { - return map[string]any{"limits": m.cpuLimits(m.Say), "in-force": buildcpu.ReadInForce()} + return map[string]any{"limits": m.cpuLimits(), "in-force": buildcpu.ReadInForce()} } diff --git a/modules/mesh-build-go/cmd/mesh-build-go/installed_toolchain_test.go b/modules/mesh-build-go/cmd/mesh-build-go/installed_toolchain_test.go index 6ca48aa8..a498c044 100644 --- a/modules/mesh-build-go/cmd/mesh-build-go/installed_toolchain_test.go +++ b/modules/mesh-build-go/cmd/mesh-build-go/installed_toolchain_test.go @@ -49,16 +49,21 @@ func TestEveryStepEnvironmentSaysGotoolchainLocal(t *testing.T) { t.Errorf("%s: GOMAXPROCS said %d times, want once: %v", name, procs, env) } } - // Three steps at once under shanks' default half of its processors share them: each gets a third of the half. - settings := filepath.Join(t.TempDir(), "settings.json") - os.WriteFile(settings, []byte(`{"capacity":"shanks=3","cpu-cap":"shanks=50"}`), 0o600) - m = &Module{Cache: t.TempDir(), Node: "shanks", Settings: settings} - l := m.cpuLimits(nil) - if want := buildcpu.Threads(50, runtime.NumCPU(), 3); l.Percent != 50 || l.Slots != 3 || l.Threads != want || - !strings.Contains(strings.Join(m.goEnv(), " "), "GOMAXPROCS="+strconv.Itoa(want)) { + // Three slots of its own and no words from the composer: every build seat beside it at three, at the default + // share; with the composer's words, its part of the node's share (ADR 0553 §4). + t.Setenv("MESH_BUILD_CPU_SHARE", "") + t.Setenv("MESH_BUILD_SLOTS", "") + m = &Module{Cache: t.TempDir(), Node: "shanks", Capacity: func() int { return 3 }} + if l := m.cpuLimits(); l.Percent != 50 || l.Slots != 9 || l.Threads != buildcpu.Threads(50, runtime.NumCPU(), 9) { + t.Errorf("without words: %+v", l) + } + t.Setenv("MESH_BUILD_CPU_SHARE", "50") + t.Setenv("MESH_BUILD_SLOTS", "4") + want := buildcpu.Threads(50, runtime.NumCPU(), 4) + if l := m.cpuLimits(); l.Slots != 4 || !strings.Contains(strings.Join(m.goEnv(), " "), "GOMAXPROCS="+strconv.Itoa(want)) { t.Errorf("limits %+v, goEnv %v; want %d threads", l, m.goEnv(), want) } - if st := m.cpuStatus(); st["limits"].(buildcpu.Limits) != l { + if st := m.cpuStatus(); st["limits"].(buildcpu.Limits) != m.cpuLimits() { t.Errorf("the status says %v", st) } } diff --git a/modules/mesh-build-go/files/settings.json b/modules/mesh-build-go/files/settings.json index 31e080c2..3175fe39 100644 --- a/modules/mesh-build-go/files/settings.json +++ b/modules/mesh-build-go/files/settings.json @@ -1,4 +1,3 @@ { - "capacity": "shanks=3,ace=2,g14=0,novox=0", - "cpu-cap": "shanks=50,g14=50,ace=75" + "capacity": "shanks=3,ace=2,g14=0,novox=0" } diff --git a/modules/mesh-build-go/go.mod b/modules/mesh-build-go/go.mod index ecb04bd9..54bdeaf8 100644 --- a/modules/mesh-build-go/go.mod +++ b/modules/mesh-build-go/go.mod @@ -3,7 +3,7 @@ module meshbuildgo go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026124909-76deab6c67c2 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132005-852ba81c5af1 github.com/distribution/distribution/v3 v3.1.2 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 diff --git a/modules/mesh-build-go/go.sum b/modules/mesh-build-go/go.sum index 07bac378..3eb507ac 100644 --- a/modules/mesh-build-go/go.sum +++ b/modules/mesh-build-go/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026124909-76deab6c67c2 h1:DugsX7h46z+1FOiNhtDE43Vf1WInz2vgwi0mn/1VZDg= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026124909-76deab6c67c2/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132005-852ba81c5af1 h1:6O9WKnlVR1uHV5DDQueHGbMAQzfVS06J7DBKjUPBG4E= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132005-852ba81c5af1/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= diff --git a/modules/mesh-build-go/module.json b/modules/mesh-build-go/module.json index 7742aab8..6346a6ad 100644 --- a/modules/mesh-build-go/module.json +++ b/modules/mesh-build-go/module.json @@ -29,12 +29,6 @@ "description": "How many asks of the Go build seat this module runs at once on each node, as node=asks pairs separated by commas, such as shanks=3,ace=2 (novox/hq ADR 0525 §5); a node set to 0 pulls nothing, a node not named runs one at a time. The control-node and the laptop build nothing.", "pattern": "|[a-z0-9_-]+=[0-9]+(,[a-z0-9_-]+=[0-9]+)*", "default": "shanks=3,ace=2,g14=0,novox=0" - }, - "cpu-cap": { - "type": "string", - "description": "The most of each node's processors this module's builds may take, as node=percent pairs separated by commas, such as shanks=50,ace=75 (novox/hq issue 1806); a node not named takes 50. The node-engine holds the module's runtime to it as a CPU quota, at idle priority, and each build step starts no more threads than its part of it.", - "pattern": "|[a-z0-9_-]+=([1-9]|[1-9][0-9]|100)%?(,[a-z0-9_-]+=([1-9]|[1-9][0-9]|100)%?)*", - "default": "shanks=50,g14=50,ace=75" } }, "tools": [ @@ -112,7 +106,7 @@ "path": "${dir:state}/settings.json", "mode": "0600", "owner": "mesh-build-go", - "takes-config": ["capacity", "cpu-cap"], + "takes-config": ["capacity"], "merge": "json", "template": "files/settings.json" }, diff --git a/modules/mesh-build-image/cmd/mesh-build-image/cpu.go b/modules/mesh-build-image/cmd/mesh-build-image/cpu.go index d38b9616..1e253ff6 100644 --- a/modules/mesh-build-image/cmd/mesh-build-image/cpu.go +++ b/modules/mesh-build-image/cmd/mesh-build-image/cpu.go @@ -5,14 +5,15 @@ import ( "git.novox.be/novox/mesh-sdk/go/buildstep" ) -// The processors a step may take (novox/hq issue 1806). The kernel holds the module's runtime to idle priority and to -// its node's share of the processors (the setting cpu-cap, written into its unit by the node-engine); inside that -// bound a step starts no more threads than its part of it, so the steps running at once do not wait in the kernel's -// throttle. +// The processors a step may take (novox/hq ADR 0553, issue 1806). The node-engine holds this module's runtime, with +// every other build module's of the node, in one slice at idle priority under the node's one share of its processors; +// inside that bound a step starts no more threads than its part of it: the share divided by every build slot of the +// node, from the words the composer gives (buildstep.NodeLimits), so the steps running at once do not wait in the +// kernel's throttle. -// cpuLimits is what one step is held to now: the cap from the settings file, the machine's processors and the node's -// capacity, read at every call like the capacity, so a change takes effect at the next step. -func (m *Module) cpuLimits(say func(string, ...any)) buildcpu.Limits { +// cpuLimits is what one step is held to now. Without the composer's words, it assumes every build seat of the node +// beside it at this module's own capacity. +func (m *Module) cpuLimits() buildcpu.Limits { slots := 1 switch { case m.Capacity != nil: @@ -20,20 +21,17 @@ func (m *Module) cpuLimits(say func(string, ...any)) buildcpu.Limits { case m.Settings != "": slots = buildstep.NodeCapacity(m.Settings, buildstep.DefaultCapacities, m.Node, nil)() } - if slots < 1 { - slots = 1 - } - return buildstep.NodeLimits(m.Settings, m.Node, slots, say) + return buildstep.NodeLimits(slots) } // threadEnv is the environment buildah is given for its threads (it is a Go program). What a build's RUN lines start -// runs in the container's own environment, inside the runtime's cgroup: the quota and the nice level bind it. +// runs in the container's own environment, inside the runtime's cgroup: the slice's quota and the nice level bind it. func (m *Module) threadEnv() []string { - return buildcpu.Env(m.cpuLimits(nil).Threads) + return buildcpu.Env(m.cpuLimits().Threads) } // cpuStatus is what the status tool says of it: the limits a step is given, and what the kernel holds this runtime -// to, read from its cgroup rather than assumed. +// to (its unit and its slice), read from its cgroup rather than assumed. func (m *Module) cpuStatus() map[string]any { - return map[string]any{"limits": m.cpuLimits(m.Say), "in-force": buildcpu.ReadInForce()} + return map[string]any{"limits": m.cpuLimits(), "in-force": buildcpu.ReadInForce()} } diff --git a/modules/mesh-build-image/files/settings.json b/modules/mesh-build-image/files/settings.json index 31e080c2..3175fe39 100644 --- a/modules/mesh-build-image/files/settings.json +++ b/modules/mesh-build-image/files/settings.json @@ -1,4 +1,3 @@ { - "capacity": "shanks=3,ace=2,g14=0,novox=0", - "cpu-cap": "shanks=50,g14=50,ace=75" + "capacity": "shanks=3,ace=2,g14=0,novox=0" } diff --git a/modules/mesh-build-image/go.mod b/modules/mesh-build-image/go.mod index cda53ec0..bd683c1c 100644 --- a/modules/mesh-build-image/go.mod +++ b/modules/mesh-build-image/go.mod @@ -3,7 +3,7 @@ module meshbuildimage go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026124909-76deab6c67c2 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132005-852ba81c5af1 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-build-image/go.sum b/modules/mesh-build-image/go.sum index 6ca74757..1679d427 100644 --- a/modules/mesh-build-image/go.sum +++ b/modules/mesh-build-image/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026124909-76deab6c67c2 h1:DugsX7h46z+1FOiNhtDE43Vf1WInz2vgwi0mn/1VZDg= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026124909-76deab6c67c2/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132005-852ba81c5af1 h1:6O9WKnlVR1uHV5DDQueHGbMAQzfVS06J7DBKjUPBG4E= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132005-852ba81c5af1/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= diff --git a/modules/mesh-build-image/module.json b/modules/mesh-build-image/module.json index 41e08763..6e25dbfa 100644 --- a/modules/mesh-build-image/module.json +++ b/modules/mesh-build-image/module.json @@ -24,12 +24,6 @@ "description": "How many asks of the image build seat this module runs at once on each node, as node=asks pairs separated by commas, such as shanks=3,ace=2 (novox/hq ADR 0525 §5); a node set to 0 pulls nothing, a node not named runs one at a time. The control-node and the laptop build nothing.", "pattern": "|[a-z0-9_-]+=[0-9]+(,[a-z0-9_-]+=[0-9]+)*", "default": "shanks=3,ace=2,g14=0,novox=0" - }, - "cpu-cap": { - "type": "string", - "description": "The most of each node's processors this module's builds may take, as node=percent pairs separated by commas, such as shanks=50,ace=75 (novox/hq issue 1806); a node not named takes 50. The node-engine holds the module's runtime to it as a CPU quota, at idle priority, and each build step starts no more threads than its part of it.", - "pattern": "|[a-z0-9_-]+=([1-9]|[1-9][0-9]|100)%?(,[a-z0-9_-]+=([1-9]|[1-9][0-9]|100)%?)*", - "default": "shanks=50,g14=50,ace=75" } }, "tools": [ @@ -83,7 +77,7 @@ "path": "${dir:state}/settings.json", "mode": "0600", "owner": "mesh-build-image", - "takes-config": ["capacity", "cpu-cap"], + "takes-config": ["capacity"], "merge": "json", "template": "files/settings.json" }, diff --git a/modules/mesh-build-node/cmd/mesh-build-node/cpu.go b/modules/mesh-build-node/cmd/mesh-build-node/cpu.go index 5221728a..a0b18454 100644 --- a/modules/mesh-build-node/cmd/mesh-build-node/cpu.go +++ b/modules/mesh-build-node/cmd/mesh-build-node/cpu.go @@ -5,14 +5,15 @@ import ( "git.novox.be/novox/mesh-sdk/go/buildstep" ) -// The processors a step may take (novox/hq issue 1806). The kernel holds the module's runtime to idle priority and to -// its node's share of the processors (the setting cpu-cap, written into its unit by the node-engine); inside that -// bound a step starts no more threads than its part of it, so the steps running at once do not wait in the kernel's -// throttle. +// The processors a step may take (novox/hq ADR 0553, issue 1806). The node-engine holds this module's runtime, with +// every other build module's of the node, in one slice at idle priority under the node's one share of its processors; +// inside that bound a step starts no more threads than its part of it: the share divided by every build slot of the +// node, from the words the composer gives (buildstep.NodeLimits), so the steps running at once do not wait in the +// kernel's throttle. -// cpuLimits is what one step is held to now: the cap from the settings file, the machine's processors and the node's -// capacity, read at every call like the capacity, so a change takes effect at the next step. -func (m *Module) cpuLimits(say func(string, ...any)) buildcpu.Limits { +// cpuLimits is what one step is held to now. Without the composer's words, it assumes every build seat of the node +// beside it at this module's own capacity. +func (m *Module) cpuLimits() buildcpu.Limits { slots := 1 switch { case m.Capacity != nil: @@ -20,20 +21,17 @@ func (m *Module) cpuLimits(say func(string, ...any)) buildcpu.Limits { case m.Settings != "": slots = buildstep.NodeCapacity(m.Settings, buildstep.DefaultCapacities, m.Node, nil)() } - if slots < 1 { - slots = 1 - } - return buildstep.NodeLimits(m.Settings, m.Node, slots, say) + return buildstep.NodeLimits(slots) } // threadEnv is the environment every command of a step is given for its threads: GOMAXPROCS for esbuild and every Go // program, UV_THREADPOOL_SIZE for Node's worker pool. func (m *Module) threadEnv() []string { - return buildcpu.Env(m.cpuLimits(nil).Threads) + return buildcpu.Env(m.cpuLimits().Threads) } // cpuStatus is what the status tool says of it: the limits a step is given, and what the kernel holds this runtime -// to, read from its cgroup rather than assumed. +// to (its unit and its slice), read from its cgroup rather than assumed. func (m *Module) cpuStatus() map[string]any { - return map[string]any{"limits": m.cpuLimits(m.Say), "in-force": buildcpu.ReadInForce()} + return map[string]any{"limits": m.cpuLimits(), "in-force": buildcpu.ReadInForce()} } diff --git a/modules/mesh-build-node/files/settings.json b/modules/mesh-build-node/files/settings.json index 31e080c2..3175fe39 100644 --- a/modules/mesh-build-node/files/settings.json +++ b/modules/mesh-build-node/files/settings.json @@ -1,4 +1,3 @@ { - "capacity": "shanks=3,ace=2,g14=0,novox=0", - "cpu-cap": "shanks=50,g14=50,ace=75" + "capacity": "shanks=3,ace=2,g14=0,novox=0" } diff --git a/modules/mesh-build-node/go.mod b/modules/mesh-build-node/go.mod index a7882138..a13058a1 100644 --- a/modules/mesh-build-node/go.mod +++ b/modules/mesh-build-node/go.mod @@ -3,7 +3,7 @@ module meshbuildnode go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026124909-76deab6c67c2 + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132005-852ba81c5af1 github.com/distribution/distribution/v3 v3.1.2 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-build-node/go.sum b/modules/mesh-build-node/go.sum index 3a53c941..79aabaac 100644 --- a/modules/mesh-build-node/go.sum +++ b/modules/mesh-build-node/go.sum @@ -1,5 +1,5 @@ -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026124909-76deab6c67c2 h1:DugsX7h46z+1FOiNhtDE43Vf1WInz2vgwi0mn/1VZDg= -git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026124909-76deab6c67c2/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132005-852ba81c5af1 h1:6O9WKnlVR1uHV5DDQueHGbMAQzfVS06J7DBKjUPBG4E= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026132005-852ba81c5af1/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= diff --git a/modules/mesh-build-node/module.json b/modules/mesh-build-node/module.json index dace2a37..993fb7a1 100644 --- a/modules/mesh-build-node/module.json +++ b/modules/mesh-build-node/module.json @@ -25,12 +25,6 @@ "description": "How many asks of the Node build seat this module runs at once on each node, as node=asks pairs separated by commas, such as shanks=3,ace=2 (novox/hq ADR 0525 §5); a node set to 0 pulls nothing, a node not named runs one at a time. The control-node and the laptop build nothing.", "pattern": "|[a-z0-9_-]+=[0-9]+(,[a-z0-9_-]+=[0-9]+)*", "default": "shanks=3,ace=2,g14=0,novox=0" - }, - "cpu-cap": { - "type": "string", - "description": "The most of each node's processors this module's builds may take, as node=percent pairs separated by commas, such as shanks=50,ace=75 (novox/hq issue 1806); a node not named takes 50. The node-engine holds the module's runtime to it as a CPU quota, at idle priority, and each build step starts no more threads than its part of it.", - "pattern": "|[a-z0-9_-]+=([1-9]|[1-9][0-9]|100)%?(,[a-z0-9_-]+=([1-9]|[1-9][0-9]|100)%?)*", - "default": "shanks=50,g14=50,ace=75" } }, "tools": [ @@ -105,7 +99,7 @@ "path": "${dir:state}/settings.json", "mode": "0600", "owner": "mesh-build-node", - "takes-config": ["capacity", "cpu-cap"], + "takes-config": ["capacity"], "merge": "json", "template": "files/settings.json" },