Files
2016-05-04 00:26:27 -04:00

579 lines
13 KiB
C

#include <io.h>
#include <byte.h>
#include <str.h>
#include <fmt.h>
#include <scan.h>
#include <socket.h>
#include <errmsg.h>
#include <dns.h>
#include <ip6.h>
#include <textcode.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <buffer.h>
#include <fcntl.h>
#include <string.h>
#include "havealloca.h"
void usage() {
die(0,"usage: bench [-n requests] [-c concurrency] [-t timeout] [-k] [-K count]\n"
" [-C cookie-file] ([http://]host[:port]/uri|@host:port)");
}
unsigned long r[10];
unsigned long kaputt;
static int make_connection(char* ip,uint16 port,uint32 scope_id,int s) {
int v6=byte_diff(ip,12,V4mappedprefix);
if (v6) {
if (s==-1) {
s=socket_tcp6();
if (s==-1) return -1;
}
if (socket_connect6(s,ip,port,scope_id)==-1) {
if (errno==EAGAIN || errno==EINPROGRESS || errno==EISCONN)
return s;
++kaputt;
if (errno!=ECONNREFUSED && errno!=ECONNRESET)
carpsys("socket_connect6");
close(s);
return -1;
}
} else {
if (s==-1) {
s=socket_tcp4();
if (s==-1) return -1;
}
if (socket_connect4(s,ip+12,port)==-1) {
if (errno==EAGAIN || errno==EINPROGRESS || errno==EISCONN)
return s;
++kaputt;
if (errno!=ECONNREFUSED && errno!=ECONNRESET)
carpsys("socket_connect6");
close(s);
return -1;
}
}
return s;
}
buffer* cookies;
void cookiefile(const char* s) {
static buffer cookiebuffer;
static char cookiebuf[8192];
int fd;
if (!s) {
lseek(cookiebuffer.fd,0,SEEK_SET);
buffer_init(&cookiebuffer,read,cookiebuffer.fd,cookiebuf,sizeof cookiebuf);
} else {
fd=open(s,O_RDONLY);
if (fd==-1) die(1,"could not open cookie file \"",s,"\"!");
if (cookiebuffer.fd!=0) close(cookiebuffer.fd);
buffer_init(&cookiebuffer,read,fd,cookiebuf,sizeof cookiebuf);
cookies=&cookiebuffer;
}
}
int nextcookie(char* dest,unsigned long destlen) {
int len;
if (!cookies) return -1;
if ((len=buffer_getline(cookies,dest,destlen))) {
if (dest[len]!='\n')
die(0,"line too long: ",dest);
dest[len]=0;
} else {
cookiefile(0);
if ((len=buffer_getline(cookies,dest,destlen))) {
if (dest[len]!='\n')
die(0,"line too long: ",dest);
dest[len]=0;
} else
return -1;
}
return len;
}
int main(int argc,char* argv[]) {
char server[1024];
int* fds;
int* avail;
int* keepleft;
long long* expected;
unsigned long n=10000; /* requests */
unsigned long c=10; /* concurrency */
unsigned long t=0; /* time limit in seconds */
unsigned long k=0; /* keep-alive */
unsigned long K=1; /* keep-alive counter */
int report=0;
unsigned long long errors=0;
unsigned long long bytes=0;
int v=0;
unsigned long i,done;
uint16 port=80;
uint32 scope_id=0;
stralloc ips={0};
char* request,* krequest;
unsigned long rlen, krlen;
tai6464 first,now,next,last;
enum { SAME, REPLAY } mode;
char* hostname;
server[0]=0;
errmsg_iam("bench");
#ifndef __MINGW32__
signal(SIGPIPE,SIG_IGN);
#endif
for (;;) {
int i;
int ch=getopt(argc,argv,"n:c:t:kvK:C:r");
if (ch==-1) break;
switch (ch) {
case 'r':
report=1;
break;
case 'n':
i=scan_ulong(optarg,&n);
if (i==0) die(1,"could not parse -n argument \"",optarg,"\".\n");
break;
case 'c':
i=scan_ulong(optarg,&c);
if (i==0) die(1,"could not parse -c argument \"",optarg,"\".\n");
break;
case 't':
i=scan_ulong(optarg,&t);
if (i==0) die(1,"could not parse -t argument \"",optarg,"\".\n");
break;
case 'k':
k=1;
break;
case 'K':
i=scan_ulong(optarg,&K);
break;
case 'v':
v=1;
break;
case 'C':
cookiefile(optarg);
break;
case '?':
break;
default:
usage();
}
}
if (n<1 || c<1 || !argv[optind]) usage();
if (argv[optind][0]=='@') {
mode=REPLAY;
char* host;
n=(unsigned long)-1;
host=argv[optind]+1;
{
int tmp;
tmp=str_chr(host,'/');
if (host[tmp]) {
host[tmp]=0;
if (!scan_ushort(host+tmp+1,&port)) usage();
}
tmp=str_chr(host,'%');
if (host[tmp]) {
host[tmp]=0;
scope_id=socket_getifidx(host+tmp+1);
if (scope_id==0)
carp("warning: network interface \"",host+tmp+1,"\" not found.");
}
}
{
stralloc a={0};
stralloc_copys(&a,host);
if (dns_ip6(&ips,&a)==-1)
die(1,"could not find IP for \"",host,"\"!");
}
hostname=host;
request=krequest=0;
rlen=krlen=0;
} else {
char* host=argv[optind];
int colon;
int slash;
char* c;
mode=SAME;
if (byte_equal(host,7,"http://")) host+=7;
colon=str_chr(host,':');
slash=str_chr(host,'/');
if (host[0]=='[') { /* ipv6 IP notation */
int tmp;
++host;
--colon; --slash;
tmp=str_chr(host,']');
if (host[tmp]==']') host[tmp]=0;
if (host[tmp+1]==':') colon=tmp+1;
if (colon<tmp+1) colon=tmp+1+str_len(host+tmp+1);
}
if (colon<slash) {
host[colon]=0;
c=host+colon+1;
if (c[scan_ushort(c,&port)]!='/') usage();
*c=0;
}
host[colon]=0;
c=host+slash;
*c=0;
{
char* tmp=alloca(str_len(host)+1);
tmp[fmt_str(tmp,host)]=0;
host=tmp;
}
*c='/';
{
int tmp=str_chr(host,'%');
if (host[tmp]) {
host[tmp]=0;
scope_id=socket_getifidx(host+tmp+1);
if (scope_id==0)
carp("warning: network interface \"",host+tmp+1,"\" not found.");
}
}
{
stralloc a={0};
stralloc_copys(&a,host);
if (dns_ip6(&ips,&a)==-1)
die(1,"could not find IP for \"",host,"\"!");
}
request=alloca(1300+str_len(host)+3*str_len(c));
krequest=alloca(1300+str_len(host)+3*str_len(c));
{
int i,j;
i=fmt_str(request,"GET ");
i+=fmt_urlencoded(request+i,c,str_len(c));
i+=fmt_str(request+i," HTTP/1.0\r\nHost: ");
i+=fmt_str(request+i,host);
i+=fmt_str(request+i,":");
i+=fmt_ulong(request+i,port);
i+=fmt_str(request+i,"\r\nUser-Agent: bench/1.0\r\nConnection: ");
j=i;
i+=fmt_str(request+i,"close\r\n\r\n");
rlen=i; request[rlen]=0;
byte_copy(krequest,rlen,request);
i=j+fmt_str(krequest+j,"keep-alive\r\n\r\n");
krlen=i; krequest[krlen]=0;
}
hostname=host;
}
fds=alloca(c*sizeof(*fds));
avail=alloca(c*sizeof(*avail));
expected=alloca(c*sizeof(*expected));
keepleft=alloca(c*sizeof(*keepleft));
last.sec.x=23;
if (!k) K=1;
for (i=0; i<c; ++i) { fds[i]=-1; avail[i]=1; keepleft[i]=K; }
taia_now(&first);
for (done=0; done<n; ) {
if (t) {
/* calculate timeout */
taia_now(&now);
if (now.sec.x != last.sec.x) {
byte_copy(&last,sizeof(now),&now);
byte_copy(&next,sizeof(now),&now);
next.sec.x += t;
while ((i=io_timeouted())!=-1) {
unsigned long j;
char numbuf[FMT_ULONG];
numbuf[fmt_ulong(numbuf,i)]=0;
carp("timeout on fd ",numbuf,"!");
j=(unsigned long)io_getcookie(i);
io_close(i);
avail[j]=1;
fds[j]=-1;
}
}
}
/* first, fill available connections */
for (i=0; i<c; ++i)
if (avail[i]==1 && fds[i]==-1) {
fds[i]=make_connection(ips.s,port,scope_id,-1);
if (fds[i]==-1) diesys(1,"socket/connect");
avail[i]=2;
if (io_fd_canwrite(fds[i])==0) diesys(1,"io_fd");
io_setcookie(fds[i],(void*)i);
// io_wantread(fds[i]);
io_wantwrite(fds[i]);
}
if (t)
io_waituntil(next);
else
io_wait();
/* second, see if we can write on a connection */
while ((i=io_canwrite())!=-1) {
int j;
j=(unsigned long)io_getcookie(i);
if (avail[j]==2) {
if (make_connection(ips.s,port,scope_id,i)==-1) {
++errors;
if (v) write(1,"!",1);
io_close(i);
avail[j]=1;
fds[j]=-1;
continue;
}
}
{
char* towrite;
int writelen;
if (mode==REPLAY) {
static long lines;
char line[1024];
char req[2048];
int len;
int i;
char* c;
char* host;
int hlen;
if ((len=buffer_getline(buffer_0,line,sizeof(line)))) {
++lines;
if (line[len]!='\n')
die(0,"line too long: ",line);
line[len]=0;
c=line;
if (str_start(line,"http://")) c+=7;
if (c[0]=='/') {
host=hostname;
hlen=strlen(hostname);
} else {
host=c;
c+=(hlen=str_chr(c,'/'));
}
if (!*c)
c="/";
i=fmt_str(req,"GET ");
i+=fmt_urlencoded(req+i,c,str_len(c));
i+=fmt_str(req+i," HTTP/1.0\r\nHost: ");
byte_copy(req+i,hlen,host); i+=hlen;
i+=fmt_str(req+i,":");
i+=fmt_ulong(req+i,port);
if (cookies) {
int j;
i+=fmt_str(req+i,"\r\n");
j=nextcookie(req+i,sizeof(req)-i-100);
if (j!=-1) i+=j; else i-=2;
}
i+=fmt_str(req+i,"\r\nUser-Agent: bench/1.0\r\nConnection: ");
i+=fmt_str(req+i,keepleft[j]>1?"keep-alive\r\n\r\n":"close\r\n\r\n");
req[i]=0;
towrite=req;
writelen=i;
} else {
n=done;
break;
}
} else {
if (keepleft[j]>1) {
towrite=krequest;
writelen=krlen;
} else {
towrite=request;
writelen=rlen;
}
if (cookies) {
int i=writelen-2;
int j=nextcookie(towrite+i,900);
if (j!=-1) i+=j;
i+=fmt_str(towrite+i,"\r\n\r\n");
writelen=i;
}
}
if (io_trywrite(i,towrite,writelen)!=writelen) {
++errors;
if (v) write(1,"-",1);
io_close(i);
avail[j]=1;
fds[j]=-1;
continue;
}
}
io_dontwantwrite(i);
io_wantread(i);
expected[j]=-1;
if (v) write(1,"+",1);
}
/* third, see if we got served */
while ((i=io_canread())!=-1) {
char buf[8193];
int l,j;
buf[8192]=0;
j=(unsigned long)io_getcookie(i);
if ((l=io_tryread(i,buf,sizeof(buf)-1))<=0) {
if (l==0) { /* EOF. Mhh. */
if (expected[j]>0) {
++errors;
if (v) write(1,"-",1); /* so whine a little */
}
if (expected[j]==-2)
++done;
io_close(i);
avail[j]=1;
fds[j]=-1;
} else if (l==-3) {
++errors;
if (v) write(1,"!",1);
// carpsys("read");
}
} else {
bytes+=l;
if (v) write(1,".",1);
/* read something */
if (expected[j]==-1) { /* expecting header */
int k;
/* OK, so this is a very simplistic header parser. No
* buffering. At all. We expect the Content-Length header to
* come in one piece. */
if (l>10 && !memcmp(buf,"HTTP/1.",7)) {
if (buf[9]>='0' && buf[9]<='9')
r[buf[9]-'0']++;
else {
write(1,buf,15); write(1,"\n",1);
}
}
expected[j]=-2;
if (!done) {
for (k=0; k<l; ++k)
if (str_start(buf+k,"\nServer: ")) {
char* tmp=buf+(k+=9);
for (; k<l; ++k)
if (buf[k]=='\r') break;
k=buf+k-tmp;
if (k>sizeof(server)-1) k=sizeof(server)-1;
byte_copy(server,k,tmp);
server[k]=0;
break;
}
}
for (k=0; k<l; ++k) {
if (str_start(buf+k,"\nContent-Length: ")) {
k+=17;
if (buf[k+scan_ulonglong(buf+k,(unsigned long long*)expected+j)] != '\r')
die(1,"parse error in HTTP header!");
} else if (str_start(buf+k,"\r\n\r\n"))
break;
}
if (expected[j]>0) {
if (l-(k+4)>expected[j])
expected[j]=0;
else
expected[j]-=l-(k+4);
}
} else if (expected[j]==-2) {
/* no content-length header, eat everything until EOF */
} else {
if (l>expected[j]) {
carp("got more than expected!");
expected[j]=0;
} else
expected[j]-=l;
}
if (expected[j]==0) {
++done; /* one down! */
avail[j]=1;
// printf("fd %d: keepleft[%d]=%d\n",i,j,keepleft[j]);
if (keepleft[j]>1) {
--keepleft[j];
io_dontwantread(i);
io_wantwrite(i);
expected[j]=0;
} else {
keepleft[j]=K;
io_close(i);
fds[j]=-1;
}
}
}
}
}
taia_now(&now);
taia_sub(&now,&now,&first);
{
char a[FMT_ULONG];
char b[FMT_ULONG];
char C[FMT_ULONG];
char d[FMT_ULONG];
char e[FMT_ULONG];
char f[FMT_ULONG];
char g[FMT_ULONG];
char h[FMT_ULONG];
char i[FMT_ULONG];
char j[FMT_ULONG];
unsigned long long l;
a[fmt_ulong(a,now.sec.x)]=0;
b[fmt_ulong0(b,(now.nano%1000000000)/100000,4)]=0;
C[fmt_ulong(C,done)]=0;
d[fmt_ulonglong(d,errors)]=0;
e[fmt_ulonglong(e,bytes)]=0;
/* let's say bytes = 10 MB, time = 1.2 sec.
* then we want 10*1024*1024/1.2 == 8 MB/sec */
l = (now.sec.x * 1024) + now.nano/976562;
if (l) {
int i;
l=bytes/l;
if (report)
i=fmt_ulong(f,l);
else {
i=fmt_humank(f,l*1024);
i+=fmt_str(f+i,"iB/sec");
}
f[i]=0;
} else
strcpy(f,"n/a");
l = (now.sec.x * 1000) + now.nano/1000000;
l = l ? (done*10000) / l : 0;
g[fmt_ulong(g,l/10)]=0;
h[fmt_ulong(h,c)]=0;
i[fmt_ulong(i,K)]=0;
j[fmt_ulong(j,kaputt)]=0;
if (server[0]) msg("Server: ",server);
if (report) {
errmsg_iam(0);
msg("req\terr\tconcur\tkeep\tkbytes\tsec\ttput\tr/s\treset");
msg(C,"\t",d,"\t",h,"\t",i,"\t",e,"\t",a,".",b,"\t",f,"\t",g,"\t",j);
} else {
msg(C," requests, ",d," errors.");
msg(e," bytes in ",a,".",b," seconds.");
msg("Throughput: ",f);
msg("Requests per second: ",g);
msg("Connection refused/reset by peer: ",j);
}
{
int i;
for (i=0; i<9; ++i) {
a[fmt_ulong(a,r[i])]=0;
b[0]=i+'0'; b[1]=0;
msg(b,"xx: ",a);
}
}
}
return 0;
}