diff options
| author | 刘煜 <[email protected]> | 2024-06-07 01:31:55 +0000 |
|---|---|---|
| committer | 刘煜 <[email protected]> | 2024-06-07 01:31:55 +0000 |
| commit | d91f658284d4250e1e055f3423c2ef3ee513314a (patch) | |
| tree | b2d97d0d7585ff6ff77360a468f9c3765016d6ae | |
| parent | 6e83a56c5926adec4045e08809b09c6f956e55f4 (diff) | |
第一个功能完成的版本
70 files changed, 6545 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c416a66 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +build +CMakeFiles +cmake_install.cmake +CMakeCache.txt +Makefile
\ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..ee23a3d --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**" + ], + "defines": [], + "compilerPath": "/usr/bin/clang-14", + "cStandard": "c17", + "cppStandard": "c++14", + "intelliSenseMode": "linux-clang-x64", + "configurationProvider": "ms-vscode.cmake-tools" + } + ], + "version": 4 +}
\ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2aff784 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,97 @@ +{ + "editor.formatOnSave": true, //保存时格式化开关 + "files.associations": { + "array": "c", + "atomic": "c", + "bit": "c", + "*.tcc": "c", + "cctype": "c", + "clocale": "c", + "cmath": "c", + "compare": "c", + "concepts": "c", + "cstdarg": "c", + "cstddef": "c", + "cstdint": "c", + "cstdio": "c", + "cstdlib": "c", + "cwchar": "c", + "cwctype": "c", + "deque": "c", + "string": "c", + "unordered_map": "c", + "vector": "c", + "exception": "c", + "algorithm": "c", + "functional": "c", + "iterator": "c", + "memory": "c", + "memory_resource": "c", + "numeric": "c", + "random": "c", + "string_view": "c", + "system_error": "c", + "tuple": "c", + "type_traits": "c", + "utility": "c", + "initializer_list": "c", + "iosfwd": "c", + "limits": "c", + "new": "c", + "numbers": "c", + "ostream": "c", + "stdexcept": "c", + "streambuf": "c", + "cinttypes": "c", + "typeinfo": "c", + "bbq_memory.h": "c", + "stdatomic.h": "c", + "bbq.h": "c", + "stdio.h": "c", + "stdbool.h": "c", + "bbq_errno.h": "c", + "stddef.h": "c", + "any": "cpp", + "condition_variable": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "map": "cpp", + "set": "cpp", + "optional": "cpp", + "ratio": "cpp", + "source_location": "cpp", + "iomanip": "cpp", + "iostream": "cpp", + "istream": "cpp", + "mutex": "cpp", + "semaphore": "cpp", + "sstream": "cpp", + "stop_token": "cpp", + "thread": "cpp", + "variant": "cpp", + "test.h": "c", + "demo.h": "c", + "math.h": "c", + "stdint.h": "c", + "minini.h": "c", + "common.h": "c", + "iniparser.h": "c", + "bbq_log.h": "c", + "string.h": "c", + "time.h": "c", + "unistd.h": "c", + "pthread.h": "c", + "bcm_queue.h": "c", + "rte_ring.h": "c", + "bcm_queue_dpdk.h": "c", + "stdlib.h": "c", + "bbq_common.h": "c", + "bcm_common.h": "c", + "test_common.h": "c", + "numa.h": "c", + "test_mix.h": "c", + "test_queue.h": "c", + "prctl.h": "c", + "types.h": "c" + } +}
\ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..142785d --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.0) +project(BBQ_TOP) + +# 添加子目录 +add_subdirectory(bbq) # bbq库 +add_subdirectory(perf)# perf目录用于各类消息队列的性能测试,需要依赖dpdk等环境
\ No newline at end of file diff --git a/bbq/CMakeLists.txt b/bbq/CMakeLists.txt new file mode 100644 index 0000000..1714295 --- /dev/null +++ b/bbq/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.0) +project(BBQ) + +# 头文件目录 +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +# 设置输出目录 +SET(OUTPUT_DIR ${PROJECT_SOURCE_DIR}/build/output) +# 库生成的路径 +set(LIB_PATH ${OUTPUT_DIR}/lib) +# 测试程序生成的路径 +set(EXEC_PATH ${OUTPUT_DIR}/bin) + +# 静态库的名字 +set(BBQ_LIB bbq) +# 可执行程序的名字 +set(TESTS_NAME tests) + +enable_testing() # 开启测试,否则无法执行make test + +# 添加子目录 +add_subdirectory(src) +add_subdirectory(tests)
\ No newline at end of file diff --git a/bbq/include/bbq.h b/bbq/include/bbq.h new file mode 100644 index 0000000..61d4f66 --- /dev/null +++ b/bbq/include/bbq.h @@ -0,0 +1,238 @@ +/* + * @Author: [email protected] + * @LastEditTime: 2024-06-06 10:07:17 + * @Describe: bbq(Block-based Bounded Queue)头文件 + * 参考:https://www.usenix.org/system/files/atc22-wang-jiawei.pdf + */ + +#ifndef _BBQ_H_ +#define _BBQ_H_ + +#include <stdatomic.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +// #define BBQ_MEMORY +// #define BBQ_DEBUG + +#ifndef __cplusplus +// C +typedef atomic_uint_fast64_t aotmic_uint64; +typedef aotmic_uint64 bbq_cursor; +typedef aotmic_uint64 bbq_head; +#else +// C++ 为了兼容gtest测试 +using bbq_cursor = std::atomic<uint64_t>; +using bbq_head = std::atomic<uint64_t>; +using aotmic_uint64 = std::atomic<uint64_t>; +#endif + +typedef struct { + bbq_cursor committed; // 已提交(version|offset) + bbq_cursor allocated; // 已分配(version|offset) + bbq_cursor reserved; // 已预留(version|offset) + bbq_cursor consumed; // 已消费(version|offset), 在drop-old模式下没用 + char *entries; // BS大小的动态数组 +} bbq_block_s; + +typedef enum { + BBQ_SUCCESS = 0, + BBQ_BLOCK_DONE, // 当前块已的entry已用完,需要移动到下一个块 + BBQ_NO_ENTRY, // 没有条目可以使用 + BBQ_NOT_AVAILABLE, // 当前块不可以用状态(将返回busy) + BBQ_ALLOCATED, // 已分配,返回entry信息 + BBQ_RESERVED, // 已保留,返回entry信息 +} bbq_queue_state_e; + +typedef struct { + bbq_block_s *block; // 指向所在的block + uint64_t bbq_off; // entry在当前block的偏移(offset) + uint64_t vsn; // allocated游标的版本(version) +} bbq_entry_desc_s; + +typedef struct { + bbq_queue_state_e state; // 队列状态 + union { + uint64_t vsn; // state==BLOCK_DONE时生效 + bbq_entry_desc_s e; // state为ALLOCATED、RESERVED生效 + }; +} bbq_queue_state_s; + +#define BBQ_F_DEFAULT 0x0 +// flags 第一位控制入队策略,默认是retry new +#define BBQ_F_POLICY_DROP_OLD 0x0001 +#define BBQ_F_POLICY_RETRY_NEW BBQ_F_DEFAULT +#define BBQ_POLIC_DROP_OLD(flags) (flags & BBQ_F_POLICY_DROP_OLD) +#define BBQ_POLIC_RETRY_NEW(flags) (!(flags & BBQ_F_POLICY_DROP_OLD)) + +// flags 第二位控制入队时的数据拷贝策略,默认是copy pointer +#define BBQ_F_COPY_VALUE 0x0002 +#define BBQ_F_COPY_POINTER BBQ_F_DEFAULT +#define BBQ_COPY_VALUE(flags) (flags & BBQ_F_COPY_VALUE) +#define BBQ_COPY_POINTER(flags) (!(flags & BBQ_F_COPY_VALUE)) + +typedef struct { + int socket_id; // 在哪个socket_id上使用libnuma分配内存,-1表示无效,使用malloc分配 + size_t bs; // 每个block里entries成员的大小 + size_t bn; // blocks个数 + size_t obj_size; + size_t entry_size; + unsigned int flags; + unsigned int idx_bits; + unsigned int off_bits; + uint64_t idx_mask; + uint64_t off_mask; + + bbq_head phead; // 生产者头,指向块的索引(version|idx) + bbq_head chead; // 消费者头,指向块的索引(version|idx) + bbq_block_s *blocks; // bn大小的动态数组 +} bbq_queue_s; + +// -----------------------------对外接口------------------------------- +/** + * bbq_ring_create系列接口,用于创建并返回bbq队列结构体,功能类似,区别在于是否需要 + * 指定socket,是否手动手动指定块个数(bn),块的大小(bs),即每个块包含的条目数。 + * @param count + * 队列所有条目的个数(总容量),必须大于1,且是2的N次方。将根据公式自动计算块的个数,以及块的大小。 + * 计算公式:log2(blocks) = max(1, ⌊log2(count)/4⌋) 注:⌊ ⌋代表向下取整。 + * @param flags + * 第一位控制入队策略,默认是retry new模式,即队列满了当前入队失败。如果要设置为drop old模式,需要flags|BBQ_F_DROP_OLD + * 第二位控制入队列时,传入的时候指针,还是指针指向的值。默认传入指针,如果要传入指向的值,需要设置flags|BBQ_F_COPY_VALUE + * @param obj_size + * 队列里每个成员的存储大小,如存储int类型数据:sizeof(int) + * @param socket_id + * socket ID,多numa架构下,队列里的空间将针对指定socket调用libnuma库函数分配内存。 + * 当检测到不支持多numa,将转为malloc分配内存。 + * @return + * 非NULL:消息队列结构体指针,用于后续出队入队等操作。 + * NULL:创建失败。 + */ +extern bbq_queue_s *bbq_ring_create(uint32_t count, size_t obj_size, unsigned int flags); +extern bbq_queue_s *bbq_ring_create_with_socket(uint32_t count, size_t obj_size, int socket_id, unsigned int flags); +extern bbq_queue_s *bbq_ring_create_bnbs(uint32_t bn, uint32_t bs, size_t obj_size, unsigned int flags); +extern bbq_queue_s *bbq_ring_create_bnbs_with_socket(uint32_t bn, uint32_t bs, size_t obj_size, int socket_id, unsigned int flags); + +/** + * 用于释放消息队列,与bbq_ring_create系列函数成对。 + * @param q + * 队列指针 + */ +extern void bbq_ring_free(bbq_queue_s *q); + +/** + * 消息队列入队 + * @param q + * 队列指针 + * @param data + * void *类型,指针也要取地址后传入。例如int a 或 int *a 都传入 (void *)(&a) + * @return + * BBQ_OK 0 :成功 + * BBQ_QUEUE_FULL -1001 : 失败,队列已满 + * BBQ_QUEUE_BUSY -1002 : 失败,队列忙碌中 + * BBQ_ERROR -1 :未知错误 + * BBQ_NULL_PTR -3 :传入空指针 + */ +extern int bbq_enqueue(bbq_queue_s *q, void *data); + +/** + * 消息队列出队 + * @param q + * 队列指针 + * @param deq_data + * 出队成功将复制数据到该参数。 + * 请不要以此判断是否出队列成功,应以return值作为判断依据。 + * @return + * BBQ_OK 0 :成功 + * BBQ_QUEUE_BUSY -1002 : 失败,队列忙碌中 + * BBQ_QUEUE_EMPTY -1003 : 失败,队列已空(出队) + * BBQ_ERROR -1 :未知错误 + * BBQ_NULL_PTR -3 :传入空指针 + */ +extern int bbq_dequeue(bbq_queue_s *q, void *deq_data); + +/** + * 将多个对象从一个环形队列ring取出,直到达到最大数量,或是出队失败 + * @param q + * 队列指针 + * @param obj_table + * @param n + * obj_table的成员个数 + * @return + * 实际出队个数 + */ +extern uint32_t bbq_dequeue_burst(bbq_queue_s *q, void *obj_table, uint32_t n); + +// flags 第一位控制传入的是一维数组还是二维数组 +#define BBQ_F_ARRAY_1D 0x0001 +#define BBQ_F_ARRAY_2D BBQ_F_DEFAULT +#define BBQ_CHK_ARRAY_1D(flags) (flags & BBQ_F_ARRAY_1D) +#define BBQ_CHK_ARRAY_2D(flags) (!(flags & BBQ_F_ARRAY_1D)) +/** + * 尝试一次入队多个数据,直到达到最大数量,或是入队失败 + * + * @param q + * 队列指针 + * @param obj_table + * @param flags + * @param n + * obj_table的成员个数 + * @return + * 实际入队个数 + */ +uint32_t bbq_enqueue_burst(bbq_queue_s *q, void *obj_table, size_t n, unsigned int flags); + +// -----------------------------用于内存测试------------------------------- +// 当BBQ_MEMORY宏定义开关打开,将对内存分配释放进行统计,方便排查内存泄漏 +typedef enum { + BBQ_MODULE_QUEUE = 0, + BBQ_MODULE_QUEUE_BLOCK_NB, + BBQ_MODULE_QUEUE_BLOCK_ENTRY, + BBQ_MODULE_MAX, +} bbq_module_e; + +// -----------------------------错误码------------------------------- +// 通用返回码 +#define BBQ_OK 0 // 成功 +#define BBQ_ERROR -1 // 通用错误 +#define BBQ_ALLOC_ERR -2 // 内存分配失败 +#define BBQ_NULL_PTR -3 // 空指针 +#define BBQ_UNKNOWN_TYPE -3 // 未知类型 + +// 队列错误 +#define BBQ_QUEUE_FULL -1001 // 队列已满(入队失败) +#define BBQ_QUEUE_BUSY -1002 // 队列忙碌中(入队或出队失败) +#define BBQ_QUEUE_EMPTY -1003 // 队列已空(出队失败) + +// -----------------------------日志宏定义------------------------------- +#ifdef BBQ_DEBUG +#define BBQ_DBG_LOG(fmt, ...) \ + do { \ + printf("[DBG][%s:%d:%s]" fmt "\n", __func__, __LINE__, __FILE__, ##__VA_ARGS__); \ + } while (0) + +#else +#define BBQ_DBG_LOG(fmt, ...) \ + do { \ + } while (0) +#endif + +#define BBQ_ERR_LOG(fmt, ...) \ + do { \ + printf("\x1b[31m [ERR][%s:%d:%s]" fmt "\x1b[0m\n", __func__, __LINE__, __FILE__, ##__VA_ARGS__); \ + } while (0) + +#define BBQ_INFO_LOG(fmt, ...) \ + do { \ + printf("[INFO][%s:%d:%s]" fmt "\n", __func__, __LINE__, __FILE__, ##__VA_ARGS__); \ + } while (0) + +#define BBQ_COLOR_PRINT(fmt, ...) \ + do { \ + printf("\033[32m" fmt "\033[0m\n", ##__VA_ARGS__); \ + } while (0) + +// -----------------------------其他宏定义------------------------------- +#define BBQ_INVALID_SOCKET -1 + +#endif
\ No newline at end of file diff --git a/bbq/src/CMakeLists.txt b/bbq/src/CMakeLists.txt new file mode 100644 index 0000000..fa91e18 --- /dev/null +++ b/bbq/src/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.0) +project(BBQ_LIB) + +file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.c) #搜索当前cmake所在目录下的c文件 +set(LIBRARY_OUTPUT_PATH ${LIB_PATH}) #设置库生成目录 + +add_library(${BBQ_LIB} STATIC ${SRC_LIST}) #生成静态库 +# add_library(${BBQ_LIB} SHARED ${SRC_LIST}) #生成动态库 + +target_link_libraries(${BBQ_LIB} numa) # 链接库 diff --git a/bbq/src/bbq.c b/bbq/src/bbq.c new file mode 100644 index 0000000..bfdc30f --- /dev/null +++ b/bbq/src/bbq.c @@ -0,0 +1,631 @@ +/* + * @Author: liuyu + * @LastEditTime: 2024-06-06 12:02:08 + * @Email: [email protected] + * @Describe: bbq(Block-based Bounded Queue)实现 + * 参考:https://www.usenix.org/system/files/atc22-wang-jiawei.pdf + */ +#include "bbq.h" +#include <math.h> +#include <numa.h> +#include <stdio.h> +#include <string.h> + +extern bbq_queue_state_s allocate_entry(bbq_queue_s *q, bbq_block_s *block); +extern void commit_entry(bbq_queue_s *q, bbq_entry_desc_s *e, void *data); +extern bbq_queue_state_s advance_phead(bbq_queue_s *q, uint64_t ph); +extern bbq_queue_state_s reserve_entry(bbq_queue_s *q, bbq_block_s *block); +extern bool consume_entry(bbq_queue_s *q, bbq_entry_desc_s *e, void *deq_data); +extern bool advance_chead(bbq_queue_s *q, uint64_t ch, uint64_t ver); +extern inline uint64_t bbq_idx(bbq_queue_s *q, uint64_t x); +extern inline uint64_t bbq_off(bbq_queue_s *q, uint64_t x); +extern inline uint64_t bbq_head_vsn(bbq_queue_s *q, uint64_t x); +extern inline uint64_t bbq_cur_vsn(bbq_queue_s *q, uint64_t x); +extern inline uint64_t set_cur_vsn(bbq_queue_s *q, uint64_t ver); +extern void *bbq_memset(void *data, size_t size); +extern void *bbq_malloc(bbq_module_e module, int socket_id, size_t size); +extern void bbq_free(bbq_module_e module, int socket_id, void *ptr, size_t size); + +/* 原子的比较两个值大小,并设置较大的值,成功则返回设置前的旧值 */ +uint64_t fetch_max(aotmic_uint64 *atom, uint64_t upd) { + uint64_t old_value; + do { + old_value = atomic_load(atom); // 读取当前值 + } while (old_value < upd && !atomic_compare_exchange_weak(atom, &old_value, upd)); + + return old_value; +} + +/* 检查参数是否为2的N次幂 */ +bool bbq_check_power_of_two(uint32_t n) { + if (n <= 0) { + return false; + } + + return (n & (n - 1)) == 0; +} + +/* 根据entries大小返回合理的block个数 + * 计算公式:log2(blocks) = max(1, ⌊log2(count)/4⌋) 注:⌊ ⌋代表向下取整。*/ +uint32_t bbq_blocks_calc(uint32_t entries) { + double log_entries = log2((double)entries); + uint32_t over4 = (uint32_t)(log_entries / 4); // 向下取整 + uint32_t max_value = (over4 > 1) ? over4 : 1; + uint32_t n = pow(2, max_value); + return n; +} + +/* 块初始化 */ +int block_init(bbq_queue_s *q, bbq_block_s *block, bool cursor_init) { + block->entries = bbq_malloc(BBQ_MODULE_QUEUE_BLOCK_ENTRY, q->socket_id, + sizeof(*block->entries) * q->bs * q->entry_size); + if (block->entries == NULL) { + BBQ_ERR_LOG("bbq_malloc error"); + return BBQ_ALLOC_ERR; + } + + block->committed = ATOMIC_VAR_INIT(0); + block->allocated = ATOMIC_VAR_INIT(0); + block->reserved = ATOMIC_VAR_INIT(0); + block->consumed = ATOMIC_VAR_INIT(0); + + if (cursor_init) { + // block数组里,除了第一块之外需要设置 + block->committed = ATOMIC_VAR_INIT(q->bs); + block->allocated = ATOMIC_VAR_INIT(q->bs); + block->reserved = ATOMIC_VAR_INIT(q->bs); + if (BBQ_POLIC_RETRY_NEW(q->flags)) { + block->consumed = ATOMIC_VAR_INIT(q->bs); + } else { + block->consumed = ATOMIC_VAR_INIT(0); + } + } + + return BBQ_OK; +} + +/* 块清理函数,与block_init成对*/ +void block_cleanup(bbq_queue_s *q, bbq_block_s *block) { + if (block->entries) { + bbq_free(BBQ_MODULE_QUEUE_BLOCK_ENTRY, q->socket_id, + block->entries, sizeof(*block->entries) * q->bs * q->entry_size); + block->entries = NULL; + } +} + +/* +求x在二进制表示中最高位1所在的位置,x参数不能为0。 +例如:x=1,return 0 (...1) +x=3,return 1 (..11) +x=9,return 3 (1..1) +*/ +unsigned floor_log2(uint64_t x) { + return x == 1 ? 0 : 1 + floor_log2(x >> 1); +} + +/* +返回以2为底x的对数,并向上取整值。 +例如:x=1,return 0 (2^0=1) +x=99, return 7(2^6=64 2^7=128) +*/ +unsigned ceil_log2(uint64_t x) { + return x == 1 ? 0 : floor_log2(x - 1) + 1; +} + +/* 创建消息队列,bn和bs必须是2的N次幂,socket_id用于多numa分配内存,free_func先设置NULL */ +bbq_queue_s *bbq_ring_create_bnbs_with_socket(uint32_t bn, uint32_t bs, size_t obj_size, int socket_id, unsigned int flags) { + int ret = 0; + bool numa_enable = true; + + if (bbq_check_power_of_two(bn) == false) { + BBQ_ERR_LOG("block number is not power of two, now is :%lu", bn); + return NULL; + } + + if (bbq_check_power_of_two(bs) == false) { + BBQ_ERR_LOG("block size is not power of two, now is :%lu", bs); + return NULL; + } + + if (obj_size == 0) { + BBQ_ERR_LOG("obj_size is 0"); + return NULL; + } + + if (numa_available() < 0) { + // 不支持numa,设置 + socket_id = BBQ_INVALID_SOCKET; + } + + bbq_queue_s *q = bbq_malloc(BBQ_MODULE_QUEUE, socket_id, sizeof(*q)); + if (q == NULL) { + BBQ_ERR_LOG("malloc for bbq queue error"); + return NULL; + } + bbq_memset(q, sizeof(*q)); + + q->bn = bn; + q->bs = bs; + q->obj_size = obj_size; + if (BBQ_COPY_POINTER(flags)) { + q->entry_size = sizeof(uintptr_t); + } else { + q->entry_size = obj_size; + } + q->socket_id = socket_id; + q->phead = 0; + q->chead = 0; + q->flags = flags; + + q->blocks = bbq_malloc(BBQ_MODULE_QUEUE_BLOCK_NB, socket_id, bn * sizeof(*q->blocks)); + if (q->blocks == NULL) { + BBQ_ERR_LOG("bbq malloc for blocks error"); + goto error; + } + bbq_memset(q->blocks, sizeof(*q->blocks)); + + for (uint32_t i = 0; i < bn; ++i) { + // 第一个block不需要设置cursor_init_flag + bool cursor_init_flag = (i == 0 ? false : true); + ret = block_init(q, &(q->blocks[i]), cursor_init_flag); + if (ret != BBQ_OK) { + BBQ_ERR_LOG("bbq block init error"); + goto error; + } + } + + q->idx_bits = ceil_log2(bn); + q->off_bits = ceil_log2(bs) + 1; + + q->idx_mask = (1 << q->idx_bits) - 1; + q->off_mask = (1 << q->off_bits) - 1; + + return q; + +error: + bbq_ring_free(q); + return NULL; +} + +/* 创建消息队列,bn和bs必须是2的N次幂,free_func先设置NULL */ +bbq_queue_s *bbq_ring_create_bnbs(uint32_t bn, uint32_t bs, size_t obj_size, unsigned int flags) { + return bbq_ring_create_bnbs_with_socket(bn, bs, obj_size, BBQ_INVALID_SOCKET, flags); +} + +/* 创建消息队列,count必须大于1,且是2的N次幂,bn和bs将根据count值自动计算,socket_id用于多numa分配内存,free_func先设置NULL */ +bbq_queue_s *bbq_ring_create_with_socket(uint32_t count, size_t obj_size, int socket_id, unsigned int flags) { + if (bbq_check_power_of_two(count) == false || count == 1) { + BBQ_ERR_LOG("bbq entries number must be power of two and greater than 1, now is :%lu", count); + return NULL; + } + + uint32_t bn = bbq_blocks_calc(count); + uint32_t bs = count / bn; + return bbq_ring_create_bnbs_with_socket(bn, bs, obj_size, socket_id, flags); +} + +/* 创建消息队列,count必须大于1,且是2的N次幂,bn和bs将根据count值自动计算,free_func先设置NULL */ +bbq_queue_s *bbq_ring_create(uint32_t count, size_t obj_size, unsigned int flags) { + // 传入无效socket_id,将使用malloc分配内存 + return bbq_ring_create_with_socket(count, obj_size, BBQ_INVALID_SOCKET, flags); +} + +/* 释放消息队列,与bbq_ring_create系列接口成对*/ +void bbq_ring_free(bbq_queue_s *q) { + if (q == NULL) { + return; + } + + for (uint32_t i = 0; i < q->bn; ++i) { + block_cleanup(q, &(q->blocks[i])); + } + + bbq_free(BBQ_MODULE_QUEUE_BLOCK_NB, q->socket_id, q->blocks, q->bn * sizeof(*q->blocks)); + bbq_free(BBQ_MODULE_QUEUE, q->socket_id, q, sizeof(*q)); +} + +/* 消息队列入队 */ +int bbq_enqueue(bbq_queue_s *q, void *data) { + if (q == NULL || data == NULL) { + return BBQ_NULL_PTR; + } + + while (true) { + // 获取当前phead,转为索引后获取到当前的blk + uint64_t ph = atomic_load(&q->phead); + bbq_block_s *blk = &(q->blocks[bbq_idx(q, ph)]); + + bbq_queue_state_s ps; + bbq_queue_state_s state = allocate_entry(q, blk); + + switch (state.state) { + case BBQ_ALLOCATED: + commit_entry(q, &state.e, data); + return BBQ_OK; + case BBQ_BLOCK_DONE: + ps = advance_phead(q, ph); + switch (ps.state) { + case BBQ_NO_ENTRY: + return BBQ_QUEUE_FULL; + case BBQ_NOT_AVAILABLE: + return BBQ_QUEUE_BUSY; + case BBQ_SUCCESS: + continue; + } + break; + default: + BBQ_ERR_LOG("Invalid QueueState in bbq_enqueue: %d", state.state); + return BBQ_ERROR; + } + } +} + +/* 消息队列出队 */ +int bbq_dequeue(bbq_queue_s *q, void *deq_data) { + if (q == NULL || deq_data == NULL) { + return BBQ_NULL_PTR; + } + + while (true) { + uint64_t ch = atomic_load(&q->chead); + bbq_block_s *blk = &(q->blocks[bbq_idx(q, ch)]); + + bbq_queue_state_s state; + state = reserve_entry(q, blk); + + switch (state.state) { + case BBQ_RESERVED: + if (consume_entry(q, &state.e, deq_data)) { + return BBQ_OK; + } else { + continue; + } + case BBQ_NO_ENTRY: + return BBQ_QUEUE_EMPTY; + case BBQ_NOT_AVAILABLE: + return BBQ_QUEUE_BUSY; + case BBQ_BLOCK_DONE: + if (advance_chead(q, ch, state.vsn)) { + continue; + } else { + return BBQ_QUEUE_EMPTY; + } + default: + BBQ_ERR_LOG("Invalid QueueState in dequeue state: %d", state.state); + return BBQ_ERROR; + } + } +} + +/* 将多个对象从一个环形队列ring取出,直到达到最大数量,或是出队失败 */ +uint32_t bbq_dequeue_burst(bbq_queue_s *q, void *obj_table, uint32_t n) { + if (q == NULL || obj_table == NULL) { + return BBQ_NULL_PTR; + } + + uint32_t cnt = 0; + int ret = 0; + void *obj = NULL; + + for (cnt = 0; cnt < n; cnt++) { + if (BBQ_COPY_VALUE(q->flags)) { + obj = (obj == NULL ? obj_table : obj + q->obj_size); + } else { + obj = &((void **)obj_table)[cnt]; + } + + ret = bbq_dequeue(q, obj); + if (ret != BBQ_OK) { + return cnt; + } + } + + return cnt; +} + +/* 尝试一次入队多个数据,直到达到最大数量,或是入队失败 */ +uint32_t bbq_enqueue_burst(bbq_queue_s *q, void *obj_table, size_t n, unsigned int flags) { + if (q == NULL || obj_table == NULL) { + return BBQ_NULL_PTR; + } + + uint32_t cnt = 0; + void *obj = NULL; + + for (cnt = 0; cnt < n; cnt++) { + if (BBQ_CHK_ARRAY_1D(flags)) { + obj = (obj == NULL ? obj_table : obj + q->obj_size); + } else { + obj = ((void **)obj_table)[cnt]; + } + + if (BBQ_OK != bbq_enqueue(q, obj)) { + return cnt; + } + } + + return cnt; +} + +bbq_queue_state_s allocate_entry(bbq_queue_s *q, bbq_block_s *block) { + bbq_queue_state_s state = {0}; + if (bbq_off(q, atomic_load(&block->allocated)) >= q->bs) { + state.state = BBQ_BLOCK_DONE; + return state; + } + + uint64_t old = atomic_fetch_add(&block->allocated, 1); + uint64_t committed_vsn = bbq_cur_vsn(q, atomic_load(&block->committed)); + + // committed_vsn,在当前块被初始化后值是不变的,判断vsn是考虑到极限情况下给off预留的空间不足导致,allocated的off溢出(vsn+1) + uint64_t cur_vsn = bbq_cur_vsn(q, old); + uint64_t cur_off = bbq_off(q, old); + if ((cur_vsn != committed_vsn) || (cur_off >= q->bs)) { + state.state = BBQ_BLOCK_DONE; + return state; + } + + state.state = BBQ_ALLOCATED; + state.e.block = block; + state.e.vsn = cur_vsn; + state.e.bbq_off = cur_off; + + return state; +} + +void commit_entry(bbq_queue_s *q, bbq_entry_desc_s *e, void *data) { + size_t idx = e->bbq_off * q->entry_size; + if (BBQ_COPY_POINTER(q->flags)) { + uintptr_t *uptr = (uintptr_t *)(&(e->block->entries[idx])); + *uptr = (uintptr_t)(data); + } else { + memcpy(&(e->block->entries[idx]), data, q->entry_size); + } + atomic_fetch_add(&e->block->committed, 1); +} + +bbq_queue_state_s advance_phead(bbq_queue_s *q, uint64_t ph) { + // 获取下一个block + bbq_block_s *n_blk; + n_blk = &(q->blocks[(bbq_idx(q, ph) + 1) & q->idx_mask]); + + uint64_t cur, reserved; + bbq_queue_state_s state = {0}; + if (BBQ_POLIC_RETRY_NEW(q->flags)) { + cur = atomic_load(&n_blk->consumed); + if (bbq_cur_vsn(q, cur) < bbq_head_vsn(q, ph) || // 生产者赶上了消费者 + (bbq_cur_vsn(q, cur) == bbq_head_vsn(q, ph) && bbq_off(q, cur) != q->bs)) { + reserved = atomic_load(&n_blk->reserved); + if (bbq_off(q, reserved) == bbq_off(q, cur)) { + state.state = BBQ_NO_ENTRY; + } else { + state.state = BBQ_NOT_AVAILABLE; + } + return state; + } + } else { + cur = atomic_load(&n_blk->committed); + // 生产者避免前进到上一轮中尚未完全提交的区块 + if (bbq_cur_vsn(q, cur) == bbq_head_vsn(q, ph) && bbq_off(q, cur) != q->bs) { + state.state = BBQ_NOT_AVAILABLE; + return state; + } + } + + // 用head的version初始化下一个块,version在高位,version+1,idex/offset清零,如果没有被其他线程执行过,数值会高于旧值。多线程同时只更新一次。 + fetch_max(&n_blk->committed, set_cur_vsn(q, bbq_head_vsn(q, ph) + 1)); + fetch_max(&n_blk->allocated, set_cur_vsn(q, bbq_head_vsn(q, ph) + 1)); + + // 索引+1,当超过索引范围,也就是循环下一轮块时,version+1 + fetch_max(&q->phead, ph + 1); + state.state = BBQ_SUCCESS; + return state; +} + +bbq_queue_state_s reserve_entry(bbq_queue_s *q, bbq_block_s *block) { + while (true) { + bbq_queue_state_s state; + uint64_t reserved = atomic_load(&block->reserved); + if (bbq_off(q, reserved) < q->bs) { + uint64_t consumed = atomic_load(&block->consumed); + if (BBQ_POLIC_RETRY_NEW(q->flags) && bbq_cur_vsn(q, reserved) != bbq_cur_vsn(q, consumed)) { + // consumed溢出了,这种情况只发生在BBQ_RETRY_NEW,因为BBQ_DROP_OLD模式,consumed没有用到 + state.state = BBQ_BLOCK_DONE; + state.vsn = bbq_cur_vsn(q, reserved); + return state; + } + + uint64_t committed = atomic_load(&block->committed); + if (bbq_off(q, committed) == bbq_off(q, reserved)) { // TODO:多entry关注 + state.state = BBQ_NO_ENTRY; + return state; + } + + // 当前块的数据没有被全部commited,需要通过判断allocated和committed来判断是否存在正在入队进行中的数据 + if (bbq_off(q, committed) != q->bs) { + uint64_t allocated = atomic_load(&block->allocated); + if (bbq_off(q, allocated) != bbq_off(q, committed)) { + state.state = BBQ_NOT_AVAILABLE; + return state; + } + } + + if (fetch_max(&block->reserved, reserved + 1) == reserved) { // TODO:多entry时关注 + // fetch_max返回的是旧值,因此预期返回的旧值要等于局部变量reserved + state.state = BBQ_RESERVED; + state.e.block = block; + state.e.bbq_off = bbq_off(q, reserved); + state.e.vsn = bbq_cur_vsn(q, reserved); + + return state; + } else { + // 如果不等于代表block.reserved被其他线程Reserved了 + continue; + } + } + + state.state = BBQ_BLOCK_DONE; + state.vsn = bbq_cur_vsn(q, reserved); + return state; + } +} + +bool consume_entry(bbq_queue_s *q, bbq_entry_desc_s *e, void *deq_data) { + size_t idx = e->bbq_off * q->entry_size; + if (BBQ_COPY_POINTER(q->flags)) { + uintptr_t *uptr = (uintptr_t *)(&(e->block->entries[idx])); + *((void **)deq_data) = (void *)(*uptr); + } else { + memcpy(deq_data, &(e->block->entries[idx]), q->entry_size); + } + + uint64_t allocated; + if (BBQ_POLIC_RETRY_NEW(q->flags)) { + atomic_fetch_add(&e->block->consumed, 1); + } else { + allocated = atomic_load(&e->block->allocated); + // 预留的entry所在的块,已经被新生产的数据赶上了 + if (bbq_cur_vsn(q, allocated) != e->vsn) { + return false; + } + } + + return true; +} + +bool advance_chead(bbq_queue_s *q, uint64_t ch, uint64_t ver) { + bbq_block_s *n_blk = &(q->blocks[(bbq_idx(q, ch) + 1) & q->idx_mask]); + + uint64_t committed = atomic_load(&n_blk->committed); + if (BBQ_POLIC_RETRY_NEW(q->flags)) { + if (bbq_cur_vsn(q, committed) != bbq_head_vsn(q, ch) + 1) { + // 消费者追上了生产者,下一块还未开始生产 + return false; + } + fetch_max(&n_blk->consumed, set_cur_vsn(q, bbq_head_vsn(q, ch) + 1)); + fetch_max(&n_blk->reserved, set_cur_vsn(q, bbq_head_vsn(q, ch) + 1)); + } else { + // 通过检查下一个块的版本是否大于或等于当前块来保证 FIFO 顺序. + // 第一个块是一个特殊情况,因为与其他块相比,它的版本总是相差一个。因此,如果 ch.bbq_idx == 0,我们在比较中加 1 + if (bbq_cur_vsn(q, committed) < ver + (bbq_idx(q, ch) == 0)) + return false; + fetch_max(&n_blk->reserved, set_cur_vsn(q, bbq_cur_vsn(q, committed))); + } + + fetch_max(&q->chead, ch + 1); + return true; +} + +inline uint64_t bbq_idx(bbq_queue_s *q, uint64_t x) { + return x & q->idx_mask; +} + +inline uint64_t bbq_off(bbq_queue_s *q, uint64_t x) { + return x & q->off_mask; +} + +inline uint64_t bbq_head_vsn(bbq_queue_s *q, uint64_t x) { + return x >> q->idx_bits; +} + +inline uint64_t bbq_cur_vsn(bbq_queue_s *q, uint64_t x) { + return x >> q->off_bits; +} + +inline uint64_t set_cur_vsn(bbq_queue_s *q, uint64_t ver) { + return ver << q->off_bits; +} + +void *bbq_memset(void *data, size_t size) { + if (data != NULL && size > 0) { + memset(data, 0, size); + } +} + +#ifdef BBQ_MEMORY +typedef struct { + aotmic_uint64 malloc_cnt; + aotmic_uint64 malloc_size; + aotmic_uint64 free_cnt; + aotmic_uint64 free_size; +} bbq_memory_s; +bbq_memory_s bbq_memory_g[BBQ_MODULE_MAX] = {0}; +#endif + +void *bbq_malloc(bbq_module_e module, int socket_id, size_t size) { + void *ptr = NULL; + if (socket_id >= 0) { + ptr = numa_alloc_onnode(size, 0); + } else { + ptr = malloc(size); + } +#ifdef BBQ_MEMORY + if (ptr != NULL) { + atomic_fetch_add(&bbq_memory_g[module].malloc_cnt, 1); + atomic_fetch_add(&bbq_memory_g[module].malloc_size, size); + } +#endif + + return ptr; +} + +void bbq_free(bbq_module_e module, int socket_id, void *ptr, size_t size) { +#ifdef BBQ_MEMORY + if (ptr != NULL) { + atomic_fetch_add(&bbq_memory_g[module].free_cnt, 1); + atomic_fetch_add(&bbq_memory_g[module].free_size, size); + } +#endif + // free(ptr); + if (socket_id >= 0) { + numa_free(ptr, size); + } else { + free(ptr); + } +} + +bool bbq_malloc_free_equal() { +#ifdef BBQ_MEMORY + bool ret = true; + for (int i = 0; i < BBQ_MODULE_MAX; i++) { + uint64_t malloc_cnt = atomic_load(&bbq_memory_g[i].malloc_cnt); + uint64_t free_cnt = atomic_load(&bbq_memory_g[i].free_cnt); + if (malloc_cnt != free_cnt) { + BBQ_ERR_LOG("[module:%d] malloc:%lu free:%lu, bbq mmalloc-free count not equal\n", i, malloc_cnt, free_cnt); + ret = false; + } + + uint64_t malloc_size = atomic_load(&bbq_memory_g[i].malloc_size); + uint64_t free_size = atomic_load(&bbq_memory_g[i].free_size); + if (malloc_size != free_size) { + BBQ_ERR_LOG("[module:%d] malloc:%lu free:%lu, bbq mmalloc-free size not equal\n", i, malloc_cnt, free_cnt); + ret = false; + } + } + return ret; +#else + return true; +#endif +} + +void bbq_memory_info() { +#ifdef BBQ_MEMORY + for (int i = 0; i < BBQ_MODULE_MAX; i++) { + uint64_t malloc_cnt = atomic_load(&bbq_memory_g[i].malloc_cnt); + uint64_t free_cnt = atomic_load(&bbq_memory_g[i].free_cnt); + if (malloc_cnt == 0 && free_cnt == 0) { + continue; + } + + BBQ_INFO_LOG("[%d]bbq malloc:%lu free:%lu", i, + atomic_load(&bbq_memory_g[i].malloc_cnt), + atomic_load(&bbq_memory_g[i].free_cnt)); + } + + if (bbq_malloc_free_equal()) { + BBQ_INFO_LOG("all memory free"); + } else { + BBQ_ERR_LOG("memory not all free"); + } +#endif +}
\ No newline at end of file diff --git a/bbq/tests/CMakeLists.txt b/bbq/tests/CMakeLists.txt new file mode 100644 index 0000000..2513fbd --- /dev/null +++ b/bbq/tests/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.0) +project(BBQ_TESTS) + +file(GLOB SRC_COMMON_LIST "${CMAKE_CURRENT_SOURCE_DIR}/common/*.c") + +# 指定库路径 +link_directories(${LIB_PATH}) +# 指定头文件 +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/common/ +) + +# 链接静态库 +link_libraries(bbq) +# 指定可执行文件输出路径 +set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH}) + +add_subdirectory(unittest) diff --git a/bbq/tests/common/test_mix.c b/bbq/tests/common/test_mix.c new file mode 100644 index 0000000..1a3869e --- /dev/null +++ b/bbq/tests/common/test_mix.c @@ -0,0 +1,206 @@ +/* + * @Description: 描述信息 + * @Date: 2024-05-25 10:55:48 + * @LastEditTime: 2024-06-06 18:55:12 + */ + +#include "test_mix.h" +#include "bbq.h" +#include <string.h> +#include <sys/time.h> + +typedef struct { + aotmic_uint64 malloc_cnt; + aotmic_uint64 free_cnt; +} test_memory_s; + +test_memory_s test_memory_g[TEST_MODULE_MAX] = {0}; + +void *test_malloc(test_module_e module, size_t size) { + void *ptr = malloc(size); + if (ptr != NULL) { + atomic_fetch_add(&test_memory_g[module].malloc_cnt, 1); + } + + return ptr; +} + +void *test_calloc(test_module_e module, size_t size) { + void *ptr = test_malloc(module, size); + if (ptr != NULL) { + memset(ptr, 0, size); + } +} + +void test_free(test_module_e module, void *ptr) { + if (ptr != NULL) { + atomic_fetch_add(&test_memory_g[module].free_cnt, 1); + } + free(ptr); +} + +bool test_malloc_free_equal() { + bool ret = true; + for (int i = 0; i < TEST_MODULE_MAX; i++) { + uint64_t malloc_cnt = atomic_load(&test_memory_g[i].malloc_cnt); + uint64_t free_cnt = atomic_load(&test_memory_g[i].free_cnt); + if (malloc_cnt != free_cnt) { + BBQ_ERR_LOG("[module:%d] malloc:%lu free:%lu, test malloc-free not equal\n", i, malloc_cnt, free_cnt); + ret = false; + } + } + return ret; +} + +void test_memory_counter_clear() { + memset(test_memory_g, 0, sizeof(test_memory_g)); +} + +void test_memory_info() { + for (int i = 0; i < TEST_MODULE_MAX; i++) { + uint64_t malloc_cnt = atomic_load(&test_memory_g[i].malloc_cnt); + uint64_t free_cnt = atomic_load(&test_memory_g[i].free_cnt); + if (malloc_cnt == 0 && free_cnt == 0) { + continue; + } + + BBQ_INFO_LOG("[%d]test malloc:%lu free:%lu", i, + atomic_load(&test_memory_g[i].malloc_cnt), + atomic_load(&test_memory_g[i].free_cnt)); + } + + if (test_malloc_free_equal()) { + BBQ_INFO_LOG("all memory free"); + } else { + BBQ_ERR_LOG("memory not all free"); + } +} + +test_time_metric test_clock_time_get() { + test_time_metric metric = {0}; + struct timespec timestamp; + + // clock_gettime(CLOCK_MONOTONIC, &metric.timestamp); //从系统启动这一刻起开始计时,不受系统时间被用户改变的影响 + clock_gettime(CLOCK_REALTIME, &metric.timestamp); // 系统实时时间,随系统实时时间改变而改变 + return metric; +} + +uint64_t test_clock_time_to_ns(test_time_metric *metric) { + return metric->timestamp.tv_nsec + metric->timestamp.tv_sec * 1000 * 1000 * 1000; +} + +double test_clock_time_to_double(test_time_metric *metric) { + return metric->timestamp.tv_sec + + metric->timestamp.tv_nsec * 1.0 / 1000 / 1000 / 1000; +} + +bool test_clock_time_is_zero(test_time_metric *metric) { + return metric->timestamp.tv_sec == 0 && metric->timestamp.tv_nsec == 0; +} + +bool test_timespec_is_after(const struct timespec *a, const struct timespec *b) { + if (a->tv_sec > b->tv_sec) { + // a的秒数大于b的秒数,所以a在b之后 + return true; + } else if (a->tv_sec == b->tv_sec && a->tv_nsec > b->tv_nsec) { + // a和b的秒数相同,但a的纳秒数大于b的纳秒数,所以a在b之后 + return true; + } + // 否则,a不在b之后 + return false; +} + +test_time_metric test_clock_time_sub(test_time_metric now, test_time_metric last) { + test_time_metric diff = { + .timestamp.tv_sec = now.timestamp.tv_sec - last.timestamp.tv_sec, + .timestamp.tv_nsec = now.timestamp.tv_nsec - last.timestamp.tv_nsec, + }; + + if (now.timestamp.tv_nsec > last.timestamp.tv_nsec) { + diff.timestamp.tv_nsec = now.timestamp.tv_nsec - last.timestamp.tv_nsec; + } else { + // 从秒借位 + diff.timestamp.tv_sec--; + diff.timestamp.tv_nsec = 1000 * 1000 * 1000 + now.timestamp.tv_nsec - last.timestamp.tv_nsec; + } + + return diff; +} + +test_workload_e test_workload_str2enum(const char *workload) { + if (strcmp(workload, "simple") == 0) { + return TEST_WORKLOAD_SIMPLE; + } else if (strcmp(workload, "complex") == 0) { + return TEST_WORKLOAD_COMPLEX; + } + + return TEST_WORKLOAD_MAX; +} + +char *ring_type_map[TEST_RING_TYPE_MAX] = { + [TEST_RING_TYPE_BBQ] = TEST_RING_TYPE_BBQ_STR, + [TEST_RING_TYPE_DPDK] = TEST_RING_TYPE_DPDK_STR, + [TEST_RING_TYPE_RMIND] = TEST_RING_TYPE_RMIND_STR, +}; + +test_ring_type test_ring_type_str2enum(const char *ring_type) { + if (strcmp(ring_type, TEST_RING_TYPE_BBQ_STR) == 0) { + return TEST_RING_TYPE_BBQ; + } else if (strcmp(ring_type, TEST_RING_TYPE_DPDK_STR) == 0) { + return TEST_RING_TYPE_DPDK; + } else if (strcmp(ring_type, TEST_RING_TYPE_RMIND_STR) == 0) { + return TEST_RING_TYPE_RMIND; + } + + return TEST_RING_TYPE_MAX; +} + +char *test_ring_type_enum2str(test_ring_type ring_type) { + if (ring_type >= TEST_RING_TYPE_MAX) { + return "unknown"; + } else { + return ring_type_map[ring_type]; + } +} + +uint16_t **test_enqueue_table_create(uint32_t count) { + uint16_t **table = test_malloc(TEST_MODULE_TABLE, sizeof(uint16_t **) * count); + if (table == NULL) { + return NULL; + } + memset(table, 0, sizeof(uint16_t **) * count); + + for (uint32_t i = 0; i < count; i++) { + table[i] = test_malloc(TEST_MODULE_TABLE, sizeof(uint16_t)); + if (table[i] == NULL) { + goto error; + } + *table[i] = TEST_TABLE_DATA_MAGIC; + } + + return table; + +error: + if (table) { + for (uint32_t i = 0; i < count; i++) { + if (table[i] != NULL) { + test_free(TEST_MODULE_TABLE, table[i]); + } else { + break; + } + } + test_free(TEST_MODULE_TABLE, table); + } + return NULL; +} + +void test_enqueue_table_destory(uint16_t **table, uint32_t count) { + if (table == NULL) { + return; + } + + for (uint32_t i = 0; i < count; i++) { + test_free(TEST_MODULE_TABLE, table[i]); + } + test_free(TEST_MODULE_TABLE, table); +} diff --git a/bbq/tests/common/test_mix.h b/bbq/tests/common/test_mix.h new file mode 100644 index 0000000..511378f --- /dev/null +++ b/bbq/tests/common/test_mix.h @@ -0,0 +1,120 @@ +/* + * @Author: liuyu + * @LastEditTime: 2024-06-07 00:43:49 + * @Email: [email protected] + * @Describe: TODO + */ +#ifndef _TEST_MIX_H_ +#define _TEST_MIX_H_ + +#include <stdatomic.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <time.h> + +#define TEST_TABLE_DATA_MAGIC 12345 + +typedef enum { + TEST_THREAD_PRODUCER, + TEST_THREAD_CONSUMER, + TEST_THREAD_TYPE_MAX, +} test_thread_type_e; + +typedef struct { + struct timespec timestamp; // 系统时间戳 + // uint64_t cycles; // cpu运行的cycle +} test_time_metric; + +typedef struct { + uint64_t throughput; // 吞吐量:每秒消耗的条目总数。 + double data_latency; // 数据延迟:每个数据在队列中停留的平均时间。 + double op_latency; // 操作延迟:每个入队或出队操作的平均延迟。 + double* fairness; // 公平性:每个生产者/消费者的吞吐量(占总吞吐的百分比) + double full_empty; // 队列满时入队的延迟/队列空时出队的延迟(仅用于简单工作负载)。 + uint64_t oversubscription; // 比核心/超线程更多的生产者和消费者的吞吐量 +} test_report; + +typedef enum { + TEST_WORKLOAD_SIMPLE, // 简单负载,每个生产者或消费者都有自己的线程,它们在循环中不断执行入队或出队操作。每次出队后都会验证数据。 + TEST_WORKLOAD_COMPLEX, // 复杂负载,基于简单工作负载。生产者和消费者为数据分配空间,执行入队和出队,然后手动释放 + TEST_WORKLOAD_MAX, +} test_workload_e; + +#define TEST_RING_TYPE_BBQ_STR "bbq" +#define TEST_RING_TYPE_DPDK_STR "dpdk" +#define TEST_RING_TYPE_RMIND_STR "rmind" +typedef enum { + TEST_RING_TYPE_BBQ, + TEST_RING_TYPE_DPDK, + TEST_RING_TYPE_RMIND, + TEST_RING_TYPE_MAX, +} test_ring_type; + +typedef struct { + char name[128]; // 配置文件名 + char introduce[128]; // 测试配置说明 + uint16_t cores_cnt; // 测试用核心个数 +} test_cfg_base; + +typedef struct { + test_ring_type ring_type; // ring buffer类型 + uint32_t producer_cnt; // 生产者个数 + uint32_t consumer_cnt; // 消费者个数 + test_workload_e workload; // 负载模式 + uint64_t entries_cnt; // ring初始化时分配entry的个数 + uint32_t block_count; // bbq block个数,为0时表示根据entries_cnt自动计算 + uint32_t burst_cnt; // 批量出入队个数 +} test_cfg_ring; + +typedef struct { + uint64_t run_ok_times; // 成功入队/入队次数 + uint64_t run_time; // 整体运行时间,单位秒 + volatile bool running; // 默认为true,当设置为false,即所有生产者消费者即将退出 + +#ifndef __cplusplus + // C + atomic_uint thread_start; + atomic_uint producer_exit; +#else + // C++ 为了兼容gtest测试 + std::atomic<uint32_t> thread_start; + std::atomic<uint32_t> producer_exit; +#endif +} test_cfg_run; + +typedef struct { + test_cfg_base base; + test_cfg_ring ring; + test_cfg_run run; +} test_cfg; + +typedef enum { + TEST_MODULE_UTEST, + TEST_MODULE_COMMON, + TEST_MODULE_DATA, + TEST_MODULE_BCM, + TEST_MODULE_TABLE, + TEST_MODULE_RMIND, + TEST_MODULE_MAX, +} test_module_e; + +extern test_time_metric test_clock_time_get(); +extern test_time_metric test_clock_time_sub(test_time_metric now, test_time_metric last); +extern int test_load_config(const char* config, const char* ring_type, uint32_t burst_cnt, test_cfg* cfg); +extern test_workload_e test_workload_str2enum(const char* workload); +extern test_ring_type test_ring_type_str2enum(const char* ring_type); +extern bool test_clock_time_is_zero(test_time_metric* metric); +extern bool test_timespec_is_after(const struct timespec* a, const struct timespec* b); +extern char* test_ring_type_enum2str(test_ring_type ring_type); +extern uint64_t test_clock_time_to_ns(test_time_metric* metric); +extern double test_clock_time_to_double(test_time_metric* metric); +extern void* test_malloc(test_module_e module, size_t size); +extern void* test_calloc(test_module_e module, size_t size); +extern void test_free(test_module_e module, void* ptr); +extern void test_memory_info(); +extern void test_memory_counter_clear(); +extern bool test_malloc_free_equal(); +extern uint16_t** test_enqueue_table_create(uint32_t count); +extern void test_enqueue_table_destory(uint16_t** table, uint32_t count); +#endif
\ No newline at end of file diff --git a/bbq/tests/common/test_queue.c b/bbq/tests/common/test_queue.c new file mode 100644 index 0000000..56ab253 --- /dev/null +++ b/bbq/tests/common/test_queue.c @@ -0,0 +1,446 @@ +/* + * @Author: liuyu + * @LastEditTime: 2024-06-07 00:38:03 + * @Email: [email protected] + * @Describe: TODO + */ + +#include "test_queue.h" +#include <sys/prctl.h> +#include <unistd.h> +#include "bbq.h" +#include "test_mix.h" + +void test_busy_loop(uint32_t busy_loop) { + uint32_t loop = 0; + while (loop <= busy_loop) { + loop++; + }; +} + +int test_queue_init_bbq(test_cfg* cfg, test_queue_s* q) { + bbq_queue_s* ring; + size_t obj_size = sizeof(test_data); + + if (cfg->ring.block_count == 0) { + q->ring = bbq_ring_create_with_socket(cfg->ring.entries_cnt, obj_size, 0, BBQ_F_POLICY_RETRY_NEW | BBQ_F_COPY_POINTER); + } else { + q->ring = bbq_ring_create_bnbs_with_socket(cfg->ring.block_count, + cfg->ring.entries_cnt / cfg->ring.block_count, + obj_size, + 0, BBQ_F_POLICY_RETRY_NEW | BBQ_F_COPY_POINTER); + } + + if (q->ring == NULL) { + BBQ_ERR_LOG("bbq create queue failed"); + return BBQ_NULL_PTR; + } + + bbq_queue_s* bbq_ring = (bbq_queue_s*)q->ring; + BBQ_DBG_LOG("block number:%lu size:%lu", bbq_ring->bn, bbq_ring->bs); + + q->ring_free_f = (test_ring_free_f)bbq_ring_free; + q->enqueue_f = (test_ring_enqueue_f)bbq_enqueue; + q->dequeue_f = (test_ring_dequeue_f)bbq_dequeue; + q->dequeue_burst_f = (test_dequeue_burst_f)bbq_dequeue_burst; + q->enqueue_burst_f = (test_enqueue_burst_f)bbq_enqueue_burst; + + return 0; +} + +void test_queue_destory(test_queue_s* q) { + if (q != NULL && q->ring_free_f != NULL) { + q->ring_free_f(q->ring); + } +} + +bool test_all_producer_exit(test_cfg* cfg) { + return atomic_load(&cfg->run.producer_exit) == cfg->ring.producer_cnt; +} + +void test_wait_all_threads_ready(test_cfg* cfg) { + uint32_t thread_cnt_max = cfg->ring.consumer_cnt + + cfg->ring.producer_cnt; + + while (atomic_load(&cfg->run.thread_start) != thread_cnt_max) { + // 忙等待其他线程准备好 + // BBQ_DBG_LOG("wait all thread init done!"); + // sleep(1); + } + BBQ_DBG_LOG("thread init done!"); +} + +test_exit_data* test_exit_data_create(test_thread_arg_s* t_arg) { + test_exit_data* exit_data = (test_exit_data*)test_malloc(TEST_MODULE_COMMON, sizeof(test_exit_data)); + if (exit_data == NULL) { + BBQ_ERR_LOG("malloc failed"); + exit(-1); + } + + size_t size = t_arg->cfg->ring.entries_cnt; + exit_data->simple_data_cnt = size; + exit_data->simple_data = test_data_create(size); + + if (exit_data->simple_data == NULL) { + BBQ_ERR_LOG("malloc failed"); + exit(-1); + } + exit_data->arg = t_arg; + exit_data->thread_id = pthread_self(); + exit_data->latency_ns = 0; + exit_data->data_error_cnt = 0; + + return exit_data; +} + +test_exit_data* test_exit_data_destory(test_exit_data* data) { + test_data_destory(data->simple_data, data->simple_data_cnt); + test_free(TEST_MODULE_COMMON, data->arg); + test_free(TEST_MODULE_COMMON, data); +} + +test_data** test_data_create(size_t cnt) { + test_data** simple_data = test_malloc(TEST_MODULE_DATA, sizeof(*simple_data) * cnt); + test_time_metric enqueue_time = test_clock_time_get(); + for (size_t i = 0; i < cnt; i++) { + simple_data[i] = test_malloc(TEST_MODULE_DATA, sizeof(*simple_data[i])); + simple_data[i]->data = TEST_DATA_MAGIC; + simple_data[i]->enqueue_time = enqueue_time; + } + + return simple_data; +} + +void test_data_destory(test_data** data, size_t cnt) { + for (size_t i = 0; i < cnt; i++) { + test_free(TEST_MODULE_DATA, data[i]); + } + test_free(TEST_MODULE_DATA, data); +} + +uint32_t test_exec_enqueue(test_queue_s* q, test_data** data, size_t burst_cnt, test_time_metric* op_use_diff, uint16_t thread_idx) { + uint32_t enqueue_cnt = 0; + test_time_metric op_use_start = test_clock_time_get(); + if (burst_cnt == 1) { + if (q->enqueue_f) { + if (q->enqueue_f(q->ring, data[0]) == 0) { + enqueue_cnt = 1; + } + } + } else { + enqueue_cnt = q->enqueue_burst_f(q->ring, data, burst_cnt, BBQ_F_ARRAY_2D, thread_idx); + } + *op_use_diff = test_clock_time_sub(test_clock_time_get(), op_use_start); + + return enqueue_cnt; +} + +uint32_t test_exec_dequeue(test_queue_s* q, test_data** data, size_t burst_cnt, test_time_metric* op_use_diff) { + uint32_t dequeue_cnt = 0; + + test_time_metric op_use_start = test_clock_time_get(); + if (burst_cnt == 1) { + if (q->dequeue_f) { + if (q->dequeue_f(q->ring, &data[0]) == 0) { + dequeue_cnt = 1; + } + } + } else { + dequeue_cnt = q->dequeue_burst_f(q->ring, data, burst_cnt); + } + *op_use_diff = test_clock_time_sub(test_clock_time_get(), op_use_start); + + return dequeue_cnt; +} + +void* test_thread_producer_start(void* arg) { + int ret = 0; + uint32_t enqueue_cnt = 0; + uint64_t ok_cnt = 0; + uint64_t run_times = 0; + test_thread_arg_s* t_arg = (test_thread_arg_s*)arg; + test_cfg* cfg = t_arg->cfg; + test_queue_s* q = t_arg->q; + test_exit_data* exit_data = test_exit_data_create(t_arg); + + char thread_name[128] = {0}; + uint64_t op_ok_latency_ns = 0; + uint64_t op_err_latency_ns = 0; + uint64_t run_ok_times = cfg->run.run_ok_times / cfg->ring.producer_cnt; + test_time_metric op_latency = {0}; + + snprintf(thread_name, sizeof(thread_name), "producer:%x", exit_data->thread_id); + prctl(PR_SET_NAME, thread_name); + + atomic_fetch_add(&cfg->run.thread_start, 1); + test_wait_all_threads_ready(cfg); + + exit_data->metric_start = test_clock_time_get(); + while (true) { + if ((run_ok_times > 0 && ok_cnt >= run_ok_times) || (!cfg->run.running)) { + // 控制次数的循环或运行时间到了 + break; + } + + if (cfg->ring.workload == TEST_WORKLOAD_SIMPLE) { + enqueue_cnt = test_exec_enqueue(q, exit_data->simple_data, cfg->ring.burst_cnt, &op_latency, t_arg->thread_idx); + } else { + // 由于rmind不支持指定个数的批量出队列,为了兼容它,这里分配的空间位置entries大小。 + test_data** data = test_data_create(cfg->ring.entries_cnt); + if (data == NULL) { + BBQ_ERR_LOG("malloc falied"); + exit(-1); + } + enqueue_cnt = test_exec_enqueue(q, data, cfg->ring.burst_cnt, &op_latency, t_arg->thread_idx); + for (uint32_t i = enqueue_cnt; i < cfg->ring.entries_cnt; i++) { + test_free(TEST_MODULE_DATA, data[i]); + } + + test_free(TEST_MODULE_DATA, data); + test_busy_loop(t_arg->busy_loop); + } + + if (enqueue_cnt > 0) { + ok_cnt += enqueue_cnt; + op_ok_latency_ns += test_clock_time_to_ns(&op_latency); + } else { + op_err_latency_ns += test_clock_time_to_ns(&op_latency); + } + + run_times++; + } + + exit_data->metric_end = test_clock_time_get(); + exit_data->run_times = run_times; + exit_data->ok_cnt = ok_cnt; + + exit_data->op_ok_latency_ns = op_ok_latency_ns; + exit_data->op_err_latency_ns = op_err_latency_ns; + atomic_fetch_add(&cfg->run.producer_exit, 1); + + BBQ_DBG_LOG("producer-----> en_ok:%lu", ok_cnt); + pthread_exit(exit_data); +} + +void* test_thread_consumer_start(void* arg) { + uint32_t deq_cnt = -1; + uint64_t ok_cnt = 0; + uint64_t run_times = 0; + test_thread_arg_s* t_arg = (test_thread_arg_s*)arg; + test_cfg* cfg = t_arg->cfg; + test_queue_s* q = t_arg->q; + test_exit_data* exit_data = test_exit_data_create(t_arg); + uint64_t latency_ns = 0; + test_time_metric op_latency = {0}; + uint64_t op_ok_latency_ns; + uint64_t op_err_latency_ns = 0; + uint64_t data_error_cnt = 0; + char thread_name[128] = {0}; + test_data** deq_data = test_malloc(TEST_MODULE_DATA, sizeof(*deq_data) * cfg->ring.entries_cnt); + + snprintf(thread_name, sizeof(thread_name), "consumer:%x", exit_data->thread_id); + prctl(PR_SET_NAME, thread_name); + + atomic_fetch_add(&cfg->run.thread_start, 1); + test_wait_all_threads_ready(cfg); + + exit_data->metric_start = test_clock_time_get(); + + uint32_t last_times = cfg->ring.entries_cnt; + while (true) { + if (!cfg->run.running || test_all_producer_exit(cfg)) { + // 运行时间到了或是所有生产者退出了,检查生产者是否全部退出,且队列被消费完了 + if (deq_cnt == 0 && (last_times-- <= 0)) { + break; + } + } + + deq_cnt = test_exec_dequeue(q, deq_data, cfg->ring.burst_cnt, &op_latency); + if (deq_cnt > 0) { + for (uint32_t i = 0; i < deq_cnt; i++) { + test_data* data = deq_data[i]; + if (t_arg->cfg->ring.workload == TEST_WORKLOAD_SIMPLE) { + if (data->data != TEST_DATA_MAGIC) { + BBQ_ERR_LOG("the obtained data is not consistent with the expectation, expect:%u actual:%u", TEST_DATA_MAGIC, data->data); + exit_data->data_error_cnt += 1; + } + } else { + test_time_metric latency = test_clock_time_sub(test_clock_time_get(), data->enqueue_time); + if (test_clock_time_is_zero(&data->enqueue_time)) { + BBQ_ERR_LOG("enqueue_time is 0"); + exit(-1); + } + + if (data->data != TEST_DATA_MAGIC) { + BBQ_ERR_LOG("the obtained data is not consistent with the expectation, expect:%u actual:%u", TEST_DATA_MAGIC, data->data); + data_error_cnt += 1; + } + + latency_ns += test_clock_time_to_ns(&latency); + test_free(TEST_MODULE_DATA, data); + test_busy_loop(t_arg->busy_loop); + } + } + ok_cnt += deq_cnt; + op_ok_latency_ns += test_clock_time_to_ns(&op_latency); + } else { + op_err_latency_ns += test_clock_time_to_ns(&op_latency); + } + + run_times++; + } + + exit_data->metric_end = test_clock_time_get(); + exit_data->run_times = run_times; + exit_data->ok_cnt = ok_cnt; + exit_data->latency_ns = latency_ns; + exit_data->op_ok_latency_ns = op_ok_latency_ns; + exit_data->op_err_latency_ns = op_err_latency_ns; + exit_data->data_error_cnt = data_error_cnt; + + test_free(TEST_MODULE_DATA, deq_data); + BBQ_DBG_LOG("consumer-----> de_ok:%lu", ok_cnt); + pthread_exit(exit_data); +} + +void test_check_run_time(test_cfg* cfg) { + if (cfg->run.run_time > 0) { + BBQ_DBG_LOG("sleep %lus, and notify all threads to exit...", cfg->run.run_time); + sleep(cfg->run.run_time); + cfg->run.running = false; + } +} + +pthread_t* +test_one_thread_create(test_cfg* cfg, test_queue_s* q, test_thread_type_e ttype, int core, uint16_t thread_id, pthread_t* thread) { + BBQ_DBG_LOG("thread type:%d core:%d", ttype, core); + test_thread_arg_s* arg = (test_thread_arg_s*)test_malloc(TEST_MODULE_COMMON, sizeof(test_thread_arg_s)); // 线程回收时free + arg->cfg = cfg; + arg->q = q; + arg->ttype = ttype; + arg->core = core; + arg->busy_loop = (core + 1) * 7 % 100; + arg->thread_idx = thread_id; + + if (ttype == TEST_THREAD_PRODUCER) { + pthread_create(thread, NULL, test_thread_producer_start, arg); + } else { + pthread_create(thread, NULL, test_thread_consumer_start, arg); + } +} + +#define CORE_ID_CHK_SET(core_id, max_id) \ + do { \ + core_id = (core_id + 1) < max_id ? (core_id + 1) : core_id; \ + } while (0) + +pthread_t* test_threads_create(test_cfg* cfg, test_queue_s* q) { + // 创建生产者消费者线程 + int ret; + uint16_t thread_id = 0; + int core_id = 0; + size_t thread_cnt = cfg->ring.producer_cnt + cfg->ring.consumer_cnt; + pthread_t* threads = (pthread_t*)test_malloc(TEST_MODULE_COMMON, sizeof(pthread_t) * thread_cnt); // 存储所有线程ID的数组 + + // MPSC 或 SPMC 场景在第一个核心/超线程上分配单个生产者或消费者,然后将其他线程按顺序分配给核心/超线程。 + // MPMC,我们将生产者和消费者一一交错分配 + // 如果数量不同,则在最后分配剩余部分。 + if (cfg->ring.producer_cnt == 1 && cfg->ring.consumer_cnt >= 1) { + // SPMC,第一个核心给生产者,其他分配给消费者 + test_one_thread_create(cfg, q, TEST_THREAD_PRODUCER, core_id, thread_id, &(threads[thread_id])); + thread_id++; + for (int i = 0; i < cfg->ring.consumer_cnt; i++) { + CORE_ID_CHK_SET(core_id, cfg->base.cores_cnt); + test_one_thread_create(cfg, q, TEST_THREAD_CONSUMER, core_id, thread_id, &(threads[thread_id])); + thread_id++; + } + } else if (cfg->ring.consumer_cnt == 1 && cfg->ring.producer_cnt >= 1) { + // MPSC,第一个核心给消费者,其他分配给生产者 + test_one_thread_create(cfg, q, TEST_THREAD_CONSUMER, core_id, thread_id, &(threads[thread_id])); + thread_id++; + for (int i = 0; i < cfg->ring.producer_cnt; i++) { + CORE_ID_CHK_SET(core_id, cfg->base.cores_cnt); + test_one_thread_create(cfg, q, TEST_THREAD_PRODUCER, core_id, thread_id, &(threads[thread_id])); + thread_id++; + } + } else { + // MPMC 或 只有生产者 或这有消费者,核心交错分配 + uint32_t pcnt = cfg->ring.producer_cnt; // 生产者个数 + uint32_t ccnt = cfg->ring.consumer_cnt; // 消费者个数 + for (core_id = 0; core_id < cfg->base.cores_cnt && pcnt > 0 && ccnt > 0;) { + if ((core_id & 1) == 0) { + // 偶数 + test_one_thread_create(cfg, q, TEST_THREAD_PRODUCER, core_id, thread_id, &(threads[thread_id])); + thread_id++; + pcnt--; + } else { + test_one_thread_create(cfg, q, TEST_THREAD_CONSUMER, core_id, thread_id, &(threads[thread_id])); + thread_id++; + ccnt--; + } + CORE_ID_CHK_SET(core_id, cfg->base.cores_cnt); + } + + for (int i = 0; i < pcnt; i++) { + test_one_thread_create(cfg, q, TEST_THREAD_PRODUCER, core_id, thread_id, &(threads[thread_id])); + thread_id++; + CORE_ID_CHK_SET(core_id, cfg->base.cores_cnt); + } + + for (int i = 0; i < ccnt; i++) { + test_one_thread_create(cfg, q, TEST_THREAD_CONSUMER, core_id, thread_id, &(threads[thread_id])); + thread_id++; + CORE_ID_CHK_SET(core_id, cfg->base.cores_cnt); + } + } + + return threads; +} + +void test_threads_destory(pthread_t* threads) { + test_free(TEST_MODULE_COMMON, threads); +} + +void test_merge_data_detail(test_merge_data* merge, test_exit_data* exit_data) { + merge->run_times += exit_data->run_times; + merge->ok_cnt += exit_data->ok_cnt; + merge->latency_ns += exit_data->latency_ns; + merge->op_err_latency_ns = exit_data->op_err_latency_ns; + merge->op_ok_latency_ns += exit_data->op_ok_latency_ns; + merge->data_error_cnt += exit_data->data_error_cnt; +} + +void test_merge_all_data(test_exit_data** exit_data, uint32_t thread_cnt, test_merge_s* merge) { + test_time_metric p_start = {0}; + test_time_metric p_end = {0}; + test_time_metric c_start = {0}; + test_time_metric c_end = {0}; + + for (uint32_t i = 0; i < thread_cnt; i++) { + // 根据生产者/消费者 线程最早开始和最晚结束,记录时间 + if (exit_data[i]->arg->ttype == TEST_THREAD_PRODUCER) { + if (test_clock_time_is_zero(&p_start) || test_timespec_is_after(&p_start.timestamp, &exit_data[i]->metric_start.timestamp)) { + p_start = exit_data[i]->metric_start; + } + + if (test_timespec_is_after(&exit_data[i]->metric_start.timestamp, &p_end.timestamp)) { + p_end = exit_data[i]->metric_end; + } + + test_merge_data_detail(&merge->producer, exit_data[i]); + } else { + if (test_clock_time_is_zero(&c_start) || test_timespec_is_after(&c_start.timestamp, &exit_data[i]->metric_start.timestamp)) { + c_start = exit_data[i]->metric_start; + } + + if (test_timespec_is_after(&exit_data[i]->metric_start.timestamp, &c_end.timestamp)) { + c_end = exit_data[i]->metric_end; + } + + test_merge_data_detail(&merge->consumer, exit_data[i]); + } + } + + merge->producer.use_time = test_clock_time_sub(p_end, p_start); + merge->consumer.use_time = test_clock_time_sub(c_end, c_start); +}
\ No newline at end of file diff --git a/bbq/tests/common/test_queue.h b/bbq/tests/common/test_queue.h new file mode 100644 index 0000000..0d6454f --- /dev/null +++ b/bbq/tests/common/test_queue.h @@ -0,0 +1,101 @@ +/* + * @Author: liuyu + * @LastEditTime: 2024-06-06 16:39:11 + * @Email: [email protected] + * @Describe: TODO + */ +#ifndef _TEST_QUEUE_H_ +#define _TEST_QUEUE_H_ +#include "bbq.h" +#include "test_mix.h" +#include <pthread.h> + +#define TEST_DATA_MAGIC 1234567890 + +typedef void (*test_ring_free_f)(void *ring); +typedef int (*test_ring_enqueue_f)(void *ring, void *obj); +typedef int (*test_ring_dequeue_f)(void *ring, void *obj); +typedef uint32_t (*test_enqueue_burst_f)(void *ring, void *obj_table, size_t n, unsigned int flags, uint16_t thread_idx); +typedef uint32_t (*test_dequeue_burst_f)(void *ring, void *obj_table, uint32_t n); +typedef bool (*test_ring_empty_f)(void *ring); + +typedef struct { + void *ring; + test_ring_type ring_type; + test_ring_free_f ring_free_f; + test_ring_enqueue_f enqueue_f; + test_ring_dequeue_f dequeue_f; + test_enqueue_burst_f enqueue_burst_f; + test_dequeue_burst_f dequeue_burst_f; +} test_queue_s; + +typedef struct { + int core; + uint16_t thread_idx; // 线程索引,不是pthread_id + uint32_t busy_loop; // 忙循环次数 + test_thread_type_e ttype; + test_cfg *cfg; + test_queue_s *q; +} test_thread_arg_s; + +typedef struct bcm_benchmark { + uint32_t data; // 数据 + test_time_metric enqueue_time; // 入队时间 +} test_data; + +typedef struct { + test_data *data; + uint64_t data_cnt; +} test_complex_data; + +typedef struct { + pthread_t thread_id; + test_time_metric metric_start; + test_time_metric metric_end; + uint64_t run_times; + uint64_t ok_cnt; + uint64_t latency_ns; // 仅消费者有效,数据停留的时延 + uint64_t op_ok_latency_ns; // 成功操作的时延 + uint64_t op_err_latency_ns; // 操作失败的时延, 如满队入队,空队出队 + uint64_t data_error_cnt; // 发生过至少一次数据不一致的次数 + test_thread_arg_s *arg; + size_t simple_data_cnt; + test_data **simple_data; +} test_exit_data; + +typedef struct { + test_time_metric use_time; + uint64_t run_times; + uint64_t ok_cnt; + uint64_t latency_ns; // 仅消费者有效,数据停留的时延 + uint64_t op_ok_latency_ns; // 成功操作的时延 + uint64_t op_err_latency_ns; // 操作失败的时延, 如满队入队,空队出队 + uint64_t data_error_cnt; // 发生过至少一次数据不一致的次数 +} test_merge_data; + +typedef struct { + test_merge_data producer; + test_merge_data consumer; +} test_merge_s; + +extern void test_threads_destory(pthread_t *threads); +extern pthread_t *test_threads_create(test_cfg *cfg, test_queue_s *q); +extern pthread_t *test_one_thread_create(test_cfg *cfg, test_queue_s *q, test_thread_type_e ttype, int core, uint16_t thread_id, pthread_t *thread); +extern void test_check_run_time(test_cfg *cfg); +extern void *test_thread_consumer_start(void *arg); +extern void *test_thread_producer_start(void *arg); +extern uint32_t test_exec_dequeue(test_queue_s *q, test_data **data, size_t burst_cnt, test_time_metric *op_use_diff); +extern uint32_t test_exec_enqueue(test_queue_s *q, test_data **data, size_t burst_cnt, test_time_metric *op_use_diff, uint16_t thread_idx); +extern test_exit_data *test_exit_data_destory(test_exit_data *data); +extern test_exit_data *test_exit_data_create(test_thread_arg_s *t_arg); +extern void test_wait_all_threads_ready(test_cfg *cfg); +extern void test_queue_destory(test_queue_s *q); +extern int test_queue_init_bbq(test_cfg *cfg, test_queue_s *q); +extern void test_merge_all_data(test_exit_data **exit_data, uint32_t thread_cnt, test_merge_s *merge); +extern uint64_t bbq_idx(bbq_queue_s *q, uint64_t x); +extern uint64_t bbq_off(bbq_queue_s *q, uint64_t x); +extern uint64_t bbq_head_vsn(bbq_queue_s *q, uint64_t x); +extern uint64_t bbq_cur_vsn(bbq_queue_s *q, uint64_t x); +extern test_data **test_data_create(size_t cnt); +extern void test_data_destory(test_data **data, size_t cnt); +#endif
\ No newline at end of file diff --git a/bbq/tests/unittest/CMakeLists.txt b/bbq/tests/unittest/CMakeLists.txt new file mode 100644 index 0000000..835058b --- /dev/null +++ b/bbq/tests/unittest/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.0) +project(BBQ_UNITTEST) + +# 搜索当前cmake文件所在目录下的c文件 +file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/*.cc") +list(APPEND SRC_LIST ${SRC_COMMON_LIST}) + +add_executable(bbq_unittest ${SRC_LIST}) # 添加可执行程序 +target_link_libraries(bbq_unittest gtest gtest_main pthread) # 链接gtest库 + +add_test(bbq_unittest ${EXEC_PATH}/bbq_unittest) # 添加测试,保证make test可以执行该测试用例
\ No newline at end of file diff --git a/bbq/tests/unittest/ut.h b/bbq/tests/unittest/ut.h new file mode 100644 index 0000000..b733c9f --- /dev/null +++ b/bbq/tests/unittest/ut.h @@ -0,0 +1,37 @@ +/* + * @Author: liuyu + * @LastEditTime: 2024-05-31 17:33:50 + * @Email: [email protected] + * @Describe: TODO + */ + +#ifndef _UT_H_ +#define _UT_H_ +#include "bbq.h" +#include "test_mix.h" +#include <stdint.h> + +typedef struct { + uint64_t v1; + uint64_t v2; +} testdata_s; + +typedef struct { + bbq_queue_s *q; + uint32_t usleep; // 该线程每次执行间隔睡眠时间 + bool until_end; // 循环读取,直到队列空或满 + uint64_t thread_exec_times; // 每个线程生产/消费次数 +} thread_arg_s; + +typedef struct { + pthread_t thread_id; + test_thread_type_e type; // 线程类型 + uint64_t exec_times; // 执行次数 + uint64_t succ_times; // 成功次数 + uint64_t full_times; // 失败,队列满次数 + uint64_t busy_times; // 失败,队列忙次数 + uint64_t empty_times; // 失败,队列空次数 + testdata_s *data; // 线程数据 + thread_arg_s *arg; // 创建线程时传入的参数 +} thread_data_s; +#endif
\ No newline at end of file diff --git a/bbq/tests/unittest/ut_data.cc b/bbq/tests/unittest/ut_data.cc new file mode 100644 index 0000000..493c003 --- /dev/null +++ b/bbq/tests/unittest/ut_data.cc @@ -0,0 +1,76 @@ +/* + * @Author: liuyu + * @LastEditTime: 2024-06-07 00:51:44 + * @Email: [email protected] + * @Describe: TODO + */ + +#include "gtest/gtest.h" +extern "C" { +#include "test_mix.h" +#include "test_queue.h" +#include "ut.h" +extern bool bbq_malloc_free_equal(); +extern bool test_malloc_free_equal(); +extern void bbq_memory_info(); +} + +TEST(data, correct) { + test_memory_counter_clear(); + + test_cfg cfg = { + .base = { + .name = "data correct", + .introduce = "data correct", + .cores_cnt = 4, + }, + .ring = { + .ring_type = TEST_RING_TYPE_BBQ, + .producer_cnt = 5, + .consumer_cnt = 5, + .workload = TEST_WORKLOAD_SIMPLE, + .entries_cnt = 4096, + .block_count = 0, + .burst_cnt = 1, + }, + .run = { + .run_ok_times = 50000, + .running = true, + }, + }; + + // 队列初始化 + int ret = -1; + test_queue_s q; + ret = test_queue_init_bbq(&cfg, &q); + ASSERT_TRUE(ret == 0); + + // 创建线程 + pthread_t* threads = test_threads_create(&cfg, &q); + ASSERT_TRUE(threads); + + // 等待所有线程完成,回收数据 + uint32_t thread_cnt = cfg.ring.producer_cnt + cfg.ring.consumer_cnt; + test_exit_data** exit_data = (test_exit_data**)test_malloc(TEST_MODULE_UTEST, sizeof(test_exit_data**) * (thread_cnt)); + uint32_t i = 0; + + for (i = 0; i < thread_cnt; i++) { + pthread_join(threads[i], (void**)(&exit_data[i])); // 等待每个线程结束 + } + + // 比较数据 + test_merge_s merge = {0}; + test_merge_all_data(exit_data, thread_cnt, &merge); + EXPECT_EQ(merge.consumer.data_error_cnt, 0); + EXPECT_EQ(merge.consumer.ok_cnt, merge.producer.ok_cnt); + + // 释放数据 + for (i = 0; i < thread_cnt; i++) { + test_exit_data_destory(exit_data[i]); + } + test_free(TEST_MODULE_UTEST, exit_data); + test_threads_destory(threads); + test_queue_destory(&q); + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +}
\ No newline at end of file diff --git a/bbq/tests/unittest/ut_example.cc b/bbq/tests/unittest/ut_example.cc new file mode 100644 index 0000000..049d41f --- /dev/null +++ b/bbq/tests/unittest/ut_example.cc @@ -0,0 +1,230 @@ +/* + * @Author: liuyu + * @LastEditTime: 2024-06-05 17:21:25 + * @Email: [email protected] + * @Describe: TODO + */ +/* + * @Author: liuyu + * @LastEditTime: 2024-06-04 18:16:51 + * @Email: [email protected] + * @Describe: 简单的测试用例,测试基本功能,同时让调用者快速上手 + */ + +#include "gtest/gtest.h" +extern "C" { +#include "test_mix.h" +#include "test_queue.h" +#include "ut.h" +extern bool bbq_malloc_free_equal(); +extern void bbq_memory_info(); +} + +TEST(burst, table_cp_value) { + test_memory_counter_clear(); + + uint32_t ret1 = 0; + uint32_t ret2 = 0; + uint32_t ret3 = 0; + bbq_queue_s *q; + uint32_t buf_cnt = 4096; + uint32_t cnt1 = 2048; + uint32_t cnt2 = 2048; + uint32_t cnt3 = 128; + + // 创建测试数据 + uint64_t enq_table1[cnt1]; + uint64_t enq_table2[cnt2]; + uint64_t enq_table3[cnt3]; + memset(enq_table1, 1, sizeof(enq_table1)); + memset(enq_table2, 1, sizeof(enq_table2)); + memset(enq_table3, 1, sizeof(enq_table3)); + + uint64_t deq_table1[cnt1]; + uint64_t deq_table2[cnt2]; + uint64_t deq_table3[cnt3]; + + // 创建队列 + q = bbq_ring_create(buf_cnt, sizeof(uint64_t), BBQ_F_COPY_VALUE | BBQ_F_POLICY_RETRY_NEW); + EXPECT_TRUE(q); + + // 批量入队 + ret1 = bbq_enqueue_burst(q, (void *)enq_table1, cnt1, BBQ_F_ARRAY_1D); + EXPECT_EQ(ret1, cnt1); + ret3 = bbq_enqueue_burst(q, (void *)enq_table3, cnt3, BBQ_F_ARRAY_1D); + EXPECT_EQ(ret3, cnt3); + // 队列已满,实际入队<cnt2 + ret2 = bbq_enqueue_burst(q, (void *)enq_table2, cnt2, BBQ_F_ARRAY_1D); + EXPECT_EQ(ret2, cnt2 - cnt3); + EXPECT_EQ(buf_cnt, ret1 + ret2 + ret3); + + ret3 = bbq_dequeue_burst(q, (void *)deq_table3, cnt3); + EXPECT_EQ(ret3, cnt3); + ret2 = bbq_dequeue_burst(q, (void *)deq_table2, cnt2); + EXPECT_EQ(ret2, cnt2); + // 队列已空,实际入队<cnt1 + ret1 = bbq_dequeue_burst(q, (void *)deq_table1, cnt1); + EXPECT_EQ(ret1, cnt1 - cnt3); + EXPECT_EQ(buf_cnt, ret1 + ret2 + ret3); + + // 验证数据 + uint64_t tmp = 0; + memset(&tmp, 1, sizeof(tmp)); + + for (uint32_t i = 0; i < ret1; i++) { + EXPECT_TRUE(deq_table1[i]); + EXPECT_EQ(deq_table1[i], tmp); + } + for (uint32_t i = 0; i < ret2; i++) { + EXPECT_TRUE(deq_table2[i]); + EXPECT_EQ(deq_table2[i], tmp); + } + for (uint32_t i = 0; i < ret3; i++) { + EXPECT_TRUE(deq_table3[i]); + EXPECT_EQ(deq_table3[i], tmp); + } + + bbq_ring_free(q); + + // 内存泄漏检测 + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +} + +#define BUF_CNT 4096 +#define ENQ_TABLE1_CNT 4000 +#define ENQ_TABLE2_CNT 90 +#define ENQ_TABLE3_CNT 10 + +TEST(burst, ptr_table_cp_value) { + test_memory_counter_clear(); + uint32_t ret1 = 0; + uint32_t ret2 = 0; + uint32_t ret3 = 0; + bbq_queue_s *q; + + // 创建测试数据,3种数据类型,均将数据值入队列 + uint16_t **enq_table1 = test_enqueue_table_create(ENQ_TABLE1_CNT); + uint16_t *enq_table2[ENQ_TABLE2_CNT] = {0}; + for (int i = 0; i < ENQ_TABLE2_CNT; i++) { + enq_table2[i] = (uint16_t *)test_malloc(TEST_MODULE_DATA, sizeof(uint16_t)); + *enq_table2[i] = TEST_TABLE_DATA_MAGIC; + } + uint16_t enq_table3[ENQ_TABLE3_CNT]; + for (int i = 0; i < ENQ_TABLE3_CNT; i++) { + enq_table3[i] = TEST_TABLE_DATA_MAGIC; + } + + uint16_t deq_table1[ENQ_TABLE1_CNT] = {0}; + uint16_t *deq_table2 = (uint16_t *)test_malloc(TEST_MODULE_DATA, sizeof(uint16_t) * BUF_CNT); + + // 创建队列 + q = bbq_ring_create(BUF_CNT, sizeof(uint16_t), BBQ_F_POLICY_RETRY_NEW | BBQ_F_COPY_VALUE); + EXPECT_TRUE(q); + + // 批量入队(全成功) + ret1 = bbq_enqueue_burst(q, (void *)enq_table1, ENQ_TABLE1_CNT, BBQ_F_ARRAY_2D); + EXPECT_EQ(ret1, ENQ_TABLE1_CNT); + + // 批量入队(全成功) + ret2 = bbq_enqueue_burst(q, (void *)enq_table2, ENQ_TABLE2_CNT, BBQ_F_ARRAY_2D); + EXPECT_EQ(ret2, ENQ_TABLE2_CNT); + + // 批量入队(部分成功) + ret3 = bbq_enqueue_burst(q, (void *)enq_table3, ENQ_TABLE3_CNT, BBQ_F_ARRAY_1D); + EXPECT_EQ(ret3, BUF_CNT - ret1 - ret2); + + /*------------------------------------------------------------------*/ + // 出队列(全成功) + ret1 = bbq_dequeue_burst(q, (void *)deq_table1, ENQ_TABLE1_CNT); + EXPECT_EQ(ret1, ENQ_TABLE1_CNT); + + // 出队列(部分成功) + ret2 = bbq_dequeue_burst(q, (void *)deq_table2, BUF_CNT); + EXPECT_EQ(ret2, BUF_CNT - ret1); + + // 验证数据 + for (uint32_t i = 0; i < ret1; i++) { + EXPECT_TRUE(deq_table1[i]); + EXPECT_EQ(deq_table1[i], TEST_TABLE_DATA_MAGIC) << "i :" << i; + } + + for (uint32_t i = 0; i < ret2; i++) { + EXPECT_TRUE(deq_table2[i]); + EXPECT_EQ(deq_table2[i], TEST_TABLE_DATA_MAGIC) << "i :" << i; + } + + bbq_ring_free(q); + + // 释放测试数据 + test_enqueue_table_destory(enq_table1, ENQ_TABLE1_CNT); + for (int i = 0; i < ENQ_TABLE2_CNT; i++) { + test_free(TEST_MODULE_DATA, enq_table2[i]); + } + test_free(TEST_MODULE_DATA, deq_table2); + // 内存泄漏检测 + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +} + +TEST(burst, ptr_table_cp_pointer) { + test_memory_counter_clear(); + uint32_t ret1 = 0; + uint32_t ret2 = 0; + uint32_t ret3 = 0; + bbq_queue_s *q; + + // 创建测试数据,3种数据类型,均将数据值入队列 + uint16_t **enq_table1 = test_enqueue_table_create(ENQ_TABLE1_CNT); + uint16_t *enq_table2[ENQ_TABLE2_CNT] = {0}; + for (int i = 0; i < ENQ_TABLE2_CNT; i++) { + enq_table2[i] = (uint16_t *)test_malloc(TEST_MODULE_DATA, sizeof(uint16_t)); + *enq_table2[i] = TEST_TABLE_DATA_MAGIC; + } + uint16_t enq_table3[ENQ_TABLE3_CNT]; + for (int i = 0; i < ENQ_TABLE3_CNT; i++) { + enq_table3[i] = TEST_TABLE_DATA_MAGIC; + } + + uint16_t *deq_table1[BUF_CNT] = {0}; + uint16_t *deq_table2[BUF_CNT] = {0}; + uint16_t *deq_table3[BUF_CNT] = {0}; + + // 创建队列 + q = bbq_ring_create(BUF_CNT, sizeof(uint16_t), BBQ_F_POLICY_RETRY_NEW | BBQ_F_COPY_POINTER); + EXPECT_TRUE(q); + + ret3 = bbq_enqueue_burst(q, (void *)enq_table3, ENQ_TABLE3_CNT, BBQ_F_ARRAY_1D); + EXPECT_EQ(ret3, ENQ_TABLE3_CNT); + + /*------------------------------------------------------------------*/ + ret3 = bbq_dequeue_burst(q, (void *)deq_table3, ENQ_TABLE3_CNT); + EXPECT_EQ(ret3, ENQ_TABLE3_CNT); + + // 验证数据 + for (uint32_t i = 0; i < ret1; i++) { + EXPECT_TRUE(deq_table1[i]); + EXPECT_EQ(*deq_table1[i], TEST_TABLE_DATA_MAGIC) << "i :" << i; + } + + for (uint32_t i = 0; i < ret2; i++) { + EXPECT_TRUE(deq_table2[i]); + EXPECT_EQ(*deq_table2[i], TEST_TABLE_DATA_MAGIC) << "i :" << i; + } + + for (uint32_t i = 0; i < ret3; i++) { + EXPECT_TRUE(deq_table3[i]); + EXPECT_EQ(*deq_table3[i], TEST_TABLE_DATA_MAGIC) << "i :" << i; + } + + bbq_ring_free(q); + + // 释放测试数据 + test_enqueue_table_destory(enq_table1, ENQ_TABLE1_CNT); + for (int i = 0; i < ENQ_TABLE2_CNT; i++) { + test_free(TEST_MODULE_DATA, enq_table2[i]); + } + // 内存泄漏检测 + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +}
\ No newline at end of file diff --git a/bbq/tests/unittest/ut_head_cursor.cc b/bbq/tests/unittest/ut_head_cursor.cc new file mode 100644 index 0000000..be65906 --- /dev/null +++ b/bbq/tests/unittest/ut_head_cursor.cc @@ -0,0 +1,522 @@ +/* + * @Author: liuyu + * @LastEditTime: 2024-06-07 01:13:55 + * @Email: [email protected] + * @Describe: TODO + */ +#include "gtest/gtest.h" +extern "C" { +#include "test_queue.h" +#include "ut.h" +extern bool bbq_malloc_free_equal(); +extern void bbq_memory_info(); +} + +void expect_phead(bbq_queue_s* q, uint64_t idx, uint64_t vsn, int line) { + EXPECT_EQ(bbq_idx(q, q->phead), idx) << "line: " << line; + EXPECT_EQ(bbq_head_vsn(q, q->phead), vsn) << "line: " << line; +} + +void expect_chead(bbq_queue_s* q, uint64_t idx, uint64_t vsn, int line) { + EXPECT_EQ(bbq_idx(q, q->chead), idx) << "line: " << line; + EXPECT_EQ(bbq_head_vsn(q, q->chead), vsn) << "line: " << line; +} + +void expect_eq_allocated(bbq_queue_s* q, bbq_block_s* block, uint64_t off, uint64_t vsn, int line) { + EXPECT_EQ(bbq_off(q, block->allocated), off) << "line: " << line; + EXPECT_EQ(bbq_cur_vsn(q, block->allocated), vsn) << "line: " << line; +} + +void expect_eq_committed(bbq_queue_s* q, bbq_block_s* block, uint64_t off, uint64_t vsn, int line) { + EXPECT_EQ(bbq_off(q, block->committed), off) << "line: " << line; + EXPECT_EQ(bbq_cur_vsn(q, block->committed), vsn) << "line: " << line; +} + +void expect_eq_consumed(bbq_queue_s* q, bbq_block_s* block, uint64_t off, uint64_t vsn, int line) { + EXPECT_EQ(bbq_off(q, block->consumed), off) << "line: " << line; + EXPECT_EQ(bbq_cur_vsn(q, block->consumed), vsn) << "line: " << line; +} + +void expect_eq_reserved(bbq_queue_s* q, bbq_block_s* block, uint64_t off, uint64_t vsn, int line) { + EXPECT_EQ(bbq_off(q, block->reserved), off) << "line: " << line; + EXPECT_EQ(bbq_cur_vsn(q, block->reserved), vsn) << "line: " << line; +} + +// 初始化状态 +TEST(head_cursor, init) { + test_memory_counter_clear(); + + int ret = 0; + bbq_queue_s* q; + uint32_t bn = 2; + uint32_t bs = 4; + int enqueue_data = TEST_DATA_MAGIC; + + q = bbq_ring_create_bnbs(bn, bs, sizeof(int), BBQ_F_COPY_VALUE | BBQ_F_POLICY_RETRY_NEW); + EXPECT_TRUE(q); + + // 1.初始化状态,除了第一个block外其他块的4个游标都指向最后一个条目 + EXPECT_EQ(q->phead, 0); + EXPECT_EQ(q->chead, 0); + + expect_eq_allocated(q, &q->blocks[0], 0, 0, __LINE__); + expect_eq_committed(q, &q->blocks[0], 0, 0, __LINE__); + expect_eq_reserved(q, &q->blocks[0], 0, 0, __LINE__); + expect_eq_consumed(q, &q->blocks[0], 0, 0, __LINE__); + for (uint32_t i = 1; i < bn; i++) { + expect_eq_allocated(q, &q->blocks[i], bs, 0, __LINE__); + expect_eq_committed(q, &q->blocks[i], bs, 0, __LINE__); + expect_eq_reserved(q, &q->blocks[i], bs, 0, __LINE__); + expect_eq_consumed(q, &q->blocks[i], bs, 0, __LINE__); + } + + bbq_ring_free(q); + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +} + +void ut_produce_something(uint32_t produce_cnt) { + int ret = 0; + bbq_queue_s* q; + uint32_t bn = 8; + uint32_t bs = 4096; + int enqueue_data = TEST_DATA_MAGIC; + int dequeue_data = 0; + + EXPECT_GT(produce_cnt, 0); + EXPECT_LE(produce_cnt, bs); + + q = bbq_ring_create_bnbs(bn, bs, sizeof(int), BBQ_F_COPY_VALUE | BBQ_F_POLICY_RETRY_NEW); + EXPECT_TRUE(q); + + // 生产produce_cnt + for (uint32_t i = 0; i < produce_cnt; i++) { + ret = bbq_enqueue(q, &enqueue_data); + EXPECT_TRUE(ret == BBQ_OK); + } + + EXPECT_EQ(q->phead, 0); + EXPECT_EQ(q->chead, 0); + expect_eq_allocated(q, &q->blocks[0], produce_cnt, 0, __LINE__); + expect_eq_committed(q, &q->blocks[0], produce_cnt, 0, __LINE__); + expect_eq_reserved(q, &q->blocks[0], 0, 0, __LINE__); + expect_eq_consumed(q, &q->blocks[0], 0, 0, __LINE__); + + // 消费完 + for (uint32_t i = 0; i < produce_cnt; i++) { + ret = bbq_dequeue(q, &dequeue_data); + EXPECT_TRUE(ret == BBQ_OK); + EXPECT_EQ(dequeue_data, TEST_DATA_MAGIC); + } + + EXPECT_EQ(q->phead, 0); + EXPECT_EQ(q->chead, 0); + expect_eq_allocated(q, &q->blocks[0], produce_cnt, 0, __LINE__); + expect_eq_committed(q, &q->blocks[0], produce_cnt, 0, __LINE__); + expect_eq_reserved(q, &q->blocks[0], produce_cnt, 0, __LINE__); + expect_eq_consumed(q, &q->blocks[0], produce_cnt, 0, __LINE__); + + for (uint32_t i = 1; i < bn; i++) { + expect_eq_allocated(q, &q->blocks[i], bs, 0, __LINE__); + expect_eq_committed(q, &q->blocks[i], bs, 0, __LINE__); + expect_eq_reserved(q, &q->blocks[i], bs, 0, __LINE__); + expect_eq_consumed(q, &q->blocks[i], bs, 0, __LINE__); + } + + bbq_ring_free(q); +} +// 在第一块内生产,然后被消费完 +TEST(head_cursor, produce_something) { + test_memory_counter_clear(); + + ut_produce_something(1); + ut_produce_something(567); + ut_produce_something(789); + ut_produce_something(4096); + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +} + +void ut_produce_next_block(uint32_t over) { + int ret = 0; + bbq_queue_s* q; + uint32_t bn = 8; + uint32_t bs = 4096; + uint32_t produce_cnt = bs + over; + int enqueue_data = TEST_DATA_MAGIC; + int dequeue_data = 0; + + EXPECT_GT(over, 0); + EXPECT_LT(over, bs); + + q = bbq_ring_create_bnbs(bn, bs, sizeof(int), BBQ_F_COPY_VALUE | BBQ_F_POLICY_RETRY_NEW); + EXPECT_TRUE(q); + + // 生产至第二块的第一个entry + for (uint32_t i = 0; i < produce_cnt; i++) { + ret = bbq_enqueue(q, &enqueue_data); + EXPECT_TRUE(ret == BBQ_OK); + } + + EXPECT_EQ(q->chead, 0); + expect_phead(q, 1, 0, __LINE__); + expect_eq_allocated(q, &q->blocks[0], bs, 0, __LINE__); + expect_eq_committed(q, &q->blocks[0], bs, 0, __LINE__); + expect_eq_reserved(q, &q->blocks[0], 0, 0, __LINE__); + expect_eq_consumed(q, &q->blocks[0], 0, 0, __LINE__); + + expect_eq_allocated(q, &q->blocks[1], over, 1, __LINE__); + expect_eq_committed(q, &q->blocks[1], over, 1, __LINE__); + expect_eq_reserved(q, &q->blocks[1], bs, 0, __LINE__); + expect_eq_consumed(q, &q->blocks[1], bs, 0, __LINE__); + + // 消费完 + for (uint32_t i = 0; i < produce_cnt; i++) { + ret = bbq_dequeue(q, &dequeue_data); + EXPECT_TRUE(ret == BBQ_OK); + EXPECT_EQ(dequeue_data, TEST_DATA_MAGIC); + } + + expect_phead(q, 1, 0, __LINE__); + expect_chead(q, 1, 0, __LINE__); + expect_eq_allocated(q, &q->blocks[0], bs, 0, __LINE__); + expect_eq_committed(q, &q->blocks[0], bs, 0, __LINE__); + expect_eq_reserved(q, &q->blocks[0], bs, 0, __LINE__); + expect_eq_consumed(q, &q->blocks[0], bs, 0, __LINE__); + + expect_eq_allocated(q, &q->blocks[1], over, 1, __LINE__); + expect_eq_committed(q, &q->blocks[1], over, 1, __LINE__); + expect_eq_reserved(q, &q->blocks[1], over, 1, __LINE__); + expect_eq_consumed(q, &q->blocks[1], over, 1, __LINE__); + + bbq_ring_free(q); +} + +// 第一块生产完毕,第二块生产了若干,然后被消费完 +TEST(head_cursor, produce_next_block) { + test_memory_counter_clear(); + + ut_produce_next_block(1); + ut_produce_next_block(123); + ut_produce_next_block(456); + ut_produce_next_block(4095); + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +} + +void ut_produce_all_loop(uint32_t loop) { + int ret = 0; + bbq_queue_s* q; + uint32_t bn = 8; + uint32_t bs = 4096; + uint32_t produce_cnt = bn * bs; + int enqueue_data = TEST_DATA_MAGIC; + int dequeue_data = 0; + + q = bbq_ring_create_bnbs(bn, bs, sizeof(int), BBQ_F_COPY_VALUE | BBQ_F_POLICY_RETRY_NEW); + EXPECT_TRUE(q); + + for (uint32_t cnt = 0; cnt < loop; cnt++) { + // 所有entry生产完毕 + for (uint32_t i = 0; i < produce_cnt; i++) { + ret = bbq_enqueue(q, &enqueue_data); + EXPECT_TRUE(ret == BBQ_OK); + } + + // 消费完 + for (uint32_t i = 0; i < produce_cnt; i++) { + ret = bbq_dequeue(q, &dequeue_data); + EXPECT_TRUE(ret == BBQ_OK); + EXPECT_EQ(dequeue_data, TEST_DATA_MAGIC); + } + } + + expect_phead(q, bn - 1, loop - 1, __LINE__); + expect_chead(q, bn - 1, loop - 1, __LINE__); + + expect_eq_allocated(q, &q->blocks[0], bs, loop - 1, __LINE__); + expect_eq_committed(q, &q->blocks[0], bs, loop - 1, __LINE__); + expect_eq_reserved(q, &q->blocks[0], bs, loop - 1, __LINE__); + expect_eq_consumed(q, &q->blocks[0], bs, loop - 1, __LINE__); + + for (uint32_t i = 1; i < bn; i++) { + expect_eq_allocated(q, &q->blocks[i], bs, loop, __LINE__); + expect_eq_committed(q, &q->blocks[i], bs, loop, __LINE__); + expect_eq_reserved(q, &q->blocks[i], bs, loop, __LINE__); + expect_eq_consumed(q, &q->blocks[i], bs, loop, __LINE__); + } +} + +// 完成多轮的满生产和满消费 +TEST(head_cursor, produce_all_loop) { + test_memory_counter_clear(); + + ut_produce_all_loop(1); + ut_produce_all_loop(10); + ut_produce_all_loop(23); + ut_produce_all_loop(79); + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +} + +TEST(boundary, retry_new_full_empty) { + test_memory_counter_clear(); + int ret = 0; + uint32_t entries_cnt = 4096; + uint32_t loop = 1000; + bbq_queue_s* q; + + int* data = (int*)test_malloc(TEST_MODULE_UTEST, sizeof(*data) * entries_cnt); + int tmp_data = 0; + EXPECT_TRUE(data); + + q = bbq_ring_create(entries_cnt, sizeof(int), BBQ_F_COPY_VALUE | BBQ_F_POLICY_RETRY_NEW); + EXPECT_TRUE(q); + + for (uint32_t i = 0; i < loop; i++) { + // 入满队 + for (uint32_t j = 0; j < entries_cnt; j++) { + data[j] = (i + 1) * j; + ret = bbq_enqueue(q, &data[j]); + EXPECT_TRUE(ret == BBQ_OK) << "ret " << ret; + } + + // 满队再入队 + for (uint32_t j = 0; j < entries_cnt / 3; j++) { + ret = bbq_enqueue(q, &data[j]); + EXPECT_TRUE(ret == BBQ_QUEUE_FULL); + } + + if (i == 0) { + EXPECT_EQ((q->phead.load() + 1) & q->idx_mask, q->chead.load() & q->idx_mask); + } else { + EXPECT_EQ((q->phead.load()) & q->idx_mask, q->chead.load() & q->idx_mask); + } + for (uint32_t i = 0; i < q->bn; i++) { + EXPECT_EQ(q->blocks[i].committed.load() & q->off_mask, q->bs); + EXPECT_GE(q->blocks[i].allocated.load() & q->off_mask, q->bs); + } + + // 全出队 + for (uint32_t j = 0; j < entries_cnt; j++) { + ret = bbq_dequeue(q, &tmp_data); + EXPECT_TRUE(ret == BBQ_OK); + EXPECT_EQ(tmp_data, data[j]); + } + + // 空出队再出队 + for (uint32_t j = 0; j < entries_cnt / 2; j++) { + ret = bbq_dequeue(q, &tmp_data); + EXPECT_TRUE(ret == BBQ_QUEUE_EMPTY); + } + + EXPECT_EQ(q->phead.load() & q->idx_mask, q->chead.load() & q->idx_mask); + for (uint32_t i = 0; i < q->bn; i++) { + EXPECT_EQ(q->blocks[i].committed.load() & q->off_mask, q->bs); + EXPECT_GE(q->blocks[i].allocated.load() & q->off_mask, q->bs); + EXPECT_EQ(q->blocks[i].consumed.load() & q->off_mask, q->bs); + EXPECT_GE(q->blocks[i].reserved.load() & q->off_mask, q->bs); + } + } + + test_free(TEST_MODULE_UTEST, data); + bbq_ring_free(q); + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +} + +TEST(boundary, mpsc_faa) { + test_memory_counter_clear(); + + test_cfg cfg = { + .base = { + .name = "mpsc_faa", + .introduce = "mpsc_faa", + .cores_cnt = 4, + }, + .ring = { + .ring_type = TEST_RING_TYPE_BBQ, + .producer_cnt = 10, + .consumer_cnt = 1, + .workload = TEST_WORKLOAD_SIMPLE, + .entries_cnt = 1, + .block_count = 1, + .burst_cnt = 1, + }, + .run = { + .run_time = 5, + .running = true, + }, + }; + + // 队列初始化 + int ret = -1; + test_queue_s q; + ret = test_queue_init_bbq(&cfg, &q); + ASSERT_TRUE(ret == 0); + + // 创建线程 + pthread_t* threads = test_threads_create(&cfg, &q); + ASSERT_TRUE(threads); + + // 等待所有线程完成,回收数据 + uint32_t thread_cnt = cfg.ring.producer_cnt + cfg.ring.consumer_cnt; + test_exit_data** exit_data = (test_exit_data**)test_malloc(TEST_MODULE_UTEST, sizeof(test_exit_data**) * (thread_cnt)); + uint32_t i = 0; + + test_check_run_time(&cfg); + + for (i = 0; i < thread_cnt; i++) { + pthread_join(threads[i], (void**)(&exit_data[i])); // 等待每个线程结束 + } + + // 比较数据 + test_merge_s merge = {0}; + test_merge_all_data(exit_data, thread_cnt, &merge); + EXPECT_EQ(merge.consumer.data_error_cnt, 0); + EXPECT_EQ(merge.consumer.ok_cnt, merge.producer.ok_cnt); + + // 释放数据 + for (i = 0; i < thread_cnt; i++) { + test_exit_data_destory(exit_data[i]); + } + test_free(TEST_MODULE_UTEST, exit_data); + test_threads_destory(threads); + test_queue_destory(&q); + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +} + +void debug_head_print(bbq_queue_s* q) { + printf("phead vsn:%d idx:%d\n", bbq_head_vsn(q, q->phead), bbq_idx(q, q->phead)); + printf("chead vsn:%d idx:%d\n", bbq_head_vsn(q, q->chead), bbq_idx(q, q->chead)); +} + +void debug_block_print(bbq_queue_s* q) { + for (int i = 0; i < q->bn; i++) { + printf("[%d]zzzz allocated:vsn:%d off:%d\n", i, bbq_cur_vsn(q, q->blocks[i].allocated.load()), bbq_off(q, q->blocks[i].allocated.load())); + printf("[%d]zzzz committed:%d off:%d\n", i, bbq_cur_vsn(q, q->blocks[i].committed.load()), bbq_off(q, q->blocks[i].committed.load())); + printf("[%d]zzzz reserved:%d off:%d\n", i, bbq_cur_vsn(q, q->blocks[i].reserved.load()), bbq_off(q, q->blocks[i].reserved.load())); + printf("[%d]zzzz consumed:%d off:%d\n\n", i, bbq_cur_vsn(q, q->blocks[i].consumed.load()), bbq_off(q, q->blocks[i].consumed.load())); + } +} + +TEST(boundary, drop_old_full_empty1) { + test_memory_counter_clear(); + int ret = 0; + uint32_t bn = 2; + uint32_t bs = 4; + uint32_t over_cnt = 3; + uint32_t loop = 1000; + bbq_queue_s* q; + + int tmp_data = 0; + q = bbq_ring_create_bnbs(bn, bs, sizeof(int), BBQ_F_COPY_VALUE | BBQ_F_POLICY_DROP_OLD); + EXPECT_TRUE(q); + + for (uint32_t j = 0; j < loop; j++) { + // 入满队列 + for (uint32_t i = 0; i < bn * bs; i++) { + ret = bbq_enqueue(q, &i); + EXPECT_TRUE(ret == BBQ_OK) << "ret " << ret; + } + + // 全出队 + for (uint32_t i = 0; i < bn * bs; i++) { + ret = bbq_dequeue(q, &tmp_data); + EXPECT_TRUE(ret == BBQ_OK) << "ret " << ret; + EXPECT_EQ(tmp_data, i); + } + + // 空队再出队,失败 + for (uint32_t i = 0; i < bn * bs; i++) { + ret = bbq_dequeue(q, &tmp_data); + EXPECT_TRUE(ret == BBQ_QUEUE_EMPTY) << "ret " << ret; + } + + expect_phead(q, bn - 1, j, __LINE__); + expect_chead(q, bn - 1, j, __LINE__); + for (uint32_t i = 0; i < q->bn; i++) { + expect_eq_committed(q, &q->blocks[i], q->bs, i == 0 ? j : j + 1, __LINE__); + expect_eq_allocated(q, &q->blocks[i], q->bs, i == 0 ? j : j + 1, __LINE__); + expect_eq_reserved(q, &q->blocks[i], q->bs, i == 0 ? j : j + 1, __LINE__); + EXPECT_EQ(q->blocks[i].consumed.load(), 0); + } + } + + bbq_ring_free(q); + + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +} + +TEST(boundary, drop_old_full_empty2) { + test_memory_counter_clear(); + int ret = 0; + uint32_t bn = 2; + uint32_t bs = 4; + uint32_t loop = 1000; + uint32_t over_cnt = bs + 2; + bbq_queue_s* q; + + EXPECT_EQ(over_cnt / bs, 1); + + int tmp_data = 0; + q = bbq_ring_create_bnbs(bn, bs, sizeof(int), BBQ_F_COPY_VALUE | BBQ_F_POLICY_DROP_OLD); + EXPECT_TRUE(q); + + // 入满队列,再入over_cnt + for (uint32_t i = 0; i < bn * bs * loop + over_cnt; i++) { + ret = bbq_enqueue(q, &i); + EXPECT_TRUE(ret == BBQ_OK) << "ret " << ret; + } + + expect_phead(q, 1, loop, __LINE__); + expect_chead(q, 0, 0, __LINE__); + // 检查每一个block上游标的正确性 + for (uint32_t i = 0; i < bn; i++) { + expect_eq_committed(q, &q->blocks[i], + i == bn - 1 ? over_cnt - bs : bs, + i == 0 ? loop : loop + 1, + __LINE__); + expect_eq_allocated(q, &q->blocks[i], + i == bn - 1 ? over_cnt - bs : bs, + i == 0 ? loop : loop + 1, + __LINE__); + expect_eq_reserved(q, &q->blocks[i], + i == 0 ? 0 : bs, 0, + __LINE__); + EXPECT_EQ(q->blocks[i].consumed.load(), 0); + } + + // 队列中的数据全出队 + for (uint32_t i = 0; i < over_cnt - bs; i++) { + ret = bbq_dequeue(q, &tmp_data); + EXPECT_TRUE(ret == BBQ_OK) << "ret " << ret; + } + + for (uint32_t i = 0; i < bn * bs; i++) { + ret = bbq_dequeue(q, &tmp_data); + EXPECT_TRUE(ret == BBQ_QUEUE_EMPTY) << "ret " << ret; + } + + expect_chead(q, 1, 0, __LINE__); + for (uint32_t i = 0; i < bn; i++) { + expect_eq_committed(q, &q->blocks[i], + i == bn - 1 ? over_cnt - bs : bs, + i == 0 ? loop : loop + 1, + __LINE__); + expect_eq_allocated(q, &q->blocks[i], + i == bn - 1 ? over_cnt - bs : bs, + i == 0 ? loop : loop + 1, + __LINE__); + expect_eq_reserved(q, &q->blocks[i], + i == bn - 1 ? over_cnt - bs : bs, + i == 1 ? loop + 1 : 0, __LINE__); + EXPECT_EQ(q->blocks[i].consumed.load(), 0); + } + + bbq_ring_free(q); + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +}
\ No newline at end of file diff --git a/bbq/tests/unittest/ut_mix.cc b/bbq/tests/unittest/ut_mix.cc new file mode 100644 index 0000000..4b33524 --- /dev/null +++ b/bbq/tests/unittest/ut_mix.cc @@ -0,0 +1,153 @@ +/* + * @Author: liuyu + * @LastEditTime: 2024-06-06 23:54:27 + * @Email: [email protected] + * @Describe: bbq除了队列操作外,其他函数的测试 + */ +#include "gtest/gtest.h" +extern "C" { +#include <math.h> +#include "bbq.h" +#include "test_mix.h" +#include "ut.h" +extern bool bbq_check_power_of_two(int n); +extern uint32_t bbq_blocks_calc(uint32_t entries); +extern unsigned ceil_log2(uint64_t x); +extern uint64_t fetch_max(aotmic_uint64* atom, uint64_t upd); +extern bool bbq_malloc_free_equal(); +extern bool test_malloc_free_equal(); +} +namespace { + +typedef struct { + uint64_t thread_cnt; + aotmic_uint64 data; + aotmic_uint64 ready_thread_cnt; +} ut_fetch_arg; + +void* fetch_max_thread_func(void* arg) { + ut_fetch_arg* fetch_arg = (ut_fetch_arg*)arg; + fetch_arg->ready_thread_cnt.fetch_add(1); + + while (fetch_arg->ready_thread_cnt.load() != fetch_arg->thread_cnt) { + } + + uint64_t* ret = (uint64_t*)test_malloc(TEST_MODULE_UTEST, sizeof(*ret)); + // 不同线程写入不同的>3的数 + *ret = fetch_max(&fetch_arg->data, pthread_self() + 3); + pthread_exit(ret); +} + +TEST(bbq_mix, fetch_max) { + test_memory_counter_clear(); + + uint64_t ret = 0; + ut_fetch_arg arg = {0}; + arg.data.store(1); // 初始化1 + arg.thread_cnt = 50; + + ret = fetch_max(&arg.data, 2); // max比较后设置为2 + EXPECT_EQ(arg.data.load(), 2); + EXPECT_EQ(ret, 1); + + pthread_t* threads = (pthread_t*)test_malloc(TEST_MODULE_UTEST, sizeof(*threads) * arg.thread_cnt); + for (uint64_t i = 0; i < arg.thread_cnt; i++) { + // 多个线程同时fetch_max,输入 > 3的数据 + pthread_create(&threads[i], NULL, fetch_max_thread_func, (void*)&arg); + } + + int eq_cnt = 0; + for (int i = 0; i < arg.thread_cnt; i++) { + uint64_t* tret; + pthread_join(threads[i], (void**)&tret); // 等待每个线程结束 + if (*tret == 2) { + eq_cnt++; // 统计返回2的个数 + } + test_free(TEST_MODULE_UTEST, tret); + } + + // EXPECT_EQ(eq_cnt, 1); + test_free(TEST_MODULE_UTEST, threads); + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +} + +TEST(bbq_mix, power_of_two) { + test_memory_counter_clear(); + + uint32_t tmp = 0; + uint32_t max = pow(2, 32) - 1; + + EXPECT_FALSE(bbq_check_power_of_two(0)); + + tmp = 3; + for (uint32_t val = 5; val < max; val *= tmp) { + EXPECT_FALSE(bbq_check_power_of_two(val)); + if (val >= max / tmp) { + break; // 即将越界 + } + } + + tmp = 2; + for (uint32_t val = 1; val < max; val *= tmp) { + EXPECT_TRUE(bbq_check_power_of_two(val)); + if (val >= max / tmp) { + break; + } + } + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +} + +TEST(bbq_mix, bbq_blocks_calc) { + test_memory_counter_clear(); + + uint32_t tmp = 0; + uint32_t max = pow(2, 32) - 1; + + tmp = 2; + for (uint32_t val = 1; val < max; val *= tmp) { + if (val <= 128) { + EXPECT_TRUE(bbq_blocks_calc(val) == 2); + } else if (val <= 2048) { + EXPECT_TRUE(bbq_blocks_calc(val) == 4); + } else if (val <= 32768) { + EXPECT_TRUE(bbq_blocks_calc(val) == 8); + } else if (val <= 524288) { + EXPECT_TRUE(bbq_blocks_calc(val) == 16); + } else if (val <= 8388608) { + EXPECT_TRUE(bbq_blocks_calc(val) == 32); + } else if (val <= 134217728) { + EXPECT_TRUE(bbq_blocks_calc(val) == 64); + } else if (val <= 2147483648) { + EXPECT_TRUE(bbq_blocks_calc(val) == 128); + } else { + EXPECT_TRUE(0); // 异常 + } + + if (val >= max / tmp) { + break; + } + } + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +} + +TEST(bbq_mix, ceil_log2) { + test_memory_counter_clear(); + + uint32_t tmp = 0; + uint32_t max = pow(2, 32) - 1; + + tmp = 2; + for (uint32_t val = 1, bbq_idx = 0; val < max; val *= tmp, bbq_idx++) { + EXPECT_TRUE(bbq_idx == ceil_log2(val)); + if (val >= max / tmp) { + break; + } + EXPECT_TRUE(log2(val) == ceil_log2(val)); + } + EXPECT_TRUE(bbq_malloc_free_equal()); + EXPECT_TRUE(test_malloc_free_equal()); +} +} // namespace
\ No newline at end of file diff --git a/perf/CMakeLists.txt b/perf/CMakeLists.txt new file mode 100644 index 0000000..b72c737 --- /dev/null +++ b/perf/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.0) +project(BBQ_BENCHMARK) + +# 头文件目录 +include_directories( + ${CMAKE_CURRENT_SOURCE_DIR}/../bbq/include + ${CMAKE_CURRENT_SOURCE_DIR}/../bbq/tests/common + ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/iniparser + ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/rmind_ringbuf +) + + +# 将bbq单元测试里的公共文件,添加到perf里。 +SET(TEST_COMMON_DIR ${PROJECT_SOURCE_DIR}/../bbq/tests/common) + +# 设置输出目录 +SET(OUTPUT_DIR ${PROJECT_SOURCE_DIR}/build/output) +# 库生成的路径 +set(LIB_PATH ${OUTPUT_DIR}/lib) +# 测试程序生成的路径 +set(EXEC_PATH ${OUTPUT_DIR}/bin) + +# 指定库路径 +link_directories(${LIB_PATH}) +link_directories(../bbq/build/output/lib/) + +# 可执行程序的名字 +set(BENCHMARK_NAME benchmark) + +# 添加子目录 +add_subdirectory(benchmark) +add_subdirectory(thirdparty) + diff --git a/perf/benchmark/CMakeLists.txt b/perf/benchmark/CMakeLists.txt new file mode 100644 index 0000000..39bada7 --- /dev/null +++ b/perf/benchmark/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.0) +project(BBQ_BENCHMARK) + +# 搜索当前cmake文件所在目录下的c文件 +file(GLOB SRC_LIST "${CMAKE_CURRENT_SOURCE_DIR}/*.c") +file(GLOB SRC_COMMON_LIST "${TEST_COMMON_DIR}/*.c") +list(APPEND SRC_LIST ${SRC_COMMON_LIST}) + +# 指定可执行文件输出路径 +set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH}) + +add_executable(benchmark ${SRC_LIST}) # 添加可执行程序 +target_link_libraries(benchmark iniparser pthread rte_ring rte_eal rte_kvargs rte_telemetry rmind_ringbuf bbq m) # 链接库
\ No newline at end of file diff --git a/perf/benchmark/bcm_benchmark.c b/perf/benchmark/bcm_benchmark.c new file mode 100644 index 0000000..d66819c --- /dev/null +++ b/perf/benchmark/bcm_benchmark.c @@ -0,0 +1,165 @@ +/* + * @Author: liuyu + * @LastEditTime: 2024-06-07 00:33:43 + * @Email: [email protected] + * @Describe: TODO + */ +#include <pthread.h> +#include <stdatomic.h> +#include <stdlib.h> +#include <string.h> +#include <sys/prctl.h> +#include <unistd.h> +#include "bbq.h" +#include "bcm_queue.h" +#include "iniparser.h" +#include "test_mix.h" +#include "test_queue.h" + +extern void bbq_memory_info(); +extern bool bbq_malloc_free_equal(); + +void bcm_report_printf(test_cfg* cfg, test_merge_data* data, test_exit_data** raw_data, uint32_t thread_cnt, test_thread_type_e ttype) { + char name[10] = {0}; + double latency_ns = 0; + double throughput = 0; + + BBQ_COLOR_PRINT("\n---------%s---------", ttype == TEST_THREAD_PRODUCER ? "生产者" : "消费者"); + double use_time = test_clock_time_to_double(&data->use_time); + BBQ_COLOR_PRINT("执行时间 : %lf 秒", use_time); + BBQ_COLOR_PRINT("执行次数 : %lu (burst=%u)", data->run_times, cfg->ring.burst_cnt); + BBQ_COLOR_PRINT("成功%s : %lu", ttype == TEST_THREAD_PRODUCER ? "入队" : "出队", data->ok_cnt); + BBQ_COLOR_PRINT("数据错误次数 : %lu", data->data_error_cnt); + + // 同时有生产者、消费者时才输出 + if (cfg->ring.producer_cnt > 0 && cfg->ring.consumer_cnt > 0) { + throughput = data->ok_cnt / use_time; + BBQ_COLOR_PRINT("吞吐 :%.0lf/s (%e/s)", throughput, throughput); + + // 多生产者单消费者 或 单生产者多消费才输出 + if ((cfg->ring.producer_cnt == 1 && cfg->ring.consumer_cnt > 1) || + (cfg->ring.producer_cnt > 1 && cfg->ring.consumer_cnt == 1)) { + for (uint32_t i = 0, bbq_idx = 1; i < thread_cnt; i++) { + if (raw_data[i]->arg->ttype == ttype) { + test_time_metric tmp_time = test_clock_time_sub(raw_data[i]->metric_end, raw_data[i]->metric_start); + throughput = raw_data[i]->ok_cnt / test_clock_time_to_double(&tmp_time); + BBQ_COLOR_PRINT(" %s-%d 吞吐 :%.0lf/s (%e/s)", name, bbq_idx, throughput, throughput); + bbq_idx++; + } + } + } + + if (ttype == TEST_THREAD_CONSUMER && cfg->ring.workload == TEST_WORKLOAD_COMPLEX) { + latency_ns = data->latency_ns * 1.0 / data->ok_cnt; + BBQ_COLOR_PRINT("数据延迟 :%.0lf 纳秒 (%e)", latency_ns, latency_ns); + } + + latency_ns = data->op_ok_latency_ns * 1.0 / data->ok_cnt; + BBQ_COLOR_PRINT("操作延迟 :%.0lf 纳秒 (%e)", latency_ns, latency_ns); + } else { + latency_ns = data->op_err_latency_ns * 1.0 / (data->run_times - data->ok_cnt); + if (ttype == TEST_THREAD_PRODUCER) { + BBQ_COLOR_PRINT("满队入队操作延迟 :%.0lf 纳秒 (%e)", latency_ns, latency_ns); + } else { + BBQ_COLOR_PRINT("空队出队操作延迟 :%.0lf 纳秒 (%e)", latency_ns, latency_ns); + } + } +} + +void bcm_report_generate(test_cfg* cfg, test_exit_data** exit_data, uint32_t thread_cnt) { + // test_report report; + + test_merge_s merge = {0}; + test_merge_all_data(exit_data, thread_cnt, &merge); + + BBQ_COLOR_PRINT("ring类型: %s", test_ring_type_enum2str(cfg->ring.ring_type)); + BBQ_COLOR_PRINT("简介: %s", cfg->base.introduce); + BBQ_COLOR_PRINT("配置: %s", cfg->base.name); + if (cfg->ring.producer_cnt > 0) { + bcm_report_printf(cfg, &merge.producer, exit_data, thread_cnt, TEST_THREAD_PRODUCER); + } + + if (cfg->ring.consumer_cnt > 0) { + bcm_report_printf(cfg, &merge.consumer, exit_data, thread_cnt, TEST_THREAD_CONSUMER); + } + + if (cfg->ring.producer_cnt > 0 && cfg->ring.consumer_cnt > 0) { + BBQ_COLOR_PRINT("生产消费个数验证: %s", merge.consumer.ok_cnt == merge.producer.ok_cnt ? "相等" : "不等!!!!!!!!!"); + } +} + +int main(int argc, char* argv[]) { + char* config; + char* ring_type; + uint32_t burst_cnt = 0; + + if (argc == 4) { + config = argv[1]; + ring_type = argv[2]; + burst_cnt = strtoul(argv[3], NULL, 0); + if (burst_cnt <= 0) { + burst_cnt = 1; + } + } else { + config = "/root/code/c/bbq-ly/perf/benchmark/config/compare/perf/perf_case2_simple_mpmc.ini"; + // ring_type = "rmind"; + // ring_type = "dpdk"; + ring_type = "bbq"; + burst_cnt = 1; + BBQ_ERR_LOG("use default config, ringt_type:%s burst:%u config:%s argc:%d", ring_type, burst_cnt, config, argc); + } + + char thread_name[128] = {0}; + snprintf(thread_name, sizeof(thread_name), "main:%x", pthread_self()); + prctl(PR_SET_NAME, thread_name); + + // 加载配置 + test_cfg cfg; + if (test_load_config(config, ring_type, burst_cnt, &cfg) != 0) { + BBQ_ERR_LOG("load config error"); + return -1; + } + + // 队列初始化 + int ret = -1; + test_queue_s q; + ret = bcm_queue_init(&cfg, &q); + if (ret != 0) { + BBQ_ERR_LOG("init failed :%d", ret); + return ret; + } + + // 创建线程 + pthread_t* threads = test_threads_create(&cfg, &q); + if (threads == NULL) { + BBQ_ERR_LOG("pthread_arr is NULL"); + return ret; + } + + test_check_run_time(&cfg); + pthread_t thread_run_time; + + // 等待所有线程完成,回收数据 + uint32_t thread_cnt = cfg.ring.producer_cnt + cfg.ring.consumer_cnt; + test_exit_data** exit_data = (test_exit_data**)test_malloc(TEST_MODULE_BCM, sizeof(test_exit_data**) * (thread_cnt)); + uint32_t i = 0; + + for (i = 0; i < thread_cnt; i++) { + pthread_join(threads[i], (void**)(&exit_data[i])); // 等待每个线程结束 + } + + // 生成benchmark报告 + bcm_report_generate(&cfg, exit_data, thread_cnt); + + // 回收、释放数据 + for (i = 0; i < thread_cnt; i++) { + test_exit_data_destory(exit_data[i]); + } + test_free(TEST_MODULE_BCM, exit_data); + test_threads_destory(threads); + test_queue_destory(&q); + bbq_memory_info(); + test_memory_info(); + + return 0; +}
\ No newline at end of file diff --git a/perf/benchmark/bcm_loadconfig.c b/perf/benchmark/bcm_loadconfig.c new file mode 100644 index 0000000..94566f6 --- /dev/null +++ b/perf/benchmark/bcm_loadconfig.c @@ -0,0 +1,77 @@ +/* + * @Author: liuyu + * @LastEditTime: 2024-06-06 23:54:46 + * @Email: [email protected] + * @Describe: TODO + */ +#include <string.h> +#include "bbq.h" +#include "iniparser.h" +#include "test_mix.h" + +int test_load_config(const char* config, const char* ring_type, uint32_t burst_cnt, test_cfg* cfg) { + // 加载配置 + BBQ_INFO_LOG("load config:%s", config); + dictionary* ini = iniparser_load(config); + if (ini == NULL) { + return -1; + } + + strncpy(cfg->base.name, config, sizeof(cfg->base.name) - 1); + cfg->base.name[sizeof(cfg->base.name) - 1] = '\0'; + + // 获取键值 + char* introduce = (char*)iniparser_getstring(ini, "base:introduce", "none"); + strncpy(cfg->base.introduce, introduce, sizeof(cfg->base.introduce) - 1); + cfg->base.introduce[sizeof(cfg->base.introduce) - 1] = '\0'; // 手工写上 \0 + cfg->base.cores_cnt = iniparser_getint(ini, "base:cores_cnt", 0); + char* workload = (char*)iniparser_getstring(ini, "ring:workload", "unknown"); + cfg->ring.workload = test_workload_str2enum(workload); + cfg->ring.entries_cnt = iniparser_getuint64(ini, "ring:entries_cnt", 0); + cfg->ring.producer_cnt = iniparser_getint(ini, "ring:producer_cnt", 0); + cfg->ring.consumer_cnt = iniparser_getint(ini, "ring:consumer_cnt", 0); + cfg->ring.block_count = iniparser_getint(ini, "ring:block_count", 0); + cfg->run.run_ok_times = iniparser_getint(ini, "run:run_ok_times", 0); + cfg->run.run_time = iniparser_getuint64(ini, "run:run_time", 0); + + // 设置ring_type + cfg->ring.ring_type = test_ring_type_str2enum(ring_type); + if (cfg->ring.ring_type >= TEST_RING_TYPE_MAX) { + BBQ_ERR_LOG("unknown ring type:%d", cfg->ring.ring_type); + return -1; + } + + cfg->run.running = true; + cfg->run.thread_start = ATOMIC_VAR_INIT(0); + cfg->run.producer_exit = ATOMIC_VAR_INIT(0); + cfg->ring.burst_cnt = burst_cnt; + + if (cfg->ring.ring_type == TEST_RING_TYPE_RMIND) { + // rmind仅支持1个消费者,仅支持burst方式 + if (cfg->ring.consumer_cnt > 1) { + BBQ_ERR_LOG("ring type:%s only support single consumer", TEST_RING_TYPE_RMIND_STR); + return -1; + } + + if (cfg->ring.burst_cnt <= 1) { + BBQ_ERR_LOG("ring type:%s only support burst_cnt > 1 !", TEST_RING_TYPE_RMIND_STR); + return -1; + } + } + + BBQ_INFO_LOG("introduce:%s", cfg->base.introduce); + BBQ_INFO_LOG("cores_cnt:%u", cfg->base.cores_cnt); + BBQ_INFO_LOG("workload:%s(%u)", workload, cfg->ring.workload); + BBQ_INFO_LOG("entries_cnt:%u", cfg->ring.entries_cnt); + BBQ_INFO_LOG("producer_cnt:%u", cfg->ring.producer_cnt); + BBQ_INFO_LOG("consumer_cnt:%u", cfg->ring.consumer_cnt); + BBQ_INFO_LOG("block_count:%u", cfg->ring.block_count); + BBQ_INFO_LOG("run_ok_times:%lu", cfg->run.run_ok_times); + BBQ_INFO_LOG("run_time:%lu", cfg->run.run_time); + BBQ_INFO_LOG("ring_type:%s(%u)", ring_type, cfg->ring.ring_type); + BBQ_INFO_LOG("burst_cnt:%u", burst_cnt); + + // 释放dictionary对象 + iniparser_freedict(ini); + return 0; +}
\ No newline at end of file diff --git a/perf/benchmark/bcm_queue.c b/perf/benchmark/bcm_queue.c new file mode 100644 index 0000000..98b0d29 --- /dev/null +++ b/perf/benchmark/bcm_queue.c @@ -0,0 +1,179 @@ +/* + * @Author: liuyu + * @LastEditTime: 2024-06-06 17:36:40 + * @Email: [email protected] + * @Describe: TODO + */ +#include "bcm_queue.h" +#include "ringbuf.h" + +static __rte_always_inline unsigned int +bcm_ring_enqueue_burst(struct rte_ring *r, void *obj_table, size_t n, unsigned int flags) { + return rte_ring_enqueue_burst(r, (void *const *)obj_table, n, NULL); +} + +static __rte_always_inline unsigned int +bcm_ring_dequeue_burst(struct rte_ring *r, void *obj_table, unsigned int n) { + return rte_ring_dequeue_burst(r, (void **)obj_table, n, NULL); +} + +int test_queue_init_dpdk(test_cfg *cfg, test_queue_s *q) { + char *argv[1] = {"bcm_dpdk"}; + + // eal环境初始化,如接口、内存等 + if (rte_eal_init(1, argv) < 0) { + rte_exit(EXIT_FAILURE, "Error with EAL init\n"); + } + + q->ring_type = TEST_RING_TYPE_DPDK; + unsigned int flags = 0; + if (cfg->ring.producer_cnt <= 1) { + flags |= RING_F_SP_ENQ; + } else { + flags |= RING_F_MP_RTS_ENQ; + } + + if (cfg->ring.consumer_cnt <= 1) { + flags |= RING_F_SC_DEQ; + } else { + flags |= RING_F_MC_RTS_DEQ; + } + + q->ring = (void *)rte_ring_create("dpdk_ring", cfg->ring.entries_cnt, rte_socket_id(), RING_F_MP_RTS_ENQ | RING_F_MC_RTS_DEQ); + if (q->ring == NULL) { + return BBQ_NULL_PTR; + } + + q->ring_free_f = (test_ring_free_f)rte_ring_free; + q->enqueue_f = (test_ring_enqueue_f)rte_ring_enqueue; + q->dequeue_f = (test_ring_dequeue_f)rte_ring_dequeue; + q->enqueue_burst_f = (test_enqueue_burst_f)bcm_ring_enqueue_burst; + q->dequeue_burst_f = (test_dequeue_burst_f)bcm_ring_dequeue_burst; + + return BBQ_OK; +} + +unsigned char *rmind_buf; +uint16_t worker_cnt; +ringbuf_worker_t **rmind_workers; + +void test_queue_free_rmind(void *ring) { + for (uint16_t i = 0; i < worker_cnt; i++) { + ringbuf_unregister((ringbuf_t *)ring, rmind_workers[i]); + } + + test_free(TEST_MODULE_RMIND, rmind_workers); + test_free(TEST_MODULE_RMIND, rmind_buf); + test_free(TEST_MODULE_RMIND, ring); +} + +uint32_t test_enqueue_burst_rmind(void *ring, void *obj_table, size_t n, unsigned int flags, uint16_t thread_idx) { + uint32_t cnt = 0; + int ret = 0; + size_t off = 0; + void *obj = NULL; + ringbuf_worker_t *w = (ringbuf_worker_t *)rmind_workers[thread_idx]; + size_t len = sizeof(uintptr_t); + + for (cnt = 0; cnt < n; cnt++) { + obj = ((void **)obj_table)[cnt]; + uintptr_t uptr = (uintptr_t)obj; + + if ((ret = ringbuf_acquire(ring, w, len)) != -1) { + off = (size_t)ret; + memcpy(&rmind_buf[off], &uptr, len); + ringbuf_produce(ring, w); + } else { + break; + } + } + + return cnt; +} + +uint32_t test_dequeue_burst_rmind(void *ring, void *obj_table, uint32_t n) { + size_t len = 0; + size_t off = 0; + size_t per_size = sizeof(void *); + void **table = (void **)obj_table; + + if ((len = ringbuf_consume(ring, &off)) != 0) { + size_t rem = len; + size_t i = 0; + + while (rem) { + uintptr_t *data = (uintptr_t *)(&rmind_buf[off]); + table[i] = (void *)(*data); + i++; + off += per_size; + rem -= sizeof(void *); + } + ringbuf_release(ring, len); + return i; + } + + return 0; +} + +int test_queue_init_rmind(test_cfg *cfg, test_queue_s *q) { + static size_t ringbuf_obj_size; + worker_cnt = cfg->ring.producer_cnt + cfg->ring.consumer_cnt; + + ringbuf_get_sizes(worker_cnt, &ringbuf_obj_size, NULL); + ringbuf_t *r = test_malloc(TEST_MODULE_RMIND, ringbuf_obj_size); + if (r == NULL) { + exit(-1); + } + + size_t buf_size = sizeof(void *) * cfg->ring.entries_cnt; + rmind_buf = test_malloc(TEST_MODULE_RMIND, buf_size); + if (rmind_buf == NULL) { + exit(-1); + } + ringbuf_setup(r, worker_cnt, buf_size); + + rmind_workers = test_malloc(TEST_MODULE_RMIND, sizeof(*rmind_workers) * worker_cnt); + if (rmind_workers == NULL) { + exit(-1); + } + for (uint32_t i = 0; i < worker_cnt; i++) { + rmind_workers[i] = ringbuf_register(r, i); + if (rmind_workers[i] == NULL) { + exit(-1); + } + } + + q->ring = r; + q->ring_free_f = (test_ring_free_f)test_queue_free_rmind; + q->enqueue_f = NULL; + q->dequeue_f = NULL; + q->enqueue_burst_f = (test_enqueue_burst_f)test_enqueue_burst_rmind; + q->dequeue_burst_f = (test_dequeue_burst_f)test_dequeue_burst_rmind; + + return 0; +} + +int bcm_queue_init(test_cfg *cfg, test_queue_s *q) { + if (cfg == NULL || q == NULL) { + return BBQ_NULL_PTR; + } + + memset(q, 0, sizeof(*q)); + int ret = -1; + q->ring_type = cfg->ring.ring_type; + switch (q->ring_type) { + case TEST_RING_TYPE_DPDK: + ret = test_queue_init_dpdk(cfg, q); + break; + case TEST_RING_TYPE_BBQ: + ret = test_queue_init_bbq(cfg, q); + break; + case TEST_RING_TYPE_RMIND: + ret = test_queue_init_rmind(cfg, q); + break; + default: + return BBQ_UNKNOWN_TYPE; + } + + return ret; +} diff --git a/perf/benchmark/bcm_queue.h b/perf/benchmark/bcm_queue.h new file mode 100644 index 0000000..edb360c --- /dev/null +++ b/perf/benchmark/bcm_queue.h @@ -0,0 +1,4 @@ +#include "rte_ring.h" +#include "test_queue.h" + +extern int bcm_queue_init(test_cfg *cfg, test_queue_s *q);
\ No newline at end of file diff --git a/perf/benchmark/benchmark.sh b/perf/benchmark/benchmark.sh new file mode 100755 index 0000000..4151dfb --- /dev/null +++ b/perf/benchmark/benchmark.sh @@ -0,0 +1,86 @@ +#!/bin/bash +### +# @Author: liuyu +# @LastEditTime: 2024-06-06 22:04:41 +# @Email: [email protected] +# @Describe: TODO +### + +ring_type_arr=("bbq" "dpdk" "rmind") + +# 检查参数数量 +if [ "$#" -ne 4 ]; then + echo "Usage: $0 <path_to_benchmark> <path_to_config_directory> <ring_type> <burst_cnt>" + echo " <ring_type> is one of: ${my_array[*]} or input all" # 列出可选的 ring_type + echo " <burst_cnt> : The number of obj enqueue or dequeue at a time" # 列出可选的 ring_type + exit 1 +fi + +# 获取参数值 +BENCHMARK_PATH=$1 +CONFIG_DIR=$2 +RING_TYPE=$3 +BURST_CNT=$4 + +if [ "$BURST_CNT" -le 0 ]; then + echo "burst_cnt need >=1" + exit 1 +fi + +function exec_benchmark_ring_type() { + local ini="$1" + local ring="$2" + + # 如果以perf开头的配置文件,还要执行perf统计 + if [[ $(basename "$ini") == perf* ]]; then + echo "skip perf*" + # echo perf stat -a -e L1-dcache-loads,L1-dcache-load-misses,cache-references,cache-misses "$BENCHMARK_PATH" "$ini" "$ring" "$BURST_CNT" + # perf stat -a -e L1-dcache-loads,L1-dcache-load-misses,cache-references,cache-misses "$BENCHMARK_PATH" "$ini" "$ring" "$BURST_CNT" + else + "$BENCHMARK_PATH" "$ini" "$ring" "$BURST_CNT" + fi +} + +function exec_benchmark() { + # 提取配置文件名(不带路径) + INI_FILE=$1 + + # 执行benchmark命令并传递配置文件作为参数 + echo "Executing benchmark with $INI_FILE" + + # 使用所有ring_type + if [ "$RING_TYPE" == "all" ]; then + for ring_type_tmp in "${ring_type_arr[@]}"; do + exec_benchmark_ring_type "$INI_FILE" "$ring_type_tmp" + echo "--------------------------------------------------------------" + done + else + exec_benchmark_ring_type "$INI_FILE" "$RING_TYPE" + fi +} + +# 检查benchmark文件是否存在且可执行 +if [ ! -x "$BENCHMARK_PATH" ]; then + echo "Error: Benchmark executable '$BENCHMARK_PATH' does not exist or is not executable." + exit 1 +fi + +# 检查配置文件目录是否存在 +if [ ! -e "$CONFIG_DIR" ]; then + echo "Error: Config directory '$CONFIG_DIR' does not exist." + exit 1 +fi + +if [[ -f "$CONFIG_DIR" ]]; then + # 如果是文件,直接执行 + exec_benchmark $CONFIG_DIR +else + # 使用 find 命令递归地搜索所有的 .ini 文件,并按文件名排序 + find "$CONFIG_DIR" -type f -name "*.ini" -print0 | sort -z | while IFS= read -r -d '' INI_FILE; do + if [ -f "$INI_FILE" ]; then + exec_benchmark $INI_FILE + fi + done +fi + +echo "done......." diff --git a/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block16.ini b/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block16.ini new file mode 100644 index 0000000..806f902 --- /dev/null +++ b/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block16.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,多生产者、多消费者 block=16" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 16 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block2.ini b/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block2.ini new file mode 100644 index 0000000..45055f8 --- /dev/null +++ b/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block2.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,多生产者、多消费者 block=2" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 2 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block32.ini b/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block32.ini new file mode 100644 index 0000000..3bb8d7e --- /dev/null +++ b/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block32.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,多生产者、多消费者 block=32" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 32 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block4.ini b/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block4.ini new file mode 100644 index 0000000..6e08ffe --- /dev/null +++ b/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block4.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,多生产者、多消费者 block=4" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 4 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block8.ini b/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block8.ini new file mode 100644 index 0000000..e3ab733 --- /dev/null +++ b/perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block8.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,多生产者、多消费者 block=8" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 8 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_mpsc/case_b1_simple_bbq_mpsc_block2.ini b/perf/benchmark/config/bbq_block_mpsc/case_b1_simple_bbq_mpsc_block2.ini new file mode 100644 index 0000000..0a9b960 --- /dev/null +++ b/perf/benchmark/config/bbq_block_mpsc/case_b1_simple_bbq_mpsc_block2.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,多生产者、单消费者 block=2" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 2 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_mpsc/case_b2_simple_bbq_mpsc_block4.ini b/perf/benchmark/config/bbq_block_mpsc/case_b2_simple_bbq_mpsc_block4.ini new file mode 100644 index 0000000..5e56b19 --- /dev/null +++ b/perf/benchmark/config/bbq_block_mpsc/case_b2_simple_bbq_mpsc_block4.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,多生产者、单消费者 block=4" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 4 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_mpsc/case_b3_simple_bbq_mpsc_block8.ini b/perf/benchmark/config/bbq_block_mpsc/case_b3_simple_bbq_mpsc_block8.ini new file mode 100644 index 0000000..873d94a --- /dev/null +++ b/perf/benchmark/config/bbq_block_mpsc/case_b3_simple_bbq_mpsc_block8.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,多生产者、单消费者 block=8" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 8 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_mpsc/case_b4_simple_bbq_mpsc_block16.ini b/perf/benchmark/config/bbq_block_mpsc/case_b4_simple_bbq_mpsc_block16.ini new file mode 100644 index 0000000..d121a3b --- /dev/null +++ b/perf/benchmark/config/bbq_block_mpsc/case_b4_simple_bbq_mpsc_block16.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,多生产者、单消费者 block=16" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 16 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_mpsc/case_b5_simple_bbq_mpsc_block32.ini b/perf/benchmark/config/bbq_block_mpsc/case_b5_simple_bbq_mpsc_block32.ini new file mode 100644 index 0000000..c5a26c2 --- /dev/null +++ b/perf/benchmark/config/bbq_block_mpsc/case_b5_simple_bbq_mpsc_block32.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,多生产者、单消费者 block=32" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 32 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block16.ini b/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block16.ini new file mode 100644 index 0000000..cf480c3 --- /dev/null +++ b/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block16.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,单生产者、多消费者 block=16" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 16 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block2.ini b/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block2.ini new file mode 100644 index 0000000..1d54c80 --- /dev/null +++ b/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block2.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,单生产者、多消费者 block=2" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 2 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block32.ini b/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block32.ini new file mode 100644 index 0000000..af5e429 --- /dev/null +++ b/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block32.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,单生产者、多消费者 block=32" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 32 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block4.ini b/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block4.ini new file mode 100644 index 0000000..baaafd2 --- /dev/null +++ b/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block4.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,单生产者、多消费者 block=4" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 4 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block8.ini b/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block8.ini new file mode 100644 index 0000000..e31e868 --- /dev/null +++ b/perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block8.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,单生产者、多消费者 block=8" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 8 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block16.ini b/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block16.ini new file mode 100644 index 0000000..b3c094c --- /dev/null +++ b/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block16.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,单生产者、单消费者 block=16" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 16 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block2.ini b/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block2.ini new file mode 100644 index 0000000..57910e8 --- /dev/null +++ b/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block2.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,单生产者、单消费者 block=2" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 2 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block32.ini b/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block32.ini new file mode 100644 index 0000000..40bf494 --- /dev/null +++ b/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block32.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,单生产者、单消费者 block=32" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 32 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block4.ini b/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block4.ini new file mode 100644 index 0000000..69415e4 --- /dev/null +++ b/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block4.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,单生产者、单消费者 block=4" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 4 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block8.ini b/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block8.ini new file mode 100644 index 0000000..2627e48 --- /dev/null +++ b/perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block8.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,单生产者、单消费者 block=8" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 8 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/bbq_debug/debug.ini b/perf/benchmark/config/bbq_debug/debug.ini new file mode 100644 index 0000000..44c3216 --- /dev/null +++ b/perf/benchmark/config/bbq_debug/debug.ini @@ -0,0 +1,15 @@ +[base] + introduce = "bbq简单负载下,多生产者、单消费者 block=0" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 8 ;ring初始化时分配entry的个数 + block_count = 2 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 10 ; 整体运行的秒数,大于0生效 + enqueue_cnt = 0 ;每个线程入队次数,大于0生效 + dequeue_cnt = 0 ;每个线程出队次数,大于0生效
\ No newline at end of file diff --git a/perf/benchmark/config/compare/general/case1_simple_spsc.ini b/perf/benchmark/config/compare/general/case1_simple_spsc.ini new file mode 100644 index 0000000..19bb059 --- /dev/null +++ b/perf/benchmark/config/compare/general/case1_simple_spsc.ini @@ -0,0 +1,14 @@ +[base] + introduce = "case1 简单负载下,单生产者、单消费者 吞吐测试" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 0 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 5 ; 整体运行的秒数,大于0生效 + run_ok_times = 0 ;成功入队/出队次数,大于0生效。
\ No newline at end of file diff --git a/perf/benchmark/config/compare/general/case2_simple_spmc.ini b/perf/benchmark/config/compare/general/case2_simple_spmc.ini new file mode 100644 index 0000000..ca05962 --- /dev/null +++ b/perf/benchmark/config/compare/general/case2_simple_spmc.ini @@ -0,0 +1,14 @@ +[base] + introduce = "case2 简单负载下,单生产者、多消费者 吞吐测试" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 0 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 5 ; 整体运行的秒数,大于0生效 + run_ok_times = 0 ;成功入队/出队次数,大于0生效。 diff --git a/perf/benchmark/config/compare/general/case3_simple_mpsc.ini b/perf/benchmark/config/compare/general/case3_simple_mpsc.ini new file mode 100644 index 0000000..4f3d4fa --- /dev/null +++ b/perf/benchmark/config/compare/general/case3_simple_mpsc.ini @@ -0,0 +1,14 @@ +[base] + introduce = "case3 简单负载下,多生产者、单消费者 吞吐测试" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 0 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 5 ; 整体运行的秒数,大于0生效 + run_ok_times = 0 ;成功入队/出队次数,大于0生效。
\ No newline at end of file diff --git a/perf/benchmark/config/compare/general/case4_complex_spmc.ini b/perf/benchmark/config/compare/general/case4_complex_spmc.ini new file mode 100644 index 0000000..70764bc --- /dev/null +++ b/perf/benchmark/config/compare/general/case4_complex_spmc.ini @@ -0,0 +1,14 @@ +[base] + introduce = "case4 复杂负载下,单生产者、多消费者 吞吐测试" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "complex" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 0 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 5 ; 整体运行的秒数,大于0生效 + run_ok_times = 0 ;成功入队/出队次数,大于0生效。
\ No newline at end of file diff --git a/perf/benchmark/config/compare/general/case5_complex_mpsc.ini b/perf/benchmark/config/compare/general/case5_complex_mpsc.ini new file mode 100644 index 0000000..05a1fb0 --- /dev/null +++ b/perf/benchmark/config/compare/general/case5_complex_mpsc.ini @@ -0,0 +1,14 @@ +[base] + introduce = "case5 复杂负载下,多生产者、单消费者 吞吐测试" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "complex" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 0 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 5 ; 整体运行的秒数,大于0生效 + run_ok_times = 0 ;成功入队/出队次数,大于0生效。
\ No newline at end of file diff --git a/perf/benchmark/config/compare/general/case6_simple_mp0c.ini b/perf/benchmark/config/compare/general/case6_simple_mp0c.ini new file mode 100644 index 0000000..50f91b5 --- /dev/null +++ b/perf/benchmark/config/compare/general/case6_simple_mp0c.ini @@ -0,0 +1,14 @@ +[base] + introduce = "case6 简单负载下,多生产者、无消费者,测试满队入队操作时延" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 0 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 0 ;消费者个数 + +[run] + run_time = 5 ; 整体运行的秒数,大于0生效 + run_ok_times = 0 ;成功入队/出队次数,大于0生效。
\ No newline at end of file diff --git a/perf/benchmark/config/compare/general/case7_simple_0pmc.ini b/perf/benchmark/config/compare/general/case7_simple_0pmc.ini new file mode 100644 index 0000000..5652c21 --- /dev/null +++ b/perf/benchmark/config/compare/general/case7_simple_0pmc.ini @@ -0,0 +1,14 @@ +[base] + introduce = "case7 简单负载下,无生产者、多消费者,测试空队出队操作时延" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 0 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 0 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 5 ; 整体运行的秒数,大于0生效 + run_ok_times = 0 ;成功入队/出队次数,大于0生效。
\ No newline at end of file diff --git a/perf/benchmark/config/compare/general/case8_simple_mpmc.ini b/perf/benchmark/config/compare/general/case8_simple_mpmc.ini new file mode 100644 index 0000000..a365cd0 --- /dev/null +++ b/perf/benchmark/config/compare/general/case8_simple_mpmc.ini @@ -0,0 +1,14 @@ +[base] + introduce = "case5 复杂负载下,多生产者、多消费者 吞吐测试" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 0 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 5 ; 整体运行的秒数,大于0生效 + run_ok_times = 0 ;成功入队/出队次数,大于0生效。
\ No newline at end of file diff --git a/perf/benchmark/config/compare/general/case9_simple_mpmc_overcore.ini b/perf/benchmark/config/compare/general/case9_simple_mpmc_overcore.ini new file mode 100644 index 0000000..4de4b36 --- /dev/null +++ b/perf/benchmark/config/compare/general/case9_simple_mpmc_overcore.ini @@ -0,0 +1,14 @@ +[base] + introduce = "case8 简单负载下,多生产者、多消费者 线程核心超过已有核心时的吞吐测试" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 0 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 16 ;生产者个数 + consumer_cnt = 16 ;消费者个数 + +[run] + run_time = 5 ; 整体运行的秒数,大于0生效 + run_ok_times = 0 ;成功入队/出队次数,大于0生效。
\ No newline at end of file diff --git a/perf/benchmark/config/compare/perf/perf_case1_simple_spsc.ini b/perf/benchmark/config/compare/perf/perf_case1_simple_spsc.ini new file mode 100644 index 0000000..f59ec9b --- /dev/null +++ b/perf/benchmark/config/compare/perf/perf_case1_simple_spsc.ini @@ -0,0 +1,14 @@ +[base] + introduce = "case9 简单负载下,单生产者、单消费者" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 0 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 0 ; 整体运行的秒数,大于0生效 + run_ok_times = 0 ;成功入队/出队次数,大于0生效。
\ No newline at end of file diff --git a/perf/benchmark/config/compare/perf/perf_case2_simple_mpmc.ini b/perf/benchmark/config/compare/perf/perf_case2_simple_mpmc.ini new file mode 100644 index 0000000..8bc370f --- /dev/null +++ b/perf/benchmark/config/compare/perf/perf_case2_simple_mpmc.ini @@ -0,0 +1,14 @@ +[base] + introduce = "case9 简单负载下,多生产者、多消费者" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 0 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 0 ; 整体运行的秒数,大于0生效 + run_ok_times = 0 ;成功入队/出队次数,大于0生效。
\ No newline at end of file diff --git a/perf/benchmark/config/compare/perf/perf_case3_simple_spmp.ini b/perf/benchmark/config/compare/perf/perf_case3_simple_spmp.ini new file mode 100644 index 0000000..3392b83 --- /dev/null +++ b/perf/benchmark/config/compare/perf/perf_case3_simple_spmp.ini @@ -0,0 +1,14 @@ +[base] + introduce = "case9 简单负载下,单生产者、多消费者" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 0 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 1 ;生产者个数 + consumer_cnt = 4 ;消费者个数 + +[run] + run_time = 0 ; 整体运行的秒数,大于0生效 + run_ok_times = 0 ;成功入队/出队次数,大于0生效。
\ No newline at end of file diff --git a/perf/benchmark/config/compare/perf/perf_case4_simple_mpsc.ini b/perf/benchmark/config/compare/perf/perf_case4_simple_mpsc.ini new file mode 100644 index 0000000..945ed87 --- /dev/null +++ b/perf/benchmark/config/compare/perf/perf_case4_simple_mpsc.ini @@ -0,0 +1,14 @@ +[base] + introduce = "case9 简单负载下,多生产者、单消费者" ;测试配置说明 + cores_cnt = 8 ;测试用核心个数 + +[ring] + workload = "simple" ;负载模式 simple/complex + entries_cnt = 4096 ;ring初始化时分配entry的个数 + block_count = 0 ;bbq配置,等于0则表示根据entries_cnt自动计算 + producer_cnt = 4 ;生产者个数 + consumer_cnt = 1 ;消费者个数 + +[run] + run_time = 0 ; 整体运行的秒数,大于0生效 + run_ok_times = 0 ;成功入队/出队次数,大于0生效。
\ No newline at end of file diff --git a/perf/thirdparty/CMakeLists.txt b/perf/thirdparty/CMakeLists.txt new file mode 100644 index 0000000..5ad8bd4 --- /dev/null +++ b/perf/thirdparty/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.0) +project(BBQ_THIRDPARTY) + +add_subdirectory(iniparser) +add_subdirectory(rmind_ringbuf) diff --git a/perf/thirdparty/iniparser/CMakeLists.txt b/perf/thirdparty/iniparser/CMakeLists.txt new file mode 100644 index 0000000..f24e1e8 --- /dev/null +++ b/perf/thirdparty/iniparser/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.0) +project(INIPARSER) + +file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.c) #搜索当前cmake所在目录下的c文件 +set(LIBRARY_OUTPUT_PATH ${LIB_PATH}) #设置库生成目录 + +add_library(iniparser STATIC ${SRC_LIST}) #生成静态库 +# add_library(${BBQ_LIB} SHARED ${SRC_LIST}) #生成动态库
\ No newline at end of file diff --git a/perf/thirdparty/iniparser/dictionary.c b/perf/thirdparty/iniparser/dictionary.c new file mode 100644 index 0000000..85dfdc0 --- /dev/null +++ b/perf/thirdparty/iniparser/dictionary.c @@ -0,0 +1,383 @@ +/*-------------------------------------------------------------------------*/ +/** + @file dictionary.c + @author N. Devillard + @brief Implements a dictionary for string variables. + + This module implements a simple dictionary object, i.e. a list + of string/string associations. This object is useful to store e.g. + informations retrieved from a configuration file (ini files). +*/ +/*--------------------------------------------------------------------------*/ + +/*--------------------------------------------------------------------------- + Includes + ---------------------------------------------------------------------------*/ +#include "dictionary.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/** Minimal allocated number of entries in a dictionary */ +#define DICTMINSZ 128 + +/*--------------------------------------------------------------------------- + Private functions + ---------------------------------------------------------------------------*/ + +/*-------------------------------------------------------------------------*/ +/** + @brief Duplicate a string + @param s String to duplicate + @return Pointer to a newly allocated string, to be freed with free() + + This is a replacement for strdup(). This implementation is provided + for systems that do not have it. + */ +/*--------------------------------------------------------------------------*/ +static char * xstrdup(const char * s) +{ + char * t ; + size_t len ; + if (!s) + return NULL ; + + len = strlen(s) + 1 ; + t = (char*) malloc(len) ; + if (t) { + memcpy(t, s, len) ; + } + return t ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Double the size of the dictionary + @param d Dictionary to grow + @return This function returns non-zero in case of failure + */ +/*--------------------------------------------------------------------------*/ +static int dictionary_grow(dictionary * d) +{ + char ** new_val ; + char ** new_key ; + unsigned * new_hash ; + + new_val = (char**) calloc(d->size * 2, sizeof *d->val); + new_key = (char**) calloc(d->size * 2, sizeof *d->key); + new_hash = (unsigned*) calloc(d->size * 2, sizeof *d->hash); + if (!new_val || !new_key || !new_hash) { + /* An allocation failed, leave the dictionary unchanged */ + if (new_val) + free(new_val); + if (new_key) + free(new_key); + if (new_hash) + free(new_hash); + return -1 ; + } + /* Initialize the newly allocated space */ + memcpy(new_val, d->val, d->size * sizeof(char *)); + memcpy(new_key, d->key, d->size * sizeof(char *)); + memcpy(new_hash, d->hash, d->size * sizeof(unsigned)); + /* Delete previous data */ + free(d->val); + free(d->key); + free(d->hash); + /* Actually update the dictionary */ + d->size *= 2 ; + d->val = new_val; + d->key = new_key; + d->hash = new_hash; + return 0 ; +} + +/*--------------------------------------------------------------------------- + Function codes + ---------------------------------------------------------------------------*/ +/*-------------------------------------------------------------------------*/ +/** + @brief Compute the hash key for a string. + @param key Character string to use for key. + @return 1 unsigned int on at least 32 bits. + + This hash function has been taken from an Article in Dr Dobbs Journal. + This is normally a collision-free function, distributing keys evenly. + The key is stored anyway in the struct so that collision can be avoided + by comparing the key itself in last resort. + */ +/*--------------------------------------------------------------------------*/ +unsigned dictionary_hash(const char * key) +{ + size_t len ; + unsigned hash ; + size_t i ; + + if (!key) + return 0 ; + + len = strlen(key); + for (hash=0, i=0 ; i<len ; i++) { + hash += (unsigned)key[i] ; + hash += (hash<<10); + hash ^= (hash>>6) ; + } + hash += (hash <<3); + hash ^= (hash >>11); + hash += (hash <<15); + return hash ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Create a new dictionary object. + @param size Optional initial size of the dictionary. + @return 1 newly allocated dictionary object. + + This function allocates a new dictionary object of given size and returns + it. If you do not know in advance (roughly) the number of entries in the + dictionary, give size=0. + */ +/*-------------------------------------------------------------------------*/ +dictionary * dictionary_new(size_t size) +{ + dictionary * d ; + + /* If no size was specified, allocate space for DICTMINSZ */ + if (size<DICTMINSZ) size=DICTMINSZ ; + + d = (dictionary*) calloc(1, sizeof *d) ; + + if (d) { + d->size = size ; + d->val = (char**) calloc(size, sizeof *d->val); + d->key = (char**) calloc(size, sizeof *d->key); + d->hash = (unsigned*) calloc(size, sizeof *d->hash); + if (!d->size || !d->val || !d->hash) { + free((void *) d->size); + free((void *) d->val); + free((void *) d->hash); + free(d); + d = NULL; + } + } + return d ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a dictionary object + @param d dictionary object to deallocate. + @return void + + Deallocate a dictionary object and all memory associated to it. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_del(dictionary * d) +{ + size_t i ; + + if (d==NULL) return ; + for (i=0 ; i<d->size ; i++) { + if (d->key[i]!=NULL) + free(d->key[i]); + if (d->val[i]!=NULL) + free(d->val[i]); + } + free(d->val); + free(d->key); + free(d->hash); + free(d); + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get a value from a dictionary. + @param d dictionary object to search. + @param key Key to look for in the dictionary. + @param def Default value to return if key not found. + @return 1 pointer to internally allocated character string. + + This function locates a key in a dictionary and returns a pointer to its + value, or the passed 'def' pointer if no such key can be found in + dictionary. The returned character pointer points to data internal to the + dictionary object, you should not try to free it or modify it. + */ +/*--------------------------------------------------------------------------*/ +const char * dictionary_get(const dictionary * d, const char * key, const char * def) +{ + unsigned hash ; + size_t i ; + + if(d == NULL || key == NULL) + return def ; + + hash = dictionary_hash(key); + for (i=0 ; i<d->size ; i++) { + if (d->key[i]==NULL) + continue ; + /* Compare hash */ + if (hash==d->hash[i]) { + /* Compare string, to avoid hash collisions */ + if (!strcmp(key, d->key[i])) { + return d->val[i] ; + } + } + } + return def ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Set a value in a dictionary. + @param d dictionary object to modify. + @param key Key to modify or add. + @param val Value to add. + @return int 0 if Ok, anything else otherwise + + If the given key is found in the dictionary, the associated value is + replaced by the provided one. If the key cannot be found in the + dictionary, it is added to it. + + It is Ok to provide a NULL value for val, but NULL values for the dictionary + or the key are considered as errors: the function will return immediately + in such a case. + + Notice that if you dictionary_set a variable to NULL, a call to + dictionary_get will return a NULL value: the variable will be found, and + its value (NULL) is returned. In other words, setting the variable + content to NULL is equivalent to deleting the variable from the + dictionary. It is not possible (in this implementation) to have a key in + the dictionary without value. + + This function returns non-zero in case of failure. + */ +/*--------------------------------------------------------------------------*/ +int dictionary_set(dictionary * d, const char * key, const char * val) +{ + size_t i ; + unsigned hash ; + + if (d==NULL || key==NULL) return -1 ; + + /* Compute hash for this key */ + hash = dictionary_hash(key) ; + /* Find if value is already in dictionary */ + if (d->n>0) { + for (i=0 ; i<d->size ; i++) { + if (d->key[i]==NULL) + continue ; + if (hash==d->hash[i]) { /* Same hash value */ + if (!strcmp(key, d->key[i])) { /* Same key */ + /* Found a value: modify and return */ + if (d->val[i]!=NULL) + free(d->val[i]); + d->val[i] = (val ? xstrdup(val) : NULL); + /* Value has been modified: return */ + return 0 ; + } + } + } + } + /* Add a new value */ + /* See if dictionary needs to grow */ + if (d->n==d->size) { + /* Reached maximum size: reallocate dictionary */ + if (dictionary_grow(d) != 0) + return -1; + } + + /* Insert key in the first empty slot. Start at d->n and wrap at + d->size. Because d->n < d->size this will necessarily + terminate. */ + for (i=d->n ; d->key[i] ; ) { + if(++i == d->size) i = 0; + } + /* Copy key */ + d->key[i] = xstrdup(key); + d->val[i] = (val ? xstrdup(val) : NULL) ; + d->hash[i] = hash; + d->n ++ ; + return 0 ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a key in a dictionary + @param d dictionary object to modify. + @param key Key to remove. + @return void + + This function deletes a key in a dictionary. Nothing is done if the + key cannot be found. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_unset(dictionary * d, const char * key) +{ + unsigned hash ; + size_t i ; + + if (key == NULL || d == NULL) { + return; + } + + hash = dictionary_hash(key); + for (i=0 ; i<d->size ; i++) { + if (d->key[i]==NULL) + continue ; + /* Compare hash */ + if (hash==d->hash[i]) { + /* Compare string, to avoid hash collisions */ + if (!strcmp(key, d->key[i])) { + /* Found key */ + break ; + } + } + } + if (i>=d->size) + /* Key not found */ + return ; + + free(d->key[i]); + d->key[i] = NULL ; + if (d->val[i]!=NULL) { + free(d->val[i]); + d->val[i] = NULL ; + } + d->hash[i] = 0 ; + d->n -- ; + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump + @param f Opened file pointer. + @return void + + Dumps a dictionary onto an opened file pointer. Key pairs are printed out + as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as + output file pointers. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_dump(const dictionary * d, FILE * out) +{ + size_t i ; + + if (d==NULL || out==NULL) return ; + if (d->n<1) { + fprintf(out, "empty dictionary\n"); + return ; + } + for (i=0 ; i<d->size ; i++) { + if (d->key[i]) { + fprintf(out, "%20s\t[%s]\n", + d->key[i], + d->val[i] ? d->val[i] : "UNDEF"); + } + } + return ; +} diff --git a/perf/thirdparty/iniparser/dictionary.h b/perf/thirdparty/iniparser/dictionary.h new file mode 100644 index 0000000..f459cfe --- /dev/null +++ b/perf/thirdparty/iniparser/dictionary.h @@ -0,0 +1,170 @@ + +/*-------------------------------------------------------------------------*/ +/** + @file dictionary.h + @author N. Devillard + @brief Implements a dictionary for string variables. + + This module implements a simple dictionary object, i.e. a list + of string/string associations. This object is useful to store e.g. + informations retrieved from a configuration file (ini files). +*/ +/*--------------------------------------------------------------------------*/ + +#ifndef _DICTIONARY_H_ +#define _DICTIONARY_H_ + +/*--------------------------------------------------------------------------- + Includes + ---------------------------------------------------------------------------*/ + +#include <stdio.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/*--------------------------------------------------------------------------- + New types + ---------------------------------------------------------------------------*/ + + +/*-------------------------------------------------------------------------*/ +/** + @brief Dictionary object + + This object contains a list of string/string associations. Each + association is identified by a unique string key. Looking up values + in the dictionary is speeded up by the use of a (hopefully collision-free) + hash function. + */ +/*-------------------------------------------------------------------------*/ +typedef struct _dictionary_ { + unsigned n ; /** Number of entries in dictionary */ + size_t size ; /** Storage size */ + char ** val ; /** List of string values */ + char ** key ; /** List of string keys */ + unsigned * hash ; /** List of hash values for keys */ +} dictionary ; + + +/*--------------------------------------------------------------------------- + Function prototypes + ---------------------------------------------------------------------------*/ + +/*-------------------------------------------------------------------------*/ +/** + @brief Compute the hash key for a string. + @param key Character string to use for key. + @return 1 unsigned int on at least 32 bits. + + This hash function has been taken from an Article in Dr Dobbs Journal. + This is normally a collision-free function, distributing keys evenly. + The key is stored anyway in the struct so that collision can be avoided + by comparing the key itself in last resort. + */ +/*--------------------------------------------------------------------------*/ +unsigned dictionary_hash(const char * key); + +/*-------------------------------------------------------------------------*/ +/** + @brief Create a new dictionary object. + @param size Optional initial size of the dictionary. + @return 1 newly allocated dictionary object. + + This function allocates a new dictionary object of given size and returns + it. If you do not know in advance (roughly) the number of entries in the + dictionary, give size=0. + */ +/*--------------------------------------------------------------------------*/ +dictionary * dictionary_new(size_t size); + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a dictionary object + @param d dictionary object to deallocate. + @return void + + Deallocate a dictionary object and all memory associated to it. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_del(dictionary * vd); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get a value from a dictionary. + @param d dictionary object to search. + @param key Key to look for in the dictionary. + @param def Default value to return if key not found. + @return 1 pointer to internally allocated character string. + + This function locates a key in a dictionary and returns a pointer to its + value, or the passed 'def' pointer if no such key can be found in + dictionary. The returned character pointer points to data internal to the + dictionary object, you should not try to free it or modify it. + */ +/*--------------------------------------------------------------------------*/ +const char * dictionary_get(const dictionary * d, const char * key, const char * def); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Set a value in a dictionary. + @param d dictionary object to modify. + @param key Key to modify or add. + @param val Value to add. + @return int 0 if Ok, anything else otherwise + + If the given key is found in the dictionary, the associated value is + replaced by the provided one. If the key cannot be found in the + dictionary, it is added to it. + + It is Ok to provide a NULL value for val, but NULL values for the dictionary + or the key are considered as errors: the function will return immediately + in such a case. + + Notice that if you dictionary_set a variable to NULL, a call to + dictionary_get will return a NULL value: the variable will be found, and + its value (NULL) is returned. In other words, setting the variable + content to NULL is equivalent to deleting the variable from the + dictionary. It is not possible (in this implementation) to have a key in + the dictionary without value. + + This function returns non-zero in case of failure. + */ +/*--------------------------------------------------------------------------*/ +int dictionary_set(dictionary * vd, const char * key, const char * val); + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete a key in a dictionary + @param d dictionary object to modify. + @param key Key to remove. + @return void + + This function deletes a key in a dictionary. Nothing is done if the + key cannot be found. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_unset(dictionary * d, const char * key); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump + @param f Opened file pointer. + @return void + + Dumps a dictionary onto an opened file pointer. Key pairs are printed out + as @c [Key]=[Value], one per line. It is Ok to provide stdout or stderr as + output file pointers. + */ +/*--------------------------------------------------------------------------*/ +void dictionary_dump(const dictionary * d, FILE * out); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/perf/thirdparty/iniparser/iniparser.c b/perf/thirdparty/iniparser/iniparser.c new file mode 100644 index 0000000..4cffb96 --- /dev/null +++ b/perf/thirdparty/iniparser/iniparser.c @@ -0,0 +1,949 @@ + +/*-------------------------------------------------------------------------*/ +/** + @file iniparser.c + @author N. Devillard + @brief Parser for ini files. +*/ +/*--------------------------------------------------------------------------*/ +/*---------------------------- Includes ------------------------------------*/ +#include <ctype.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include "iniparser.h" + +/*---------------------------- Defines -------------------------------------*/ +#define ASCIILINESZ (1024) +#define INI_INVALID_KEY ((char*)-1) + +/*--------------------------------------------------------------------------- + Private to this module + ---------------------------------------------------------------------------*/ +/** + * This enum stores the status for each parsed line (internal use only). + */ +typedef enum _line_status_ { + LINE_UNPROCESSED, + LINE_ERROR, + LINE_EMPTY, + LINE_COMMENT, + LINE_SECTION, + LINE_VALUE +} line_status ; + +/*-------------------------------------------------------------------------*/ +/** + @brief Convert a string to lowercase. + @param in String to convert. + @param out Output buffer. + @param len Size of the out buffer. + @return ptr to the out buffer or NULL if an error occured. + + This function convert a string into lowercase. + At most len - 1 elements of the input string will be converted. + */ +/*--------------------------------------------------------------------------*/ +static const char * strlwc(const char * in, char *out, unsigned len) +{ + unsigned i ; + + if (in==NULL || out == NULL || len==0) return NULL ; + i=0 ; + while (in[i] != '\0' && i < len-1) { + out[i] = (char)tolower((int)in[i]); + i++ ; + } + out[i] = '\0'; + return out ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Duplicate a string + @param s String to duplicate + @return Pointer to a newly allocated string, to be freed with free() + + This is a replacement for strdup(). This implementation is provided + for systems that do not have it. + */ +/*--------------------------------------------------------------------------*/ +static char * xstrdup(const char * s) +{ + char * t ; + size_t len ; + if (!s) + return NULL ; + + len = strlen(s) + 1 ; + t = (char*) malloc(len) ; + if (t) { + memcpy(t, s, len) ; + } + return t ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Remove blanks at the beginning and the end of a string. + @param str String to parse and alter. + @return unsigned New size of the string. + */ +/*--------------------------------------------------------------------------*/ +static unsigned strstrip(char * s) +{ + char *last = NULL ; + char *dest = s; + + if (s==NULL) return 0; + + last = s + strlen(s); + while (isspace((int)*s) && *s) s++; + while (last > s) { + if (!isspace((int)*(last-1))) + break ; + last -- ; + } + *last = (char)0; + + memmove(dest,s,last - s + 1); + return last - s; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Default error callback for iniparser: wraps `fprintf(stderr, ...)`. + */ +/*--------------------------------------------------------------------------*/ +static int default_error_callback(const char *format, ...) +{ + int ret; + va_list argptr; + va_start(argptr, format); + ret = vfprintf(stderr, format, argptr); + va_end(argptr); + return ret; +} + +static int (*iniparser_error_callback)(const char*, ...) = default_error_callback; + +/*-------------------------------------------------------------------------*/ +/** + @brief Configure a function to receive the error messages. + @param errback Function to call. + + By default, the error will be printed on stderr. If a null pointer is passed + as errback the error callback will be switched back to default. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_set_error_callback(int (*errback)(const char *, ...)) +{ + if (errback) { + iniparser_error_callback = errback; + } else { + iniparser_error_callback = default_error_callback; + } +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get number of sections in a dictionary + @param d Dictionary to examine + @return int Number of sections found in dictionary + + This function returns the number of sections found in a dictionary. + The test to recognize sections is done on the string stored in the + dictionary: a section name is given as "section" whereas a key is + stored as "section:key", thus the test looks for entries that do not + contain a colon. + + This clearly fails in the case a section name contains a colon, but + this should simply be avoided. + + This function returns -1 in case of error. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getnsec(const dictionary * d) +{ + size_t i ; + int nsec ; + + if (d==NULL) return -1 ; + nsec=0 ; + for (i=0 ; i<d->size ; i++) { + if (d->key[i]==NULL) + continue ; + if (strchr(d->key[i], ':')==NULL) { + nsec ++ ; + } + } + return nsec ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get name for section n in a dictionary. + @param d Dictionary to examine + @param n Section number (from 0 to nsec-1). + @return Pointer to char string + + This function locates the n-th section in a dictionary and returns + its name as a pointer to a string statically allocated inside the + dictionary. Do not free or modify the returned string! + + This function returns NULL in case of error. + */ +/*--------------------------------------------------------------------------*/ +const char * iniparser_getsecname(const dictionary * d, int n) +{ + size_t i ; + int foundsec ; + + if (d==NULL || n<0) return NULL ; + foundsec=0 ; + for (i=0 ; i<d->size ; i++) { + if (d->key[i]==NULL) + continue ; + if (strchr(d->key[i], ':')==NULL) { + foundsec++ ; + if (foundsec>n) + break ; + } + } + if (foundsec<=n) { + return NULL ; + } + return d->key[i] ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump. + @param f Opened file pointer to dump to. + @return void + + This function prints out the contents of a dictionary, one element by + line, onto the provided file pointer. It is OK to specify @c stderr + or @c stdout as output files. This function is meant for debugging + purposes mostly. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dump(const dictionary * d, FILE * f) +{ + size_t i ; + + if (d==NULL || f==NULL) return ; + for (i=0 ; i<d->size ; i++) { + if (d->key[i]==NULL) + continue ; + if (d->val[i]!=NULL) { + fprintf(f, "[%s]=[%s]\n", d->key[i], d->val[i]); + } else { + fprintf(f, "[%s]=UNDEF\n", d->key[i]); + } + } + return ; +} + +static void escape_value(char *escaped, char *value) { + char c; + int v = 0; + int e = 0; + + if(!escaped || !value) + return; + + while((c = value[v]) != '\0') { + if(c == '\\' || c == '"') { + escaped[e] = '\\'; + e++; + } + escaped[e] = c; + v++; + e++; + } + escaped[e] = '\0'; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary to a loadable ini file + @param d Dictionary to dump + @param f Opened file pointer to dump to + @return void + + This function dumps a given dictionary into a loadable ini file. + It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dump_ini(const dictionary * d, FILE * f) +{ + size_t i ; + size_t nsec ; + const char * secname ; + char escaped[ASCIILINESZ+1] = ""; + + if (d==NULL || f==NULL) return ; + + nsec = iniparser_getnsec(d); + if (nsec<1) { + /* No section in file: dump all keys as they are */ + for (i=0 ; i<d->size ; i++) { + if (d->key[i]==NULL) + continue ; + escape_value(escaped, d->val[i]); + fprintf(f, "%s = \"%s\"\n", d->key[i], escaped); + } + return ; + } + for (i=0 ; i<nsec ; i++) { + secname = iniparser_getsecname(d, i) ; + iniparser_dumpsection_ini(d, secname, f); + } + fprintf(f, "\n"); + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary section to a loadable ini file + @param d Dictionary to dump + @param s Section name of dictionary to dump + @param f Opened file pointer to dump to + @return void + + This function dumps a given section of a given dictionary into a loadable ini + file. It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f) +{ + size_t j ; + char keym[ASCIILINESZ+1]; + int seclen ; + char escaped[ASCIILINESZ+1] = ""; + + if (d==NULL || f==NULL) return ; + if (! iniparser_find_entry(d, s)) return ; + + seclen = (int)strlen(s); + fprintf(f, "\n[%s]\n", s); + sprintf(keym, "%s:", s); + for (j=0 ; j<d->size ; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], keym, seclen+1)) { + escape_value(escaped, d->val[j]); + fprintf(f, "%-30s = \"%s\"\n", d->key[j]+seclen+1, escaped); + } + } + fprintf(f, "\n"); + return ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @return Number of keys in section + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getsecnkeys(const dictionary * d, const char * s) +{ + int seclen, nkeys ; + char keym[ASCIILINESZ+1]; + size_t j ; + + nkeys = 0; + + if (d==NULL) return nkeys; + if (! iniparser_find_entry(d, s)) return nkeys; + + seclen = (int)strlen(s); + strlwc(s, keym, sizeof(keym)); + keym[seclen] = ':'; + + for (j=0 ; j<d->size ; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], keym, seclen+1)) + nkeys++; + } + + return nkeys; + +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @param keys Already allocated array to store the keys in + @return The pointer passed as `keys` argument or NULL in case of error + + This function queries a dictionary and finds all keys in a given section. + The keys argument should be an array of pointers which size has been + determined by calling `iniparser_getsecnkeys` function prior to this one. + + Each pointer in the returned char pointer-to-pointer is pointing to + a string allocated in the dictionary; do not free or modify them. + */ +/*--------------------------------------------------------------------------*/ +const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys) +{ + size_t i, j, seclen ; + char keym[ASCIILINESZ+1]; + + if (d==NULL || keys==NULL) return NULL; + if (! iniparser_find_entry(d, s)) return NULL; + + seclen = strlen(s); + strlwc(s, keym, sizeof(keym)); + keym[seclen] = ':'; + + i = 0; + + for (j=0 ; j<d->size ; j++) { + if (d->key[j]==NULL) + continue ; + if (!strncmp(d->key[j], keym, seclen+1)) { + keys[i] = d->key[j]; + i++; + } + } + + return keys; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key + @param d Dictionary to search + @param key Key string to look for + @param def Default value to return if key not found. + @return pointer to statically allocated character string + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the pointer passed as 'def' is returned. + The returned char pointer is pointing to a string allocated in + the dictionary, do not free or modify it. + */ +/*--------------------------------------------------------------------------*/ +const char * iniparser_getstring(const dictionary * d, const char * key, const char * def) +{ + const char * lc_key ; + const char * sval ; + char tmp_str[ASCIILINESZ+1]; + + if (d==NULL || key==NULL) + return def ; + + lc_key = strlwc(key, tmp_str, sizeof(tmp_str)); + sval = dictionary_get(d, lc_key, def); + return sval ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an long int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return long integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + "42" -> 42 + "042" -> 34 (octal -> decimal) + "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + + Credits: Thanks to A. Becker for suggesting strtol() + */ +/*--------------------------------------------------------------------------*/ +long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound) +{ + const char * str ; + + str = iniparser_getstring(d, key, INI_INVALID_KEY); + if (str==NULL || str==INI_INVALID_KEY) return notfound ; + return strtol(str, NULL, 0); +} + +int64_t iniparser_getint64(const dictionary * d, const char * key, int64_t notfound) +{ + const char * str ; + + str = iniparser_getstring(d, key, INI_INVALID_KEY); + if (str==NULL || str==INI_INVALID_KEY) return notfound ; + return strtoimax(str, NULL, 0); +} + +uint64_t iniparser_getuint64(const dictionary * d, const char * key, uint64_t notfound) +{ + const char * str ; + + str = iniparser_getstring(d, key, INI_INVALID_KEY); + if (str==NULL || str==INI_INVALID_KEY) return notfound ; + return strtoumax(str, NULL, 0); +} + + + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + "42" -> 42 + "042" -> 34 (octal -> decimal) + "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + + Credits: Thanks to A. Becker for suggesting strtol() + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getint(const dictionary * d, const char * key, int notfound) +{ + return (int)iniparser_getlongint(d, key, notfound); +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a double + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return double + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + */ +/*--------------------------------------------------------------------------*/ +double iniparser_getdouble(const dictionary * d, const char * key, double notfound) +{ + const char * str ; + + str = iniparser_getstring(d, key, INI_INVALID_KEY); + if (str==NULL || str==INI_INVALID_KEY) return notfound ; + return atof(str); +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a boolean + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + A true boolean is found if one of the following is matched: + + - A string starting with 'y' + - A string starting with 'Y' + - A string starting with 't' + - A string starting with 'T' + - A string starting with '1' + + A false boolean is found if one of the following is matched: + + - A string starting with 'n' + - A string starting with 'N' + - A string starting with 'f' + - A string starting with 'F' + - A string starting with '0' + + The notfound value returned if no boolean is identified, does not + necessarily have to be 0 or 1. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getboolean(const dictionary * d, const char * key, int notfound) +{ + int ret ; + const char * c ; + + c = iniparser_getstring(d, key, INI_INVALID_KEY); + if (c==NULL || c==INI_INVALID_KEY) return notfound ; + if (c[0]=='y' || c[0]=='Y' || c[0]=='1' || c[0]=='t' || c[0]=='T') { + ret = 1 ; + } else if (c[0]=='n' || c[0]=='N' || c[0]=='0' || c[0]=='f' || c[0]=='F') { + ret = 0 ; + } else { + ret = notfound ; + } + return ret; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Finds out if a given entry exists in a dictionary + @param ini Dictionary to search + @param entry Name of the entry to look for + @return integer 1 if entry exists, 0 otherwise + + Finds out if a given entry exists in the dictionary. Since sections + are stored as keys with NULL associated values, this is the only way + of querying for the presence of sections in a dictionary. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_find_entry(const dictionary * ini, const char * entry) +{ + int found=0 ; + if (iniparser_getstring(ini, entry, INI_INVALID_KEY)!=INI_INVALID_KEY) { + found = 1 ; + } + return found ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Set an entry in a dictionary. + @param ini Dictionary to modify. + @param entry Entry to modify (entry name) + @param val New value to associate to the entry. + @return int 0 if Ok, -1 otherwise. + + If the given entry can be found in the dictionary, it is modified to + contain the provided value. If it cannot be found, the entry is created. + It is Ok to set val to NULL. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_set(dictionary * ini, const char * entry, const char * val) +{ + char tmp_str[ASCIILINESZ+1]; + return dictionary_set(ini, strlwc(entry, tmp_str, sizeof(tmp_str)), val) ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete an entry in a dictionary + @param ini Dictionary to modify + @param entry Entry to delete (entry name) + @return void + + If the given entry can be found, it is deleted from the dictionary. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_unset(dictionary * ini, const char * entry) +{ + char tmp_str[ASCIILINESZ+1]; + dictionary_unset(ini, strlwc(entry, tmp_str, sizeof(tmp_str))); +} + +static void parse_quoted_value(char *value, char quote) { + char c; + char *quoted; + int q = 0, v = 0; + int esc = 0; + + if(!value) + return; + + quoted = xstrdup(value); + + if(!quoted) { + iniparser_error_callback("iniparser: memory allocation failure\n"); + goto end_of_value; + } + + while((c = quoted[q]) != '\0') { + if(!esc) { + if(c == '\\') { + esc = 1; + q++; + continue; + } + + if(c == quote) { + goto end_of_value; + } + } + esc = 0; + value[v] = c; + v++; + q++; + } +end_of_value: + value[v] = '\0'; + free(quoted); +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Load a single line from an INI file + @param input_line Input line, may be concatenated multi-line input + @param section Output space to store section + @param key Output space to store key + @param value Output space to store value + @return line_status value + */ +/*--------------------------------------------------------------------------*/ +static line_status iniparser_line( + const char * input_line, + char * section, + char * key, + char * value) +{ + line_status sta ; + char * line = NULL; + size_t len ; + int d_quote; + + line = xstrdup(input_line); + len = strstrip(line); + + sta = LINE_UNPROCESSED ; + if (len<1) { + /* Empty line */ + sta = LINE_EMPTY ; + } else if (line[0]=='#' || line[0]==';') { + /* Comment line */ + sta = LINE_COMMENT ; + } else if (line[0]=='[' && line[len-1]==']') { + /* Section name without opening square bracket */ + sscanf(line, "[%[^\n]", section); + len = strlen(section); + /* Section name without closing square bracket */ + if(section[len-1] == ']') + { + section[len-1] = '\0'; + } + strstrip(section); + strlwc(section, section, len); + sta = LINE_SECTION ; + } else if ((d_quote = sscanf (line, "%[^=] = \"%[^\n]\"", key, value)) == 2 + || sscanf (line, "%[^=] = '%[^\n]'", key, value) == 2) { + /* Usual key=value with quotes, with or without comments */ + strstrip(key); + strlwc(key, key, len); + if(d_quote == 2) + parse_quoted_value(value, '"'); + else + parse_quoted_value(value, '\''); + /* Don't strip spaces from values surrounded with quotes */ + sta = LINE_VALUE ; + } else if (sscanf (line, "%[^=] = %[^;#]", key, value) == 2) { + /* Usual key=value without quotes, with or without comments */ + strstrip(key); + strlwc(key, key, len); + strstrip(value); + /* + * sscanf cannot handle '' or "" as empty values + * this is done here + */ + if (!strcmp(value, "\"\"") || (!strcmp(value, "''"))) { + value[0]=0 ; + } + sta = LINE_VALUE ; + } else if (sscanf(line, "%[^=] = %[;#]", key, value)==2 + || sscanf(line, "%[^=] %[=]", key, value) == 2) { + /* + * Special cases: + * key= + * key=; + * key=# + */ + strstrip(key); + strlwc(key, key, len); + value[0]=0 ; + sta = LINE_VALUE ; + } else { + /* Generate syntax error */ + sta = LINE_ERROR ; + } + + free(line); + return sta ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Parse an ini file and return an allocated dictionary object + @param in File to read. + @param ininame Name of the ini file to read (only used for nicer error messages) + @return Pointer to newly allocated dictionary + + This is the parser for ini files. This function is called, providing + the file to be read. It returns a dictionary object that should not + be accessed directly, but through accessor functions instead. + + The returned dictionary must be freed using iniparser_freedict(). + */ +/*--------------------------------------------------------------------------*/ +dictionary * iniparser_load_file(FILE * in, const char * ininame) +{ + char line [ASCIILINESZ+1] ; + char section [ASCIILINESZ+1] ; + char key [ASCIILINESZ+1] ; + char tmp [(ASCIILINESZ * 2) + 2] ; + char val [ASCIILINESZ+1] ; + + int last=0 ; + int len ; + int lineno=0 ; + int errs=0; + int mem_err=0; + + dictionary * dict ; + + dict = dictionary_new(0) ; + if (!dict) { + return NULL ; + } + + memset(line, 0, ASCIILINESZ); + memset(section, 0, ASCIILINESZ); + memset(key, 0, ASCIILINESZ); + memset(val, 0, ASCIILINESZ); + last=0 ; + + while (fgets(line+last, ASCIILINESZ-last, in)!=NULL) { + lineno++ ; + len = (int)strlen(line)-1; + if (len<=0) + continue; + /* Safety check against buffer overflows */ + if (line[len]!='\n' && !feof(in)) { + iniparser_error_callback( + "iniparser: input line too long in %s (%d)\n", + ininame, + lineno); + dictionary_del(dict); + return NULL ; + } + /* Get rid of \n and spaces at end of line */ + while ((len>=0) && + ((line[len]=='\n') || (isspace(line[len])))) { + line[len]=0 ; + len-- ; + } + if (len < 0) { /* Line was entirely \n and/or spaces */ + len = 0; + } + /* Detect multi-line */ + if (line[len]=='\\') { + /* Multi-line value */ + last=len ; + continue ; + } else { + last=0 ; + } + switch (iniparser_line(line, section, key, val)) { + case LINE_EMPTY: + case LINE_COMMENT: + break ; + + case LINE_SECTION: + mem_err = dictionary_set(dict, section, NULL); + break ; + + case LINE_VALUE: + sprintf(tmp, "%s:%s", section, key); + mem_err = dictionary_set(dict, tmp, val); + break ; + + case LINE_ERROR: + iniparser_error_callback( + "iniparser: syntax error in %s (%d):\n-> %s\n", + ininame, + lineno, + line); + errs++ ; + break; + + default: + break ; + } + memset(line, 0, ASCIILINESZ); + last=0; + if (mem_err<0) { + iniparser_error_callback("iniparser: memory allocation failure\n"); + break ; + } + } + if (errs) { + dictionary_del(dict); + dict = NULL ; + } + return dict ; +} + +/*-------------------------------------------------------------------------*/ +/** + @brief Parse an ini file and return an allocated dictionary object + @param ininame Name of the ini file to read. + @return Pointer to newly allocated dictionary + + This is the parser for ini files. This function is called, providing + the name of the file to be read. It returns a dictionary object that + should not be accessed directly, but through accessor functions + instead. + + The returned dictionary must be freed using iniparser_freedict(). + */ +/*--------------------------------------------------------------------------*/ +dictionary * iniparser_load(const char * ininame) +{ + FILE * in ; + dictionary * dict ; + + if ((in=fopen(ininame, "r"))==NULL) { + iniparser_error_callback("iniparser: cannot open %s\n", ininame); + return NULL ; + } + + dict = iniparser_load_file(in, ininame); + fclose(in); + + return dict ; +} + + +/*-------------------------------------------------------------------------*/ +/** + @brief Free all memory associated to an ini dictionary + @param d Dictionary to free + @return void + + Free all memory associated to an ini dictionary. + It is mandatory to call this function before the dictionary object + gets out of the current context. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_freedict(dictionary * d) +{ + dictionary_del(d); +} diff --git a/perf/thirdparty/iniparser/iniparser.h b/perf/thirdparty/iniparser/iniparser.h new file mode 100644 index 0000000..d8026a8 --- /dev/null +++ b/perf/thirdparty/iniparser/iniparser.h @@ -0,0 +1,446 @@ + +/*-------------------------------------------------------------------------*/ +/** + @file iniparser.h + @author N. Devillard + @brief Parser for ini files. +*/ +/*--------------------------------------------------------------------------*/ + +#ifndef _INIPARSER_H_ +#define _INIPARSER_H_ + +/*--------------------------------------------------------------------------- + Includes + ---------------------------------------------------------------------------*/ + +#include "dictionary.h" +#include <stdint.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/*-------------------------------------------------------------------------*/ +/** + @brief Configure a function to receive the error messages. + @param errback Function to call. + + By default, the error will be printed on stderr. If a null pointer is passed + as errback the error callback will be switched back to default. + */ +/*--------------------------------------------------------------------------*/ + +void iniparser_set_error_callback(int (*errback)(const char *, ...)); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get number of sections in a dictionary + @param d Dictionary to examine + @return int Number of sections found in dictionary + + This function returns the number of sections found in a dictionary. + The test to recognize sections is done on the string stored in the + dictionary: a section name is given as "section" whereas a key is + stored as "section:key", thus the test looks for entries that do not + contain a colon. + + This clearly fails in the case a section name contains a colon, but + this should simply be avoided. + + This function returns -1 in case of error. + */ +/*--------------------------------------------------------------------------*/ + +int iniparser_getnsec(const dictionary * d); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Get name for section n in a dictionary. + @param d Dictionary to examine + @param n Section number (from 0 to nsec-1). + @return Pointer to char string + + This function locates the n-th section in a dictionary and returns + its name as a pointer to a string statically allocated inside the + dictionary. Do not free or modify the returned string! + + This function returns NULL in case of error. + */ +/*--------------------------------------------------------------------------*/ + +const char * iniparser_getsecname(const dictionary * d, int n); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary to a loadable ini file + @param d Dictionary to dump + @param f Opened file pointer to dump to + + This function dumps a given dictionary into a loadable ini file. + It is Ok to specify @c stderr or @c stdout as output files. + + All values are quoted, these charecters are escaped: + + - ' : the quote character (e.g. "String with \"Quotes\"") + - \ : the backslash character (e.g. "C:\\tmp") + + */ +/*--------------------------------------------------------------------------*/ + +void iniparser_dump_ini(const dictionary * d, FILE * f); + +/*-------------------------------------------------------------------------*/ +/** + @brief Save a dictionary section to a loadable ini file + @param d Dictionary to dump + @param s Section name of dictionary to dump + @param f Opened file pointer to dump to + + This function dumps a given section of a given dictionary into a loadable ini + file. It is Ok to specify @c stderr or @c stdout as output files. + */ +/*--------------------------------------------------------------------------*/ + +void iniparser_dumpsection_ini(const dictionary * d, const char * s, FILE * f); + +/*-------------------------------------------------------------------------*/ +/** + @brief Dump a dictionary to an opened file pointer. + @param d Dictionary to dump. + @param f Opened file pointer to dump to. + + This function prints out the contents of a dictionary, one element by + line, onto the provided file pointer. It is OK to specify @c stderr + or @c stdout as output files. This function is meant for debugging + purposes mostly. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_dump(const dictionary * d, FILE * f); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @return Number of keys in section + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getsecnkeys(const dictionary * d, const char * s); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the number of keys in a section of a dictionary. + @param d Dictionary to examine + @param s Section name of dictionary to examine + @param keys Already allocated array to store the keys in + @return The pointer passed as `keys` argument or NULL in case of error + + This function queries a dictionary and finds all keys in a given section. + The keys argument should be an array of pointers which size has been + determined by calling `iniparser_getsecnkeys` function prior to this one. + + Each pointer in the returned char pointer-to-pointer is pointing to + a string allocated in the dictionary; do not free or modify them. + */ +/*--------------------------------------------------------------------------*/ +const char ** iniparser_getseckeys(const dictionary * d, const char * s, const char ** keys); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key + @param d Dictionary to search + @param key Key string to look for + @param def Default value to return if key not found. + @return pointer to statically allocated character string + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the pointer passed as 'def' is returned. + The returned char pointer is pointing to a string allocated in + the dictionary, do not free or modify it. + */ +/*--------------------------------------------------------------------------*/ +const char * iniparser_getstring(const dictionary * d, const char * key, const char * def); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + - "42" -> 42 + - "042" -> 34 (octal -> decimal) + - "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + + Credits: Thanks to A. Becker for suggesting strtol() + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getint(const dictionary * d, const char * key, int notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an long int + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + - "42" -> 42 + - "042" -> 34 (octal -> decimal) + - "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtol(), see the associated man page for overflow + handling. + */ +/*--------------------------------------------------------------------------*/ +long int iniparser_getlongint(const dictionary * d, const char * key, long int notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an int64_t + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + - "42" -> 42 + - "042" -> 34 (octal -> decimal) + - "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtoimax(), see the associated man page for overflow + handling. + + This function is usefull on 32bit architectures where `long int` is only + 32bit. + */ +/*--------------------------------------------------------------------------*/ +int64_t iniparser_getint64(const dictionary * d, const char * key, int64_t notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to an uint64_t + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + Supported values for integers include the usual C notation + so decimal, octal (starting with 0) and hexadecimal (starting with 0x) + are supported. Examples: + + - "42" -> 42 + - "042" -> 34 (octal -> decimal) + - "0x42" -> 66 (hexa -> decimal) + + Warning: the conversion may overflow in various ways. Conversion is + totally outsourced to strtoumax(), see the associated man page for overflow + handling. + + This function is usefull on 32bit architectures where `long int` is only + 32bit. + */ +/*--------------------------------------------------------------------------*/ +uint64_t iniparser_getuint64(const dictionary * d, const char * key, uint64_t notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a double + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return double + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + */ +/*--------------------------------------------------------------------------*/ +double iniparser_getdouble(const dictionary * d, const char * key, double notfound); + +/*-------------------------------------------------------------------------*/ +/** + @brief Get the string associated to a key, convert to a boolean + @param d Dictionary to search + @param key Key string to look for + @param notfound Value to return in case of error + @return integer + + This function queries a dictionary for a key. A key as read from an + ini file is given as "section:key". If the key cannot be found, + the notfound value is returned. + + A true boolean is found if one of the following is matched: + + - A string starting with 'y' + - A string starting with 'Y' + - A string starting with 't' + - A string starting with 'T' + - A string starting with '1' + + A false boolean is found if one of the following is matched: + + - A string starting with 'n' + - A string starting with 'N' + - A string starting with 'f' + - A string starting with 'F' + - A string starting with '0' + + The notfound value returned if no boolean is identified, does not + necessarily have to be 0 or 1. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_getboolean(const dictionary * d, const char * key, int notfound); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Set an entry in a dictionary. + @param ini Dictionary to modify. + @param entry Entry to modify (entry name) + @param val New value to associate to the entry. + @return int 0 if Ok, -1 otherwise. + + If the given entry can be found in the dictionary, it is modified to + contain the provided value. If it cannot be found, the entry is created. + It is Ok to set val to NULL. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_set(dictionary * ini, const char * entry, const char * val); + + +/*-------------------------------------------------------------------------*/ +/** + @brief Delete an entry in a dictionary + @param ini Dictionary to modify + @param entry Entry to delete (entry name) + + If the given entry can be found, it is deleted from the dictionary. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_unset(dictionary * ini, const char * entry); + +/*-------------------------------------------------------------------------*/ +/** + @brief Finds out if a given entry exists in a dictionary + @param ini Dictionary to search + @param entry Name of the entry to look for + @return integer 1 if entry exists, 0 otherwise + + Finds out if a given entry exists in the dictionary. Since sections + are stored as keys with NULL associated values, this is the only way + of querying for the presence of sections in a dictionary. + */ +/*--------------------------------------------------------------------------*/ +int iniparser_find_entry(const dictionary * ini, const char * entry) ; + +/*-------------------------------------------------------------------------*/ +/** + @brief Parse an ini file and return an allocated dictionary object + @param ininame Name of the ini file to read. + @return Pointer to newly allocated dictionary + + This is the parser for ini files. This function is called, providing + the name of the file to be read. It returns a dictionary object that + should not be accessed directly, but through accessor functions + instead. + + Iff the value is a quoted string it supports some escape sequences: + + - \" or ' : the quote character + (e.g. 'String with "Quotes"' or "String with 'Quotes'") + - \ : the backslash character (e.g. "C:\tmp") + + Escape sequences always start with a backslash. Additional escape sequences + might be added in the future. Backslash characters must be escaped. Any other + sequence then those outlined above is invalid and may lead to unpredictable + results. + + The returned dictionary must be freed using iniparser_freedict(). + */ +/*--------------------------------------------------------------------------*/ +dictionary * iniparser_load(const char * ininame); + +/*-------------------------------------------------------------------------*/ +/** + @brief Parse an ini file and return an allocated dictionary object + @param in File to read. + @param ininame Name of the ini file to read (only used for nicer error messages) + @return Pointer to newly allocated dictionary + + This is the parser for ini files. This function is called, providing + the file to be read. It returns a dictionary object that should not + be accessed directly, but through accessor functions instead. + + Iff the value is a quoted string it supports some escape sequences: + + - \" or ' : the quote character + (e.g. 'String with "Quotes"' or "String with 'Quotes'") + - \ : the backslash character (e.g. "C:\tmp") + + Escape sequences always start with a backslash. Additional escape sequences + might be added in the future. Backslash characters must be escaped. Any other + sequence then those outlined above is invalid and may lead to unpredictable + results. + + The returned dictionary must be freed using iniparser_freedict(). + */ +/*--------------------------------------------------------------------------*/ +dictionary * iniparser_load_file(FILE * in, const char * ininame); + +/*-------------------------------------------------------------------------*/ +/** + @brief Free all memory associated to an ini dictionary + @param d Dictionary to free + + Free all memory associated to an ini dictionary. + It is mandatory to call this function before the dictionary object + gets out of the current context. + */ +/*--------------------------------------------------------------------------*/ +void iniparser_freedict(dictionary * d); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/perf/thirdparty/rmind_ringbuf/CMakeLists.txt b/perf/thirdparty/rmind_ringbuf/CMakeLists.txt new file mode 100644 index 0000000..7b65eaa --- /dev/null +++ b/perf/thirdparty/rmind_ringbuf/CMakeLists.txt @@ -0,0 +1,7 @@ +cmake_minimum_required(VERSION 3.0) +project(RMIND_RINGBUF) + +file(GLOB SRC_LIST ${CMAKE_CURRENT_SOURCE_DIR}/*.c) #搜索当前cmake所在目录下的c文件 +set(LIBRARY_OUTPUT_PATH ${LIB_PATH}) #设置库生成目录 + +add_library(rmind_ringbuf STATIC ${SRC_LIST}) #生成静态库
\ No newline at end of file diff --git a/perf/thirdparty/rmind_ringbuf/ringbuf.c b/perf/thirdparty/rmind_ringbuf/ringbuf.c new file mode 100644 index 0000000..75cfee9 --- /dev/null +++ b/perf/thirdparty/rmind_ringbuf/ringbuf.c @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2016-2017 Mindaugas Rasiukevicius <rmind at noxt eu> + * All rights reserved. + * + * Use is subject to license terms, as specified in the LICENSE file. + */ + +/* + * Atomic multi-producer single-consumer ring buffer, which supports + * contiguous range operations and which can be conveniently used for + * message passing. + * + * There are three offsets -- think of clock hands: + * - NEXT: marks the beginning of the available space, + * - WRITTEN: the point up to which the data is actually written. + * - Observed READY: point up to which data is ready to be written. + * + * Producers + * + * Observe and save the 'next' offset, then request N bytes from + * the ring buffer by atomically advancing the 'next' offset. Once + * the data is written into the "reserved" buffer space, the thread + * clears the saved value; these observed values are used to compute + * the 'ready' offset. + * + * Consumer + * + * Writes the data between 'written' and 'ready' offsets and updates + * the 'written' value. The consumer thread scans for the lowest + * seen value by the producers. + * + * Key invariant + * + * Producers cannot go beyond the 'written' offset; producers are + * also not allowed to catch up with the consumer. Only the consumer + * is allowed to catch up with the producer i.e. set the 'written' + * offset to be equal to the 'next' offset. + * + * Wrap-around + * + * If the producer cannot acquire the requested length due to little + * available space at the end of the buffer, then it will wraparound. + * WRAP_LOCK_BIT in 'next' offset is used to lock the 'end' offset. + * + * There is an ABA problem if one producer stalls while a pair of + * producer and consumer would both successfully wrap-around and set + * the 'next' offset to the stale value of the first producer, thus + * letting it to perform a successful CAS violating the invariant. + * A counter in the 'next' offset (masked by WRAP_COUNTER) is used + * to prevent from this problem. It is incremented on wraparounds. + * + * The same ABA problem could also cause a stale 'ready' offset, + * which could be observed by the consumer. We set WRAP_LOCK_BIT in + * the 'seen' value before advancing the 'next' and clear this bit + * after the successful advancing; this ensures that only the stable + * 'ready' is observed by the consumer. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdbool.h> +#include <inttypes.h> +#include <string.h> +#include <limits.h> +#include <errno.h> + +#include "ringbuf.h" +#include "utils.h" + +#define RBUF_OFF_MASK (0x00000000ffffffffUL) +#define WRAP_LOCK_BIT (0x8000000000000000UL) +#define RBUF_OFF_MAX (UINT64_MAX & ~WRAP_LOCK_BIT) + +#define WRAP_COUNTER (0x7fffffff00000000UL) +#define WRAP_INCR(x) (((x) + 0x100000000UL) & WRAP_COUNTER) + +typedef uint64_t ringbuf_off_t; + +struct ringbuf_worker { + volatile ringbuf_off_t seen_off; + int registered; +}; + +struct ringbuf { + /* Ring buffer space. */ + size_t space; + + /* + * The NEXT hand is atomically updated by the producer. + * WRAP_LOCK_BIT is set in case of wrap-around; in such case, + * the producer can update the 'end' offset. + */ + volatile ringbuf_off_t next; + ringbuf_off_t end; + + /* The following are updated by the consumer. */ + ringbuf_off_t written; + unsigned nworkers; + ringbuf_worker_t workers[]; +}; + +/* + * ringbuf_setup: initialise a new ring buffer of a given length. + */ +int +ringbuf_setup(ringbuf_t *rbuf, unsigned nworkers, size_t length) +{ + if (length >= RBUF_OFF_MASK) { + errno = EINVAL; + return -1; + } + memset(rbuf, 0, offsetof(ringbuf_t, workers[nworkers])); + rbuf->space = length; + rbuf->end = RBUF_OFF_MAX; + rbuf->nworkers = nworkers; + return 0; +} + +/* + * ringbuf_get_sizes: return the sizes of the ringbuf_t and ringbuf_worker_t. + */ +void +ringbuf_get_sizes(unsigned nworkers, + size_t *ringbuf_size, size_t *ringbuf_worker_size) +{ + if (ringbuf_size) + *ringbuf_size = offsetof(ringbuf_t, workers[nworkers]); + if (ringbuf_worker_size) + *ringbuf_worker_size = sizeof(ringbuf_worker_t); +} + +/* + * ringbuf_register: register the worker (thread/process) as a producer + * and pass the pointer to its local store. + */ +ringbuf_worker_t * +ringbuf_register(ringbuf_t *rbuf, unsigned i) +{ + ringbuf_worker_t *w = &rbuf->workers[i]; + + w->seen_off = RBUF_OFF_MAX; + atomic_store_explicit(&w->registered, true, memory_order_release); + return w; +} + +void +ringbuf_unregister(ringbuf_t *rbuf, ringbuf_worker_t *w) +{ + w->registered = false; + (void)rbuf; +} + +/* + * stable_nextoff: capture and return a stable value of the 'next' offset. + */ +static inline ringbuf_off_t +stable_nextoff(ringbuf_t *rbuf) +{ + unsigned count = SPINLOCK_BACKOFF_MIN; + ringbuf_off_t next; +retry: + next = atomic_load_explicit(&rbuf->next, memory_order_acquire); + if (next & WRAP_LOCK_BIT) { + SPINLOCK_BACKOFF(count); + goto retry; + } + ASSERT((next & RBUF_OFF_MASK) < rbuf->space); + return next; +} + +/* + * stable_seenoff: capture and return a stable value of the 'seen' offset. + */ +static inline ringbuf_off_t +stable_seenoff(ringbuf_worker_t *w) +{ + unsigned count = SPINLOCK_BACKOFF_MIN; + ringbuf_off_t seen_off; +retry: + seen_off = atomic_load_explicit(&w->seen_off, memory_order_acquire); + if (seen_off & WRAP_LOCK_BIT) { + SPINLOCK_BACKOFF(count); + goto retry; + } + return seen_off; +} + +/* + * ringbuf_acquire: request a space of a given length in the ring buffer. + * + * => On success: returns the offset at which the space is available. + * => On failure: returns -1. + */ +ssize_t +ringbuf_acquire(ringbuf_t *rbuf, ringbuf_worker_t *w, size_t len) +{ + ringbuf_off_t seen, next, target; + + ASSERT(len > 0 && len <= rbuf->space); + ASSERT(w->seen_off == RBUF_OFF_MAX); + + do { + ringbuf_off_t written; + + /* + * Get the stable 'next' offset. Save the observed 'next' + * value (i.e. the 'seen' offset), but mark the value as + * unstable (set WRAP_LOCK_BIT). + * + * Note: CAS will issue a memory_order_release for us and + * thus ensures that it reaches global visibility together + * with new 'next'. + */ + seen = stable_nextoff(rbuf); + next = seen & RBUF_OFF_MASK; + ASSERT(next < rbuf->space); + atomic_store_explicit(&w->seen_off, next | WRAP_LOCK_BIT, + memory_order_relaxed); + + /* + * Compute the target offset. Key invariant: we cannot + * go beyond the WRITTEN offset or catch up with it. + */ + target = next + len; + written = rbuf->written; + if (__predict_false(next < written && target >= written)) { + /* The producer must wait. */ + atomic_store_explicit(&w->seen_off, + RBUF_OFF_MAX, memory_order_release); + return -1; + } + + if (__predict_false(target >= rbuf->space)) { + const bool exceed = target > rbuf->space; + + /* + * Wrap-around and start from the beginning. + * + * If we would exceed the buffer, then attempt to + * acquire the WRAP_LOCK_BIT and use the space in + * the beginning. If we used all space exactly to + * the end, then reset to 0. + * + * Check the invariant again. + */ + target = exceed ? (WRAP_LOCK_BIT | len) : 0; + if ((target & RBUF_OFF_MASK) >= written) { + atomic_store_explicit(&w->seen_off, + RBUF_OFF_MAX, memory_order_release); + return -1; + } + /* Increment the wrap-around counter. */ + target |= WRAP_INCR(seen & WRAP_COUNTER); + } else { + /* Preserve the wrap-around counter. */ + target |= seen & WRAP_COUNTER; + } + } while (!atomic_compare_exchange_weak(&rbuf->next, &seen, target)); + + /* + * Acquired the range. Clear WRAP_LOCK_BIT in the 'seen' value + * thus indicating that it is stable now. + * + * No need for memory_order_release, since CAS issued a fence. + */ + atomic_store_explicit(&w->seen_off, w->seen_off & ~WRAP_LOCK_BIT, + memory_order_relaxed); + + /* + * If we set the WRAP_LOCK_BIT in the 'next' (because we exceed + * the remaining space and need to wrap-around), then save the + * 'end' offset and release the lock. + */ + if (__predict_false(target & WRAP_LOCK_BIT)) { + /* Cannot wrap-around again if consumer did not catch-up. */ + ASSERT(rbuf->written <= next); + ASSERT(rbuf->end == RBUF_OFF_MAX); + rbuf->end = next; + next = 0; + + /* + * Unlock: ensure the 'end' offset reaches global + * visibility before the lock is released. + */ + atomic_store_explicit(&rbuf->next, + (target & ~WRAP_LOCK_BIT), memory_order_release); + } + ASSERT((target & RBUF_OFF_MASK) <= rbuf->space); + return (ssize_t)next; +} + +/* + * ringbuf_produce: indicate the acquired range in the buffer is produced + * and is ready to be consumed. + */ +void +ringbuf_produce(ringbuf_t *rbuf, ringbuf_worker_t *w) +{ + (void)rbuf; + ASSERT(w->registered); + ASSERT(w->seen_off != RBUF_OFF_MAX); + atomic_store_explicit(&w->seen_off, RBUF_OFF_MAX, memory_order_release); +} + +/* + * ringbuf_consume: get a contiguous range which is ready to be consumed. + */ +size_t +ringbuf_consume(ringbuf_t *rbuf, size_t *offset) +{ + ringbuf_off_t written = rbuf->written, next, ready; + size_t towrite; +retry: + /* + * Get the stable 'next' offset. Note: stable_nextoff() issued + * a load memory barrier. The area between the 'written' offset + * and the 'next' offset will be the *preliminary* target buffer + * area to be consumed. + */ + next = stable_nextoff(rbuf) & RBUF_OFF_MASK; + if (written == next) { + /* If producers did not advance, then nothing to do. */ + return 0; + } + + /* + * Observe the 'ready' offset of each producer. + * + * At this point, some producer might have already triggered the + * wrap-around and some (or all) seen 'ready' values might be in + * the range between 0 and 'written'. We have to skip them. + */ + ready = RBUF_OFF_MAX; + + for (unsigned i = 0; i < rbuf->nworkers; i++) { + ringbuf_worker_t *w = &rbuf->workers[i]; + ringbuf_off_t seen_off; + + /* + * Skip if the worker has not registered. + * + * Get a stable 'seen' value. This is necessary since we + * want to discard the stale 'seen' values. + */ + if (!atomic_load_explicit(&w->registered, memory_order_relaxed)) + continue; + seen_off = stable_seenoff(w); + + /* + * Ignore the offsets after the possible wrap-around. + * We are interested in the smallest seen offset that is + * not behind the 'written' offset. + */ + if (seen_off >= written) { + ready = MIN(seen_off, ready); + } + ASSERT(ready >= written); + } + + /* + * Finally, we need to determine whether wrap-around occurred + * and deduct the safe 'ready' offset. + */ + if (next < written) { + const ringbuf_off_t end = MIN(rbuf->space, rbuf->end); + + /* + * Wrap-around case. Check for the cut off first. + * + * Reset the 'written' offset if it reached the end of + * the buffer or the 'end' offset (if set by a producer). + * However, we must check that the producer is actually + * done (the observed 'ready' offsets are clear). + */ + if (ready == RBUF_OFF_MAX && written == end) { + /* + * Clear the 'end' offset if was set. + */ + if (rbuf->end != RBUF_OFF_MAX) { + rbuf->end = RBUF_OFF_MAX; + } + + /* + * Wrap-around the consumer and start from zero. + */ + written = 0; + atomic_store_explicit(&rbuf->written, + written, memory_order_release); + goto retry; + } + + /* + * We cannot wrap-around yet; there is data to consume at + * the end. The ready range is smallest of the observed + * 'ready' or the 'end' offset. If neither is set, then + * the actual end of the buffer. + */ + ASSERT(ready > next); + ready = MIN(ready, end); + ASSERT(ready >= written); + } else { + /* + * Regular case. Up to the observed 'ready' (if set) + * or the 'next' offset. + */ + ready = MIN(ready, next); + } + towrite = ready - written; + *offset = written; + + ASSERT(ready >= written); + ASSERT(towrite <= rbuf->space); + return towrite; +} + +/* + * ringbuf_release: indicate that the consumed range can now be released. + */ +void +ringbuf_release(ringbuf_t *rbuf, size_t nbytes) +{ + const size_t nwritten = rbuf->written + nbytes; + + ASSERT(rbuf->written <= rbuf->space); + ASSERT(rbuf->written <= rbuf->end); + ASSERT(nwritten <= rbuf->space); + + rbuf->written = (nwritten == rbuf->space) ? 0 : nwritten; +} diff --git a/perf/thirdparty/rmind_ringbuf/ringbuf.h b/perf/thirdparty/rmind_ringbuf/ringbuf.h new file mode 100644 index 0000000..e8fc767 --- /dev/null +++ b/perf/thirdparty/rmind_ringbuf/ringbuf.h @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016 Mindaugas Rasiukevicius <rmind at noxt eu> + * All rights reserved. + * + * Use is subject to license terms, as specified in the LICENSE file. + */ + +#ifndef _RINGBUF_H_ +#define _RINGBUF_H_ + +__BEGIN_DECLS + +typedef struct ringbuf ringbuf_t; +typedef struct ringbuf_worker ringbuf_worker_t; + +int ringbuf_setup(ringbuf_t *, unsigned, size_t); +void ringbuf_get_sizes(unsigned, size_t *, size_t *); + +ringbuf_worker_t *ringbuf_register(ringbuf_t *, unsigned); +void ringbuf_unregister(ringbuf_t *, ringbuf_worker_t *); + +ssize_t ringbuf_acquire(ringbuf_t *, ringbuf_worker_t *, size_t); +void ringbuf_produce(ringbuf_t *, ringbuf_worker_t *); +size_t ringbuf_consume(ringbuf_t *, size_t *); +void ringbuf_release(ringbuf_t *, size_t); + +__END_DECLS + +#endif diff --git a/perf/thirdparty/rmind_ringbuf/utils.h b/perf/thirdparty/rmind_ringbuf/utils.h new file mode 100644 index 0000000..413157b --- /dev/null +++ b/perf/thirdparty/rmind_ringbuf/utils.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Berkeley Software Design, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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. + * 3. Neither the name of the University 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 REGENTS 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 REGENTS 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. + * + * @(#)cdefs.h 8.8 (Berkeley) 1/9/95 + */ + +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include <assert.h> + +/* + * A regular assert (debug/diagnostic only). + */ +#if defined(DEBUG) +#define ASSERT assert +#else +#define ASSERT(x) +#endif + +/* + * Minimum, maximum and rounding macros. + */ + +#ifndef MIN +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#endif + +#ifndef MAX +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#endif + +/* + * Branch prediction macros. + */ +#ifndef __predict_true +#define __predict_true(x) __builtin_expect((x) != 0, 1) +#define __predict_false(x) __builtin_expect((x) != 0, 0) +#endif + +/* + * Atomic operations and memory barriers. If C11 API is not available, + * then wrap the GCC builtin routines. + * + * Note: This atomic_compare_exchange_weak does not do the C11 thing of + * filling *(expected) with the actual value, because we don't need + * that here. + */ +#ifndef atomic_compare_exchange_weak +#define atomic_compare_exchange_weak(ptr, expected, desired) \ + __sync_bool_compare_and_swap(ptr, *(expected), desired) +#endif + +#ifndef atomic_thread_fence +#define memory_order_relaxed __ATOMIC_RELAXED +#define memory_order_acquire __ATOMIC_ACQUIRE +#define memory_order_release __ATOMIC_RELEASE +#define memory_order_seq_cst __ATOMIC_SEQ_CST +#define atomic_thread_fence(m) __atomic_thread_fence(m) +#endif +#ifndef atomic_store_explicit +#define atomic_store_explicit __atomic_store_n +#endif +#ifndef atomic_load_explicit +#define atomic_load_explicit __atomic_load_n +#endif + +/* + * Exponential back-off for the spinning paths. + */ +#define SPINLOCK_BACKOFF_MIN 4 +#define SPINLOCK_BACKOFF_MAX 128 +#if defined(__x86_64__) || defined(__i386__) +#define SPINLOCK_BACKOFF_HOOK __asm volatile("pause" ::: "memory") +#else +#define SPINLOCK_BACKOFF_HOOK +#endif +#define SPINLOCK_BACKOFF(count) \ +do { \ + for (int __i = (count); __i != 0; __i--) { \ + SPINLOCK_BACKOFF_HOOK; \ + } \ + if ((count) < SPINLOCK_BACKOFF_MAX) \ + (count) += (count); \ +} while (/* CONSTCOND */ 0); + +#endif |
