disk-load retires a reading only on its module's key, and measures as soon as its watch catches up (review round 1)
A round no longer retires readings missing from the watched set, which may be empty or partial; a module's readings are retired when its key of mesh-host_data is deleted or names fewer items. A round refused before the watch caught up runs once it has, not an hour later. disk_round reads this machine's key alone, as its grant allows. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -115,5 +115,7 @@ func (w *Watched) Follow(k *Keeper) error {
|
||||
return nil
|
||||
})
|
||||
w.Ready()
|
||||
// A round refused while the watch caught up runs now, not at the next hour (the delivery's readings within minutes).
|
||||
go k.RoundIfRefused(context.Background())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ func TestTheItemsComeFromTheEnginesKeysAndARoundSaysHowFarItGot(t *testing.T) {
|
||||
if r := round(); r.Ended == nil || !strings.Contains(r.Error, "not watched yet") {
|
||||
t.Fatalf("the round before the watch is kept as %+v", r)
|
||||
}
|
||||
refusedAt := round().Started
|
||||
for _, step := range []struct {
|
||||
name string
|
||||
change stdio.StateChange
|
||||
@@ -88,11 +89,16 @@ func TestTheItemsComeFromTheEnginesKeysAndARoundSaysHowFarItGot(t *testing.T) {
|
||||
}
|
||||
w.Ready()
|
||||
|
||||
// A round measures every item it was handed; the missing one fails, and the round goes on and says it.
|
||||
if _, _, err := k.Round(context.Background(), false); err != nil {
|
||||
t.Fatalf("the round: %v", err)
|
||||
// The round refused before the watch caught up runs once it has (Watched.Follow), not at the next hour: it measures
|
||||
// every item it was handed; the missing one fails, and the round goes on and says it.
|
||||
now = now.Add(time.Minute)
|
||||
if !k.RoundIfRefused(context.Background()) || k.RoundIfRefused(context.Background()) {
|
||||
t.Fatal("the round refused before the watch was not run once when the watch caught up")
|
||||
}
|
||||
r := round()
|
||||
if !r.Started.After(refusedAt) {
|
||||
t.Fatalf("the round's key still says the refused round: %+v", r)
|
||||
}
|
||||
if r.Ended == nil || r.Items != 3 || r.Measured != 3 || len(r.Failed) != 1 || r.Failed[0].Item != "pg.wal" ||
|
||||
!strings.Contains(r.Failed[0].Why, "does not exist") || measured["/srv/pg"] != 1 || measured["/srv/web"] != 1 {
|
||||
t.Fatalf("the round's key says %+v; measured %v", r, measured)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
@@ -59,6 +60,19 @@ type Keeper struct {
|
||||
pmu sync.Mutex
|
||||
pending map[string]bool
|
||||
wake chan struct{}
|
||||
|
||||
// unwatched says a round was refused because the watch had not caught up (ErrNotWatchedYet): one runs as soon as
|
||||
// it has (Watched.Follow), not an hour later.
|
||||
unwatched atomic.Bool
|
||||
}
|
||||
|
||||
// RoundIfRefused runs one round now when the last was refused for a watch not caught up, and says whether it did.
|
||||
func (k *Keeper) RoundIfRefused(ctx context.Context) bool {
|
||||
if !k.unwatched.CompareAndSwap(true, false) {
|
||||
return false
|
||||
}
|
||||
_, _, _ = k.Round(ctx, false)
|
||||
return true
|
||||
}
|
||||
|
||||
// SayEvery is how many items a round measures between two lines saying how far it got.
|
||||
@@ -120,6 +134,9 @@ func (k *Keeper) Round(ctx context.Context, walks bool) (written, kept int, err
|
||||
k.Say("a round of measuring the data declared here starts (walks: %v)", walks)
|
||||
items, err := k.Items(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrNotWatchedYet) {
|
||||
k.unwatched.Store(true)
|
||||
}
|
||||
r.Error = err.Error()
|
||||
end()
|
||||
k.Say("the round measured nothing: %v", err)
|
||||
@@ -164,13 +181,8 @@ func (k *Keeper) Round(ctx context.Context, walks bool) (written, kept int, err
|
||||
}
|
||||
}
|
||||
}
|
||||
retired, err := k.retireGone(items)
|
||||
written += retired
|
||||
r.Written = written
|
||||
if err != nil {
|
||||
failed = append(failed, err.Error())
|
||||
r.Failed = append(r.Failed, FailedItem{Item: "the readings no longer declared", Why: err.Error()})
|
||||
}
|
||||
// A round retires nothing: a reading is retired only when its module's key says so (MeasureModule), never because
|
||||
// an item is missing from what the watch holds now, which may be empty or partial.
|
||||
end()
|
||||
k.Say("the round ended: %d of %d items measured, %d readings written, %d unchanged, %d failed", r.Measured, r.Items,
|
||||
written, kept, len(r.Failed))
|
||||
@@ -237,13 +249,13 @@ func (k *Keeper) MeasureModule(ctx context.Context, module string) {
|
||||
k.Say("%s's data items changed and are not measured: %v", module, err)
|
||||
return
|
||||
}
|
||||
n, err := k.retireGone(module, items[module])
|
||||
if err != nil {
|
||||
k.Say("%s's readings of items it no longer declares here are not retired: %v", module, err)
|
||||
} else if n > 0 {
|
||||
k.Say("%s no longer declares %d item(s) here: their readings are retired, measured no more", module, n)
|
||||
}
|
||||
if len(items[module]) == 0 {
|
||||
n, err := k.retireGone(items)
|
||||
if err != nil {
|
||||
k.Say("%s declares no data item here any more, and its readings are not retired: %v", module, err)
|
||||
return
|
||||
}
|
||||
k.Say("%s declares no data item here any more: %d reading(s) retired, measured no more", module, n)
|
||||
return
|
||||
}
|
||||
written, kept := 0, 0
|
||||
@@ -268,19 +280,19 @@ func (k *Keeper) MeasureModule(ctx context.Context, module string) {
|
||||
kept, len(failed), map[bool]string{true: " (" + strings.Join(failed, "; ") + ")", false: ""}[len(failed) > 0])
|
||||
}
|
||||
|
||||
// retireGone marks retired every reading of this node whose item the node no longer declares — its module was unassigned
|
||||
// there, or is no longer pulled in — with when and why, and keeps it: the record of what the module left behind, until a
|
||||
// person deletes it (ADR 0233). It is cleared when the item is declared again (measureOne).
|
||||
func (k *Keeper) retireGone(declared map[string][]catalogue.PlacedDataItem) (int, error) {
|
||||
// retireGone marks retired every reading of this node of module whose item its key of the engine's state data no longer
|
||||
// names — the key was deleted (the module was unassigned there, or is no longer pulled in) or names fewer items — with
|
||||
// when and why, and keeps it: the record of what the module left behind, until a person deletes it (ADR 0233). It is
|
||||
// asked only on a change of that module's key (novox/hq ADR 0577 clause 2), never from a round. It is cleared when the
|
||||
// item is declared again (measureOne).
|
||||
func (k *Keeper) retireGone(module string, declared []catalogue.PlacedDataItem) (int, error) {
|
||||
keys, err := k.Store.Keys()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
declaredNow := map[string]bool{}
|
||||
for module, its := range declared {
|
||||
for _, it := range its {
|
||||
declaredNow[health.ReadingKey(k.Node, module, it.ID)] = true
|
||||
}
|
||||
for _, it := range declared {
|
||||
declaredNow[health.ReadingKey(k.Node, module, it.ID)] = true
|
||||
}
|
||||
now := k.Measurer.Now().UTC()
|
||||
n := 0
|
||||
@@ -293,7 +305,7 @@ func (k *Keeper) retireGone(declared map[string][]catalogue.PlacedDataItem) (int
|
||||
continue
|
||||
}
|
||||
r, err := health.DecodeReading(raw)
|
||||
if err != nil || r.Node != k.Node || !r.RetiredAt.IsZero() {
|
||||
if err != nil || r.Node != k.Node || r.Module != module || !r.RetiredAt.IsZero() {
|
||||
continue
|
||||
}
|
||||
r.RetiredAt = now
|
||||
|
||||
@@ -78,11 +78,16 @@ func TestARoundMeasuresTheDeclaredItemsAndWritesWhatChanged(t *testing.T) {
|
||||
if _, _, err := k.MeasureNow(context.Background(), "pg", "data"); err != nil || walks != 2 {
|
||||
t.Fatalf("measure now: %v, %d walks", err, walks)
|
||||
}
|
||||
// An item no longer declared is kept, retired with when and why; declared again, it is not retired any more.
|
||||
// A round retires nothing, even when no item is declared (the watched set may be empty or partial); an item its
|
||||
// module's key no longer names is kept, retired with when and why; declared again, it is not retired any more.
|
||||
delete(placed, "pg")
|
||||
if _, _, err := k.Round(context.Background(), false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if kept, _ := health.DecodeReading(store.m["ace.pg.data"]); !kept.RetiredAt.IsZero() {
|
||||
t.Fatalf("a round retired a reading: %+v", kept)
|
||||
}
|
||||
k.MeasureModule(context.Background(), "pg")
|
||||
gone, _ := health.DecodeReading(store.m["ace.pg.data"])
|
||||
if gone.RetiredAt.IsZero() || !strings.Contains(gone.RetiredWhy, "no longer declared on ace") || gone.FirstSeen.IsZero() {
|
||||
t.Fatalf("a reading of an item no longer declared: %+v", gone)
|
||||
|
||||
@@ -73,17 +73,12 @@ func ReadingTools(k *Keeper) []stdio.Tool {
|
||||
{
|
||||
Name: "disk_round",
|
||||
Description: "How far this machine's last round of measuring its data items got (novox/hq ADR 0577 clause 3), from " +
|
||||
"disk-load's state `rounds`, key `<node>`: when it started and ended (no end while it runs), how many items it had, " +
|
||||
"measured, wrote and kept unchanged, each item that failed and why, and how old the round is; `node` reads " +
|
||||
"another machine's key. A round that never ends, or fails every item, shows here. (r)",
|
||||
Input: map[string]any{
|
||||
"node": map[string]any{"type": "string", "description": "the machine whose last round to read; this one by default"},
|
||||
},
|
||||
"disk-load's state `rounds`, this machine's key `<node>`: when it started and ended (no end while it runs), how " +
|
||||
"many items it had, measured, wrote and kept unchanged, each item that failed and why, and how old the round is. " +
|
||||
"A round that never ends, or fails every item, shows here. (r)",
|
||||
Input: map[string]any{},
|
||||
Run: func(args map[string]any) (any, error) {
|
||||
node := text(args, "node")
|
||||
if node == "" {
|
||||
node = k.Node
|
||||
}
|
||||
node := k.Node
|
||||
if k.Rounds == nil {
|
||||
return nil, fmt.Errorf("this disk-load keeps no rounds")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user