Files
nmon/internal/groups/plan.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
}