1 #include "socket.h"
2 #include "byte.h"
3 #include "dns.h"
4 #include "buffer.h"
5 #include "scan.h"
6 #include "ip6.h"
7 #include "str.h"
8 #include "fmt.h"
9 #include "ip4.h"
10 #include "textcode.h"
11 #include <sys/types.h>
12 #include <unistd.h>
13 #include <signal.h>
14 #include <sys/time.h>
15 #include <sys/resource.h>
16 #include <stdlib.h>
17 #include <errno.h>
18 #include "havealloca.h"
19 #include "ndelay.h"
20 
carp(const char * routine)21 static void carp(const char* routine) {
22   buffer_flush(buffer_1);
23   buffer_puts(buffer_2,"httpbench: ");
24   buffer_puts(buffer_2,routine);
25   buffer_puts(buffer_2,": ");
26   buffer_puterror(buffer_2);
27   buffer_putnlflush(buffer_2);
28 }
29 
panic(const char * routine)30 static void panic(const char* routine) {
31   carp(routine);
32   exit(111);
33 }
34 
35 uint16 bindport=0;
36 
make_connection(char * ip,uint16 port,uint32 scope_id)37 static int make_connection(char* ip,uint16 port,uint32 scope_id) {
38   int v6=byte_diff(ip,12,V4mappedprefix);
39   int s;
40   if (v6) {
41     s=socket_tcp6();
42     if (s==-1)
43       panic("socket_tcp6()");
44     ndelay_off(s);
45     if (bindport) {
46       for (;;) {
47 	int r=socket_bind6_reuse(s,V6any,bindport,0);
48 	if (++bindport<1024) bindport=1024;
49 	if (r==0) break;
50 	if (errno!=EADDRINUSE)
51 	  panic("socket_bind6");
52       }
53     }
54     if (socket_connect6(s,ip,port,scope_id)==-1) {
55       carp("socket_connect6");
56       close(s);
57       return -1;
58     }
59   } else {
60     s=socket_tcp4();
61     if (s==-1)
62       panic("socket_tcp4()");
63     ndelay_off(s);
64     if (bindport) {
65       for (;;) {
66 	int r=socket_bind4_reuse(s,V6any,bindport);
67 	if (++bindport<1024) bindport=1024;
68 	if (r==0) break;
69 	if (errno!=EADDRINUSE)
70 	  panic("socket_bind4");
71       }
72     }
73     if (socket_connect4(s,ip+12,port)==-1) {
74       carp("socket_connect4");
75       close(s);
76       return -1;
77     }
78   }
79   return s;
80 }
81 
readanswer(int s,int measurethroughput)82 static int readanswer(int s,int measurethroughput) {
83   char buf[8192];
84   int i,j,body=-1,r;
85   unsigned long rest;
86   unsigned long done=0;
87   struct timeval a,b;
88   i=0;
89   while ((r=read(s,buf+i,sizeof(buf)-i)) > 0) {
90     i+=r;
91     for (j=0; j+3<i; ++j) {
92       if (buf[j]=='\r' && buf[j+1]=='\n' && buf[j+2]=='\r' && buf[j+3]=='\n') {
93 	body=j+4;
94 	break;
95       }
96     }
97     if (body!=-1) {
98       if (byte_diff(buf,7,"HTTP/1.")) {
99 	buffer_putsflush(buffer_2,"invalid HTTP response!\n");
100 	return -1;
101       }
102       break;
103     }
104   }
105   if (r==-1) return -1;
106   rest=-1;
107   for (j=0; j<r; j+=str_chr(buf+j,'\n')) {
108     if (byte_equal(buf+j,17,"\nContent-Length: ")) {
109       char* c=buf+j+17;
110       if (c[scan_ulong(c,&rest)]!='\r') {
111 	buffer_putsflush(buffer_2,"invalid Content-Length header!\n");
112 	return -1;
113       }
114       break;
115     }
116     ++j;
117   }
118   if (measurethroughput) {
119     gettimeofday(&a,0);
120     done=r-body;
121   }
122   rest-=(r-body);
123   while (rest) {
124     r=read(s,buf,rest>sizeof(buf)?sizeof(buf):rest);
125     if (r<1) {
126       if (r==-1)
127 	carp("read from HTTP socket");
128       else {
129 	buffer_puts(buffer_2,"early HTTP EOF; expected ");
130 	buffer_putulong(buffer_2,rest);
131 	buffer_putsflush(buffer_2,"more bytes!\n");
132 	return -1;
133       }
134     } else {
135       rest-=r;
136       if (measurethroughput) {
137 	unsigned long x=done/1000000;
138 	unsigned long y;
139 	done+=r;
140 	y=done/1000000;
141 	if (x!=y) {
142 	  unsigned long d;
143 	  unsigned long long z;
144 	  gettimeofday(&b,0);
145 	  d=(b.tv_sec-a.tv_sec)*1000000;
146 	  d=d+b.tv_usec-a.tv_usec;
147 	  buffer_puts(buffer_1,"tput ");
148 	  z=1000000000ull/d;
149 	  buffer_putulong(buffer_1,z);
150 	  buffer_putnlflush(buffer_1);
151 
152 	  byte_copy(&a,sizeof(a),&b);
153 	}
154       }
155     }
156   }
157   return 0;
158 }
159 
main(int argc,char * argv[])160 int main(int argc,char* argv[]) {
161   unsigned long count=1000;
162   unsigned long interval=10;
163   unsigned long sample=5;
164   int keepalive=0;
165   char ip[16];
166   uint16 port=80;
167   uint32 scope_id=0;
168   stralloc ips={0};
169   int s;
170   char* request;
171   int rlen;
172 
173   signal(SIGPIPE,SIG_IGN);
174 
175   if (!geteuid()) {
176     struct rlimit rl;
177     long l;
178 #ifdef RLIMIT_NPROC
179     rl.rlim_cur=RLIM_INFINITY; rl.rlim_max=RLIM_INFINITY;
180     setrlimit(RLIMIT_NPROC,&rl);
181 #endif
182     for (l=0; l<20000; l+=500) {
183       rl.rlim_cur=l; rl.rlim_max=l;
184       if (setrlimit(RLIMIT_NOFILE,&rl)==-1) break;
185     }
186   }
187 
188   for (;;) {
189     int i;
190     int c=getopt(argc,argv,"c:i:s:kb");
191     if (c==-1) break;
192     switch (c) {
193     case 'k':
194       keepalive=1;
195       break;
196     case 'i':
197       i=scan_ulong(optarg,&interval);
198       if (i==0 || optarg[i]) {
199 	buffer_puts(buffer_2,"httpbench: warning: could not parse interval: ");
200 	buffer_puts(buffer_2,optarg+i+1);
201 	buffer_putsflush(buffer_2,"\n");
202       }
203       break;
204     case 'c':
205       i=scan_ulong(optarg,&count);
206       if (i==0 || optarg[i]) {
207 	buffer_puts(buffer_2,"httpbench: warning: could not parse count: ");
208 	buffer_puts(buffer_2,optarg+i+1);
209 	buffer_putsflush(buffer_2,"\n");
210       }
211       break;
212     case 's':
213       i=scan_ulong(optarg,&sample);
214       if (i==0 || optarg[i]) {
215 	buffer_puts(buffer_2,"httpbench: warning: could not parse sample size: ");
216 	buffer_puts(buffer_2,optarg+i+1);
217 	buffer_putsflush(buffer_2,"\n");
218       }
219       break;
220     case 'b':
221       bindport=10000;
222       break;
223     case '?':
224 usage:
225       buffer_putsflush(buffer_2,
226 		  "usage: httpbench [-hb] [-c count] [-i interval] [-s sample] url\n"
227 		  "\n"
228 		  "\t-h\tprint this help\n"
229 		  "\t-c n\topen n connections total (default: 1000)\n"
230 		  "\t-i n\tevery n connections, measure the latency (default: 10)\n"
231 		  "\t-s n\tlatency == average of time to fetch an URL n times (default: 5)\n"
232 		  "\t-k\tenable HTTP keep-alive\n"
233 		  "\t-b\tbind the sockets ourselves, so the OS doesn't choose the ports\n"
234 		  "Setting the number of connections to 1 measures the throughput\n"
235 		  "instead of the latency (give URL to a large file).\n");
236       return 0;
237     }
238   }
239 
240   if (!argv[optind]) goto usage;
241   if (byte_diff(argv[optind],7,"http://")) goto usage;
242   {
243     char* host=argv[optind]+7;
244     int colon=str_chr(host,':');
245     int slash=str_chr(host,'/');
246     char* c;
247     if (host[0]=='[') {	/* ipv6 IP notation */
248       int tmp;
249       ++host;
250       --colon; --slash;
251       tmp=str_chr(host,']');
252       if (host[tmp]==']') host[tmp]=0;
253       if (host[tmp+1]==':') colon=tmp+1;
254       if (colon<tmp+1) colon=tmp+1+str_len(host+tmp+1);
255     }
256     if (colon<slash) {
257       host[colon]=0;
258       c=host+colon+1;
259       if (c[scan_ushort(c,&port)]!='/') goto usage;
260       *c=0;
261     }
262     host[colon]=0;
263     c=host+slash;
264     *c=0;
265     {
266       char* tmp=alloca(str_len(host)+1);
267       tmp[fmt_str(tmp,host)]=0;
268       host=tmp;
269     }
270     *c='/';
271     {
272       int tmp=str_chr(host,'%');
273       if (host[tmp]) {
274 	host[tmp]=0;
275 	scope_id=socket_getifidx(host+tmp+1);
276 	if (scope_id==0) {
277 	  buffer_puts(buffer_2,"httpbench: warning: network interface ");
278 	  buffer_puts(buffer_2,host+tmp+1);
279 	  buffer_putsflush(buffer_2," not found.\n");
280 	}
281       }
282     }
283 
284     {
285       stralloc a={0};
286       stralloc_copys(&a,host);
287       if (dns_ip6(&ips,&a)==-1) {
288 	buffer_puts(buffer_2,"httpbench: could not resolve IP: ");
289 	buffer_puts(buffer_2,host);
290 	buffer_putnlflush(buffer_2);
291 	return 1;
292       }
293     }
294 
295     request=malloc(300+str_len(host)+str_len(c)*3);
296     if (!request) panic("malloc");
297     {
298       int i;
299       i=fmt_str(request,"GET ");
300       i+=fmt_urlencoded(request+i,c,str_len(c));
301       i+=fmt_str(request+i," HTTP/1.0\r\nHost: ");
302       i+=fmt_str(request+i,host);
303       i+=fmt_str(request+i,":");
304       i+=fmt_ulong(request+i,port);
305       i+=fmt_str(request+i,"\r\nUser-Agent: httpbench/1.0\r\nConnection: ");
306       i+=fmt_str(request+i,keepalive?"keep-alive":"close");
307       i+=fmt_str(request+i,"\r\n\r\n");
308       rlen=i; request[rlen]=0;
309     }
310 
311   }
312 
313   {
314     int i;
315     s=-1;
316     for (i=0; i+16<=ips.len; i+=16) {
317       char buf[IP6_FMT];
318       int v6=byte_diff(ips.s+i,12,V4mappedprefix);
319       buffer_puts(buffer_1,"connecting to ");
320       buffer_put(buffer_1,buf,
321 		 v6?
322 		 fmt_ip6(buf,ips.s+i):
323 		 fmt_ip4(buf,ips.s+i+12));
324       buffer_puts(buffer_1," port ");
325       buffer_putulong(buffer_1,port);
326       buffer_putnlflush(buffer_1);
327       s=make_connection(ips.s+i,port,scope_id);
328       if (s!=-1) {
329 	byte_copy(ip,16,ips.s+i);
330 	break;
331       }
332     }
333     if (s==-1)
334       return 1;
335   }
336   if (write(s,request,rlen)!=rlen) panic("write");
337   if (readanswer(s,count==1)==-1) exit(1);
338   close(s);
339   if (count==1)
340     return 0;
341 
342   {
343     long i;
344     long j;
345     long err=0;
346     int *socks;
347     socks=malloc(sizeof(int)*count);
348     if (!socks) panic("malloc");
349     for (i=j=0; i<count; ++i) {
350       struct timeval a,b;
351       long d;
352       if (j==0) {
353 	int k,s=0;
354 	long x=0,y=0;
355 	for (k=0; k<sample; ++k) {
356 	  if (!keepalive || !k) {
357 	    gettimeofday(&a,0);
358 	    s=make_connection(ip,port,scope_id);
359 	    if (s==-1)
360 	      panic("make_connection");
361 	    gettimeofday(&b,0);
362 	    d=(b.tv_sec-a.tv_sec)*1000000;
363 	    d=d+b.tv_usec-a.tv_usec;
364 	    x+=d;
365 	  }
366 	  gettimeofday(&a,0);
367 	  write(s,request,rlen);
368 	  if (readanswer(s,0)==-1) {
369 	    ++err;
370 	    keepalive=0;
371 	  }
372 	  gettimeofday(&b,0);
373 	  d=(b.tv_sec-a.tv_sec)*1000000;
374 	  d=d+b.tv_usec-a.tv_usec;
375 	  y+=d;
376 	  if (!keepalive) close(s);
377 	}
378 	if (keepalive) close(s);
379 	buffer_puts(buffer_1,"sample ");
380 	buffer_putulong(buffer_1,x);
381 	buffer_puts(buffer_1," ");
382 	buffer_putulong(buffer_1,y/sample);
383 	buffer_putnlflush(buffer_1);
384       }
385       ++j; if (j==interval) j=0;
386 
387       gettimeofday(&a,0);
388       socks[i]=make_connection(ip,port,scope_id);
389       if (socks[i]==-1)
390 	panic("make_connection");
391       gettimeofday(&b,0);
392       d=(b.tv_sec-a.tv_sec)*1000000;
393       d=d+b.tv_usec-a.tv_usec;
394       buffer_puts(buffer_1,"clat ");
395       buffer_putulong(buffer_1,d);
396       buffer_putnlflush(buffer_1);
397     }
398   }
399   buffer_flush(buffer_1);
400   return 0;
401 }
402