diff --git a/pkg/pools/communities.go b/pkg/pools/communities.go index 54107d1..3ec5fea 100644 --- a/pkg/pools/communities.go +++ b/pkg/pools/communities.go @@ -4,6 +4,7 @@ import ( "math" "reflect" "sync" + "unsafe" "github.com/alice-lg/alice-lg/pkg/api" ) @@ -12,14 +13,14 @@ import ( // This works with large and standard communities. For extended // communities, use the ExtCommunityPool. type CommunitiesPool struct { - root *Node + root *Node[int, api.Community] sync.RWMutex } // NewCommunitiesPool creates a new pool for a single BGP community func NewCommunitiesPool() *CommunitiesPool { return &CommunitiesPool{ - root: NewNode(api.Community{}), + root: NewNode[int, api.Community](api.Community{}), } } @@ -28,9 +29,9 @@ func (p *CommunitiesPool) Acquire(c api.Community) api.Community { p.Lock() defer p.Unlock() if len(c) == 0 { - return p.root.ptr.(api.Community) // root + return p.root.value } - return p.root.traverse(c, c).(api.Community) + return p.root.traverse(c, c) } // Read a single bgp community @@ -38,20 +39,20 @@ func (p *CommunitiesPool) Read(c api.Community) api.Community { p.RLock() defer p.RUnlock() if len(c) == 0 { - return p.root.ptr.(api.Community) // root + return p.root.value // root } - v := p.root.read(c, c) + v := p.root.read(c) if v == nil { return nil } - return v.(api.Community) + return v } // CommunitiesSetPool is for deduplicating a list of BGP communities // (Large and default. The ext communities representation right now // makes problems and need to be fixed. TODO.) type CommunitiesSetPool struct { - root *Node + root *Node[unsafe.Pointer, []api.Community] sync.Mutex } @@ -59,7 +60,7 @@ type CommunitiesSetPool struct { // of BGP communities. func NewCommunitiesSetPool() *CommunitiesSetPool { return &CommunitiesSetPool{ - root: NewNode([]api.Community{}), + root: NewNode[unsafe.Pointer, []api.Community]([]api.Community{}), } } @@ -69,24 +70,30 @@ func (p *CommunitiesSetPool) Acquire(communities []api.Community) []api.Communit defer p.Unlock() // Make identification list by using the pointer address // of the deduplicated community as ID - ids := make([]int, len(communities)) + ids := make([]unsafe.Pointer, len(communities)) set := make([]api.Community, len(communities)) for i, comm := range communities { commPtr := Communities.Acquire(comm) - addr := reflect.ValueOf(commPtr).UnsafePointer() - ids[i] = int(uintptr(addr)) + ids[i] = reflect.ValueOf(commPtr).UnsafePointer() set[i] = commPtr } if len(ids) == 0 { - return p.root.ptr.([]api.Community) + return p.root.value } - return p.root.traverse(set, ids).([]api.Community) + return p.root.traverse(set, ids) } -// NewExtCommunitiesSetPool creates a new pool for extended communities -func NewExtCommunitiesSetPool() *CommunitiesSetPool { - return &CommunitiesSetPool{ - root: NewNode([]api.ExtCommunity{}), +// ExtCommunitiesSetPool is for deduplicating a list of ext. BGP communities +type ExtCommunitiesSetPool struct { + root *Node[unsafe.Pointer, []api.ExtCommunity] + sync.Mutex +} + +// NewExtCommunitiesSetPool creates a new pool for lists +// of BGP communities. +func NewExtCommunitiesSetPool() *ExtCommunitiesSetPool { + return &ExtCommunitiesSetPool{ + root: NewNode[unsafe.Pointer, []api.ExtCommunity]([]api.ExtCommunity{}), } } @@ -99,23 +106,22 @@ func extPrefixToInt(s string) int { } // AcquireExt a list of ext bgp communities -func (p *CommunitiesSetPool) AcquireExt(communities []api.ExtCommunity) []api.ExtCommunity { +func (p *ExtCommunitiesSetPool) Acquire(communities []api.ExtCommunity) []api.ExtCommunity { p.Lock() defer p.Unlock() // Make identification list - ids := make([]int, len(communities)) + ids := make([]unsafe.Pointer, len(communities)) for i, comm := range communities { r := extPrefixToInt(comm[0].(string)) icomm := []int{r, comm[1].(int), comm[2].(int)} // get community identifier commPtr := ExtCommunities.Acquire(icomm) - addr := reflect.ValueOf(commPtr).UnsafePointer() - ids[i] = int(uintptr(addr)) + ids[i] = reflect.ValueOf(commPtr).UnsafePointer() } if len(ids) == 0 { - return p.root.ptr.([]api.ExtCommunity) + return p.root.value } - return p.root.traverse(communities, ids).([]api.ExtCommunity) + return p.root.traverse(communities, ids) } diff --git a/pkg/pools/communities_test.go b/pkg/pools/communities_test.go index 883bd4b..49777b6 100644 --- a/pkg/pools/communities_test.go +++ b/pkg/pools/communities_test.go @@ -125,14 +125,14 @@ func TestAcquireExtCommunitiesSets(t *testing.T) { c3 := []api.ExtCommunity{ {"ro", 6, 1}, {"rt", 6, 2}, - {"rt", 1, 1}, + {"xyz", 1, 1}, } p := NewExtCommunitiesSetPool() - pc1 := p.AcquireExt(c1) - pc2 := p.AcquireExt(c2) - pc3 := p.AcquireExt(c3) + pc1 := p.Acquire(c1) + pc2 := p.Acquire(c2) + pc3 := p.Acquire(c3) if fmt.Sprintf("%p", c1) == fmt.Sprintf("%p", c2) { t.Error("expected c1 !== c2") diff --git a/pkg/pools/lists.go b/pkg/pools/lists.go index d7c9275..09371d9 100644 --- a/pkg/pools/lists.go +++ b/pkg/pools/lists.go @@ -4,77 +4,19 @@ import ( "sync" ) -// Node is a node in the tree. -type Node struct { - children map[int]*Node - counter int - ptr interface{} -} - -// NewNode creates a new tree node -func NewNode(ptr interface{}) *Node { - return &Node{ - children: map[int]*Node{}, - ptr: ptr, - } -} - -// Traverse read only; returns nil if not found -func (n *Node) read(list interface{}, tail []int) interface{} { - value := tail[0] - tail = tail[1:] - - // Seek for value in children - child, ok := n.children[value] - if !ok { - return nil - } - - // Set list ptr if required - if len(tail) == 0 { - return child.ptr - } - - return child.read(list, tail) -} - -// Internally acquire list by traversing the tree and -// creating nodes if required. -func (n *Node) traverse(list interface{}, tail []int) interface{} { - value := tail[0] - tail = tail[1:] - - // Seek for value in children - child, ok := n.children[value] - if !ok { - child = NewNode(nil) - n.children[value] = child - } - - // Set list ptr if required - if len(tail) == 0 { - if child.ptr == nil { - child.ptr = list - } - return child.ptr - } - - return child.traverse(list, tail) -} - // A IntListPool can be used to deduplicate // lists of integers. Like an AS path or BGP communities. // // A Tree datastructure is used. type IntListPool struct { - root *Node + root *Node[int, []int] sync.Mutex } // NewIntListPool creates a new int list pool func NewIntListPool() *IntListPool { return &IntListPool{ - root: NewNode([]int{}), + root: NewNode[int, []int]([]int{}), } } @@ -84,16 +26,16 @@ func (p *IntListPool) Acquire(list []int) []int { defer p.Unlock() if len(list) == 0 { - return p.root.ptr.([]int) // root + return p.root.value // root } - return p.root.traverse(list, list).([]int) + return p.root.traverse(list, list) } // A StringListPool can be used for deduplicating lists // of strings. (This is a variant of an int list, as string // values are converted to int. type StringListPool struct { - root *Node + root *Node[int, []string] values map[string]int head int sync.Mutex @@ -104,14 +46,14 @@ func NewStringListPool() *StringListPool { return &StringListPool{ head: 1, values: map[string]int{}, - root: NewNode([]string{}), + root: NewNode[int, []string]([]string{}), } } // Acquire the string list pointer from the pool func (p *StringListPool) Acquire(list []string) []string { if len(list) == 0 { - return p.root.ptr.([]string) // root + return p.root.value } // Make idenfier list @@ -127,5 +69,5 @@ func (p *StringListPool) Acquire(list []string) []string { id[i] = v } - return p.root.traverse(list, id).([]string) + return p.root.traverse(list, id) } diff --git a/pkg/pools/lists_test.go b/pkg/pools/lists_test.go index 78cda5c..69c6dc4 100644 --- a/pkg/pools/lists_test.go +++ b/pkg/pools/lists_test.go @@ -2,9 +2,7 @@ package pools import ( "fmt" - "reflect" "testing" - "unsafe" ) func TestAcquireIntList(t *testing.T) { @@ -27,23 +25,6 @@ func TestAcquireIntList(t *testing.T) { t.Log(fmt.Sprintf("Ptr: %p %p => %p %p", a, b, r1, r2)) } -func TestPtrIntList(t *testing.T) { - a := []int{23, 42, 1337, 65535, 1} - b := []int{23, 42, 1337, 65535, 1} - - pa := unsafe.Pointer(&a[0]) - pb := unsafe.Pointer(&b[0]) - - t.Log(fmt.Sprintf("Ptr: %p %p", a, b)) - t.Log(fmt.Sprintf("P: %p %p", pa, pb)) - - ra := reflect.ValueOf(a).UnsafePointer() - rb := reflect.ValueOf(b).UnsafePointer() - t.Log(fmt.Sprintf("P: %x %x %v", ra, rb, ra == rb)) - - t.Log(fmt.Sprintf("P: %x %x %v", int(uintptr(ra)), rb, ra == rb)) -} - func TestAcquireStringList(t *testing.T) { q := []string{"foo", "bar", "bgp"} w := []string{"foo", "bar", "bgp"} diff --git a/pkg/pools/node.go b/pkg/pools/node.go new file mode 100644 index 0000000..1bd0f79 --- /dev/null +++ b/pkg/pools/node.go @@ -0,0 +1,311 @@ +package pools + +import ( + "unsafe" + + "github.com/alice-lg/alice-lg/pkg/api" +) + +// NOTE: Yes, generics could be used here. +// This also looks like a pretty good use case for them. +// However, the performance penalty is too high. +// +// With: Refreshed routes of rs1.foo (BIRD2 IPv4) in 26.905169348s +// Without: Refreshed routes of rs1.foo (BIRD2 IPv4) in 46.760695927s +// +// So yeah. Copy and paste time! + +type Node[T comparable, V any] struct { + children map[T]*Node[T, V] // map of children + value V + final bool +} + +// NewNode creates a new tree node +func NewNode[T comparable, V any](value V) *Node[T, V] { + return &Node[T, V]{ + children: map[T]*Node[T, V]{}, + value: value, + final: false, + } +} + +// traverse inserts a new node into the three if required +// or returns the object if it already exists. +func (n *Node[T, V]) traverse(value V, tail []T) V { + id := tail[0] + tail = tail[1:] + + // Seek for identifier in children + child, ok := n.children[id] + if !ok { + var zero V + child = NewNode[T, V](zero) + n.children[id] = child + } + + // Set obj if required + if len(tail) == 0 { + if !child.final { + child.value = value + child.final = true + } + return child.value + } + + return child.traverse(value, tail) +} + +// read returns the object if it exists or nil if not. +func (n *Node[T, V]) read(tail []T) V { + id := tail[0] + tail = tail[1:] + + // Seek for identifier in children + child, ok := n.children[id] + if !ok { + var zero V + return zero + } + + // Set obj if required + if len(tail) == 0 { + return child.value + } + + return child.read(tail) +} + +// IntListNode is a node with an integer as key and +// a list of integers as value. +type IntListNode struct { + children map[int]*IntListNode + value []int +} + +// NewIntListNode creates a new int tree node +func NewIntListNode(value []int) *IntListNode { + return &IntListNode{ + children: map[int]*IntListNode{}, + value: value, + } +} + +// IntList traverse inserts a new node into the three if required +// or returns the object if it already exists. +func (n *IntListNode) traverse(value []int, tail []int) []int { + id := tail[0] + tail = tail[1:] + + // Seek for identifier in children + child, ok := n.children[id] + if !ok { + child = NewIntListNode(nil) + n.children[id] = child + } + + // Set obj if required + if len(tail) == 0 { + if child.value == nil { + child.value = value + } + return child.value + } + + return child.traverse(value, tail) +} + +// read returns the object if it exists or nil if not. +func (n *IntListNode) read(tail []int) []int { + id := tail[0] + tail = tail[1:] + + // Seek for identifier in children + child, ok := n.children[id] + if !ok { + return nil + } + + // Set obj if required + if len(tail) == 0 { + return child.value + } + + return child.read(tail) +} + +// StringListNode is a node with an integer as key and +// a list of integers as value. +type StringListNode struct { + children map[int]*StringListNode + value []string +} + +// NewStringListNode creates a new int tree node +func NewStringListNode(value []string) *StringListNode { + return &StringListNode{ + children: map[int]*StringListNode{}, + value: value, + } +} + +// StringList traverse inserts a new node into the three if required +// or returns the object if it already exists. +func (n *StringListNode) traverse(value []string, tail []int) []string { + id := tail[0] + tail = tail[1:] + + // Seek for identifier in children + child, ok := n.children[id] + if !ok { + child = NewStringListNode(nil) + n.children[id] = child + } + + // Set obj if required + if len(tail) == 0 { + if child.value == nil { + child.value = value + } + return child.value + } + + return child.traverse(value, tail) +} + +// read returns the object if it exists or nil if not. +func (n *StringListNode) read(tail []int) []string { + id := tail[0] + tail = tail[1:] + + // Seek for identifier in children + child, ok := n.children[id] + if !ok { + return nil + } + + // Set obj if required + if len(tail) == 0 { + return child.value + } + + return child.read(tail) +} + +// CommunityNode is a node with an integer as key +type CommunityNode struct { + children map[int]*CommunityNode + value api.Community +} + +// NewCommunityNode creates a new int tree node +func NewCommunityNode(value []int) *CommunityNode { + return &CommunityNode{ + children: map[int]*CommunityNode{}, + value: value, + } +} + +// CommunityNode: traverse inserts a new node into the three if required +// or returns the object if it already exists. +func (n *CommunityNode) traverse(value api.Community, tail []int) api.Community { + id := tail[0] + tail = tail[1:] + + // Seek for identifier in children + child, ok := n.children[id] + if !ok { + child = NewCommunityNode(nil) + n.children[id] = child + } + + // Set obj if required + if len(tail) == 0 { + if child.value == nil { + child.value = value + } + return child.value + } + + return child.traverse(value, tail) +} + +// read returns the object if it exists or nil if not. +func (n *CommunityNode) read(tail []int) api.Community { + id := tail[0] + tail = tail[1:] + + // Seek for identifier in children + child, ok := n.children[id] + if !ok { + return nil + } + + // Set obj if required + if len(tail) == 0 { + return child.value + } + + return child.read(tail) +} + +// CommunityListNode is a node with an integer as key and +// a list of integers as value. +type CommunityListNode struct { + children map[unsafe.Pointer]*CommunityListNode + value []api.Community +} + +// NewCommunityListNode creates a new int tree node +func NewCommunityListNode(value []api.Community) *CommunityListNode { + return &CommunityListNode{ + children: map[unsafe.Pointer]*CommunityListNode{}, + value: value, + } +} + +// CommunityList traverse inserts a new node into the three if required +// or returns the object if it already exists. +func (n *CommunityListNode) traverse( + value []api.Community, + tail []unsafe.Pointer, +) []api.Community { + id := tail[0] + tail = tail[1:] + + // Seek for identifier in children + child, ok := n.children[id] + if !ok { + child = NewCommunityListNode(nil) + n.children[id] = child + } + + // Set obj if required + if len(tail) == 0 { + if child.value == nil { + child.value = value + } + return child.value + } + + return child.traverse(value, tail) +} + +// read returns the object if it exists or nil if not. +func (n *CommunityListNode) read(tail []unsafe.Pointer) []api.Community { + id := tail[0] + tail = tail[1:] + + // Seek for identifier in children + child, ok := n.children[id] + if !ok { + return nil + } + + // Set obj if required + if len(tail) == 0 { + return child.value + } + + return child.read(tail) +} diff --git a/pkg/pools/pools.go b/pkg/pools/pools.go index 328c6df..c44722f 100644 --- a/pkg/pools/pools.go +++ b/pkg/pools/pools.go @@ -47,7 +47,7 @@ var ExtCommunities *CommunitiesPool var CommunitiesSets *CommunitiesSetPool // ExtCommunitiesSets stores a list of extended communities -var ExtCommunitiesSets *CommunitiesSetPool +var ExtCommunitiesSets *ExtCommunitiesSetPool // LargeCommunitiesSets store a list of large BGP communities var LargeCommunitiesSets *CommunitiesSetPool diff --git a/pkg/sources/birdwatcher/parsers.go b/pkg/sources/birdwatcher/parsers.go index 2a5fece..e681834 100644 --- a/pkg/sources/birdwatcher/parsers.go +++ b/pkg/sources/birdwatcher/parsers.go @@ -259,7 +259,7 @@ func parseRouteBgpInfo(data interface{}) *api.BGPInfo { LocalPref: localPref, Med: med, Communities: pools.CommunitiesSets.Acquire(communities), - ExtCommunities: pools.ExtCommunitiesSets.AcquireExt(extCommunities), + ExtCommunities: pools.ExtCommunitiesSets.Acquire(extCommunities), LargeCommunities: pools.LargeCommunitiesSets.Acquire(largeCommunities), } return bgp diff --git a/pkg/sources/gobgp/routes.go b/pkg/sources/gobgp/routes.go index 4f2aad5..7209567 100644 --- a/pkg/sources/gobgp/routes.go +++ b/pkg/sources/gobgp/routes.go @@ -159,7 +159,7 @@ func (gobgp *GoBGP) parsePathIntoRoute( route.BGP.AsPath = pools.ASPaths.Acquire(route.BGP.AsPath) route.BGP.Communities = pools.CommunitiesSets.Acquire(route.BGP.Communities) - route.BGP.ExtCommunities = pools.ExtCommunitiesSets.AcquireExt(route.BGP.ExtCommunities) + route.BGP.ExtCommunities = pools.ExtCommunitiesSets.Acquire(route.BGP.ExtCommunities) route.BGP.LargeCommunities = pools.LargeCommunitiesSets.Acquire(route.BGP.LargeCommunities) route.Metric = (route.BGP.LocalPref + route.BGP.Med) diff --git a/pkg/sources/openbgpd/decoders.go b/pkg/sources/openbgpd/decoders.go index 35403b8..e267243 100644 --- a/pkg/sources/openbgpd/decoders.go +++ b/pkg/sources/openbgpd/decoders.go @@ -184,7 +184,7 @@ func decodeRoute(details map[string]interface{}) (*api.Route, error) { AsPath: pools.ASPaths.Acquire(asPath), NextHop: pools.Gateways4.Acquire(trueNextHop), Communities: pools.CommunitiesSets.Acquire(communities), - ExtCommunities: pools.ExtCommunitiesSets.AcquireExt(extendedCommunities), + ExtCommunities: pools.ExtCommunitiesSets.Acquire(extendedCommunities), LargeCommunities: pools.LargeCommunitiesSets.Acquire(largeCommunities), LocalPref: localPref, }