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