Build modules: a per-node processor cap, and each step's threads within it (issue 1806)
The operator's desktop slows while it builds (shanks at 92%, the Go build runtime at 1650% of 24 processors). The build modules declare cpu-cap (default shanks=50,g14=50,ace=75), which the composer turns into the runtime's quota at idle priority; each step's go, node and buildah commands get GOMAXPROCS and UV_THREADPOOL_SIZE for their part of it, and the status tools show the limits and what the kernel holds the runtime to.
This commit is contained in:
@@ -110,6 +110,9 @@ func TestBuildRunsBuildahRootlessAndAnswersTheDigest(t *testing.T) {
|
||||
if !strings.Contains(env, "XDG_RUNTIME_DIR="+m.Store+"/xdg") {
|
||||
t.Errorf("the environment of %q lacks the module's own runtime directory: %s", c.line, env)
|
||||
}
|
||||
if !strings.Contains(env, "GOMAXPROCS=") {
|
||||
t.Errorf("the environment of %q gives buildah no thread limit (issue 1806): %s", c.line, env)
|
||||
}
|
||||
}
|
||||
// The image is removed from the store once the digest is read, so the store does not grow with every build.
|
||||
if len(f.called("rmi")) != 1 {
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.novox.be/novox/mesh-sdk/go/buildcpu"
|
||||
"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.
|
||||
|
||||
// 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 {
|
||||
slots := 1
|
||||
switch {
|
||||
case m.Capacity != nil:
|
||||
slots = m.Capacity()
|
||||
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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (m *Module) threadEnv() []string {
|
||||
return buildcpu.Env(m.cpuLimits(nil).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.
|
||||
func (m *Module) cpuStatus() map[string]any {
|
||||
return map[string]any{"limits": m.cpuLimits(m.Say), "in-force": buildcpu.ReadInForce()}
|
||||
}
|
||||
@@ -128,7 +128,8 @@ func (m *Module) Health(ctx context.Context) health.Answer {
|
||||
// Status is what the module reports of itself.
|
||||
func (m *Module) Status(ctx context.Context) map[string]any {
|
||||
l, err := m.look(ctx)
|
||||
out := map[string]any{"node": m.Node, "account": m.Account, "buildah": l.Buildah, "subuids": l.UIDs, "subgids": l.GIDs, "refusing": err != nil}
|
||||
out := map[string]any{"node": m.Node, "account": m.Account, "buildah": l.Buildah, "subuids": l.UIDs, "subgids": l.GIDs, "refusing": err != nil,
|
||||
"cpu": m.cpuStatus()}
|
||||
if err != nil {
|
||||
out["why"] = err.Error()
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ func (m *Module) storeFlags() []string {
|
||||
// error, and the next look refuses with it.
|
||||
func (m *Module) env() []string {
|
||||
_ = m.ensureRuntimeDir()
|
||||
return []string{"XDG_RUNTIME_DIR=" + m.runtimeDir()}
|
||||
return append([]string{"XDG_RUNTIME_DIR=" + m.runtimeDir()}, m.threadEnv()...)
|
||||
}
|
||||
|
||||
// runtimeDir is buildah's runtime directory, inside the store.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"capacity": "shanks=3,ace=2,g14=0,novox=0"
|
||||
"capacity": "shanks=3,ace=2,g14=0,novox=0",
|
||||
"cpu-cap": "shanks=50,g14=50,ace=75"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ module meshbuildimage
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025175617-518dae5ec9c8
|
||||
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
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025175617-518dae5ec9c8 h1:x4mvY7yiGTP5lnzxbiwyj1o24p05hAmzZP7xHg3XX2M=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261025175617-518dae5ec9c8/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=
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
"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": [
|
||||
@@ -77,7 +83,7 @@
|
||||
"path": "${dir:state}/settings.json",
|
||||
"mode": "0600",
|
||||
"owner": "mesh-build-image",
|
||||
"takes-config": ["capacity"],
|
||||
"takes-config": ["capacity", "cpu-cap"],
|
||||
"merge": "json",
|
||||
"template": "files/settings.json"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user