feat: enhance observability with prefix aggregation metrics
CI / changes (push) Successful in 7s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 33s
CI / docker-web (push) Has been skipped
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 16s
CI / docker-go (push) Successful in 8m30s
CI / changes (push) Successful in 7s
CI / openapi (push) Has been skipped
CI / go (push) Successful in 33s
CI / docker-web (push) Has been skipped
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Successful in 16s
CI / docker-go (push) Successful in 8m30s
- Added new histograms to track prefix aggregation duration, raw count, and aggregated count during tenant rendering. - Implemented `RecordPrefixAggregation` function to record metrics for aggregation performance. - Updated `RenderTenantRevision` and `RenderTenantRevisionFromPrefixes` functions to log aggregation statistics. - Refactored `smartAggregatePrefixRows` to support both IPv4 and IPv6 aggregation, improving overall prefix handling.
This commit is contained in:
@@ -8,6 +8,15 @@ import (
|
||||
"evobgp/internal/store"
|
||||
)
|
||||
|
||||
func prefixRowSet(rows []store.PrefixRow) map[string]struct{} {
|
||||
got := make(map[string]struct{}, len(rows))
|
||||
for _, r := range rows {
|
||||
c := prefixRowCommunity(r)
|
||||
got[r.Prefix+"|"+c+"|"+r.Source] = struct{}{}
|
||||
}
|
||||
return got
|
||||
}
|
||||
|
||||
func TestRefreshModule_AggregatesAllEnabledModules(t *testing.T) {
|
||||
t.Setenv("EVOBGP_ASN_RESOLVE", "0")
|
||||
|
||||
@@ -119,3 +128,144 @@ func TestSmartAggregatePrefixRows_RespectsCommunityAndSource(t *testing.T) {
|
||||
t.Fatalf("expected 3 resulting rows, got %d: %+v", len(out), out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmartAggregatePrefixRows_PruneCoveredPrefixes(t *testing.T) {
|
||||
rows := []store.PrefixRow{
|
||||
{Prefix: "10.0.0.0/16", Source: "ip_range"},
|
||||
{Prefix: "10.0.0.0/24", Source: "ip_range"},
|
||||
{Prefix: "10.0.1.0/24", Source: "ip_range"},
|
||||
}
|
||||
out := smartAggregatePrefixRows(rows)
|
||||
got := prefixRowSet(out)
|
||||
if len(out) != 1 {
|
||||
t.Fatalf("expected 1 row after covered prune, got %d: %+v", len(out), out)
|
||||
}
|
||||
if _, ok := got["10.0.0.0/16||ip_range"]; !ok {
|
||||
t.Fatalf("expected /16 only, got: %+v", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmartAggregatePrefixRows_MergeChainFourSlash24(t *testing.T) {
|
||||
rows := []store.PrefixRow{
|
||||
{Prefix: "10.0.0.0/24", Source: "ip_range"},
|
||||
{Prefix: "10.0.1.0/24", Source: "ip_range"},
|
||||
{Prefix: "10.0.2.0/24", Source: "ip_range"},
|
||||
{Prefix: "10.0.3.0/24", Source: "ip_range"},
|
||||
}
|
||||
out := smartAggregatePrefixRows(rows)
|
||||
got := prefixRowSet(out)
|
||||
if _, ok := got["10.0.0.0/22||ip_range"]; !ok {
|
||||
t.Fatalf("expected merged /22, got: %+v", out)
|
||||
}
|
||||
if len(out) != 1 {
|
||||
t.Fatalf("expected 1 row, got %d: %+v", len(out), out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmartAggregatePrefixRows_DoesNotMergeAdjacentSlash8(t *testing.T) {
|
||||
rows := []store.PrefixRow{
|
||||
{Prefix: "10.0.0.0/8", Source: "ip_range"},
|
||||
{Prefix: "11.0.0.0/8", Source: "ip_range"},
|
||||
}
|
||||
out := smartAggregatePrefixRows(rows)
|
||||
if len(out) != 2 {
|
||||
t.Fatalf("expected two /8 prefixes, got %d: %+v", len(out), out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmartAggregatePrefixRows_MergesIPv6Slash64(t *testing.T) {
|
||||
rows := []store.PrefixRow{
|
||||
{Prefix: "2001:db8:0:0::/64", Source: "ip_range"},
|
||||
{Prefix: "2001:db8:0:1::/64", Source: "ip_range"},
|
||||
}
|
||||
out := smartAggregatePrefixRows(rows)
|
||||
got := prefixRowSet(out)
|
||||
if _, ok := got["2001:db8::/63||ip_range"]; !ok {
|
||||
t.Fatalf("expected merged IPv6 /63, got: %+v", out)
|
||||
}
|
||||
if len(out) != 1 {
|
||||
t.Fatalf("expected 1 row, got %d: %+v", len(out), out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmartAggregatePrefixRows_StableOutputOrder(t *testing.T) {
|
||||
rows := []store.PrefixRow{
|
||||
{Prefix: "192.168.0.0/24", Source: "cdn:b"},
|
||||
{Prefix: "10.0.0.0/24", Source: "ip_range"},
|
||||
{Prefix: "10.0.1.0/24", Source: "ip_range"},
|
||||
}
|
||||
out1 := smartAggregatePrefixRows(rows)
|
||||
out2 := smartAggregatePrefixRows(rows)
|
||||
if len(out1) != len(out2) {
|
||||
t.Fatalf("length mismatch: %d vs %d", len(out1), len(out2))
|
||||
}
|
||||
for i := range out1 {
|
||||
if out1[i].Prefix != out2[i].Prefix || out1[i].Source != out2[i].Source {
|
||||
t.Fatalf("order not stable at %d: %+v vs %+v", i, out1[i], out2[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderTenantRevisionFromPrefixes_SkipsWhenRedundantRawRows(t *testing.T) {
|
||||
t.Setenv("EVOBGP_ASN_RESOLVE", "0")
|
||||
|
||||
m := store.NewMemory()
|
||||
m.SeedDemo()
|
||||
tenant, _, modIP, _, _ := m.DemoIDs()
|
||||
for _, mod := range m.ListModules(tenant) {
|
||||
if mod == nil || mod.ID == modIP {
|
||||
continue
|
||||
}
|
||||
disabled := false
|
||||
if _, err := m.UpdateModule(tenant, mod.ID, &store.ModulePatch{Enabled: &disabled}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
baseRows := []store.PrefixRow{{Prefix: "10.0.0.0/16", Source: "ip_range"}}
|
||||
rev1, err := RenderTenantRevisionFromPrefixes(ctx, m, http.DefaultClient, tenant, modIP, baseRows)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rev2, err := RenderTenantRevisionFromPrefixes(ctx, m, http.DefaultClient, tenant, modIP, append(baseRows,
|
||||
store.PrefixRow{Prefix: "10.0.0.0/24", Source: "ip_range"},
|
||||
))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rev2 != rev1 {
|
||||
t.Fatalf("expected hash skip for redundant covered prefix, got rev1=%s rev2=%s", rev1, rev2)
|
||||
}
|
||||
|
||||
before, _, _ := m.ListRevisions(tenant, "", "", 200)
|
||||
rev3, err := RenderTenantRevisionFromPrefixes(ctx, m, http.DefaultClient, tenant, modIP, append(baseRows,
|
||||
store.PrefixRow{Prefix: "10.0.0.0/24", Source: "ip_range"},
|
||||
store.PrefixRow{Prefix: "10.0.0.0/24", Source: "ip_range"},
|
||||
))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rev3 != rev1 {
|
||||
t.Fatalf("expected hash skip for duplicate raw rows, got rev1=%s rev3=%s", rev1, rev3)
|
||||
}
|
||||
after, _, _ := m.ListRevisions(tenant, "", "", 200)
|
||||
if len(after) != len(before) {
|
||||
t.Fatalf("duplicate raw rows should not create revision, before=%d after=%d", len(before), len(after))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashAggregatedMaterialization_DedupesIdenticalRows(t *testing.T) {
|
||||
rows := []store.PrefixRow{
|
||||
{Prefix: "10.0.0.0/24", Source: "ip_range"},
|
||||
{Prefix: "10.0.0.0/24", Source: "ip_range"},
|
||||
}
|
||||
h1 := hashAggregatedMaterialization("tenant-a", rows)
|
||||
h2 := hashAggregatedMaterialization("tenant-a", []store.PrefixRow{
|
||||
{Prefix: "10.0.0.0/24", Source: "ip_range"},
|
||||
})
|
||||
if h1 != h2 {
|
||||
t.Fatalf("expected deduped hash to match, got %s vs %s", h1, h2)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user