diff --git a/modules/mesh-delivery-view/go.mod b/modules/mesh-delivery-view/go.mod index d082fed..e5b4817 100644 --- a/modules/mesh-delivery-view/go.mod +++ b/modules/mesh-delivery-view/go.mod @@ -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 diff --git a/modules/mesh-delivery-view/go.sum b/modules/mesh-delivery-view/go.sum index a460d12..98dc72c 100644 --- a/modules/mesh-delivery-view/go.sum +++ b/modules/mesh-delivery-view/go.sum @@ -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= diff --git a/modules/mesh-delivery-view/internal/view/usage.go b/modules/mesh-delivery-view/internal/view/usage.go index 35e1fbc..49787a3 100644 --- a/modules/mesh-delivery-view/internal/view/usage.go +++ b/modules/mesh-delivery-view/internal/view/usage.go @@ -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{} // . -> setting + resolved := map[string]int{} // . -> its capacity unknown := "" // why this tool cannot say a capacity: mesh-config's values cannot be read here unreadable := map[string]string{} // . -> 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 diff --git a/modules/mesh-delivery-view/internal/view/usage_test.go b/modules/mesh-delivery-view/internal/view/usage_test.go index dc33ff1..2762f29 100644 --- a/modules/mesh-delivery-view/internal/view/usage_test.go +++ b/modules/mesh-delivery-view/internal/view/usage_test.go @@ -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) {