View: build_usage reads each node's capacity as the integer mesh-config resolves for it, default 0, since no repository names a node (ADR 0567 §12, issue 1126)
This commit is contained in:
@@ -20,3 +20,5 @@ require (
|
||||
golang.org/x/sys v0.48.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
)
|
||||
|
||||
replace git.novox.be/novox/mesh-sdk/go => /home/jochen/projects/novox/mesh-sdk/go
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028090029-3028c7ec4ed2 h1:eSwObLdxs3jxPrlcwdaMo9hzIDTUz2ogWw8cjX8To7I=
|
||||
git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261028090029-3028c7ec4ed2/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=
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/buildstep"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery/buildrun"
|
||||
)
|
||||
|
||||
// BuildUsageTool is the view's tool of the build nodes' use.
|
||||
@@ -261,7 +260,7 @@ func (u UsageReader) Usage() UsageAnswer {
|
||||
}
|
||||
|
||||
// Capacity: mesh-config's resolved value of the module's `capacity` on each node.
|
||||
resolved := map[string]string{} // <node>.<module> -> setting
|
||||
resolved := map[string]int{} // <node>.<module> -> its capacity
|
||||
unknown := "" // why this tool cannot say a capacity: mesh-config's values cannot be read here
|
||||
unreadable := map[string]string{} // <node>.<module> -> why its resolved key holds no readable value: the puller uses 0
|
||||
if u.Resolved.Keys == nil || u.Resolved.Get == nil {
|
||||
@@ -288,7 +287,7 @@ func (u UsageReader) Usage() UsageAnswer {
|
||||
Source string `json:"source"`
|
||||
}
|
||||
key := strings.TrimSuffix(k, "."+buildstep.CapacitySetting)
|
||||
setting := ""
|
||||
n := 0
|
||||
switch {
|
||||
case i >= len(raws) || len(raws[i]) == 0:
|
||||
unreadable[key] = "its resolved key holds no value"
|
||||
@@ -296,10 +295,11 @@ func (u UsageReader) Usage() UsageAnswer {
|
||||
unreadable[key] = "its resolved key is not a resolved value"
|
||||
case r.Source == "unreadable":
|
||||
unreadable[key] = "mesh-config resolves it as unreadable"
|
||||
case len(r.Value) == 0 || string(r.Value) == "null" || json.Unmarshal(r.Value, &setting) != nil || strings.TrimSpace(setting) == "":
|
||||
unreadable[key] = fmt.Sprintf("its resolved value %s is empty or not a capacity", strings.TrimSpace(string(r.Value)))
|
||||
case len(r.Value) == 0 || string(r.Value) == "null" || json.Unmarshal(r.Value, &n) != nil || n < 0:
|
||||
// The capacity is the node's integer on its layer of mesh-config (novox/hq ADR 0567 §12).
|
||||
unreadable[key] = fmt.Sprintf("its resolved value %s is not a whole number of asks at once", strings.TrimSpace(string(r.Value)))
|
||||
default:
|
||||
resolved[key] = setting
|
||||
resolved[key] = n
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -310,34 +310,10 @@ func (u UsageReader) Usage() UsageAnswer {
|
||||
node, module, _ := strings.Cut(key, ".")
|
||||
seat(node, strings.TrimPrefix(module, "mesh-build-"))
|
||||
}
|
||||
// Every node a capacity names is listed, so "is this node eligible" is answered for any node: the nodes each module's
|
||||
// resolved value names (as resolved on the first node in name order), or the modules' default when mesh-config
|
||||
// resolves none for the module.
|
||||
keys := make([]string, 0, len(resolved))
|
||||
// Every node with a resolved capacity is listed, so "is this node eligible" is answered for it.
|
||||
for key := range resolved {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
named := map[string]string{} // language -> the module's setting, as resolved on some node
|
||||
namedOn := map[string]string{} // language -> that node
|
||||
for _, key := range keys {
|
||||
node, module, _ := strings.Cut(key, ".")
|
||||
lang := strings.TrimPrefix(module, "mesh-build-")
|
||||
seat(node, lang)
|
||||
if _, ok := named[lang]; !ok {
|
||||
named[lang], namedOn[lang] = resolved[key], node
|
||||
}
|
||||
}
|
||||
for _, lang := range delivery.AskLanguages {
|
||||
setting, ok := named[lang]
|
||||
if !ok {
|
||||
setting = buildstep.DefaultCapacities
|
||||
}
|
||||
if caps, err := buildrun.ParseCapacity(setting); err == nil {
|
||||
for node := range caps {
|
||||
seat(node, lang)
|
||||
}
|
||||
}
|
||||
seat(node, strings.TrimPrefix(module, "mesh-build-"))
|
||||
}
|
||||
// The steps of each build module.
|
||||
steps := map[string]map[string][]usageStep{} // node -> language -> steps
|
||||
@@ -449,30 +425,17 @@ func (u UsageReader) Usage() UsageAnswer {
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
setting, ok := resolved[node+"."+s.Module]
|
||||
from := "mesh-config, resolved for " + s.Module + " on " + node
|
||||
if _, has := named[lang]; !ok && has {
|
||||
from = "mesh-config, resolved for " + s.Module + " on " + namedOn[lang] + " (" + s.Module + " is not assigned to " + node + ")"
|
||||
// mesh-config resolves the module's value only where it is assigned; elsewhere its capacity is what that
|
||||
// value names for the node.
|
||||
setting, ok = named[lang], true
|
||||
}
|
||||
capacity, ok := resolved[node+"."+s.Module]
|
||||
bad, isBad := unreadable[node+"."+s.Module]
|
||||
if unknown != "" {
|
||||
switch {
|
||||
case unknown != "":
|
||||
s.CapacityFrom = "capacity unknown to this tool: " + unknown
|
||||
} else if isBad {
|
||||
case isBad:
|
||||
s.CapacityFrom = "mesh-config's value for " + s.Module + " on " + node + " cannot be read (" + bad + "), so its puller uses 0"
|
||||
} else if ok {
|
||||
s.CapacityFrom = from
|
||||
if caps, err := buildrun.ParseCapacity(setting); err != nil {
|
||||
s.CapacityFrom = fmt.Sprintf("mesh-config's value %q, which cannot be read, so the module pulls nothing: %v", setting, err)
|
||||
} else {
|
||||
s.Capacity = (&buildrun.Places{Capacity: caps}).CapacityOf(node)
|
||||
}
|
||||
} else {
|
||||
caps, _ := buildrun.ParseCapacity(buildstep.DefaultCapacities)
|
||||
s.Capacity = (&buildrun.Places{Capacity: caps}).CapacityOf(node)
|
||||
s.CapacityFrom = fmt.Sprintf("the modules' default %q: mesh-config holds no resolved value for %s on %s", buildstep.DefaultCapacities, s.Module, node)
|
||||
case ok:
|
||||
s.Capacity, s.CapacityFrom = capacity, "mesh-config, resolved for "+s.Module+" on "+node
|
||||
default:
|
||||
s.CapacityFrom = fmt.Sprintf("the modules' default 0: mesh-config holds no resolved value for %s on %s", s.Module, node)
|
||||
}
|
||||
if c, ok := judged[node+"."+lang]; ok && s.Holds && unknown == "" && !isBad && c.Configured == s.Capacity {
|
||||
// The module running on the node says its build hours, and judges by less than mesh-config gives when its
|
||||
|
||||
@@ -48,9 +48,10 @@ func step(node, id, tool, state string, started, ended time.Time) string {
|
||||
return fmt.Sprintf(`{"v":1,"id":%q,"tool":%q,"node":%q,"state":%q,"started":%q%s}`, id, tool, node, state, started.Format(time.RFC3339), e)
|
||||
}
|
||||
|
||||
// usageFixture is four nodes: shanks builds Go at capacity 2 (two asks running, one left running, a try's ask, a check,
|
||||
// a recheck of the morning and a hand step ended); ace holds the Go seat at capacity 2 and is paused; g14 holds it at
|
||||
// capacity 0; the Node seat on shanks has no resolved capacity and takes the modules' default.
|
||||
// usageFixture is three nodes, each capacity the node's integer on its layer of mesh-config (ADR 0567 §12): shanks builds
|
||||
// Go at capacity 2 (two asks running, one left running, a try's ask, a check, a recheck of the morning and a hand step
|
||||
// ended); ace holds the Go seat at capacity 2 and is paused, and its Node capacity is still of the old form; g14 holds the
|
||||
// Go seat at capacity 0; the Node seat on shanks has no resolved capacity and takes the modules' default 0.
|
||||
func usageFixture() UsageReader {
|
||||
running := `{"v":1,"phase":"running","health":"ok"}`
|
||||
tryAsk := stepID(at("11:30"), "a00003")
|
||||
@@ -74,9 +75,10 @@ func usageFixture() UsageReader {
|
||||
Haves: states(map[string]string{"shanks.mesh-build-go": running, "shanks.mesh-build-node": running, "ace.mesh-build-go": running,
|
||||
"g14.mesh-build-go": running, "g14.mesh-build-node": running, "shanks.node": `{}`, "shanks.mesh-delivery-view": running}),
|
||||
Resolved: states(map[string]string{
|
||||
"shanks.mesh-build-go.capacity": `{"node":"shanks","module":"mesh-build-go","key":"capacity","value":"shanks=2,ace=2,g14=0,novox=0","source":"mesh"}`,
|
||||
"ace.mesh-build-go.capacity": `{"node":"ace","module":"mesh-build-go","key":"capacity","value":"shanks=2,ace=2,g14=0,novox=0","source":"mesh"}`,
|
||||
"g14.mesh-build-go.capacity": `{"node":"g14","module":"mesh-build-go","key":"capacity","value":"shanks=2,ace=2,g14=0,novox=0","source":"mesh"}`,
|
||||
"shanks.mesh-build-go.capacity": `{"node":"shanks","module":"mesh-build-go","key":"capacity","value":2,"source":"node"}`,
|
||||
"ace.mesh-build-go.capacity": `{"node":"ace","module":"mesh-build-go","key":"capacity","value":2,"source":"node"}`,
|
||||
"g14.mesh-build-go.capacity": `{"node":"g14","module":"mesh-build-go","key":"capacity","value":0,"source":"node"}`,
|
||||
"ace.mesh-build-node.capacity": `{"node":"ace","module":"mesh-build-node","key":"capacity","value":"shanks=2,ace=2","source":"mesh"}`,
|
||||
"shanks.mesh-config.other": `{"value":"x"}`,
|
||||
"g14.mesh-build-node.capacity": `{"node":"g14","module":"mesh-build-node","key":"capacity","source":"unreadable"}`,
|
||||
}),
|
||||
@@ -122,17 +124,16 @@ func TestBuildUsageAnswersEveryNodesUseFromTheStates(t *testing.T) {
|
||||
what string
|
||||
got, want any
|
||||
}{
|
||||
{"nodes, novox named by the default capacity", len(a.Nodes), 4},
|
||||
{"eligible", eligible, map[string]string{"shanks": "eligible", "ace": "not eligible", "g14": "not eligible", "novox": "not eligible"}},
|
||||
{"nodes, none named by a default", len(a.Nodes), 3},
|
||||
{"eligible", eligible, map[string]string{"shanks": "eligible", "ace": "not eligible", "g14": "not eligible"}},
|
||||
{"a resolved key with no readable value: the puller uses 0", seatOf(t, a, "g14", "node-build-node").WhyNot, "its capacity on g14 cannot be read, so its puller uses 0"},
|
||||
{"and is said so", seatOf(t, a, "g14", "node-build-node").CapacityFrom, "mesh-config's value for mesh-build-node on g14 cannot be read (mesh-config resolves it as unreadable), so its puller uses 0"},
|
||||
{"novox why not", seatOf(t, a, "novox", "node-build-go").WhyNot, "mesh-build-go is not on novox"},
|
||||
{"novox capacity, from the module's value resolved elsewhere", seatOf(t, a, "novox", "node-build-go").CapacityFrom, "mesh-config, resolved for mesh-build-go on ace (mesh-build-go is not assigned to novox)"},
|
||||
{"a capacity of the old form naming nodes is unreadable", seatOf(t, a, "ace", "node-build-node").CapacityFrom, `mesh-config's value for mesh-build-node on ace cannot be read (its resolved value "shanks=2,ace=2" is not a whole number of asks at once), so its puller uses 0`},
|
||||
{"ended by restart or bound, apart", len(sg.EndedStale), 2},
|
||||
{"ace why not", seatOf(t, a, "ace", "node-build-go").WhyNot, "paused (node-build-go.resume takes asks again)"},
|
||||
{"g14 why not", seatOf(t, a, "g14", "node-build-go").WhyNot, "its capacity on g14 is 0"},
|
||||
{"shanks capacity", sg.Capacity, 2},
|
||||
{"shanks node seat takes the default", seatOf(t, a, "shanks", "node-build-node").Capacity, 3},
|
||||
{"shanks node seat takes the default", seatOf(t, a, "shanks", "node-build-node").Capacity, 0},
|
||||
{"running", sg.Running.Total, 2},
|
||||
{"running by kind", sg.Running.ByKind, map[string]int{"delivery": 1, "check": 1}},
|
||||
{"running steps, the hand step's ended", len(sg.Steps), 2},
|
||||
@@ -152,7 +153,7 @@ func TestBuildUsageAnswersEveryNodesUseFromTheStates(t *testing.T) {
|
||||
{"backlog", a.Backlog, []BacklogRow{{Language: "go", Kind: "check", Waiting: 3, Held: 2, OldestSecs: 240}}},
|
||||
{"tries running", len(a.TriesRunning), 1},
|
||||
{"said", a.Nodes[0].Said[:1], []string{"node-build-go: not eligible, paused (node-build-go.resume takes asks again); 0 of capacity 2 running; last hour 0 finished, median 0 s, at capacity 0% of it; last day 0 finished, median 0 s, at capacity 0.0% of it"}},
|
||||
{"said shanks", a.Nodes[3].Said[0], "node-build-go: eligible; 2 of capacity 2 running (check 1, delivery 1), 1 left running; last hour 3 finished, median 360 s, at capacity 8% of it; last day 4 finished, median 360 s, at capacity 0.3% of it"},
|
||||
{"said shanks", a.Nodes[2].Said[0], "node-build-go: eligible; 2 of capacity 2 running (check 1, delivery 1), 1 left running; last hour 3 finished, median 360 s, at capacity 8% of it; last day 4 finished, median 360 s, at capacity 0.3% of it"},
|
||||
{"cpu says where it is", strings.Contains(a.Nodes[0].CPU, "/disk-load.cpu_top"), true},
|
||||
} {
|
||||
if fmt.Sprint(c.got) != fmt.Sprint(c.want) {
|
||||
|
||||
Reference in New Issue
Block a user