diff --git a/common/helpers/bimap.go b/common/helpers/bimap/bimap.go similarity index 87% rename from common/helpers/bimap.go rename to common/helpers/bimap/bimap.go index 75af77f1..39635da6 100644 --- a/common/helpers/bimap.go +++ b/common/helpers/bimap/bimap.go @@ -1,7 +1,8 @@ // SPDX-FileCopyrightText: 2022 Free Mobile // SPDX-License-Identifier: AGPL-3.0-only -package helpers +// Package bimap exposes a bidirectional map structure. +package bimap import "fmt" @@ -11,8 +12,8 @@ type Bimap[K, V comparable] struct { inverse map[V]K } -// NewBimap returns a new bimap from an existing map. -func NewBimap[K, V comparable](input map[K]V) *Bimap[K, V] { +// New returns a new bimap from an existing map. +func New[K, V comparable](input map[K]V) *Bimap[K, V] { output := &Bimap[K, V]{ forward: make(map[K]V), inverse: make(map[V]K), diff --git a/common/helpers/bimap_test.go b/common/helpers/bimap/bimap_test.go similarity index 89% rename from common/helpers/bimap_test.go rename to common/helpers/bimap/bimap_test.go index c48fd548..2e95d457 100644 --- a/common/helpers/bimap_test.go +++ b/common/helpers/bimap/bimap_test.go @@ -1,17 +1,18 @@ // SPDX-FileCopyrightText: 2022 Free Mobile // SPDX-License-Identifier: AGPL-3.0-only -package helpers_test +package bimap_test import ( "sort" "testing" "akvorado/common/helpers" + "akvorado/common/helpers/bimap" ) func TestBimapLoadValue(t *testing.T) { - input := helpers.NewBimap(map[int]string{ + input := bimap.New(map[int]string{ 1: "hello", 2: "world", 3: "happy", @@ -38,7 +39,7 @@ func TestBimapLoadValue(t *testing.T) { } func TestBimapLoadKey(t *testing.T) { - input := helpers.NewBimap(map[int]string{ + input := bimap.New(map[int]string{ 1: "hello", 2: "world", 3: "happy", @@ -65,7 +66,7 @@ func TestBimapLoadKey(t *testing.T) { } func TestBimapKeys(t *testing.T) { - input := helpers.NewBimap(map[int]string{ + input := bimap.New(map[int]string{ 1: "hello", 2: "world", 3: "happy", @@ -80,7 +81,7 @@ func TestBimapKeys(t *testing.T) { } func TestBimapValues(t *testing.T) { - input := helpers.NewBimap(map[int]string{ + input := bimap.New(map[int]string{ 1: "hello", 2: "world", 3: "happy", diff --git a/common/helpers/intern.go b/common/helpers/intern/intern.go similarity index 71% rename from common/helpers/intern.go rename to common/helpers/intern/intern.go index ed5df770..f04be43b 100644 --- a/common/helpers/intern.go +++ b/common/helpers/intern/intern.go @@ -1,55 +1,57 @@ // SPDX-FileCopyrightText: 2022 Free Mobile // SPDX-License-Identifier: AGPL-3.0-only -package helpers +// Package intern manages a pool of interned values. An interned value is +// replaced by a small int. +package intern -// InternValue is the interface that should be implemented by types +// Value is the interface that should be implemented by types // used in an intern pool. Also, it should be immutable. -type InternValue[T any] interface { +type Value[T any] interface { Hash() uint64 Equal(T) bool } -// InternReference is a reference to an interned value. 0 is not a +// Reference is a reference to an interned value. 0 is not a // valid reference value. -type InternReference[T any] uint32 +type Reference[T any] uint32 -// InternPool keeps values in a pool by storing only one distinct copy +// Pool keeps values in a pool by storing only one distinct copy // of each. Values will be referred as an uint32 (implemented as an // index). -type InternPool[T InternValue[T]] struct { +type Pool[T Value[T]] struct { values []internValue[T] - availableIndexes []InternReference[T] - valueIndexes map[uint64]InternReference[T] + availableIndexes []Reference[T] + valueIndexes map[uint64]Reference[T] } // internValue is the value stored in an intern pool. It adds resource // keeping to the raw value. -type internValue[T InternValue[T]] struct { - next InternReference[T] // next value with the same hash - previous InternReference[T] // previous value with the same hash +type internValue[T Value[T]] struct { + next Reference[T] // next value with the same hash + previous Reference[T] // previous value with the same hash refCount uint32 value T } -// NewInternPool creates a new intern pool. -func NewInternPool[T InternValue[T]]() *InternPool[T] { - return &InternPool[T]{ +// NewPool creates a new intern pool. +func NewPool[T Value[T]]() *Pool[T] { + return &Pool[T]{ values: make([]internValue[T], 1), // first slot is reserved - availableIndexes: make([]InternReference[T], 0), - valueIndexes: make(map[uint64]InternReference[T]), + availableIndexes: make([]Reference[T], 0), + valueIndexes: make(map[uint64]Reference[T]), } } // Get retrieves a (copy of the) value from the intern pool using its reference. -func (p *InternPool[T]) Get(ref InternReference[T]) T { +func (p *Pool[T]) Get(ref Reference[T]) T { return p.values[ref].value } // Take removes a value from the intern pool. If this is the last // used reference, it will be deleted from the pool. -func (p *InternPool[T]) Take(ref InternReference[T]) { +func (p *Pool[T]) Take(ref Reference[T]) { value := &p.values[ref] value.refCount-- if value.refCount == 0 { @@ -73,7 +75,7 @@ func (p *InternPool[T]) Take(ref InternReference[T]) { } // Ref returns the reference an interned value would have. -func (p *InternPool[T]) Ref(value T) (InternReference[T], bool) { +func (p *Pool[T]) Ref(value T) (Reference[T], bool) { hash := value.Hash() if index := p.valueIndexes[hash]; index > 0 { for index > 0 { @@ -87,7 +89,7 @@ func (p *InternPool[T]) Ref(value T) (InternReference[T], bool) { } // Put adds a value to the intern pool, returning its reference. -func (p *InternPool[T]) Put(value T) InternReference[T] { +func (p *Pool[T]) Put(value T) Reference[T] { v := internValue[T]{ value: value, refCount: 1, @@ -96,7 +98,7 @@ func (p *InternPool[T]) Put(value T) InternReference[T] { } // Allocate a new index - newIndex := func() InternReference[T] { + newIndex := func() Reference[T] { availCount := len(p.availableIndexes) if availCount > 0 { index := p.availableIndexes[availCount-1] @@ -111,7 +113,7 @@ func (p *InternPool[T]) Put(value T) InternReference[T] { } index := len(p.values) p.values = p.values[:index+1] - return InternReference[T](index) + return Reference[T](index) } // Check if we have already something @@ -143,6 +145,6 @@ func (p *InternPool[T]) Put(value T) InternReference[T] { } // Len returns the number of elements in the pool. -func (p *InternPool[T]) Len() int { +func (p *Pool[T]) Len() int { return len(p.values) - len(p.availableIndexes) - 1 } diff --git a/common/helpers/intern_test.go b/common/helpers/intern/intern_test.go similarity index 83% rename from common/helpers/intern_test.go rename to common/helpers/intern/intern_test.go index 8ff556e3..8a4f174b 100644 --- a/common/helpers/intern_test.go +++ b/common/helpers/intern/intern_test.go @@ -1,9 +1,13 @@ // SPDX-FileCopyrightText: 2022 Free Mobile // SPDX-License-Identifier: AGPL-3.0-only -package helpers +package intern -import "testing" +import ( + "testing" + + "akvorado/common/helpers" +) type likeInt int @@ -11,7 +15,7 @@ func (i likeInt) Equal(j likeInt) bool { return i == j } func (i likeInt) Hash() uint64 { return uint64(i) % 10 } func TestPut(t *testing.T) { - p := NewInternPool[likeInt]() + p := NewPool[likeInt]() a := p.Put(likeInt(10)) b := p.Put(likeInt(10)) @@ -36,7 +40,7 @@ func TestPut(t *testing.T) { } func TestRef(t *testing.T) { - p := NewInternPool[likeInt]() + p := NewPool[likeInt]() a := p.Put(likeInt(10)) b, bOK := p.Ref(likeInt(10)) c, cOK := p.Ref(likeInt(20)) @@ -55,7 +59,7 @@ func TestRef(t *testing.T) { } func TestPutCollision(t *testing.T) { - p := NewInternPool[likeInt]() + p := NewPool[likeInt]() a := p.Put(likeInt(10)) b := p.Put(likeInt(20)) @@ -67,7 +71,7 @@ func TestPutCollision(t *testing.T) { } func TestTake(t *testing.T) { - p := NewInternPool[likeInt]() + p := NewPool[likeInt]() val1 := likeInt(10) ref1 := p.Put(val1) @@ -87,7 +91,7 @@ func TestTake(t *testing.T) { {value: 22, refCount: 1, previous: 2, next: 4}, {value: 32, refCount: 1, previous: 3}, } - if diff := Diff(p.values, expectedValues, DiffUnexported); diff != "" { + if diff := helpers.Diff(p.values, expectedValues, helpers.DiffUnexported); diff != "" { t.Fatalf("p.values (-got, +want):\n%s", diff) } @@ -100,7 +104,7 @@ func TestTake(t *testing.T) { {value: 22, refCount: 0, previous: 2, next: 4}, // free {value: 32, refCount: 1, previous: 2}, } - if diff := Diff(p.values, expectedValues, DiffUnexported); diff != "" { + if diff := helpers.Diff(p.values, expectedValues, helpers.DiffUnexported); diff != "" { t.Fatalf("p.values (-got, +want):\n%s", diff) } @@ -116,7 +120,7 @@ func TestTake(t *testing.T) { {value: 42, refCount: 1, previous: 4}, {value: 32, refCount: 1, previous: 2, next: 3}, } - if diff := Diff(p.values, expectedValues, DiffUnexported); diff != "" { + if diff := helpers.Diff(p.values, expectedValues, helpers.DiffUnexported); diff != "" { t.Fatalf("p.values (-got, +want):\n%s", diff) } @@ -129,7 +133,7 @@ func TestTake(t *testing.T) { {value: 42, refCount: 1, previous: 4}, {value: 32, refCount: 1, next: 3}, } - if diff := Diff(p.values, expectedValues, DiffUnexported); diff != "" { + if diff := helpers.Diff(p.values, expectedValues, helpers.DiffUnexported); diff != "" { t.Fatalf("p.values (-got, +want):\n%s", diff) } @@ -142,7 +146,7 @@ func TestTake(t *testing.T) { {value: 42, refCount: 1}, {value: 32, refCount: 0, next: 3}, // free } - if diff := Diff(p.values, expectedValues, DiffUnexported); diff != "" { + if diff := helpers.Diff(p.values, expectedValues, helpers.DiffUnexported); diff != "" { t.Fatalf("p.values (-got, +want):\n%s", diff) } @@ -155,7 +159,7 @@ func TestTake(t *testing.T) { {value: 42, refCount: 0}, // free {value: 32, refCount: 0, next: 3}, // free } - if diff := Diff(p.values, expectedValues, DiffUnexported); diff != "" { + if diff := helpers.Diff(p.values, expectedValues, helpers.DiffUnexported); diff != "" { t.Fatalf("p.values (-got, +want):\n%s", diff) } diff --git a/common/kafka/config.go b/common/kafka/config.go index 90257d4b..f51d132b 100644 --- a/common/kafka/config.go +++ b/common/kafka/config.go @@ -14,9 +14,9 @@ import ( "fmt" "os" - "github.com/Shopify/sarama" + "akvorado/common/helpers/bimap" - "akvorado/common/helpers" + "github.com/Shopify/sarama" ) // Configuration defines how we connect to a Kafka cluster. @@ -98,7 +98,7 @@ const ( SASLSCRAMSHA512 // SASLSCRAMSHA512 enables SCRAM challenge with SHA512 ) -var saslAlgorithmMap = helpers.NewBimap(map[SASLMechanism]string{ +var saslAlgorithmMap = bimap.New(map[SASLMechanism]string{ SASLNone: "none", SASLPlainText: "plain", SASLSCRAMSHA256: "scram-sha256", diff --git a/console/query_consts.go b/console/query_consts.go index 1bd3eb8f..662a80cd 100644 --- a/console/query_consts.go +++ b/console/query_consts.go @@ -3,7 +3,7 @@ package console -import "akvorado/common/helpers" +import "akvorado/common/helpers/bimap" const ( queryColumnExporterAddress queryColumn = iota + 1 @@ -56,7 +56,7 @@ const ( queryColumnPacketSizeBucket ) -var queryColumnMap = helpers.NewBimap(map[queryColumn]string{ +var queryColumnMap = bimap.New(map[queryColumn]string{ queryColumnExporterAddress: "ExporterAddress", queryColumnExporterName: "ExporterName", queryColumnExporterGroup: "ExporterGroup", diff --git a/inlet/bmp/rib.go b/inlet/bmp/rib.go index 4dec57ae..a12207f6 100644 --- a/inlet/bmp/rib.go +++ b/inlet/bmp/rib.go @@ -10,7 +10,7 @@ import ( "sync/atomic" "unsafe" - "akvorado/common/helpers" + "akvorado/common/helpers/intern" "github.com/kentik/patricia" tree "github.com/kentik/patricia/generics_tree" @@ -20,9 +20,9 @@ import ( // rib represents the RIB. type rib struct { tree *tree.TreeV6[route] - nlris *helpers.InternPool[nlri] - nextHops *helpers.InternPool[nextHop] - rtas *helpers.InternPool[routeAttributes] + nlris *intern.Pool[nlri] + nextHops *intern.Pool[nextHop] + rtas *intern.Pool[routeAttributes] } // route contains the peer (external opaque value), the NLRI, the next @@ -30,9 +30,9 @@ type rib struct { // and nlri. type route struct { peer uint32 - nlri helpers.InternReference[nlri] - nextHop helpers.InternReference[nextHop] - attributes helpers.InternReference[routeAttributes] + nlri intern.Reference[nlri] + nextHop intern.Reference[nextHop] + attributes intern.Reference[routeAttributes] } // nlri is the NLRI for the route (when combined with prefix). The @@ -211,8 +211,8 @@ func (r *rib) flushPeer(ctx context.Context, peer uint32, min int) (int, bool) { func newRIB() *rib { return &rib{ tree: tree.NewTreeV6[route](), - nlris: helpers.NewInternPool[nlri](), - nextHops: helpers.NewInternPool[nextHop](), - rtas: helpers.NewInternPool[routeAttributes](), + nlris: intern.NewPool[nlri](), + nextHops: intern.NewPool[nextHop](), + rtas: intern.NewPool[routeAttributes](), } } diff --git a/inlet/core/config.go b/inlet/core/config.go index 6758a7bf..5d75c377 100644 --- a/inlet/core/config.go +++ b/inlet/core/config.go @@ -10,6 +10,7 @@ import ( "time" "akvorado/common/helpers" + "akvorado/common/helpers/bimap" "github.com/mitchellh/mapstructure" ) @@ -62,7 +63,7 @@ const ( ProviderBMPExceptPrivate ) -var asnProviderMap = helpers.NewBimap(map[ASNProvider]string{ +var asnProviderMap = bimap.New(map[ASNProvider]string{ ProviderFlow: "flow", ProviderFlowExceptPrivate: "flow-except-private", ProviderGeoIP: "geoip",