summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author刘煜 <[email protected]>2024-06-07 01:31:55 +0000
committer刘煜 <[email protected]>2024-06-07 01:31:55 +0000
commitd91f658284d4250e1e055f3423c2ef3ee513314a (patch)
treeb2d97d0d7585ff6ff77360a468f9c3765016d6ae
parent6e83a56c5926adec4045e08809b09c6f956e55f4 (diff)
第一个功能完成的版本
-rw-r--r--.gitignore5
-rw-r--r--.vscode/c_cpp_properties.json17
-rw-r--r--.vscode/settings.json97
-rw-r--r--CMakeLists.txt6
-rw-r--r--bbq/CMakeLists.txt25
-rw-r--r--bbq/include/bbq.h238
-rw-r--r--bbq/src/CMakeLists.txt10
-rw-r--r--bbq/src/bbq.c631
-rw-r--r--bbq/tests/CMakeLists.txt18
-rw-r--r--bbq/tests/common/test_mix.c206
-rw-r--r--bbq/tests/common/test_mix.h120
-rw-r--r--bbq/tests/common/test_queue.c446
-rw-r--r--bbq/tests/common/test_queue.h101
-rw-r--r--bbq/tests/unittest/CMakeLists.txt11
-rw-r--r--bbq/tests/unittest/ut.h37
-rw-r--r--bbq/tests/unittest/ut_data.cc76
-rw-r--r--bbq/tests/unittest/ut_example.cc230
-rw-r--r--bbq/tests/unittest/ut_head_cursor.cc522
-rw-r--r--bbq/tests/unittest/ut_mix.cc153
-rw-r--r--perf/CMakeLists.txt33
-rw-r--r--perf/benchmark/CMakeLists.txt13
-rw-r--r--perf/benchmark/bcm_benchmark.c165
-rw-r--r--perf/benchmark/bcm_loadconfig.c77
-rw-r--r--perf/benchmark/bcm_queue.c179
-rw-r--r--perf/benchmark/bcm_queue.h4
-rwxr-xr-xperf/benchmark/benchmark.sh86
-rw-r--r--perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block16.ini15
-rw-r--r--perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block2.ini15
-rw-r--r--perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block32.ini15
-rw-r--r--perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block4.ini15
-rw-r--r--perf/benchmark/config/bbq_block_mpmc/case_b0_simple_bbq_mpmc_block8.ini15
-rw-r--r--perf/benchmark/config/bbq_block_mpsc/case_b1_simple_bbq_mpsc_block2.ini15
-rw-r--r--perf/benchmark/config/bbq_block_mpsc/case_b2_simple_bbq_mpsc_block4.ini15
-rw-r--r--perf/benchmark/config/bbq_block_mpsc/case_b3_simple_bbq_mpsc_block8.ini15
-rw-r--r--perf/benchmark/config/bbq_block_mpsc/case_b4_simple_bbq_mpsc_block16.ini15
-rw-r--r--perf/benchmark/config/bbq_block_mpsc/case_b5_simple_bbq_mpsc_block32.ini15
-rw-r--r--perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block16.ini15
-rw-r--r--perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block2.ini15
-rw-r--r--perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block32.ini15
-rw-r--r--perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block4.ini15
-rw-r--r--perf/benchmark/config/bbq_block_spmc/case_b0_simple_bbq_spmc_block8.ini15
-rw-r--r--perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block16.ini15
-rw-r--r--perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block2.ini15
-rw-r--r--perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block32.ini15
-rw-r--r--perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block4.ini15
-rw-r--r--perf/benchmark/config/bbq_block_spsc/case_b0_simple_bbq_spsc_block8.ini15
-rw-r--r--perf/benchmark/config/bbq_debug/debug.ini15
-rw-r--r--perf/benchmark/config/compare/general/case1_simple_spsc.ini14
-rw-r--r--perf/benchmark/config/compare/general/case2_simple_spmc.ini14
-rw-r--r--perf/benchmark/config/compare/general/case3_simple_mpsc.ini14
-rw-r--r--perf/benchmark/config/compare/general/case4_complex_spmc.ini14
-rw-r--r--perf/benchmark/config/compare/general/case5_complex_mpsc.ini14
-rw-r--r--perf/benchmark/config/compare/general/case6_simple_mp0c.ini14
-rw-r--r--perf/benchmark/config/compare/general/case7_simple_0pmc.ini14
-rw-r--r--perf/benchmark/config/compare/general/case8_simple_mpmc.ini14
-rw-r--r--perf/benchmark/config/compare/general/case9_simple_mpmc_overcore.ini14
-rw-r--r--perf/benchmark/config/compare/perf/perf_case1_simple_spsc.ini14
-rw-r--r--perf/benchmark/config/compare/perf/perf_case2_simple_mpmc.ini14
-rw-r--r--perf/benchmark/config/compare/perf/perf_case3_simple_spmp.ini14
-rw-r--r--perf/benchmark/config/compare/perf/perf_case4_simple_mpsc.ini14
-rw-r--r--perf/thirdparty/CMakeLists.txt5
-rw-r--r--perf/thirdparty/iniparser/CMakeLists.txt8
-rw-r--r--perf/thirdparty/iniparser/dictionary.c383
-rw-r--r--perf/thirdparty/iniparser/dictionary.h170
-rw-r--r--perf/thirdparty/iniparser/iniparser.c949
-rw-r--r--perf/thirdparty/iniparser/iniparser.h446
-rw-r--r--perf/thirdparty/rmind_ringbuf/CMakeLists.txt7
-rw-r--r--perf/thirdparty/rmind_ringbuf/ringbuf.c430
-rw-r--r--perf/thirdparty/rmind_ringbuf/ringbuf.h29
-rw-r--r--perf/thirdparty/rmind_ringbuf/utils.h115
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
+# @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