sysbench是一款比较流行的测试工具,主要用于测试fileio、cpu、memory、threads、mutex等的性能测试,但是最主要的还是做数据库的性能测试,经常用来测试MySQL、Drizzle、AttachSQL、Oracle、PostgreSQL等相关的数据库,也可以扩展支持其他的数据库以及测试用例,本文主要介绍sysbench的代码逻辑、代码调试、lua脚本以及相关扩展功能。
本文的介绍主要基于sysbench 1.0的代码:
repo:https://github.com/akopytov/sysbench
branch:1.0
1. 代码逻辑
sysbench的代码逻辑如下(已经过滤掉一些不需要额外说明的代码文件):
sysbench/
├── src
│ ├── db_driver.c // db驱动测试功能接口,主要串联sysbench测试框架和具体的db-driver
│ ├── db_driver.h // db驱动接口申明,具体的实现见下面的drivers目录
│ ├── drivers // 具体的driver驱动实现
│ │ ├── attachsql
│ │ ├── drizzle
│ │ ├── mysql
│ │ ├── oracle
│ │ └── pgsql
│ ├── lua // 内置的oltp测试工具实现
│ │ ├── bulk_insert.lua
│ │ ├── internal
│ │ ├── oltp_common.lua
│ │ ├── oltp_delete.lua
│ │ ├── oltp_insert.lua
│ │ ├── oltp_point_select.lua
│ │ ├── oltp_read_only.lua
│ │ ├── oltp_read_write.lua
│ │ ├── oltp_update_index.lua
│ │ ├── oltp_update_non_index.lua
│ │ ├── oltp_write_only.lua
│ │ ├── select_random_points.lua
│ │ └── select_random_ranges.lua
│ ├── sb_barrier.h // 线程屏障,用于线程的同步
│ ├── sb_ck_pr.h // 原子操作,具体的在../third_party/concurrency_kit中实现
│ ├── sb_counter.h // 计数器程序,由于统计
│ ├── sb_global.h
│ ├── sb_histogram.h // todo
│ ├── sb_list.h // List的C语言实现
│ ├── sb_logger.h // logger实现
│ ├── sb_lua.c
│ ├── sb_lua.h // 串联C和Lua程序,目标:1. Lua可以调用C中的db-driver-api;2. C可以调用Lua程序
│ ├── sb_options.h // 参数解析实现
│ ├── sb_rand.h // 随机数、字符串实现
│ ├── sb_thread.h // 线程函数实现
│ ├── sb_timer.h // 定时器实现
│ ├── sb_util.h // 功能函数,主要是对齐(align)相关的宏
│ ├── sb_win.h // 跨平台代码,windows支持
│ ├── sysbench.c // sysbench的main函数
│ ├── sysbench.h
│ ├── tests // 其他的测试模型程序,包括cpu、fileio等
│ │ ├── cpu
│ │ ├── fileio
│ │ ├── memory
│ │ ├── mutex
│ │ ├── sb_cpu.h
│ │ ├── sb_fileio.h
│ │ ├── sb_memory.h
│ │ ├── sb_mutex.h
│ │ ├── sb_threads.h
│ │ └── threads
│ └── xoroshiro128plus.h
├── tests // db-driver的测试用例
│ ├── include
│ │ ├── api_sql_common.sh
│ │ ├── config.sh.in
│ │ ├── drv_common.sh
│ │ ├── inspect.lua
│ │ ├── mysql_common.sh
│ │ ├── oltp_legacy
│ │ ├── pgsql_common.sh
│ │ ├── script_bulk_insert_common.sh
│ │ ├── script_oltp_common.sh
│ │ ├── script_oltp_legacy_common.sh
│ │ ├── script_select_random_common.sh
│ │ └── script_select_random_legacy_common.sh
│ ├── t // sysbench单元测试程序
│ └── test_run.sh
└── third_party // sysbench的第三方依赖库
├── concurrency_kit // 并发控制模块
├── cram // 命令后程序测试框架
└── luajit // lua的jit编译器
阅读代码从sysbench.c中的main函数开始,流程逻辑还是比较清晰的:
// source file: src/sysbench.c
int main(int argc, char *argv[])
{
sb_test_t *test = NULL;
int rc;
sb_globals.argc = argc;
sb_globals.argv = malloc(argc * sizeof(char *));
memcpy(sb_globals.argv, argv, argc * sizeof(char *));
/* Initialize options library */
sb_options_init();
/* First register the logger */
if (log_register())
return EXIT_FAILURE;
/* Register available tests */
if (register_tests())
{
fprintf(stderr, "Failed to register tests.\n");
return EXIT_FAILURE;
}
/* Parse command line arguments */
if (parse_general_arguments(argc, argv))
return EXIT_FAILURE;
/* Initialize global variables and logger */
if (init() || log_init() || sb_counters_init())
return EXIT_FAILURE;
print_header();
test = sb_load_lua(NULL);
current_test = test;
/* Load and parse test-specific options */
if (parse_test_arguments(test, argc, argv))
return EXIT_FAILURE;
/* prepare, cleanup, run. */
if (!strcmp(sb_globals.cmdname, "prepare"))
{
rc = test->builtin_cmds.prepare();
}
else if (!strcmp(sb_globals.cmdname, "cleanup"))
{
rc = test->builtin_cmds.cleanup();
}
else if (!strcmp(sb_globals.cmdname, "run"))
{
rc = run_test(test) ? EXIT_FAILURE : EXIT_SUCCESS;
}
/* clean up. */
end:
if (sb_lua_loaded())
sb_lua_done();
db_done();
sb_counters_done();
log_done();
sb_options_done();
sb_rand_done();
sb_thread_done();
free(timers);
free(timers_copy);
free(sb_globals.argv);
return rc;
}
2. Lua脚本
Lua和C的亲和性非常好,被称为最好的胶水语言,Lua可以调用C中定义的方法,C也可以非常方便的调用Lua,为什么要C和Lua结合使用呢:
- C的优势是高性能,但它是编译语言,程序发生变动,就需要重新进行编译,相对来说不太灵活;
- Lua的优势是灵活,它是解释语言,无需编译,就可以执行,一般用来写一些经常变动的规则性代码。
那问题来了:Lua不会拖慢C的性能吗?那就不得不提LuaJIT了,它是Lua的虚拟机,主要功能就是将Lua程序编译成字节码程序去执行,LuaJIT比较轻量,在C程序里面很容易就能集成进去,这就使得Lua的性能并不会有大的影响,并且支持动态的加载Lua程序。
下面是一个Lua的简单的代码:
-- file: lua_test.lua
function domain(num)
-- call C function.
local tab = gettab()
-- show key and value
for k, v in pairs(tab) do
print("key: ".. k)
print("val: ".. v)
print()
end
end
下面是和C交互的代码:
// file: c_test.c
int get_tab(lua_State *L) {
// create table.
lua_newtable(L);
// push (key, value).
int i;
char value[10] = {0};
for(i=0; i<5; ++i) {
sprintf(value, "value%d", i+1);
lua_pushnumber(L, i+1); //key
lua_pushstring(L, value); //value
lua_settable(L, -3); //push key,value
}
// deal return.
return 1;
}
int main()
{
// create a state and load standard library.
lua_State* L = luaL_newstate();
luaL_openlibs(L);
// register function be called by lua.
lua_register(L, "gettab", get_tab);
// load and exec the specified lua file.
int error = luaL_dofile(L, "lua_test.lua");
if(error) {
perror("luaL_dofile error");
exit(1);
}
// get the domain function from lua file.
lua_getglobal(L, "domain");
// exec the domain function.
error = lua_pcall(L, 0, 0, 0);
if (error) {
fprintf(stderr, "%s\n",lua_tostring(L, -1));
lua_pop(L, 1);
}
// close lua state.
lua_close(L);
return 0;
}
这种通过lua_State交互的方式对C代码的侵入较大,还有一种就是ffi的方式,可以参考这个链接:
https://moonbingbing.gitbooks.io/openresty-best-practices/content/lua/FFI.html
sysbench以上两种方式都用了:
- 通过lua_State进行交互:主要用在Lua调用C中定义好的db-driver api;
- ffi主要用在Lua使用C中定义好的结构体、字段类型、常规函数(sleep、随机函数)等。
3. 扩展功能
sysbench测试db场景比较简单,表结构如下:
CREATE TABLE `sbtest1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k_1` (`k`) BLOCK_SIZE 16384 GLOBAL
);
在我们测试其他场景,比如宽表、大字段、小字段等,这种场景用原生的sysbench测试脚本就力不从心了。
这个时候就需要修改代码的实现了,实现也比较简单,就是在Lua脚本中实现这几个函数就可以了:
// src/sb_lua.c
#define EVENT_FUNC "event" // worker线程执行内容
#define PREPARE_FUNC "prepare" // prepare阶段:创建表、预加载数据
#define CLEANUP_FUNC "cleanup" // cleanup阶段:清理数据等
#define HELP_FUNC "help" // help函数,展示测试用例的参数用法
#define THREAD_INIT_FUNC "thread_init" // worker线程初始化:设置运行参数、生成标名称等
#define THREAD_DONE_FUNC "thread_done" // worker线程执行完:清理线程中的资源
#define THREAD_RUN_FUNC "thread_run" // 一般无需实现,只需要关注event
#define INIT_FUNC "init" // 全局init:一般无需实现
#define DONE_FUNC "done" // 全局done:一般无需实现
sysbench自带的oltp的测试case就是这样实现的,主要包含两个文件:
- oltp.lua:定义了thread_init、event函数;
- common.lua:定义了prepare、cleanup函数。
下面写一个我们自己的测试case脚本:
-- file: test_cjf.lua
-- A simple test case.
function prepare()
db_connect()
-- create table.
local query = [[
CREATE TABLE `sbtest_cjf` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
)
]]
db_query(query)
print("create table sbtest_cjf succ.")
-- insert 1000 records.
local i
for i = 1, 1000 do
query = [[
insert into sbtest_cjf(`id`, `k`, `c`, `pad`) values(
]] .. i .. ',' .. i + 1000 .. ", 'c', 'pad')"
db_query(query)
end
print("insert 1000 record to sbtest_cjf succ.")
return 0
end
function cleanup()
db_query('DROP TABLE IF EXISTS sbtest_cjf')
print('drop table sbtest_cjf succ.')
return 0
end
function event()
db_query('select * from sbtest_cjf where id = 1')
end
使用sysbench对上述test_cjf.lua进行测试:
$./src/sysbench ./tests/include/oltp_legacy/test_cjf.lua --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-user=root --mysql-password=xxx --mysql-db=dbtest --db-driver=mysql --report-interval=2 --threads=1 --time=10 prepare
sysbench 1.0.20-6cb07f3 (using bundled LuaJIT 2.1.0-beta2)
create table sbtest_cjf succ.
insert 1000 record to sbtest_cjf succ.
$./src/sysbench ./tests/include/oltp_legacy/test_cjf.lua --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-user=root --mysql-password=xxx --mysql-db=dbtest --db-driver=mysql --report-interval=2 --threads=1 --time=10 run
sysbench 1.0.20-6cb07f3 (using bundled LuaJIT 2.1.0-beta2)
Running the test with following options:
Number of threads: 1
Report intermediate results every 2 second(s)
Initializing random number generator from current time
Initializing worker threads...
Threads started!
[ 2s ] thds: 1 tps: 2182.39 qps: 2182.39 (r/w/o: 2182.39/0.00/0.00) lat (ms,95%): 0.55 err/s: 0.00 reconn/s: 0.00
[ 4s ] thds: 1 tps: 2323.41 qps: 2323.41 (r/w/o: 2323.41/0.00/0.00) lat (ms,95%): 0.47 err/s: 0.00 reconn/s: 0.00
[ 6s ] thds: 1 tps: 2299.49 qps: 2299.49 (r/w/o: 2299.49/0.00/0.00) lat (ms,95%): 0.47 err/s: 0.00 reconn/s: 0.00
[ 8s ] thds: 1 tps: 2304.50 qps: 2304.50 (r/w/o: 2304.50/0.00/0.00) lat (ms,95%): 0.48 err/s: 0.00 reconn/s: 0.00
SQL statistics:
queries performed:
read: 22804
write: 0
other: 0
total: 22804
transactions: 22804 (2279.41 per sec.)
queries: 22804 (2279.41 per sec.)
ignored errors: 0 (0.00 per sec.)
reconnects: 0 (0.00 per sec.)
General statistics:
total time: 10.0024s
total number of events: 22804
Latency (ms):
min: 0.40
avg: 0.44
max: 14.68
95th percentile: 0.53
sum: 9974.14
Threads fairness:
events (avg/stddev): 22804.0000/0.00
execution time (avg/stddev): 9.9741/0.00
$./src/sysbench ./tests/include/oltp_legacy/test_cjf.lua --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-user=root --mysql-password=xxx --mysql-db=dbtest --db-driver=mysql --report-interval=2 --threads=1 --time=10 cleanup
sysbench 1.0.20-6cb07f3 (using bundled LuaJIT 2.1.0-beta2)
drop table sbtest_cjf succ.
4. 参考文档
Lua教程:https://www.w3xue.com/manual/lua/Lua-Chinese.pdf
GDB入门:https://zhuanlan.zhihu.com/p/74897601
GDB命令:https://developer.aliyun.com/article/232348