Siege web 压测工具代码分析

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及重定向等代码都可以移植到自已的程序中使用。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值