tests: posix: eventfd: add stress test

This test simply counts how many times `eventfd_read()`
and `eventfd_write()` can be called on an
`eventfd(0, EFD_SEMAPHORE | EFD_NONBLOCK)` file
descriptor.

Prior to the recent changes in `eventfd`, we were seeing
approximately < 1000 writes / s. However, the previous
`eventfd` implementation would fail this test with the
result that the number of successful reads was far greater
than the number of successful writes.

This should be impossible, and with the recent `eventfd`
changes that was fixed. Additionally, we are seeing an
increase in over 40x for non-blocking eventfd reads and
writes.

```
START - test_stress
I: BOARD: qemu_riscv64_smp
I: TEST_DURATION_S: 5
I: UPDATE_INTERVAL_S: 1
I: avg: 48537 reads/s
I: avg: 48575 writes/s
PASS - test_stress in 5.002 seconds
```

Signed-off-by: Christopher Friedt <cfriedt@meta.com>
This commit is contained in:
Christopher Friedt 2023-06-02 08:53:23 -04:00 committed by Anas Nashif
parent 3e27c7f4a7
commit 386f6c7006
3 changed files with 156 additions and 1 deletions

View file

@ -0,0 +1,43 @@
# Copyright (c) 2023, Meta
#
# SPDX-License-Identifier: Apache-2.0
source "Kconfig.zephyr"
config TEST_DURATION_S
int "Number of seconds to run the test"
range 1 21600
default 5
help
Duration for the test, in seconds. The range has a reblatively high
upper bound because we should expect that eventfd_read() and
eventfd_write() are stable enough to run for an arbitrarily long
period of time without encountering any race conditions.
config TEST_TIMEOUT_S
int "Number of seconds to run the test"
range 1 21600
default 10
config TEST_STACK_SIZE
int "Size of each thread stack in this test"
default 2048
help
The minimal stack size required to run a no-op thread.
config TEST_EXTRA_ASSERTIONS
bool "Add extra assertions into the hot path"
help
In order to get a true benchmark, there should be as few branches
as possible on the hot path. Say 'y' here to add extra assertions
on the hot path as well to verify functionality.
config TEST_EXTRA_QUIET
bool "Do not print out regular reports"
help
In order to get a true benchmark, there should be as few branches
as possible on the hot path. Say 'y' here to skip reporting.
module = EVENTFD_TEST
module-str = eventfd
source "subsys/logging/Kconfig.template.log_config"

View file

@ -71,7 +71,7 @@ ZTEST_F(eventfd, test_unset_poll_event_block)
eventfd_poll_unset_common(fixture->fd);
}
K_THREAD_STACK_DEFINE(thread_stack, 2048);
K_THREAD_STACK_DEFINE(thread_stack, CONFIG_TEST_STACK_SIZE);
static void thread_fun(void *arg1, void *arg2, void *arg3)
{

View file

@ -0,0 +1,112 @@
/*
* Copyright (c) 2023, Meta
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "_main.h"
/* update interval for printing stats */
#if CONFIG_TEST_DURATION_S >= 60
#define UPDATE_INTERVAL_S 10
#elif CONFIG_TEST_DURATION_S >= 30
#define UPDATE_INTERVAL_S 5
#else
#define UPDATE_INTERVAL_S 1
#endif
enum th_id {
WRITER,
READER,
};
typedef int (*eventfd_op_t)(int fd);
static size_t count[2];
static struct k_thread th[2];
static const char *msg[2] = {
[READER] = "reads",
[WRITER] = "writes",
};
static int read_op(int fd);
static int write_op(int fd);
static const eventfd_op_t op[2] = {
[READER] = read_op,
[WRITER] = write_op,
};
static K_THREAD_STACK_ARRAY_DEFINE(th_stack, 2, CONFIG_TEST_STACK_SIZE);
static int read_op(int fd)
{
eventfd_t value;
return eventfd_read(fd, &value);
}
static int write_op(int fd)
{
return eventfd_write(fd, 1);
}
static void th_fun(void *arg1, void *arg2, void *arg3)
{
int ret;
uint64_t now;
uint64_t end;
uint64_t report;
enum th_id id = POINTER_TO_UINT(arg1);
struct eventfd_fixture *fixture = arg2;
const uint64_t report_ms = UPDATE_INTERVAL_S * MSEC_PER_SEC;
const uint64_t end_ms = CONFIG_TEST_DURATION_S * MSEC_PER_SEC;
for (now = k_uptime_get(), end = now + end_ms, report = now + report_ms; now < end;
now = k_uptime_get()) {
ret = op[id](fixture->fd);
if (IS_ENABLED(CONFIG_TEST_EXTRA_ASSERTIONS)) {
zassert_true(ret == 0 || (ret == -1 && errno == EAGAIN),
"ret: %d errno: %d", ret, errno);
}
count[id] += (ret == 0);
if (!IS_ENABLED(CONFIG_TEST_EXTRA_QUIET)) {
if (now >= report) {
printk("%zu %s\n", count[id], msg[id]);
report += report_ms;
}
}
}
printk("avg: %zu %s/s\n", (size_t)((count[id] * MSEC_PER_SEC) / end_ms), msg[id]);
}
ZTEST_F(eventfd, test_stress)
{
enum th_id i;
enum th_id begin = MIN(READER, WRITER);
enum th_id end = MAX(READER, WRITER) + 1;
printk("BOARD: %s\n", CONFIG_BOARD);
printk("TEST_DURATION_S: %u\n", CONFIG_TEST_DURATION_S);
printk("UPDATE_INTERVAL_S: %u\n", UPDATE_INTERVAL_S);
reopen(&fixture->fd, 0, EFD_NONBLOCK | EFD_SEMAPHORE);
for (i = begin; i < end; ++i) {
k_thread_create(&th[i], th_stack[i], K_THREAD_STACK_SIZEOF(th_stack[0]), th_fun,
UINT_TO_POINTER(i), fixture, NULL, K_LOWEST_APPLICATION_THREAD_PRIO,
0, K_NO_WAIT);
}
for (i = begin; i < end; ++i) {
zassert_ok(k_thread_join(&th[i], K_FOREVER));
}
zassert_true(count[READER] > 0, "read count is zero");
zassert_true(count[WRITER] > 0, "write count is zero");
zassert_true(count[WRITER] >= count[READER], "read count (%zu) > write count (%zu)",
count[READER], count[WRITER]);
}