package grants import ( "fmt" "strings" "git.novox.be/novox/mesh-sdk/go/asks" "git.novox.be/novox/mesh-sdk/go/catalogue" ) // **A history stream or an object bucket past 75 percent of its cap is said, in one place for every module** (novox/hq // ADR 0592 §6, §25; ADR 0593 §10 gives the controller's part to this part). Such a stream or bucket refuses a new message // or object when full rather than dropping its oldest, so filling up is the one way it fails: the part reads every // stream's size from the bus module's nats_streams at each pass (HeldStreams) and raises the warning FullCondition naming // each one past catalogue.FullWarnPercent, cleared once none is. // FullCondition is the key of the warning raised while a history stream or an object bucket is past its mark. const FullCondition = "mesh-grants.history-full" // Fill is how full one history stream or object bucket is: its module, its stream on the bus, its bytes and its cap. type Fill struct { Module string `json:"module"` Stream string `json:"stream"` Bytes int64 `json:"bytes"` Cap int64 `json:"cap"` } // Percent is its bytes in percent of its cap, rounded down. func (f Fill) Percent() int64 { return f.Bytes * 100 / f.Cap } // Full is every history stream and object bucket of a placed module past catalogue.FullWarnPercent of its cap, by the // sizes the bus holds (bytes, by stream name), in module and stream order. One the bus does not hold is not full. Pure. func Full(in Inputs, bytes map[string]int64) []Fill { var out []Fill for _, module := range sortedKeys(in.Manifests) { if !assigned(in, module) { continue } m := in.Manifests[module] var capped []Fill for _, s := range m.Streams { if s.KeepsHistory() { capped = append(capped, Fill{Module: module, Stream: s.BusName(), Cap: int64(s.Cap()) << 20}) } } for _, o := range objectsOf(m) { capped = append(capped, Fill{Module: module, Stream: "OBJ_" + o.Bucket(), Cap: int64(o.MaxMiB) << 20}) } for _, f := range capped { held, ok := bytes[f.Stream] if !ok || f.Cap <= 0 { continue } f.Bytes = held if f.Bytes*100 >= catalogue.FullWarnPercent*f.Cap { out = append(out, f) } } } return out } // sayFull raises FullCondition while a history stream or an object bucket is past its mark, and clears it once none is: // said once per set of streams, again when that set changes, and at the first pass after a start. func (p *Part) sayFull(full []Fill) { if p.Say == nil { return } var names, lines []string for _, f := range full { names = append(names, f.Stream) lines = append(lines, fmt.Sprintf("%s of %s: %d of %d MiB (%d%%)", f.Stream, f.Module, f.Bytes>>20, f.Cap>>20, f.Percent())) } said := strings.Join(names, " ") p.holdMu.Lock() defer p.holdMu.Unlock() if p.fullKnown && said == p.fullSaid { return } c := asks.Condition{Key: FullCondition, Subject: asks.Scope{Scope: "module", ID: Module}} if said == "" { c.State = asks.StateCleared } else { c.Severity, c.State = asks.SeverityWarning, asks.StateRaised c.Summary = fmt.Sprintf("%d history stream(s) or object bucket(s) past %d%% of their cap: %s", len(full), catalogue.FullWarnPercent, strings.Join(lines, "; ")) c.Headline, c.Needs = "A stream that keeps its history is filling up", "a larger cap in the module's manifest, or data removed" c.Explanation = asks.NeedsYou + "a decision before it is full: such a stream or bucket refuses new messages when full " + "rather than dropping its oldest (novox/hq ADR 0592 §6). " + strings.Join(lines, "; ") c.Resolved = "every history stream and object bucket is below its mark" } if p.Say(c) == nil { p.fullSaid, p.fullKnown = said, true } }