web性能压力测试工具:WebBench详解

1.Webbench官方主页:

http://home.tiscali.cz/~cz210552/webbench.html

Webbench的官方介绍:

Web Bench is very simple tool for benchmarking WWW or proxy servers. Uses fork() for simulating multiple clients and can use HTTP/0.9-HTTP/1.1 requests. This benchmark is not very realistic, but it can test if your HTTPD can realy handle that many clients at once (try to run some CGIs) without taking your machine down. Displays pages/min and bytes/sec. Can be used in more aggressive mode with -f switch.

  1. Webbench能测试处在相同硬件上,不同服务的性能,也能测试不同硬件上同一个服务的运行状况。
  2. webbench的标准测试可以展示服务器的两项内容每秒钟相应请求数和每秒钟传输数据量。
  3. webbench具有静态页面的测试能力,能对动态页面(ASP,PHP,JAVA,CGI)进行测试;也支持对含有SSL的安全网站例如电子商务网站进行静态或动态的性能测试

2. webbench在运维工作中的作用:

压力测试在运维工作中有重要的作用。在一个网站上线之前,能承受的访问量、在高并发访问情况下的性能怎样,这些数据指标直接体现了系统的性能,压力测试可以给开发者一个对产品质量的评估。

但是,压力测试的结果与实际负载结果不会完全相同,就算压力测试工作做的再好,也不能保证100%和线上性能指标相同。面对这些问题,我们只能尽量去想方设法去模拟。所以,压力测试非常有必要,有了这些数据,我们就能对自己做维护的平台做到心中有数。

3.Webbench的使用:

web性能压力测试工具:WebBench详解_第1张图片

一般用到-c和-t两个参数:

像下面的例子可测试百度网站的性能情况:

webbench -c 300 -t 60 http://www.baidu.com/

-t表示测试的时间,-c表示并发访问网站的客户数。

测试运行的结果如下:

web性能压力测试工具:WebBench详解_第2张图片

返回的结果中有两个指标:

  1. pages/min:每分输出的页面数;
  2. bytes/sec:每秒传输的比特数;
  3. succeed和failed表示请求的成功数目和失败数目;

4.webbench的源码分析:

项目的源代码位于socket.c和webbench.c文件中,其他文档都是说明文档和用户编译的文档。

项目中涉及的知识点有:命令行参数解析(getopt_long)、信号处理(sigaction)、socket管道读写。

项目主要函数:

  • main提供程序的入口;
  • build_request构造HTTP请求;
  • bench派生子进程,父子进程间通过管道通信最后输出计算结果;
  • benchcore负责每个进程实际发起请求;
  • alarm_handler负责信号处理,在时钟结束时调用;
  • usage输出webbench命令的具体参数;

项目主要流程:

      1. 解析命令行参数,根据命令行指定参数设定变量,可以认为是初始化配置。

      2.根据指定的配置构造 HTTP 请求报文格式。

      3.开始执行 bench 函数,先进行一次 socket 连接建立与断开,测试是否可以正常访问。

      4.建立管道,派生根据指定进程数派生子进程。

      5.每个子进程调用 benchcore 函数,先通过 sigaction 安装信号,用 alarm 设置闹钟函数,接着不断建立 socket 进行通信,与服务器交互数据,直到收到信号结束访问测试。子进程将访问测试结果写进管道。

      6.父进程读取管道数据,汇总子进程信息,收到所有子进程消息后,输出汇总信息,结束。

流程图:

  web性能压力测试工具:WebBench详解_第3张图片

项目源码:

webbench.c:

/*
* (C) Radim Kolar 1997-2004
* This is free software, see GNU Public License version 2 for
* details.
*
* Simple forking WWW Server benchmark:
*
* Usage:
*   webbench --help
*
* Return codes:
*    0 - sucess
*    1 - benchmark failed (server is not on-line)
*    2 - bad param
*    3 - internal error, fork failed
* 
*/ 

#include "socket.c"
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* values */
volatile int timerexpired=0;
int speed=0;
int failed=0;
int bytes=0;

/* globals */
int http10=1; /* 0 - http/0.9, 1 - http/1.0, 2 - http/1.1 */
/* Allow: GET, HEAD, OPTIONS, TRACE */
#define METHOD_GET 0
#define METHOD_HEAD 1
#define METHOD_OPTIONS 2
#define METHOD_TRACE 3
#define PROGRAM_VERSION "1.5"
int method=METHOD_GET; //GET方式
int clients=1;         //仅模拟一个客户端
int force=0;           //等待响应
int force_reload=0;    //失败时重新请求
int proxyport=80;      //访问端口
char *proxyhost=NULL;  //代理服务器
int benchtime=30;      //模拟请求时间

/* internal */
int mypipe[2];              //管道
char host[MAXHOSTNAMELEN];  //网络地址
#define REQUEST_SIZE 2048
char request[REQUEST_SIZE]; //请求

static const struct option long_options[]=
{
    {"force",no_argument,&force,1},
    {"reload",no_argument,&force_reload,1},
    {"time",required_argument,NULL,'t'},
    {"help",no_argument,NULL,'?'},
    {"http09",no_argument,NULL,'9'},
    {"http10",no_argument,NULL,'1'},
    {"http11",no_argument,NULL,'2'},
    {"get",no_argument,&method,METHOD_GET},
    {"head",no_argument,&method,METHOD_HEAD},
    {"options",no_argument,&method,METHOD_OPTIONS},
    {"trace",no_argument,&method,METHOD_TRACE},
    {"version",no_argument,NULL,'V'},
    {"proxy",required_argument,NULL,'p'},
    {"clients",required_argument,NULL,'c'},
    {NULL,0,NULL,0}
};

/* prototypes */
static void benchcore(const char* host,const int port, const char *request);
static int bench(void);
static void build_request(const char *url);

static void alarm_handler(int signal)
{
    timerexpired=1;
}	

static void usage(void)
{
    fprintf(stderr,
            "webbench [option]... URL\n"
            "  -f|--force               Don't wait for reply from server.\n"
            "  -r|--reload              Send reload request - Pragma: no-cache.\n"
            "  -t|--time           Run benchmark for  seconds. Default 30.\n"
            "  -p|--proxy  Use proxy server for request.\n"
            "  -c|--clients          Run  HTTP clients at once. Default one.\n"
            "  -9|--http09              Use HTTP/0.9 style requests.\n"
            "  -1|--http10              Use HTTP/1.0 protocol.\n"
            "  -2|--http11              Use HTTP/1.1 protocol.\n"
            "  --get                    Use GET request method.\n"
            "  --head                   Use HEAD request method.\n"
            "  --options                Use OPTIONS request method.\n"
            "  --trace                  Use TRACE request method.\n"
            "  -?|-h|--help             This information.\n"
            "  -V|--version             Display program version.\n"
           );
}

int main(int argc, char *argv[])
{
    int opt=0;
    int options_index=0;
    char *tmp=NULL;
	//不带参数时,直接输出help信息
    if(argc==1)
    {
        usage();
        return 2;
    } 
	//getopt_long为命令行解析的库函数
    while((opt=getopt_long(argc,argv,"912Vfrt:p:c:?h",long_options,&options_index))!=EOF )
    {
        switch(opt)
        {
            case  0 : break;
            case 'f': force=1;break;
            case 'r': force_reload=1;break; 
            case '9': http10=0;break;
            case '1': http10=1;break;
            case '2': http10=2;break;
            case 'V': printf(PROGRAM_VERSION"\n");exit(0);
            case 't': benchtime=atoi(optarg);break;	     
            case 'p': 
            /* proxy server parsing server:port */
            tmp=strrchr(optarg,':');
            proxyhost=optarg;
            if(tmp==NULL)
            {
                break;
            }
            if(tmp==optarg)
            {
                fprintf(stderr,"Error in option --proxy %s: Missing hostname.\n",optarg);
                return 2;
            }
            if(tmp==optarg+strlen(optarg)-1)
            {
                fprintf(stderr,"Error in option --proxy %s Port number is missing.\n",optarg);
                return 2;
            }
            *tmp='\0';
            proxyport=atoi(tmp+1);break;
            case ':':
            case 'h':
            case '?': usage();return 2;break;
            case 'c': clients=atoi(optarg);break;
        }
    }
	//optind被getopt_long设置为命令行参数中未读物的下一个元素下标值
    if(optind==argc) {
        fprintf(stderr,"webbench: Missing URL!\n");
        usage();
        return 2;
    }
	//不能指定客户端数和请求时间为0
    if(clients==0) clients=1;
    if(benchtime==0) benchtime=30;
 
    /* Copyright */
    fprintf(stderr,"Webbench - Simple Web Benchmark "PROGRAM_VERSION"\n"
            "Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.\n"
            );
	//构造HTTP请求到request数组
    build_request(argv[optind]);
 
    // print request info ,do it in function build_request
    /*printf("Benchmarking: ");
 
    switch(method)
    {
        case METHOD_GET:
        default:
        printf("GET");break;
        case METHOD_OPTIONS:
        printf("OPTIONS");break;
        case METHOD_HEAD:
        printf("HEAD");break;
        case METHOD_TRACE:
        printf("TRACE");break;
    }
    
    printf(" %s",argv[optind]);
    
    switch(http10)
    {
        case 0: printf(" (using HTTP/0.9)");break;
        case 2: printf(" (using HTTP/1.1)");break;
    }
 
    printf("\n");
    */

    printf("Runing info: ");

    if(clients==1) 
        printf("1 client");
    else
        printf("%d clients",clients);

    printf(", running %d sec", benchtime);
    
    if(force) printf(", early socket close");
    if(proxyhost!=NULL) printf(", via proxy server %s:%d",proxyhost,proxyport);
    if(force_reload) printf(", forcing reload");
    
    printf(".\n");
    //开始压力测试
    return bench();
}

void build_request(const char *url)
{
    char tmp[10];
    int i;

    //bzero(host,MAXHOSTNAMELEN);
    //bzero(request,REQUEST_SIZE);
    memset(host,0,MAXHOSTNAMELEN);
    memset(request,0,REQUEST_SIZE);
	//判断应该使用的HTTP协议
    if(force_reload && proxyhost!=NULL && http10<1) http10=1;
    if(method==METHOD_HEAD && http10<1) http10=1;
    if(method==METHOD_OPTIONS && http10<2) http10=2;
    if(method==METHOD_TRACE && http10<2) http10=2;

	//填写method方法
    switch(method)
    {
        default:
        case METHOD_GET: strcpy(request,"GET");break;
        case METHOD_HEAD: strcpy(request,"HEAD");break;
        case METHOD_OPTIONS: strcpy(request,"OPTIONS");break;
        case METHOD_TRACE: strcpy(request,"TRACE");break;
    }

    strcat(request," ");
	//url合法性判断
    if(NULL==strstr(url,"://"))
    {
        fprintf(stderr, "\n%s: is not a valid URL.\n",url);
        exit(2);
    }
    if(strlen(url)>1500)
    {
        fprintf(stderr,"URL is too long.\n");
        exit(2);
    }
    if (0!=strncasecmp("http://",url,7)) 
    { 
		//仅支持HTTP协议
        fprintf(stderr,"\nOnly HTTP protocol is directly supported, set --proxy for others.\n");
        exit(2);
    }
    //找到主机名开始的地方
    /* protocol/host delimiter */
    i=strstr(url,"://")-url+3;
	//必须以/结束
    if(strchr(url+i,'/')==NULL) {
        fprintf(stderr,"\nInvalid URL syntax - hostname don't ends with '/'.\n");
        exit(2);
    }
    
    if(proxyhost==NULL)
    {
        /* get port from hostname */
        if(index(url+i,':')!=NULL && index(url+i,':')0)
        strcat(request,"User-Agent: WebBench "PROGRAM_VERSION"\r\n");
    if(proxyhost==NULL && http10>0)
    {
        strcat(request,"Host: ");
        strcat(request,host);
        strcat(request,"\r\n");
    }
 
    if(force_reload && proxyhost!=NULL)
    {
        strcat(request,"Pragma: no-cache\r\n");
    }
  
    if(http10>1)
        strcat(request,"Connection: close\r\n");
    
    /* add empty line at end */
    if(http10>0) strcat(request,"\r\n"); 
    
    printf("\nRequest:\n%s\n",request);
}

/* vraci system rc error kod */
static int bench(void)
{
    int i,j,k;	
    pid_t pid=0;
    FILE *f;

    /* check avaibility of target server */
    i=Socket(proxyhost==NULL?host:proxyhost,proxyport);
    if(i<0) { 
        fprintf(stderr,"\nConnect to server failed. Aborting benchmark.\n");
        return 1;
    }
    close(i);
    
    /* create pipe */
    if(pipe(mypipe))
    {
        perror("pipe failed.");
        return 3;
    }

    /* not needed, since we have alarm() in childrens */
    /* wait 4 next system clock tick */
    /*
    cas=time(NULL);
    while(time(NULL)==cas)
    sched_yield();
    */

    /* fork childs */
	//派生子进程
    for(i=0;i0)
            {
                /* fprintf(stderr,"Correcting failed by signal\n"); */
                failed--;
            }
            return;
        }
        //建立socket,进行http请求
        s=Socket(host,port);                          
        if(s<0) { failed++;continue;} 
        if(rlen!=write(s,req,rlen)) {failed++;close(s);continue;}
        if(http10==0) 
        if(shutdown(s,1)) { failed++;close(s);continue;}
        if(force==0) 
        {
            /* read all available data from socket */
            while(1)
            {
                if(timerexpired) break; 
                i=read(s,buf,1500);
                /* fprintf(stderr,"%d\n",i); */
                if(i<0) 
                { 
                    failed++;
                    close(s);
                    goto nexttry;
                }
                else
                if(i==0) break;
                else
                bytes+=i;
            }
        }
        if(close(s)) {failed++;continue;}
        speed++;
    }
}

socket.c源码:

/* $Id: socket.c 1.1 1995/01/01 07:11:14 cthuang Exp $
 *
 * This module has been modified by Radim Kolar for OS/2 emx
 */

/***********************************************************************
  module:       socket.c
  program:      popclient
  SCCS ID:      @(#)socket.c    1.5  4/1/94
  programmer:   Virginia Tech Computing Center
  compiler:     DEC RISC C compiler (Ultrix 4.1)
  environment:  DEC Ultrix 4.3 
  description:  UNIX sockets code.
 ***********************************************************************/
 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int Socket(const char *host, int clientPort)
{
    int sock;
    unsigned long inaddr;
    struct sockaddr_in ad;
    struct hostent *hp;
    //初始化地址
    memset(&ad, 0, sizeof(ad));
    ad.sin_family = AF_INET;
	//主机名转化为数字
    inaddr = inet_addr(host);
    if (inaddr != INADDR_NONE)
        memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
    else
    {
		//获取IP地址
        hp = gethostbyname(host);
        if (hp == NULL)
            return -1;
        memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
    }
    ad.sin_port = htons(clientPort);
    //建立socket
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
        return sock;
	//建立连接
    if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0)
        return -1;
    return sock;
}

参考:

https://blog.csdn.net/pony_maggie/article/details/62887547

https://blog.csdn.net/pony_maggie/article/details/62887547

你可能感兴趣的:(C++开源项目)