原本打算用mysql自带的mysqlslap做压力测试,可惜这工具不给力,可控制的地方不多,尤其不能够产生随机的测试语句。遂改用super smack。貌似它风评还不错。
安装
1.从网上下载tar.gz的安装包。http://vegan.net/tony/supersmack/ 我下载到的最新版本是1.3。
2.解压后运行./configure; make; make install
./configure的时候有些参数值得注意:
a) --with-mysql (支持mysql)
b) --with-pgsql (支持postgresql)
c) --with-mysql-lib=/mysql/lib (设置mysql lib库的路径)
d) --with-mysql-include=/mysql/include (设置mysql include的路径)
3.安装过程中可能会碰见这个错误:
办法是编译之前修改文件super-smack-1.3/src/query.cc
第193行:
< int len = 0, num_recs = 0;
修改成:
> long len = 0; int num_recs = 0;
第199,200行
< int str_len = (*i).first.length();
< if((unsigned)p + str_len + 3 *sizeof(int) < (unsigned)p_end )
修改成:
> long str_len = (*i).first.length();
> if((long)p + str_len + 3 *sizeof(int) < (long)p_end )
第219行
< len = (unsigned)p - (unsigned)buf;
修改成:
> len = (long)p - (long)buf;
(以上修改方法摘抄自网上的一篇blog,可惜link我已经找不到了,真不是我抄袭)
4.安装完成后,可以用自带的配置文件测试一下:
super-smack -d mysql ./smacks/select-key.smack 2 5
2 表示启动2个客户端,5 则表示每个客户端轮询5次。
测试过程可能会碰到一些问题(比如我碰到过找不到/tmp/mysql.sock),这是因为select-key.smack中一些参数设置和实际不符。修改一下就好了。
结果分析
[root@vm189 mysqltest]# super-smack -d mysql ./select_key.smack 20 100
Query Barrel Report for client smacker1
connect: max=0ms min=0ms avg= 0ms from 20 clients
Query_type num_queries max_time min_time q_per_s
select_index 2000 4910 147 6.59
很容易理解。结果包括一共执行了多少次查询(num_queries),最大的一次查询时间(max_time)是4910ms,耗时最少的一次查询是147ms,每秒完成查询(min_time)6.59次
定义自己的测试配置文件
要做自定义的MySQL测试,当然就必须编辑自己的配置文件(.smack)。Super Smack之所以很灵活,就在于可以在它的配置文件中自定义很多的东西。当然,随着而来的就是配置文件的相对复杂。理解它的配置文件的结构,是学会用Super Smack的关键所在。
下文将以select-key.smack为例,讲解.smack配置文件的结构。
网上也有一些相关的link:http://imysql.cn/docs/High_Performance_MySQL/0596003064/hpmysql-CHP-3-SECT-3.html
client "admin"
{
user "root";
host "localhost";
db "test";
pass "";
socket "/data0/mysql/mysql.sock"; // this only applies to MySQL and is
// ignored for PostgreSQL
}
这部分定义了Super Smack连接数据库需要用到的一些信息,比如用户名,机器名,数据库名,密码等等。很容易理解,不解释。
// ensure the table exists and meets the conditions
table "http_auth"
{
client "admin"; // connect with this client
// if the table is not found or does not pass the checks, create it
// with the following, dropping the old one if needed
create "create table http_auth
(username char(25) not null primary key,
pass char(25),
uid integer not null,
gid integer not null
)";
min_rows "90000"; // the table must have at least that many rows
data_file "words.dat"; // if the table is empty, load the data from
//this file
gen_data_file "gen-data -n 90000 -f %12-12s%n,%25-25s,%n,%d";
// if the file above does not exist, generate it with the above shell command
// you can replace this command with anything that prints comma-delimited
// data to stdout, just make sure you have the right number of columns
}
这里定义了要进行测试的数据库表的信息。包括:
创建这个表所使用的sql语句。如果Super Smack发现数据库中没有这个表的话,会使用这条sql语句创建表。
注意,这里设置了client "admin",表明是用前面定义的admin这个client来创建表。
min_rows这个参数是一个对表的约束,要求表内的记录必须达到的数量。例子中的值是90000,表示表中必须要有90000条数据。
“ data_file "words.dat"; ”是定义了一个数据文件,这个文件中的每一行都可以作为表中的一条记录。如果表http_auth没有达到90000条数据,那么Super Smack会从words.dat中读取数据然后插入到表http_auth中。
gen_data_file "gen-data -n 90000 -f %12-12s%n,%25-25s,%n,%d"; -- 自动生成测试数据,如果words.dat为空的话,Super Smack使用这条语句生成测试数据然后写入到words.dat中。
gen-data是安装Super Smack后提供的一个产生随机数据的小程序。下面解读一下它的命令格式:
-n 90000表示生成90000条记录;
-f后面跟的是记录的格式;
%S表示产生字符串,%12-12s表示产生的字符串长度在12与12之间;
%D表示生成数字,%d生成随机数;
%N %n表示可以从1开始递增的数值,可用于表中的主键,比如第一条记录的%n就会被1代替,以此类推。
上面的这句“gen-data -n 90000 -f %12-12s%n,%25-25s,%n,%d” 输出的每行是大概这样的:
josamibdnjdb3,eyhkbsombltouujdrbwcrrcgb,3,485560280
qwdqweqwdwev4,regergergftyyujeqwqwecdfr,4,239013239
rhbtjrbtywlf5,edeffkergpqqmcxvkrtmbhwer,5,233021393
默认gen-data的输出都是打印了标准输出上,可以用重定向让gen-data的输出结果写入到文件中。
需要指出的是,这里采用的例子select-key.smack是对一个表做查询测试,所以要求table中必须首先有一定量的数据。如果想做插入的测试,完全可以将min_rows设置成0,这样,Super Smack也就不需要调用gen_data_file产生数据。
//define a dictionary
dictionary "word"
{
type "rand"; // words are retrieved in random order
source_type "file"; // words come from a file
source "words.dat"; // file location
delim ","; // take the part of the line before ,
file_size_equiv "45000"; // if the file is greater than this
//divive the real file size by this value obtaining N and take every Nth
//line skipping others. This is needed to be able to target a wide key
// range without using up too much memory with test keys
}
//define a query
query "select_by_username"
{
query "select * from http_auth where username = '$word'";
// $word will be substitute with the read from the 'word' dictionary
type "select_index";
// query stats will be grouped by type
has_result_set "y";
// the query is expected to return a result set
parsed "y";
// the query string should be first processed by super-smack to do
// dictionary substitution
}
dictionary和query组合在一起,定义了需要的测试查询语句。
一条普通的查询语句,比如:select * from http_auth where username = 'test'; 它其实是由一个模板和一个或者几个参数构成的。select * from http_auth where username = '$word'就是这条语句的模板,而$word = test则是设置参数。
query部分定义了查询语句的模板,而dictionary则产生随机的各种参数。
query的结构很简单,最重要的就是“ query "select * from http_auth where username = '$word'"; ”,这定义了查询的模板;
dictionary的结构则非常的灵活,它可以有多种配置:
source_type "file"; -- 表示这个dictionary是一个文件的形式;
source "words.dat"; -- 表示文件名为words.dat;
delim ","; -- 表示words.dat中第一个","之前的部分是这个dictionary的内容;
type:rand -- 表示随机的从中抽取值;
把它们综合起来理解。
之前看过,words.dat的数据是这样的:
josamibdnjdb3,eyhkbsombltouujdrbwcrrcgb,3,485560280
qwdqweqwdwev4,regergergftyyujeqwqwecdfr,4,239013239
rhbtjrbtywlf5,edeffkergpqqmcxvkrtmbhwer,5,233021393
那么,这里定义的dictionary其实是读取word.dat,并将每一行的第一个字符串(比如josamibdnjdb3)作为字典内的元素,再和query组合起来,就变成了实际的查询语句:
select * from http_auth where username = 'qwdqweqwdwev4'
select * from http_auth where username = 'rhbtjrbtywlf5'
select * from http_auth where username = 'josamibdnjdb3'
type除了可以定义为rand,还可以定义为
seq,表示Values are used sequentially;
unique,Generate unique values using the same method as gen-data;
source_type除了可以定义为 file,还可以定义为
list,表示a user-supplied list of words, comma-separated;
template,表示the format to use when type is unique. For example, "jzawodn_%07d" generates values composed of jzawodn_ and a seven-digit number
举例来说,可以这样定义:
dictionary "gender" {
type: rand
source_type "list";
source "male,female";
}
它表示这个gender的dictionary只有两个值,要么是male,要么是female
dictionary "name"
{
type "unique";
source_type "template";
source "%15s";
}
表示name这个dictionary是一个长度为15的字符串,Super Smack将用gen-data命令产生这个字符串。
很多时候,如果你的查询模板需要多个参数的话,那么你可能需要准备多个dictionary。比如要产生如 select * from tablename where age < 20 AND id = 1234这样的查询,就需要两个dictionary,一个是age,一个是id。
当然,还有一种讨巧的方案,只需要生成一个dictionary,定义它的每一行是这样的:
Age < *** AND id = ***
而对应的query模板就是
select * from tablename where dictionary;
// define database client type
client "smacker1"
{
user "test"; // connect as this user
pass ""; // use this password
host "localhost"; // connect to this host
db "test"; // switch to this database
socket "/data0/mysql/mysql.sock"; // this only applies to MySQL and is
// ignored for PostgreSQL
query_barrel "2 select_by_username"; // on each round,
// run select_by_username query 2 times
}
main
{
smacker1.init(); // initialize the client
smacker1.set_num_rounds($2); // second arg on the command line defines
// the number of rounds for each client
smacker1.create_threads($1);
// first argument on the command line defines how many client instances
// to fork. Anything after this will be done once for each client until
// you collect the threads
smacker1.connect();
// you must connect after you fork
smacker1.unload_query_barrel(); // for each client fire the query barrel
// it will now do the number of rounds specified by set_num_rounds()
// on each round, query_barrel of the client is executed
smacker1.collect_threads();
// the master thread waits for the children, each child reports the stats
// the stats are printed
smacker1.disconnect();
// the children now disconnect and exit
}
这里又定义了一个client,它是执行查询语句所用到的client(最早的client是创建表的client,这两个client权限应该不一样。)
query_barrel "2 select_by_username"; -- 表示这个client在一次循环中执行select_by_username这个查询两次;
main中则定义了Super Smack具体的执行流程。很简单,不解释。