test/mbuf: add unit test on mbuf flag names
[dpdk.git] / app / test / test_rwlock.c
index 7ab8458..40f9175 100644 (file)
@@ -1,54 +1,21 @@
-/*-
- *   BSD LICENSE
- * 
- *   Copyright(c) 2010-2012 Intel Corporation. All rights reserved.
- *   All rights reserved.
- * 
- *   Redistribution and use in source and binary forms, with or without 
- *   modification, are permitted provided that the following conditions 
- *   are met:
- * 
- *     * Redistributions of source code must retain the above copyright 
- *       notice, this list of conditions and the following disclaimer.
- *     * Redistributions in binary form must reproduce the above copyright 
- *       notice, this list of conditions and the following disclaimer in 
- *       the documentation and/or other materials provided with the 
- *       distribution.
- *     * Neither the name of Intel Corporation nor the names of its 
- *       contributors may be used to endorse or promote products derived 
- *       from this software without specific prior written permission.
- * 
- *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
- *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
- *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
- *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
- *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
- *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
- *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
- *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
- *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
- *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
- *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- * 
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2010-2014 Intel Corporation
  */
 
 #include <stdio.h>
 #include <stdint.h>
+#include <inttypes.h>
 #include <unistd.h>
 #include <sys/queue.h>
-
-#include <cmdline_parse.h>
+#include <string.h>
 
 #include <rte_common.h>
 #include <rte_memory.h>
-#include <rte_memzone.h>
 #include <rte_per_lcore.h>
 #include <rte_launch.h>
 #include <rte_atomic.h>
 #include <rte_rwlock.h>
-#include <rte_tailq.h>
 #include <rte_eal.h>
-#include <rte_per_lcore.h>
 #include <rte_lcore.h>
 #include <rte_cycles.h>
 
 /*
  * rwlock test
  * ===========
- *
- * - There is a global rwlock and a table of rwlocks (one per lcore).
- *
- * - The test function takes all of these locks and launches the
- *   ``test_rwlock_per_core()`` function on each core (except the master).
- *
- *   - The function takes the global write lock, display something,
- *     then releases the global lock.
- *   - Then, it takes the per-lcore write lock, display something, and
- *     releases the per-core lock.
- *   - Finally, a read lock is taken during 100 ms, then released.
- *
- * - The main function unlocks the per-lcore locks sequentially and
- *   waits between each lock. This triggers the display of a message
- *   for each core, in the correct order.
- *
- *   Then, it tries to take the global write lock and display the last
- *   message. The autotest script checks that the message order is correct.
+ * Provides UT for rte_rwlock API.
+ * Main concern is on functional testing, but also provides some
+ * performance measurements.
+ * Obviously for proper testing need to be executed with more than one lcore.
  */
 
+#define ITER_NUM       0x80
+
+#define TEST_SEC       5
+
 static rte_rwlock_t sl;
 static rte_rwlock_t sl_tab[RTE_MAX_LCORE];
+static rte_atomic32_t synchro;
+
+enum {
+       LC_TYPE_RDLOCK,
+       LC_TYPE_WRLOCK,
+};
+
+static struct {
+       rte_rwlock_t lock;
+       uint64_t tick;
+       volatile union {
+               uint8_t u8[RTE_CACHE_LINE_SIZE];
+               uint64_t u64[RTE_CACHE_LINE_SIZE / sizeof(uint64_t)];
+       } data;
+} __rte_cache_aligned try_rwlock_data;
+
+struct try_rwlock_lcore {
+       int32_t rc;
+       int32_t type;
+       struct {
+               uint64_t tick;
+               uint64_t fail;
+               uint64_t success;
+       } stat;
+} __rte_cache_aligned;
+
+static struct try_rwlock_lcore try_lcore_data[RTE_MAX_LCORE];
 
 static int
 test_rwlock_per_core(__attribute__((unused)) void *arg)
@@ -100,8 +84,100 @@ test_rwlock_per_core(__attribute__((unused)) void *arg)
        return 0;
 }
 
-int
-test_rwlock(void)
+static rte_rwlock_t lk = RTE_RWLOCK_INITIALIZER;
+static volatile uint64_t rwlock_data;
+static uint64_t time_count[RTE_MAX_LCORE] = {0};
+
+#define MAX_LOOP 10000
+#define TEST_RWLOCK_DEBUG 0
+
+static int
+load_loop_fn(__attribute__((unused)) void *arg)
+{
+       uint64_t time_diff = 0, begin;
+       uint64_t hz = rte_get_timer_hz();
+       uint64_t lcount = 0;
+       const unsigned int lcore = rte_lcore_id();
+
+       /* wait synchro for slaves */
+       if (lcore != rte_get_master_lcore())
+               while (rte_atomic32_read(&synchro) == 0)
+                       ;
+
+       begin = rte_rdtsc_precise();
+       while (lcount < MAX_LOOP) {
+               rte_rwlock_write_lock(&lk);
+               ++rwlock_data;
+               rte_rwlock_write_unlock(&lk);
+
+               rte_rwlock_read_lock(&lk);
+               if (TEST_RWLOCK_DEBUG && !(lcount % 100))
+                       printf("Core [%u] rwlock_data = %"PRIu64"\n",
+                               lcore, rwlock_data);
+               rte_rwlock_read_unlock(&lk);
+
+               lcount++;
+               /* delay to make lock duty cycle slightly realistic */
+               rte_pause();
+       }
+
+       time_diff = rte_rdtsc_precise() - begin;
+       time_count[lcore] = time_diff * 1000000 / hz;
+       return 0;
+}
+
+static int
+test_rwlock_perf(void)
+{
+       unsigned int i;
+       uint64_t total = 0;
+
+       printf("\nRwlock Perf Test on %u cores...\n", rte_lcore_count());
+
+       /* clear synchro and start slaves */
+       rte_atomic32_set(&synchro, 0);
+       if (rte_eal_mp_remote_launch(load_loop_fn, NULL, SKIP_MASTER) < 0)
+               return -1;
+
+       /* start synchro and launch test on master */
+       rte_atomic32_set(&synchro, 1);
+       load_loop_fn(NULL);
+
+       rte_eal_mp_wait_lcore();
+
+       RTE_LCORE_FOREACH(i) {
+               printf("Core [%u] cost time = %"PRIu64" us\n",
+                       i, time_count[i]);
+               total += time_count[i];
+       }
+
+       printf("Total cost time = %"PRIu64" us\n", total);
+       memset(time_count, 0, sizeof(time_count));
+
+       return 0;
+}
+
+/*
+ * - There is a global rwlock and a table of rwlocks (one per lcore).
+ *
+ * - The test function takes all of these locks and launches the
+ *   ``test_rwlock_per_core()`` function on each core (except the master).
+ *
+ *   - The function takes the global write lock, display something,
+ *     then releases the global lock.
+ *   - Then, it takes the per-lcore write lock, display something, and
+ *     releases the per-core lock.
+ *   - Finally, a read lock is taken during 100 ms, then released.
+ *
+ * - The main function unlocks the per-lcore locks sequentially and
+ *   waits between each lock. This triggers the display of a message
+ *   for each core, in the correct order.
+ *
+ *   Then, it tries to take the global write lock and display the last
+ *   message. The autotest script checks that the message order is correct.
+ */
+static int
+rwlock_test1(void)
 {
        int i;
 
@@ -130,5 +206,350 @@ test_rwlock(void)
 
        rte_eal_mp_wait_lcore();
 
+       if (test_rwlock_perf() < 0)
+               return -1;
+
        return 0;
 }
+
+static int
+try_read(uint32_t lc)
+{
+       int32_t rc;
+       uint32_t i;
+
+       rc = rte_rwlock_read_trylock(&try_rwlock_data.lock);
+       if (rc != 0)
+               return rc;
+
+       for (i = 0; i != RTE_DIM(try_rwlock_data.data.u64); i++) {
+
+               /* race condition occurred, lock doesn't work properly */
+               if (try_rwlock_data.data.u64[i] != 0) {
+                       printf("%s(%u) error: unexpected data pattern\n",
+                               __func__, lc);
+                       rte_memdump(stdout, NULL,
+                               (void *)(uintptr_t)&try_rwlock_data.data,
+                               sizeof(try_rwlock_data.data));
+                       rc = -EFAULT;
+                       break;
+               }
+       }
+
+       rte_rwlock_read_unlock(&try_rwlock_data.lock);
+       return rc;
+}
+
+static int
+try_write(uint32_t lc)
+{
+       int32_t rc;
+       uint32_t i, v;
+
+       v = RTE_MAX(lc % UINT8_MAX, 1U);
+
+       rc = rte_rwlock_write_trylock(&try_rwlock_data.lock);
+       if (rc != 0)
+               return rc;
+
+       /* update by bytes in reverese order */
+       for (i = RTE_DIM(try_rwlock_data.data.u8); i-- != 0; ) {
+
+               /* race condition occurred, lock doesn't work properly */
+               if (try_rwlock_data.data.u8[i] != 0) {
+                       printf("%s:%d(%u) error: unexpected data pattern\n",
+                               __func__, __LINE__, lc);
+                       rte_memdump(stdout, NULL,
+                               (void *)(uintptr_t)&try_rwlock_data.data,
+                               sizeof(try_rwlock_data.data));
+                       rc = -EFAULT;
+                       break;
+               }
+
+               try_rwlock_data.data.u8[i] = v;
+       }
+
+       /* restore by bytes in reverese order */
+       for (i = RTE_DIM(try_rwlock_data.data.u8); i-- != 0; ) {
+
+               /* race condition occurred, lock doesn't work properly */
+               if (try_rwlock_data.data.u8[i] != v) {
+                       printf("%s:%d(%u) error: unexpected data pattern\n",
+                               __func__, __LINE__, lc);
+                       rte_memdump(stdout, NULL,
+                               (void *)(uintptr_t)&try_rwlock_data.data,
+                               sizeof(try_rwlock_data.data));
+                       rc = -EFAULT;
+                       break;
+               }
+
+               try_rwlock_data.data.u8[i] = 0;
+       }
+
+       rte_rwlock_write_unlock(&try_rwlock_data.lock);
+       return rc;
+}
+
+static int
+try_read_lcore(__rte_unused void *data)
+{
+       int32_t rc;
+       uint32_t i, lc;
+       uint64_t ftm, stm, tm;
+       struct try_rwlock_lcore *lcd;
+
+       lc = rte_lcore_id();
+       lcd = try_lcore_data + lc;
+       lcd->type = LC_TYPE_RDLOCK;
+
+       ftm = try_rwlock_data.tick;
+       stm = rte_get_timer_cycles();
+
+       do {
+               for (i = 0; i != ITER_NUM; i++) {
+                       rc = try_read(lc);
+                       if (rc == 0)
+                               lcd->stat.success++;
+                       else if (rc == -EBUSY)
+                               lcd->stat.fail++;
+                       else
+                               break;
+                       rc = 0;
+               }
+               tm = rte_get_timer_cycles() - stm;
+       } while (tm < ftm && rc == 0);
+
+       lcd->rc = rc;
+       lcd->stat.tick = tm;
+       return rc;
+}
+
+static int
+try_write_lcore(__rte_unused void *data)
+{
+       int32_t rc;
+       uint32_t i, lc;
+       uint64_t ftm, stm, tm;
+       struct try_rwlock_lcore *lcd;
+
+       lc = rte_lcore_id();
+       lcd = try_lcore_data + lc;
+       lcd->type = LC_TYPE_WRLOCK;
+
+       ftm = try_rwlock_data.tick;
+       stm = rte_get_timer_cycles();
+
+       do {
+               for (i = 0; i != ITER_NUM; i++) {
+                       rc = try_write(lc);
+                       if (rc == 0)
+                               lcd->stat.success++;
+                       else if (rc == -EBUSY)
+                               lcd->stat.fail++;
+                       else
+                               break;
+                       rc = 0;
+               }
+               tm = rte_get_timer_cycles() - stm;
+       } while (tm < ftm && rc == 0);
+
+       lcd->rc = rc;
+       lcd->stat.tick = tm;
+       return rc;
+}
+
+static void
+print_try_lcore_stats(const struct try_rwlock_lcore *tlc, uint32_t lc)
+{
+       uint64_t f, s;
+
+       f = RTE_MAX(tlc->stat.fail, 1ULL);
+       s = RTE_MAX(tlc->stat.success, 1ULL);
+
+       printf("try_lcore_data[%u]={\n"
+               "\trc=%d,\n"
+               "\ttype=%s,\n"
+               "\tfail=%" PRIu64 ",\n"
+               "\tsuccess=%" PRIu64 ",\n"
+               "\tcycles=%" PRIu64 ",\n"
+               "\tcycles/op=%#Lf,\n"
+               "\tcycles/success=%#Lf,\n"
+               "\tsuccess/fail=%#Lf,\n"
+               "};\n",
+               lc,
+               tlc->rc,
+               tlc->type == LC_TYPE_RDLOCK ? "RDLOCK" : "WRLOCK",
+               tlc->stat.fail,
+               tlc->stat.success,
+               tlc->stat.tick,
+               (long double)tlc->stat.tick /
+               (tlc->stat.fail + tlc->stat.success),
+               (long double)tlc->stat.tick / s,
+               (long double)tlc->stat.success / f);
+}
+
+static void
+collect_try_lcore_stats(struct try_rwlock_lcore *tlc,
+       const struct try_rwlock_lcore *lc)
+{
+       tlc->stat.tick += lc->stat.tick;
+       tlc->stat.fail += lc->stat.fail;
+       tlc->stat.success += lc->stat.success;
+}
+
+/*
+ * Process collected results:
+ *  - check status
+ *  - collect and print statistics
+ */
+static int
+process_try_lcore_stats(void)
+{
+       int32_t rc;
+       uint32_t lc, rd, wr;
+       struct try_rwlock_lcore rlc, wlc;
+
+       memset(&rlc, 0, sizeof(rlc));
+       memset(&wlc, 0, sizeof(wlc));
+
+       rlc.type = LC_TYPE_RDLOCK;
+       wlc.type = LC_TYPE_WRLOCK;
+       rd = 0;
+       wr = 0;
+
+       rc = 0;
+       RTE_LCORE_FOREACH(lc) {
+               rc |= try_lcore_data[lc].rc;
+               if (try_lcore_data[lc].type == LC_TYPE_RDLOCK) {
+                       collect_try_lcore_stats(&rlc, try_lcore_data + lc);
+                       rd++;
+               } else {
+                       collect_try_lcore_stats(&wlc, try_lcore_data + lc);
+                       wr++;
+               }
+       }
+
+       if (rc == 0) {
+               RTE_LCORE_FOREACH(lc)
+                       print_try_lcore_stats(try_lcore_data + lc, lc);
+
+               if (rd != 0) {
+                       printf("aggregated stats for %u RDLOCK cores:\n", rd);
+                       print_try_lcore_stats(&rlc, rd);
+               }
+
+               if (wr != 0) {
+                       printf("aggregated stats for %u WRLOCK cores:\n", wr);
+                       print_try_lcore_stats(&wlc, wr);
+               }
+       }
+
+       return rc;
+}
+
+static void
+try_test_reset(void)
+{
+       memset(&try_lcore_data, 0, sizeof(try_lcore_data));
+       memset(&try_rwlock_data, 0, sizeof(try_rwlock_data));
+       try_rwlock_data.tick = TEST_SEC * rte_get_tsc_hz();
+}
+
+/* all lcores grab RDLOCK */
+static int
+try_rwlock_test_rda(void)
+{
+       try_test_reset();
+
+       /* start read test on all avaialble lcores */
+       rte_eal_mp_remote_launch(try_read_lcore, NULL, CALL_MASTER);
+       rte_eal_mp_wait_lcore();
+
+       return process_try_lcore_stats();
+}
+
+/* all slave lcores grab RDLOCK, master one grabs WRLOCK */
+static int
+try_rwlock_test_rds_wrm(void)
+{
+       try_test_reset();
+
+       rte_eal_mp_remote_launch(try_read_lcore, NULL, SKIP_MASTER);
+       try_write_lcore(NULL);
+       rte_eal_mp_wait_lcore();
+
+       return process_try_lcore_stats();
+}
+
+/* master and even slave lcores grab RDLOCK, odd lcores grab WRLOCK */
+static int
+try_rwlock_test_rde_wro(void)
+{
+       uint32_t lc, mlc;
+
+       try_test_reset();
+
+       mlc = rte_get_master_lcore();
+
+       RTE_LCORE_FOREACH(lc) {
+               if (lc != mlc) {
+                       if ((lc & 1) == 0)
+                               rte_eal_remote_launch(try_read_lcore,
+                                               NULL, lc);
+                       else
+                               rte_eal_remote_launch(try_write_lcore,
+                                               NULL, lc);
+               }
+       }
+       try_read_lcore(NULL);
+       rte_eal_mp_wait_lcore();
+
+       return process_try_lcore_stats();
+}
+
+static int
+test_rwlock(void)
+{
+       uint32_t i;
+       int32_t rc, ret;
+
+       static const struct {
+               const char *name;
+               int (*ftst)(void);
+       } test[] = {
+               {
+                       .name = "rwlock_test1",
+                       .ftst = rwlock_test1,
+               },
+               {
+                       .name = "try_rwlock_test_rda",
+                       .ftst = try_rwlock_test_rda,
+               },
+               {
+                       .name = "try_rwlock_test_rds_wrm",
+                       .ftst = try_rwlock_test_rds_wrm,
+               },
+               {
+                       .name = "try_rwlock_test_rde_wro",
+                       .ftst = try_rwlock_test_rde_wro,
+               },
+       };
+
+       ret = 0;
+       for (i = 0; i != RTE_DIM(test); i++) {
+               printf("starting test %s;\n", test[i].name);
+               rc = test[i].ftst();
+               printf("test %s completed with status %d\n", test[i].name, rc);
+               ret |= rc;
+       }
+
+       return ret;
+}
+
+REGISTER_TEST_COMMAND(rwlock_autotest, test_rwlock);
+
+/* subtests used in meson for CI */
+REGISTER_TEST_COMMAND(rwlock_test1_autotest, rwlock_test1);
+REGISTER_TEST_COMMAND(rwlock_rda_autotest, try_rwlock_test_rda);
+REGISTER_TEST_COMMAND(rwlock_rds_wrm_autotest, try_rwlock_test_rds_wrm);
+REGISTER_TEST_COMMAND(rwlock_rde_wro_autotest, try_rwlock_test_rde_wro);