From 3cf91fb9eb5e6aa51e63edcd237ee266373aec79 Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Tue, 16 May 2023 13:25:48 +0200 Subject: [PATCH] Nest: Add tests and benchmark for FIB Basic fib_get() / fib_find() test for random prefixes, FIB_WALK() test, and benchmark for fib_find(). Also generalize and reuse some code from trie tests. --- filter/trie_test.c | 33 +----- nest/Makefile | 2 +- nest/rt-fib_test.c | 246 +++++++++++++++++++++++++++++++++++++++++++++ test/birdtest.h | 4 + test/bt-utils.c | 132 ++++++++++++++++++++++++ test/bt-utils.h | 6 ++ 6 files changed, 394 insertions(+), 29 deletions(-) create mode 100644 nest/rt-fib_test.c diff --git a/filter/trie_test.c b/filter/trie_test.c index dc791280..5724e49f 100644 --- a/filter/trie_test.c +++ b/filter/trie_test.c @@ -28,12 +28,6 @@ struct f_prefix_node { struct f_prefix prefix; }; -static u32 -xrandom(u32 max) -{ - return (bt_random() % max); -} - static inline uint get_exp_random(void) { @@ -95,27 +89,10 @@ is_prefix_included(list *prefixes, const net_addr *needle) return 0; /* FAIL */ } -static void -get_random_net(net_addr *net, int v6) -{ - if (!v6) - { - uint pxlen = xrandom(24)+8; - ip4_addr ip4 = ip4_from_u32((u32) bt_random()); - net_fill_ip4(net, ip4_and(ip4, ip4_mkmask(pxlen)), pxlen); - } - else - { - uint pxlen = xrandom(120)+8; - ip6_addr ip6 = ip6_build(bt_random(), bt_random(), bt_random(), bt_random()); - net_fill_ip6(net, ip6_and(ip6, ip6_mkmask(pxlen)), pxlen); - } -} - static void get_random_prefix(struct f_prefix *px, int v6, int tight) { - get_random_net(&px->net, v6); + bt_random_net(&px->net, !v6 ? NET_IP4 : NET_IP6); if (tight) { @@ -379,7 +356,7 @@ select_random_prefix_subset(list *src[], net_addr dst[], int sn, int dn) struct f_prefix_node *px; WALK_LIST(px, *src[i]) { - if (xrandom(rnd) != 0) + if (bt_random_n(rnd) != 0) continue; net_copy(&dst[n], &px->prefix.net); @@ -395,7 +372,7 @@ done: /* Shuffle networks */ for (int i = 0; i < dn; i++) { - int j = xrandom(dn); + int j = bt_random_n(dn); if (i == j) continue; @@ -444,7 +421,7 @@ t_match_random_net(void) for (int i = 0; i < PREFIX_TESTS_NUM; i++) { net_addr net; - get_random_net(&net, v6); + bt_random_net(&net, !v6 ? NET_IP4 : NET_IP6); test_match_net(prefixes, trie, &net); } @@ -828,7 +805,7 @@ t_trie_walk_to_root(void) for (i = 0; i < (PREFIX_TESTS_NUM / 10); i++) { net_addr from; - get_random_net(&from, v6); + bt_random_net(&from, !v6 ? NET_IP4 : NET_IP6); net_addr found[129]; int found_num = find_covering_nets(pxset, num, &from, found); diff --git a/nest/Makefile b/nest/Makefile index 163a1199..5a244c75 100644 --- a/nest/Makefile +++ b/nest/Makefile @@ -9,6 +9,6 @@ $(o)proto-build.c: Makefile $(lastword $(MAKEFILE_LIST)) $(objdir)/.dir-stamp prepare: $(o)proto-build.c -tests_src := a-set_test.c a-path_test.c +tests_src := a-set_test.c a-path_test.c rt-fib_test.c tests_targets := $(tests_targets) $(tests-target-files) tests_objs := $(tests_objs) $(src-o-files) diff --git a/nest/rt-fib_test.c b/nest/rt-fib_test.c new file mode 100644 index 00000000..2dd7ce8a --- /dev/null +++ b/nest/rt-fib_test.c @@ -0,0 +1,246 @@ +/* + * BIRD -- Forwarding Information Base -- Tests + * + * (c) 2023 CZ.NIC z.s.p.o. + * + * Can be freely distributed and used under the terms of the GNU GPL. + */ + +#include "test/birdtest.h" +#include "test/bt-utils.h" + +#include "nest/route.h" + + +#define TESTS_NUM 10 +#define PREFIXES_NUM 400000 +#define PREFIX_TESTS_NUM 200000 +#define PREFIX_BENCH_MAX 1000000 +#define PREFIX_BENCH_NUM 10000000 + +struct test_node +{ + int pos; + struct fib_node n; +}; + +static inline int net_match(struct test_node *tn, net_addr *query, net_addr *data) +{ return (tn->pos < PREFIXES_NUM) && net_equal(query, &data[tn->pos]); } + +static int +t_match_random_net(void) +{ + bt_bird_init(); + bt_config_parse(BT_CONFIG_SIMPLE); + + for (int round = 0; round < TESTS_NUM; round++) + { + int type = !(round & 1) ? NET_IP4 : NET_IP6; + + pool *p = rp_new(&root_pool, "FIB pool"); + net_addr *nets = bt_random_nets(type, PREFIXES_NUM); + + /* Make FIB structure */ + struct fib f; + fib_init(&f, &root_pool, type, sizeof(struct test_node), OFFSETOF(struct test_node, n), 4, NULL); + + for (int i = 0; i < PREFIXES_NUM; i++) + { + struct test_node *tn = fib_get(&f, &nets[i]); + bt_assert(!tn->pos || net_match(tn, &nets[i], nets)); + tn->pos = i; + } + + /* Test (mostly) negative matches */ + for (int i = 0; i < PREFIX_TESTS_NUM; i++) + { + net_addr net; + bt_random_net(&net, type); + + struct test_node *tn = fib_find(&f, &net); + bt_assert(!tn || net_match(tn, &net, nets)); + } + + /* Test positive matches */ + for (int i = 0; i < PREFIX_TESTS_NUM; i++) + { + int j = bt_random_n(PREFIXES_NUM); + + struct test_node *tn = fib_find(&f, &nets[j]); + bt_assert(tn && net_match(tn, &nets[j], nets)); + } + + rfree(p); + tmp_flush(); + } + + bt_bird_cleanup(); + return 1; +} + +static int +t_fib_walk(void) +{ + bt_bird_init(); + bt_config_parse(BT_CONFIG_SIMPLE); + + for (int round = 0; round < TESTS_NUM; round++) + { + int type = !(round & 1) ? NET_IP4 : NET_IP6; + + pool *p = rp_new(&root_pool, "FIB pool"); + net_addr *nets = bt_random_nets(type, PREFIXES_NUM); + byte *marks = tmp_allocz(PREFIXES_NUM); + + /* Make FIB structure */ + struct fib f; + fib_init(&f, p, type, sizeof(struct test_node), OFFSETOF(struct test_node, n), 4, NULL); + + for (int i = 1; i < PREFIXES_NUM; i++) + { + struct test_node *tn = fib_get(&f, &nets[i]); + bt_assert(!tn->pos || net_match(tn, &nets[i], nets)); + if (tn->pos) + { + /* Mark dupicate nets */ + bt_assert(!marks[tn->pos]); + marks[tn->pos] = 1; + } + tn->pos = i; + } + + /* Walk FIB and mark nets */ + FIB_WALK(&f, struct test_node, tn) + { + bt_assert(!marks[tn->pos]); + marks[tn->pos] = 1; + } + FIB_WALK_END; + + /* Check in all nets are marked */ + for (int i = 1; i < PREFIXES_NUM; i++) + bt_assert(marks[i]); + + rfree(p); + tmp_flush(); + } + + bt_bird_cleanup(); + return 1; +} + +static int +benchmark_fib_dataset(const char *filename, int type) +{ + net_addr *nets, *test_r, *test_s; + uint n = PREFIX_BENCH_MAX; + int tn = PREFIX_BENCH_NUM; + int match; + + bt_reset_suite_case_timer(); + bt_log_suite_case_result(1, "Reading %s", filename, n); + nets = bt_read_net_file(filename, type, &n); + bt_log_suite_case_result(1, "Read net data, %u nets", n); + bt_reset_suite_case_timer(); + + pool *p = rp_new(&root_pool, "FIB pool"); + + /* Make FIB structure */ + struct fib f; + fib_init(&f, p, type, sizeof(struct test_node), OFFSETOF(struct test_node, n), 0, NULL); + + for (int i = 0; i < (int) n; i++) + { + struct test_node *tn = fib_get(&f, &nets[i]); + tn->pos = i; + } + + bt_log_suite_case_result(1, "Fill FIB structure, %u nets, order %u", n, f.hash_order); + bt_reset_suite_case_timer(); + + /* Compute FIB size */ + size_t fib_size = rmemsize(p).effective * 1000 / (1024*1024); + bt_log_suite_case_result(1, "FIB size: %u.%03u MB", (uint) (fib_size / 1000), (uint) (fib_size % 1000)); + + /* Compute FIB histogram */ + uint hist[16] = {}; + uint sum = 0; + for (uint i = 0; i < f.hash_size; i++) + { + int len = 0; + for (struct fib_node *fn = f.hash_table[i]; fn; fn = fn->next) + len++; + + sum += len; + len = MIN(len, 15); + hist[len]++; + } + bt_log_suite_case_result(1, "FIB histogram:"); + for (uint i = 0; i < 16; i++) + if (hist[i]) + bt_log_suite_case_result(1, "%02u: %8u", i, hist[i]); + + uint avg = (sum * 1000) / (f.hash_size - hist[0]); + bt_log_suite_case_result(1, "FIB chain length: %u.%03u", (uint) (avg / 1000), (uint) (avg % 1000)); + bt_reset_suite_case_timer(); + + /* Make test data */ + test_r = bt_random_nets(type, tn); + test_s = bt_random_net_subset(nets, n, tn); + + bt_log_suite_case_result(1, "Make test data, 2x %u nets", tn); + bt_reset_suite_case_timer(); + + /* Test (mostly negative) random matches */ + match = 0; + for (int i = 0; i < tn; i++) + if (fib_find(&f, &test_r[i])) + match++; + + bt_log_suite_case_result(1, "Random match, %d / %d matches", match, tn); + bt_reset_suite_case_timer(); + + /* Test (positive) subset matches */ + match = 0; + for (int i = 0; i < tn; i++) + if (fib_find(&f, &test_s[i])) + match++; + + bt_log_suite_case_result(1, "Subset match, %d / %d matches", match, tn); + bt_log_suite_case_result(1, ""); + bt_reset_suite_case_timer(); + + rfree(p); + tmp_flush(); + return 1; +} + +static int UNUSED +t_bench_fib_datasets(void) +{ + bt_bird_init(); + bt_config_parse(BT_CONFIG_SIMPLE); + + /* Specific datasets, not included */ + benchmark_fib_dataset("fib-data-bgp-v4-1", NET_IP4); + benchmark_fib_dataset("fib-data-bgp-v4-10", NET_IP4); + benchmark_fib_dataset("fib-data-bgp-v6-1", NET_IP6); + benchmark_fib_dataset("fib-data-bgp-v6-10", NET_IP6); + + bt_bird_cleanup(); + + return 1; +} + +int +main(int argc, char *argv[]) +{ + bt_init(argc, argv); + + bt_test_suite(t_match_random_net, "Testing random prefix matching"); + bt_test_suite(t_fib_walk, "Testing FIB_WALK() on random FIB"); + + // bt_test_suite(t_bench_fib_datasets, "Benchmark FIB from datasets by random subset of nets"); + + return bt_exit_value(); +} diff --git a/test/birdtest.h b/test/birdtest.h index cfeebb98..540092d6 100644 --- a/test/birdtest.h +++ b/test/birdtest.h @@ -37,6 +37,10 @@ int bt_test_suite_base(int (*test_fn)(const void *), const char *test_id, const static inline u64 bt_random(void) { return ((u64) random() & 0xffffffff) | ((u64) random() << 32); } +static inline u32 bt_random_n(u32 max) +{ return random() % max; } + + void bt_log_suite_result(int result, const char *fmt, ...); void bt_log_suite_case_result(int result, const char *fmt, ...); diff --git a/test/bt-utils.c b/test/bt-utils.c index 8496e185..fb42cd35 100644 --- a/test/bt-utils.c +++ b/test/bt-utils.c @@ -219,3 +219,135 @@ bt_bytes_to_hex(char *buf, const byte *in_data, size_t size) sprintf(buf + i*2, "%02x", in_data[i]); } +void +bt_random_net(net_addr *net, int type) +{ + ip4_addr ip4; + ip6_addr ip6; + uint pxlen; + + switch (type) + { + case NET_IP4: + pxlen = bt_random_n(24)+8; + ip4 = ip4_from_u32((u32) bt_random()); + net_fill_ip4(net, ip4_and(ip4, ip4_mkmask(pxlen)), pxlen); + break; + + case NET_IP6: + pxlen = bt_random_n(120)+8; + ip6 = ip6_build(bt_random(), bt_random(), bt_random(), bt_random()); + net_fill_ip6(net, ip6_and(ip6, ip6_mkmask(pxlen)), pxlen); + break; + + default: + die("Net type %d not implemented", type); + } +} + +net_addr * +bt_random_nets(int type, uint n) +{ + net_addr *nets = tmp_alloc(n * sizeof(net_addr)); + + for (uint i = 0; i < n; i++) + bt_random_net(&nets[i], type); + + return nets; +} + +net_addr * +bt_random_net_subset(net_addr *src, uint sn, uint dn) +{ + net_addr *nets = tmp_alloc(dn * sizeof(net_addr)); + + for (uint i = 0; i < dn; i++) + net_copy(&nets[i], &src[bt_random_n(sn)]); + + return nets; +} + +void +bt_read_net(const char *str, net_addr *net, int type) +{ + ip4_addr ip4; + ip6_addr ip6; + uint pxlen; + char addr[64]; + + switch (type) + { + case NET_IP4: + if (sscanf(str, "%[0-9.]/%u", addr, &pxlen) != 2) + goto err; + + if (!ip4_pton(addr, &ip4)) + goto err; + + if (!net_validate_px4(ip4, pxlen)) + goto err; + + net_fill_ip4(net, ip4, pxlen); + break; + + case NET_IP6: + if (sscanf(str, "%[0-9a-fA-F:.]/%u", addr, &pxlen) != 2) + goto err; + + if (!ip6_pton(addr, &ip6)) + goto err; + + if (!net_validate_px6(ip6, pxlen)) + goto err; + + net_fill_ip6(net, ip6, pxlen); + break; + + default: + die("Net type %d not implemented", type); + } + return; + +err: + bt_abort_msg("Invalid network '%s'", str); +} + +net_addr * +bt_read_nets(FILE *f, int type, uint *n) +{ + char str[80]; + + net_addr *nets = tmp_alloc(*n * sizeof(net_addr)); + uint i = 0; + + errno = 0; + while (fgets(str, sizeof(str), f)) + { + if (str[0] == '\n') + break; + + if (i >= *n) + bt_abort_msg("Too many networks"); + + bt_read_net(str, &nets[i], type); + bt_debug("ADD %s\n", str); + i++; + } + bt_syscall(errno, "fgets()"); + + bt_debug("DONE reading %u nets\n", i); + + *n = i; + return nets; +} + +net_addr * +bt_read_net_file(const char *filename, int type, uint *n) +{ + FILE *f = fopen(filename, "r"); + bt_syscall(!f, "fopen(%s)", filename); + net_addr *nets = bt_read_nets(f, type, n); + fclose(f); + + return nets; +} diff --git a/test/bt-utils.h b/test/bt-utils.h index 13d267cc..d29a0b7c 100644 --- a/test/bt-utils.h +++ b/test/bt-utils.h @@ -26,6 +26,12 @@ uint bt_naive_pow(uint base, uint power); void bt_bytes_to_hex(char *buf, const byte *in_data, size_t size); +void bt_random_net(net_addr *net, int type); +net_addr *bt_random_nets(int type, uint n); +net_addr *bt_random_net_subset(net_addr *src, uint sn, uint dn); +void bt_read_net(const char *str, net_addr *net, int type); +net_addr *bt_read_nets(FILE *f, int type, uint *n); +net_addr *bt_read_net_file(const char *filename, int type, uint *n); void bt_bird_init(void); void bt_bird_cleanup(void);