diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..4756434 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: MIT +# Copyright (c) Huawei Technologies Co., Ltd. 2021-2025. All rights reserved. + +cmake_minimum_required(VERSION 3.10) +project(ub_bench C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_SKIP_RPATH TRUE) + +if(CROSS_COMPILE) + set(CMAKE_C_COMPILER "${CROSS_COMPILE}") + message(STATUS "CMAKE_C_COMPILER: ${CROSS_COMPILE}") +endif() + +set(UB_BENCH_C_FLAGS " -Wall -Werror -Wformat -Wfloat-equal -Wtrampolines -g -O2 \ +-fno-strict-aliasing -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIE -fPIC") + +set(UB_BENCH_FLAGS_ARM64 " -march=armv8-a+crc -DUB_ARCH_ARM64") +set(UB_BENCH_FLAGS_x86_64 " -msse4.2 -DUB_ARCH_X86_64") +if("${X86_CROSS_COMPILATION}" STREQUAL "enable") + set(UB_BENCH_FLAGS_x86_64 " -DUB_ARCH_X86_64") + message(STATUS "x86 cross compilation, disabling msse4.2!") +endif() + +if("${CMAKE_HOST_SYSTEM_PROCESSOR}" STREQUAL "aarch64") + set(CMAKE_C_FLAGS "${UB_BENCH_C_FLAGS} ${UB_BENCH_FLAGS_ARM64}") +else() + set(CMAKE_C_FLAGS "${UB_BENCH_C_FLAGS} ${UB_BENCH_FLAGS_x86_64}") +endif() + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -rdynamic -pie \ +-Wl,-z,noexecstack,-z,relro,-z,now") + +if("${ASAN}" STREQUAL "enable") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fsanitize-recover=address \ +-fsanitize=leak -fsanitize=undefined -fno-omit-frame-pointer") + message(STATUS "ASAN enabled") +endif() + +if("${PERF_CYCLE}" STREQUAL "enable") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DPERF_CYCLE_FLAG") + message(STATUS "PERF_CYCLE enabled") +endif() + +message(STATUS "CMAKE_C_FLAGS = ${CMAKE_C_FLAGS}") + +find_library(URMA_LIB NAMES urma) +if(NOT URMA_LIB) + message(FATAL_ERROR + "liburma not found. Please install UMDK first.") +endif() +message(STATUS "Found urma: ${URMA_LIB}") + +find_library(URMA_COMMON_LIB NAMES urma_common) +if(NOT URMA_COMMON_LIB) + message(FATAL_ERROR + "liburma_common not found. Please install UMDK first.") +endif() +message(STATUS "Found urma_common: ${URMA_COMMON_LIB}") + +find_path(URMA_INCLUDE_DIR urma_api.h + PATHS /usr/include/ub/umdk/urma + NO_DEFAULT_PATH +) +if(NOT URMA_INCLUDE_DIR) + message(FATAL_ERROR + "urma headers not found. Please install UMDK first.") +endif() +message(STATUS "Found urma headers: ${URMA_INCLUDE_DIR}") + +add_executable(ub_bench + src/ub_bench.c + src/ub_bench_hist.c + src/ub_bench_run.c + src/ub_bench_log.c + src/ub_bench_mgmt.c + src/ub_bench_mgmt_tcp.c + src/ub_bench_mgmt_ub.c + src/ub_bench_parameters.c + src/ub_bench_resources.c + src/ub_bench_run_test.c +) + +target_include_directories(ub_bench + PRIVATE + ${URMA_INCLUDE_DIR} + ${CMAKE_SOURCE_DIR}/include/ub + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(ub_bench + PRIVATE + ${URMA_LIB} + ${URMA_COMMON_LIB} + pthread + m +) + +install(TARGETS ub_bench + DESTINATION /usr/bin +) diff --git a/README.md b/README.md index 31b63b7..92ee84d 100644 --- a/README.md +++ b/README.md @@ -1 +1,170 @@ # UBBench + +URMA 性能基准测试工具,基于 urma_perftest 演进,新增带宽与时延同时采集、QPS 限速、QPS 扫描等特性。 + +## 1. 功能概述 + +涵盖收发(SEND)、读(READ)、写(WRITE)、原子操作(ATOMIC)四类语义,每种语义支持时延和带宽测试。区分 server 端和 client 端分别起 ub_bench 进程开展测试并输出测试结果。 + +### 与 urma_perftest 的关系 + +- 完全兼容 urma_perftest 的所有命令和参数 +- LAT 模式(write_lat/read_lat/send_lat/atomic_lat)代码与 urma_perftest 一致 +- BW 模式新增两种发包方式: + - **顺序模式(sequential,默认)**:post 一个 → 等完成 → QPS 间隔 → post 下一个,逐包精确采集时延 + - **流水线模式(pipeline)**:post 满队列 → 批量 poll → 重复,最大带宽吞吐 + +### 新增特性 + +| 特性 | 参数 | 说明 | +|------|------|------| +| 带宽+时延同时采集 | 默认行为 | 顺序模式逐包采集时延(histogram),同时输出带宽 | +| QPS 限速 | `--qps` | 每线程限速,总 QPS = threads × qps | +| QPS 扫描 | `--sweep` | 线性扫描 QPS,自动找到带宽时延拐点 | +| 多线程 | `--threads` | 每线程独立 jetty + CQ,无锁并行 | +| 纯带宽模式 | `--bw-only` | 跳过时延采集,最大化带宽 | + +## 2. 命令格式 + +``` +Usage: ub_bench command [command options] +ub_bench URMA benchmark tool +Command syntax: + read_lat Test for read latency. + write_lat Test for write latency. + send_lat Test for send latency. + atomic_lat Test for atomic latency. + read_bw Test for read bandwidth. + write_bw Test for write bandwidth. + send_bw Test for send bandwidth. + atomic_bw Test for atomic bandwidth. +``` + +## 3. 使用示例 + +### 时延测试 + +```bash +# server 端 +ub_bench write_lat -d -s [SIZE] -n [ITERATIONS] + +# client 端 +ub_bench write_lat -d -s [SIZE] -n [ITERATIONS] -S +``` + +### 带宽测试(pipeline 模式,对标 urma_perftest) + +```bash +# server 端 +ub_bench write_bw -d -s [SIZE] --mode pipeline --bw-only + +# client 端 +ub_bench write_bw -d -s [SIZE] -S --mode pipeline --bw-only +``` + +### 带宽 + 时延同时采集(sequential 模式) + +```bash +# server 端 +ub_bench write_bw -d -s [SIZE] + +# client 端 +ub_bench write_bw -d -s [SIZE] -S +``` + +输出示例: +``` +---- ub_bench Report ---- + actual_QPS target_QPS BW_avg[MiB/s] MsgRate[Mpps] t_min[us] t_median[us] t_avg[us] P99[us] P99.9[us] P99.99[us] P99.999[us] Pmax[us] + 769160 0 48073 0.769160 3.25 12.56 12.74 20.02 22.76 25.44 34.32 297.03 +``` + +### 多线程带宽测试 + +```bash +# 10 线程,CTP 模式 +numactl --cpunodebind=0 --membind=0 ub_bench write_bw -d --ctp -S -s 1048576 --threads 10 +``` + +### QPS 限速测试 + +```bash +# 每线程 100K QPS,10 线程,总 1M QPS +ub_bench write_bw -d --ctp -S -s 65536 --threads 10 --qps 100000 +``` + +### QPS 扫描(找带宽时延拐点) + +```bash +# 从 100K 到 1.5M QPS,步长 100K,每步 10 秒 +ub_bench write_bw -d --ctp -S -s 65536 --threads 10 \ + --sweep 100000:1500000:100000 --sweep-duration 10 +``` + +### CTP 传输层 + +```bash +# 加 --ctp 使用 CTP 传输层(bonding 设备) +ub_bench write_bw -d bonding_dev_0 --ctp -S -s 65536 +``` + +## 4. 新增参数说明 + +| 参数 | 类型 | 说明 | 默认值 | +|------|------|------|--------| +| `--mode ` | string | BW 测试模式:sequential(默认)或 pipeline | sequential | +| `--threads ` | uint32 | 工作线程数,每线程独立 jetty + CQ | 1 | +| `--qps ` | uint64 | 每线程 QPS 限速(0=不限速),仅 BW sequential 模式 | 0 | +| `--sweep ` | string | 线性 QPS 扫描,每步增加 step,仅 sequential 模式 | - | +| `--sweep-duration ` | uint32 | 每个 sweep 步骤持续时间 | 10 | +| `--bw-only` | bool | 跳过时延采集,纯带宽测试 | false | + +### 参数约束 + +- `--mode pipeline` 仅用于 BW 测试 +- `--qps` 仅用于 BW sequential 模式 +- `--sweep` 仅用于 BW sequential 模式,不支持 bidirection 和 send_bw +- `--mode sequential` 不支持 `--infinite`、`--bidirection`、`--all` +- `--threads` 不支持 `--infinite` +- `--bidirection` 不支持 sequential 模式、多线程、pipeline + 时延采集 + +## 5. 两种模式对比 + +| | 顺序模式(sequential) | 流水线模式(pipeline) | +|---|---|---| +| **发包方式** | post 1 个 → poll 1 个 → post 下一个 | post 满队列(128) → 批量 poll | +| **jfs_depth** | 1 | 128 | +| **cq_mod** | 1(每 WR 生成 CQE) | 100(100 WR 生成 1 个 CQE) | +| **时延采集** | 逐包精确(tcompleted - tposted) | user_ctx 携带时间戳,CQE 带回 | +| **--qps** | 支持 | 不支持 | +| **--sweep** | 支持 | 不支持 | +| **--bw-only** | 可关闭时延采集 | 可关闭时延采集 | +| **带宽** | 大包接近 link,小包需多线程 | 接近 link | +| **时延含义** | 真实单包完成时间 | 含排队等待(系统时延) | + +## 6. 编译 + +### 依赖 + +- UMDK 用户态库(urmacore 等) +- cmake >= 3.10 +- gcc / g++ + +### 编译步骤 + +```bash +cd UBBench +mkdir build && cd build +cmake .. +make -j$(nproc) +``` + +编译产物:`build/ub_bench` + +## 7. 原有参数 + +ub_bench 保留了 urma_perftest 的全部参数,包括但不限于: + +`-d/--dev` `-S/--server` `-s/--size` `-n/--iters` `-D/--duration` `-J/--jettys` `-T/--jfs_depth` `-Q/--cq_mod` `-a/--all` `-B/--bidirection` `-b/--simplex_mode` `--ctp` `--ctp` `--rate_limit` `--burst_size` `--enable_credit` `--enable_imm` `--enable_notify` `--use_jfce` `--use_flat_api` `--lock_free` `--trans_mode` `--inline_size` `--jfr_depth` `--jfs_post_list` `--sge_num` `--pair_num` `--bond_mode` `--bond_level` 等。 + +执行 `ub_bench -h` 查看完整参数列表。 diff --git a/include/ub/ub_get_clock.h b/include/ub/ub_get_clock.h new file mode 100644 index 0000000..955a104 --- /dev/null +++ b/include/ub/ub_get_clock.h @@ -0,0 +1,53 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2022-2025. All rights reserved. + * Description: clock for ub_bench + * Author: Qian Guoxin + * Create: 2022-04-03 + * Note: + * History: 2022-04-03 create file + */ + +#ifndef UB_GET_CLOCK_H +#define UB_GET_CLOCK_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define CLOCK_SIZE_OF_INT (32) + +#if defined(__x86_64__) +static inline uint64_t get_cycles(void) +{ + uint32_t low, high; + uint64_t val; + asm volatile ("rdtsc" : "=a" (low), "=d" (high)); + val = high; + val = (val << CLOCK_SIZE_OF_INT) | low; + return val; +} +#elif defined(__aarch64__) +static inline uint64_t get_cycles(void) +{ + uint64_t freq; + asm volatile("isb" : : : "memory"); + asm volatile("mrs %0, cntvct_el0" : "=r" (freq)); + return freq; +} +#else +#warning get_cycles not implemented +#endif + +/* Warning: Function takes more than 200 ms to run. */ +extern double get_cpu_mhz(bool cpu_freq_warn); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/include/ub/ub_util.h b/include/ub/ub_util.h new file mode 100644 index 0000000..ec262d6 --- /dev/null +++ b/include/ub/ub_util.h @@ -0,0 +1,308 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2025. All rights reserved. + * Description: ub util head file + * Author: Lilijun + * Create: 2020-8-11 + * Note: + * History: + */ + + +#ifndef UB_UTIL_H +#define UB_UTIL_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if __GNUC__ && !defined(__CHECKER__) +#define UB_UNUSED __attribute__((__unused__)) +#define UB_LIKELY(CONDITION) __builtin_expect(!!(CONDITION), 1) +#define UB_UNLIKELY(CONDITION) __builtin_expect(!!(CONDITION), 0) +#else +#define UB_UNUSED +#define UB_LIKELY(CONDITION) (!!(CONDITION)) +#define UB_UNLIKELY(CONDITION) (!!(CONDITION)) +#endif + +#define UB_CPU_ALLOC_SIZE(count) \ + ((((count) + __NCPUBITS - 1) / __NCPUBITS) * sizeof(__cpu_mask)) +#define UB_CPU_ALLOC(count) (malloc(UB_CPU_ALLOC_SIZE(count))) +#define CPUSET_NBITS(setsize) (8 * (setsize)) +#define UB_CPU_ISSET_S(cpu, setsize, cpusetp) \ + ({ size_t __cpu = (cpu); \ + __cpu < 8 * (setsize) \ + ? ((((__cpu_mask *) ((cpusetp)->__bits))[__CPUELT (__cpu)] \ + & __CPUMASK (__cpu))) != 0 \ + : 0; }) + +#define UB_CONSTRUCTOR(f) \ + static void f(void) __attribute__((constructor)); \ + static void f(void) + +#ifndef NDEBUG +#define UB_ASSERT(CONDITION) \ + if (UB_LIKELY(!(CONDITION))) { \ + assert(CONDITION); \ + } +#else +#define UB_ASSERT(CONDITION) ((void)(CONDITION)) +#endif + +static inline void ub_abort(void) +{ + abort(); +} + +#define UB_SOURCE_LOCATOR __FILE__ ":" UB_STRINGIZE(__LINE__) +#define UB_STRINGIZE(AUX) #AUX + +#ifndef MAX +#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#endif + +typedef enum urma_huge_page_size { + UB_HUGE_PAGE_SIZE_2MB, + UB_HUGE_PAGE_SIZE_1GB, + UB_HUGE_PAGE_SIZE_ANY, +} urma_huge_page_size_t; + +/* get the 1 bits count. */ +static inline unsigned int ub_count_1bits(uint64_t x) +{ + return (unsigned int)__builtin_popcountll(x); +} + +/* get the last 1-bit of x */ +static inline uintmax_t ub_rightmost_1bit(uintmax_t x) +{ + return x & (uintmax_t)(-x); +} + +/* clear the last 1-bit of x */ +static inline uintmax_t ub_zero_rightmost_1bit(uintmax_t x) +{ + return x & (x - 1); +} + +/* Undefined when x == 0 */ +static inline int ub_count_trail_zero(uint64_t x) +{ + return (__builtin_constant_p(x <= UINT32_MAX) && x <= UINT32_MAX + ? __builtin_ctz((unsigned int)x) + : __builtin_ctzll(x)); +} + +#define BITS_PER_LONG 64 +#define BITS_PER_LONG_SHIFT 6 +#define BITS_PER_LONG_MASK (BITS_PER_LONG - 1) +#define NANO_IN_SEC 1000000000 +/** for bit ops */ +#define BITS_PER_BYTE 8 +#define BITS_PER_UINT32 32 + +#define ARRAY_SIZE(ARRAY) (sizeof(ARRAY) / sizeof((ARRAY)[0])) + +#ifndef DIV_ROUND_UP +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +#endif + +#ifndef ROUND_UP +#define ROUND_UP(n, d) (DIV_ROUND_UP(n, d) * (d)) +#endif + +#ifndef ROUND_DOWN +#define ROUND_DOWN(n, d) ((n) / (d) * (d)) +#endif + +#ifndef IS_POW2 +#define IS_POW2(n) (((n) != 0) && (((n) & ((n) - 1)) == 0)) +#endif + +static inline bool ub_is_pow2(uint64_t x) +{ + return IS_POW2(x); +} + +#define BITS_TO_LONGS(cnt) DIV_ROUND_UP((cnt), BITS_PER_LONG) + +#define for_each_set_bit(bit, addr, size) \ + for ((bit) = ub_find_first_bit((addr), (size)); \ + (bit) < (size); \ + (bit) = ub_find_next_bit((addr), (size), (bit) + 1)) + +static inline void __attribute__((always_inline)) set_bit(uint32_t nr, unsigned long *addr) +{ + if (nr >= (sizeof(*addr) * BITS_PER_BYTE)) { + return; + } + addr[nr >> BITS_PER_LONG_SHIFT] |= 1UL << (nr & BITS_PER_LONG_MASK); +} + +static inline void __attribute__((always_inline)) clear_bit(uint32_t nr, unsigned long *addr) +{ + if (nr >= (sizeof(*addr) * BITS_PER_BYTE)) { + return; + } + addr[nr >> BITS_PER_LONG_SHIFT] &= ~(1UL << (nr & BITS_PER_LONG_MASK)); +} + +static inline int __attribute__((always_inline)) test_bit(unsigned int nr, const unsigned long *addr) +{ + return ((1UL << (nr & BITS_PER_LONG_MASK)) & + (((unsigned long *)addr)[nr >> BITS_PER_LONG_SHIFT])) != 0; +} + +static inline void __attribute__((always_inline)) bitmap_zero(unsigned int nbits, unsigned long *addr) +{ + size_t len = BITS_TO_LONGS(nbits) * sizeof(unsigned long); + memset(addr, 0, len); +} + +#define BITOP_WORD(nr) ((nr) >> BITS_PER_LONG_SHIFT) + +static inline unsigned long __attribute__((always_inline)) ub_ffs(unsigned long word) +{ + return (unsigned long)((unsigned long)__builtin_ffsl(word) - 1UL); +} + +/* + * Find the first set bit in unsigned long array. For example, + * array[2] has two unsigned long with array[1] is set to 128(1000 0000). + * Then ub_find_first_bit returns 71(64+7). If all bits are not set, + * then the total size of bits will be returned. + * @array: unsigned long array for searching + * @size: total size of bits, not the number of array elements. For example, + * array[2] has size 128 and array size 2. + */ +unsigned long ub_find_first_bit(const unsigned long *array, unsigned long size); + +unsigned long ub_find_next_bit(const unsigned long *array, unsigned long size, unsigned long offset); + +unsigned long ub_find_next_zero_bit(const unsigned long *array, unsigned long size, unsigned long offset); + +/* ffz - find first zero bit in word */ +static inline unsigned long __attribute__((always_inline)) ffz(unsigned long word) +{ + return (unsigned long)((unsigned long)__builtin_ffsl(~(word)) - 1UL); +} + +unsigned long ub_find_first_zero_bit(const unsigned long *array, unsigned long size); + +#define OBJ_OFFSETOF(obj_ptr, field) offsetof(typeof(*(obj_ptr)), field) + +/* get the size of field in the struct_type. */ +#define SIZEOF_FIELD(struct_type, field) (sizeof(((struct_type *)NULL)->field)) + +/* get the offset of the end of field in the struct. */ +#define OFFSET_OF_FIELD_END(struct_type, field) \ + (offsetof(struct_type, field) + SIZEOF_FIELD(struct_type, field)) + +/* get the structure object from the pointer of the given field by struct type */ +#define CONTAINER_OF_FIELD(field_ptr, struct_type, field) \ + ((struct_type *)(void *)((char *)(field_ptr) - offsetof(struct_type, field))) + +/* get the structure object from the pointer of the given field by type of obj_ptr */ +#define OBJ_CONTAINING(field_ptr, obj_ptr, field) \ + ((typeof(obj_ptr))(void *)((char *)(field_ptr) - OBJ_OFFSETOF(obj_ptr, field))) + +/* get the structure object from the pointer of the given field by struct type, + * Then assign the structure object to the obj_ptr + */ +#define ASSIGN_CONTAINER_PTR(obj_ptr, field_ptr, field) \ + ((obj_ptr) = OBJ_CONTAINING(field_ptr, obj_ptr, field), (void)0) + +/* initialize obj_ptr and ASSIGN_CONTAINER_PTR to avoid compile warnings. */ +#define INIT_CONTAINER_PTR(obj_ptr, field_ptr, field) \ + ((obj_ptr) = NULL, ASSIGN_CONTAINER_PTR(obj_ptr, field_ptr, field)) + +int safe_write_value_to_file(const char *path, const char *str); + +/* easy to covert string and integer */ +struct str_int { + char *s; + int integer; +}; + +static inline int get_int_from_string(const struct str_int *array, int array_size, const char *str) +{ + int i; + for (i = 0; i < array_size; i++) { + if (strcmp(array[i].s, str) == 0) { + return (int)array[i].integer; + } + } + return -1; +} + +static inline char *get_string_from_int(const struct str_int *array, int array_size, int num) +{ + int i; + for (i = 0; i < array_size; i++) { + if (array[i].integer == num) { + return array[i].s; + } + } + return NULL; +} + +bool hexits_value(const char *s, size_t n, uintmax_t *value); + +bool is_valid_digit(const char *digit_str); +int ub_str_to_bool(const char *buf, bool *bool_res); +int ub_str_to_u8(const char *buf, uint8_t *u8); +int ub_str_to_u16(const char *buf, uint16_t *u16); +int ub_str_to_u32(const char *buf, uint32_t *u32); +int ub_str_to_u64(const char *buf, uint64_t *u64); +int ub_str_to_int(const char *buf, int *integer); +int ub_hex_str_to_u64(const char *p, uint64_t *out, uint64_t max); +int ub_parse_sysfs_val(const char *filename, unsigned long *val); +void *ub_hugemalloc(size_t i_length, urma_huge_page_size_t hps, void *p_addr_hint); +int ub_hugefree(void *p_addr, size_t i_length); +int memset_s_large_buf(void *dest, size_t destMax, int c, size_t count); +int memcpy_s_large_buf(void *dest, size_t destMax, const void *src, size_t count); + +#define RETVAL_SZ 256 +static char g_ub_util_ret[RETVAL_SZ] = { 0 }; + +static inline const char *ub_strerror(int errnum) +{ + if (strerror_r(errnum, g_ub_util_ret, RETVAL_SZ) != 0) { + if (snprintf(g_ub_util_ret, RETVAL_SZ - 1, "Unknown error %d", errnum) <= 0) { + return NULL; + } + } + return g_ub_util_ret; +} + +static inline uint64_t gethrtime_epoch(void) +{ + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) != 0) { + return (uint64_t)(-1); + } + + return (uint64_t)((ts.tv_sec * NANO_IN_SEC) + ts.tv_nsec); +} + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/ub_bench.c b/src/ub_bench.c new file mode 100644 index 0000000..f885ff2 --- /dev/null +++ b/src/ub_bench.c @@ -0,0 +1,234 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + * Description: ub_bench + * Create: 2026-09-16 + * Note: + * History: 2026-09-16 create file + */ + +#include + +#include "urma_api.h" + +#include "ub_bench_mgmt.h" +#include "ub_bench_parameters.h" +#include "ub_bench_resources.h" +#include "ub_bench_run_test.h" +#include "ub_bench_run.h" + +typedef struct context_cfg { + perftest_context_t *ctx; + perftest_config_t *cfg; +} context_cfg_t; + +static int run_test(perftest_context_t *ctx, perftest_config_t *cfg) +{ + int ret = 0; + uint32_t i = 0; + + for (i = 0; i < cfg->pair_num; i++) { + ret = sync_time(cfg, i, "Start test"); + if (ret != 0) { + LOG_ERROR("Failed to sync time, start test.\n"); + return ret; + } + } + + switch (cfg->cmd) { + case PERFTEST_READ_LAT: + ret = run_read_lat(ctx, cfg); + break; + case PERFTEST_WRITE_LAT: + ret = run_write_lat(ctx, cfg); + break; + case PERFTEST_SEND_LAT: + ret = run_send_lat(ctx, cfg); + break; + case PERFTEST_ATOMIC_LAT: + ret = run_atomic_lat(ctx, cfg); + break; + case PERFTEST_READ_BW: + if (cfg->mode == PERFTEST_MODE_SEQUENTIAL) { + ret = bench_run_sequential(ctx, cfg); + } else { + ret = run_read_bw(ctx, cfg); + } + break; + case PERFTEST_WRITE_BW: + if (cfg->mode == PERFTEST_MODE_SEQUENTIAL) { + ret = bench_run_sequential(ctx, cfg); + } else { + ret = run_write_bw(ctx, cfg); + } + break; + case PERFTEST_ATOMIC_BW: + if (cfg->mode == PERFTEST_MODE_SEQUENTIAL) { + ret = bench_run_sequential(ctx, cfg); + } else { + ret = run_atomic_bw(ctx, cfg); + } + break; + case PERFTEST_SEND_BW: + if (cfg->mode == PERFTEST_MODE_SEQUENTIAL) { + ret = bench_run_sequential(ctx, cfg); + } else { + ret = run_send_bw(ctx, cfg); + } + break; + default: + break; + } + + if (cfg->type == PERFTEST_BW && cfg->enable_write_dirty == true) { + cfg->enable_write_dirty = false; /* close the write dirty thread. */ + (void)pthread_join(ctx->write_dirty_thread_id, NULL); + } + + if (ret != 0) { + LOG_ERROR("Failed to run test: %d.\n", (int)cfg->cmd); + return ret; + } + if (!g_exit_flag) { + for (i = 0; i < cfg->pair_num; i++) { + ret = sync_time(cfg, i, "End test"); + if (ret != 0) { + LOG_ERROR("Failed to sync time, End test.\n"); + return ret; + } + } + } + return ret; +} + +int rearm_jfc(perftest_context_t *ctx, const perftest_config_t *cfg) +{ + urma_status_t status; + if (cfg->api_type == PERFTEST_WRITE) { + return -1; + } + for (uint32_t i = 0; i < cfg->jettys; i++) { + status = urma_rearm_jfc(ctx->jfc_s[i], false); + if (status != URMA_SUCCESS) { + LOG_ERROR("Couldn't rearm jfc_s %u\n", i); + return -1; + } + if (cfg->api_type == PERFTEST_SEND) { + status = urma_rearm_jfc(ctx->jfc_r[i], false); + if (status != URMA_SUCCESS) { + LOG_ERROR("Couldn't rearm jfc_r %u\n", i); + return -1; + } + } + if (cfg->pair_flag == false || cfg->type == PERFTEST_BW) { + break; + } + } + return 0; +} + +static void *write_dirty_thread(void *args) +{ + context_cfg_t *ctx_cfg = (context_cfg_t *)args; + perftest_context_t *ctx = ctx_cfg->ctx; + perftest_config_t *cfg = ctx_cfg->cfg; + + while (cfg->enable_write_dirty == true && cfg->write_dirty_period >= PERFTEST_DEF_INF_PERIOD_MS) { + int rand_num = rand() % PERFTEST_CHAR_MAX_VALUE; + if (cfg->seg_pre_jetty == false) { + char *str = (char *)ctx->local_buf[0] + ctx->buf_size * cfg->jettys; + for (size_t i = 0; i < ctx->buf_size * cfg->jettys; i++) { + str[i] = rand_num; + } + } else { + for (uint32_t jetty = 0; jetty < cfg->jettys; jetty++) { + char *str = (char *)ctx->local_buf[jetty] + ctx->buf_size; + for (size_t i = 0; i < ctx->buf_size; i++) { + str[i] = rand_num; + } + } + } + usleep(cfg->write_dirty_period * PERFTEST_MSEC_TO_USEC); + } + return NULL; +} + +static int prepare_test(perftest_context_t *ctx, perftest_config_t *cfg, context_cfg_t *args) +{ + print_cfg(cfg); + if (cfg->use_jfce == true) { + if (rearm_jfc(ctx, cfg) != 0) { + return -1; + } + } + + if (cfg->type == PERFTEST_BW && cfg->enable_write_dirty == true) { + if (pthread_create(&ctx->write_dirty_thread_id, NULL, write_dirty_thread, args) != 0) { + LOG_ERROR("Failed to create write_dirty_thread.\n"); + return -1; + } + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret; + perftest_config_t cfg = {0}; /* cfg.server_ip shoule be initialized as NULL to avoid core dump */ + perftest_context_t ctx; + context_cfg_t args; + + args.cfg = &cfg; + args.ctx = &ctx; + // Parse parameters and check for conflicts + ret = perftest_parse_args(argc, argv, &cfg); + if (ret != 0) { + goto clean_cfg; + } + + ret = check_local_cfg(&cfg); + if (ret != 0) { + goto clean_cfg; + } + + // Establish connection between client and server + ret = establish_connection(&cfg); + if (ret != 0) { + goto clean_cfg; + } + + // Exchange configuration information and check + ret = check_remote_cfg(&cfg); + if (ret != 0) { + goto close_connect; + } + + // Create resource for test + ret = create_ctx(&ctx, &cfg); + if (ret != 0) { + goto close_connect; + } + + // Prepare the operation before the test. For example: print test information, create wr_ list. + ret = prepare_test(&ctx, &cfg, &args); + if (ret != 0) { + goto destroy_ctx; + } + // Flush print from flowbuffer to output before starting test + (void)fflush(stdout); + + // Run test for each cmd + ret = run_test(&ctx, &cfg); + if (ret != 0) { + goto destroy_ctx; + } + +destroy_ctx: + destroy_ctx(&ctx, &cfg); +close_connect: + close_connection(&cfg); +clean_cfg: + destroy_cfg(&cfg); + return ret; +} diff --git a/src/ub_bench_hist.c b/src/ub_bench_hist.c new file mode 100644 index 0000000..266480a --- /dev/null +++ b/src/ub_bench_hist.c @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + * Description: log2 histogram implementation for ub_bench + * Create: 2026-09-16 + */ + +#include "ub_bench_hist.h" + +uint64_t hist_percentile(const latency_hist_t *h, double pct) +{ + if (h->total_count == 0) { + return 0; + } + uint64_t threshold = (uint64_t)(h->total_count * (1.0 - pct)); + uint64_t count = 0; + for (int i = HIST_BUCKET_NUM - 1; i >= 0; i--) { + count += h->buckets[i]; + if (count > threshold) { + return hist_bucket_to_value(i); + } + } + return h->max_delta; +} + +void hist_merge(latency_hist_t *dst, const latency_hist_t *src) +{ + for (int i = 0; i < HIST_BUCKET_NUM; i++) { + dst->buckets[i] += src->buckets[i]; + } + dst->total_count += src->total_count; + dst->sum_delta += src->sum_delta; + if (src->min_delta < dst->min_delta) { + dst->min_delta = src->min_delta; + } + if (src->max_delta > dst->max_delta) { + dst->max_delta = src->max_delta; + } +} + +double hist_avg(const latency_hist_t *h) +{ + if (h->total_count == 0) { + return 0; + } + return (double)h->sum_delta / (double)h->total_count; +} diff --git a/src/ub_bench_hist.h b/src/ub_bench_hist.h new file mode 100644 index 0000000..1d03866 --- /dev/null +++ b/src/ub_bench_hist.h @@ -0,0 +1,91 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + * Description: log2 histogram for ub_bench latency collection + * Create: 2026-09-16 + */ + +#ifndef UB_BENCH_HIST_H +#define UB_BENCH_HIST_H + +#include +#include +#include + +#define HIST_SUB_BITS 9 +#define HIST_MAX_MAG 28 // max 2^28 cycles (~111ms @2.4GHz), covers 8MB packets + tail spikes +#define HIST_SUB_NUM (1 << HIST_SUB_BITS) +#define HIST_BUCKET_NUM (HIST_MAX_MAG * HIST_SUB_NUM) + +typedef struct { + uint32_t buckets[HIST_BUCKET_NUM]; + uint64_t total_count; + uint64_t sum_delta; + uint64_t min_delta; + uint64_t max_delta; +} latency_hist_t; + +static inline void hist_init(latency_hist_t *h) +{ + memset(h, 0, sizeof(*h)); + h->min_delta = (uint64_t)-1; + h->max_delta = 0; +} + +static inline void hist_reset(latency_hist_t *h) +{ + memset(h->buckets, 0, sizeof(h->buckets)); + h->total_count = 0; + h->sum_delta = 0; + h->min_delta = (uint64_t)-1; + h->max_delta = 0; +} + +static inline void hist_add(latency_hist_t *h, uint64_t delta) +{ + if (delta == 0) { + h->buckets[0]++; + h->total_count++; + h->sum_delta += delta; + h->min_delta = 0; + return; + } + int mag = delta ? (63 - __builtin_clzll(delta)) : 0; + if (mag >= HIST_MAX_MAG) { + mag = HIST_MAX_MAG - 1; + } + uint32_t sub_size = (1U << mag) >> HIST_SUB_BITS; + if (sub_size == 0) { + sub_size = 1; + } + int sub = (int)((delta - (1ULL << mag)) / sub_size); + if (sub >= HIST_SUB_NUM) { + sub = HIST_SUB_NUM - 1; + } + h->buckets[mag * HIST_SUB_NUM + sub]++; + h->total_count++; + h->sum_delta += delta; + if (delta < h->min_delta) { + h->min_delta = delta; + } + if (delta > h->max_delta) { + h->max_delta = delta; + } +} + +static inline uint64_t hist_bucket_to_value(int bucket_idx) +{ + int mag = bucket_idx >> HIST_SUB_BITS; + int sub = bucket_idx & (HIST_SUB_NUM - 1); + uint32_t sub_size = (1U << mag) >> HIST_SUB_BITS; + if (sub_size == 0) { + sub_size = 1; + } + return (1ULL << mag) + (uint64_t)sub * sub_size; +} + +uint64_t hist_percentile(const latency_hist_t *h, double pct); +void hist_merge(latency_hist_t *dst, const latency_hist_t *src); +double hist_avg(const latency_hist_t *h); + +#endif diff --git a/src/ub_bench_log.c b/src/ub_bench_log.c new file mode 100644 index 0000000..61622a0 --- /dev/null +++ b/src/ub_bench_log.c @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + * Description: ub_bench log implementation file + * Create: 2026-09-16 + * Note: + * History: 2026-09-16 Create file + */ + +#include "ub_bench_log.h" + +perftest_vlog_level_t verbose_level = VLOG_LEVEL_INFO; diff --git a/src/ub_bench_log.h b/src/ub_bench_log.h new file mode 100644 index 0000000..7ec79b2 --- /dev/null +++ b/src/ub_bench_log.h @@ -0,0 +1,54 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + * Description: ub_bench log head file + * Create: 2026-09-16 + * Note: + * History: 2026-09-16 Create file + */ + +#ifndef UB_BENCH_LOG_H +#define UB_BENCH_LOG_H + +#include +#include + +typedef enum perftest_vlog_level { + VLOG_LEVEL_QUIET = 0, + VLOG_LEVEL_INFO = 1, + VLOG_LEVEL_VERBOSE = 2, + VLOG_LEVEL_VVERBOSE = 3, +} perftest_vlog_level_t; + +extern perftest_vlog_level_t verbose_level; + +static inline void verbose_set_level(perftest_vlog_level_t level) +{ + verbose_level = level; +} + +static inline perftest_vlog_level_t verbose_get_level(void) +{ + return verbose_level; +} + +static inline void verbose_print(FILE *stream, perftest_vlog_level_t level, + const char *fmt, ...) +{ + if (verbose_level < level) { + return; + } + + va_list va; + va_start(va, fmt); + (void)vfprintf(stream, fmt, va); + va_end(va); +} + +#define LOG_QUIET(...) verbose_print(stdout, VLOG_LEVEL_QUIET, __VA_ARGS__) +#define LOG_INFO(...) verbose_print(stdout, VLOG_LEVEL_INFO, __VA_ARGS__) +#define LOG_VERBOSE(...) verbose_print(stdout, VLOG_LEVEL_VERBOSE, __VA_ARGS__) +#define LOG_VVERBOSE(...) verbose_print(stdout, VLOG_LEVEL_VVERBOSE, __VA_ARGS__) +#define LOG_ERROR(...) verbose_print(stderr, VLOG_LEVEL_QUIET, __VA_ARGS__) + +#endif diff --git a/src/ub_bench_mgmt.c b/src/ub_bench_mgmt.c new file mode 100644 index 0000000..9f6259e --- /dev/null +++ b/src/ub_bench_mgmt.c @@ -0,0 +1,128 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + * Description: management for ub_bench + * Create: 2026-09-16 + * Note: + * History: 2026-09-16 create file + */ + +#include + +#include "ub_bench_mgmt_tcp.h" +#include "ub_bench_mgmt_ub.h" +#include "ub_bench_parameters.h" + +#include "ub_bench_mgmt.h" + +int establish_connection(const perftest_config_t *cfg) +{ + switch (cfg->mgmt_type) { + case PERFTEST_MGMT_TCP: { + comm_tcp_cfg_t tcp_cfg = { + .server_ip = cfg->server_ip, + .bind_ip = cfg->bind_ip, + .enable_ipv6 = cfg->enable_ipv6, + .port = cfg->port, + .sock_num = cfg->pair_num, + }; + return tcp_establish_connection(&tcp_cfg); + } + case PERFTEST_MGMT_UB: { + const bool port_specified = cfg->port != PERFTEST_DEF_PORT; + comm_ub_cfg_t ub_cfg = { + .src_eid = cfg->mgmt_addr, + .dst_eid = cfg->server_ip, + .dst_jetty_id = port_specified ? cfg->port : 0, + }; + return ub_establish_connection(&ub_cfg); + } + default: + LOG_ERROR("Invalid management channel type: %d.\n", (int)cfg->mgmt_type); + return -1; + } +} + +void close_connection(perftest_config_t *cfg) +{ + switch (cfg->mgmt_type) { + case PERFTEST_MGMT_TCP: + tcp_close_connection(); + break; + case PERFTEST_MGMT_UB: + ub_close_connection(); + break; + default: + break; + } + free(cfg->server_ip); + cfg->server_ip = NULL; + if (cfg->bind_ip != NULL) { + free(cfg->bind_ip); + cfg->bind_ip = NULL; + } + if (cfg->mgmt_addr != NULL) { + free(cfg->mgmt_addr); + cfg->mgmt_addr = NULL; + } +} + +int sync_data(const perftest_config_t *cfg, uint32_t index, int size, char *local_data, char *remote_data) +{ + switch (cfg->mgmt_type) { + case PERFTEST_MGMT_TCP: + return tcp_sync_data(index, size, local_data, remote_data); + case PERFTEST_MGMT_UB: + return ub_sync_data(index, size, local_data, remote_data); + default: + return -1; + } +} + +int sync_time(const perftest_config_t *cfg, uint32_t index, const char *a) +{ + switch (cfg->mgmt_type) { + case PERFTEST_MGMT_TCP: + return tcp_sync_time(index, a); + case PERFTEST_MGMT_UB: + return ub_sync_time(index, a); + default: + return -1; + } +} + +ssize_t comm_send(const perftest_config_t *cfg, uint32_t index, const void *buf, size_t size) +{ + switch (cfg->mgmt_type) { + case PERFTEST_MGMT_TCP: + return tcp_comm_send(index, buf, size); + case PERFTEST_MGMT_UB: + return ub_comm_send(index, buf, size); + default: + return -1; + } +} + +ssize_t comm_recv(const perftest_config_t *cfg, uint32_t index, void *buf, size_t size) +{ + switch (cfg->mgmt_type) { + case PERFTEST_MGMT_TCP: + return tcp_comm_recv(index, buf, size); + case PERFTEST_MGMT_UB: + return ub_comm_recv(index, buf, size); + default: + return -1; + } +} + +int comm_poll(const perftest_config_t *cfg, uint32_t index, int timeout_ms) +{ + switch (cfg->mgmt_type) { + case PERFTEST_MGMT_TCP: + return tcp_comm_poll(index, timeout_ms); + case PERFTEST_MGMT_UB: + return ub_comm_poll(index, timeout_ms); + default: + return -1; + } +} diff --git a/src/ub_bench_mgmt.h b/src/ub_bench_mgmt.h new file mode 100644 index 0000000..1bc3242 --- /dev/null +++ b/src/ub_bench_mgmt.h @@ -0,0 +1,34 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + * Description: management header file for ub_bench + * Create: 2026-09-16 + * Note: + * History: 2026-09-16 create file + */ + +#ifndef UB_BENCH_MGMT_H +#define UB_BENCH_MGMT_H + +#include +#include +#include + +typedef struct perftest_config perftest_config_t; + +typedef enum perftest_mgmt_type { + PERFTEST_MGMT_TCP = 0, + PERFTEST_MGMT_UB, + PERFTEST_MGMT_TYPE_NUM +} perftest_mgmt_type_t; + +int establish_connection(const perftest_config_t *cfg); +void close_connection(perftest_config_t *cfg); + +int sync_data(const perftest_config_t *cfg, uint32_t index, int size, char *local_data, char *remote_data); +int sync_time(const perftest_config_t *cfg, uint32_t index, const char *a); +ssize_t comm_send(const perftest_config_t *cfg, uint32_t index, const void *buf, size_t size); +ssize_t comm_recv(const perftest_config_t *cfg, uint32_t index, void *buf, size_t size); +int comm_poll(const perftest_config_t *cfg, uint32_t index, int timeout_ms); + +#endif diff --git a/src/ub_bench_mgmt_tcp.c b/src/ub_bench_mgmt_tcp.c new file mode 100644 index 0000000..fb7e818 --- /dev/null +++ b/src/ub_bench_mgmt_tcp.c @@ -0,0 +1,485 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + * Description: tcp management for ub_bench + * Create: 2026-09-16 + * Note: + * History: 2026-09-16 create file + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ub_bench_log.h" +#include "ub_bench_parameters.h" + +#include "ub_bench_mgmt_tcp.h" + +#define PERFTEST_MAX_CONNECTIONS (10) +#define PERFTEST_CONNECT_COUNT (5) +#define ERFTEST_SLEEP_TIME (100 * 1000) /* Sleep for 100 ms */ + +typedef struct comm_tcp_ctx { + int listen_fd; + int *sock_fd; + uint32_t sock_num; +} comm_tcp_ctx_t; + +static comm_tcp_ctx_t comm_ctx = { + .listen_fd = -1, + .sock_fd = NULL, + .sock_num = 0, +}; + +static int send_all(int sock_fd, const char *buf, int size) +{ + ssize_t send_bytes; + int total_send_bytes = 0; + + while (total_send_bytes < size) { + send_bytes = send(sock_fd, buf + total_send_bytes, (size_t)(size - total_send_bytes), MSG_NOSIGNAL); + if (send_bytes <= 0) { + if (send_bytes < 0 && errno == EINTR) { + continue; + } + LOG_ERROR("Failed to send data, errno: [%d]%s, total_size:%d, expect_size:%d.\n", + errno, strerror(errno), total_send_bytes, size); + return -1; + } + total_send_bytes += (int)send_bytes; + } + + return 0; +} + +static int recv_all(int sock_fd, char *buf, int size) +{ + ssize_t recv_bytes; + int total_recv_bytes = 0; + + while (total_recv_bytes < size) { + recv_bytes = recv(sock_fd, buf + total_recv_bytes, (size_t)(size - total_recv_bytes), 0); + if (recv_bytes == 0) { + LOG_ERROR("Peer closed connection, total_size:%d, expect_size:%d.\n", total_recv_bytes, size); + return -1; + } + if (recv_bytes < 0) { + if (errno == EINTR) { + continue; + } + LOG_ERROR("Failed to recv data, errno: [%d]%s, total_size:%d, expect_size:%d.\n", + errno, strerror(errno), total_recv_bytes, size); + return -1; + } + total_recv_bytes += (int)recv_bytes; + } + + return 0; +} + +static int alloc_comm_sock(uint32_t sock_num) +{ + if (comm_ctx.listen_fd >= 0 || comm_ctx.sock_fd != NULL) { + errno = EBUSY; + return -1; + } + + comm_ctx.listen_fd = -1; + comm_ctx.sock_fd = (int *)calloc(sock_num, sizeof(int)); + if (comm_ctx.sock_fd == NULL) { + comm_ctx.sock_num = 0; + return -1; + } + + comm_ctx.sock_num = sock_num; + for (uint32_t i = 0; i < sock_num; i++) { + comm_ctx.sock_fd[i] = -1; + } + return 0; +} + +static void cleanup_comm_ctx(void) +{ + if (comm_ctx.listen_fd >= 0) { + (void)close(comm_ctx.listen_fd); + comm_ctx.listen_fd = -1; + } + if (comm_ctx.sock_fd != NULL) { + for (uint32_t i = 0; i < comm_ctx.sock_num; i++) { + if (comm_ctx.sock_fd[i] >= 0) { + (void)close(comm_ctx.sock_fd[i]); + comm_ctx.sock_fd[i] = -1; + } + } + free(comm_ctx.sock_fd); + } + comm_ctx.listen_fd = -1; + comm_ctx.sock_fd = NULL; + comm_ctx.sock_num = 0; +} + +static int ip_set_sockopts(int sockfd) +{ + int ret; + int enable_reuse = 1; + int enable_nodelay = 1; + + /* Set socket reuse. When the server is restarted, + * the problem of the connection failure of the client is solved */ + ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &enable_reuse, sizeof(enable_reuse)); + if (ret < 0) { + LOG_ERROR("socket set_opt failed. enable_reuse:%d, ret: %d, err: [%d]%s.\n", + SO_REUSEPORT, ret, errno, strerror(errno)); + return ret; + } + + // Close Nagle algorithm, and fix 42ms delay problem. + ret = setsockopt(sockfd, SOL_TCP, TCP_NODELAY, &enable_nodelay, sizeof(enable_nodelay)); + if (ret < 0) { + LOG_ERROR("socket set_opt failed. opt:%d, ret: %d, err: [%d]%s.\n", + TCP_NODELAY, ret, errno, strerror(errno)); + return ret; + } + + return 0; +} + +#define PERFTEST_PORT_LEN_MAX 32 +static int check_add_port(int port, const char *server_ip, struct addrinfo *hints, struct addrinfo **res) +{ + int num; + char service[PERFTEST_PORT_LEN_MAX] = {0}; + + if (port < 0 || port > UINT16_MAX) { + LOG_ERROR("Invalid port: %d.\n", port); + return -1; + } + + if (snprintf(service, sizeof(service), "%d", port) <= 0) { + return -1; + } + + num = getaddrinfo(server_ip, service, hints, res); + if (num < 0) { + LOG_ERROR("%s for %s:%d\n", gai_strerror(num), server_ip, port); + return -1; + } + + return 0; +} + +static int connect_retry(int sockfd, struct sockaddr *addr, uint32_t size) +{ + uint32_t times = 0; + for (int i = 1; i <= PERFTEST_CONNECT_COUNT; i++) { + if (connect(sockfd, addr, size) != 0) { + times += i * (uint32_t)ERFTEST_SLEEP_TIME; + (void)usleep(times); + continue; + } + return 0; + } + return -1; +} + +static int client_connect(const comm_tcp_cfg_t *comm) +{ + struct addrinfo *res = NULL, *tmp = NULL, *client_res = NULL, *client_tmp = NULL; + struct addrinfo hints = {0}, client_hints = {0}; + uint32_t i = 0; + + if (alloc_comm_sock(comm->sock_num) != 0) { + return -1; + } + hints.ai_family = comm->enable_ipv6 ? AF_INET6 : AF_INET; + hints.ai_socktype = SOCK_STREAM; + + if (comm->bind_ip != NULL) { + int err, bound = 0; + client_hints.ai_family = hints.ai_family; + client_hints.ai_socktype = SOCK_STREAM; + err = getaddrinfo(comm->bind_ip, NULL, &client_hints, &client_res); + if (err != 0) { + LOG_ERROR("Problem in resolving bind IP '%s': %s\n", + comm->bind_ip, gai_strerror(err)); + goto bind_client_error; + } + for (client_tmp = client_res; client_tmp != NULL; client_tmp = client_tmp->ai_next) { + if (client_tmp->ai_family == hints.ai_family) { + bound = 1; + break; + } + } + if (!bound) { + LOG_ERROR("Bind IP not found : %s\n", comm->bind_ip); + goto create_client_error; + } + } + + for (i = 0; i < comm->sock_num; i++) { + if (check_add_port((comm->port + i), comm->server_ip, &hints, &res)) { + LOG_ERROR("Problem in resolving basic address and port\n"); + goto create_client_error; + } + + for (tmp = res; tmp != NULL; tmp = tmp->ai_next) { + bool try_connect = true; + comm_ctx.sock_fd[i] = socket(tmp->ai_family, tmp->ai_socktype, tmp->ai_protocol); + if (comm_ctx.sock_fd[i] < 0) { + continue; + } + if (comm->bind_ip != NULL) { + if (bind(comm_ctx.sock_fd[i], client_tmp->ai_addr, client_tmp->ai_addrlen) != 0) { + try_connect = false; + LOG_ERROR("Failed to bind ip: %s\n", comm->bind_ip); + } + } + if (try_connect && connect_retry(comm_ctx.sock_fd[i], tmp->ai_addr, tmp->ai_addrlen) == 0) { + break; + } + close(comm_ctx.sock_fd[i]); + comm_ctx.sock_fd[i] = -1; + } + + if (res != NULL) { + freeaddrinfo(res); + res = NULL; + } + + if (comm_ctx.sock_fd[i] < 0) { + LOG_ERROR("Failed to connect %s:%d\n\n", comm->server_ip, (comm->port + i)); + goto create_client_error; + } + + if (ip_set_sockopts(comm_ctx.sock_fd[i]) != 0) { + LOG_ERROR("Failed to set_sockopts, sockfd:%d, errno: %s\n", comm_ctx.sock_fd[i], strerror(errno)); + (void)close(comm_ctx.sock_fd[i]); + comm_ctx.sock_fd[i] = -1; + goto create_client_error; + } + } + + if (comm->bind_ip != NULL) { + if (client_res != NULL) { + freeaddrinfo(client_res); + } + } + + return 0; +create_client_error: + cleanup_comm_ctx(); + if (client_res != NULL) { + freeaddrinfo(client_res); + } + return -1; +bind_client_error: + cleanup_comm_ctx(); + return -1; +} + +static int server_connect(const comm_tcp_cfg_t *comm) +{ + struct addrinfo *res = NULL, *tmp = NULL; + struct addrinfo hints = {0}; + uint32_t accept_num = 0; + + if (alloc_comm_sock(comm->sock_num) != 0) { + return -1; + } + hints.ai_flags = AI_PASSIVE; + hints.ai_family = comm->enable_ipv6 ? AF_INET6 : AF_INET; + hints.ai_socktype = SOCK_STREAM; + + if (check_add_port(comm->port, comm->bind_ip, &hints, &res)) { + LOG_ERROR("Problem in resolving basic address and port\n"); + goto free_sock; + } + + for (tmp = res; tmp != NULL; tmp = tmp->ai_next) { + if (tmp->ai_family != hints.ai_family) { + continue; + } + + comm_ctx.listen_fd = socket(tmp->ai_family, tmp->ai_socktype, tmp->ai_protocol); + if (comm_ctx.listen_fd >= 0) { + if (ip_set_sockopts(comm_ctx.listen_fd) != 0) { + LOG_ERROR("Failed to set_sockopts, sockfd:%d, errno: %s\n", + comm_ctx.listen_fd, strerror(errno)); + goto free_res; + } + if (bind(comm_ctx.listen_fd, tmp->ai_addr, tmp->ai_addrlen) == 0) { + break; + } + (void)close(comm_ctx.listen_fd); + comm_ctx.listen_fd = -1; + } + } + + if (comm_ctx.listen_fd < 0) { + LOG_ERROR("Failed to bind, port:%d.\n", comm->port); + goto free_res; + } + + if (listen(comm_ctx.listen_fd, PERFTEST_MAX_CONNECTIONS) != 0) { + LOG_ERROR("Failed to listen, listenfd:%d, errno: [%d]%s\n", + comm_ctx.listen_fd, errno, strerror(errno)); + goto free_res; + } + + while (accept_num < comm->sock_num) { + comm_ctx.sock_fd[accept_num] = accept(comm_ctx.listen_fd, NULL, 0); + if (comm_ctx.sock_fd[accept_num] < 0) { + LOG_ERROR("Failed to accept, listenfd:%d, errno: [%d]%s\n", + comm_ctx.listen_fd, errno, strerror(errno)); + goto free_res; + } + + if (ip_set_sockopts(comm_ctx.sock_fd[accept_num]) != 0) { + LOG_ERROR("Failed to set_sockopts, sockfd:%d, errno: [%d]%s\n", + comm_ctx.sock_fd[accept_num], errno, strerror(errno)); + (void)close(comm_ctx.sock_fd[accept_num]); + comm_ctx.sock_fd[accept_num] = -1; + goto free_res; + } + accept_num++; + } + + freeaddrinfo(res); + (void)close(comm_ctx.listen_fd); // No other connections need to be accepted. + comm_ctx.listen_fd = -1; + return 0; + +free_res: + if (res != NULL) { + freeaddrinfo(res); + } +free_sock: + cleanup_comm_ctx(); + return -1; +} + +int tcp_establish_connection(const comm_tcp_cfg_t *cfg) +{ + int ret; + + if (cfg->server_ip != NULL) { + /* client side */ + ret = client_connect(cfg); + } else { + /* server side */ + LOG_INFO(PERFTEST_RESULT_LINE); + LOG_INFO(" Waiting for client to connect...\n"); + ret = server_connect(cfg); + } + + return ret; +} + +void tcp_close_connection(void) +{ + cleanup_comm_ctx(); +} + +int tcp_sync_data(uint32_t index, int size, char *local_data, char *remote_data) +{ + int sock_fd; + + if (comm_ctx.sock_fd == NULL || index >= comm_ctx.sock_num) { + errno = EINVAL; + return -1; + } + sock_fd = comm_ctx.sock_fd[index]; + if (sock_fd < 0) { + errno = EINVAL; + return -1; + } + + if (send_all(sock_fd, local_data, size) != 0) { + LOG_ERROR("Failed to send data during tcp_sync_data.\n"); + return -1; + } + + if (recv_all(sock_fd, remote_data, size) != 0) { + LOG_ERROR("Failed to recv data during tcp_sync_data.\n"); + return -1; + } + + return 0; +} + +int tcp_sync_time(uint32_t index, const char *a) +{ + if (a == NULL) { + LOG_ERROR("Invalid parameter with a nullptr.\n"); + return -1; + } + int len = (int)strlen(a); + char *b = calloc(1, (unsigned long)len + 1); + int ret = 0; + if (b == NULL) { + return -ENOMEM; + } + ret = tcp_sync_data(index, len, (char *)a, b); + if (ret != 0) { + LOG_ERROR("sync time error, %s, ret: %d.\n", a, ret); + goto sync_ret; + } + ret = memcmp(a, b, (unsigned long)len); + if (ret != 0) { + b[len] = '\0'; + LOG_ERROR("sync time error, %s != %s.\n", a, b); + goto sync_ret; + } + +sync_ret: + free(b); + return ret; +} + +ssize_t tcp_comm_send(uint32_t index, const void *buf, size_t size) +{ + if (comm_ctx.sock_fd == NULL || index >= comm_ctx.sock_num || comm_ctx.sock_fd[index] < 0) { + errno = EINVAL; + return -1; + } + return send(comm_ctx.sock_fd[index], buf, size, MSG_NOSIGNAL); +} + +ssize_t tcp_comm_recv(uint32_t index, void *buf, size_t size) +{ + if (comm_ctx.sock_fd == NULL || index >= comm_ctx.sock_num || comm_ctx.sock_fd[index] < 0) { + errno = EINVAL; + return -1; + } + return recv(comm_ctx.sock_fd[index], buf, size, MSG_PEEK); +} + +int tcp_comm_poll(uint32_t index, int timeout_ms) +{ + if (comm_ctx.sock_fd == NULL || index >= comm_ctx.sock_num || comm_ctx.sock_fd[index] < 0) { + errno = EINVAL; + return -1; + } + + struct pollfd pfd = { + .fd = comm_ctx.sock_fd[index], + .events = POLLIN, + .revents = 0, + }; + int ret = poll(&pfd, 1, timeout_ms); + if (ret > 0 && (pfd.revents & (POLLIN | POLLHUP | POLLERR)) == 0) { + errno = EIO; + return -1; + } + return ret; +} diff --git a/src/ub_bench_mgmt_tcp.h b/src/ub_bench_mgmt_tcp.h new file mode 100644 index 0000000..756fe8e --- /dev/null +++ b/src/ub_bench_mgmt_tcp.h @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + * Description: tcp management header file for ub_bench + * Create: 2026-09-16 + * Note: + * History: 2026-09-16 create file + */ + +#ifndef UB_BENCH_MGMT_TCP_H +#define UB_BENCH_MGMT_TCP_H + +#include +#include +#include +#include + +typedef struct comm_tcp_cfg { + char *server_ip; + char *bind_ip; + bool enable_ipv6; + uint16_t port; /* Server port for bind or connect, default 21115. */ + uint32_t sock_num; +} comm_tcp_cfg_t; + +int tcp_establish_connection(const comm_tcp_cfg_t *cfg); +void tcp_close_connection(void); + +int tcp_sync_data(uint32_t index, int size, char *local_data, char *remote_data); +int tcp_sync_time(uint32_t index, const char *a); +ssize_t tcp_comm_send(uint32_t index, const void *buf, size_t size); +ssize_t tcp_comm_recv(uint32_t index, void *buf, size_t size); +int tcp_comm_poll(uint32_t index, int timeout_ms); + +#endif diff --git a/src/ub_bench_mgmt_ub.c b/src/ub_bench_mgmt_ub.c new file mode 100644 index 0000000..ed9bf68 --- /dev/null +++ b/src/ub_bench_mgmt_ub.c @@ -0,0 +1,880 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + * Description: ub management channel implementation for ub_bench + * Create: 2026-09-16 + * Note: UB mgmt channel over URMA RM. Local EID from --mgmt_addr. + * Single pair only. Server must start before client. + * History: 2026-09-16 create file + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "urma_api.h" +#include "ub_bench_log.h" +#include "ub_bench_parameters.h" +#include "ub_bench_run_test.h" + +#include "ub_bench_mgmt_ub.h" + +#define UB_MGMT_TOKEN_VALUE (0xABCDEF) /* same as g_perftest_token in perftest_resources.c */ +#define UB_MGMT_HANDSHAKE_SIZE (1) +#define UB_MGMT_SEG_ALIGN (4096) /* UMMU table mode requires 4K-aligned VA */ +#define UB_MGMT_BONDING_DEV_PREFIX "bonding_dev" +#define UB_MGMT_BONDING_DEV_PREFIX_LEN (11) + +static urma_token_t g_ub_mgmt_token = { + .token = UB_MGMT_TOKEN_VALUE, +}; + +typedef struct ub_jpair { + urma_jetty_t *jetty; /* local mgmt jetty */ + urma_target_jetty_t *tjetty; /* peer mgmt jetty (after handshake) */ + /* comm_poll/comm_recv coordination: UB has no kernel-buffered "readable" + * state; poll posts a 1B probe recv, the CQE+data remain for comm_recv. */ + bool have_pending_recv; /* probe recv posted, not yet completed */ + bool have_pending_data; /* probe recv completed, recv_buf has data */ + uint32_t pending_data_len; /* valid when have_pending_data == true */ +} ub_jpair_t; + +typedef struct ub_mgmt_ctx { + urma_context_t *urma_ctx; + urma_device_t *dev; + urma_jfc_t *jfc; /* shared jfc for both send and recv CQE */ + urma_jfr_t *jfr; /* shared jfr (UB requires share_jfr) */ + char *send_buf; /* shared 4KB buffer for SEND */ + char *recv_buf; /* shared 4KB buffer for data RECV */ + char *recv_buf_hs; /* dedicated 4KB buffer for handshake RECV (server only) */ + urma_target_seg_t *tseg_send; /* registered seg covering send_buf */ + urma_target_seg_t *tseg_recv; /* registered seg covering recv_buf */ + urma_target_seg_t *tseg_recv_hs; /* registered seg covering recv_buf_hs (server only) */ + bool is_server; + ub_jpair_t pair; /* single pair (UB mgmt does not support multi-pair) */ +} ub_mgmt_ctx_t; + +static ub_mgmt_ctx_t *g_ub_ctx = NULL; + +/* ========================================================================== */ +/* internal helpers */ +/* ========================================================================== */ + +/* Build rjetty descriptor for importing peer mgmt jetty. tp_type=CTP. */ +static void fill_rjetty(urma_eid_t peer_eid, uint32_t jetty_id, urma_rjetty_t *rjetty) +{ + (void)memset(rjetty, 0, sizeof(*rjetty)); + rjetty->jetty_id.eid = peer_eid; + rjetty->jetty_id.id = jetty_id; + rjetty->trans_mode = URMA_TM_RM; + rjetty->type = URMA_JETTY; + rjetty->tp_type = URMA_CTP; +} + +static int poll_one_cqe(urma_jfc_t *jfc, urma_cr_t *cr, bool interruptible) +{ + while (true) { + if (interruptible && g_exit_flag) { + return -EINTR; + } + int n = urma_poll_jfc(jfc, 1, cr); + if (n < 0) { + LOG_ERROR("Failed to poll jfc.\n"); + return -1; + } + if (n == 1) { + if (cr->status != URMA_CR_SUCCESS) { + LOG_ERROR("mgmt CR status %d (s_r=%u).\n", (int)cr->status, cr->flag.bs.s_r); + return -1; + } + return 0; + } + } +} + +/* Post one max-size RECV WR. Used for pre-post at handshake and refill in sync_data. */ +static int ub_post_one_recv(ub_mgmt_ctx_t *ctx) +{ + urma_sge_t sge = {0}; + urma_jfr_wr_t wr = {0}; + urma_jfr_wr_t *bad = NULL; + sge.addr = (uint64_t)ctx->recv_buf; + sge.len = UB_MGMT_MSG_MAX_SIZE; + sge.tseg = ctx->tseg_recv; + wr.src.sge = &sge; + wr.src.num_sge = 1; + wr.next = NULL; + return (urma_post_jetty_recv_wr(ctx->pair.jetty, &wr, &bad) == URMA_SUCCESS) ? 0 : -1; +} + +/* Resolve (dev, eid_index) from src EID string. Mirrors ping_run.c:init_urma_resource. */ +static int ub_resolve_dev_and_eid_idx(const char *src_eid_str, urma_device_t **dev_out, uint32_t *eid_idx_out) +{ + urma_eid_t src_eid = {0}; + urma_device_t *dev = NULL; + urma_eid_info_t *eid_list = NULL; + uint32_t eid_cnt = 0; + uint32_t i = 0; + bool found = false; + + if (urma_str_to_eid(src_eid_str, &src_eid) != 0) { + LOG_ERROR("Failed to parse src eid: %s\n", src_eid_str); + return -1; + } + + dev = urma_get_device_by_eid(src_eid, URMA_TRANSPORT_UB); + if (dev == NULL) { + LOG_ERROR("Failed to find UB device for src eid: %s\n", src_eid_str); + return -1; + } + + /* Reject bonding (aggregation) devices: mgmt channel needs a bare UB device. */ + if (strncmp(dev->name, UB_MGMT_BONDING_DEV_PREFIX, UB_MGMT_BONDING_DEV_PREFIX_LEN) == 0) { + LOG_ERROR("UB mgmt channel does not support bonding device: %s\n", dev->name); + return -1; + } + + eid_list = urma_get_eid_list(dev, &eid_cnt); + if (eid_list == NULL) { + LOG_ERROR("Failed to get eid list from device: %s\n", dev->name); + return -1; + } + for (i = 0; i < eid_cnt; i++) { + if (memcmp(&src_eid, &eid_list[i].eid, sizeof(urma_eid_t)) == 0) { + *eid_idx_out = eid_list[i].eid_index; + found = true; + break; + } + } + urma_free_eid_list(eid_list); + if (!found) { + LOG_ERROR("Src eid not found in device eid list: dev=%s, eid=%s\n", dev->name, src_eid_str); + return -1; + } + + *dev_out = dev; + return 0; +} + +/* ========================================================================== */ +/* establish / close */ +/* ========================================================================== */ + +static int ub_create_local_resources(const comm_ub_cfg_t *cfg, ub_mgmt_ctx_t **ctx_out) +{ + urma_device_t *dev = NULL; + urma_context_t *urma_ctx = NULL; + urma_jfc_t *jfc = NULL; + urma_jfr_t *jfr = NULL; + urma_target_seg_t *tseg_send = NULL; + urma_target_seg_t *tseg_recv = NULL; + urma_target_seg_t *tseg_recv_hs = NULL; + char *send_buf = NULL; + char *recv_buf = NULL; + char *recv_buf_hs = NULL; + urma_jfc_cfg_t jfc_cfg = {0}; + urma_seg_cfg_t seg_cfg = {0}; + urma_jfs_cfg_t jfs_cfg = {0}; + urma_jfr_cfg_t jfr_cfg = {0}; + urma_jetty_cfg_t jetty_cfg = {0}; + ub_mgmt_ctx_t *ctx = NULL; + uint32_t local_jetty_id = 0; + uint32_t eid_idx = 0; + + if (ub_resolve_dev_and_eid_idx(cfg->src_eid, &dev, &eid_idx) != 0) { + return -1; + } + + urma_ctx = urma_create_context(dev, eid_idx); + if (urma_ctx == NULL) { + LOG_ERROR("Failed to create urma context.\n"); + return -1; + } + + jfc_cfg.depth = UB_MGMT_JFC_DEPTH; + jfc_cfg.flag.value = 0; + jfc_cfg.jfce = NULL; + jfc_cfg.user_ctx = (uint64_t)NULL; + /* urma_create_jfc (not alloc_jfc) initializes the CQ buffer; alloc_jfc leaves it NULL. */ + jfc = urma_create_jfc(urma_ctx, &jfc_cfg); + if (jfc == NULL) { + LOG_ERROR("Failed to create mgmt jfc.\n"); + goto delete_ctx; + } + + send_buf = (char *)memalign(UB_MGMT_SEG_ALIGN, UB_MGMT_MSG_MAX_SIZE); + recv_buf = (char *)memalign(UB_MGMT_SEG_ALIGN, UB_MGMT_MSG_MAX_SIZE); + recv_buf_hs = (char *)memalign(UB_MGMT_SEG_ALIGN, UB_MGMT_MSG_MAX_SIZE); + if (send_buf == NULL || recv_buf == NULL || recv_buf_hs == NULL) { + LOG_ERROR("Failed to alloc mgmt buffers.\n"); + goto free_jfc; + } + memset(send_buf, 0, UB_MGMT_MSG_MAX_SIZE); + memset(recv_buf, 0, UB_MGMT_MSG_MAX_SIZE); + memset(recv_buf_hs, 0, UB_MGMT_MSG_MAX_SIZE); + + seg_cfg.len = UB_MGMT_MSG_MAX_SIZE; + seg_cfg.token_id = NULL; + seg_cfg.token_value = g_ub_mgmt_token; + seg_cfg.flag.value = 0; + seg_cfg.flag.bs.access = URMA_ACCESS_LOCAL_ONLY; + seg_cfg.user_ctx = (uint64_t)NULL; + seg_cfg.iova = 0; + + seg_cfg.va = (uint64_t)send_buf; + tseg_send = urma_register_seg(urma_ctx, &seg_cfg); + if (tseg_send == NULL) { + LOG_ERROR("Failed to register mgmt send seg.\n"); + goto free_bufs; + } + + seg_cfg.va = (uint64_t)recv_buf; + tseg_recv = urma_register_seg(urma_ctx, &seg_cfg); + if (tseg_recv == NULL) { + LOG_ERROR("Failed to register mgmt recv seg.\n"); + goto unregister_send; + } + + /* + * Dedicated handshake recv seg. Server posts 2 recvs at handshake entry: + * WQE[0] = handshake (1B) on recv_buf_hs + * WQE[1] = data (4KB) on recv_buf + * Both stay outstanding before peer's first send arrives, eliminating the + * race where peer's sync_data SEND reaches our RQ before stage 6.5's + * post_recv runs (RNR in UB+RM, no kernel buffering). + * + * recv_buf_hs is unused on client; allocated unconditionally to keep the + * ctx layout symmetric and simplify rollback/cleanup paths. + */ + seg_cfg.va = (uint64_t)recv_buf_hs; + tseg_recv_hs = urma_register_seg(urma_ctx, &seg_cfg); + if (tseg_recv_hs == NULL) { + LOG_ERROR("Failed to register mgmt handshake recv seg.\n"); + goto unregister_recv; + } + + jfs_cfg.depth = UB_MGMT_JFS_DEPTH; + jfs_cfg.flag.value = 0; + jfs_cfg.trans_mode = URMA_TM_RM; + jfs_cfg.priority = 0; + jfs_cfg.max_sge = 1; + jfs_cfg.max_rsge = 1; + jfs_cfg.max_inline_data = 0; /* mgmt uses registered seg, no inline needed */ + jfs_cfg.rnr_retry = URMA_TYPICAL_RNR_RETRY; + jfs_cfg.err_timeout = URMA_TYPICAL_ERR_TIMEOUT; + jfs_cfg.jfc = jfc; + jfs_cfg.user_ctx = (uint64_t)NULL; + + jfr_cfg.id = 0; + jfr_cfg.depth = UB_MGMT_JFR_DEPTH; + jfr_cfg.flag.value = 0; + jfr_cfg.trans_mode = URMA_TM_RM; + jfr_cfg.max_sge = 1; + jfr_cfg.min_rnr_timer = URMA_TYPICAL_MIN_RNR_TIMER; + jfr_cfg.jfc = jfc; + jfr_cfg.token_value = g_ub_mgmt_token; + jfr_cfg.user_ctx = (uint64_t)NULL; + + /* UB dev requires share_jfr (urma_cp_api.c:1563). Create 1 shared jfr + * here, then attach it to the mgmt jetty via jetty_cfg.shared.jfr. */ + jfr = urma_create_jfr(urma_ctx, &jfr_cfg); + if (jfr == NULL) { + LOG_ERROR("Failed to create mgmt shared jfr.\n"); + goto unregister_recv_hs; + } + + ctx = (ub_mgmt_ctx_t *)calloc(1, sizeof(ub_mgmt_ctx_t)); + if (ctx == NULL) { + LOG_ERROR("Failed to alloc mgmt ctx.\n"); + goto delete_jfr; + } + ctx->urma_ctx = urma_ctx; + ctx->dev = dev; + ctx->jfc = jfc; + ctx->jfr = jfr; + ctx->send_buf = send_buf; + ctx->recv_buf = recv_buf; + ctx->recv_buf_hs = recv_buf_hs; + ctx->tseg_send = tseg_send; + ctx->tseg_recv = tseg_recv; + ctx->tseg_recv_hs = tseg_recv_hs; + ctx->is_server = (cfg->dst_eid == NULL); + + if (ctx->is_server) { + local_jetty_id = cfg->dst_jetty_id; + } else { + local_jetty_id = 0; + } + + jetty_cfg.id = local_jetty_id; + jetty_cfg.flag.value = 0; + jetty_cfg.flag.bs.share_jfr = URMA_SHARE_JFR; + jetty_cfg.jfs_cfg = jfs_cfg; + jetty_cfg.shared.jfr = jfr; + jetty_cfg.shared.jfc = jfc; + jetty_cfg.jetty_grp = NULL; + jetty_cfg.user_ctx = (uint64_t)NULL; + + ctx->pair.jetty = urma_create_jetty(urma_ctx, &jetty_cfg); + if (ctx->pair.jetty == NULL) { + LOG_ERROR("Failed to create mgmt jetty.\n"); + goto free_ctx; + } + ctx->pair.tjetty = NULL; + + *ctx_out = ctx; + return 0; + +free_ctx: + free(ctx); +delete_jfr: + (void)urma_delete_jfr(jfr); +unregister_recv_hs: + (void)urma_unregister_seg(tseg_recv_hs); +unregister_recv: + (void)urma_unregister_seg(tseg_recv); +unregister_send: + (void)urma_unregister_seg(tseg_send); +free_bufs: + free(send_buf); + free(recv_buf); + free(recv_buf_hs); +free_jfc: + (void)urma_delete_jfc(jfc); +delete_ctx: + (void)urma_delete_context(urma_ctx); + return -1; +} + +/* + * Server handshake: post [handshake recv (1B), data recv (4KB)] in FIFO order, + * poll handshake CQE, then import peer jetty via cr.remote_id (HW-filled). + * Data recv stays outstanding across handshake to avoid RNR race with the + * first sync_data send. Dedicated handshake buffer avoids overwriting by data. + */ +static int ub_server_handshake(ub_mgmt_ctx_t *ctx) +{ + urma_sge_t sge = {0}; + urma_jfr_wr_t wr = {0}; + urma_jfr_wr_t *bad = NULL; + urma_cr_t cr = {0}; + urma_rjetty_t rjetty = {0}; + + LOG_INFO(PERFTEST_RESULT_LINE); + LOG_INFO(" Waiting for client to connect...\n"); + + /* WQE[0]: handshake recv (1B). */ + sge.addr = (uint64_t)ctx->recv_buf_hs; + sge.len = UB_MGMT_HANDSHAKE_SIZE; + sge.tseg = ctx->tseg_recv_hs; + wr.src.sge = &sge; + wr.src.num_sge = 1; + wr.user_ctx = 0; + wr.next = NULL; + if (urma_post_jetty_recv_wr(ctx->pair.jetty, &wr, &bad) != URMA_SUCCESS) { + LOG_ERROR("Failed to post handshake recv.\n"); + return -1; + } + + /* WQE[1]: data recv (4KB), stays in RQ across handshake. */ + sge.addr = (uint64_t)ctx->recv_buf; + sge.len = UB_MGMT_MSG_MAX_SIZE; + sge.tseg = ctx->tseg_recv; + wr.src.sge = &sge; + wr.src.num_sge = 1; + wr.user_ctx = 0; + wr.next = NULL; + if (urma_post_jetty_recv_wr(ctx->pair.jetty, &wr, &bad) != URMA_SUCCESS) { + LOG_ERROR("Failed to pre-post data recv.\n"); + return -1; + } + + if (poll_one_cqe(ctx->jfc, &cr, true) != 0) { + return -1; + } + + /* cr.remote_id (eid + id) is filled by HW on RECV. */ + fill_rjetty(cr.remote_id.eid, cr.remote_id.id, &rjetty); + ctx->pair.tjetty = urma_import_jetty(ctx->urma_ctx, &rjetty, &g_ub_mgmt_token); + if (ctx->pair.tjetty == NULL) { + LOG_ERROR("Failed to import peer mgmt jetty (handshake).\n"); + return -1; + } + return 0; +} + +/* Client handshake: parse server EID, import server jetty, post 1B send, poll CQE. */ +static int ub_client_handshake(ub_mgmt_ctx_t *ctx, const char *peer_eid_str, uint32_t peer_jetty_id) +{ + urma_eid_t peer_eid = {0}; + urma_rjetty_t rjetty = {0}; + urma_sge_t sge = {0}; + urma_jfs_wr_t wr = {0}; + urma_jfs_wr_t *bad = NULL; + urma_cr_t cr = {0}; + + if (urma_str_to_eid(peer_eid_str, &peer_eid) != 0) { + LOG_ERROR("Failed to parse peer eid: %s\n", peer_eid_str); + return -1; + } + + fill_rjetty(peer_eid, peer_jetty_id, &rjetty); + ctx->pair.tjetty = urma_import_jetty(ctx->urma_ctx, &rjetty, &g_ub_mgmt_token); + if (ctx->pair.tjetty == NULL) { + LOG_ERROR("Failed to import peer mgmt jetty.\n"); + return -1; + } + + ctx->send_buf[0] = 'H'; + sge.addr = (uint64_t)ctx->send_buf; + sge.len = UB_MGMT_HANDSHAKE_SIZE; + sge.tseg = ctx->tseg_send; + wr.opcode = URMA_OPC_SEND; + wr.flag.value = 0; + wr.flag.bs.complete_enable = 1; + wr.tjetty = ctx->pair.tjetty; + wr.user_ctx = 0; + wr.send.src.sge = &sge; + wr.send.src.num_sge = 1; + wr.send.imm_data = 0; + wr.next = NULL; + + if (urma_post_jetty_send_wr(ctx->pair.jetty, &wr, &bad) != URMA_SUCCESS) { + LOG_ERROR("Failed to post handshake send.\n"); + return -1; + } + + if (poll_one_cqe(ctx->jfc, &cr, true) != 0) { + return -1; + } + return 0; +} + +int ub_establish_connection(const comm_ub_cfg_t *cfg) +{ + ub_mgmt_ctx_t *ctx = NULL; + urma_init_attr_t init_attr = { .token = 0, .uasid = 0 }; + urma_status_t status; + + if (cfg == NULL) { + return -EINVAL; + } + + /* mgmt channel runs before init_device; must urma_init here. Tolerate EEXIST. */ + status = urma_init(&init_attr); + if (status != URMA_SUCCESS && status != URMA_EEXIST) { + LOG_ERROR("Failed to urma_init for mgmt channel, status: %d.\n", (int)status); + return -1; + } + + if (cfg->src_eid == NULL || cfg->src_eid[0] == '\0') { + LOG_ERROR("Invalid mgmt ub cfg: src_eid missing.\n"); + return -EINVAL; + } + if (cfg->dst_eid != NULL && cfg->dst_eid[0] == '\0') { + LOG_ERROR("Invalid mgmt ub cfg: empty dst_eid.\n"); + return -EINVAL; + } + if (g_ub_ctx != NULL) { + LOG_ERROR("mgmt ub ctx already initialized.\n"); + return -EEXIST; + } + + if (cfg->dst_eid == NULL && cfg->dst_jetty_id == 0) { + LOG_ERROR("UB mgmt server requires -P for jetty id.\n"); + return -EINVAL; + } + + if (ub_create_local_resources(cfg, &ctx) != 0) { + return -1; + } + + if (ctx->is_server) { + if (ub_server_handshake(ctx) != 0) { + goto rollback_handshake; + } + } else { + if (ub_client_handshake(ctx, cfg->dst_eid, cfg->dst_jetty_id) != 0) { + goto rollback_handshake; + } + } + + /* + * Pre-post one data RECV on client before any sync_data send. Server + * already has data recv pre-posted inside ub_server_handshake (WQE[1]). + * Without this, peer's SEND arriving at an empty RQ exhausts rnr_retry + * in RM mode (UB has no kernel buffering). Mirrors ping_run.c:682. + */ + if (!ctx->is_server) { + if (ub_post_one_recv(ctx) != 0) { + LOG_ERROR("Failed to pre-post mgmt recv.\n"); + goto rollback_handshake; + } + } + ctx->pair.have_pending_recv = !ctx->is_server; + + g_ub_ctx = ctx; + return 0; + +rollback_handshake: + if (ctx->pair.tjetty != NULL) { + (void)urma_unimport_jetty(ctx->pair.tjetty); + ctx->pair.tjetty = NULL; + } + if (ctx->pair.jetty != NULL) { + (void)urma_delete_jetty(ctx->pair.jetty); + } + (void)urma_delete_jfr(ctx->jfr); + (void)urma_unregister_seg(ctx->tseg_recv_hs); + (void)urma_unregister_seg(ctx->tseg_recv); + (void)urma_unregister_seg(ctx->tseg_send); + free(ctx->send_buf); + free(ctx->recv_buf); + free(ctx->recv_buf_hs); + (void)urma_delete_jfc(ctx->jfc); + (void)urma_delete_context(ctx->urma_ctx); + free(ctx); + return -1; +} + +void ub_close_connection(void) +{ + if (g_ub_ctx == NULL) { + return; + } + + /* Caller (destroy_*_ctx) must invoke close_connection BEFORE uninit_device: + * urma_uninit dlclose()'s provider .so, after which ctx->ops dangles. */ + if (g_ub_ctx->pair.tjetty != NULL) { + (void)urma_unimport_jetty(g_ub_ctx->pair.tjetty); + g_ub_ctx->pair.tjetty = NULL; + } + if (g_ub_ctx->pair.jetty != NULL) { + (void)urma_delete_jetty(g_ub_ctx->pair.jetty); + g_ub_ctx->pair.jetty = NULL; + } + if (g_ub_ctx->jfr != NULL) { + (void)urma_delete_jfr(g_ub_ctx->jfr); + g_ub_ctx->jfr = NULL; + } + if (g_ub_ctx->tseg_recv != NULL) { + (void)urma_unregister_seg(g_ub_ctx->tseg_recv); + g_ub_ctx->tseg_recv = NULL; + } + if (g_ub_ctx->tseg_recv_hs != NULL) { + (void)urma_unregister_seg(g_ub_ctx->tseg_recv_hs); + g_ub_ctx->tseg_recv_hs = NULL; + } + if (g_ub_ctx->tseg_send != NULL) { + (void)urma_unregister_seg(g_ub_ctx->tseg_send); + g_ub_ctx->tseg_send = NULL; + } + if (g_ub_ctx->jfc != NULL) { + (void)urma_delete_jfc(g_ub_ctx->jfc); + g_ub_ctx->jfc = NULL; + } + if (g_ub_ctx->urma_ctx != NULL) { + (void)urma_delete_context(g_ub_ctx->urma_ctx); + g_ub_ctx->urma_ctx = NULL; + } + free(g_ub_ctx->send_buf); + g_ub_ctx->send_buf = NULL; + free(g_ub_ctx->recv_buf); + g_ub_ctx->recv_buf = NULL; + free(g_ub_ctx->recv_buf_hs); + g_ub_ctx->recv_buf_hs = NULL; + free(g_ub_ctx); + g_ub_ctx = NULL; +} + +/* ========================================================================== */ +/* sync_data / sync_time */ +/* ========================================================================== */ + +int ub_sync_data(uint32_t index, int size, char *local_data, char *remote_data) +{ + urma_sge_t send_sge = {0}; + urma_jfs_wr_t send_wr = {0}; + urma_jfs_wr_t *send_bad = NULL; + urma_cr_t cr = {0}; + int pending = 2; /* expect 1 send CQE + 1 recv CQE */ + + (void)index; /* single pair; index ignored */ + + if (g_ub_ctx == NULL || size <= 0 || size > UB_MGMT_MSG_MAX_SIZE || + local_data == NULL || remote_data == NULL) { + LOG_ERROR("Invalid ub_sync_data args: ctx=%p, size=%d\n", (void *)g_ub_ctx, size); + return -EINVAL; + } + + /* + * RECV is already pre-posted (ub_establish_connection) and refilled + * after each consumption below. DO NOT post recv here: posting recv + * synchronously with peer's post_send races in UB+RM. UB has no kernel + * buffering (unlike TCP), so the brief window where peer's SEND arrives + * before our post_recv is processed by HW exhausts rnr_retry=7 in RM + * mode. Same idiom as ping_run.c:682 (pre-post before send) and + * urma_sample.c:624 (pre-post RECV_BATCH_CNT before accept loop). + */ + + /* copy local_data into registered send_buf */ + (void)memcpy(g_ub_ctx->send_buf, local_data, (size_t)size); + + /* post send (buffer=send_buf) */ + send_sge.addr = (uint64_t)g_ub_ctx->send_buf; + send_sge.len = (uint32_t)size; + send_sge.tseg = g_ub_ctx->tseg_send; + send_wr.opcode = URMA_OPC_SEND; + send_wr.flag.value = 0; + send_wr.flag.bs.complete_enable = 1; + send_wr.tjetty = g_ub_ctx->pair.tjetty; + send_wr.user_ctx = 0; + send_wr.send.src.sge = &send_sge; + send_wr.send.src.num_sge = 1; + send_wr.send.imm_data = 0; + send_wr.next = NULL; + if (urma_post_jetty_send_wr(g_ub_ctx->pair.jetty, &send_wr, &send_bad) != URMA_SUCCESS) { + LOG_ERROR("Failed to post mgmt send wr.\n"); + return -1; + } + + /* poll for 2 CQEs (send + recv); refill recv immediately on recv CQE. */ + while (pending > 0) { + if (g_exit_flag) { + return -EINTR; + } + int n = urma_poll_jfc(g_ub_ctx->jfc, 1, &cr); + if (n < 0) { + LOG_ERROR("Failed to poll jfc.\n"); + return -1; + } + if (n == 1) { + if (cr.status != URMA_CR_SUCCESS) { + LOG_ERROR("mgmt CR status %d (s_r=%u).\n", (int)cr.status, cr.flag.bs.s_r); + return -1; + } + if (cr.flag.bs.s_r == 1) { /* recv CQE: refill */ + if (ub_post_one_recv(g_ub_ctx) != 0) { + LOG_ERROR("Failed to refill mgmt recv.\n"); + return -1; + } + } + pending--; + } + } + + /* copy recv_buf into remote_data. After this call RQ has 1 recv (the + * refill above), have_pending_recv=false (not a probe). Caller may + * immediately call comm_poll, which will skip posting a duplicate probe. */ + (void)memcpy(remote_data, g_ub_ctx->recv_buf, (size_t)size); + return 0; +} + +int ub_sync_time(uint32_t index, const char *tag) +{ + int len; + char *b = NULL; + int ret; + + if (tag == NULL) { + LOG_ERROR("Invalid parameter: tag is nullptr.\n"); + return -EINVAL; + } + len = (int)strlen(tag); + if (len >= UB_MGMT_MSG_MAX_SIZE) { + LOG_ERROR("sync_time tag too long: %d.\n", len); + return -EINVAL; + } + b = (char *)calloc(1, (size_t)len + 1); + if (b == NULL) { + return -ENOMEM; + } + ret = ub_sync_data(index, len, (char *)tag, b); + if (ret != 0) { + LOG_ERROR("sync_time ub error, tag: %s, ret: %d.\n", tag, ret); + free(b); + return ret; + } + ret = (memcmp(tag, b, (size_t)len) == 0) ? 0 : -1; + if (ret != 0) { + b[len] = '\0'; + LOG_ERROR("sync_time ub mismatch: %s != %s.\n", tag, b); + } + free(b); + return ret; +} + +/* ========================================================================== */ +/* comm_send / comm_recv / comm_poll (infinite BW mode control flow) */ +/* ========================================================================== */ + +ssize_t ub_comm_send(uint32_t index, const void *buf, size_t size) +{ + urma_sge_t sge = {0}; + urma_jfs_wr_t wr = {0}; + urma_jfs_wr_t *bad = NULL; + urma_cr_t cr = {0}; + + (void)index; /* single pair; index ignored */ + + if (g_ub_ctx == NULL || buf == NULL || size == 0 || size > UB_MGMT_MSG_MAX_SIZE) { + return -EINVAL; + } + (void)memcpy(g_ub_ctx->send_buf, buf, size); + + sge.addr = (uint64_t)g_ub_ctx->send_buf; + sge.len = (uint32_t)size; + sge.tseg = g_ub_ctx->tseg_send; + wr.opcode = URMA_OPC_SEND; + wr.flag.bs.complete_enable = 1; + wr.tjetty = g_ub_ctx->pair.tjetty; + wr.send.src.sge = &sge; + wr.send.src.num_sge = 1; + wr.next = NULL; + + if (urma_post_jetty_send_wr(g_ub_ctx->pair.jetty, &wr, &bad) != URMA_SUCCESS) { + LOG_ERROR("Failed to post comm_send wr.\n"); + return -1; + } + + if (poll_one_cqe(g_ub_ctx->jfc, &cr, false) != 0) { + return -1; + } + return (ssize_t)size; +} + +ssize_t ub_comm_recv(uint32_t index, void *buf, size_t size) +{ + urma_sge_t sge = {0}; + urma_jfr_wr_t wr = {0}; + urma_jfr_wr_t *bad = NULL; + urma_cr_t cr = {0}; + + (void)index; /* single pair; index ignored */ + + if (g_ub_ctx == NULL || buf == NULL || size == 0 || size > UB_MGMT_MSG_MAX_SIZE) { + return -EINVAL; + } + + /* If comm_poll already captured probe data, return it directly. + * perftest's comm_recv is only for 1B PERFTEST_EXIT_CMD. */ + if (g_ub_ctx->pair.have_pending_data) { + if (size > 1) { + LOG_ERROR("comm_recv: size=%zu but pending probe data is 1B only.\n", size); + return -EINVAL; + } + (void)memcpy(buf, g_ub_ctx->recv_buf, 1); + g_ub_ctx->pair.have_pending_data = false; + g_ub_ctx->pair.pending_data_len = 0; + return 1; + } + + if (!g_ub_ctx->pair.have_pending_recv) { + /* Fresh recv: post with caller's size. */ + sge.addr = (uint64_t)g_ub_ctx->recv_buf; + sge.len = (uint32_t)size; + sge.tseg = g_ub_ctx->tseg_recv; + wr.src.sge = &sge; + wr.src.num_sge = 1; + wr.next = NULL; + if (urma_post_jetty_recv_wr(g_ub_ctx->pair.jetty, &wr, &bad) != URMA_SUCCESS) { + LOG_ERROR("Failed to post comm_recv wr.\n"); + return -1; + } + } else { + /* Probe recv in flight; just wait for its CQE. */ + g_ub_ctx->pair.have_pending_recv = false; + } + + if (poll_one_cqe(g_ub_ctx->jfc, &cr, true) != 0) { + return -1; + } + uint32_t got = cr.completion_len; + if (got > size) { + got = (uint32_t)size; + } + (void)memcpy(buf, g_ub_ctx->recv_buf, got); + return (ssize_t)got; +} + +/* + * UB equivalent of TCP poll(fd, POLLIN, timeout_ms). UB has no kernel + * buffering, so poll posts a 1B probe recv and non-blocking polls jfc + * until CQE arrives or timeout. Returns >0 if data ready, 0 on timeout. + */ +int ub_comm_poll(uint32_t index, int timeout_ms) +{ + urma_sge_t sge = {0}; + urma_jfr_wr_t wr = {0}; + urma_jfr_wr_t *bad = NULL; + + (void)index; /* single pair; index ignored */ + + if (g_ub_ctx == NULL) { + errno = EINVAL; + return -1; + } + if (timeout_ms < 0) { + timeout_ms = 0; + } + + /* If a previous poll already got data, return immediately. */ + if (g_ub_ctx->pair.have_pending_data) { + return 1; + } + + /* Post probe recv if not already in flight. */ + if (!g_ub_ctx->pair.have_pending_recv) { + sge.addr = (uint64_t)g_ub_ctx->recv_buf; + sge.len = 1; /* probe: just 1B to detect peer exit signal */ + sge.tseg = g_ub_ctx->tseg_recv; + wr.src.sge = &sge; + wr.src.num_sge = 1; + wr.next = NULL; + if (urma_post_jetty_recv_wr(g_ub_ctx->pair.jetty, &wr, &bad) != URMA_SUCCESS) { + LOG_ERROR("Failed to post comm_poll probe recv.\n"); + errno = EIO; + return -1; + } + g_ub_ctx->pair.have_pending_recv = true; + } + + /* Non-blocking poll loop with 1ms sleep, up to timeout_ms iterations. */ + struct timespec ts_start = {0}; + (void)clock_gettime(CLOCK_MONOTONIC, &ts_start); + uint64_t start_ms = (uint64_t)ts_start.tv_sec * 1000 + (uint64_t)ts_start.tv_nsec / 1000000; + + while (true) { + urma_cr_t cr = {0}; + int n = urma_poll_jfc(g_ub_ctx->jfc, 1, &cr); + if (n < 0) { + LOG_ERROR("Failed to poll jfc in comm_poll.\n"); + errno = EIO; + return -1; + } + if (n == 1) { + if (cr.status != URMA_CR_SUCCESS) { + LOG_ERROR("comm_poll CR status %d.\n", (int)cr.status); + errno = EIO; + return -1; + } + /* Probe completed: data is in recv_buf. Keep it for comm_recv. */ + g_ub_ctx->pair.have_pending_recv = false; + g_ub_ctx->pair.have_pending_data = true; + g_ub_ctx->pair.pending_data_len = cr.completion_len; + return 1; + } + + struct timespec ts_now = {0}; + (void)clock_gettime(CLOCK_MONOTONIC, &ts_now); + uint64_t now_ms = (uint64_t)ts_now.tv_sec * 1000 + (uint64_t)ts_now.tv_nsec / 1000000; + if (now_ms - start_ms >= (uint64_t)timeout_ms) { + return 0; /* timeout, probe recv still pending */ + } + (void)usleep(1000); /* 1ms backoff to avoid burning CPU */ + } +} diff --git a/src/ub_bench_mgmt_ub.h b/src/ub_bench_mgmt_ub.h new file mode 100644 index 0000000..6dc6b3d --- /dev/null +++ b/src/ub_bench_mgmt_ub.h @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + * Description: ub management header file for ub_bench + * Create: 2026-09-16 + * Note: + * History: 2026-09-16 create file + */ + +#ifndef UB_BENCH_MGMT_UB_H +#define UB_BENCH_MGMT_UB_H + +#include +#include +#include + +#define UB_MGMT_JFC_DEPTH (64) +#define UB_MGMT_JFS_DEPTH (8) +#define UB_MGMT_JFR_DEPTH (8) +#define UB_MGMT_MSG_MAX_SIZE (4096) + +typedef struct comm_ub_cfg { + char *src_eid; /* Local mgmt EID. Required. */ + char *dst_eid; /* Server mgmt EID, NULL on server side. */ + uint32_t dst_jetty_id; /* Server mgmt jetty id, 0 on client (driver auto-assigns). */ +} comm_ub_cfg_t; + +int ub_establish_connection(const comm_ub_cfg_t *cfg); +void ub_close_connection(void); + +int ub_sync_data(uint32_t index, int size, char *local_data, char *remote_data); +int ub_sync_time(uint32_t index, const char *tag); +ssize_t ub_comm_send(uint32_t index, const void *buf, size_t size); +ssize_t ub_comm_recv(uint32_t index, void *buf, size_t size); +int ub_comm_poll(uint32_t index, int timeout_ms); + +#endif diff --git a/src/ub_bench_parameters.c b/src/ub_bench_parameters.c new file mode 100644 index 0000000..056793d --- /dev/null +++ b/src/ub_bench_parameters.c @@ -0,0 +1,2000 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) Huawei Technologies Co., Ltd. 2026-2026. All rights reserved. + * Description: parse parameters for ub_bench + * Create: 2026-09-16 + * Note: + * History: 2026-09-16 create file + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "ub_get_clock.h" +#include "ub_util.h" +#include "urma_types.h" +#include "urma_types_str.h" +#include "urma_ubagg.h" + +#include "ub_bench_parameters.h" + +#define PERFTEST_CACHE_LINE_FILE_SIZE (10) +#define PERFTEST_JFC_MUL_THRESHOLD (4) +#define PERFTEST_DEFAULT_DURATION (5) + +#define PERFTEST_RTP_MAX_SEND_SIZE (65536) +#define PERFTEST_CTP_MAX_SEND_SIZE (4096) +#define PERFTEST_RTP_MAX_ORDER (16) +#define PERFTEST_CTP_MAX_ORDER (12) + +typedef struct perftest_cmd { + char *cmd; + perftest_cmd_type_t type; +} perftest_cmd_t; + +static const char *g_atomic_types_str[] = { + [PERFTEST_CAS] = "cas", + [PERFTEST_FAA] = "faa", +}; +static const char *g_print_test_str[] = { + [PERFTEST_READ] = "URMA_READ", + [PERFTEST_WRITE] = "URMA_WRITE", + [PERFTEST_SEND] = "URMA_SEND", + [PERFTEST_ATOMIC] = "URMA_ATOMIC", +}; +static const char *g_trans_mode_str[] = { + [URMA_TM_RM] = "URMA_TM_RM", + [URMA_TM_RC] = "URMA_TM_RC", + [URMA_TM_UM] = "URMA_TM_UM", +}; +static const char *g_jetty_mode_str[] = { + [PERFTEST_JETTY_SIMPLEX] = "SIMPLEX", + [PERFTEST_JETTY_DUPLEX] = "DUPLEX", +}; +static const char *g_bond_mode_str[] = { + [BONDP_BONDING_MODE_STANDALONE] = "standalone", + [BONDP_BONDING_MODE_ACTIVE_BACKUP] = "active_backup", + [BONDP_BONDING_MODE_BALANCE] = "balance", +}; +static const char *g_bond_level_str[] = { + [BONDP_BONDING_LEVEL_IODIE] = "iodie", + [BONDP_BONDING_LEVEL_PORT] = "port", +}; + +#define PERFTEST_BOOL_TO_STR(val) ((val) == true ? "true" : "false") + +static const perftest_cmd_t g_cmd[] = { + {"read_lat", PERFTEST_READ_LAT}, + {"write_lat", PERFTEST_WRITE_LAT}, + {"send_lat", PERFTEST_SEND_LAT}, + {"atomic_lat", PERFTEST_ATOMIC_LAT}, + {"read_bw", PERFTEST_READ_BW}, + {"write_bw", PERFTEST_WRITE_BW}, + {"send_bw", PERFTEST_SEND_BW}, + {"atomic_bw", PERFTEST_ATOMIC_BW}, +}; + +static void command_usage(const char *argv0) +{ + LOG_QUIET( + "Usage: %s command [command options]\n" + " %s UB benchmark tool\n" + "Command syntax:\n" + " read_lat Test for read latency.\n" + " write_lat Test for write latency.\n" + " send_lat Test for send latency.\n" + " atomic_lat Test for atomic latency.\n" + " read_bw Test for read bandwidth.\n" + " write_bw Test for write bandwidth.\n" + " send_bw Test for send bandwidth.\n" + " atomic_bw Test for atomic bandwidth.\n", + argv0, argv0); +} + +static void usage(const char *argv0) +{ + command_usage(argv0); + LOG_QUIET( + "Options:\n" + " -a, --all[order] Run sizes from 2 till 2^23,\n" + " default 2^12 for send, 2^16 for others, order: exponent of 2.\n" + " -A, --atomic_type Specify atomic type, {cas|faa}.\n" + " -b, --simplex_mode Run with simplex mode(jfs/jfr), duplex jetty mode for reserved.\n" + " -B, --bidirection Measure bidirectional bandwidth (default unidirectional).\n" + " -c, --jfc_inline Enable jfc_inline to upgrade latency performance.\n" + " -C, --jfc_depth Size of jfc depth (default 4096 for bw, 1024 for ip bw, 1 for lat.\n" + " -d, --dev The name of ubep device.\n" + " -D, --duration Run test for a customized period of seconds, this cfg covers iters.\n" + " -e, --use_jfce use jfc event.\n" + " --eid_idx Specified eid index of device.\n" + " -E, --err_timeout