Merge pull request 'Build modules size their steps from the node's build share once it is given (issue 1806, change A)' (#499) from fix/build-cpu-yields into main
This commit was merged in pull request #499.
This commit is contained in:
@@ -183,12 +183,12 @@ func (m *Module) buildEnv(dir string) []string {
|
|||||||
} else if modfile, ok := m.pinModfile(dir); ok {
|
} else if modfile, ok := m.pinModfile(dir); ok {
|
||||||
mod += " -modfile=" + modfile // a pin on a pull request's head the fetch placed beside the workspace (pins.go)
|
mod += " -modfile=" + modfile // a pin on a pull request's head the fetch placed beside the workspace (pins.go)
|
||||||
}
|
}
|
||||||
return []string{
|
return append([]string{
|
||||||
"CGO_ENABLED=0",
|
"CGO_ENABLED=0",
|
||||||
"GOFLAGS=" + recipeFlags + " " + mod,
|
"GOFLAGS=" + recipeFlags + " " + mod,
|
||||||
"GOTOOLCHAIN=local",
|
"GOTOOLCHAIN=local",
|
||||||
"GOMODCACHE=" + filepath.Join(m.Cache, "gomod"),
|
"GOMODCACHE=" + filepath.Join(m.Cache, "gomod"),
|
||||||
"GOCACHE=" + filepath.Join(m.Cache, "gobuild"),
|
"GOCACHE=" + filepath.Join(m.Cache, "gobuild"),
|
||||||
"GOPROXY=off",
|
"GOPROXY=off",
|
||||||
}
|
}, m.threadEnv()...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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 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:
|
||||||
|
// that quota bounds the total. Threads only size each step: the share the composer gives (buildstep.NodeLimits) over
|
||||||
|
// this module's own capacity, so a step does not wait in the kernel's throttle. Without the composer's share there is
|
||||||
|
// no slice to size a step to, and no thread limit is set.
|
||||||
|
|
||||||
|
// cpuLimits is what one step is held to now.
|
||||||
|
func (m *Module) cpuLimits() 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)()
|
||||||
|
}
|
||||||
|
return buildstep.NodeLimits(slots)
|
||||||
|
}
|
||||||
|
|
||||||
|
// threadEnv is the environment (nothing without a share) 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().Threads)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cpuStatus is what the status tool says of it: the limits a step is given, and what the kernel holds this runtime
|
||||||
|
// 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(), "in-force": buildcpu.ReadInForce()}
|
||||||
|
}
|
||||||
@@ -292,7 +292,7 @@ func (m *Module) goEnv() []string {
|
|||||||
if m.GoModCache != "" {
|
if m.GoModCache != "" {
|
||||||
modcache = m.GoModCache
|
modcache = m.GoModCache
|
||||||
}
|
}
|
||||||
return []string{
|
return append([]string{
|
||||||
"GOTOOLCHAIN=local",
|
"GOTOOLCHAIN=local",
|
||||||
"GOFLAGS=-mod=mod",
|
"GOFLAGS=-mod=mod",
|
||||||
"GOMODCACHE=" + modcache,
|
"GOMODCACHE=" + modcache,
|
||||||
@@ -300,7 +300,7 @@ func (m *Module) goEnv() []string {
|
|||||||
"GOPRIVATE=git.novox.be",
|
"GOPRIVATE=git.novox.be",
|
||||||
"GIT_TERMINAL_PROMPT=0",
|
"GIT_TERMINAL_PROMPT=0",
|
||||||
"GIT_SSH_COMMAND=ssh -o BatchMode=yes",
|
"GIT_SSH_COMMAND=ssh -o BatchMode=yes",
|
||||||
}
|
}, m.threadEnv()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// goEnvFor is goEnv for a command run in dir: a directory that vendors (vendor/modules.txt) is read from its vendor
|
// goEnvFor is goEnv for a command run in dir: a directory that vendors (vendor/modules.txt) is read from its vendor
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.novox.be/novox/mesh-sdk/go/buildcpu"
|
||||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||||
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
"git.novox.be/novox/mesh-sdk/go/forgekey"
|
||||||
"git.novox.be/novox/mesh-sdk/go/health"
|
"git.novox.be/novox/mesh-sdk/go/health"
|
||||||
@@ -28,6 +31,7 @@ func toolchainValues(env []string) []string {
|
|||||||
// 1572/AC1 (ADR 0477 §1): every environment a step gives its go commands says GOTOOLCHAIN=local, once, and names no
|
// 1572/AC1 (ADR 0477 §1): every environment a step gives its go commands says GOTOOLCHAIN=local, once, and names no
|
||||||
// release.
|
// release.
|
||||||
func TestEveryStepEnvironmentSaysGotoolchainLocal(t *testing.T) {
|
func TestEveryStepEnvironmentSaysGotoolchainLocal(t *testing.T) {
|
||||||
|
t.Setenv("MESH_BUILD_CPU_SHARE", "50")
|
||||||
m := &Module{Cache: t.TempDir()}
|
m := &Module{Cache: t.TempDir()}
|
||||||
vend := t.TempDir()
|
vend := t.TempDir()
|
||||||
os.MkdirAll(filepath.Join(vend, "vendor"), 0o755)
|
os.MkdirAll(filepath.Join(vend, "vendor"), 0o755)
|
||||||
@@ -41,6 +45,24 @@ func TestEveryStepEnvironmentSaysGotoolchainLocal(t *testing.T) {
|
|||||||
if got := toolchainValues(env); len(got) != 1 || got[0] != "GOTOOLCHAIN=local" {
|
if got := toolchainValues(env); len(got) != 1 || got[0] != "GOTOOLCHAIN=local" {
|
||||||
t.Errorf("%s: GOTOOLCHAIN settings %v, want exactly GOTOOLCHAIN=local", name, got)
|
t.Errorf("%s: GOTOOLCHAIN settings %v, want exactly GOTOOLCHAIN=local", name, got)
|
||||||
}
|
}
|
||||||
|
// Issue 1806: and its threads, its part of the node's processor cap (probeEnv runs no build).
|
||||||
|
if procs := strings.Count(strings.Join(env, "\n"), "GOMAXPROCS="); name != "probeEnv" && procs != 1 {
|
||||||
|
t.Errorf("%s: GOMAXPROCS said %d times, want once: %v", name, procs, env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Without the composer's share no thread limit; with it, the share over its own three slots (ADR 0553 §4).
|
||||||
|
t.Setenv("MESH_BUILD_CPU_SHARE", "")
|
||||||
|
m = &Module{Cache: t.TempDir(), Node: "shanks", Capacity: func() int { return 3 }}
|
||||||
|
if l := m.cpuLimits(); l.Threads != 0 || strings.Contains(strings.Join(m.goEnv(), " "), "GOMAXPROCS=") {
|
||||||
|
t.Errorf("without the share: %+v, goEnv %v", l, m.goEnv())
|
||||||
|
}
|
||||||
|
t.Setenv("MESH_BUILD_CPU_SHARE", "50")
|
||||||
|
want := buildcpu.Threads(50, runtime.NumCPU(), 3)
|
||||||
|
if l := m.cpuLimits(); l.Slots != 3 || !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) != m.cpuLimits() {
|
||||||
|
t.Errorf("the status says %v", st)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func (m *Module) isolation(workspace string, write bool) buildstep.Isolation {
|
|||||||
Workspace: filepath.Join(m.Work, workspace),
|
Workspace: filepath.Join(m.Work, workspace),
|
||||||
Caches: m.caches(),
|
Caches: m.caches(),
|
||||||
WriteCaches: write,
|
WriteCaches: write,
|
||||||
Env: []string{"PATH=" + path, "LC_ALL=C"},
|
Env: append([]string{"PATH=" + path, "LC_ALL=C"}, m.threadEnv()...),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -189,13 +189,14 @@ func (m *Module) Short(run func(ctx context.Context, args map[string]any) (any,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Status is what the module reports of itself: the node, the recipe, the toolchain found and why it refuses, if it
|
// Status is what the module reports of itself: the node, the recipe, the toolchain found and why it refuses, if it
|
||||||
// does.
|
// does, and the processors its steps may take (issue 1806).
|
||||||
func (m *Module) Status(ctx context.Context) map[string]any {
|
func (m *Module) Status(ctx context.Context) map[string]any {
|
||||||
err := m.Check(ctx)
|
err := m.Check(ctx)
|
||||||
installed, _ := m.Probe(ctx)
|
installed, _ := m.Probe(ctx)
|
||||||
out := map[string]any{
|
out := map[string]any{
|
||||||
"node": m.Node, "recipe": m.Recipe.Version(), "installed": installed,
|
"node": m.Node, "recipe": m.Recipe.Version(), "installed": installed,
|
||||||
"postgres-major": m.Recipe.PostgresMajor, "nats-server": m.Recipe.NatsServer, "refusing": err != nil,
|
"postgres-major": m.Recipe.PostgresMajor, "nats-server": m.Recipe.NatsServer, "refusing": err != nil,
|
||||||
|
"cpu": m.cpuStatus(),
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
out["why"] = err.Error()
|
out["why"] = err.Error()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module meshbuildgo
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026130530-33395f68b309
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026134601-17b7cc480d6d
|
||||||
github.com/distribution/distribution/v3 v3.1.2
|
github.com/distribution/distribution/v3 v3.1.2
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026130530-33395f68b309 h1:mKJToG8E6ZnZj9700mtfu6YUNNnxzSj0KTfZplQkUCo=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026134601-17b7cc480d6d h1:7F/dFerm01wvuGLPgJ67/VfdcvDexc+BQEE3qzOrlu4=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026130530-33395f68b309/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026134601-17b7cc480d6d/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
|
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/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=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ func setup(t *testing.T, edit func(string) string) (*Module, *fake, *keptStates)
|
|||||||
// Dockerfile of the module's own directory and that directory as the context, the target, the pinned base as a build
|
// Dockerfile of the module's own directory and that directory as the context, the target, the pinned base as a build
|
||||||
// argument; no mount, no credential file, a runtime directory of its own, and it answers the image's digest.
|
// argument; no mount, no credential file, a runtime directory of its own, and it answers the image's digest.
|
||||||
func TestBuildRunsBuildahRootlessAndAnswersTheDigest(t *testing.T) {
|
func TestBuildRunsBuildahRootlessAndAnswersTheDigest(t *testing.T) {
|
||||||
|
t.Setenv("MESH_BUILD_CPU_SHARE", "50") // the composer's share (ADR 0553): buildah is given its threads
|
||||||
m, f, states := setup(t, same)
|
m, f, states := setup(t, same)
|
||||||
res, st := runBuild(t, m, states, nil)
|
res, st := runBuild(t, m, states, nil)
|
||||||
if st.State != delivery.StepPassed {
|
if st.State != delivery.StepPassed {
|
||||||
@@ -110,6 +111,9 @@ func TestBuildRunsBuildahRootlessAndAnswersTheDigest(t *testing.T) {
|
|||||||
if !strings.Contains(env, "XDG_RUNTIME_DIR="+m.Store+"/xdg") {
|
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)
|
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 under a share (ADR 0553): %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.
|
// 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 {
|
if len(f.called("rmi")) != 1 {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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 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:
|
||||||
|
// that quota bounds the total. Threads only size each step: the share the composer gives (buildstep.NodeLimits) over
|
||||||
|
// this module's own capacity, so a step does not wait in the kernel's throttle. Without the composer's share there is
|
||||||
|
// no slice to size a step to, and no thread limit is set.
|
||||||
|
|
||||||
|
// cpuLimits is what one step is held to now.
|
||||||
|
func (m *Module) cpuLimits() 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)()
|
||||||
|
}
|
||||||
|
return buildstep.NodeLimits(slots)
|
||||||
|
}
|
||||||
|
|
||||||
|
// threadEnv is the environment (nothing without a share) 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 slice's quota and the nice level bind it.
|
||||||
|
func (m *Module) threadEnv() []string {
|
||||||
|
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 (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(), "in-force": buildcpu.ReadInForce()}
|
||||||
|
}
|
||||||
@@ -131,7 +131,8 @@ func (m *Module) Health(ctx context.Context) health.Answer {
|
|||||||
// Status is what the module reports of itself.
|
// Status is what the module reports of itself.
|
||||||
func (m *Module) Status(ctx context.Context) map[string]any {
|
func (m *Module) Status(ctx context.Context) map[string]any {
|
||||||
l, err := m.look(ctx)
|
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 {
|
if err != nil {
|
||||||
out["why"] = err.Error()
|
out["why"] = err.Error()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ func (m *Module) storeFlags() []string {
|
|||||||
// error, and the next look refuses with it.
|
// error, and the next look refuses with it.
|
||||||
func (m *Module) env() []string {
|
func (m *Module) env() []string {
|
||||||
_ = m.ensureRuntimeDir()
|
_ = 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.
|
// runtimeDir is buildah's runtime directory, inside the store.
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module meshbuildimage
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026125154-6b8497314297
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026134601-17b7cc480d6d
|
||||||
github.com/nats-io/nats-server/v2 v2.11.17
|
github.com/nats-io/nats-server/v2 v2.11.17
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026125154-6b8497314297 h1:k31FtFp/DGXGatRjKV1S0z1gBAHfVgtvBX6xF0g1Vgw=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026134601-17b7cc480d6d h1:7F/dFerm01wvuGLPgJ67/VfdcvDexc+BQEE3qzOrlu4=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026125154-6b8497314297/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026134601-17b7cc480d6d/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 h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI=
|
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=
|
github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo=
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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 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:
|
||||||
|
// that quota bounds the total. Threads only size each step: the share the composer gives (buildstep.NodeLimits) over
|
||||||
|
// this module's own capacity, so a step does not wait in the kernel's throttle. Without the composer's share there is
|
||||||
|
// no slice to size a step to, and no thread limit is set.
|
||||||
|
|
||||||
|
// cpuLimits is what one step is held to now.
|
||||||
|
func (m *Module) cpuLimits() 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)()
|
||||||
|
}
|
||||||
|
return buildstep.NodeLimits(slots)
|
||||||
|
}
|
||||||
|
|
||||||
|
// threadEnv is the environment (nothing without a share) 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().Threads)
|
||||||
|
}
|
||||||
|
|
||||||
|
// cpuStatus is what the status tool says of it: the limits a step is given, and what the kernel holds this runtime
|
||||||
|
// 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(), "in-force": buildcpu.ReadInForce()}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -225,6 +226,7 @@ func mustReadDir(t *testing.T, dir string) []string {
|
|||||||
// are given PATH, HOME and the locale and their own words; and the real clean runner, run on `env` here, prints only
|
// are given PATH, HOME and the locale and their own words; and the real clean runner, run on `env` here, prints only
|
||||||
// those.
|
// those.
|
||||||
func TestNoCommandOfTheStepSeesATokenOfTheProcess(t *testing.T) {
|
func TestNoCommandOfTheStepSeesATokenOfTheProcess(t *testing.T) {
|
||||||
|
t.Setenv("MESH_BUILD_CPU_SHARE", "50")
|
||||||
t.Setenv("MESH_BUS_CREDENTIAL", "hunter2")
|
t.Setenv("MESH_BUS_CREDENTIAL", "hunter2")
|
||||||
t.Setenv("NPM_TOKEN", "hunter3")
|
t.Setenv("NPM_TOKEN", "hunter3")
|
||||||
files := typeScriptModule(`{"dependencies":{"lodash":"^4"}}`)
|
files := typeScriptModule(`{"dependencies":{"lodash":"^4"}}`)
|
||||||
@@ -245,6 +247,11 @@ func TestNoCommandOfTheStepSeesATokenOfTheProcess(t *testing.T) {
|
|||||||
if len(c.env) < 3 || !strings.HasPrefix(c.env[0], "PATH=") || !strings.HasPrefix(c.env[1], "HOME=") || c.env[2] != "LC_ALL=C" {
|
if len(c.env) < 3 || !strings.HasPrefix(c.env[0], "PATH=") || !strings.HasPrefix(c.env[1], "HOME=") || c.env[2] != "LC_ALL=C" {
|
||||||
t.Errorf("%s was given %v, want PATH, HOME and the locale first", c.name, c.env)
|
t.Errorf("%s was given %v, want PATH, HOME and the locale first", c.name, c.env)
|
||||||
}
|
}
|
||||||
|
// And its threads, its part of the node's share the composer gave (ADR 0553).
|
||||||
|
if !slices.ContainsFunc(c.env, func(e string) bool { return strings.HasPrefix(e, "GOMAXPROCS=") }) ||
|
||||||
|
!slices.ContainsFunc(c.env, func(e string) bool { return strings.HasPrefix(e, "UV_THREADPOOL_SIZE=") }) {
|
||||||
|
t.Errorf("%s was given %v, without its threads", c.name, c.env)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// The runner main wires: what it hands a command is exactly what the step gave.
|
// The runner main wires: what it hands a command is exactly what the step gave.
|
||||||
ran := forgekey.ExecRunnerClean(10*time.Second)(context.Background(), append(m.baseEnv(), "ONLY=this"), "env")
|
ran := forgekey.ExecRunnerClean(10*time.Second)(context.Background(), append(m.baseEnv(), "ONLY=this"), "env")
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func (m *Module) isolation(root string, write bool) buildstep.Isolation {
|
|||||||
ReadOnly: []string{m.toolchainDir()},
|
ReadOnly: []string{m.toolchainDir()},
|
||||||
Caches: []string{m.npmCache()},
|
Caches: []string{m.npmCache()},
|
||||||
WriteCaches: write,
|
WriteCaches: write,
|
||||||
Env: []string{"PATH=" + pathOrDefault(), "LC_ALL=C"},
|
Env: append([]string{"PATH=" + pathOrDefault(), "LC_ALL=C"}, m.threadEnv()...),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -205,7 +205,8 @@ func (m *Module) Status(ctx context.Context) map[string]any {
|
|||||||
err := m.Check(ctx)
|
err := m.Check(ctx)
|
||||||
installed, _ := m.Probe(ctx)
|
installed, _ := m.Probe(ctx)
|
||||||
r := m.recipe()
|
r := m.recipe()
|
||||||
out := map[string]any{"node": m.Node, "recipe": r.Version(), "installed": installed, "refusing": err != nil}
|
out := map[string]any{"node": m.Node, "recipe": r.Version(), "installed": installed, "refusing": err != nil,
|
||||||
|
"cpu": m.cpuStatus()}
|
||||||
if m.Toolchain != nil {
|
if m.Toolchain != nil {
|
||||||
out["toolchain-dir"] = m.Toolchain.Paths.Dir(r.Version())
|
out["toolchain-dir"] = m.Toolchain.Paths.Dir(r.Version())
|
||||||
out["toolchain-installed"] = m.Toolchain.Present(r.Version())
|
out["toolchain-installed"] = m.Toolchain.Present(r.Version())
|
||||||
@@ -230,7 +231,7 @@ const defaultPath = "/usr/local/bin:/usr/bin:/bin"
|
|||||||
// files and git configuration) and a fixed locale. A command is given these and its own words, never this process's
|
// files and git configuration) and a fixed locale. A command is given these and its own words, never this process's
|
||||||
// environment, which holds the module's bus credentials (ADR 0466 §4).
|
// environment, which holds the module's bus credentials (ADR 0466 §4).
|
||||||
func (m *Module) baseEnv() []string {
|
func (m *Module) baseEnv() []string {
|
||||||
return []string{"PATH=" + pathOrDefault(), "HOME=" + m.Toolchain.homeOrEmpty(), "LC_ALL=C"}
|
return append([]string{"PATH=" + pathOrDefault(), "HOME=" + m.Toolchain.homeOrEmpty(), "LC_ALL=C"}, m.threadEnv()...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func pathOrDefault() string {
|
func pathOrDefault() string {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ module meshbuildnode
|
|||||||
go 1.26.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026125154-6b8497314297
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026134601-17b7cc480d6d
|
||||||
github.com/distribution/distribution/v3 v3.1.2
|
github.com/distribution/distribution/v3 v3.1.2
|
||||||
github.com/nats-io/nats.go v1.54.0
|
github.com/nats-io/nats.go v1.54.0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026125154-6b8497314297 h1:k31FtFp/DGXGatRjKV1S0z1gBAHfVgtvBX6xF0g1Vgw=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026134601-17b7cc480d6d h1:7F/dFerm01wvuGLPgJ67/VfdcvDexc+BQEE3qzOrlu4=
|
||||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026125154-6b8497314297/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261026134601-17b7cc480d6d/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY=
|
||||||
github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
|
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/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=
|
github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0=
|
||||||
|
|||||||
Reference in New Issue
Block a user