Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,477 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2021 Facebook */
#include <argp.h>
#include <linux/log2.h>
#include <pthread.h>
#include "bench.h"
#include "bloom_filter_bench.skel.h"
#include "bpf_util.h"
static struct ctx {
bool use_array_map;
bool use_hashmap;
bool hashmap_use_bloom;
bool count_false_hits;
struct bloom_filter_bench *skel;
int bloom_fd;
int hashmap_fd;
int array_map_fd;
pthread_mutex_t map_done_mtx;
pthread_cond_t map_done_cv;
bool map_done;
bool map_prepare_err;
__u32 next_map_idx;
} ctx = {
.map_done_mtx = PTHREAD_MUTEX_INITIALIZER,
.map_done_cv = PTHREAD_COND_INITIALIZER,
};
struct stat {
__u32 stats[3];
};
static struct {
__u32 nr_entries;
__u8 nr_hash_funcs;
__u8 value_size;
} args = {
.nr_entries = 1000,
.nr_hash_funcs = 3,
.value_size = 8,
};
enum {
ARG_NR_ENTRIES = 3000,
ARG_NR_HASH_FUNCS = 3001,
ARG_VALUE_SIZE = 3002,
};
static const struct argp_option opts[] = {
{ "nr_entries", ARG_NR_ENTRIES, "NR_ENTRIES", 0,
"Set number of expected unique entries in the bloom filter"},
{ "nr_hash_funcs", ARG_NR_HASH_FUNCS, "NR_HASH_FUNCS", 0,
"Set number of hash functions in the bloom filter"},
{ "value_size", ARG_VALUE_SIZE, "VALUE_SIZE", 0,
"Set value size (in bytes) of bloom filter entries"},
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
long ret;
switch (key) {
case ARG_NR_ENTRIES:
ret = strtol(arg, NULL, 10);
if (ret < 1 || ret > UINT_MAX) {
fprintf(stderr, "Invalid nr_entries count.");
argp_usage(state);
}
args.nr_entries = ret;
break;
case ARG_NR_HASH_FUNCS:
ret = strtol(arg, NULL, 10);
if (ret < 1 || ret > 15) {
fprintf(stderr,
"The bloom filter must use 1 to 15 hash functions.");
argp_usage(state);
}
args.nr_hash_funcs = ret;
break;
case ARG_VALUE_SIZE:
ret = strtol(arg, NULL, 10);
if (ret < 2 || ret > 256) {
fprintf(stderr,
"Invalid value size. Must be between 2 and 256 bytes");
argp_usage(state);
}
args.value_size = ret;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
/* exported into benchmark runner */
const struct argp bench_bloom_map_argp = {
.options = opts,
.parser = parse_arg,
};
static void validate(void)
{
if (env.consumer_cnt != 0) {
fprintf(stderr,
"The bloom filter benchmarks do not support consumer\n");
exit(1);
}
}
static inline void trigger_bpf_program(void)
{
syscall(__NR_getpgid);
}
static void *producer(void *input)
{
while (true)
trigger_bpf_program();
return NULL;
}
static void *map_prepare_thread(void *arg)
{
__u32 val_size, i;
void *val = NULL;
int err;
val_size = args.value_size;
val = malloc(val_size);
if (!val) {
ctx.map_prepare_err = true;
goto done;
}
while (true) {
i = __atomic_add_fetch(&ctx.next_map_idx, 1, __ATOMIC_RELAXED);
if (i > args.nr_entries)
break;
again:
/* Populate hashmap, bloom filter map, and array map with the same
* random values
*/
err = syscall(__NR_getrandom, val, val_size, 0);
if (err != val_size) {
ctx.map_prepare_err = true;
fprintf(stderr, "failed to get random value: %d\n", -errno);
break;
}
if (ctx.use_hashmap) {
err = bpf_map_update_elem(ctx.hashmap_fd, val, val, BPF_NOEXIST);
if (err) {
if (err != -EEXIST) {
ctx.map_prepare_err = true;
fprintf(stderr, "failed to add elem to hashmap: %d\n",
-errno);
break;
}
goto again;
}
}
i--;
if (ctx.use_array_map) {
err = bpf_map_update_elem(ctx.array_map_fd, &i, val, 0);
if (err) {
ctx.map_prepare_err = true;
fprintf(stderr, "failed to add elem to array map: %d\n", -errno);
break;
}
}
if (ctx.use_hashmap && !ctx.hashmap_use_bloom)
continue;
err = bpf_map_update_elem(ctx.bloom_fd, NULL, val, 0);
if (err) {
ctx.map_prepare_err = true;
fprintf(stderr,
"failed to add elem to bloom filter map: %d\n", -errno);
break;
}
}
done:
pthread_mutex_lock(&ctx.map_done_mtx);
ctx.map_done = true;
pthread_cond_signal(&ctx.map_done_cv);
pthread_mutex_unlock(&ctx.map_done_mtx);
if (val)
free(val);
return NULL;
}
static void populate_maps(void)
{
unsigned int nr_cpus = bpf_num_possible_cpus();
pthread_t map_thread;
int i, err, nr_rand_bytes;
ctx.bloom_fd = bpf_map__fd(ctx.skel->maps.bloom_map);
ctx.hashmap_fd = bpf_map__fd(ctx.skel->maps.hashmap);
ctx.array_map_fd = bpf_map__fd(ctx.skel->maps.array_map);
for (i = 0; i < nr_cpus; i++) {
err = pthread_create(&map_thread, NULL, map_prepare_thread,
NULL);
if (err) {
fprintf(stderr, "failed to create pthread: %d\n", -errno);
exit(1);
}
}
pthread_mutex_lock(&ctx.map_done_mtx);
while (!ctx.map_done)
pthread_cond_wait(&ctx.map_done_cv, &ctx.map_done_mtx);
pthread_mutex_unlock(&ctx.map_done_mtx);
if (ctx.map_prepare_err)
exit(1);
nr_rand_bytes = syscall(__NR_getrandom, ctx.skel->bss->rand_vals,
ctx.skel->rodata->nr_rand_bytes, 0);
if (nr_rand_bytes != ctx.skel->rodata->nr_rand_bytes) {
fprintf(stderr, "failed to get random bytes\n");
exit(1);
}
}
static void check_args(void)
{
if (args.value_size < 8) {
__u64 nr_unique_entries = 1ULL << (args.value_size * 8);
if (args.nr_entries > nr_unique_entries) {
fprintf(stderr,
"Not enough unique values for the nr_entries requested\n");
exit(1);
}
}
}
static struct bloom_filter_bench *setup_skeleton(void)
{
struct bloom_filter_bench *skel;
check_args();
setup_libbpf();
skel = bloom_filter_bench__open();
if (!skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
skel->rodata->hashmap_use_bloom = ctx.hashmap_use_bloom;
skel->rodata->count_false_hits = ctx.count_false_hits;
/* Resize number of entries */
bpf_map__set_max_entries(skel->maps.hashmap, args.nr_entries);
bpf_map__set_max_entries(skel->maps.array_map, args.nr_entries);
bpf_map__set_max_entries(skel->maps.bloom_map, args.nr_entries);
/* Set value size */
bpf_map__set_value_size(skel->maps.array_map, args.value_size);
bpf_map__set_value_size(skel->maps.bloom_map, args.value_size);
bpf_map__set_value_size(skel->maps.hashmap, args.value_size);
/* For the hashmap, we use the value as the key as well */
bpf_map__set_key_size(skel->maps.hashmap, args.value_size);
skel->bss->value_size = args.value_size;
/* Set number of hash functions */
bpf_map__set_map_extra(skel->maps.bloom_map, args.nr_hash_funcs);
if (bloom_filter_bench__load(skel)) {
fprintf(stderr, "failed to load skeleton\n");
exit(1);
}
return skel;
}
static void bloom_lookup_setup(void)
{
struct bpf_link *link;
ctx.use_array_map = true;
ctx.skel = setup_skeleton();
populate_maps();
link = bpf_program__attach(ctx.skel->progs.bloom_lookup);
if (!link) {
fprintf(stderr, "failed to attach program!\n");
exit(1);
}
}
static void bloom_update_setup(void)
{
struct bpf_link *link;
ctx.use_array_map = true;
ctx.skel = setup_skeleton();
populate_maps();
link = bpf_program__attach(ctx.skel->progs.bloom_update);
if (!link) {
fprintf(stderr, "failed to attach program!\n");
exit(1);
}
}
static void false_positive_setup(void)
{
struct bpf_link *link;
ctx.use_hashmap = true;
ctx.hashmap_use_bloom = true;
ctx.count_false_hits = true;
ctx.skel = setup_skeleton();
populate_maps();
link = bpf_program__attach(ctx.skel->progs.bloom_hashmap_lookup);
if (!link) {
fprintf(stderr, "failed to attach program!\n");
exit(1);
}
}
static void hashmap_with_bloom_setup(void)
{
struct bpf_link *link;
ctx.use_hashmap = true;
ctx.hashmap_use_bloom = true;
ctx.skel = setup_skeleton();
populate_maps();
link = bpf_program__attach(ctx.skel->progs.bloom_hashmap_lookup);
if (!link) {
fprintf(stderr, "failed to attach program!\n");
exit(1);
}
}
static void hashmap_no_bloom_setup(void)
{
struct bpf_link *link;
ctx.use_hashmap = true;
ctx.skel = setup_skeleton();
populate_maps();
link = bpf_program__attach(ctx.skel->progs.bloom_hashmap_lookup);
if (!link) {
fprintf(stderr, "failed to attach program!\n");
exit(1);
}
}
static void measure(struct bench_res *res)
{
unsigned long total_hits = 0, total_drops = 0, total_false_hits = 0;
static unsigned long last_hits, last_drops, last_false_hits;
unsigned int nr_cpus = bpf_num_possible_cpus();
int hit_key, drop_key, false_hit_key;
int i;
hit_key = ctx.skel->rodata->hit_key;
drop_key = ctx.skel->rodata->drop_key;
false_hit_key = ctx.skel->rodata->false_hit_key;
if (ctx.skel->bss->error != 0) {
fprintf(stderr, "error (%d) when searching the bloom filter\n",
ctx.skel->bss->error);
exit(1);
}
for (i = 0; i < nr_cpus; i++) {
struct stat *s = (void *)&ctx.skel->bss->percpu_stats[i];
total_hits += s->stats[hit_key];
total_drops += s->stats[drop_key];
total_false_hits += s->stats[false_hit_key];
}
res->hits = total_hits - last_hits;
res->drops = total_drops - last_drops;
res->false_hits = total_false_hits - last_false_hits;
last_hits = total_hits;
last_drops = total_drops;
last_false_hits = total_false_hits;
}
const struct bench bench_bloom_lookup = {
.name = "bloom-lookup",
.argp = &bench_bloom_map_argp,
.validate = validate,
.setup = bloom_lookup_setup,
.producer_thread = producer,
.measure = measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_bloom_update = {
.name = "bloom-update",
.argp = &bench_bloom_map_argp,
.validate = validate,
.setup = bloom_update_setup,
.producer_thread = producer,
.measure = measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_bloom_false_positive = {
.name = "bloom-false-positive",
.argp = &bench_bloom_map_argp,
.validate = validate,
.setup = false_positive_setup,
.producer_thread = producer,
.measure = measure,
.report_progress = false_hits_report_progress,
.report_final = false_hits_report_final,
};
const struct bench bench_hashmap_without_bloom = {
.name = "hashmap-without-bloom",
.argp = &bench_bloom_map_argp,
.validate = validate,
.setup = hashmap_no_bloom_setup,
.producer_thread = producer,
.measure = measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_hashmap_with_bloom = {
.name = "hashmap-with-bloom",
.argp = &bench_bloom_map_argp,
.validate = validate,
.setup = hashmap_with_bloom_setup,
.producer_thread = producer,
.measure = measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
@@ -0,0 +1,185 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
#include <argp.h>
#include "bench.h"
#include "crypto_bench.skel.h"
#define MAX_CIPHER_LEN 32
static char *input;
static struct crypto_ctx {
struct crypto_bench *skel;
int pfd;
} ctx;
static struct crypto_args {
u32 crypto_len;
char *crypto_cipher;
} args = {
.crypto_len = 16,
.crypto_cipher = "ecb(aes)",
};
enum {
ARG_CRYPTO_LEN = 5000,
ARG_CRYPTO_CIPHER = 5001,
};
static const struct argp_option opts[] = {
{ "crypto-len", ARG_CRYPTO_LEN, "CRYPTO_LEN", 0,
"Set the length of crypto buffer" },
{ "crypto-cipher", ARG_CRYPTO_CIPHER, "CRYPTO_CIPHER", 0,
"Set the cipher to use (default:ecb(aes))" },
{},
};
static error_t crypto_parse_arg(int key, char *arg, struct argp_state *state)
{
switch (key) {
case ARG_CRYPTO_LEN:
args.crypto_len = strtoul(arg, NULL, 10);
if (!args.crypto_len ||
args.crypto_len > sizeof(ctx.skel->bss->dst)) {
fprintf(stderr, "Invalid crypto buffer len (limit %zu)\n",
sizeof(ctx.skel->bss->dst));
argp_usage(state);
}
break;
case ARG_CRYPTO_CIPHER:
args.crypto_cipher = strdup(arg);
if (!strlen(args.crypto_cipher) ||
strlen(args.crypto_cipher) > MAX_CIPHER_LEN) {
fprintf(stderr, "Invalid crypto cipher len (limit %d)\n",
MAX_CIPHER_LEN);
argp_usage(state);
}
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
const struct argp bench_crypto_argp = {
.options = opts,
.parser = crypto_parse_arg,
};
static void crypto_validate(void)
{
if (env.consumer_cnt != 0) {
fprintf(stderr, "bpf crypto benchmark doesn't support consumer!\n");
exit(1);
}
}
static void crypto_setup(void)
{
LIBBPF_OPTS(bpf_test_run_opts, opts);
int err, pfd;
size_t i, sz;
sz = args.crypto_len;
if (!sz || sz > sizeof(ctx.skel->bss->dst)) {
fprintf(stderr, "invalid encrypt buffer size (source %zu, target %zu)\n",
sz, sizeof(ctx.skel->bss->dst));
exit(1);
}
setup_libbpf();
ctx.skel = crypto_bench__open();
if (!ctx.skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
snprintf(ctx.skel->bss->cipher, 128, "%s", args.crypto_cipher);
memcpy(ctx.skel->bss->key, "12345678testtest", 16);
ctx.skel->bss->key_len = 16;
ctx.skel->bss->authsize = 0;
srandom(time(NULL));
input = malloc(sz);
for (i = 0; i < sz - 1; i++)
input[i] = '1' + random() % 9;
input[sz - 1] = '\0';
ctx.skel->rodata->len = args.crypto_len;
err = crypto_bench__load(ctx.skel);
if (err) {
fprintf(stderr, "failed to load skeleton\n");
crypto_bench__destroy(ctx.skel);
exit(1);
}
pfd = bpf_program__fd(ctx.skel->progs.crypto_setup);
if (pfd < 0) {
fprintf(stderr, "failed to get fd for setup prog\n");
crypto_bench__destroy(ctx.skel);
exit(1);
}
err = bpf_prog_test_run_opts(pfd, &opts);
if (err || ctx.skel->bss->status) {
fprintf(stderr, "failed to run setup prog: err %d, status %d\n",
err, ctx.skel->bss->status);
crypto_bench__destroy(ctx.skel);
exit(1);
}
}
static void crypto_encrypt_setup(void)
{
crypto_setup();
ctx.pfd = bpf_program__fd(ctx.skel->progs.crypto_encrypt);
}
static void crypto_decrypt_setup(void)
{
crypto_setup();
ctx.pfd = bpf_program__fd(ctx.skel->progs.crypto_decrypt);
}
static void crypto_measure(struct bench_res *res)
{
res->hits = atomic_swap(&ctx.skel->bss->hits, 0);
}
static void *crypto_producer(void *unused)
{
LIBBPF_OPTS(bpf_test_run_opts, opts,
.repeat = 64,
.data_in = input,
.data_size_in = args.crypto_len,
);
while (true)
(void)bpf_prog_test_run_opts(ctx.pfd, &opts);
return NULL;
}
const struct bench bench_crypto_encrypt = {
.name = "crypto-encrypt",
.argp = &bench_crypto_argp,
.validate = crypto_validate,
.setup = crypto_encrypt_setup,
.producer_thread = crypto_producer,
.measure = crypto_measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_crypto_decrypt = {
.name = "crypto-decrypt",
.argp = &bench_crypto_argp,
.validate = crypto_validate,
.setup = crypto_decrypt_setup,
.producer_thread = crypto_producer,
.measure = crypto_measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Bytedance */
#include "bench.h"
#include "bpf_hashmap_full_update_bench.skel.h"
#include "bpf_util.h"
/* BPF triggering benchmarks */
static struct ctx {
struct bpf_hashmap_full_update_bench *skel;
} ctx;
#define MAX_LOOP_NUM 10000
static void validate(void)
{
if (env.consumer_cnt != 0) {
fprintf(stderr, "benchmark doesn't support consumer!\n");
exit(1);
}
}
static void *producer(void *input)
{
while (true) {
/* trigger the bpf program */
syscall(__NR_getpgid);
}
return NULL;
}
static void measure(struct bench_res *res)
{
}
static void hashmap_full_update_setup(enum bpf_map_type map_type)
{
struct bpf_link *link;
int map_fd, i, max_entries;
setup_libbpf();
ctx.skel = bpf_hashmap_full_update_bench__open();
if (!ctx.skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
bpf_map__set_type(ctx.skel->maps.hash_map_bench, map_type);
if (map_type == BPF_MAP_TYPE_RHASH)
bpf_map__set_map_flags(ctx.skel->maps.hash_map_bench,
BPF_F_NO_PREALLOC);
if (bpf_hashmap_full_update_bench__load(ctx.skel)) {
fprintf(stderr, "failed to load skeleton\n");
exit(1);
}
ctx.skel->bss->nr_loops = MAX_LOOP_NUM;
link = bpf_program__attach(ctx.skel->progs.benchmark);
if (!link) {
fprintf(stderr, "failed to attach program!\n");
exit(1);
}
/* fill hash_map */
map_fd = bpf_map__fd(ctx.skel->maps.hash_map_bench);
max_entries = bpf_map__max_entries(ctx.skel->maps.hash_map_bench);
for (i = 0; i < max_entries; i++)
bpf_map_update_elem(map_fd, &i, &i, BPF_ANY);
}
static void setup(void)
{
hashmap_full_update_setup(BPF_MAP_TYPE_HASH);
}
static void rhash_setup(void)
{
hashmap_full_update_setup(BPF_MAP_TYPE_RHASH);
}
static void hashmap_report_final(struct bench_res res[], int res_cnt)
{
unsigned int nr_cpus = bpf_num_possible_cpus();
int i;
for (i = 0; i < nr_cpus; i++) {
u64 time = ctx.skel->bss->percpu_time[i];
if (!time)
continue;
printf("%d:hash_map_full_perf %lld events per sec\n",
i, ctx.skel->bss->nr_loops * 1000000000ll / time);
}
}
const struct bench bench_bpf_hashmap_full_update = {
.name = "bpf-hashmap-full-update",
.validate = validate,
.setup = setup,
.producer_thread = producer,
.measure = measure,
.report_progress = NULL,
.report_final = hashmap_report_final,
};
const struct bench bench_bpf_rhashmap_full_update = {
.name = "bpf-rhashmap-full-update",
.validate = validate,
.setup = rhash_setup,
.producer_thread = producer,
.measure = measure,
.report_progress = NULL,
.report_final = hashmap_report_final,
};
@@ -0,0 +1,304 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2023 Isovalent */
#include <sys/random.h>
#include <argp.h>
#include "bench.h"
#include "bpf_hashmap_lookup.skel.h"
#include "bpf_util.h"
/* BPF triggering benchmarks */
static struct ctx {
struct bpf_hashmap_lookup *skel;
} ctx;
/* only available to kernel, so define it here */
#define BPF_MAX_LOOPS (1<<23)
#define MAX_KEY_SIZE 1024 /* the size of the key map */
static struct {
__u32 key_size;
__u32 map_flags;
__u32 max_entries;
__u32 nr_entries;
__u32 nr_loops;
} args = {
.key_size = 4,
.map_flags = 0,
.max_entries = 1000,
.nr_entries = 500,
.nr_loops = 1000000,
};
enum {
ARG_KEY_SIZE = 8001,
ARG_MAP_FLAGS,
ARG_MAX_ENTRIES,
ARG_NR_ENTRIES,
ARG_NR_LOOPS,
};
static const struct argp_option opts[] = {
{ "key_size", ARG_KEY_SIZE, "KEY_SIZE", 0,
"The hashmap key size (max 1024)"},
{ "map_flags", ARG_MAP_FLAGS, "MAP_FLAGS", 0,
"The hashmap flags passed to BPF_MAP_CREATE"},
{ "max_entries", ARG_MAX_ENTRIES, "MAX_ENTRIES", 0,
"The hashmap max entries"},
{ "nr_entries", ARG_NR_ENTRIES, "NR_ENTRIES", 0,
"The number of entries to insert/lookup"},
{ "nr_loops", ARG_NR_LOOPS, "NR_LOOPS", 0,
"The number of loops for the benchmark"},
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
long ret;
switch (key) {
case ARG_KEY_SIZE:
ret = strtol(arg, NULL, 10);
if (ret < 1 || ret > MAX_KEY_SIZE) {
fprintf(stderr, "invalid key_size");
argp_usage(state);
}
args.key_size = ret;
break;
case ARG_MAP_FLAGS:
ret = strtol(arg, NULL, 0);
if (ret < 0 || ret > UINT_MAX) {
fprintf(stderr, "invalid map_flags");
argp_usage(state);
}
args.map_flags = ret;
break;
case ARG_MAX_ENTRIES:
ret = strtol(arg, NULL, 10);
if (ret < 1 || ret > UINT_MAX) {
fprintf(stderr, "invalid max_entries");
argp_usage(state);
}
args.max_entries = ret;
break;
case ARG_NR_ENTRIES:
ret = strtol(arg, NULL, 10);
if (ret < 1 || ret > UINT_MAX) {
fprintf(stderr, "invalid nr_entries");
argp_usage(state);
}
args.nr_entries = ret;
break;
case ARG_NR_LOOPS:
ret = strtol(arg, NULL, 10);
if (ret < 1 || ret > BPF_MAX_LOOPS) {
fprintf(stderr, "invalid nr_loops: %ld (min=1 max=%u)\n",
ret, BPF_MAX_LOOPS);
argp_usage(state);
}
args.nr_loops = ret;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
const struct argp bench_hashmap_lookup_argp = {
.options = opts,
.parser = parse_arg,
};
static void validate(void)
{
if (env.consumer_cnt != 0) {
fprintf(stderr, "benchmark doesn't support consumer!\n");
exit(1);
}
if (args.nr_entries > args.max_entries) {
fprintf(stderr, "args.nr_entries is too big! (max %u, got %u)\n",
args.max_entries, args.nr_entries);
exit(1);
}
}
static void *producer(void *input)
{
while (true) {
/* trigger the bpf program */
syscall(__NR_getpgid);
}
return NULL;
}
static void measure(struct bench_res *res)
{
}
static inline void patch_key(u32 i, u32 *key)
{
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
*key = i + 1;
#else
*key = __builtin_bswap32(i + 1);
#endif
/* the rest of key is random */
}
static void hashmap_lookup_setup(enum bpf_map_type map_type)
{
struct bpf_link *link;
__u32 map_flags;
int map_fd;
int ret;
int i;
setup_libbpf();
ctx.skel = bpf_hashmap_lookup__open();
if (!ctx.skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
map_flags = args.map_flags;
if (map_type == BPF_MAP_TYPE_RHASH)
map_flags |= BPF_F_NO_PREALLOC;
bpf_map__set_type(ctx.skel->maps.hash_map_bench, map_type);
bpf_map__set_max_entries(ctx.skel->maps.hash_map_bench, args.max_entries);
bpf_map__set_key_size(ctx.skel->maps.hash_map_bench, args.key_size);
bpf_map__set_value_size(ctx.skel->maps.hash_map_bench, 8);
bpf_map__set_map_flags(ctx.skel->maps.hash_map_bench, map_flags);
ctx.skel->bss->nr_entries = args.nr_entries;
ctx.skel->bss->nr_loops = args.nr_loops / args.nr_entries;
if (args.key_size > 4) {
for (i = 1; i < args.key_size/4; i++)
ctx.skel->bss->key[i] = 2654435761 * i;
}
ret = bpf_hashmap_lookup__load(ctx.skel);
if (ret) {
bpf_hashmap_lookup__destroy(ctx.skel);
fprintf(stderr, "failed to load map: %s", strerror(-ret));
exit(1);
}
/* fill in the hash_map */
map_fd = bpf_map__fd(ctx.skel->maps.hash_map_bench);
for (u64 i = 0; i < args.nr_entries; i++) {
patch_key(i, ctx.skel->bss->key);
bpf_map_update_elem(map_fd, ctx.skel->bss->key, &i, BPF_ANY);
}
link = bpf_program__attach(ctx.skel->progs.benchmark);
if (!link) {
fprintf(stderr, "failed to attach program!\n");
exit(1);
}
}
static void setup(void)
{
hashmap_lookup_setup(BPF_MAP_TYPE_HASH);
}
static void rhash_setup(void)
{
hashmap_lookup_setup(BPF_MAP_TYPE_RHASH);
}
static inline double events_from_time(u64 time)
{
if (time)
return args.nr_loops * 1000000000llu / time / 1000000.0L;
return 0;
}
static int compute_events(u64 *times, double *events_mean, double *events_stddev, u64 *mean_time)
{
int i, n = 0;
*events_mean = 0;
*events_stddev = 0;
*mean_time = 0;
for (i = 0; i < 32; i++) {
if (!times[i])
break;
*mean_time += times[i];
*events_mean += events_from_time(times[i]);
n += 1;
}
if (!n)
return 0;
*mean_time /= n;
*events_mean /= n;
if (n > 1) {
for (i = 0; i < n; i++) {
double events_i = *events_mean - events_from_time(times[i]);
*events_stddev += events_i * events_i / (n - 1);
}
*events_stddev = sqrt(*events_stddev);
}
return n;
}
static void hashmap_report_final(struct bench_res res[], int res_cnt)
{
unsigned int nr_cpus = bpf_num_possible_cpus();
double events_mean, events_stddev;
u64 mean_time;
int i, n;
for (i = 0; i < nr_cpus; i++) {
n = compute_events(ctx.skel->bss->percpu_times[i], &events_mean,
&events_stddev, &mean_time);
if (n == 0)
continue;
if (env.quiet) {
/* we expect only one cpu to be present */
if (env.affinity)
printf("%.3lf\n", events_mean);
else
printf("cpu%02d %.3lf\n", i, events_mean);
} else {
printf("cpu%02d: lookup %.3lfM ± %.3lfM events/sec"
" (approximated from %d samples of ~%lums)\n",
i, events_mean, 2*events_stddev,
n, mean_time / 1000000);
}
}
}
const struct bench bench_bpf_hashmap_lookup = {
.name = "bpf-hashmap-lookup",
.argp = &bench_hashmap_lookup_argp,
.validate = validate,
.setup = setup,
.producer_thread = producer,
.measure = measure,
.report_progress = NULL,
.report_final = hashmap_report_final,
};
const struct bench bench_bpf_rhashmap_lookup = {
.name = "bpf-rhashmap-lookup",
.argp = &bench_hashmap_lookup_argp,
.validate = validate,
.setup = rhash_setup,
.producer_thread = producer,
.measure = measure,
.report_progress = NULL,
.report_final = hashmap_report_final,
};
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2021 Facebook */
#include <argp.h>
#include "bench.h"
#include "bpf_loop_bench.skel.h"
/* BPF triggering benchmarks */
static struct ctx {
struct bpf_loop_bench *skel;
} ctx;
static struct {
__u32 nr_loops;
} args = {
.nr_loops = 10,
};
enum {
ARG_NR_LOOPS = 4000,
};
static const struct argp_option opts[] = {
{ "nr_loops", ARG_NR_LOOPS, "nr_loops", 0,
"Set number of loops for the bpf_loop helper"},
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
switch (key) {
case ARG_NR_LOOPS:
args.nr_loops = strtol(arg, NULL, 10);
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
/* exported into benchmark runner */
const struct argp bench_bpf_loop_argp = {
.options = opts,
.parser = parse_arg,
};
static void validate(void)
{
if (env.consumer_cnt != 0) {
fprintf(stderr, "benchmark doesn't support consumer!\n");
exit(1);
}
}
static void *producer(void *input)
{
while (true)
/* trigger the bpf program */
syscall(__NR_getpgid);
return NULL;
}
static void measure(struct bench_res *res)
{
res->hits = atomic_swap(&ctx.skel->bss->hits, 0);
}
static void setup(void)
{
struct bpf_link *link;
setup_libbpf();
ctx.skel = bpf_loop_bench__open_and_load();
if (!ctx.skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
link = bpf_program__attach(ctx.skel->progs.benchmark);
if (!link) {
fprintf(stderr, "failed to attach program!\n");
exit(1);
}
ctx.skel->bss->nr_loops = args.nr_loops;
}
const struct bench bench_bpf_loop = {
.name = "bpf-loop",
.argp = &bench_bpf_loop_argp,
.validate = validate,
.setup = setup,
.producer_thread = producer,
.measure = measure,
.report_progress = ops_report_progress,
.report_final = ops_report_final,
};
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include "bench.h"
#include "bench_bpf_timing.h"
#include "bpf_nop_bench.skel.h"
#include "bpf_util.h"
static struct ctx {
struct bpf_nop_bench *skel;
struct bpf_bench_timing timing;
int prog_fd;
} ctx;
static void nop_validate(void)
{
if (env.consumer_cnt != 0) {
fprintf(stderr, "benchmark doesn't support consumers\n");
exit(1);
}
}
static void nop_run_once(void *unused __always_unused)
{
LIBBPF_OPTS(bpf_test_run_opts, topts);
bpf_prog_test_run_opts(ctx.prog_fd, &topts);
}
static void nop_setup(void)
{
struct bpf_nop_bench *skel;
int err;
setup_libbpf();
skel = bpf_nop_bench__open();
if (!skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
err = bpf_nop_bench__load(skel);
if (err) {
fprintf(stderr, "failed to load skeleton: %s\n", strerror(-err));
bpf_nop_bench__destroy(skel);
exit(1);
}
ctx.skel = skel;
ctx.prog_fd = bpf_program__fd(skel->progs.bench_nop);
BENCH_TIMING_INIT(&ctx.timing, skel, 0);
bpf_bench_calibrate(&ctx.timing, nop_run_once, NULL);
env.duration_sec = 600;
}
static void *nop_producer(void *input)
{
while (true)
nop_run_once(NULL);
return NULL;
}
static void nop_measure(struct bench_res *res)
{
bpf_bench_timing_measure(&ctx.timing, res);
}
static void nop_report_final(struct bench_res res[], int res_cnt)
{
bpf_bench_timing_report(&ctx.timing, "bpf-nop", NULL);
}
const struct bench bench_bpf_nop = {
.name = "bpf-nop",
.validate = nop_validate,
.setup = nop_setup,
.producer_thread = nop_producer,
.measure = nop_measure,
.report_final = nop_report_final,
};
@@ -0,0 +1,298 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "bench_bpf_timing.h"
#include "bpf_util.h"
struct timing_stats {
double min, max;
double median, p99;
double mean, stddev;
int count;
};
static int cmp_double(const void *a, const void *b)
{
double da = *(const double *)a;
double db = *(const double *)b;
if (da < db)
return -1;
if (da > db)
return 1;
return 0;
}
static double percentile(const double *sorted, int n, double pct)
{
int idx = (int)(n * pct / 100.0);
if (idx >= n)
idx = n - 1;
return sorted[idx];
}
static int collect_samples(struct bpf_bench_timing *t,
double *out, int max_out)
{
unsigned int nr_cpus = bpf_num_possible_cpus();
__u32 timed_iters = t->batch_iters;
int total = 0;
if (nr_cpus > BENCH_NR_CPUS)
nr_cpus = BENCH_NR_CPUS;
for (unsigned int cpu = 0; cpu < nr_cpus; cpu++) {
__u32 count = t->idx[cpu];
if (count > BENCH_NR_SAMPLES)
count = BENCH_NR_SAMPLES;
for (__u32 i = 0; i < count && total < max_out; i++) {
__u64 sample = t->samples[cpu][i];
if (sample == 0)
continue;
out[total++] = (double)sample / timed_iters;
}
}
qsort(out, total, sizeof(double), cmp_double);
return total;
}
static int filter_outliers_iqr(double *sorted, int n)
{
double q1, q3, iqr, lo, hi;
int start = 0, end = n;
if (n < 8)
return n;
q1 = sorted[n / 4];
q3 = sorted[3 * n / 4];
iqr = q3 - q1;
lo = q1 - 1.5 * iqr;
hi = q3 + 1.5 * iqr;
while (start < end && sorted[start] < lo)
start++;
while (end > start && sorted[end - 1] > hi)
end--;
if (start > 0)
memmove(sorted, sorted + start, (end - start) * sizeof(double));
return end - start;
}
static void compute_stats(const double *sorted, int n,
struct timing_stats *s)
{
double sum = 0, var_sum = 0;
memset(s, 0, sizeof(*s));
s->count = n;
if (n == 0)
return;
s->min = sorted[0];
s->max = sorted[n - 1];
s->median = sorted[n / 2];
s->p99 = percentile(sorted, n, 99);
for (int i = 0; i < n; i++)
sum += sorted[i];
s->mean = sum / n;
for (int i = 0; i < n; i++) {
double d = sorted[i] - s->mean;
var_sum += d * d;
}
s->stddev = n > 1 ? sqrt(var_sum / (n - 1)) : 0;
}
void bpf_bench_timing_measure(struct bpf_bench_timing *t, struct bench_res *res)
{
unsigned int nr_cpus;
__u32 total_samples;
int i;
t->warmup_ticks++;
if (t->warmup_ticks < env.warmup_sec)
return;
if (t->warmup_ticks == env.warmup_sec) {
*t->timing_enabled = 1;
return;
}
nr_cpus = bpf_num_possible_cpus();
if (nr_cpus > BENCH_NR_CPUS)
nr_cpus = BENCH_NR_CPUS;
total_samples = 0;
for (i = 0; i < (int)nr_cpus; i++) {
__u32 cnt = t->idx[i];
if (cnt > BENCH_NR_SAMPLES)
cnt = BENCH_NR_SAMPLES;
total_samples += cnt;
}
if (total_samples >= (__u32)env.producer_cnt * t->target_samples && !t->done) {
t->done = true;
*t->timing_enabled = 0;
bench_force_done();
}
}
void bpf_bench_timing_report(struct bpf_bench_timing *t, const char *name, const char *description)
{
int max_out = BENCH_NR_CPUS * BENCH_NR_SAMPLES;
struct timing_stats s;
double *all;
int total;
all = calloc(max_out, sizeof(*all));
if (!all) {
fprintf(stderr, "failed to allocate timing buffer\n");
return;
}
total = collect_samples(t, all, max_out);
if (total == 0) {
printf("No timing samples collected.\n");
free(all);
return;
}
total = filter_outliers_iqr(all, total);
compute_stats(all, total, &s);
if (t->machine_readable) {
printf("RESULT scenario=%s samples=%d median=%.2f stddev=%.2f cv=%.2f min=%.2f "
"p99=%.2f max=%.2f\n", name, total, s.median, s.stddev,
s.mean > 0 ? s.stddev / s.mean * 100.0 : 0.0, s.min, s.p99, s.max);
} else {
printf("%s: median %.2f ns/op, stddev %.2f, p99 %.2f (%d samples)\n", name,
s.median, s.stddev, s.p99, total);
}
free(all);
}
#define CALIBRATE_SEED_BATCH 100
#define CALIBRATE_MIN_BATCH 100
#define CALIBRATE_MAX_BATCH 10000000
#define CALIBRATE_TARGET_MS 10
#define CALIBRATE_RUNS 5
#define PROPORTIONALITY_TOL 0.05 /* 5% */
static void reset_timing(struct bpf_bench_timing *t)
{
*t->timing_enabled = 0;
memset(t->samples, 0, sizeof(__u64) * BENCH_NR_CPUS * BENCH_NR_SAMPLES);
memset(t->idx, 0, sizeof(__u32) * BENCH_NR_CPUS);
}
static __u64 measure_elapsed(struct bpf_bench_timing *t, bpf_bench_run_fn run_fn, void *run_ctx,
__u32 iters, int runs)
{
__u64 buf[CALIBRATE_RUNS];
int n = 0, i, j;
reset_timing(t);
*t->batch_iters_bss = iters;
*t->timing_enabled = 1;
for (i = 0; i < runs; i++)
run_fn(run_ctx);
*t->timing_enabled = 0;
for (i = 0; i < BENCH_NR_CPUS && n < runs; i++) {
__u32 cnt = t->idx[i];
for (j = 0; j < (int)cnt && n < runs; j++)
buf[n++] = t->samples[i][j];
}
if (n == 0)
return 0;
for (i = 1; i < n; i++) {
__u64 key = buf[i];
j = i - 1;
while (j >= 0 && buf[j] > key) {
buf[j + 1] = buf[j];
j--;
}
buf[j + 1] = key;
}
return buf[n / 2];
}
static __u32 compute_batch_iters(__u64 per_op_ns)
{
__u64 target_ns = (__u64)CALIBRATE_TARGET_MS * 1000000ULL;
__u32 iters;
if (per_op_ns == 0)
return CALIBRATE_MIN_BATCH;
iters = target_ns / per_op_ns;
if (iters < CALIBRATE_MIN_BATCH)
iters = CALIBRATE_MIN_BATCH;
if (iters > CALIBRATE_MAX_BATCH)
iters = CALIBRATE_MAX_BATCH;
return iters;
}
void bpf_bench_calibrate(struct bpf_bench_timing *t, bpf_bench_run_fn run_fn, void *run_ctx)
{
__u64 elapsed, per_op_ns;
__u64 time_n, time_2n;
double ratio;
elapsed = measure_elapsed(t, run_fn, run_ctx, CALIBRATE_SEED_BATCH, CALIBRATE_RUNS);
if (elapsed == 0) {
fprintf(stderr, "calibration: no timing samples, using default\n");
t->batch_iters = 10000;
*t->batch_iters_bss = t->batch_iters;
reset_timing(t);
return;
}
per_op_ns = elapsed / CALIBRATE_SEED_BATCH;
t->batch_iters = compute_batch_iters(per_op_ns);
time_n = measure_elapsed(t, run_fn, run_ctx, t->batch_iters, CALIBRATE_RUNS);
time_2n = measure_elapsed(t, run_fn, run_ctx, t->batch_iters * 2, CALIBRATE_RUNS);
if (time_n > 0 && time_2n > 0) {
ratio = (double)time_2n / (double)time_n;
if (fabs(ratio - 2.0) / 2.0 > PROPORTIONALITY_TOL)
fprintf(stderr,
"WARNING: proportionality check failed (2N/N ratio=%.3f, "
"expected=2.000, error=%.1f%%)\n System noise may be affecting "
"results.\n",
ratio, fabs(ratio - 2.0) / 2.0 * 100.0);
}
*t->batch_iters_bss = t->batch_iters;
reset_timing(t);
}
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2020 Facebook */
#include "bench.h"
/* COUNT-GLOBAL benchmark */
static struct count_global_ctx {
struct counter hits;
} count_global_ctx;
static void *count_global_producer(void *input)
{
struct count_global_ctx *ctx = &count_global_ctx;
while (true) {
atomic_inc(&ctx->hits.value);
}
return NULL;
}
static void count_global_measure(struct bench_res *res)
{
struct count_global_ctx *ctx = &count_global_ctx;
res->hits = atomic_swap(&ctx->hits.value, 0);
}
/* COUNT-local benchmark */
static struct count_local_ctx {
struct counter *hits;
} count_local_ctx;
static void count_local_setup(void)
{
struct count_local_ctx *ctx = &count_local_ctx;
ctx->hits = calloc(env.producer_cnt, sizeof(*ctx->hits));
if (!ctx->hits)
exit(1);
}
static void *count_local_producer(void *input)
{
struct count_local_ctx *ctx = &count_local_ctx;
int idx = (long)input;
while (true) {
atomic_inc(&ctx->hits[idx].value);
}
return NULL;
}
static void count_local_measure(struct bench_res *res)
{
struct count_local_ctx *ctx = &count_local_ctx;
int i;
for (i = 0; i < env.producer_cnt; i++) {
res->hits += atomic_swap(&ctx->hits[i].value, 0);
}
}
const struct bench bench_count_global = {
.name = "count-global",
.producer_thread = count_global_producer,
.measure = count_global_measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_count_local = {
.name = "count-local",
.setup = count_local_setup,
.producer_thread = count_local_producer,
.measure = count_local_measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
@@ -0,0 +1,381 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2023. Huawei Technologies Co., Ltd */
#include <argp.h>
#include <stdbool.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
#include <fcntl.h>
#include "bench.h"
#include "bpf_util.h"
#include "cgroup_helpers.h"
#include "htab_mem_bench.skel.h"
struct htab_mem_use_case {
const char *name;
const char **progs;
/* Do synchronization between addition thread and deletion thread */
bool need_sync;
};
static struct htab_mem_ctx {
const struct htab_mem_use_case *uc;
struct htab_mem_bench *skel;
pthread_barrier_t *notify;
int fd;
} ctx;
const char *ow_progs[] = {"overwrite", NULL};
const char *batch_progs[] = {"batch_add_batch_del", NULL};
const char *add_del_progs[] = {"add_only", "del_only", NULL};
const static struct htab_mem_use_case use_cases[] = {
{ .name = "overwrite", .progs = ow_progs },
{ .name = "batch_add_batch_del", .progs = batch_progs },
{ .name = "add_del_on_diff_cpu", .progs = add_del_progs, .need_sync = true },
};
static struct htab_mem_args {
u32 value_size;
const char *use_case;
bool preallocated;
} args = {
.value_size = 8,
.use_case = "overwrite",
.preallocated = false,
};
enum {
ARG_VALUE_SIZE = 10000,
ARG_USE_CASE = 10001,
ARG_PREALLOCATED = 10002,
};
static const struct argp_option opts[] = {
{ "value-size", ARG_VALUE_SIZE, "VALUE_SIZE", 0,
"Set the value size of hash map (default 8)" },
{ "use-case", ARG_USE_CASE, "USE_CASE", 0,
"Set the use case of hash map: overwrite|batch_add_batch_del|add_del_on_diff_cpu" },
{ "preallocated", ARG_PREALLOCATED, NULL, 0, "use preallocated hash map" },
{},
};
static error_t htab_mem_parse_arg(int key, char *arg, struct argp_state *state)
{
switch (key) {
case ARG_VALUE_SIZE:
args.value_size = strtoul(arg, NULL, 10);
if (args.value_size > 4096) {
fprintf(stderr, "too big value size %u\n", args.value_size);
argp_usage(state);
}
break;
case ARG_USE_CASE:
args.use_case = strdup(arg);
if (!args.use_case) {
fprintf(stderr, "no mem for use-case\n");
argp_usage(state);
}
break;
case ARG_PREALLOCATED:
args.preallocated = true;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
const struct argp bench_htab_mem_argp = {
.options = opts,
.parser = htab_mem_parse_arg,
};
static void htab_mem_validate(void)
{
if (!strcmp(use_cases[2].name, args.use_case) && env.producer_cnt % 2) {
fprintf(stderr, "%s needs an even number of producers\n", args.use_case);
exit(1);
}
}
static int htab_mem_bench_init_barriers(void)
{
pthread_barrier_t *barriers;
unsigned int i, nr;
if (!ctx.uc->need_sync)
return 0;
nr = (env.producer_cnt + 1) / 2;
barriers = calloc(nr, sizeof(*barriers));
if (!barriers)
return -1;
/* Used for synchronization between two threads */
for (i = 0; i < nr; i++)
pthread_barrier_init(&barriers[i], NULL, 2);
ctx.notify = barriers;
return 0;
}
static void htab_mem_bench_exit_barriers(void)
{
unsigned int i, nr;
if (!ctx.notify)
return;
nr = (env.producer_cnt + 1) / 2;
for (i = 0; i < nr; i++)
pthread_barrier_destroy(&ctx.notify[i]);
free(ctx.notify);
}
static const struct htab_mem_use_case *htab_mem_find_use_case_or_exit(const char *name)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(use_cases); i++) {
if (!strcmp(name, use_cases[i].name))
return &use_cases[i];
}
fprintf(stderr, "no such use-case: %s\n", name);
fprintf(stderr, "available use case:");
for (i = 0; i < ARRAY_SIZE(use_cases); i++)
fprintf(stderr, " %s", use_cases[i].name);
fprintf(stderr, "\n");
exit(1);
}
static void htab_mem_setup_impl(enum bpf_map_type map_type)
{
struct bpf_map *map;
const char **names;
int err;
setup_libbpf();
ctx.uc = htab_mem_find_use_case_or_exit(args.use_case);
err = htab_mem_bench_init_barriers();
if (err) {
fprintf(stderr, "failed to init barrier\n");
exit(1);
}
ctx.fd = cgroup_setup_and_join("/htab_mem");
if (ctx.fd < 0)
goto cleanup;
ctx.skel = htab_mem_bench__open();
if (!ctx.skel) {
fprintf(stderr, "failed to open skeleton\n");
goto cleanup;
}
map = ctx.skel->maps.htab;
bpf_map__set_type(map, map_type);
bpf_map__set_value_size(map, args.value_size);
/* Ensure that different CPUs can operate on different subset */
bpf_map__set_max_entries(map, MAX(8192, 64 * env.nr_cpus));
if (map_type != BPF_MAP_TYPE_RHASH && args.preallocated)
bpf_map__set_map_flags(map, bpf_map__map_flags(map) & ~BPF_F_NO_PREALLOC);
names = ctx.uc->progs;
while (*names) {
struct bpf_program *prog;
prog = bpf_object__find_program_by_name(ctx.skel->obj, *names);
if (!prog) {
fprintf(stderr, "no such program %s\n", *names);
goto cleanup;
}
bpf_program__set_autoload(prog, true);
names++;
}
ctx.skel->bss->nr_thread = env.producer_cnt;
err = htab_mem_bench__load(ctx.skel);
if (err) {
fprintf(stderr, "failed to load skeleton\n");
goto cleanup;
}
err = htab_mem_bench__attach(ctx.skel);
if (err) {
fprintf(stderr, "failed to attach skeleton\n");
goto cleanup;
}
return;
cleanup:
htab_mem_bench__destroy(ctx.skel);
htab_mem_bench_exit_barriers();
if (ctx.fd >= 0) {
close(ctx.fd);
cleanup_cgroup_environment();
}
exit(1);
}
static void htab_mem_setup(void)
{
htab_mem_setup_impl(BPF_MAP_TYPE_HASH);
}
static void rhtab_mem_setup(void)
{
htab_mem_setup_impl(BPF_MAP_TYPE_RHASH);
}
static void htab_mem_add_fn(pthread_barrier_t *notify)
{
while (true) {
/* Do addition */
(void)syscall(__NR_getpgid, 0);
/* Notify deletion thread to do deletion */
pthread_barrier_wait(notify);
/* Wait for deletion to complete */
pthread_barrier_wait(notify);
}
}
static void htab_mem_delete_fn(pthread_barrier_t *notify)
{
while (true) {
/* Wait for addition to complete */
pthread_barrier_wait(notify);
/* Do deletion */
(void)syscall(__NR_getppid);
/* Notify addition thread to do addition */
pthread_barrier_wait(notify);
}
}
static void *htab_mem_producer(void *arg)
{
pthread_barrier_t *notify;
int seq;
if (!ctx.uc->need_sync) {
while (true)
(void)syscall(__NR_getpgid, 0);
return NULL;
}
seq = (long)arg;
notify = &ctx.notify[seq / 2];
if (seq & 1)
htab_mem_delete_fn(notify);
else
htab_mem_add_fn(notify);
return NULL;
}
static void htab_mem_read_mem_cgrp_file(const char *name, unsigned long *value)
{
char buf[32];
ssize_t got;
int fd;
fd = openat(ctx.fd, name, O_RDONLY);
if (fd < 0) {
/* cgroup v1 ? */
fprintf(stderr, "no %s\n", name);
*value = 0;
return;
}
got = read(fd, buf, sizeof(buf) - 1);
close(fd);
if (got <= 0) {
*value = 0;
return;
}
buf[got] = 0;
*value = strtoull(buf, NULL, 0);
}
static void htab_mem_measure(struct bench_res *res)
{
res->hits = atomic_swap(&ctx.skel->bss->op_cnt, 0) / env.producer_cnt;
htab_mem_read_mem_cgrp_file("memory.current", &res->gp_ct);
}
static void htab_mem_report_progress(int iter, struct bench_res *res, long delta_ns)
{
double loop, mem;
loop = res->hits / 1000.0 / (delta_ns / 1000000000.0);
mem = res->gp_ct / 1048576.0;
printf("Iter %3d (%7.3lfus): ", iter, (delta_ns - 1000000000) / 1000.0);
printf("per-prod-op %7.2lfk/s, memory usage %7.2lfMiB\n", loop, mem);
}
static void htab_mem_report_final(struct bench_res res[], int res_cnt)
{
double mem_mean = 0.0, mem_stddev = 0.0;
double loop_mean = 0.0, loop_stddev = 0.0;
unsigned long peak_mem;
int i;
for (i = 0; i < res_cnt; i++) {
loop_mean += res[i].hits / 1000.0 / (0.0 + res_cnt);
mem_mean += res[i].gp_ct / 1048576.0 / (0.0 + res_cnt);
}
if (res_cnt > 1) {
for (i = 0; i < res_cnt; i++) {
loop_stddev += (loop_mean - res[i].hits / 1000.0) *
(loop_mean - res[i].hits / 1000.0) /
(res_cnt - 1.0);
mem_stddev += (mem_mean - res[i].gp_ct / 1048576.0) *
(mem_mean - res[i].gp_ct / 1048576.0) /
(res_cnt - 1.0);
}
loop_stddev = sqrt(loop_stddev);
mem_stddev = sqrt(mem_stddev);
}
htab_mem_read_mem_cgrp_file("memory.peak", &peak_mem);
printf("Summary: per-prod-op %7.2lf \u00B1 %7.2lfk/s, memory usage %7.2lf \u00B1 %7.2lfMiB,"
" peak memory usage %7.2lfMiB\n",
loop_mean, loop_stddev, mem_mean, mem_stddev, peak_mem / 1048576.0);
close(ctx.fd);
cleanup_cgroup_environment();
}
static void rhtab_mem_validate(void)
{
if (args.preallocated) {
fprintf(stderr, "rhash map does not support preallocation\n");
exit(1);
}
htab_mem_validate();
}
const struct bench bench_htab_mem = {
.name = "htab-mem",
.argp = &bench_htab_mem_argp,
.validate = htab_mem_validate,
.setup = htab_mem_setup,
.producer_thread = htab_mem_producer,
.measure = htab_mem_measure,
.report_progress = htab_mem_report_progress,
.report_final = htab_mem_report_final,
};
const struct bench bench_rhtab_mem = {
.name = "rhtab-mem",
.argp = &bench_htab_mem_argp,
.validate = rhtab_mem_validate,
.setup = rhtab_mem_setup,
.producer_thread = htab_mem_producer,
.measure = htab_mem_measure,
.report_progress = htab_mem_report_progress,
.report_final = htab_mem_report_final,
};
@@ -0,0 +1,282 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include <argp.h>
#include <linux/btf.h>
#include "local_storage_bench.skel.h"
#include "bench.h"
#include <test_btf.h>
static struct {
__u32 nr_maps;
__u32 hashmap_nr_keys_used;
} args = {
.nr_maps = 1000,
.hashmap_nr_keys_used = 1000,
};
enum {
ARG_NR_MAPS = 6000,
ARG_HASHMAP_NR_KEYS_USED = 6001,
};
static const struct argp_option opts[] = {
{ "nr_maps", ARG_NR_MAPS, "NR_MAPS", 0,
"Set number of local_storage maps"},
{ "hashmap_nr_keys_used", ARG_HASHMAP_NR_KEYS_USED, "NR_KEYS",
0, "When doing hashmap test, set number of hashmap keys test uses"},
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
long ret;
switch (key) {
case ARG_NR_MAPS:
ret = strtol(arg, NULL, 10);
if (ret < 1 || ret > UINT_MAX) {
fprintf(stderr, "invalid nr_maps");
argp_usage(state);
}
args.nr_maps = ret;
break;
case ARG_HASHMAP_NR_KEYS_USED:
ret = strtol(arg, NULL, 10);
if (ret < 1 || ret > UINT_MAX) {
fprintf(stderr, "invalid hashmap_nr_keys_used");
argp_usage(state);
}
args.hashmap_nr_keys_used = ret;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
const struct argp bench_local_storage_argp = {
.options = opts,
.parser = parse_arg,
};
/* Keep in sync w/ array of maps in bpf */
#define MAX_NR_MAPS 1000
/* keep in sync w/ same define in bpf */
#define HASHMAP_SZ 4194304
static void validate(void)
{
if (env.producer_cnt != 1) {
fprintf(stderr, "benchmark doesn't support multi-producer!\n");
exit(1);
}
if (env.consumer_cnt != 0) {
fprintf(stderr, "benchmark doesn't support consumer!\n");
exit(1);
}
if (args.nr_maps > MAX_NR_MAPS) {
fprintf(stderr, "nr_maps must be <= 1000\n");
exit(1);
}
if (args.hashmap_nr_keys_used > HASHMAP_SZ) {
fprintf(stderr, "hashmap_nr_keys_used must be <= %u\n", HASHMAP_SZ);
exit(1);
}
}
static struct {
struct local_storage_bench *skel;
void *bpf_obj;
struct bpf_map *array_of_maps;
} ctx;
static void prepopulate_hashmap(int fd)
{
int i, key, val;
/* local_storage gets will have BPF_LOCAL_STORAGE_GET_F_CREATE flag set, so
* populate the hashmap for a similar comparison
*/
for (i = 0; i < HASHMAP_SZ; i++) {
key = val = i;
if (bpf_map_update_elem(fd, &key, &val, 0)) {
fprintf(stderr, "Error prepopulating hashmap (key %d)\n", key);
exit(1);
}
}
}
static void __setup(struct bpf_program *prog, bool hashmap)
{
struct bpf_map *inner_map;
int i, fd, mim_fd, err;
LIBBPF_OPTS(bpf_map_create_opts, create_opts);
if (!hashmap)
create_opts.map_flags = BPF_F_NO_PREALLOC;
ctx.skel->rodata->num_maps = args.nr_maps;
ctx.skel->rodata->hashmap_num_keys = args.hashmap_nr_keys_used;
inner_map = bpf_map__inner_map(ctx.array_of_maps);
create_opts.btf_key_type_id = bpf_map__btf_key_type_id(inner_map);
create_opts.btf_value_type_id = bpf_map__btf_value_type_id(inner_map);
err = local_storage_bench__load(ctx.skel);
if (err) {
fprintf(stderr, "Error loading skeleton\n");
goto err_out;
}
create_opts.btf_fd = bpf_object__btf_fd(ctx.skel->obj);
mim_fd = bpf_map__fd(ctx.array_of_maps);
if (mim_fd < 0) {
fprintf(stderr, "Error getting map_in_map fd\n");
goto err_out;
}
for (i = 0; i < args.nr_maps; i++) {
if (hashmap)
fd = bpf_map_create(BPF_MAP_TYPE_HASH, NULL, sizeof(int),
sizeof(int), HASHMAP_SZ, &create_opts);
else
fd = bpf_map_create(BPF_MAP_TYPE_TASK_STORAGE, NULL, sizeof(int),
sizeof(int), 0, &create_opts);
if (fd < 0) {
fprintf(stderr, "Error creating map %d: %d\n", i, fd);
goto err_out;
}
if (hashmap)
prepopulate_hashmap(fd);
err = bpf_map_update_elem(mim_fd, &i, &fd, 0);
if (err) {
fprintf(stderr, "Error updating array-of-maps w/ map %d\n", i);
goto err_out;
}
}
if (!bpf_program__attach(prog)) {
fprintf(stderr, "Error attaching bpf program\n");
goto err_out;
}
return;
err_out:
exit(1);
}
static void hashmap_setup(void)
{
struct local_storage_bench *skel;
setup_libbpf();
skel = local_storage_bench__open();
ctx.skel = skel;
ctx.array_of_maps = skel->maps.array_of_hash_maps;
skel->rodata->use_hashmap = 1;
skel->rodata->interleave = 0;
__setup(skel->progs.get_local, true);
}
static void local_storage_cache_get_setup(void)
{
struct local_storage_bench *skel;
setup_libbpf();
skel = local_storage_bench__open();
ctx.skel = skel;
ctx.array_of_maps = skel->maps.array_of_local_storage_maps;
skel->rodata->use_hashmap = 0;
skel->rodata->interleave = 0;
__setup(skel->progs.get_local, false);
}
static void local_storage_cache_get_interleaved_setup(void)
{
struct local_storage_bench *skel;
setup_libbpf();
skel = local_storage_bench__open();
ctx.skel = skel;
ctx.array_of_maps = skel->maps.array_of_local_storage_maps;
skel->rodata->use_hashmap = 0;
skel->rodata->interleave = 1;
__setup(skel->progs.get_local, false);
}
static void measure(struct bench_res *res)
{
res->hits = atomic_swap(&ctx.skel->bss->hits, 0);
res->important_hits = atomic_swap(&ctx.skel->bss->important_hits, 0);
}
static inline void trigger_bpf_program(void)
{
syscall(__NR_getpgid);
}
static void *producer(void *input)
{
while (true)
trigger_bpf_program();
return NULL;
}
/* cache sequential and interleaved get benchs test local_storage get
* performance, specifically they demonstrate performance cliff of
* current list-plus-cache local_storage model.
*
* cache sequential get: call bpf_task_storage_get on n maps in order
* cache interleaved get: like "sequential get", but interleave 4 calls to the
* 'important' map (idx 0 in array_of_maps) for every 10 calls. Goal
* is to mimic environment where many progs are accessing their local_storage
* maps, with 'our' prog needing to access its map more often than others
*/
const struct bench bench_local_storage_cache_seq_get = {
.name = "local-storage-cache-seq-get",
.argp = &bench_local_storage_argp,
.validate = validate,
.setup = local_storage_cache_get_setup,
.producer_thread = producer,
.measure = measure,
.report_progress = local_storage_report_progress,
.report_final = local_storage_report_final,
};
const struct bench bench_local_storage_cache_interleaved_get = {
.name = "local-storage-cache-int-get",
.argp = &bench_local_storage_argp,
.validate = validate,
.setup = local_storage_cache_get_interleaved_setup,
.producer_thread = producer,
.measure = measure,
.report_progress = local_storage_report_progress,
.report_final = local_storage_report_final,
};
const struct bench bench_local_storage_cache_hashmap_control = {
.name = "local-storage-cache-hashmap-control",
.argp = &bench_local_storage_argp,
.validate = validate,
.setup = hashmap_setup,
.producer_thread = producer,
.measure = measure,
.report_progress = local_storage_report_progress,
.report_final = local_storage_report_final,
};
@@ -0,0 +1,249 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>
#include <argp.h>
#include "bench.h"
#include "bench_local_storage_create.skel.h"
struct thread {
int *fds;
pthread_t *pthds;
int *pthd_results;
};
static struct bench_local_storage_create *skel;
static struct thread *threads;
static long create_owner_errs;
static int storage_type = BPF_MAP_TYPE_SK_STORAGE;
static int batch_sz = 32;
enum {
ARG_BATCH_SZ = 9000,
ARG_STORAGE_TYPE = 9001,
};
static const struct argp_option opts[] = {
{ "batch-size", ARG_BATCH_SZ, "BATCH_SIZE", 0,
"The number of storage creations in each batch" },
{ "storage-type", ARG_STORAGE_TYPE, "STORAGE_TYPE", 0,
"The type of local storage to test (socket or task)" },
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
int ret;
switch (key) {
case ARG_BATCH_SZ:
ret = atoi(arg);
if (ret < 1) {
fprintf(stderr, "invalid batch-size\n");
argp_usage(state);
}
batch_sz = ret;
break;
case ARG_STORAGE_TYPE:
if (!strcmp(arg, "task")) {
storage_type = BPF_MAP_TYPE_TASK_STORAGE;
} else if (!strcmp(arg, "socket")) {
storage_type = BPF_MAP_TYPE_SK_STORAGE;
} else {
fprintf(stderr, "invalid storage-type (socket or task)\n");
argp_usage(state);
}
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
const struct argp bench_local_storage_create_argp = {
.options = opts,
.parser = parse_arg,
};
static void validate(void)
{
if (env.consumer_cnt != 0) {
fprintf(stderr,
"local-storage-create benchmark does not need consumer\n");
exit(1);
}
}
static void setup(void)
{
int i;
skel = bench_local_storage_create__open_and_load();
if (!skel) {
fprintf(stderr, "error loading skel\n");
exit(1);
}
skel->bss->bench_pid = getpid();
if (storage_type == BPF_MAP_TYPE_SK_STORAGE) {
if (!bpf_program__attach(skel->progs.socket_post_create)) {
fprintf(stderr, "Error attaching bpf program\n");
exit(1);
}
} else {
if (!bpf_program__attach(skel->progs.sched_process_fork)) {
fprintf(stderr, "Error attaching bpf program\n");
exit(1);
}
}
threads = calloc(env.producer_cnt, sizeof(*threads));
if (!threads) {
fprintf(stderr, "cannot alloc thread_res\n");
exit(1);
}
for (i = 0; i < env.producer_cnt; i++) {
struct thread *t = &threads[i];
if (storage_type == BPF_MAP_TYPE_SK_STORAGE) {
t->fds = malloc(batch_sz * sizeof(*t->fds));
if (!t->fds) {
fprintf(stderr, "cannot alloc t->fds\n");
exit(1);
}
} else {
t->pthds = malloc(batch_sz * sizeof(*t->pthds));
if (!t->pthds) {
fprintf(stderr, "cannot alloc t->pthds\n");
exit(1);
}
t->pthd_results = malloc(batch_sz * sizeof(*t->pthd_results));
if (!t->pthd_results) {
fprintf(stderr, "cannot alloc t->pthd_results\n");
exit(1);
}
}
}
}
static void measure(struct bench_res *res)
{
res->hits = atomic_swap(&skel->bss->create_cnts, 0);
}
static void *sk_producer(void *input)
{
struct thread *t = &threads[(long)(input)];
int *fds = t->fds;
int i;
while (true) {
for (i = 0; i < batch_sz; i++) {
fds[i] = socket(AF_INET6, SOCK_DGRAM, 0);
if (fds[i] == -1)
atomic_inc(&create_owner_errs);
}
for (i = 0; i < batch_sz; i++) {
if (fds[i] != -1)
close(fds[i]);
}
}
return NULL;
}
static void *thread_func(void *arg)
{
return NULL;
}
static void *task_producer(void *input)
{
struct thread *t = &threads[(long)(input)];
pthread_t *pthds = t->pthds;
int *pthd_results = t->pthd_results;
int i;
while (true) {
for (i = 0; i < batch_sz; i++) {
pthd_results[i] = pthread_create(&pthds[i], NULL, thread_func, NULL);
if (pthd_results[i])
atomic_inc(&create_owner_errs);
}
for (i = 0; i < batch_sz; i++) {
if (!pthd_results[i])
pthread_join(pthds[i], NULL);
}
}
return NULL;
}
static void *producer(void *input)
{
if (storage_type == BPF_MAP_TYPE_SK_STORAGE)
return sk_producer(input);
else
return task_producer(input);
}
static void report_progress(int iter, struct bench_res *res, long delta_ns)
{
double creates_per_sec;
creates_per_sec = res->hits / 1000.0 / (delta_ns / 1000000000.0);
printf("Iter %3d (%7.3lfus): ",
iter, (delta_ns - 1000000000) / 1000.0);
printf("creates %8.3lfk/s (%7.3lfk/prod)\n",
creates_per_sec, creates_per_sec / env.producer_cnt);
}
static void report_final(struct bench_res res[], int res_cnt)
{
double creates_mean = 0.0, creates_stddev = 0.0;
long total_creates = 0;
int i;
for (i = 0; i < res_cnt; i++) {
creates_mean += res[i].hits / 1000.0 / (0.0 + res_cnt);
total_creates += res[i].hits;
}
if (res_cnt > 1) {
for (i = 0; i < res_cnt; i++)
creates_stddev += (creates_mean - res[i].hits / 1000.0) *
(creates_mean - res[i].hits / 1000.0) /
(res_cnt - 1.0);
creates_stddev = sqrt(creates_stddev);
}
printf("Summary: creates %8.3lf \u00B1 %5.3lfk/s (%7.3lfk/prod), %ld total\n",
creates_mean, creates_stddev, creates_mean / env.producer_cnt,
total_creates);
if (create_owner_errs || skel->bss->create_errs)
printf("%s() errors %ld create_errs %ld\n",
storage_type == BPF_MAP_TYPE_SK_STORAGE ?
"socket" : "pthread_create",
create_owner_errs,
skel->bss->create_errs);
}
/* Benchmark performance of creating bpf local storage */
const struct bench bench_local_storage_create = {
.name = "local-storage-create",
.argp = &bench_local_storage_create_argp,
.validate = validate,
.setup = setup,
.producer_thread = producer,
.measure = measure,
.report_progress = report_progress,
.report_final = report_final,
};
@@ -0,0 +1,263 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Meta Platforms, Inc. and affiliates. */
#include <argp.h>
#include <sys/prctl.h>
#include "local_storage_rcu_tasks_trace_bench.skel.h"
#include "bench.h"
#include <signal.h>
static struct {
__u32 nr_procs;
__u32 kthread_pid;
} args = {
.nr_procs = 1000,
.kthread_pid = 0,
};
enum {
ARG_NR_PROCS = 7000,
ARG_KTHREAD_PID = 7001,
};
static const struct argp_option opts[] = {
{ "nr_procs", ARG_NR_PROCS, "NR_PROCS", 0,
"Set number of user processes to spin up"},
{ "kthread_pid", ARG_KTHREAD_PID, "PID", 0,
"Pid of rcu_tasks_trace kthread for ticks tracking"},
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
long ret;
switch (key) {
case ARG_NR_PROCS:
ret = strtol(arg, NULL, 10);
if (ret < 1 || ret > UINT_MAX) {
fprintf(stderr, "invalid nr_procs\n");
argp_usage(state);
}
args.nr_procs = ret;
break;
case ARG_KTHREAD_PID:
ret = strtol(arg, NULL, 10);
if (ret < 1) {
fprintf(stderr, "invalid kthread_pid\n");
argp_usage(state);
}
args.kthread_pid = ret;
break;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
const struct argp bench_local_storage_rcu_tasks_trace_argp = {
.options = opts,
.parser = parse_arg,
};
#define MAX_SLEEP_PROCS 150000
static void validate(void)
{
if (env.producer_cnt != 1) {
fprintf(stderr, "benchmark doesn't support multi-producer!\n");
exit(1);
}
if (env.consumer_cnt != 0) {
fprintf(stderr, "benchmark doesn't support consumer!\n");
exit(1);
}
if (args.nr_procs > MAX_SLEEP_PROCS) {
fprintf(stderr, "benchmark supports up to %u sleeper procs!\n",
MAX_SLEEP_PROCS);
exit(1);
}
}
static long kthread_pid_ticks(void)
{
char procfs_path[100];
long stime;
FILE *f;
if (!args.kthread_pid)
return -1;
sprintf(procfs_path, "/proc/%u/stat", args.kthread_pid);
f = fopen(procfs_path, "r");
if (!f) {
fprintf(stderr, "couldn't open %s, exiting\n", procfs_path);
goto err_out;
}
if (fscanf(f, "%*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %ld", &stime) != 1) {
fprintf(stderr, "fscanf of %s failed, exiting\n", procfs_path);
goto err_out;
}
fclose(f);
return stime;
err_out:
if (f)
fclose(f);
exit(1);
return 0;
}
static struct {
struct local_storage_rcu_tasks_trace_bench *skel;
long prev_kthread_stime;
} ctx;
static void sleep_and_loop(void)
{
while (true) {
sleep(rand() % 4);
syscall(__NR_getpgid);
}
}
static void local_storage_tasks_trace_setup(void)
{
int i, err, forkret, runner_pid;
runner_pid = getpid();
for (i = 0; i < args.nr_procs; i++) {
forkret = fork();
if (forkret < 0) {
fprintf(stderr, "Error forking sleeper proc %u of %u, exiting\n", i,
args.nr_procs);
goto err_out;
}
if (!forkret) {
err = prctl(PR_SET_PDEATHSIG, SIGKILL);
if (err < 0) {
fprintf(stderr, "prctl failed with err %d, exiting\n", errno);
goto err_out;
}
if (getppid() != runner_pid) {
fprintf(stderr, "Runner died while spinning up procs, exiting\n");
goto err_out;
}
sleep_and_loop();
}
}
printf("Spun up %u procs (our pid %d)\n", args.nr_procs, runner_pid);
setup_libbpf();
ctx.skel = local_storage_rcu_tasks_trace_bench__open_and_load();
if (!ctx.skel) {
fprintf(stderr, "Error doing open_and_load, exiting\n");
goto err_out;
}
ctx.prev_kthread_stime = kthread_pid_ticks();
if (!bpf_program__attach(ctx.skel->progs.get_local)) {
fprintf(stderr, "Error attaching bpf program\n");
goto err_out;
}
if (!bpf_program__attach(ctx.skel->progs.pregp_step)) {
fprintf(stderr, "Error attaching bpf program\n");
goto err_out;
}
if (!bpf_program__attach(ctx.skel->progs.postgp)) {
fprintf(stderr, "Error attaching bpf program\n");
goto err_out;
}
return;
err_out:
exit(1);
}
static void measure(struct bench_res *res)
{
long ticks;
res->gp_ct = atomic_swap(&ctx.skel->bss->gp_hits, 0);
res->gp_ns = atomic_swap(&ctx.skel->bss->gp_times, 0);
ticks = kthread_pid_ticks();
res->stime = ticks - ctx.prev_kthread_stime;
ctx.prev_kthread_stime = ticks;
}
static void *producer(void *input)
{
while (true)
syscall(__NR_getpgid);
return NULL;
}
static void report_progress(int iter, struct bench_res *res, long delta_ns)
{
if (ctx.skel->bss->unexpected) {
fprintf(stderr, "Error: Unexpected order of bpf prog calls (postgp after pregp).");
fprintf(stderr, "Data can't be trusted, exiting\n");
exit(1);
}
if (env.quiet)
return;
printf("Iter %d\t avg tasks_trace grace period latency\t%lf ns\n",
iter, res->gp_ns / (double)res->gp_ct);
printf("Iter %d\t avg ticks per tasks_trace grace period\t%lf\n",
iter, res->stime / (double)res->gp_ct);
}
static void report_final(struct bench_res res[], int res_cnt)
{
struct basic_stats gp_stat;
grace_period_latency_basic_stats(res, res_cnt, &gp_stat);
printf("SUMMARY tasks_trace grace period latency");
printf("\tavg %.3lf us\tstddev %.3lf us\n", gp_stat.mean, gp_stat.stddev);
grace_period_ticks_basic_stats(res, res_cnt, &gp_stat);
printf("SUMMARY ticks per tasks_trace grace period");
printf("\tavg %.3lf\tstddev %.3lf\n", gp_stat.mean, gp_stat.stddev);
}
/* local-storage-tasks-trace: Benchmark performance of BPF local_storage's use
* of RCU Tasks-Trace.
*
* Stress RCU Tasks Trace by forking many tasks, all of which do no work aside
* from sleep() loop, and creating/destroying BPF task-local storage on wakeup.
* The number of forked tasks is configurable.
*
* exercising code paths which call call_rcu_tasks_trace while there are many
* thousands of tasks on the system should result in RCU Tasks-Trace having to
* do a noticeable amount of work.
*
* This should be observable by measuring rcu_tasks_trace_kthread CPU usage
* after the grace period has ended, or by measuring grace period latency.
*
* This benchmark uses both approaches, attaching to rcu_tasks_trace_pregp_step
* and rcu_tasks_trace_postgp functions to measure grace period latency and
* using /proc/PID/stat to measure rcu_tasks_trace_kthread kernel ticks
*/
const struct bench bench_local_storage_tasks_trace = {
.name = "local-storage-tasks-trace",
.argp = &bench_local_storage_rcu_tasks_trace_argp,
.validate = validate,
.setup = local_storage_tasks_trace_setup,
.producer_thread = producer,
.measure = measure,
.report_progress = report_progress,
.report_final = report_final,
};
@@ -0,0 +1,555 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Cloudflare */
/*
* All of these benchmarks operate on tries with keys in the range
* [0, args.nr_entries), i.e. there are no gaps or partially filled
* branches of the trie for any key < args.nr_entries.
*
* This gives an idea of worst-case behaviour.
*/
#include <argp.h>
#include <linux/time64.h>
#include <linux/if_ether.h>
#include "lpm_trie_bench.skel.h"
#include "lpm_trie_map.skel.h"
#include "bench.h"
#include "testing_helpers.h"
#include "progs/lpm_trie.h"
static struct ctx {
struct lpm_trie_bench *bench;
} ctx;
static struct {
__u32 nr_entries;
__u32 prefixlen;
bool random;
} args = {
.nr_entries = 0,
.prefixlen = 32,
.random = false,
};
enum {
ARG_NR_ENTRIES = 9000,
ARG_PREFIX_LEN,
ARG_RANDOM,
};
static const struct argp_option opts[] = {
{ "nr_entries", ARG_NR_ENTRIES, "NR_ENTRIES", 0,
"Number of unique entries in the LPM trie" },
{ "prefix_len", ARG_PREFIX_LEN, "PREFIX_LEN", 0,
"Number of prefix bits to use in the LPM trie" },
{ "random", ARG_RANDOM, NULL, 0, "Access random keys during op" },
{},
};
static error_t lpm_parse_arg(int key, char *arg, struct argp_state *state)
{
long ret;
switch (key) {
case ARG_NR_ENTRIES:
ret = strtol(arg, NULL, 10);
if (ret < 1 || ret > UINT_MAX) {
fprintf(stderr, "Invalid nr_entries count.");
argp_usage(state);
}
args.nr_entries = ret;
break;
case ARG_PREFIX_LEN:
ret = strtol(arg, NULL, 10);
if (ret < 1 || ret > UINT_MAX) {
fprintf(stderr, "Invalid prefix_len value.");
argp_usage(state);
}
args.prefixlen = ret;
break;
case ARG_RANDOM:
args.random = true;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
const struct argp bench_lpm_trie_map_argp = {
.options = opts,
.parser = lpm_parse_arg,
};
static void validate_common(void)
{
if (env.consumer_cnt != 0) {
fprintf(stderr, "benchmark doesn't support consumer\n");
exit(1);
}
if (args.nr_entries == 0) {
fprintf(stderr, "Missing --nr_entries parameter\n");
exit(1);
}
if ((1UL << args.prefixlen) < args.nr_entries) {
fprintf(stderr, "prefix_len value too small for nr_entries\n");
exit(1);
}
}
static void lpm_insert_validate(void)
{
validate_common();
if (env.producer_cnt != 1) {
fprintf(stderr, "lpm-trie-insert requires a single producer\n");
exit(1);
}
if (args.random) {
fprintf(stderr, "lpm-trie-insert does not support --random\n");
exit(1);
}
}
static void lpm_delete_validate(void)
{
validate_common();
if (env.producer_cnt != 1) {
fprintf(stderr, "lpm-trie-delete requires a single producer\n");
exit(1);
}
if (args.random) {
fprintf(stderr, "lpm-trie-delete does not support --random\n");
exit(1);
}
}
static void lpm_free_validate(void)
{
validate_common();
if (env.producer_cnt != 1) {
fprintf(stderr, "lpm-trie-free requires a single producer\n");
exit(1);
}
if (args.random) {
fprintf(stderr, "lpm-trie-free does not support --random\n");
exit(1);
}
}
static struct trie_key *keys;
static __u32 *vals;
static void fill_map(int map_fd)
{
int err;
DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts,
.elem_flags = 0,
.flags = 0,
);
err = bpf_map_update_batch(map_fd, keys, vals, &args.nr_entries, &opts);
if (err) {
fprintf(stderr, "failed to batch update keys to map: %d\n",
-err);
exit(1);
}
}
static void empty_map(int map_fd)
{
int err;
DECLARE_LIBBPF_OPTS(bpf_map_batch_opts, opts,
.elem_flags = 0,
.flags = 0,
);
err = bpf_map_delete_batch(map_fd, keys, &args.nr_entries, &opts);
if (err) {
fprintf(stderr, "failed to batch delete keys for map: %d\n",
-err);
exit(1);
}
}
static void attach_prog(void)
{
int i;
ctx.bench = lpm_trie_bench__open_and_load();
if (!ctx.bench) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
ctx.bench->bss->nr_entries = args.nr_entries;
ctx.bench->bss->prefixlen = args.prefixlen;
ctx.bench->bss->random = args.random;
if (lpm_trie_bench__attach(ctx.bench)) {
fprintf(stderr, "failed to attach skeleton\n");
exit(1);
}
keys = calloc(args.nr_entries, sizeof(*keys));
vals = calloc(args.nr_entries, sizeof(*vals));
for (i = 0; i < args.nr_entries; i++) {
struct trie_key *k = &keys[i];
__u32 *v = &vals[i];
k->prefixlen = args.prefixlen;
k->data = i;
*v = 1;
}
}
static void attach_prog_and_fill_map(void)
{
int fd;
attach_prog();
fd = bpf_map__fd(ctx.bench->maps.trie_map);
fill_map(fd);
}
static void lpm_noop_setup(void)
{
attach_prog();
ctx.bench->bss->op = LPM_OP_NOOP;
}
static void lpm_baseline_setup(void)
{
attach_prog();
ctx.bench->bss->op = LPM_OP_BASELINE;
}
static void lpm_lookup_setup(void)
{
attach_prog_and_fill_map();
ctx.bench->bss->op = LPM_OP_LOOKUP;
}
static void lpm_insert_setup(void)
{
attach_prog();
ctx.bench->bss->op = LPM_OP_INSERT;
}
static void lpm_update_setup(void)
{
attach_prog_and_fill_map();
ctx.bench->bss->op = LPM_OP_UPDATE;
}
static void lpm_delete_setup(void)
{
attach_prog_and_fill_map();
ctx.bench->bss->op = LPM_OP_DELETE;
}
static void lpm_free_setup(void)
{
attach_prog();
ctx.bench->bss->op = LPM_OP_FREE;
}
static void lpm_measure(struct bench_res *res)
{
res->hits = atomic_swap(&ctx.bench->bss->hits, 0);
res->duration_ns = atomic_swap(&ctx.bench->bss->duration_ns, 0);
}
static void bench_reinit_map(void)
{
int fd = bpf_map__fd(ctx.bench->maps.trie_map);
switch (ctx.bench->bss->op) {
case LPM_OP_INSERT:
/* trie_map needs to be emptied */
empty_map(fd);
break;
case LPM_OP_DELETE:
/* trie_map needs to be refilled */
fill_map(fd);
break;
default:
fprintf(stderr, "Unexpected REINIT return code for op %d\n",
ctx.bench->bss->op);
exit(1);
}
}
/* For NOOP, BASELINE, LOOKUP, INSERT, UPDATE, and DELETE */
static void *lpm_producer(void *unused __always_unused)
{
int err;
char in[ETH_HLEN]; /* unused */
LIBBPF_OPTS(bpf_test_run_opts, opts, .data_in = in,
.data_size_in = sizeof(in), .repeat = 1, );
while (true) {
int fd = bpf_program__fd(ctx.bench->progs.run_bench);
err = bpf_prog_test_run_opts(fd, &opts);
if (err) {
fprintf(stderr, "failed to run BPF prog: %d\n", err);
exit(1);
}
/* Check for kernel error code */
if ((int)opts.retval < 0) {
fprintf(stderr, "BPF prog returned error: %d\n",
opts.retval);
exit(1);
}
switch (opts.retval) {
case LPM_BENCH_SUCCESS:
break;
case LPM_BENCH_REINIT_MAP:
bench_reinit_map();
break;
default:
fprintf(stderr, "Unexpected BPF prog return code %d for op %d\n",
opts.retval, ctx.bench->bss->op);
exit(1);
}
}
return NULL;
}
static void *lpm_free_producer(void *unused __always_unused)
{
while (true) {
struct lpm_trie_map *skel;
skel = lpm_trie_map__open_and_load();
if (!skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
fill_map(bpf_map__fd(skel->maps.trie_free_map));
lpm_trie_map__destroy(skel);
}
return NULL;
}
/*
* The standard bench op_report_*() functions assume measurements are
* taken over a 1-second interval but operations that modify the map
* (INSERT, DELETE, and FREE) cannot run indefinitely without
* "resetting" the map to the initial state. Depending on the size of
* the map, this likely needs to happen before the 1-second timer fires.
*
* Calculate the fraction of a second over which the op measurement was
* taken (to ignore any time spent doing the reset) and report the
* throughput results per second.
*/
static void frac_second_report_progress(int iter, struct bench_res *res,
long delta_ns, double rate_divisor,
char rate)
{
double hits_per_sec, hits_per_prod;
hits_per_sec = res->hits / rate_divisor /
(res->duration_ns / (double)NSEC_PER_SEC);
hits_per_prod = hits_per_sec / env.producer_cnt;
printf("Iter %3d (%7.3lfus): ", iter,
(delta_ns - NSEC_PER_SEC) / 1000.0);
printf("hits %8.3lf%c/s (%7.3lf%c/prod)\n", hits_per_sec, rate,
hits_per_prod, rate);
}
static void frac_second_report_final(struct bench_res res[], int res_cnt,
double lat_divisor, double rate_divisor,
char rate, const char *unit)
{
double hits_mean = 0.0, hits_stddev = 0.0;
double latency = 0.0;
int i;
for (i = 0; i < res_cnt; i++) {
double val = res[i].hits / rate_divisor /
(res[i].duration_ns / (double)NSEC_PER_SEC);
hits_mean += val / (0.0 + res_cnt);
latency += res[i].duration_ns / res[i].hits / (0.0 + res_cnt);
}
if (res_cnt > 1) {
for (i = 0; i < res_cnt; i++) {
double val =
res[i].hits / rate_divisor /
(res[i].duration_ns / (double)NSEC_PER_SEC);
hits_stddev += (hits_mean - val) * (hits_mean - val) /
(res_cnt - 1.0);
}
hits_stddev = sqrt(hits_stddev);
}
printf("Summary: throughput %8.3lf \u00B1 %5.3lf %c ops/s (%7.3lf%c ops/prod), ",
hits_mean, hits_stddev, rate, hits_mean / env.producer_cnt,
rate);
printf("latency %8.3lf %s/op\n",
latency / lat_divisor / env.producer_cnt, unit);
}
static void insert_ops_report_progress(int iter, struct bench_res *res,
long delta_ns)
{
double rate_divisor = 1000000.0;
char rate = 'M';
frac_second_report_progress(iter, res, delta_ns, rate_divisor, rate);
}
static void delete_ops_report_progress(int iter, struct bench_res *res,
long delta_ns)
{
double rate_divisor = 1000000.0;
char rate = 'M';
frac_second_report_progress(iter, res, delta_ns, rate_divisor, rate);
}
static void free_ops_report_progress(int iter, struct bench_res *res,
long delta_ns)
{
double rate_divisor = 1000.0;
char rate = 'K';
frac_second_report_progress(iter, res, delta_ns, rate_divisor, rate);
}
static void insert_ops_report_final(struct bench_res res[], int res_cnt)
{
double lat_divisor = 1.0;
double rate_divisor = 1000000.0;
const char *unit = "ns";
char rate = 'M';
frac_second_report_final(res, res_cnt, lat_divisor, rate_divisor, rate,
unit);
}
static void delete_ops_report_final(struct bench_res res[], int res_cnt)
{
double lat_divisor = 1.0;
double rate_divisor = 1000000.0;
const char *unit = "ns";
char rate = 'M';
frac_second_report_final(res, res_cnt, lat_divisor, rate_divisor, rate,
unit);
}
static void free_ops_report_final(struct bench_res res[], int res_cnt)
{
double lat_divisor = 1000000.0;
double rate_divisor = 1000.0;
const char *unit = "ms";
char rate = 'K';
frac_second_report_final(res, res_cnt, lat_divisor, rate_divisor, rate,
unit);
}
/* noop bench measures harness-overhead */
const struct bench bench_lpm_trie_noop = {
.name = "lpm-trie-noop",
.argp = &bench_lpm_trie_map_argp,
.validate = validate_common,
.setup = lpm_noop_setup,
.producer_thread = lpm_producer,
.measure = lpm_measure,
.report_progress = ops_report_progress,
.report_final = ops_report_final,
};
/* baseline overhead for lookup and update */
const struct bench bench_lpm_trie_baseline = {
.name = "lpm-trie-baseline",
.argp = &bench_lpm_trie_map_argp,
.validate = validate_common,
.setup = lpm_baseline_setup,
.producer_thread = lpm_producer,
.measure = lpm_measure,
.report_progress = ops_report_progress,
.report_final = ops_report_final,
};
/* measure cost of doing a lookup on existing entries in a full trie */
const struct bench bench_lpm_trie_lookup = {
.name = "lpm-trie-lookup",
.argp = &bench_lpm_trie_map_argp,
.validate = validate_common,
.setup = lpm_lookup_setup,
.producer_thread = lpm_producer,
.measure = lpm_measure,
.report_progress = ops_report_progress,
.report_final = ops_report_final,
};
/* measure cost of inserting new entries into an empty trie */
const struct bench bench_lpm_trie_insert = {
.name = "lpm-trie-insert",
.argp = &bench_lpm_trie_map_argp,
.validate = lpm_insert_validate,
.setup = lpm_insert_setup,
.producer_thread = lpm_producer,
.measure = lpm_measure,
.report_progress = insert_ops_report_progress,
.report_final = insert_ops_report_final,
};
/* measure cost of updating existing entries in a full trie */
const struct bench bench_lpm_trie_update = {
.name = "lpm-trie-update",
.argp = &bench_lpm_trie_map_argp,
.validate = validate_common,
.setup = lpm_update_setup,
.producer_thread = lpm_producer,
.measure = lpm_measure,
.report_progress = ops_report_progress,
.report_final = ops_report_final,
};
/* measure cost of deleting existing entries from a full trie */
const struct bench bench_lpm_trie_delete = {
.name = "lpm-trie-delete",
.argp = &bench_lpm_trie_map_argp,
.validate = lpm_delete_validate,
.setup = lpm_delete_setup,
.producer_thread = lpm_producer,
.measure = lpm_measure,
.report_progress = delete_ops_report_progress,
.report_final = delete_ops_report_final,
};
/* measure cost of freeing a full trie */
const struct bench bench_lpm_trie_free = {
.name = "lpm-trie-free",
.argp = &bench_lpm_trie_map_argp,
.validate = lpm_free_validate,
.setup = lpm_free_setup,
.producer_thread = lpm_free_producer,
.measure = lpm_measure,
.report_progress = free_ops_report_progress,
.report_final = free_ops_report_final,
};
@@ -0,0 +1,167 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2020 Facebook */
#include <fcntl.h>
#include "bench.h"
#include "test_overhead.skel.h"
/* BPF triggering benchmarks */
static struct ctx {
struct test_overhead *skel;
struct counter hits;
int fd;
} ctx;
static void validate(void)
{
if (env.producer_cnt != 1) {
fprintf(stderr, "benchmark doesn't support multi-producer!\n");
exit(1);
}
if (env.consumer_cnt != 0) {
fprintf(stderr, "benchmark doesn't support consumer!\n");
exit(1);
}
}
static void *producer(void *input)
{
char buf[] = "test_overhead";
int err;
while (true) {
err = write(ctx.fd, buf, sizeof(buf));
if (err < 0) {
fprintf(stderr, "write failed\n");
exit(1);
}
atomic_inc(&ctx.hits.value);
}
}
static void measure(struct bench_res *res)
{
res->hits = atomic_swap(&ctx.hits.value, 0);
}
static void setup_ctx(void)
{
setup_libbpf();
ctx.skel = test_overhead__open_and_load();
if (!ctx.skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
ctx.fd = open("/proc/self/comm", O_WRONLY|O_TRUNC);
if (ctx.fd < 0) {
fprintf(stderr, "failed to open /proc/self/comm: %d\n", -errno);
exit(1);
}
}
static void attach_bpf(struct bpf_program *prog)
{
struct bpf_link *link;
link = bpf_program__attach(prog);
if (!link) {
fprintf(stderr, "failed to attach program!\n");
exit(1);
}
}
static void setup_base(void)
{
setup_ctx();
}
static void setup_kprobe(void)
{
setup_ctx();
attach_bpf(ctx.skel->progs.prog1);
}
static void setup_kretprobe(void)
{
setup_ctx();
attach_bpf(ctx.skel->progs.prog2);
}
static void setup_rawtp(void)
{
setup_ctx();
attach_bpf(ctx.skel->progs.prog3);
}
static void setup_fentry(void)
{
setup_ctx();
attach_bpf(ctx.skel->progs.prog4);
}
static void setup_fexit(void)
{
setup_ctx();
attach_bpf(ctx.skel->progs.prog5);
}
const struct bench bench_rename_base = {
.name = "rename-base",
.validate = validate,
.setup = setup_base,
.producer_thread = producer,
.measure = measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_rename_kprobe = {
.name = "rename-kprobe",
.validate = validate,
.setup = setup_kprobe,
.producer_thread = producer,
.measure = measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_rename_kretprobe = {
.name = "rename-kretprobe",
.validate = validate,
.setup = setup_kretprobe,
.producer_thread = producer,
.measure = measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_rename_rawtp = {
.name = "rename-rawtp",
.validate = validate,
.setup = setup_rawtp,
.producer_thread = producer,
.measure = measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_rename_fentry = {
.name = "rename-fentry",
.validate = validate,
.setup = setup_fentry,
.producer_thread = producer,
.measure = measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_rename_fexit = {
.name = "rename-fexit",
.validate = validate,
.setup = setup_fexit,
.producer_thread = producer,
.measure = measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
@@ -0,0 +1,619 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2020 Facebook */
#include <asm/barrier.h>
#include <linux/perf_event.h>
#include <linux/ring_buffer.h>
#include <sys/epoll.h>
#include <sys/mman.h>
#include <argp.h>
#include <stdlib.h>
#include "bench.h"
#include "ringbuf_bench.skel.h"
#include "perfbuf_bench.skel.h"
static struct {
bool back2back;
int batch_cnt;
bool sampled;
int sample_rate;
int ringbuf_sz; /* per-ringbuf, in bytes */
bool ringbuf_use_output; /* use slower output API */
int perfbuf_sz; /* per-CPU size, in pages */
bool overwrite;
bool bench_producer;
} args = {
.back2back = false,
.batch_cnt = 500,
.sampled = false,
.sample_rate = 500,
.ringbuf_sz = 512 * 1024,
.ringbuf_use_output = false,
.perfbuf_sz = 128,
.overwrite = false,
.bench_producer = false,
};
enum {
ARG_RB_BACK2BACK = 2000,
ARG_RB_USE_OUTPUT = 2001,
ARG_RB_BATCH_CNT = 2002,
ARG_RB_SAMPLED = 2003,
ARG_RB_SAMPLE_RATE = 2004,
ARG_RB_OVERWRITE = 2005,
ARG_RB_BENCH_PRODUCER = 2006,
};
static const struct argp_option opts[] = {
{ "rb-b2b", ARG_RB_BACK2BACK, NULL, 0, "Back-to-back mode"},
{ "rb-use-output", ARG_RB_USE_OUTPUT, NULL, 0, "Use bpf_ringbuf_output() instead of bpf_ringbuf_reserve()"},
{ "rb-batch-cnt", ARG_RB_BATCH_CNT, "CNT", 0, "Set BPF-side record batch count"},
{ "rb-sampled", ARG_RB_SAMPLED, NULL, 0, "Notification sampling"},
{ "rb-sample-rate", ARG_RB_SAMPLE_RATE, "RATE", 0, "Notification sample rate"},
{ "rb-overwrite", ARG_RB_OVERWRITE, NULL, 0, "Overwrite mode"},
{ "rb-bench-producer", ARG_RB_BENCH_PRODUCER, NULL, 0, "Benchmark producer"},
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
switch (key) {
case ARG_RB_BACK2BACK:
args.back2back = true;
break;
case ARG_RB_USE_OUTPUT:
args.ringbuf_use_output = true;
break;
case ARG_RB_BATCH_CNT:
args.batch_cnt = strtol(arg, NULL, 10);
if (args.batch_cnt < 0) {
fprintf(stderr, "Invalid batch count.");
argp_usage(state);
}
break;
case ARG_RB_SAMPLED:
args.sampled = true;
break;
case ARG_RB_SAMPLE_RATE:
args.sample_rate = strtol(arg, NULL, 10);
if (args.sample_rate < 0) {
fprintf(stderr, "Invalid perfbuf sample rate.");
argp_usage(state);
}
break;
case ARG_RB_OVERWRITE:
args.overwrite = true;
break;
case ARG_RB_BENCH_PRODUCER:
args.bench_producer = true;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
/* exported into benchmark runner */
const struct argp bench_ringbufs_argp = {
.options = opts,
.parser = parse_arg,
};
/* RINGBUF-LIBBPF benchmark */
static struct counter buf_hits;
static inline void bufs_trigger_batch(void)
{
(void)syscall(__NR_getpgid);
}
static void bufs_validate(void)
{
if (args.bench_producer && strcmp(env.bench_name, "rb-libbpf")) {
fprintf(stderr, "--rb-bench-producer only works with rb-libbpf!\n");
exit(1);
}
if (args.overwrite && !args.bench_producer) {
fprintf(stderr, "overwrite mode only works with --rb-bench-producer for now!\n");
exit(1);
}
if (args.bench_producer && env.consumer_cnt != 0) {
fprintf(stderr, "no consumer is needed for --rb-bench-producer!\n");
exit(1);
}
if (args.bench_producer && args.back2back) {
fprintf(stderr, "back-to-back mode makes no sense for --rb-bench-producer!\n");
exit(1);
}
if (args.bench_producer && args.sampled) {
fprintf(stderr, "sampling mode makes no sense for --rb-bench-producer!\n");
exit(1);
}
if (!args.bench_producer && env.consumer_cnt != 1) {
fprintf(stderr, "benchmarks without --rb-bench-producer require exactly one consumer!\n");
exit(1);
}
if (args.back2back && env.producer_cnt > 1) {
fprintf(stderr, "back-to-back mode makes sense only for single-producer case!\n");
exit(1);
}
}
static void *bufs_sample_producer(void *input)
{
if (args.back2back) {
/* initial batch to get everything started */
bufs_trigger_batch();
return NULL;
}
while (true)
bufs_trigger_batch();
return NULL;
}
static struct ringbuf_libbpf_ctx {
struct ringbuf_bench *skel;
struct ring_buffer *ringbuf;
} ringbuf_libbpf_ctx;
static void ringbuf_libbpf_measure(struct bench_res *res)
{
struct ringbuf_libbpf_ctx *ctx = &ringbuf_libbpf_ctx;
if (args.bench_producer)
res->hits = atomic_swap(&ctx->skel->bss->hits, 0);
else
res->hits = atomic_swap(&buf_hits.value, 0);
res->drops = atomic_swap(&ctx->skel->bss->dropped, 0);
}
static struct ringbuf_bench *ringbuf_setup_skeleton(void)
{
__u32 flags;
struct bpf_map *ringbuf;
struct ringbuf_bench *skel;
setup_libbpf();
skel = ringbuf_bench__open();
if (!skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
skel->rodata->batch_cnt = args.batch_cnt;
skel->rodata->use_output = args.ringbuf_use_output ? 1 : 0;
skel->rodata->bench_producer = args.bench_producer;
if (args.sampled)
/* record data + header take 16 bytes */
skel->rodata->wakeup_data_size = args.sample_rate * 16;
ringbuf = skel->maps.ringbuf;
if (args.overwrite) {
flags = bpf_map__map_flags(ringbuf) | BPF_F_RB_OVERWRITE;
bpf_map__set_map_flags(ringbuf, flags);
}
bpf_map__set_max_entries(ringbuf, args.ringbuf_sz);
if (ringbuf_bench__load(skel)) {
fprintf(stderr, "failed to load skeleton\n");
exit(1);
}
return skel;
}
static int buf_process_sample(void *ctx, void *data, size_t len)
{
atomic_inc(&buf_hits.value);
return 0;
}
static void ringbuf_libbpf_setup(void)
{
struct ringbuf_libbpf_ctx *ctx = &ringbuf_libbpf_ctx;
struct bpf_link *link;
int map_fd;
ctx->skel = ringbuf_setup_skeleton();
map_fd = bpf_map__fd(ctx->skel->maps.ringbuf);
ctx->ringbuf = ring_buffer__new(map_fd, buf_process_sample, NULL, NULL);
if (!ctx->ringbuf) {
fprintf(stderr, "failed to create ringbuf\n");
exit(1);
}
link = bpf_program__attach(ctx->skel->progs.bench_ringbuf);
if (!link) {
fprintf(stderr, "failed to attach program!\n");
exit(1);
}
}
static void *ringbuf_libbpf_consumer(void *input)
{
struct ringbuf_libbpf_ctx *ctx = &ringbuf_libbpf_ctx;
while (ring_buffer__poll(ctx->ringbuf, -1) >= 0) {
if (args.back2back)
bufs_trigger_batch();
}
fprintf(stderr, "ringbuf polling failed!\n");
return NULL;
}
/* RINGBUF-CUSTOM benchmark */
struct ringbuf_custom {
__u64 *consumer_pos;
__u64 *producer_pos;
__u64 mask;
void *data;
int map_fd;
};
static struct ringbuf_custom_ctx {
struct ringbuf_bench *skel;
struct ringbuf_custom ringbuf;
int epoll_fd;
struct epoll_event event;
} ringbuf_custom_ctx;
static void ringbuf_custom_measure(struct bench_res *res)
{
struct ringbuf_custom_ctx *ctx = &ringbuf_custom_ctx;
res->hits = atomic_swap(&buf_hits.value, 0);
res->drops = atomic_swap(&ctx->skel->bss->dropped, 0);
}
static void ringbuf_custom_setup(void)
{
struct ringbuf_custom_ctx *ctx = &ringbuf_custom_ctx;
const size_t page_size = getpagesize();
struct bpf_link *link;
struct ringbuf_custom *r;
void *tmp;
int err;
ctx->skel = ringbuf_setup_skeleton();
ctx->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
if (ctx->epoll_fd < 0) {
fprintf(stderr, "failed to create epoll fd: %d\n", -errno);
exit(1);
}
r = &ctx->ringbuf;
r->map_fd = bpf_map__fd(ctx->skel->maps.ringbuf);
r->mask = args.ringbuf_sz - 1;
/* Map writable consumer page */
tmp = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED,
r->map_fd, 0);
if (tmp == MAP_FAILED) {
fprintf(stderr, "failed to mmap consumer page: %d\n", -errno);
exit(1);
}
r->consumer_pos = tmp;
/* Map read-only producer page and data pages. */
tmp = mmap(NULL, page_size + 2 * args.ringbuf_sz, PROT_READ, MAP_SHARED,
r->map_fd, page_size);
if (tmp == MAP_FAILED) {
fprintf(stderr, "failed to mmap data pages: %d\n", -errno);
exit(1);
}
r->producer_pos = tmp;
r->data = tmp + page_size;
ctx->event.events = EPOLLIN;
err = epoll_ctl(ctx->epoll_fd, EPOLL_CTL_ADD, r->map_fd, &ctx->event);
if (err < 0) {
fprintf(stderr, "failed to epoll add ringbuf: %d\n", -errno);
exit(1);
}
link = bpf_program__attach(ctx->skel->progs.bench_ringbuf);
if (!link) {
fprintf(stderr, "failed to attach program\n");
exit(1);
}
}
#define RINGBUF_BUSY_BIT (1 << 31)
#define RINGBUF_DISCARD_BIT (1 << 30)
#define RINGBUF_META_LEN 8
static inline int roundup_len(__u32 len)
{
/* clear out top 2 bits */
len <<= 2;
len >>= 2;
/* add length prefix */
len += RINGBUF_META_LEN;
/* round up to 8 byte alignment */
return (len + 7) / 8 * 8;
}
static void ringbuf_custom_process_ring(struct ringbuf_custom *r)
{
unsigned long cons_pos, prod_pos;
int *len_ptr, len;
bool got_new_data;
cons_pos = smp_load_acquire(r->consumer_pos);
while (true) {
got_new_data = false;
prod_pos = smp_load_acquire(r->producer_pos);
while (cons_pos < prod_pos) {
len_ptr = r->data + (cons_pos & r->mask);
len = smp_load_acquire(len_ptr);
/* sample not committed yet, bail out for now */
if (len & RINGBUF_BUSY_BIT)
return;
got_new_data = true;
cons_pos += roundup_len(len);
atomic_inc(&buf_hits.value);
}
if (got_new_data)
smp_store_release(r->consumer_pos, cons_pos);
else
break;
}
}
static void *ringbuf_custom_consumer(void *input)
{
struct ringbuf_custom_ctx *ctx = &ringbuf_custom_ctx;
int cnt;
do {
if (args.back2back)
bufs_trigger_batch();
cnt = epoll_wait(ctx->epoll_fd, &ctx->event, 1, -1);
if (cnt > 0)
ringbuf_custom_process_ring(&ctx->ringbuf);
} while (cnt >= 0);
fprintf(stderr, "ringbuf polling failed!\n");
return 0;
}
/* PERFBUF-LIBBPF benchmark */
static struct perfbuf_libbpf_ctx {
struct perfbuf_bench *skel;
struct perf_buffer *perfbuf;
} perfbuf_libbpf_ctx;
static void perfbuf_measure(struct bench_res *res)
{
struct perfbuf_libbpf_ctx *ctx = &perfbuf_libbpf_ctx;
res->hits = atomic_swap(&buf_hits.value, 0);
res->drops = atomic_swap(&ctx->skel->bss->dropped, 0);
}
static struct perfbuf_bench *perfbuf_setup_skeleton(void)
{
struct perfbuf_bench *skel;
setup_libbpf();
skel = perfbuf_bench__open();
if (!skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
skel->rodata->batch_cnt = args.batch_cnt;
if (perfbuf_bench__load(skel)) {
fprintf(stderr, "failed to load skeleton\n");
exit(1);
}
return skel;
}
static enum bpf_perf_event_ret
perfbuf_process_sample_raw(void *input_ctx, int cpu,
struct perf_event_header *e)
{
switch (e->type) {
case PERF_RECORD_SAMPLE:
atomic_inc(&buf_hits.value);
break;
case PERF_RECORD_LOST:
break;
default:
return LIBBPF_PERF_EVENT_ERROR;
}
return LIBBPF_PERF_EVENT_CONT;
}
static void perfbuf_libbpf_setup(void)
{
struct perfbuf_libbpf_ctx *ctx = &perfbuf_libbpf_ctx;
struct perf_event_attr attr;
struct bpf_link *link;
ctx->skel = perfbuf_setup_skeleton();
memset(&attr, 0, sizeof(attr));
attr.config = PERF_COUNT_SW_BPF_OUTPUT;
attr.type = PERF_TYPE_SOFTWARE;
attr.sample_type = PERF_SAMPLE_RAW;
/* notify only every Nth sample */
if (args.sampled) {
attr.sample_period = args.sample_rate;
attr.wakeup_events = args.sample_rate;
} else {
attr.sample_period = 1;
attr.wakeup_events = 1;
}
if (args.sample_rate > args.batch_cnt) {
fprintf(stderr, "sample rate %d is too high for given batch count %d\n",
args.sample_rate, args.batch_cnt);
exit(1);
}
ctx->perfbuf = perf_buffer__new_raw(bpf_map__fd(ctx->skel->maps.perfbuf),
args.perfbuf_sz, &attr,
perfbuf_process_sample_raw, NULL, NULL);
if (!ctx->perfbuf) {
fprintf(stderr, "failed to create perfbuf\n");
exit(1);
}
link = bpf_program__attach(ctx->skel->progs.bench_perfbuf);
if (!link) {
fprintf(stderr, "failed to attach program\n");
exit(1);
}
}
static void *perfbuf_libbpf_consumer(void *input)
{
struct perfbuf_libbpf_ctx *ctx = &perfbuf_libbpf_ctx;
while (perf_buffer__poll(ctx->perfbuf, -1) >= 0) {
if (args.back2back)
bufs_trigger_batch();
}
fprintf(stderr, "perfbuf polling failed!\n");
return NULL;
}
/* PERFBUF-CUSTOM benchmark */
/* copies of internal libbpf definitions */
struct perf_cpu_buf {
struct perf_buffer *pb;
void *base; /* mmap()'ed memory */
void *buf; /* for reconstructing segmented data */
size_t buf_size;
int fd;
int cpu;
int map_key;
};
struct perf_buffer {
perf_buffer_event_fn event_cb;
perf_buffer_sample_fn sample_cb;
perf_buffer_lost_fn lost_cb;
void *ctx; /* passed into callbacks */
size_t page_size;
size_t mmap_size;
struct perf_cpu_buf **cpu_bufs;
struct epoll_event *events;
int cpu_cnt; /* number of allocated CPU buffers */
int epoll_fd; /* perf event FD */
int map_fd; /* BPF_MAP_TYPE_PERF_EVENT_ARRAY BPF map FD */
};
static void *perfbuf_custom_consumer(void *input)
{
struct perfbuf_libbpf_ctx *ctx = &perfbuf_libbpf_ctx;
struct perf_buffer *pb = ctx->perfbuf;
struct perf_cpu_buf *cpu_buf;
struct perf_event_mmap_page *header;
size_t mmap_mask = pb->mmap_size - 1;
struct perf_event_header *ehdr;
__u64 data_head, data_tail;
size_t ehdr_size;
void *base;
int i, cnt;
while (true) {
if (args.back2back)
bufs_trigger_batch();
cnt = epoll_wait(pb->epoll_fd, pb->events, pb->cpu_cnt, -1);
if (cnt <= 0) {
fprintf(stderr, "perf epoll failed: %d\n", -errno);
exit(1);
}
for (i = 0; i < cnt; ++i) {
cpu_buf = pb->events[i].data.ptr;
header = cpu_buf->base;
base = ((void *)header) + pb->page_size;
data_head = ring_buffer_read_head(header);
data_tail = header->data_tail;
while (data_head != data_tail) {
ehdr = base + (data_tail & mmap_mask);
ehdr_size = ehdr->size;
if (ehdr->type == PERF_RECORD_SAMPLE)
atomic_inc(&buf_hits.value);
data_tail += ehdr_size;
}
ring_buffer_write_tail(header, data_tail);
}
}
return NULL;
}
const struct bench bench_rb_libbpf = {
.name = "rb-libbpf",
.argp = &bench_ringbufs_argp,
.validate = bufs_validate,
.setup = ringbuf_libbpf_setup,
.producer_thread = bufs_sample_producer,
.consumer_thread = ringbuf_libbpf_consumer,
.measure = ringbuf_libbpf_measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_rb_custom = {
.name = "rb-custom",
.argp = &bench_ringbufs_argp,
.validate = bufs_validate,
.setup = ringbuf_custom_setup,
.producer_thread = bufs_sample_producer,
.consumer_thread = ringbuf_custom_consumer,
.measure = ringbuf_custom_measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_pb_libbpf = {
.name = "pb-libbpf",
.argp = &bench_ringbufs_argp,
.validate = bufs_validate,
.setup = perfbuf_libbpf_setup,
.producer_thread = bufs_sample_producer,
.consumer_thread = perfbuf_libbpf_consumer,
.measure = perfbuf_measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_pb_custom = {
.name = "pb-custom",
.argp = &bench_ringbufs_argp,
.validate = bufs_validate,
.setup = perfbuf_libbpf_setup,
.producer_thread = bufs_sample_producer,
.consumer_thread = perfbuf_custom_consumer,
.measure = perfbuf_measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
@@ -0,0 +1,599 @@
// SPDX-License-Identifier: GPL-2.0
#include <error.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/sendfile.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <argp.h>
#include "bench.h"
#include "bench_sockmap_prog.skel.h"
#include "bpf_util.h"
#define FILE_SIZE (128 * 1024)
#define DATA_REPEAT_SIZE 10
static const char snd_data[DATA_REPEAT_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
/* c1 <-> [p1, p2] <-> c2
* RX bench(BPF_SK_SKB_STREAM_VERDICT):
* ARG_FW_RX_PASS:
* send(p2) -> recv(c2) -> bpf skb passthrough -> recv(c2)
* ARG_FW_RX_VERDICT_EGRESS:
* send(c1) -> verdict skb to tx queuec of p2 -> recv(c2)
* ARG_FW_RX_VERDICT_INGRESS:
* send(c1) -> verdict skb to rx queuec of c2 -> recv(c2)
*
* TX bench(BPF_SK_MSG_VERDIC):
* ARG_FW_TX_PASS:
* send(p2) -> bpf msg passthrough -> send(p2) -> recv(c2)
* ARG_FW_TX_VERDICT_INGRESS:
* send(p2) -> verdict msg to rx queue of c2 -> recv(c2)
* ARG_FW_TX_VERDICT_EGRESS:
* send(p1) -> verdict msg to tx queue of p2 -> recv(c2)
*/
enum SOCKMAP_ARG_FLAG {
ARG_FW_RX_NORMAL = 11000,
ARG_FW_RX_PASS,
ARG_FW_RX_VERDICT_EGRESS,
ARG_FW_RX_VERDICT_INGRESS,
ARG_FW_TX_NORMAL,
ARG_FW_TX_PASS,
ARG_FW_TX_VERDICT_INGRESS,
ARG_FW_TX_VERDICT_EGRESS,
ARG_CTL_RX_STRP,
ARG_CONSUMER_DELAY_TIME,
ARG_PRODUCER_DURATION,
};
#define TXMODE_NORMAL() \
((ctx.mode) == ARG_FW_TX_NORMAL)
#define TXMODE_BPF_INGRESS() \
((ctx.mode) == ARG_FW_TX_VERDICT_INGRESS)
#define TXMODE_BPF_EGRESS() \
((ctx.mode) == ARG_FW_TX_VERDICT_EGRESS)
#define TXMODE_BPF_PASS() \
((ctx.mode) == ARG_FW_TX_PASS)
#define TXMODE_BPF() ( \
TXMODE_BPF_PASS() || \
TXMODE_BPF_INGRESS() || \
TXMODE_BPF_EGRESS())
#define TXMODE() ( \
TXMODE_NORMAL() || \
TXMODE_BPF())
#define RXMODE_NORMAL() \
((ctx.mode) == ARG_FW_RX_NORMAL)
#define RXMODE_BPF_PASS() \
((ctx.mode) == ARG_FW_RX_PASS)
#define RXMODE_BPF_VERDICT_EGRESS() \
((ctx.mode) == ARG_FW_RX_VERDICT_EGRESS)
#define RXMODE_BPF_VERDICT_INGRESS() \
((ctx.mode) == ARG_FW_RX_VERDICT_INGRESS)
#define RXMODE_BPF_VERDICT() ( \
RXMODE_BPF_VERDICT_INGRESS() || \
RXMODE_BPF_VERDICT_EGRESS())
#define RXMODE_BPF() ( \
RXMODE_BPF_PASS() || \
RXMODE_BPF_VERDICT())
#define RXMODE() ( \
RXMODE_NORMAL() || \
RXMODE_BPF())
static struct socmap_ctx {
struct bench_sockmap_prog *skel;
enum SOCKMAP_ARG_FLAG mode;
#define c1 fds[0]
#define p1 fds[1]
#define c2 fds[2]
#define p2 fds[3]
#define sfd fds[4]
int fds[5];
long send_calls;
long read_calls;
long prod_send;
long user_read;
int file_size;
int delay_consumer;
int prod_run_time;
int strp_size;
} ctx = {
.prod_send = 0,
.user_read = 0,
.file_size = FILE_SIZE,
.mode = ARG_FW_RX_VERDICT_EGRESS,
.fds = {0},
.delay_consumer = 0,
.prod_run_time = 0,
.strp_size = 0,
};
static void bench_sockmap_prog_destroy(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(ctx.fds); i++) {
if (ctx.fds[i] > 0)
close(ctx.fds[i]);
}
bench_sockmap_prog__destroy(ctx.skel);
}
static void init_addr(struct sockaddr_storage *ss,
socklen_t *len)
{
struct sockaddr_in *addr4 = memset(ss, 0, sizeof(*ss));
addr4->sin_family = AF_INET;
addr4->sin_port = 0;
addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
*len = sizeof(*addr4);
}
static bool set_non_block(int fd, bool blocking)
{
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1)
return false;
flags = blocking ? (flags | O_NONBLOCK) : (flags & ~O_NONBLOCK);
return (fcntl(fd, F_SETFL, flags) == 0);
}
static int create_pair(int *c, int *p, int type)
{
struct sockaddr_storage addr;
int err, cfd, pfd;
socklen_t addr_len = sizeof(struct sockaddr_storage);
err = getsockname(ctx.sfd, (struct sockaddr *)&addr, &addr_len);
if (err) {
fprintf(stderr, "getsockname error %d\n", errno);
return err;
}
cfd = socket(AF_INET, type, 0);
if (cfd < 0) {
fprintf(stderr, "socket error %d\n", errno);
return err;
}
err = connect(cfd, (struct sockaddr *)&addr, addr_len);
if (err && errno != EINPROGRESS) {
fprintf(stderr, "connect error %d\n", errno);
return err;
}
pfd = accept(ctx.sfd, NULL, NULL);
if (pfd < 0) {
fprintf(stderr, "accept error %d\n", errno);
return err;
}
*c = cfd;
*p = pfd;
return 0;
}
static int create_sockets(void)
{
struct sockaddr_storage addr;
int err, one = 1;
socklen_t addr_len;
init_addr(&addr, &addr_len);
ctx.sfd = socket(AF_INET, SOCK_STREAM, 0);
if (ctx.sfd < 0) {
fprintf(stderr, "socket error:%d\n", errno);
return ctx.sfd;
}
err = setsockopt(ctx.sfd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one));
if (err) {
fprintf(stderr, "setsockopt error:%d\n", errno);
return err;
}
err = bind(ctx.sfd, (struct sockaddr *)&addr, addr_len);
if (err) {
fprintf(stderr, "bind error:%d\n", errno);
return err;
}
err = listen(ctx.sfd, SOMAXCONN);
if (err) {
fprintf(stderr, "listen error:%d\n", errno);
return err;
}
err = create_pair(&ctx.c1, &ctx.p1, SOCK_STREAM);
if (err) {
fprintf(stderr, "create_pair 1 error\n");
return err;
}
err = create_pair(&ctx.c2, &ctx.p2, SOCK_STREAM);
if (err) {
fprintf(stderr, "create_pair 2 error\n");
return err;
}
printf("create socket fd c1:%d p1:%d c2:%d p2:%d\n",
ctx.c1, ctx.p1, ctx.c2, ctx.p2);
return 0;
}
static void validate(void)
{
if (env.consumer_cnt != 2 || env.producer_cnt != 1 ||
!env.affinity)
goto err;
return;
err:
fprintf(stderr, "argument '-c 2 -p 1 -a' is necessary");
exit(1);
}
static int setup_rx_sockmap(void)
{
int verdict, pass, parser, map;
int zero = 0, one = 1;
int err;
parser = bpf_program__fd(ctx.skel->progs.prog_skb_parser);
verdict = bpf_program__fd(ctx.skel->progs.prog_skb_verdict);
pass = bpf_program__fd(ctx.skel->progs.prog_skb_pass);
map = bpf_map__fd(ctx.skel->maps.sock_map_rx);
if (ctx.strp_size != 0) {
ctx.skel->bss->pkt_size = ctx.strp_size;
err = bpf_prog_attach(parser, map, BPF_SK_SKB_STREAM_PARSER, 0);
if (err)
return err;
}
if (RXMODE_BPF_VERDICT())
err = bpf_prog_attach(verdict, map, BPF_SK_SKB_STREAM_VERDICT, 0);
else if (RXMODE_BPF_PASS())
err = bpf_prog_attach(pass, map, BPF_SK_SKB_STREAM_VERDICT, 0);
if (err)
return err;
if (RXMODE_BPF_PASS())
return bpf_map_update_elem(map, &zero, &ctx.c2, BPF_NOEXIST);
err = bpf_map_update_elem(map, &zero, &ctx.p1, BPF_NOEXIST);
if (err < 0)
return err;
if (RXMODE_BPF_VERDICT_INGRESS()) {
ctx.skel->bss->verdict_dir = BPF_F_INGRESS;
err = bpf_map_update_elem(map, &one, &ctx.c2, BPF_NOEXIST);
} else {
err = bpf_map_update_elem(map, &one, &ctx.p2, BPF_NOEXIST);
}
if (err < 0)
return err;
return 0;
}
static int setup_tx_sockmap(void)
{
int zero = 0, one = 1;
int prog, map;
int err;
map = bpf_map__fd(ctx.skel->maps.sock_map_tx);
prog = TXMODE_BPF_PASS() ?
bpf_program__fd(ctx.skel->progs.prog_skmsg_pass) :
bpf_program__fd(ctx.skel->progs.prog_skmsg_verdict);
err = bpf_prog_attach(prog, map, BPF_SK_MSG_VERDICT, 0);
if (err)
return err;
if (TXMODE_BPF_EGRESS()) {
err = bpf_map_update_elem(map, &zero, &ctx.p1, BPF_NOEXIST);
err |= bpf_map_update_elem(map, &one, &ctx.p2, BPF_NOEXIST);
} else {
ctx.skel->bss->verdict_dir = BPF_F_INGRESS;
err = bpf_map_update_elem(map, &zero, &ctx.p2, BPF_NOEXIST);
err |= bpf_map_update_elem(map, &one, &ctx.c2, BPF_NOEXIST);
}
if (err < 0)
return err;
return 0;
}
static void setup(void)
{
int err;
ctx.skel = bench_sockmap_prog__open_and_load();
if (!ctx.skel) {
fprintf(stderr, "error loading skel\n");
exit(1);
}
if (create_sockets()) {
fprintf(stderr, "create_net_mode error\n");
goto err;
}
if (RXMODE_BPF()) {
err = setup_rx_sockmap();
if (err) {
fprintf(stderr, "setup_rx_sockmap error:%d\n", err);
goto err;
}
} else if (TXMODE_BPF()) {
err = setup_tx_sockmap();
if (err) {
fprintf(stderr, "setup_tx_sockmap error:%d\n", err);
goto err;
}
} else {
fprintf(stderr, "unknown sockmap bench mode: %d\n", ctx.mode);
goto err;
}
return;
err:
bench_sockmap_prog_destroy();
exit(1);
}
static void measure(struct bench_res *res)
{
res->drops = atomic_swap(&ctx.prod_send, 0);
res->hits = atomic_swap(&ctx.skel->bss->process_byte, 0);
res->false_hits = atomic_swap(&ctx.user_read, 0);
res->important_hits = atomic_swap(&ctx.send_calls, 0);
res->important_hits |= atomic_swap(&ctx.read_calls, 0) << 32;
}
static void verify_data(int *check_pos, char *buf, int rcv)
{
for (int i = 0 ; i < rcv; i++) {
if (buf[i] != snd_data[(*check_pos) % DATA_REPEAT_SIZE]) {
fprintf(stderr, "verify data fail");
exit(1);
}
(*check_pos)++;
if (*check_pos >= FILE_SIZE)
*check_pos = 0;
}
}
static void *consumer(void *input)
{
int rcv, sent;
int check_pos = 0;
int tid = (long)input;
int recv_buf_size = FILE_SIZE;
char *buf = malloc(recv_buf_size);
int delay_read = ctx.delay_consumer;
if (!buf) {
fprintf(stderr, "fail to init read buffer");
return NULL;
}
while (true) {
if (tid == 1) {
/* consumer 1 is unused for tx test and stream verdict test */
if (RXMODE_BPF() || TXMODE())
return NULL;
/* it's only for RX_NORMAL which service as reserve-proxy mode */
rcv = read(ctx.p1, buf, recv_buf_size);
if (rcv < 0) {
fprintf(stderr, "fail to read p1");
return NULL;
}
sent = send(ctx.p2, buf, recv_buf_size, 0);
if (sent < 0) {
fprintf(stderr, "fail to send p2");
return NULL;
}
} else {
if (delay_read != 0) {
if (delay_read < 0)
return NULL;
sleep(delay_read);
delay_read = 0;
}
/* read real endpoint by consumer 0 */
atomic_inc(&ctx.read_calls);
rcv = read(ctx.c2, buf, recv_buf_size);
if (rcv < 0 && errno != EAGAIN) {
fprintf(stderr, "%s fail to read c2 %d\n", __func__, errno);
return NULL;
}
verify_data(&check_pos, buf, rcv);
atomic_add(&ctx.user_read, rcv);
}
}
return NULL;
}
static void *producer(void *input)
{
int off = 0, fp, need_sent, sent;
int file_size = ctx.file_size;
struct timespec ts1, ts2;
int target;
FILE *file;
file = tmpfile();
if (!file) {
fprintf(stderr, "create file for sendfile");
return NULL;
}
/* we need simple verify */
for (int i = 0; i < file_size; i++) {
if (fwrite(&snd_data[off], sizeof(char), 1, file) != 1) {
fprintf(stderr, "init tmpfile error");
return NULL;
}
if (++off >= sizeof(snd_data))
off = 0;
}
fflush(file);
fseek(file, 0, SEEK_SET);
fp = fileno(file);
need_sent = file_size;
clock_gettime(CLOCK_MONOTONIC, &ts1);
if (RXMODE_BPF_VERDICT())
target = ctx.c1;
else if (TXMODE_BPF_EGRESS())
target = ctx.p1;
else
target = ctx.p2;
set_non_block(target, true);
while (true) {
if (ctx.prod_run_time) {
clock_gettime(CLOCK_MONOTONIC, &ts2);
if (ts2.tv_sec - ts1.tv_sec > ctx.prod_run_time)
return NULL;
}
errno = 0;
atomic_inc(&ctx.send_calls);
sent = sendfile(target, fp, NULL, need_sent);
if (sent < 0) {
if (errno != EAGAIN && errno != ENOMEM && errno != ENOBUFS) {
fprintf(stderr, "sendfile return %d, errorno %d:%s\n",
sent, errno, strerror(errno));
return NULL;
}
continue;
} else if (sent < need_sent) {
need_sent -= sent;
atomic_add(&ctx.prod_send, sent);
continue;
}
atomic_add(&ctx.prod_send, need_sent);
need_sent = file_size;
lseek(fp, 0, SEEK_SET);
}
return NULL;
}
static void report_progress(int iter, struct bench_res *res, long delta_ns)
{
double speed_mbs, prod_mbs, bpf_mbs, send_hz, read_hz;
prod_mbs = res->drops / 1000000.0 / (delta_ns / 1000000000.0);
speed_mbs = res->false_hits / 1000000.0 / (delta_ns / 1000000000.0);
bpf_mbs = res->hits / 1000000.0 / (delta_ns / 1000000000.0);
send_hz = (res->important_hits & 0xFFFFFFFF) / (delta_ns / 1000000000.0);
read_hz = (res->important_hits >> 32) / (delta_ns / 1000000000.0);
printf("Iter %3d (%7.3lfus): ",
iter, (delta_ns - 1000000000) / 1000.0);
printf("Send Speed %8.3lf MB/s (%8.3lf calls/s), BPF Speed %8.3lf MB/s, "
"Rcv Speed %8.3lf MB/s (%8.3lf calls/s)\n",
prod_mbs, send_hz, bpf_mbs, speed_mbs, read_hz);
}
static void report_final(struct bench_res res[], int res_cnt)
{
double verdict_mbs_mean = 0.0;
long verdict_total = 0;
int i;
for (i = 0; i < res_cnt; i++) {
verdict_mbs_mean += res[i].hits / 1000000.0 / (0.0 + res_cnt);
verdict_total += res[i].hits / 1000000.0;
}
printf("Summary: total trans %8.3lu MB \u00B1 %5.3lf MB/s\n",
verdict_total, verdict_mbs_mean);
}
static const struct argp_option opts[] = {
{ "rx-normal", ARG_FW_RX_NORMAL, NULL, 0,
"simple reserve-proxy mode, no bfp enabled"},
{ "rx-pass", ARG_FW_RX_PASS, NULL, 0,
"run bpf prog but no redir applied"},
{ "rx-strp", ARG_CTL_RX_STRP, "Byte", 0,
"enable strparser and set the encapsulation size"},
{ "rx-verdict-egress", ARG_FW_RX_VERDICT_EGRESS, NULL, 0,
"forward data with bpf(stream verdict)"},
{ "rx-verdict-ingress", ARG_FW_RX_VERDICT_INGRESS, NULL, 0,
"forward data with bpf(stream verdict)"},
{ "tx-normal", ARG_FW_TX_NORMAL, NULL, 0,
"simple c-s mode, no bfp enabled"},
{ "tx-pass", ARG_FW_TX_PASS, NULL, 0,
"run bpf prog but no redir applied"},
{ "tx-verdict-ingress", ARG_FW_TX_VERDICT_INGRESS, NULL, 0,
"forward msg to ingress queue of another socket"},
{ "tx-verdict-egress", ARG_FW_TX_VERDICT_EGRESS, NULL, 0,
"forward msg to egress queue of another socket"},
{ "delay-consumer", ARG_CONSUMER_DELAY_TIME, "SEC", 0,
"delay consumer start"},
{ "producer-duration", ARG_PRODUCER_DURATION, "SEC", 0,
"producer duration"},
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
switch (key) {
case ARG_FW_RX_NORMAL...ARG_FW_TX_VERDICT_EGRESS:
ctx.mode = key;
break;
case ARG_CONSUMER_DELAY_TIME:
ctx.delay_consumer = strtol(arg, NULL, 10);
break;
case ARG_PRODUCER_DURATION:
ctx.prod_run_time = strtol(arg, NULL, 10);
break;
case ARG_CTL_RX_STRP:
ctx.strp_size = strtol(arg, NULL, 10);
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
/* exported into benchmark runner */
const struct argp bench_sockmap_argp = {
.options = opts,
.parser = parse_arg,
};
/* Benchmark performance of creating bpf local storage */
const struct bench bench_sockmap = {
.name = "sockmap",
.argp = &bench_sockmap_argp,
.validate = validate,
.setup = setup,
.producer_thread = producer,
.consumer_thread = consumer,
.measure = measure,
.report_progress = report_progress,
.report_final = report_final,
};
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2021. Huawei Technologies Co., Ltd */
#include <argp.h>
#include "bench.h"
#include "strncmp_bench.skel.h"
static struct strncmp_ctx {
struct strncmp_bench *skel;
} ctx;
static struct strncmp_args {
u32 cmp_str_len;
} args = {
.cmp_str_len = 32,
};
enum {
ARG_CMP_STR_LEN = 5000,
};
static const struct argp_option opts[] = {
{ "cmp-str-len", ARG_CMP_STR_LEN, "CMP_STR_LEN", 0,
"Set the length of compared string" },
{},
};
static error_t strncmp_parse_arg(int key, char *arg, struct argp_state *state)
{
switch (key) {
case ARG_CMP_STR_LEN:
args.cmp_str_len = strtoul(arg, NULL, 10);
if (!args.cmp_str_len ||
args.cmp_str_len >= sizeof(ctx.skel->bss->str)) {
fprintf(stderr, "Invalid cmp str len (limit %zu)\n",
sizeof(ctx.skel->bss->str));
argp_usage(state);
}
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
const struct argp bench_strncmp_argp = {
.options = opts,
.parser = strncmp_parse_arg,
};
static void strncmp_validate(void)
{
if (env.consumer_cnt != 0) {
fprintf(stderr, "strncmp benchmark doesn't support consumer!\n");
exit(1);
}
}
static void strncmp_setup(void)
{
int err;
char *target;
size_t i, sz;
sz = sizeof(ctx.skel->rodata->target);
if (!sz || sz < sizeof(ctx.skel->bss->str)) {
fprintf(stderr, "invalid string size (target %zu, src %zu)\n",
sz, sizeof(ctx.skel->bss->str));
exit(1);
}
setup_libbpf();
ctx.skel = strncmp_bench__open();
if (!ctx.skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
srandom(time(NULL));
target = ctx.skel->rodata->target;
for (i = 0; i < sz - 1; i++)
target[i] = '1' + random() % 9;
target[sz - 1] = '\0';
ctx.skel->rodata->cmp_str_len = args.cmp_str_len;
memcpy(ctx.skel->bss->str, target, args.cmp_str_len);
ctx.skel->bss->str[args.cmp_str_len] = '\0';
/* Make bss->str < rodata->target */
ctx.skel->bss->str[args.cmp_str_len - 1] -= 1;
err = strncmp_bench__load(ctx.skel);
if (err) {
fprintf(stderr, "failed to load skeleton\n");
strncmp_bench__destroy(ctx.skel);
exit(1);
}
}
static void strncmp_attach_prog(struct bpf_program *prog)
{
struct bpf_link *link;
link = bpf_program__attach(prog);
if (!link) {
fprintf(stderr, "failed to attach program!\n");
exit(1);
}
}
static void strncmp_no_helper_setup(void)
{
strncmp_setup();
strncmp_attach_prog(ctx.skel->progs.strncmp_no_helper);
}
static void strncmp_helper_setup(void)
{
strncmp_setup();
strncmp_attach_prog(ctx.skel->progs.strncmp_helper);
}
static void *strncmp_producer(void *ctx)
{
while (true)
(void)syscall(__NR_getpgid);
return NULL;
}
static void strncmp_measure(struct bench_res *res)
{
res->hits = atomic_swap(&ctx.skel->bss->hits, 0);
}
const struct bench bench_strncmp_no_helper = {
.name = "strncmp-no-helper",
.argp = &bench_strncmp_argp,
.validate = strncmp_validate,
.setup = strncmp_no_helper_setup,
.producer_thread = strncmp_producer,
.measure = strncmp_measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
const struct bench bench_strncmp_helper = {
.name = "strncmp-helper",
.argp = &bench_strncmp_argp,
.validate = strncmp_validate,
.setup = strncmp_helper_setup,
.producer_thread = strncmp_producer,
.measure = strncmp_measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
@@ -0,0 +1,674 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2020 Facebook */
#define _GNU_SOURCE
#include <argp.h>
#include <unistd.h>
#include <stdint.h>
#include "bpf_util.h"
#include "bench.h"
#include "trigger_bench.skel.h"
#include "trace_helpers.h"
#define MAX_TRIG_BATCH_ITERS 1000
static struct {
__u32 batch_iters;
} args = {
.batch_iters = 100,
};
enum {
ARG_TRIG_BATCH_ITERS = 7000,
};
static const struct argp_option opts[] = {
{ "trig-batch-iters", ARG_TRIG_BATCH_ITERS, "BATCH_ITER_CNT", 0,
"Number of in-kernel iterations per one driver test run"},
{},
};
static error_t parse_arg(int key, char *arg, struct argp_state *state)
{
long ret;
switch (key) {
case ARG_TRIG_BATCH_ITERS:
ret = strtol(arg, NULL, 10);
if (ret < 1 || ret > MAX_TRIG_BATCH_ITERS) {
fprintf(stderr, "invalid --trig-batch-iters value (should be between %d and %d)\n",
1, MAX_TRIG_BATCH_ITERS);
argp_usage(state);
}
args.batch_iters = ret;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
const struct argp bench_trigger_batch_argp = {
.options = opts,
.parser = parse_arg,
};
/* adjust slot shift in inc_hits() if changing */
#define MAX_BUCKETS 256
#pragma GCC diagnostic ignored "-Wattributes"
/* BPF triggering benchmarks */
static struct trigger_ctx {
struct trigger_bench *skel;
bool usermode_counters;
int driver_prog_fd;
} ctx;
static struct counter base_hits[MAX_BUCKETS];
static __always_inline void inc_counter(struct counter *counters)
{
static __thread int tid = 0;
unsigned slot;
if (unlikely(tid == 0))
tid = sys_gettid();
/* multiplicative hashing, it's fast */
slot = 2654435769U * tid;
slot >>= 24;
atomic_inc(&base_hits[slot].value); /* use highest byte as an index */
}
static long sum_and_reset_counters(struct counter *counters)
{
int i;
long sum = 0;
for (i = 0; i < MAX_BUCKETS; i++)
sum += atomic_swap(&counters[i].value, 0);
return sum;
}
static void trigger_validate(void)
{
if (env.consumer_cnt != 0) {
fprintf(stderr, "benchmark doesn't support consumer!\n");
exit(1);
}
}
static void *trigger_producer(void *input)
{
if (ctx.usermode_counters) {
while (true) {
(void)syscall(__NR_getpgid);
inc_counter(base_hits);
}
} else {
while (true)
(void)syscall(__NR_getpgid);
}
return NULL;
}
static void *trigger_producer_batch(void *input)
{
int fd = ctx.driver_prog_fd ?: bpf_program__fd(ctx.skel->progs.trigger_driver);
while (true)
bpf_prog_test_run_opts(fd, NULL);
return NULL;
}
static void trigger_measure(struct bench_res *res)
{
if (ctx.usermode_counters)
res->hits = sum_and_reset_counters(base_hits);
else
res->hits = sum_and_reset_counters(ctx.skel->bss->hits);
}
static void setup_ctx(void)
{
setup_libbpf();
ctx.skel = trigger_bench__open();
if (!ctx.skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
/* default "driver" BPF program */
bpf_program__set_autoload(ctx.skel->progs.trigger_driver, true);
ctx.skel->rodata->batch_iters = args.batch_iters;
ctx.skel->rodata->stacktrace = env.stacktrace;
}
static void load_ctx(void)
{
int err;
err = trigger_bench__load(ctx.skel);
if (err) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
}
static void attach_bpf(struct bpf_program *prog)
{
struct bpf_link *link;
link = bpf_program__attach(prog);
if (!link) {
fprintf(stderr, "failed to attach program!\n");
exit(1);
}
}
static void trigger_syscall_count_setup(void)
{
ctx.usermode_counters = true;
}
/* Batched, staying mostly in-kernel triggering setups */
static void trigger_kernel_count_setup(void)
{
setup_ctx();
bpf_program__set_autoload(ctx.skel->progs.trigger_driver, false);
bpf_program__set_autoload(ctx.skel->progs.trigger_kernel_count, true);
load_ctx();
/* override driver program */
ctx.driver_prog_fd = bpf_program__fd(ctx.skel->progs.trigger_kernel_count);
}
static void trigger_kprobe_setup(void)
{
setup_ctx();
bpf_program__set_autoload(ctx.skel->progs.bench_trigger_kprobe, true);
load_ctx();
attach_bpf(ctx.skel->progs.bench_trigger_kprobe);
}
static void trigger_kretprobe_setup(void)
{
setup_ctx();
bpf_program__set_autoload(ctx.skel->progs.bench_trigger_kretprobe, true);
load_ctx();
attach_bpf(ctx.skel->progs.bench_trigger_kretprobe);
}
static void trigger_kprobe_multi_setup(void)
{
setup_ctx();
bpf_program__set_autoload(ctx.skel->progs.bench_trigger_kprobe_multi, true);
load_ctx();
attach_bpf(ctx.skel->progs.bench_trigger_kprobe_multi);
}
static void trigger_kretprobe_multi_setup(void)
{
setup_ctx();
bpf_program__set_autoload(ctx.skel->progs.bench_trigger_kretprobe_multi, true);
load_ctx();
attach_bpf(ctx.skel->progs.bench_trigger_kretprobe_multi);
}
static void trigger_fentry_setup(void)
{
setup_ctx();
bpf_program__set_autoload(ctx.skel->progs.bench_trigger_fentry, true);
load_ctx();
attach_bpf(ctx.skel->progs.bench_trigger_fentry);
}
static void attach_ksyms_all(struct bpf_program *empty, bool kretprobe)
{
LIBBPF_OPTS(bpf_kprobe_multi_opts, opts);
struct bpf_link *link = NULL;
struct ksyms *ksyms = NULL;
/* Some recursive functions will be skipped in
* bpf_get_ksyms -> skip_entry, as they can introduce sufficient
* overhead. However, it's difficut to skip all the recursive
* functions for a debug kernel.
*
* So, don't run the kprobe-multi-all and kretprobe-multi-all on
* a debug kernel.
*/
if (bpf_get_ksyms(&ksyms, true)) {
fprintf(stderr, "failed to get ksyms\n");
exit(1);
}
opts.syms = (const char **)ksyms->filtered_syms;
opts.cnt = ksyms->filtered_cnt;
opts.retprobe = kretprobe;
/* attach empty to all the kernel functions except bpf_get_numa_node_id. */
link = bpf_program__attach_kprobe_multi_opts(empty, NULL, &opts);
free_kallsyms_local(ksyms);
if (!link) {
fprintf(stderr, "failed to attach bpf_program__attach_kprobe_multi_opts to all\n");
exit(1);
}
}
static void trigger_kprobe_multi_all_setup(void)
{
struct bpf_program *prog, *empty;
setup_ctx();
empty = ctx.skel->progs.bench_kprobe_multi_empty;
prog = ctx.skel->progs.bench_trigger_kprobe_multi;
bpf_program__set_autoload(empty, true);
bpf_program__set_autoload(prog, true);
load_ctx();
attach_ksyms_all(empty, false);
attach_bpf(prog);
}
static void trigger_kretprobe_multi_all_setup(void)
{
struct bpf_program *prog, *empty;
setup_ctx();
empty = ctx.skel->progs.bench_kretprobe_multi_empty;
prog = ctx.skel->progs.bench_trigger_kretprobe_multi;
bpf_program__set_autoload(empty, true);
bpf_program__set_autoload(prog, true);
load_ctx();
attach_ksyms_all(empty, true);
attach_bpf(prog);
}
static void trigger_fexit_setup(void)
{
setup_ctx();
bpf_program__set_autoload(ctx.skel->progs.bench_trigger_fexit, true);
load_ctx();
attach_bpf(ctx.skel->progs.bench_trigger_fexit);
}
static void trigger_fmodret_setup(void)
{
setup_ctx();
bpf_program__set_autoload(ctx.skel->progs.trigger_driver, false);
bpf_program__set_autoload(ctx.skel->progs.trigger_driver_kfunc, true);
bpf_program__set_autoload(ctx.skel->progs.bench_trigger_fmodret, true);
load_ctx();
/* override driver program */
ctx.driver_prog_fd = bpf_program__fd(ctx.skel->progs.trigger_driver_kfunc);
attach_bpf(ctx.skel->progs.bench_trigger_fmodret);
}
static void trigger_tp_setup(void)
{
setup_ctx();
bpf_program__set_autoload(ctx.skel->progs.trigger_driver, false);
bpf_program__set_autoload(ctx.skel->progs.trigger_driver_kfunc, true);
bpf_program__set_autoload(ctx.skel->progs.bench_trigger_tp, true);
load_ctx();
/* override driver program */
ctx.driver_prog_fd = bpf_program__fd(ctx.skel->progs.trigger_driver_kfunc);
attach_bpf(ctx.skel->progs.bench_trigger_tp);
}
static void trigger_rawtp_setup(void)
{
setup_ctx();
bpf_program__set_autoload(ctx.skel->progs.trigger_driver, false);
bpf_program__set_autoload(ctx.skel->progs.trigger_driver_kfunc, true);
bpf_program__set_autoload(ctx.skel->progs.bench_trigger_rawtp, true);
load_ctx();
/* override driver program */
ctx.driver_prog_fd = bpf_program__fd(ctx.skel->progs.trigger_driver_kfunc);
attach_bpf(ctx.skel->progs.bench_trigger_rawtp);
}
/* make sure call is not inlined and not avoided by compiler, so __weak and
* inline asm volatile in the body of the function
*
* There is a performance difference between uprobing at nop location vs other
* instructions. So use two different targets, one of which starts with nop
* and another doesn't.
*
* GCC doesn't generate stack setup preamble for these functions due to them
* having no input arguments and doing nothing in the body.
*/
__nocf_check __weak void uprobe_target_nop(void)
{
asm volatile ("nop");
}
__weak void opaque_noop_func(void)
{
}
__nocf_check __weak int uprobe_target_push(void)
{
/* overhead of function call is negligible compared to uprobe
* triggering, so this shouldn't affect benchmark results much
*/
opaque_noop_func();
return 1;
}
__nocf_check __weak void uprobe_target_ret(void)
{
asm volatile ("");
}
static void *uprobe_producer_count(void *input)
{
while (true) {
uprobe_target_nop();
inc_counter(base_hits);
}
return NULL;
}
static void *uprobe_producer_nop(void *input)
{
while (true)
uprobe_target_nop();
return NULL;
}
static void *uprobe_producer_push(void *input)
{
while (true)
uprobe_target_push();
return NULL;
}
static void *uprobe_producer_ret(void *input)
{
while (true)
uprobe_target_ret();
return NULL;
}
#ifdef __x86_64__
__nocf_check __weak void uprobe_target_nop5(void)
{
asm volatile (".byte 0x0f, 0x1f, 0x44, 0x00, 0x00");
}
static void *uprobe_producer_nop5(void *input)
{
while (true)
uprobe_target_nop5();
return NULL;
}
void usdt_1(void);
void usdt_2(void);
static void *uprobe_producer_usdt_nop(void *input)
{
while (true)
usdt_1();
return NULL;
}
static void *uprobe_producer_usdt_nop5(void *input)
{
while (true)
usdt_2();
return NULL;
}
#endif
static void usetup(bool use_retprobe, bool use_multi, void *target_addr)
{
size_t uprobe_offset;
struct bpf_link *link;
int err;
setup_libbpf();
ctx.skel = trigger_bench__open();
if (!ctx.skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
if (use_multi)
bpf_program__set_autoload(ctx.skel->progs.bench_trigger_uprobe_multi, true);
else
bpf_program__set_autoload(ctx.skel->progs.bench_trigger_uprobe, true);
err = trigger_bench__load(ctx.skel);
if (err) {
fprintf(stderr, "failed to load skeleton\n");
exit(1);
}
uprobe_offset = get_uprobe_offset(target_addr);
if (use_multi) {
LIBBPF_OPTS(bpf_uprobe_multi_opts, opts,
.retprobe = use_retprobe,
.cnt = 1,
.offsets = &uprobe_offset,
);
link = bpf_program__attach_uprobe_multi(
ctx.skel->progs.bench_trigger_uprobe_multi,
-1 /* all PIDs */, "/proc/self/exe", NULL, &opts);
ctx.skel->links.bench_trigger_uprobe_multi = link;
} else {
link = bpf_program__attach_uprobe(ctx.skel->progs.bench_trigger_uprobe,
use_retprobe,
-1 /* all PIDs */,
"/proc/self/exe",
uprobe_offset);
ctx.skel->links.bench_trigger_uprobe = link;
}
if (!link) {
fprintf(stderr, "failed to attach %s!\n", use_multi ? "multi-uprobe" : "uprobe");
exit(1);
}
}
static void usermode_count_setup(void)
{
ctx.usermode_counters = true;
}
static void uprobe_nop_setup(void)
{
usetup(false, false /* !use_multi */, &uprobe_target_nop);
}
static void uretprobe_nop_setup(void)
{
usetup(true, false /* !use_multi */, &uprobe_target_nop);
}
static void uprobe_push_setup(void)
{
usetup(false, false /* !use_multi */, &uprobe_target_push);
}
static void uretprobe_push_setup(void)
{
usetup(true, false /* !use_multi */, &uprobe_target_push);
}
static void uprobe_ret_setup(void)
{
usetup(false, false /* !use_multi */, &uprobe_target_ret);
}
static void uretprobe_ret_setup(void)
{
usetup(true, false /* !use_multi */, &uprobe_target_ret);
}
static void uprobe_multi_nop_setup(void)
{
usetup(false, true /* use_multi */, &uprobe_target_nop);
}
static void uretprobe_multi_nop_setup(void)
{
usetup(true, true /* use_multi */, &uprobe_target_nop);
}
static void uprobe_multi_push_setup(void)
{
usetup(false, true /* use_multi */, &uprobe_target_push);
}
static void uretprobe_multi_push_setup(void)
{
usetup(true, true /* use_multi */, &uprobe_target_push);
}
static void uprobe_multi_ret_setup(void)
{
usetup(false, true /* use_multi */, &uprobe_target_ret);
}
static void uretprobe_multi_ret_setup(void)
{
usetup(true, true /* use_multi */, &uprobe_target_ret);
}
#ifdef __x86_64__
static void uprobe_nop5_setup(void)
{
usetup(false, false /* !use_multi */, &uprobe_target_nop5);
}
static void uretprobe_nop5_setup(void)
{
usetup(true, false /* !use_multi */, &uprobe_target_nop5);
}
static void uprobe_multi_nop5_setup(void)
{
usetup(false, true /* use_multi */, &uprobe_target_nop5);
}
static void uretprobe_multi_nop5_setup(void)
{
usetup(true, true /* use_multi */, &uprobe_target_nop5);
}
static void usdt_setup(const char *name)
{
struct bpf_link *link;
int err;
setup_libbpf();
ctx.skel = trigger_bench__open();
if (!ctx.skel) {
fprintf(stderr, "failed to open skeleton\n");
exit(1);
}
bpf_program__set_autoload(ctx.skel->progs.bench_trigger_usdt, true);
err = trigger_bench__load(ctx.skel);
if (err) {
fprintf(stderr, "failed to load skeleton\n");
exit(1);
}
link = bpf_program__attach_usdt(ctx.skel->progs.bench_trigger_usdt,
0 /*self*/, "/proc/self/exe",
"optimized_attach", name, NULL);
if (libbpf_get_error(link)) {
fprintf(stderr, "failed to attach optimized_attach:%s usdt probe\n", name);
exit(1);
}
ctx.skel->links.bench_trigger_usdt = link;
}
static void usdt_nop_setup(void)
{
usdt_setup("usdt_1");
}
static void usdt_nop5_setup(void)
{
usdt_setup("usdt_2");
}
#endif
const struct bench bench_trig_syscall_count = {
.name = "trig-syscall-count",
.validate = trigger_validate,
.setup = trigger_syscall_count_setup,
.producer_thread = trigger_producer,
.measure = trigger_measure,
.report_progress = hits_drops_report_progress,
.report_final = hits_drops_report_final,
};
/* batched (staying mostly in kernel) kprobe/fentry benchmarks */
#define BENCH_TRIG_KERNEL(KIND, NAME) \
const struct bench bench_trig_##KIND = { \
.name = "trig-" NAME, \
.setup = trigger_##KIND##_setup, \
.producer_thread = trigger_producer_batch, \
.measure = trigger_measure, \
.report_progress = hits_drops_report_progress, \
.report_final = hits_drops_report_final, \
.argp = &bench_trigger_batch_argp, \
}
BENCH_TRIG_KERNEL(kernel_count, "kernel-count");
BENCH_TRIG_KERNEL(kprobe, "kprobe");
BENCH_TRIG_KERNEL(kretprobe, "kretprobe");
BENCH_TRIG_KERNEL(kprobe_multi, "kprobe-multi");
BENCH_TRIG_KERNEL(kretprobe_multi, "kretprobe-multi");
BENCH_TRIG_KERNEL(fentry, "fentry");
BENCH_TRIG_KERNEL(kprobe_multi_all, "kprobe-multi-all");
BENCH_TRIG_KERNEL(kretprobe_multi_all, "kretprobe-multi-all");
BENCH_TRIG_KERNEL(fexit, "fexit");
BENCH_TRIG_KERNEL(fmodret, "fmodret");
BENCH_TRIG_KERNEL(tp, "tp");
BENCH_TRIG_KERNEL(rawtp, "rawtp");
/* uprobe benchmarks */
#define BENCH_TRIG_USERMODE(KIND, PRODUCER, NAME) \
const struct bench bench_trig_##KIND = { \
.name = "trig-" NAME, \
.validate = trigger_validate, \
.setup = KIND##_setup, \
.producer_thread = uprobe_producer_##PRODUCER, \
.measure = trigger_measure, \
.report_progress = hits_drops_report_progress, \
.report_final = hits_drops_report_final, \
}
BENCH_TRIG_USERMODE(usermode_count, count, "usermode-count");
BENCH_TRIG_USERMODE(uprobe_nop, nop, "uprobe-nop");
BENCH_TRIG_USERMODE(uprobe_push, push, "uprobe-push");
BENCH_TRIG_USERMODE(uprobe_ret, ret, "uprobe-ret");
BENCH_TRIG_USERMODE(uretprobe_nop, nop, "uretprobe-nop");
BENCH_TRIG_USERMODE(uretprobe_push, push, "uretprobe-push");
BENCH_TRIG_USERMODE(uretprobe_ret, ret, "uretprobe-ret");
BENCH_TRIG_USERMODE(uprobe_multi_nop, nop, "uprobe-multi-nop");
BENCH_TRIG_USERMODE(uprobe_multi_push, push, "uprobe-multi-push");
BENCH_TRIG_USERMODE(uprobe_multi_ret, ret, "uprobe-multi-ret");
BENCH_TRIG_USERMODE(uretprobe_multi_nop, nop, "uretprobe-multi-nop");
BENCH_TRIG_USERMODE(uretprobe_multi_push, push, "uretprobe-multi-push");
BENCH_TRIG_USERMODE(uretprobe_multi_ret, ret, "uretprobe-multi-ret");
#ifdef __x86_64__
BENCH_TRIG_USERMODE(uprobe_nop5, nop5, "uprobe-nop5");
BENCH_TRIG_USERMODE(uretprobe_nop5, nop5, "uretprobe-nop5");
BENCH_TRIG_USERMODE(uprobe_multi_nop5, nop5, "uprobe-multi-nop5");
BENCH_TRIG_USERMODE(uretprobe_multi_nop5, nop5, "uretprobe-multi-nop5");
BENCH_TRIG_USERMODE(usdt_nop, usdt_nop, "usdt-nop");
BENCH_TRIG_USERMODE(usdt_nop5, usdt_nop5, "usdt-nop5");
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,45 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source ./benchs/run_common.sh
set -eufo pipefail
header "Bloom filter map"
for v in 2 4 8 16 40; do
for t in 1 4 8 12 16; do
for h in {1..10}; do
subtitle "value_size: $v bytes, # threads: $t, # hashes: $h"
for e in 10000 50000 75000 100000 250000 500000 750000 1000000 2500000 5000000; do
printf "%'d entries -\n" $e
printf "\t"
summarize "Lookups, total operations: " \
"$($RUN_BENCH -p $t --nr_hash_funcs $h --nr_entries $e --value_size $v bloom-lookup)"
printf "\t"
summarize "Updates, total operations: " \
"$($RUN_BENCH -p $t --nr_hash_funcs $h --nr_entries $e --value_size $v bloom-update)"
printf "\t"
summarize_percentage "False positive rate: " \
"$($RUN_BENCH -p $t --nr_hash_funcs $h --nr_entries $e --value_size $v bloom-false-positive)"
done
printf "\n"
done
done
done
header "Hashmap without bloom filter vs. hashmap with bloom filter (throughput, 8 threads)"
for v in 2 4 8 16 40; do
for h in {1..10}; do
subtitle "value_size: $v, # hashes: $h"
for e in 10000 50000 75000 100000 250000 500000 750000 1000000 2500000 5000000; do
printf "%'d entries -\n" $e
printf "\t"
summarize_total "Hashmap without bloom filter: " \
"$($RUN_BENCH --nr_hash_funcs $h --nr_entries $e --value_size $v -p 8 hashmap-without-bloom)"
printf "\t"
summarize_total "Hashmap with bloom filter: " \
"$($RUN_BENCH --nr_hash_funcs $h --nr_entries $e --value_size $v -p 8 hashmap-with-bloom)"
done
printf "\n"
done
done
@@ -0,0 +1,11 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source ./benchs/run_common.sh
set -eufo pipefail
nr_threads=`expr $(cat /proc/cpuinfo | grep "processor"| wc -l) - 1`
summary=$($RUN_BENCH -p $nr_threads bpf-hashmap-full-update)
printf "$summary"
printf "\n"
@@ -0,0 +1,15 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source ./benchs/run_common.sh
set -eufo pipefail
for t in 1 4 8 12 16; do
for i in 10 100 500 1000 5000 10000 50000 100000 500000 1000000; do
subtitle "nr_loops: $i, nr_threads: $t"
summarize_ops "bpf_loop: " \
"$($RUN_BENCH -p $t --nr_loops $i bpf-loop)"
printf "\n"
done
done
@@ -0,0 +1,40 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source ./benchs/run_common.sh
set -eufo pipefail
htab_mem()
{
echo -n "per-prod-op: "
echo -n "$*" | sed -E "s/.* per-prod-op\s+([0-9]+\.[0-9]+ ± [0-9]+\.[0-9]+k\/s).*/\1/"
echo -n -e ", avg mem: "
echo -n "$*" | sed -E "s/.* memory usage\s+([0-9]+\.[0-9]+ ± [0-9]+\.[0-9]+MiB).*/\1/"
echo -n ", peak mem: "
echo "$*" | sed -E "s/.* peak memory usage\s+([0-9]+\.[0-9]+MiB).*/\1/"
}
summarize_htab_mem()
{
local bench="$1"
local summary=$(echo $2 | tail -n1)
printf "%-20s %s\n" "$bench" "$(htab_mem $summary)"
}
htab_mem_bench()
{
local name
for name in overwrite batch_add_batch_del add_del_on_diff_cpu
do
summarize_htab_mem "$name" "$($RUN_BENCH htab-mem --use-case $name -p8 "$@")"
done
}
header "preallocated"
htab_mem_bench "--preallocated"
header "normal bpf ma"
htab_mem_bench
@@ -0,0 +1,24 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source ./benchs/run_common.sh
set -eufo pipefail
header "Hashmap Control"
for i in 10 1000 10000 100000 4194304; do
subtitle "num keys: $i"
summarize_local_storage "hashmap (control) sequential get: "\
"$(./bench --nr_maps 1 --hashmap_nr_keys_used=$i local-storage-cache-hashmap-control)"
printf "\n"
done
header "Local Storage"
for i in 1 10 16 17 24 32 100 1000; do
subtitle "num_maps: $i"
summarize_local_storage "local_storage cache sequential get: "\
"$(./bench --nr_maps $i local-storage-cache-seq-get)"
summarize_local_storage "local_storage cache interleaved get: "\
"$(./bench --nr_maps $i local-storage-cache-int-get)"
printf "\n"
done
@@ -0,0 +1,11 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
kthread_pid=`pgrep rcu_tasks_trace_kthread`
if [ -z $kthread_pid ]; then
echo "error: Couldn't find rcu_tasks_trace_kthread"
exit 1
fi
./bench --nr_procs 15000 --kthread_pid $kthread_pid -d 600 --quiet local-storage-tasks-trace
@@ -0,0 +1,9 @@
#!/bin/bash
set -eufo pipefail
for i in base kprobe kretprobe rawtp fentry fexit
do
summary=$(sudo ./bench -w2 -d5 -a rename-$i | tail -n1 | cut -d'(' -f1 | cut -d' ' -f3-)
printf "%-10s: %s\n" $i "$summary"
done
@@ -0,0 +1,60 @@
#!/bin/bash
source ./benchs/run_common.sh
set -eufo pipefail
RUN_RB_BENCH="$RUN_BENCH -c1"
header "Single-producer, parallel producer"
for b in rb-libbpf rb-custom pb-libbpf pb-custom; do
summarize $b "$($RUN_RB_BENCH $b)"
done
header "Single-producer, parallel producer, sampled notification"
for b in rb-libbpf rb-custom pb-libbpf pb-custom; do
summarize $b "$($RUN_RB_BENCH --rb-sampled $b)"
done
header "Single-producer, back-to-back mode"
for b in rb-libbpf rb-custom pb-libbpf pb-custom; do
summarize $b "$($RUN_RB_BENCH --rb-b2b $b)"
summarize $b-sampled "$($RUN_RB_BENCH --rb-sampled --rb-b2b $b)"
done
header "Ringbuf back-to-back, effect of sample rate"
for b in 1 5 10 25 50 100 250 500 1000 2000 3000; do
summarize "rb-sampled-$b" "$($RUN_RB_BENCH --rb-b2b --rb-batch-cnt $b --rb-sampled --rb-sample-rate $b rb-custom)"
done
header "Perfbuf back-to-back, effect of sample rate"
for b in 1 5 10 25 50 100 250 500 1000 2000 3000; do
summarize "pb-sampled-$b" "$($RUN_RB_BENCH --rb-b2b --rb-batch-cnt $b --rb-sampled --rb-sample-rate $b pb-custom)"
done
header "Ringbuf back-to-back, reserve+commit vs output"
summarize "reserve" "$($RUN_RB_BENCH --rb-b2b rb-custom)"
summarize "output" "$($RUN_RB_BENCH --rb-b2b --rb-use-output rb-custom)"
header "Ringbuf sampled, reserve+commit vs output"
summarize "reserve-sampled" "$($RUN_RB_BENCH --rb-sampled rb-custom)"
summarize "output-sampled" "$($RUN_RB_BENCH --rb-sampled --rb-use-output rb-custom)"
header "Single-producer, consumer/producer competing on the same CPU, low batch count"
for b in rb-libbpf rb-custom pb-libbpf pb-custom; do
summarize $b "$($RUN_RB_BENCH --rb-batch-cnt 1 --rb-sample-rate 1 --prod-affinity 0 --cons-affinity 0 $b)"
done
header "Ringbuf, multi-producer contention"
for b in 1 2 3 4 8 12 16 20 24 28 32 36 40 44 48 52; do
summarize "rb-libbpf nr_prod $b" "$($RUN_RB_BENCH -p$b --rb-batch-cnt 50 rb-libbpf)"
done
header "Perfbuf, multi-producer"
for b in 1 2 3 4 8 12 16 20 24 28 32 36 40 44 48 52; do
summarize "pb-libbpf nr_prod $b" "$($RUN_RB_BENCH -p$b --rb-batch-cnt 50 --rb-sample-rate 50 pb-libbpf)"
done
header "Ringbuf, multi-producer contention in overwrite mode, no consumer"
for b in 1 2 3 4 8 12 16 20 24 28 32 36 40 44 48 52; do
summarize "rb-prod nr_prod $b" "$($RUN_BENCH -p$b --rb-batch-cnt 50 --rb-overwrite --rb-bench-producer rb-libbpf)"
done
@@ -0,0 +1,12 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source ./benchs/run_common.sh
set -eufo pipefail
for s in 1 8 64 512 2048 4095; do
for b in no-helper helper; do
summarize ${b}-${s} "$($RUN_BENCH --cmp-str-len=$s strncmp-${b})"
done
done
@@ -0,0 +1,23 @@
#!/bin/bash
set -eufo pipefail
def_tests=( \
usermode-count kernel-count syscall-count \
fentry fexit fmodret \
rawtp tp \
kprobe kprobe-multi kprobe-multi-all \
kretprobe kretprobe-multi kretprobe-multi-all \
)
tests=("$@")
if [ ${#tests[@]} -eq 0 ]; then
tests=("${def_tests[@]}")
fi
p=${PROD_CNT:-1}
for t in "${tests[@]}"; do
summary=$(sudo ./bench -w2 -d5 -a -p$p trig-$t | tail -n1 | cut -d'(' -f1 | cut -d' ' -f3-)
printf "%-15s: %s\n" $t "$summary"
done
@@ -0,0 +1,9 @@
#!/bin/bash
set -eufo pipefail
for i in usermode-count syscall-count {uprobe,uretprobe}-{nop,push,ret,nop5} usdt-nop usdt-nop5
do
summary=$(sudo ./bench -w2 -d5 -a trig-$i | tail -n1 | cut -d'(' -f1 | cut -d' ' -f3-)
printf "%-15s: %s\n" $i "$summary"
done
@@ -0,0 +1,79 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
source ./benchs/run_common.sh
set -eufo pipefail
WARMUP=${WARMUP:-3}
RUN="sudo ./bench -q -w${WARMUP} -a xdp-lb --machine-readable"
SEP=" +----------------------------------+----------+---------+----------+"
HDR=" | %-32s | %8s | %7s | %8s |\n"
ROW=" | %-32s | %8s | %7s | %8s |\n"
function group_header()
{
printf "%s\n" "$SEP"
printf "$HDR" "$1" "p50" "stddev" "p99"
printf "%s\n" "$SEP"
}
function rval()
{
echo "$1" | sed -nE "s/.*$2=([^ ]+).*/\1/p"
}
function run_scenario()
{
local sc="$1"
shift
local output rline
output=$($RUN --scenario "$sc" "$@" 2>&1) || true
rline=$(echo "$output" | grep '^RESULT ' || true)
if [ -z "$rline" ]; then
printf "$ROW" "$sc" "ERR" "-" "-"
return
fi
printf "$ROW" "$sc" \
"$(rval "$rline" median)" \
"$(rval "$rline" stddev)" \
"$(rval "$rline" p99)"
}
header "XDP load-balancer benchmark"
group_header "Single-flow baseline"
for sc in tcp-v4-lru-hit tcp-v4-ch \
tcp-v6-lru-hit tcp-v6-ch \
udp-v4-lru-hit udp-v6-lru-hit \
tcp-v4v6-lru-hit; do
run_scenario "$sc"
done
group_header "Diverse flows (4K src addrs)"
for sc in tcp-v4-lru-diverse tcp-v4-ch-diverse \
tcp-v6-lru-diverse tcp-v6-ch-diverse \
udp-v4-lru-diverse; do
run_scenario "$sc"
done
group_header "TCP flags"
run_scenario tcp-v4-syn
run_scenario tcp-v4-rst-miss
group_header "LRU stress"
run_scenario tcp-v4-lru-miss
run_scenario udp-v4-lru-miss
run_scenario tcp-v4-lru-warmup
group_header "Early exits"
for sc in pass-v4-no-vip pass-v6-no-vip pass-v4-icmp pass-non-ip drop-v4-frag drop-v4-options \
drop-v6-frag; do
run_scenario "$sc"
done
printf "%s\n" "$SEP"
@@ -0,0 +1,92 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
RUN_BENCH="sudo ./bench -w3 -d10 -a"
function header()
{
local len=${#1}
printf "\n%s\n" "$1"
for i in $(seq 1 $len); do printf '='; done
printf '\n'
}
function subtitle()
{
local len=${#1}
printf "\t%s\n" "$1"
}
function hits()
{
echo "$*" | sed -E "s/.*hits\s+([0-9]+\.[0-9]+ ± [0-9]+\.[0-9]+M\/s).*/\1/"
}
function drops()
{
echo "$*" | sed -E "s/.*drops\s+([0-9]+\.[0-9]+ ± [0-9]+\.[0-9]+M\/s).*/\1/"
}
function percentage()
{
echo "$*" | sed -E "s/.*Percentage\s=\s+([0-9]+\.[0-9]+).*/\1/"
}
function ops()
{
echo -n "throughput: "
echo -n "$*" | sed -E "s/.*throughput\s+([0-9]+\.[0-9]+ ± [0-9]+\.[0-9]+\sM\sops\/s).*/\1/"
echo -n -e ", latency: "
echo "$*" | sed -E "s/.*latency\s+([0-9]+\.[0-9]+\sns\/op).*/\1/"
}
function local_storage()
{
echo -n "hits throughput: "
echo -n "$*" | sed -E "s/.* hits throughput\s+([0-9]+\.[0-9]+ ± [0-9]+\.[0-9]+\sM\sops\/s).*/\1/"
echo -n -e ", hits latency: "
echo -n "$*" | sed -E "s/.* hits latency\s+([0-9]+\.[0-9]+\sns\/op).*/\1/"
echo -n ", important_hits throughput: "
echo "$*" | sed -E "s/.*important_hits throughput\s+([0-9]+\.[0-9]+ ± [0-9]+\.[0-9]+\sM\sops\/s).*/\1/"
}
function total()
{
echo "$*" | sed -E "s/.*total operations\s+([0-9]+\.[0-9]+ ± [0-9]+\.[0-9]+M\/s).*/\1/"
}
function summarize()
{
bench="$1"
summary=$(echo $2 | tail -n1)
printf "%-20s %s (drops %s)\n" "$bench" "$(hits $summary)" "$(drops $summary)"
}
function summarize_percentage()
{
bench="$1"
summary=$(echo $2 | tail -n1)
printf "%-20s %s%%\n" "$bench" "$(percentage $summary)"
}
function summarize_ops()
{
bench="$1"
summary=$(echo $2 | tail -n1)
printf "%-20s %s\n" "$bench" "$(ops $summary)"
}
function summarize_local_storage()
{
bench="$1"
summary=$(echo $2 | tail -n1)
printf "%-20s %s\n" "$bench" "$(local_storage $summary)"
}
function summarize_total()
{
bench="$1"
summary=$(echo $2 | tail -n1)
printf "%-20s %s\n" "$bench" "$(total $summary)"
}