Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Lx9FNLG2CqeBbVGGd9bFsP
90 lines
2.3 KiB
Go
90 lines
2.3 KiB
Go
package groups
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/v10networks/nmon/internal/store"
|
|
)
|
|
|
|
// plan is the membership of one group over a time range, reduced to the rows
|
|
// that may contribute and the LAG relations among them.
|
|
type plan struct {
|
|
members []store.GroupMember
|
|
lag map[string]string // member interface -> its LAG interface
|
|
}
|
|
|
|
// newPlan loads the memberships of groupID that overlap [from, to) and drops
|
|
// rows of unreliable interfaces for good: they never reach a sum or a p95.
|
|
func (s *Service) newPlan(ctx context.Context, groupID int64, from, to time.Time) (plan, error) {
|
|
rows, err := s.o.Inventory.GroupMembers(ctx, groupID, from, to)
|
|
if err != nil {
|
|
return plan{}, fmt.Errorf("groups: members of %d: %w", groupID, err)
|
|
}
|
|
reliable := map[string]bool{}
|
|
var kept []store.GroupMember
|
|
var ids []string
|
|
for _, m := range rows {
|
|
ok, seen := reliable[m.InterfaceID]
|
|
if !seen {
|
|
ifc, err := s.o.Inventory.GetInterface(ctx, m.InterfaceID)
|
|
if err != nil {
|
|
return plan{}, fmt.Errorf("groups: interface %s: %w", m.InterfaceID, err)
|
|
}
|
|
ok = !ifc.Unreliable
|
|
reliable[m.InterfaceID] = ok
|
|
if ok {
|
|
ids = append(ids, m.InterfaceID)
|
|
}
|
|
}
|
|
if ok {
|
|
kept = append(kept, m)
|
|
}
|
|
}
|
|
p := plan{members: kept, lag: map[string]string{}}
|
|
if s.o.LagParent != nil && len(ids) > 0 {
|
|
if p.lag, err = s.o.LagParent(ctx, ids); err != nil {
|
|
return plan{}, fmt.Errorf("groups: LAG relations: %w", err)
|
|
}
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// interfaceIDs lists the distinct member interfaces, sorted.
|
|
func (p plan) interfaceIDs() []string {
|
|
seen := map[string]bool{}
|
|
var ids []string
|
|
for _, m := range p.members {
|
|
if !seen[m.InterfaceID] {
|
|
seen[m.InterfaceID] = true
|
|
ids = append(ids, m.InterfaceID)
|
|
}
|
|
}
|
|
sort.Strings(ids)
|
|
return ids
|
|
}
|
|
|
|
// eligibleAt returns the members that contribute at t: those in force, less any
|
|
// LAG member whose LAG is also in force in the group (counting both would
|
|
// count the same bits twice).
|
|
func (p plan) eligibleAt(t time.Time) []store.GroupMember {
|
|
var active []store.GroupMember
|
|
in := map[string]bool{}
|
|
for _, m := range p.members {
|
|
if m.Covers(t) {
|
|
active = append(active, m)
|
|
in[m.InterfaceID] = true
|
|
}
|
|
}
|
|
out := active[:0]
|
|
for _, m := range active {
|
|
if parent, ok := p.lag[m.InterfaceID]; ok && in[parent] {
|
|
continue
|
|
}
|
|
out = append(out, m)
|
|
}
|
|
return out
|
|
}
|