common/helpers: move bimap and intern to separate packages

subnetmap would be a candidate too, but there are cyclic dependencies
because we want diff to handle it correctly.
This commit is contained in:
Vincent Bernat
2022-11-28 14:44:55 +01:00
parent 7da8b92d95
commit 24cfabb682
8 changed files with 69 additions and 60 deletions

View File

@@ -1,7 +1,8 @@
// SPDX-FileCopyrightText: 2022 Free Mobile // SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
package helpers // Package bimap exposes a bidirectional map structure.
package bimap
import "fmt" import "fmt"
@@ -11,8 +12,8 @@ type Bimap[K, V comparable] struct {
inverse map[V]K inverse map[V]K
} }
// NewBimap returns a new bimap from an existing map. // New returns a new bimap from an existing map.
func NewBimap[K, V comparable](input map[K]V) *Bimap[K, V] { func New[K, V comparable](input map[K]V) *Bimap[K, V] {
output := &Bimap[K, V]{ output := &Bimap[K, V]{
forward: make(map[K]V), forward: make(map[K]V),
inverse: make(map[V]K), inverse: make(map[V]K),

View File

@@ -1,17 +1,18 @@
// SPDX-FileCopyrightText: 2022 Free Mobile // SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
package helpers_test package bimap_test
import ( import (
"sort" "sort"
"testing" "testing"
"akvorado/common/helpers" "akvorado/common/helpers"
"akvorado/common/helpers/bimap"
) )
func TestBimapLoadValue(t *testing.T) { func TestBimapLoadValue(t *testing.T) {
input := helpers.NewBimap(map[int]string{ input := bimap.New(map[int]string{
1: "hello", 1: "hello",
2: "world", 2: "world",
3: "happy", 3: "happy",
@@ -38,7 +39,7 @@ func TestBimapLoadValue(t *testing.T) {
} }
func TestBimapLoadKey(t *testing.T) { func TestBimapLoadKey(t *testing.T) {
input := helpers.NewBimap(map[int]string{ input := bimap.New(map[int]string{
1: "hello", 1: "hello",
2: "world", 2: "world",
3: "happy", 3: "happy",
@@ -65,7 +66,7 @@ func TestBimapLoadKey(t *testing.T) {
} }
func TestBimapKeys(t *testing.T) { func TestBimapKeys(t *testing.T) {
input := helpers.NewBimap(map[int]string{ input := bimap.New(map[int]string{
1: "hello", 1: "hello",
2: "world", 2: "world",
3: "happy", 3: "happy",
@@ -80,7 +81,7 @@ func TestBimapKeys(t *testing.T) {
} }
func TestBimapValues(t *testing.T) { func TestBimapValues(t *testing.T) {
input := helpers.NewBimap(map[int]string{ input := bimap.New(map[int]string{
1: "hello", 1: "hello",
2: "world", 2: "world",
3: "happy", 3: "happy",

View File

@@ -1,55 +1,57 @@
// SPDX-FileCopyrightText: 2022 Free Mobile // SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only // 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. // used in an intern pool. Also, it should be immutable.
type InternValue[T any] interface { type Value[T any] interface {
Hash() uint64 Hash() uint64
Equal(T) bool 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. // 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 // of each. Values will be referred as an uint32 (implemented as an
// index). // index).
type InternPool[T InternValue[T]] struct { type Pool[T Value[T]] struct {
values []internValue[T] values []internValue[T]
availableIndexes []InternReference[T] availableIndexes []Reference[T]
valueIndexes map[uint64]InternReference[T] valueIndexes map[uint64]Reference[T]
} }
// internValue is the value stored in an intern pool. It adds resource // internValue is the value stored in an intern pool. It adds resource
// keeping to the raw value. // keeping to the raw value.
type internValue[T InternValue[T]] struct { type internValue[T Value[T]] struct {
next InternReference[T] // next value with the same hash next Reference[T] // next value with the same hash
previous InternReference[T] // previous value with the same hash previous Reference[T] // previous value with the same hash
refCount uint32 refCount uint32
value T value T
} }
// NewInternPool creates a new intern pool. // NewPool creates a new intern pool.
func NewInternPool[T InternValue[T]]() *InternPool[T] { func NewPool[T Value[T]]() *Pool[T] {
return &InternPool[T]{ return &Pool[T]{
values: make([]internValue[T], 1), // first slot is reserved values: make([]internValue[T], 1), // first slot is reserved
availableIndexes: make([]InternReference[T], 0), availableIndexes: make([]Reference[T], 0),
valueIndexes: make(map[uint64]InternReference[T]), valueIndexes: make(map[uint64]Reference[T]),
} }
} }
// Get retrieves a (copy of the) value from the intern pool using its reference. // 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 return p.values[ref].value
} }
// Take removes a value from the intern pool. If this is the last // Take removes a value from the intern pool. If this is the last
// used reference, it will be deleted from the pool. // 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 := &p.values[ref]
value.refCount-- value.refCount--
if value.refCount == 0 { 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. // 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() hash := value.Hash()
if index := p.valueIndexes[hash]; index > 0 { if index := p.valueIndexes[hash]; index > 0 {
for 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. // 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]{ v := internValue[T]{
value: value, value: value,
refCount: 1, refCount: 1,
@@ -96,7 +98,7 @@ func (p *InternPool[T]) Put(value T) InternReference[T] {
} }
// Allocate a new index // Allocate a new index
newIndex := func() InternReference[T] { newIndex := func() Reference[T] {
availCount := len(p.availableIndexes) availCount := len(p.availableIndexes)
if availCount > 0 { if availCount > 0 {
index := p.availableIndexes[availCount-1] index := p.availableIndexes[availCount-1]
@@ -111,7 +113,7 @@ func (p *InternPool[T]) Put(value T) InternReference[T] {
} }
index := len(p.values) index := len(p.values)
p.values = p.values[:index+1] p.values = p.values[:index+1]
return InternReference[T](index) return Reference[T](index)
} }
// Check if we have already something // 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. // 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 return len(p.values) - len(p.availableIndexes) - 1
} }

View File

@@ -1,9 +1,13 @@
// SPDX-FileCopyrightText: 2022 Free Mobile // SPDX-FileCopyrightText: 2022 Free Mobile
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
package helpers package intern
import "testing" import (
"testing"
"akvorado/common/helpers"
)
type likeInt int 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 (i likeInt) Hash() uint64 { return uint64(i) % 10 }
func TestPut(t *testing.T) { func TestPut(t *testing.T) {
p := NewInternPool[likeInt]() p := NewPool[likeInt]()
a := p.Put(likeInt(10)) a := p.Put(likeInt(10))
b := p.Put(likeInt(10)) b := p.Put(likeInt(10))
@@ -36,7 +40,7 @@ func TestPut(t *testing.T) {
} }
func TestRef(t *testing.T) { func TestRef(t *testing.T) {
p := NewInternPool[likeInt]() p := NewPool[likeInt]()
a := p.Put(likeInt(10)) a := p.Put(likeInt(10))
b, bOK := p.Ref(likeInt(10)) b, bOK := p.Ref(likeInt(10))
c, cOK := p.Ref(likeInt(20)) c, cOK := p.Ref(likeInt(20))
@@ -55,7 +59,7 @@ func TestRef(t *testing.T) {
} }
func TestPutCollision(t *testing.T) { func TestPutCollision(t *testing.T) {
p := NewInternPool[likeInt]() p := NewPool[likeInt]()
a := p.Put(likeInt(10)) a := p.Put(likeInt(10))
b := p.Put(likeInt(20)) b := p.Put(likeInt(20))
@@ -67,7 +71,7 @@ func TestPutCollision(t *testing.T) {
} }
func TestTake(t *testing.T) { func TestTake(t *testing.T) {
p := NewInternPool[likeInt]() p := NewPool[likeInt]()
val1 := likeInt(10) val1 := likeInt(10)
ref1 := p.Put(val1) ref1 := p.Put(val1)
@@ -87,7 +91,7 @@ func TestTake(t *testing.T) {
{value: 22, refCount: 1, previous: 2, next: 4}, {value: 22, refCount: 1, previous: 2, next: 4},
{value: 32, refCount: 1, previous: 3}, {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) 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: 22, refCount: 0, previous: 2, next: 4}, // free
{value: 32, refCount: 1, previous: 2}, {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) 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: 42, refCount: 1, previous: 4},
{value: 32, refCount: 1, previous: 2, next: 3}, {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) 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: 42, refCount: 1, previous: 4},
{value: 32, refCount: 1, next: 3}, {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) t.Fatalf("p.values (-got, +want):\n%s", diff)
} }
@@ -142,7 +146,7 @@ func TestTake(t *testing.T) {
{value: 42, refCount: 1}, {value: 42, refCount: 1},
{value: 32, refCount: 0, next: 3}, // 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) t.Fatalf("p.values (-got, +want):\n%s", diff)
} }
@@ -155,7 +159,7 @@ func TestTake(t *testing.T) {
{value: 42, refCount: 0}, // free {value: 42, refCount: 0}, // free
{value: 32, refCount: 0, next: 3}, // 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) t.Fatalf("p.values (-got, +want):\n%s", diff)
} }

View File

@@ -14,9 +14,9 @@ import (
"fmt" "fmt"
"os" "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. // Configuration defines how we connect to a Kafka cluster.
@@ -98,7 +98,7 @@ const (
SASLSCRAMSHA512 // SASLSCRAMSHA512 enables SCRAM challenge with SHA512 SASLSCRAMSHA512 // SASLSCRAMSHA512 enables SCRAM challenge with SHA512
) )
var saslAlgorithmMap = helpers.NewBimap(map[SASLMechanism]string{ var saslAlgorithmMap = bimap.New(map[SASLMechanism]string{
SASLNone: "none", SASLNone: "none",
SASLPlainText: "plain", SASLPlainText: "plain",
SASLSCRAMSHA256: "scram-sha256", SASLSCRAMSHA256: "scram-sha256",

View File

@@ -3,7 +3,7 @@
package console package console
import "akvorado/common/helpers" import "akvorado/common/helpers/bimap"
const ( const (
queryColumnExporterAddress queryColumn = iota + 1 queryColumnExporterAddress queryColumn = iota + 1
@@ -56,7 +56,7 @@ const (
queryColumnPacketSizeBucket queryColumnPacketSizeBucket
) )
var queryColumnMap = helpers.NewBimap(map[queryColumn]string{ var queryColumnMap = bimap.New(map[queryColumn]string{
queryColumnExporterAddress: "ExporterAddress", queryColumnExporterAddress: "ExporterAddress",
queryColumnExporterName: "ExporterName", queryColumnExporterName: "ExporterName",
queryColumnExporterGroup: "ExporterGroup", queryColumnExporterGroup: "ExporterGroup",

View File

@@ -10,7 +10,7 @@ import (
"sync/atomic" "sync/atomic"
"unsafe" "unsafe"
"akvorado/common/helpers" "akvorado/common/helpers/intern"
"github.com/kentik/patricia" "github.com/kentik/patricia"
tree "github.com/kentik/patricia/generics_tree" tree "github.com/kentik/patricia/generics_tree"
@@ -20,9 +20,9 @@ import (
// rib represents the RIB. // rib represents the RIB.
type rib struct { type rib struct {
tree *tree.TreeV6[route] tree *tree.TreeV6[route]
nlris *helpers.InternPool[nlri] nlris *intern.Pool[nlri]
nextHops *helpers.InternPool[nextHop] nextHops *intern.Pool[nextHop]
rtas *helpers.InternPool[routeAttributes] rtas *intern.Pool[routeAttributes]
} }
// route contains the peer (external opaque value), the NLRI, the next // route contains the peer (external opaque value), the NLRI, the next
@@ -30,9 +30,9 @@ type rib struct {
// and nlri. // and nlri.
type route struct { type route struct {
peer uint32 peer uint32
nlri helpers.InternReference[nlri] nlri intern.Reference[nlri]
nextHop helpers.InternReference[nextHop] nextHop intern.Reference[nextHop]
attributes helpers.InternReference[routeAttributes] attributes intern.Reference[routeAttributes]
} }
// nlri is the NLRI for the route (when combined with prefix). The // 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 { func newRIB() *rib {
return &rib{ return &rib{
tree: tree.NewTreeV6[route](), tree: tree.NewTreeV6[route](),
nlris: helpers.NewInternPool[nlri](), nlris: intern.NewPool[nlri](),
nextHops: helpers.NewInternPool[nextHop](), nextHops: intern.NewPool[nextHop](),
rtas: helpers.NewInternPool[routeAttributes](), rtas: intern.NewPool[routeAttributes](),
} }
} }

View File

@@ -10,6 +10,7 @@ import (
"time" "time"
"akvorado/common/helpers" "akvorado/common/helpers"
"akvorado/common/helpers/bimap"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
) )
@@ -62,7 +63,7 @@ const (
ProviderBMPExceptPrivate ProviderBMPExceptPrivate
) )
var asnProviderMap = helpers.NewBimap(map[ASNProvider]string{ var asnProviderMap = bimap.New(map[ASNProvider]string{
ProviderFlow: "flow", ProviderFlow: "flow",
ProviderFlowExceptPrivate: "flow-except-private", ProviderFlowExceptPrivate: "flow-except-private",
ProviderGeoIP: "geoip", ProviderGeoIP: "geoip",