Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

selftests/bpf: Add batch ops testing for htab and htab_percpu map

Tested bpf_map_lookup_batch(), bpf_map_lookup_and_delete_batch(),
bpf_map_update_batch(), and bpf_map_delete_batch() functionality.
$ ./test_maps
...
test_htab_map_batch_ops:PASS
test_htab_percpu_map_batch_ops:PASS
...

Signed-off-by: Yonghong Song <yhs@fb.com>
Signed-off-by: Brian Vazquez <brianvv@google.com>
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Link: https://lore.kernel.org/bpf/20200115184308.162644-9-brianvv@google.com

authored by

Yonghong Song and committed by
Alexei Starovoitov
30ff3c59 2ab3d86e

+283
+283
tools/testing/selftests/bpf/map_tests/htab_map_batch_ops.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 2 + /* Copyright (c) 2019 Facebook */ 3 + #include <stdio.h> 4 + #include <errno.h> 5 + #include <string.h> 6 + 7 + #include <bpf/bpf.h> 8 + #include <bpf/libbpf.h> 9 + 10 + #include <bpf_util.h> 11 + #include <test_maps.h> 12 + 13 + static void map_batch_update(int map_fd, __u32 max_entries, int *keys, 14 + void *values, bool is_pcpu) 15 + { 16 + typedef BPF_DECLARE_PERCPU(int, value); 17 + value *v = NULL; 18 + int i, j, err; 19 + DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts, 20 + .elem_flags = 0, 21 + .flags = 0, 22 + ); 23 + 24 + if (is_pcpu) 25 + v = (value *)values; 26 + 27 + for (i = 0; i < max_entries; i++) { 28 + keys[i] = i + 1; 29 + if (is_pcpu) 30 + for (j = 0; j < bpf_num_possible_cpus(); j++) 31 + bpf_percpu(v[i], j) = i + 2 + j; 32 + else 33 + ((int *)values)[i] = i + 2; 34 + } 35 + 36 + err = bpf_map_update_batch(map_fd, keys, values, &max_entries, &opts); 37 + CHECK(err, "bpf_map_update_batch()", "error:%s\n", strerror(errno)); 38 + } 39 + 40 + static void map_batch_verify(int *visited, __u32 max_entries, 41 + int *keys, void *values, bool is_pcpu) 42 + { 43 + typedef BPF_DECLARE_PERCPU(int, value); 44 + value *v = NULL; 45 + int i, j; 46 + 47 + if (is_pcpu) 48 + v = (value *)values; 49 + 50 + memset(visited, 0, max_entries * sizeof(*visited)); 51 + for (i = 0; i < max_entries; i++) { 52 + 53 + if (is_pcpu) { 54 + for (j = 0; j < bpf_num_possible_cpus(); j++) { 55 + CHECK(keys[i] + 1 + j != bpf_percpu(v[i], j), 56 + "key/value checking", 57 + "error: i %d j %d key %d value %d\n", 58 + i, j, keys[i], bpf_percpu(v[i], j)); 59 + } 60 + } else { 61 + CHECK(keys[i] + 1 != ((int *)values)[i], 62 + "key/value checking", 63 + "error: i %d key %d value %d\n", i, keys[i], 64 + ((int *)values)[i]); 65 + } 66 + 67 + visited[i] = 1; 68 + 69 + } 70 + for (i = 0; i < max_entries; i++) { 71 + CHECK(visited[i] != 1, "visited checking", 72 + "error: keys array at index %d missing\n", i); 73 + } 74 + } 75 + 76 + void __test_map_lookup_and_delete_batch(bool is_pcpu) 77 + { 78 + __u32 batch, count, total, total_success; 79 + typedef BPF_DECLARE_PERCPU(int, value); 80 + int map_fd, *keys, *visited, key; 81 + const __u32 max_entries = 10; 82 + value pcpu_values[max_entries]; 83 + int err, step, value_size; 84 + bool nospace_err; 85 + void *values; 86 + struct bpf_create_map_attr xattr = { 87 + .name = "hash_map", 88 + .map_type = is_pcpu ? BPF_MAP_TYPE_PERCPU_HASH : 89 + BPF_MAP_TYPE_HASH, 90 + .key_size = sizeof(int), 91 + .value_size = sizeof(int), 92 + }; 93 + DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts, 94 + .elem_flags = 0, 95 + .flags = 0, 96 + ); 97 + 98 + xattr.max_entries = max_entries; 99 + map_fd = bpf_create_map_xattr(&xattr); 100 + CHECK(map_fd == -1, 101 + "bpf_create_map_xattr()", "error:%s\n", strerror(errno)); 102 + 103 + value_size = is_pcpu ? sizeof(value) : sizeof(int); 104 + keys = malloc(max_entries * sizeof(int)); 105 + if (is_pcpu) 106 + values = pcpu_values; 107 + else 108 + values = malloc(max_entries * sizeof(int)); 109 + visited = malloc(max_entries * sizeof(int)); 110 + CHECK(!keys || !values || !visited, "malloc()", 111 + "error:%s\n", strerror(errno)); 112 + 113 + /* test 1: lookup/delete an empty hash table, -ENOENT */ 114 + count = max_entries; 115 + err = bpf_map_lookup_and_delete_batch(map_fd, NULL, &batch, keys, 116 + values, &count, &opts); 117 + CHECK((err && errno != ENOENT), "empty map", 118 + "error: %s\n", strerror(errno)); 119 + 120 + /* populate elements to the map */ 121 + map_batch_update(map_fd, max_entries, keys, values, is_pcpu); 122 + 123 + /* test 2: lookup/delete with count = 0, success */ 124 + count = 0; 125 + err = bpf_map_lookup_and_delete_batch(map_fd, NULL, &batch, keys, 126 + values, &count, &opts); 127 + CHECK(err, "count = 0", "error: %s\n", strerror(errno)); 128 + 129 + /* test 3: lookup/delete with count = max_entries, success */ 130 + memset(keys, 0, max_entries * sizeof(*keys)); 131 + memset(values, 0, max_entries * value_size); 132 + count = max_entries; 133 + err = bpf_map_lookup_and_delete_batch(map_fd, NULL, &batch, keys, 134 + values, &count, &opts); 135 + CHECK((err && errno != ENOENT), "count = max_entries", 136 + "error: %s\n", strerror(errno)); 137 + CHECK(count != max_entries, "count = max_entries", 138 + "count = %u, max_entries = %u\n", count, max_entries); 139 + map_batch_verify(visited, max_entries, keys, values, is_pcpu); 140 + 141 + /* bpf_map_get_next_key() should return -ENOENT for an empty map. */ 142 + err = bpf_map_get_next_key(map_fd, NULL, &key); 143 + CHECK(!err, "bpf_map_get_next_key()", "error: %s\n", strerror(errno)); 144 + 145 + /* test 4: lookup/delete in a loop with various steps. */ 146 + total_success = 0; 147 + for (step = 1; step < max_entries; step++) { 148 + map_batch_update(map_fd, max_entries, keys, values, is_pcpu); 149 + memset(keys, 0, max_entries * sizeof(*keys)); 150 + memset(values, 0, max_entries * value_size); 151 + total = 0; 152 + /* iteratively lookup/delete elements with 'step' 153 + * elements each 154 + */ 155 + count = step; 156 + nospace_err = false; 157 + while (true) { 158 + err = bpf_map_lookup_batch(map_fd, 159 + total ? &batch : NULL, 160 + &batch, keys + total, 161 + values + 162 + total * value_size, 163 + &count, &opts); 164 + /* It is possible that we are failing due to buffer size 165 + * not big enough. In such cases, let us just exit and 166 + * go with large steps. Not that a buffer size with 167 + * max_entries should always work. 168 + */ 169 + if (err && errno == ENOSPC) { 170 + nospace_err = true; 171 + break; 172 + } 173 + 174 + CHECK((err && errno != ENOENT), "lookup with steps", 175 + "error: %s\n", strerror(errno)); 176 + 177 + total += count; 178 + if (err) 179 + break; 180 + 181 + } 182 + if (nospace_err == true) 183 + continue; 184 + 185 + CHECK(total != max_entries, "lookup with steps", 186 + "total = %u, max_entries = %u\n", total, max_entries); 187 + map_batch_verify(visited, max_entries, keys, values, is_pcpu); 188 + 189 + total = 0; 190 + count = step; 191 + while (total < max_entries) { 192 + if (max_entries - total < step) 193 + count = max_entries - total; 194 + err = bpf_map_delete_batch(map_fd, 195 + keys + total, 196 + &count, &opts); 197 + CHECK((err && errno != ENOENT), "delete batch", 198 + "error: %s\n", strerror(errno)); 199 + total += count; 200 + if (err) 201 + break; 202 + } 203 + CHECK(total != max_entries, "delete with steps", 204 + "total = %u, max_entries = %u\n", total, max_entries); 205 + 206 + /* check map is empty, errono == ENOENT */ 207 + err = bpf_map_get_next_key(map_fd, NULL, &key); 208 + CHECK(!err || errno != ENOENT, "bpf_map_get_next_key()", 209 + "error: %s\n", strerror(errno)); 210 + 211 + /* iteratively lookup/delete elements with 'step' 212 + * elements each 213 + */ 214 + map_batch_update(map_fd, max_entries, keys, values, is_pcpu); 215 + memset(keys, 0, max_entries * sizeof(*keys)); 216 + memset(values, 0, max_entries * value_size); 217 + total = 0; 218 + count = step; 219 + nospace_err = false; 220 + while (true) { 221 + err = bpf_map_lookup_and_delete_batch(map_fd, 222 + total ? &batch : NULL, 223 + &batch, keys + total, 224 + values + 225 + total * value_size, 226 + &count, &opts); 227 + /* It is possible that we are failing due to buffer size 228 + * not big enough. In such cases, let us just exit and 229 + * go with large steps. Not that a buffer size with 230 + * max_entries should always work. 231 + */ 232 + if (err && errno == ENOSPC) { 233 + nospace_err = true; 234 + break; 235 + } 236 + 237 + CHECK((err && errno != ENOENT), "lookup with steps", 238 + "error: %s\n", strerror(errno)); 239 + 240 + total += count; 241 + if (err) 242 + break; 243 + } 244 + 245 + if (nospace_err == true) 246 + continue; 247 + 248 + CHECK(total != max_entries, "lookup/delete with steps", 249 + "total = %u, max_entries = %u\n", total, max_entries); 250 + 251 + map_batch_verify(visited, max_entries, keys, values, is_pcpu); 252 + err = bpf_map_get_next_key(map_fd, NULL, &key); 253 + CHECK(!err, "bpf_map_get_next_key()", "error: %s\n", 254 + strerror(errno)); 255 + 256 + total_success++; 257 + } 258 + 259 + CHECK(total_success == 0, "check total_success", 260 + "unexpected failure\n"); 261 + free(keys); 262 + free(visited); 263 + if (!is_pcpu) 264 + free(values); 265 + } 266 + 267 + void htab_map_batch_ops(void) 268 + { 269 + __test_map_lookup_and_delete_batch(false); 270 + printf("test_%s:PASS\n", __func__); 271 + } 272 + 273 + void htab_percpu_map_batch_ops(void) 274 + { 275 + __test_map_lookup_and_delete_batch(true); 276 + printf("test_%s:PASS\n", __func__); 277 + } 278 + 279 + void test_htab_map_batch_ops(void) 280 + { 281 + htab_map_batch_ops(); 282 + htab_percpu_map_batch_ops(); 283 + }