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.
- Webbench能测试处在相同硬件上,不同服务的性能,也能测试不同硬件上同一个服务的运行状况。
- webbench的标准测试可以展示服务器的两项内容:每秒钟相应请求数和每秒钟传输数据量。
- webbench具有静态页面的测试能力,能对动态页面(ASP,PHP,JAVA,CGI)进行测试;也支持对含有SSL的安全网站例如电子商务网站进行静态或动态的性能测试
2. webbench在运维工作中的作用:
压力测试在运维工作中有重要的作用。在一个网站上线之前,能承受的访问量、在高并发访问情况下的性能怎样,这些数据指标直接体现了系统的性能,压力测试可以给开发者一个对产品质量的评估。
但是,压力测试的结果与实际负载结果不会完全相同,就算压力测试工作做的再好,也不能保证100%和线上性能指标相同。面对这些问题,我们只能尽量去想方设法去模拟。所以,压力测试非常有必要,有了这些数据,我们就能对自己做维护的平台做到心中有数。
3.Webbench的使用:
一般用到-c和-t两个参数:
像下面的例子可测试百度网站的性能情况:
webbench -c 300 -t 60 http://www.baidu.com/
-t表示测试的时间,-c表示并发访问网站的客户数。
测试运行的结果如下:
返回的结果中有两个指标:
- pages/min:每分输出的页面数;
- bytes/sec:每秒传输的比特数;
- 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.父进程读取管道数据,汇总子进程信息,收到所有子进程消息后,输出汇总信息,结束。
项目源码:
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 <unistd.h>
#include <sys/param.h>
#include <rpc/types.h>
#include <getopt.h>
#include <strings.h>
#include <time.h>
#include <signal.h>
/* 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 <sec> Run benchmark for <sec> seconds. Default 30.\n"
" -p|--proxy <server:port> Use proxy server for request.\n"
" -c|--clients <n> Run <n> 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,':')<index(url+i,'/'))
{
strncpy(host,url+i,strchr(url+i,':')-url-i);
//bzero(tmp,10);
memset(tmp,0,10);
strncpy(tmp,index(url+i,':')+1,strchr(url+i,'/')-index(url+i,':')-1);
/* printf("tmp=%s\n",tmp); */
//设置端口
proxyport=atoi(tmp);
if(proxyport==0) proxyport=80;
}
else
{
strncpy(host,url+i,strcspn(url+i,"/"));
}
// printf("Host=%s\n",host);
strcat(request+strlen(request),url+i+strcspn(url+i,"/"));
}
else
{
// printf("ProxyHost=%s\nProxyPort=%d\n",proxyhost,proxyport);
strcat(request,url);
}
if(http10==1)
strcat(request," HTTP/1.0");
else if (http10==2)
strcat(request," HTTP/1.1");
strcat(request,"\r\n");
if(http10>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;i<clients;i++)
{
pid=fork();
if(pid <= (pid_t) 0)
{
/* child process or error*/
sleep(1); /* make childs faster */
break;
}
}
if( pid < (pid_t) 0)
{
fprintf(stderr,"problems forking worker no. %d\n",i);
perror("fork failed.");
return 3;
}
if(pid == (pid_t) 0)
{
/* I am a child */
//子进程发出实际请求
if(proxyhost==NULL)
benchcore(host,proxyport,request);
else
benchcore(proxyhost,proxyport,request);
/* write results to pipe */
//打开管道写
f=fdopen(mypipe[1],"w");
if(f==NULL)
{
perror("open pipe for writing failed.");
return 3;
}
/* fprintf(stderr,"Child - %d %d\n",speed,failed); */
fprintf(f,"%d %d %d\n",speed,failed,bytes);
fclose(f);
return 0;
}
else
{
//父进程打开管道读
f=fdopen(mypipe[0],"r");
if(f==NULL)
{
perror("open pipe for reading failed.");
return 3;
}
setvbuf(f,NULL,_IONBF,0);
speed=0;//传输速度
failed=0;//失败请求数
bytes=0;//传输字节数
while(1)
{
pid=fscanf(f,"%d %d %d",&i,&j,&k);
if(pid<2)
{
fprintf(stderr,"Some of our childrens died.\n");
break;
}
speed+=i;
failed+=j;
bytes+=k;
/* fprintf(stderr,"*Knock* %d %d read=%d\n",speed,failed,pid); */
//子进程是否读取完
if(--clients==0) break;
}
fclose(f);
//结果计算
printf("\nSpeed=%d pages/min, %d bytes/sec.\nRequests: %d susceed, %d failed.\n",
(int)((speed+failed)/(benchtime/60.0f)),
(int)(bytes/(float)benchtime),
speed,
failed);
}
return i;
}
void benchcore(const char *host,const int port,const char *req)
{
int rlen;
char buf[1500];
int s,i;
struct sigaction sa;
/* setup alarm signal handler */
//安装信号
sa.sa_handler=alarm_handler;
sa.sa_flags=0;
if(sigaction(SIGALRM,&sa,NULL))
exit(3);
//设置闹钟函数
alarm(benchtime); // after benchtime,then exit
rlen=strlen(req);
/* 收到信号则 timerexpired = 1 */
nexttry:while(1)
{
if(timerexpired)
{
if(failed>0)
{
/* 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 <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
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