Linux下四款Web服务器压力测试工具(http_load、webbench、ab、siege)
其中最针意siege测试工具、可以模拟多个并发用户进行测试。
一、功能简介
“Siege”意为围攻、包围。是一款使用纯C语言编写的开源WEB压测工具,适合在GNU/Linux上运行,具有较强的可移植性。Siege使用多线程实现,支持随机访问多个URL,可以通过控制并发数、总请求数(or压测时间)来实现对web服务的压测。
Siege支持http,https,ftp三种请求方式,支持GET和POST方法,压测方式为同步压测。
因为编写http client case需要压测、所以参考阅读此代码,还是有不少借鉴的地方。
二、功能使用
下载链接为:http://download.joedog.org/siege/
$ tar –xzvf siege-latest.tar.gz
$ cd siege-4.0.2
$ ./config
$ make
$ make install
执行:siege [options] 或者 siege [options] URL其中options可选项有:
常用命令 siege -c 1000 http://192.168.10.120:12306/test.dat -t60s
(设定并发数及测试时间、对于url可以定义一个urls.txt文件写入多条url记录)、测试结果:
先借了一张程序的流程图、呵呵(省得画,这张挺好的,引自http://www.cnblogs.com/xuning/p/4060166.html):
三、核心原理分析
从main函数分析@main.c初始化:
int main(int argc, char *argv[])
// 信号、参数配置、测试url准备工作
__signal_setup();
__config_setup(argc, argv);
__urls_setup();
// 建立测试BROWSER数据结构,根据并发用户数确定
for (i = 0; i < my.cusers; i++) {
BROWSER B = new_browser(i);
browser_set_urls(B, urls);
...
}
// 根据用户数建立线程池,每个用户独占一个线程进行压力测度
crew = new_crew(my.cusers, my.cusers, FALSE);
// 控制线程、用于接收SIGTERM信号、结束所有压测任务线程
pthread_create(&cease, NULL, (void*)sig_handler, (void*)crew);
// 计时线程测试时间、到时发送SIGTERM信号 -t xxx
pthread_create(&timer, NULL, (void*)siege_timer, (void*)cease);
// 测试统计数据结构
data = new_data();
data_set_start(data);
//
for (i = 0; i < my.cusers && crew_get_shutdown(crew) != TRUE; i++) {
BROWSER B = (BROWSER)array_get(browsers, i);
// 将BROWSER的start()函数加入压测任务,最重要的函数!!!
result = crew_add(crew, (void*)start, B);
}
// 等待所有压测线程结束
crew_join(crew, TRUE, &status);
// 打印测试结果
fprintf(stderr, "Elapsed time:\t\t%12.2f secs\n", data_get_elapsed(data));
fprintf(stderr, "Data transferred:\t%12.2f MB\n", data_get_megabytes(data)); /*%12llu*/
fprintf(stderr, "Response time:\t\t%12.2f secs\n", data_get_response_time(data));
fprintf(stderr, "Transaction rate:\t%12.2f trans/sec\n", data_get_transaction_rate(data));
fprintf(stderr, "Throughput:\t\t%12.2f MB/sec\n", data_get_throughput(data));
fprintf(stderr, "Concurrency:\t\t%12.2f\n", data_get_concurrency(data));
fprintf(stderr, "Successful transactions:%12u\n", data_get_code(data));
...
}
通过以上主要函数的注解、就能对siege的主要代码处理逻辑有个基本了解。
现在对于重点的代码作个简要的分析:
1、压测线程 CREW@crew.c
CREW是用来统一管理所有压测线程的结构体,它在主函数中被声明,因此可以被所有线程共享。
最重要的几个函数:
CREW new_crew(int size, int maxsize, BOOLEAN block); // 创建线程池
BOOLEAN crew_add(CREW this, void (*routine)(), void *arg); // 加入线程执行函数
BOOLEAN crew_join(CREW this, BOOLEAN finish, void **payload); // 等待线程结束
用于管理所有压测线程的结构体:
struct CREW_T
{
int size; //目标并发数目,即压测线程个数
int maxsize; //最大并发数目,即压测线程个数
int cursize; //目前的可用并发数,压测中时这个数字随压测线程实时变化
int total; //实际启动的并发数
WORK *head; //压测任务链表头部
WORK *tail; //压测任务链表尾部
BOOLEAN block; //当已经达到最大并发时,则不准再添加新的压测线程
BOOLEAN closed; //压测线程是否已经关闭
BOOLEAN shutdown; //压测线程是否应该停止了
pthread_t *threads; //长度为size的数组,存储线程号
pthread_mutex_t lock; //修改本结构体都要先加锁
pthread_cond_t not_empty; //用于表示cursize不为0的条件
pthread_cond_t not_full; //用于表示cursize不等于maxsize的条件
pthread_cond_t empty; //用于表示cursize等于0的条件
};
重点看一下压测线程,共有size个,取决于命令行 -c 后面的数字
private void *crew_thread(void *crew)
{
int c;
WORK *workptr;
CREW this = (CREW)crew;
while (TRUE) {
if ((c = pthread_mutex_lock(&(this->lock))) != 0) {
NOTIFY(FATAL, "mutex lock");
}
// 如果目前可用并发数cursize是空的,则等待。刚开始创建时都停留在此处。
while ((this->cursize == 0) && (!this->shutdown)) {
if ((c = pthread_cond_wait(&(this->not_empty), &(this->lock))) != 0)
NOTIFY(FATAL, "pthread wait");
}
// 线程停止,则释放锁,退出,这里是唯一可以停止压测的地方
if (this->shutdown == TRUE) {
if ((c = pthread_mutex_unlock(&(this->lock))) != 0) {
NOTIFY(FATAL, "mutex unlock");
}
pthread_exit(NULL);
}
//取出第一个节点上的压测程序
workptr = this->head;
this->cursize--;
if (this->cursize == 0) {
this->head = this->tail = NULL;
} else {
this->head = workptr->next;
}
if ((this->block) && (this->cursize == (this->maxsize - 1))) {
if ((c = pthread_cond_broadcast(&(this->not_full))) != 0) {
NOTIFY(FATAL, "pthread broadcast");
}
}
//现在并发量如果为0,唤醒empty condition
if (this->cursize == 0) {
if ((c = pthread_cond_signal(&(this->empty))) != 0){
NOTIFY(FATAL, "pthread signal");
}
}
if ((c = pthread_mutex_unlock(&(this->lock))) != 0) {
NOTIFY(FATAL, "pthread unlock");
}
//这里才真正在执行压测函数
(*(workptr->routine))(workptr->arg);
xfree(workptr);
}
return(NULL);
}
2、测试对象 BROWSER@browser.c
每个压测线程都会维护属于自己的一份BROWSER,他们共同构成一个长度为n的数组。
typedef struct work
{
void (*routine)();
void *arg;
struct work *next;
} WORK;
这里routine是一个函数指针,而arg是要传给前面函数的参数。整个压测任务由一个单向链表来存储在CREW中。
每个BROWSER通过BOOLEAN crew_add(CREW this, void (*routine)(), void *arg)函数加入压测函数:
那void *start(BROWSER this)@browser.c函数:
void *start(BROWSER this){
/**
* This is the initial request from the command line
* or urls.txt file. If it is text/html then it will
* be parsed in __http request function.
*/
URL tmp = array_get(this->urls, y); //选择一个url
if (tmp != NULL && url_get_hostname(tmp) != NULL) {
this->auth.bids.www = 0; /* reset */
if ((ret = __request(this, tmp))==FALSE) { //访问该url
__increment_failures();
}
}
}
private BOOLEAN __request(BROWSER this, URL U) {
this->conn->scheme = url_get_scheme(U); // 这里根据url头分析协议
switch (this->conn->scheme) {
case FTP:
return __ftp(this, U);
case HTTP:
case HTTPS:
default:
return __http(this, U);
}
}
这里主要介绍http的处理流程:
private BOOLEAN __http(BROWSER this, URL U){
__init_connection(this, U);
/**
* write to socket with a GET/POST/PUT/DELETE/HEAD
*/
if (url_get_method(U) == POST) {
http_post(this->conn, U);
}else {
http_get(this->conn, U);
}
/**
* read from socket and collect statistics.
*/
http_read_headers(this->conn, U);
/**
* read http body data
*/
bytes = http_read(this->conn, resp);
...
}
其中数据接收的主函数、根据content length将数据全部收齐:
ssize_t http_read(CONN *C, RESPONSE resp)
if (C->content.length > 0) {
length = C->content.length;
ptr = xmalloc(length+1);
do {
if ((n = socket_read(C, ptr, length)) == 0) {
break;
}
bytes += n;
} while (bytes < length);
}
}
ok、源代码基本分析得此就结束了、虽然总共有13000多行代码,但最核心的内容且清楚整个程序的设计原理即可。
比如线程池、统计及http get/post及重定向等代码都可以移植到自已的程序中使用。