sysbench原理剖析和实践

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

上一篇:MySQL数据库压测


下一篇:Postgresql使用coalesce实现类似oracle的NVL方法