File: | bin/tools/weighttp.c |
Warning: | line 1417, column 16 Although the value stored to 'default_port' is used in the enclosing expression, the value is never actually read from 'default_port' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* |
2 | * weighttp - a lightweight and simple webserver benchmarking tool |
3 | * |
4 | * Copyright (c) 2016, Glue Logic LLC. All rights reserved. code()gluelogic.com |
5 | * |
6 | * This rewrite is based on weighttp by Thomas Porzelt |
7 | * Copyright (c) 2009-2011 Thomas Porzelt |
8 | * git://git.lighttpd.net/weighttp |
9 | * https://github.com/lighttpd/weighttp/ |
10 | * |
11 | * License: |
12 | * MIT, see COPYING.weighttp file |
13 | */ |
14 | |
15 | #ifndef _GNU_SOURCE1 |
16 | #define _GNU_SOURCE1 1 |
17 | #endif |
18 | |
19 | #pragma GCC diagnostic ignored "-Wsign-conversion" |
20 | #pragma GCC diagnostic ignored "-Wconversion" |
21 | #pragma GCC diagnostic ignored "-Wvla" |
22 | |
23 | #include <sys/types.h> |
24 | #include <sys/socket.h>/* socket() connect() SOCK_NONBLOCK sockaddr_storage */ |
25 | #include <sys/stat.h> /* fstat() */ |
26 | #include <sys/time.h> /* gettimeofday() */ |
27 | #include <errno(*__errno_location ()).h> /* errno EINTR EAGAIN EWOULDBLOCK EINPROGRESS EALREADY */ |
28 | #include <fcntl.h> /* open() fcntl() pipe2() F_SETFL (O_* flags) */ |
29 | #include <inttypes.h> /* PRIu64 PRId64 */ |
30 | #include <limits.h> /* USHRT_MAX */ |
31 | #include <locale.h> /* setlocale() */ |
32 | #include <netdb.h> /* getaddrinfo() freeaddrinfo() */ |
33 | #include <poll.h> /* poll() POLLIN POLLOUT POLLERR POLLHUP */ |
34 | #include <pthread.h> /* pthread_create() pthread_join() */ |
35 | #include <stdarg.h> /* va_start() va_end() vfprintf() */ |
36 | #include <stdio.h> |
37 | #include <stdlib.h> /* calloc() free() exit() strtoul() strtoull() */ |
38 | #include <stdint.h> /* UINT32_MAX */ |
39 | #include <signal.h> /* signal() */ |
40 | #include <string.h> |
41 | #include <strings.h> /* strcasecmp() strncasecmp() */ |
42 | #include <unistd.h> /* read() write() close() getopt() optarg optind optopt*/ |
43 | |
44 | #include <arpa/inet.h> |
45 | #include <netinet/tcp.h> |
46 | #include <netinet/in.h> |
47 | #include <sys/un.h> |
48 | |
49 | #ifndef MSG_DONTWAITMSG_DONTWAIT |
50 | #define MSG_DONTWAITMSG_DONTWAIT 0 |
51 | #endif |
52 | #ifndef MSG_NOSIGNALMSG_NOSIGNAL |
53 | #define MSG_NOSIGNALMSG_NOSIGNAL 0 |
54 | #endif |
55 | #ifndef SOCK_NONBLOCKSOCK_NONBLOCK |
56 | #define SOCK_NONBLOCKSOCK_NONBLOCK 0 |
57 | #endif |
58 | |
59 | #ifndef PACKAGE_VERSION"" |
60 | #define PACKAGE_VERSION"" "" |
61 | #endif |
62 | |
63 | |
64 | /*(oversimplified; these attributes are supported by some other compilers)*/ |
65 | #if defined(__GNUC__4) || defined(__clang__1) |
66 | #ifndef __attribute_cold____attribute__((__cold__)) |
67 | #define __attribute_cold____attribute__((__cold__)) __attribute__((__cold__)) |
68 | #endif |
69 | #ifndef __attribute_hot____attribute__((__hot__)) |
70 | #define __attribute_hot____attribute__((__hot__)) __attribute__((__hot__)) |
71 | #endif |
72 | #ifndef __attribute_noinline____attribute__ ((__noinline__)) |
73 | #define __attribute_noinline____attribute__ ((__noinline__)) __attribute__((__noinline__)) |
74 | #endif |
75 | #ifndef __attribute_noreturn____attribute__((__noreturn__)) |
76 | #define __attribute_noreturn____attribute__((__noreturn__)) __attribute__((__noreturn__)) |
77 | #endif |
78 | #ifndef __attribute_pure____attribute__ ((__pure__)) |
79 | #define __attribute_pure____attribute__ ((__pure__)) __attribute__((__pure__)) |
80 | #endif |
81 | #ifndef __attribute_format__ |
82 | #define __attribute_format__(x)__attribute__((__format__ x)) __attribute__((__format__ x)) |
83 | #endif |
84 | #else |
85 | #ifndef __builtin_expect |
86 | #define __builtin_expect(x, y) (x) |
87 | #endif |
88 | #ifndef __attribute_cold____attribute__((__cold__)) |
89 | #define __attribute_cold____attribute__((__cold__)) |
90 | #endif |
91 | #ifndef __attribute_hot____attribute__((__hot__)) |
92 | #define __attribute_hot____attribute__((__hot__)) |
93 | #endif |
94 | #ifndef __attribute_noinline____attribute__ ((__noinline__)) |
95 | #define __attribute_noinline____attribute__ ((__noinline__)) |
96 | #endif |
97 | #ifndef __attribute_noreturn____attribute__((__noreturn__)) |
98 | #define __attribute_noreturn____attribute__((__noreturn__)) |
99 | #endif |
100 | #ifndef __attribute_pure____attribute__ ((__pure__)) |
101 | #define __attribute_pure____attribute__ ((__pure__)) |
102 | #endif |
103 | #ifndef __attribute_format__ |
104 | #define __attribute_format__(x)__attribute__((__format__ x)) |
105 | #endif |
106 | #endif |
107 | |
108 | |
109 | __attribute_cold____attribute__((__cold__)) |
110 | __attribute_noinline____attribute__ ((__noinline__)) |
111 | static void |
112 | show_version (void) |
113 | { |
114 | puts("\nweighttp " PACKAGE_VERSION"" |
115 | " - a lightweight and simple webserver benchmarking tool\n"); |
116 | } |
117 | |
118 | |
119 | __attribute_cold____attribute__((__cold__)) |
120 | __attribute_noinline____attribute__ ((__noinline__)) |
121 | static void |
122 | show_help (void) |
123 | { |
124 | puts( |
125 | "weighttp <options> <URI>\n" |
126 | " -n num number of requests (mandatory)\n" |
127 | " -t num thread count (default: 1)\n" |
128 | " -c num concurrent clients (default: 1)\n" |
129 | " -k keep alive (default: no)\n" |
130 | " -K num num pipelined requests (default: 1)\n" |
131 | " -6 use ipv6 (default: no)\n" |
132 | " -i use HTTP HEAD method (default: GET)\n" |
133 | " -m method use custom HTTP method (default: GET)\n" |
134 | " -H str add header to request (\"label: value\"); repeatable\n" |
135 | " -b size socket buffer sizes (SO_SNDBUF, SO_RCVBUF)\n" |
136 | " -B addr local address to bind to when making outgoing connections\n" |
137 | " -C cookie add cookie to request (\"cookie-name=value\"); repeatable\n" |
138 | " -F use TCP Fast Open (RFC 7413)\n" |
139 | " -T type Content-Type header to use for POST/PUT data,\n" |
140 | " e.g. application/x-www-form-urlencoded\n" |
141 | " (default: text/plain)\n" |
142 | " -A string add Basic WWW Authorization (str is username:password)\n" |
143 | " -P string add Basic Proxy-Authorization (str is username:password)\n" |
144 | " -X proxy proxy:port or unix domain socket path beginning w/ '/'\n" |
145 | " -p file make HTTP POST request using file contents for body\n" |
146 | " -u file make HTTP PUT request using file contents for body\n" |
147 | " -d (ignored; compatibility with Apache Bench (ab))\n" |
148 | " -l (ignored; compatibility with Apache Bench (ab))\n" |
149 | " -r (ignored; compatibility with Apache Bench (ab))\n" |
150 | " -q quiet: do not show version header or progress\n" |
151 | " -h show help and exit\n" |
152 | " -V show version and exit\n\n" |
153 | "example: \n" |
154 | " weighttpd -n 500000 -c 100 -t 2 -K 64 http://localhost/index.html\n"); |
155 | } |
156 | |
157 | /* Notes regarding pipelining |
158 | * Enabling pipelining (-p x where x > 1) results in extra requests being sent |
159 | * beyond the precise number requested on the command line. Subsequently, |
160 | * extra bytes might be read and reported in stats at the end of the test run. |
161 | * Additionally, the extra requests are dropped once the req_todo amount is |
162 | * reached, and so the target web server(s) might report errors that client |
163 | * dropped connection (client disconnect) for those final requests. |
164 | * |
165 | * The benefits of pipelining include reduced latency between request/response, |
166 | * as well as potentially fewer socket read()s for data if multiple requests or |
167 | * multiple responses are available to be read by server or client, respectively |
168 | */ |
169 | |
170 | #define CLIENT_BUFFER_SIZE32 * 1024 32 * 1024 |
171 | |
172 | |
173 | struct Stats; |
174 | typedef struct Stats Stats; |
175 | struct Client; |
176 | typedef struct Client Client; |
177 | struct Worker; |
178 | typedef struct Worker Worker; |
179 | struct Config; |
180 | typedef struct Config Config; |
181 | struct Worker_Config; |
182 | typedef struct Worker_Config Worker_Config; |
183 | |
184 | |
185 | struct Stats { |
186 | uint64_t req_todo; /* total num of requests to do */ |
187 | uint64_t req_started; /* total num of requests started */ |
188 | uint64_t req_done; /* total num of requests done */ |
189 | uint64_t req_success; /* total num of successful requests */ |
190 | uint64_t req_failed; /* total num of failed requests */ |
191 | uint64_t req_error; /* total num of errored requests */ |
192 | uint64_t bytes_total; /* total num of bytes received (headers+body) */ |
193 | uint64_t bytes_headers; /* total num of bytes received (headers) */ |
194 | uint64_t req_2xx; |
195 | uint64_t req_3xx; |
196 | uint64_t req_4xx; |
197 | uint64_t req_5xx; |
198 | }; |
199 | |
200 | struct Client { |
201 | int revents; |
202 | enum { |
203 | PARSER_CONNECT, |
204 | PARSER_START, |
205 | PARSER_HEADER, |
206 | PARSER_BODY |
207 | } parser_state; |
208 | |
209 | uint32_t buffer_offset; /* pos in buffer (size of data in buffer) */ |
210 | uint32_t parser_offset; /* pos in parsing (behind buffer_offset) */ |
211 | uint32_t request_offset; /* pos in sending request */ |
212 | int chunked; |
213 | int64_t content_length; |
214 | int64_t chunk_size; |
215 | int64_t chunk_received; |
216 | int http_status_success; |
217 | int config_keepalive; |
218 | int keepalive; |
219 | int keptalive; |
220 | int pipelined; |
221 | int pipeline_max; |
222 | int tcp_fastopen; |
223 | int http_head; |
224 | int so_bufsz; |
225 | |
226 | uint32_t request_size; |
227 | const char *request; |
228 | struct pollfd *pfd; |
229 | Stats *stats; |
230 | const struct addrinfo *raddr; |
231 | const struct addrinfo *laddr; |
232 | char buffer[CLIENT_BUFFER_SIZE32 * 1024]; |
233 | }; |
234 | |
235 | struct Worker { |
236 | struct pollfd *pfds; |
237 | Client *clients; |
238 | Stats stats; |
239 | struct addrinfo raddr; |
240 | struct addrinfo laddr; |
241 | struct sockaddr_storage raddr_storage; |
242 | struct sockaddr_storage laddr_storage; |
243 | }; |
244 | |
245 | struct Worker_Config { |
246 | const Config *config; |
247 | int id; |
248 | int num_clients; |
249 | uint64_t num_requests; |
250 | Stats stats; |
251 | /* pad struct Worker_Config for cache line separation between threads. |
252 | * Round up to 256 to avoid chance of false sharing between threads. |
253 | * Alternatively, could memalign the allocation of struct Worker_Config |
254 | * list to cache line size (e.g. 128 bytes) */ |
255 | uint64_t padding[(256 - (1*sizeof(void *)) |
256 | - (2*sizeof(int)) |
257 | - (1*sizeof(uint64_t)) |
258 | - sizeof(Stats)) |
259 | / sizeof(uint64_t)]; |
260 | }; |
261 | |
262 | struct Config { |
263 | Worker_Config *wconfs; |
264 | char *proxy; |
265 | struct timeval ts_start; |
266 | struct timeval ts_end; |
267 | |
268 | uint64_t req_count; |
269 | int thread_count; |
270 | int keep_alive; |
271 | int concur_count; |
272 | int pipeline_max; |
273 | int tcp_fastopen; |
274 | int http_head; |
275 | int so_bufsz; |
276 | |
277 | int quiet; |
278 | uint32_t request_size; |
279 | char *request; |
280 | char buf[16384]; /*(used for simple 8k memaligned request buffer on stack)*/ |
281 | struct addrinfo raddr; |
282 | struct addrinfo laddr; |
283 | struct sockaddr_storage raddr_storage; |
284 | struct sockaddr_storage laddr_storage; |
285 | struct laddrs { |
286 | struct addrinfo **addrs; |
287 | int num; |
288 | } laddrs; |
289 | }; |
290 | |
291 | |
292 | __attribute_cold____attribute__((__cold__)) |
293 | static void |
294 | client_init (Worker * const restrict worker, |
295 | const Config * const restrict config, |
296 | const int i) |
297 | { |
298 | Client * const restrict client = worker->clients+i; |
299 | client->pfd = worker->pfds+i; |
300 | client->pfd->fd = -1; |
301 | client->parser_state = PARSER_CONNECT; |
302 | |
303 | client->stats = &worker->stats; |
304 | client->raddr = &worker->raddr; |
305 | client->laddr = config->laddrs.num > 0 |
306 | ? config->laddrs.addrs[(i % config->laddrs.num)] |
307 | : (0 != worker->laddr.ai_addrlen) ? &worker->laddr : NULL((void*)0); |
308 | client->config_keepalive = config->keep_alive; |
309 | client->pipeline_max = config->pipeline_max; |
310 | client->tcp_fastopen = config->tcp_fastopen; |
311 | client->http_head = config->http_head; |
312 | client->so_bufsz = config->so_bufsz; |
313 | client->request_size = config->request_size; |
314 | client->request = config->request; |
315 | /* future: might copy config->request to new allocation in Worker |
316 | * so that all memory accesses during benchmark execution are to |
317 | * independent, per-thread allocations */ |
318 | } |
319 | |
320 | |
321 | __attribute_cold____attribute__((__cold__)) |
322 | static void |
323 | client_delete (const Client * const restrict client) |
324 | { |
325 | if (-1 != client->pfd->fd) |
326 | close(client->pfd->fd); |
327 | } |
328 | |
329 | |
330 | __attribute_cold____attribute__((__cold__)) |
331 | __attribute_noinline____attribute__ ((__noinline__)) |
332 | static void |
333 | worker_init (Worker * const restrict worker, |
334 | Worker_Config * const restrict wconf) |
335 | { |
336 | const Config * const restrict config = wconf->config; |
337 | memset(worker, 0, sizeof(Worker)); |
338 | memcpy(&worker->laddr, &config->laddr, sizeof(config->laddr)); |
339 | memcpy(&worker->raddr, &config->raddr, sizeof(config->raddr)); |
340 | if (config->laddr.ai_addrlen) |
341 | worker->laddr.ai_addr = (struct sockaddr *) |
342 | memcpy(&worker->laddr_storage, |
343 | &config->laddr_storage, config->laddr.ai_addrlen); |
344 | worker->raddr.ai_addr = (struct sockaddr *) |
345 | memcpy(&worker->raddr_storage, |
346 | &config->raddr_storage, config->raddr.ai_addrlen); |
347 | const int num_clients = wconf->num_clients; |
348 | worker->stats.req_todo = wconf->num_requests; |
349 | worker->pfds = (struct pollfd *)calloc(num_clients, sizeof(struct pollfd)); |
350 | worker->clients = (Client *)calloc(num_clients, sizeof(Client)); |
351 | for (int i = 0; i < num_clients; ++i) |
352 | client_init(worker, wconf->config, i); |
353 | } |
354 | |
355 | |
356 | __attribute_cold____attribute__((__cold__)) |
357 | __attribute_noinline____attribute__ ((__noinline__)) |
358 | static void |
359 | worker_delete (Worker * const restrict worker, |
360 | Worker_Config * const restrict wconf) |
361 | { |
362 | int i; |
363 | const int num_clients = wconf->num_clients; |
364 | |
365 | /* adjust bytes_total to discard count of excess responses |
366 | * (> worker->stats.req_todo) */ |
367 | if (worker->clients[0].pipeline_max > 1) { |
368 | for (i = 0; i < num_clients; ++i) { |
369 | worker->stats.bytes_total -= ( worker->clients[i].buffer_offset |
370 | - worker->clients[i].parser_offset ); |
371 | } |
372 | } |
373 | |
374 | memcpy(&wconf->stats, &worker->stats, sizeof(Stats)); |
375 | for (i = 0; i < num_clients; ++i) |
376 | client_delete(worker->clients+i); |
377 | free(worker->clients); |
378 | free(worker->pfds); |
379 | } |
380 | |
381 | |
382 | __attribute_cold____attribute__((__cold__)) |
383 | __attribute_noinline____attribute__ ((__noinline__)) |
384 | static void |
385 | wconfs_init (Config * const restrict config) |
386 | { |
387 | /* create Worker_Config data structures for each (future) thread */ |
388 | Worker_Config * const restrict wconfs = |
389 | (Worker_Config *)calloc(config->thread_count, sizeof(Worker_Config)); |
390 | |
391 | uint32_t rest_concur = config->concur_count % config->thread_count; |
392 | uint32_t rest_req = config->req_count % config->thread_count; |
393 | |
394 | for (int i = 0; i < config->thread_count; ++i) { |
395 | uint64_t reqs = config->req_count / config->thread_count; |
396 | int concur = config->concur_count / config->thread_count; |
397 | |
398 | if (rest_concur) { |
399 | concur += 1; |
400 | rest_concur -= 1; |
401 | } |
402 | |
403 | if (rest_req) { |
404 | reqs += 1; |
405 | rest_req -= 1; |
406 | } |
407 | |
408 | if (!config->quiet) |
409 | printf("spawning thread #%d: %d concurrent requests, " |
410 | "%"PRIu64"l" "u"" total requests\n", i+1, concur, reqs); |
411 | |
412 | wconfs[i].config = config; |
413 | wconfs[i].id = i; |
414 | wconfs[i].num_clients = concur; |
415 | wconfs[i].num_requests = reqs; |
416 | } |
417 | |
418 | config->wconfs = wconfs; |
419 | } |
420 | |
421 | |
422 | __attribute_cold____attribute__((__cold__)) |
423 | __attribute_noinline____attribute__ ((__noinline__)) |
424 | static void |
425 | wconfs_delete (const Config * const restrict config) |
426 | { |
427 | free(config->wconfs); |
428 | if (config->request < config->buf |
429 | || config->buf+sizeof(config->buf) <= config->request) |
430 | free(config->request); |
431 | |
432 | if (config->laddrs.num > 0) { |
433 | for (int i = 0; i < config->laddrs.num; ++i) |
434 | freeaddrinfo(config->laddrs.addrs[i]); |
435 | free(config->laddrs.addrs); |
436 | } |
437 | } |
438 | |
439 | |
440 | __attribute_hot____attribute__((__hot__)) |
441 | static void |
442 | client_reset (Client * const restrict client, const int success) |
443 | { |
444 | /* update worker stats */ |
445 | Stats * const restrict stats = client->stats; |
446 | |
447 | ++stats->req_done; |
448 | if (__builtin_expect( (0 != success), 1)) |
449 | ++stats->req_success; |
450 | else |
451 | ++stats->req_failed; |
452 | |
453 | client->revents = (stats->req_started < stats->req_todo) ? POLLOUT0x004 : 0; |
454 | if (client->revents && client->keepalive) { |
455 | /*(assumes writable; will find out soon if not and register interest)*/ |
456 | ++stats->req_started; |
457 | client->parser_state = PARSER_START; |
458 | client->keptalive = 1; |
459 | if (client->parser_offset == client->buffer_offset) { |
460 | client->parser_offset = 0; |
461 | client->buffer_offset = 0; |
462 | } |
463 | #if 0 |
464 | else if (client->parser_offset > (CLIENT_BUFFER_SIZE32 * 1024/2)) { |
465 | memmove(client->buffer, client->buffer+client->parser_offset, |
466 | client->buffer_offset - client->parser_offset + 1); |
467 | client->buffer_offset -= client->parser_offset; |
468 | client->parser_offset = 0; |
469 | } |
470 | /* future: if we tracked size of headers for first successful response, |
471 | * we might use that size to determine whether or not to memmove() |
472 | * any remaining contents in client->buffer to the beginning of buffer, |
473 | * e.g. if parser_offset + expected_response_len exceeds buffer size |
474 | * On the size, if we expect to already have completed response fully |
475 | * received in buffer, then skip the memmove(). */ |
476 | #endif |
477 | if (--client->pipelined && client->buffer_offset) |
478 | client->revents |= POLLIN0x001; |
479 | } |
480 | else { |
481 | close(client->pfd->fd); |
482 | client->pfd->fd = -1; |
483 | client->pfd->events = 0; |
484 | /*client->pfd->revents = 0;*/ |
485 | client->parser_state = PARSER_CONNECT; |
486 | } |
487 | } |
488 | |
489 | |
490 | __attribute_cold____attribute__((__cold__)) |
491 | __attribute_noinline____attribute__ ((__noinline__)) |
492 | static void |
493 | client_error (Client * const restrict client) |
494 | { |
495 | ++client->stats->req_error; |
496 | if (client->parser_state != PARSER_BODY) { |
497 | /*(might include subsequent responses to pipelined requests, but |
498 | * some sort of invalid response received if client_error() called)*/ |
499 | client->stats->bytes_headers += |
500 | (client->buffer_offset - client->parser_offset); |
501 | client->buffer_offset = 0; |
502 | client->parser_offset = 0; |
503 | } |
504 | client->keepalive = 0; |
505 | client_reset(client, 0); |
506 | } |
507 | |
508 | |
509 | __attribute_cold____attribute__((__cold__)) |
510 | __attribute_noinline____attribute__ ((__noinline__)) |
511 | static void |
512 | client_perror (Client * const restrict client, const char * const restrict tag) |
513 | { |
514 | const int errnum = errno(*__errno_location ()); |
515 | client->buffer[0] = '\0'; |
516 | #if defined(_GNU_SOURCE1) && defined(__GLIBC__2) |
517 | const char * const errstr = |
518 | strerror_r(errnum, client->buffer, sizeof(client->buffer)); |
519 | #else /* XSI-compliant strerror_r() */ |
520 | const char * const errstr = client->buffer; |
521 | strerror_r(errnum, client->buffer, sizeof(client->buffer)); |
522 | #endif |
523 | fprintf(stderrstderr, "error: %s failed: (%d) %s\n", tag, errnum, errstr); |
524 | client_error(client); |
525 | } |
526 | |
527 | |
528 | |
529 | static void |
530 | client_connected (Client * const restrict client) |
531 | { |
532 | client->request_offset = 0; |
533 | client->buffer_offset = 0; |
534 | client->parser_offset = 0; |
535 | client->parser_state = PARSER_START; |
536 | client->pipelined = 0; |
537 | client->keepalive = client->config_keepalive; |
538 | client->keptalive = 0; |
539 | /*client->success = 0;*/ |
540 | } |
541 | |
542 | |
543 | __attribute_noinline____attribute__ ((__noinline__)) |
544 | static int |
545 | client_connect (Client * const restrict client) |
546 | { |
547 | const struct addrinfo * const restrict raddr = client->raddr; |
548 | int fd = client->pfd->fd; |
549 | int opt; |
550 | |
551 | if (-1 == fd) { |
552 | ++client->stats->req_started; |
553 | |
554 | do { |
555 | fd = socket(raddr->ai_family,raddr->ai_socktype,raddr->ai_protocol); |
556 | } while (__builtin_expect( (-1 == fd), 0) && errno(*__errno_location ()) == EINTR4); |
557 | |
558 | if (fd >= 0) { |
559 | #if !SOCK_NONBLOCKSOCK_NONBLOCK |
560 | fcntl(fd, F_SETFL4, O_NONBLOCK04000 | O_RDWR02); /* set non-blocking */ |
561 | #endif |
562 | client->pfd->fd = fd; |
563 | } |
564 | else { |
565 | client_perror(client, "socket()"); |
566 | return 0; |
567 | } |
568 | |
569 | if (1 == client->pipeline_max && raddr->ai_family != AF_UNIX1) { |
570 | /* disable Nagle if not pipelining requests and not AF_UNIX |
571 | * (pipelining enables keepalive, but if not pipelining but |
572 | * keepalive enabled, still want to disable Nagle to reduce latency |
573 | * when sending next keepalive request after receiving response) */ |
574 | opt = 1; |
575 | setsockopt(fd, IPPROTO_TCPIPPROTO_TCP, TCP_NODELAY1, &opt, sizeof(opt)); |
576 | } |
577 | |
578 | if (0 != client->so_bufsz) { |
579 | opt = client->so_bufsz; |
580 | if (0 != setsockopt(fd, SOL_SOCKET1, SO_SNDBUF7, &opt, sizeof(opt))) |
581 | client_perror(client, "setsockopt() SO_SNDBUF"); |
582 | if (0 != setsockopt(fd, SOL_SOCKET1, SO_RCVBUF8, &opt, sizeof(opt))) |
583 | client_perror(client, "setsockopt() SO_RCVBUF"); |
584 | } |
585 | |
586 | if (raddr->ai_family != AF_UNIX1) { |
587 | /*(might not be correct for real clients, but ok for load test)*/ |
588 | struct linger l = { .l_onoff = 1, .l_linger = 0 }; |
589 | if (0 != setsockopt(fd, SOL_SOCKET1, SO_LINGER13, &l, sizeof(l))) |
590 | client_perror(client, "setsockopt() SO_LINGER"); |
591 | } |
592 | |
593 | if (NULL((void*)0) != client->laddr) { |
594 | if (0 != bind(fd,client->laddr->ai_addr,client->laddr->ai_addrlen)){ |
595 | client_perror(client, "bind() (local addr)"); |
596 | return 0; |
597 | } |
598 | } |
599 | |
600 | int rc; |
601 | #ifdef TCP_FASTOPEN23 |
602 | ssize_t wr = 0; |
603 | if (client->tcp_fastopen) {/*(disabled if config->proxy is AF_UNIX)*/ |
604 | wr = sendto(fd, client->request, client->request_size, |
605 | MSG_FASTOPENMSG_FASTOPEN | MSG_DONTWAITMSG_DONTWAIT | MSG_NOSIGNALMSG_NOSIGNAL, |
606 | raddr->ai_addr, raddr->ai_addrlen); |
607 | if (wr > 0) { |
608 | client_connected(client); |
609 | if (client->request_size == (uint32_t)wr) { |
610 | client->pfd->events |= POLLIN0x001; |
611 | if (++client->pipelined == client->pipeline_max) { |
612 | client->revents &= ~POLLOUT0x004; |
613 | client->pfd->events &= ~POLLOUT0x004; |
614 | } |
615 | } |
616 | else |
617 | client->request_offset = (uint32_t)wr; |
618 | return 1; |
619 | } |
620 | else if (-1 == wr && errno(*__errno_location ()) == EOPNOTSUPP95) |
621 | wr = 0; |
622 | else { |
623 | /*(0 == wr with sendto() should not happen |
624 | * with MSG_FASTOPEN and non-zero request_size)*/ |
625 | wr = -1; |
626 | rc = -1; |
627 | } |
628 | } |
629 | if (0 == wr) |
630 | #endif |
631 | do { |
632 | rc = connect(fd, raddr->ai_addr, raddr->ai_addrlen); |
633 | } while (__builtin_expect( (-1 == rc), 0) && errno(*__errno_location ()) == EINTR4); |
634 | |
635 | if (0 != rc) { |
636 | switch (errno(*__errno_location ())) { |
637 | case EINPROGRESS115: |
638 | case EALREADY114: |
639 | /* async connect now in progress */ |
640 | client->revents &= ~POLLOUT0x004; |
641 | client->pfd->events |= POLLOUT0x004; |
642 | return 0; |
643 | default: |
644 | client_perror(client, "connect()"); |
645 | return 0; |
646 | } |
647 | } |
648 | } |
649 | else { |
650 | opt = 0; |
651 | socklen_t optlen = sizeof(opt); |
652 | if (0 != getsockopt(fd,SOL_SOCKET1,SO_ERROR4,&opt,&optlen) || 0 != opt) { |
653 | if (0 != opt) errno(*__errno_location ()) = opt; |
654 | client_perror(client, "connect() getsockopt()"); |
655 | return 0; /* error connecting */ |
656 | } |
657 | } |
658 | |
659 | /* successfully connected */ |
660 | client_connected(client); |
661 | return 1; |
662 | } |
663 | |
664 | |
665 | |
666 | static int |
667 | client_parse_chunks (Client * const restrict client) |
668 | { |
669 | do { |
670 | char *str = client->buffer+client->parser_offset; |
671 | |
672 | if (-1 == client->chunk_size) { |
673 | /* read chunk size */ |
674 | /*char *end = strchr(str, '\n');*/ |
675 | char *end = |
676 | memchr(str, '\n', client->buffer_offset - client->parser_offset); |
677 | if (!end) /* partial line */ |
678 | return 1; |
679 | ++end; |
680 | |
681 | /* assume server sends valid chunked header |
682 | * (not validating; (invalid) chunked header without any |
683 | * hex digits is treated as 0-chunk, ending input) */ |
684 | client->chunk_size = 0; |
685 | do { |
686 | int c = *str; |
687 | client->chunk_size <<= 4; |
688 | if (c >= '0' && c <= '9') |
689 | client->chunk_size |= (c - '0'); |
690 | else if ((c |= 0x20) >= 'a' && c <= 'f') |
691 | client->chunk_size |= (c - 'a' + 10); |
692 | else { |
693 | if (c=='\r' || c=='\n' || c==' ' || c=='\t' || c==';') |
694 | break; |
695 | client_error(client); |
696 | return 0; |
697 | } |
698 | } while (*++str != '\r' && *str != '\n'); |
699 | |
700 | if (0 == client->chunk_size) { |
701 | /* chunk of size 0 marks end of content body |
702 | * check for final "\r\n" ending response |
703 | * (not handling trailers if user supplied -H "TE: trailers") */ |
704 | if (end + 2 > client->buffer + client->buffer_offset) { |
705 | client->chunk_size = -1; |
706 | return 1; /* final "\r\n" not yet received */ |
707 | } |
708 | if (end[0] == '\r' && end[1] == '\n') |
709 | client->stats->bytes_headers += 2; |
710 | else |
711 | client->keepalive = 0; /*(just close con if trailers)*/ |
712 | client->parser_offset = end - client->buffer + 2; |
713 | client_reset(client, client->http_status_success); |
714 | return 0; /*(trigger loop continue in caller)*/ |
715 | } |
716 | |
717 | client->parser_offset = end - client->buffer; |
718 | client->chunk_received = 0; |
719 | client->chunk_size += 2; /*(for chunk "\r\n" end)*/ |
720 | } |
721 | |
722 | /* consume chunk until chunk_size is reached */ |
723 | const int rd = client->buffer_offset - client->parser_offset; |
724 | int chunk_remain = client->chunk_size - client->chunk_received; |
725 | if (rd >= chunk_remain) { |
726 | client->chunk_received += chunk_remain; |
727 | client->parser_offset += chunk_remain; |
728 | |
729 | if (client->buffer[client->parser_offset-1] != '\n') { |
730 | client_error(client); |
731 | return 0; |
732 | } |
733 | |
734 | /* got whole chunk, next! */ |
735 | client->chunk_size = -1; |
736 | client->chunk_received = 0; |
737 | } |
738 | else { |
739 | client->chunk_received += rd; |
740 | client->parser_offset += rd; |
741 | } |
742 | |
743 | } while (client->parser_offset != client->buffer_offset);/* more to parse */ |
744 | |
745 | client->parser_offset = 0; |
746 | client->buffer_offset = 0; |
747 | return 1; |
748 | } |
749 | |
750 | |
751 | __attribute_hot____attribute__((__hot__)) |
752 | __attribute_pure____attribute__ ((__pure__)) |
753 | static uint64_t |
754 | client_parse_uint64 (const char * const restrict str) |
755 | { |
756 | /* quick-n-dirty conversion of numerical string to integral number |
757 | * Note: not validating field and not checking for valid number |
758 | * (weighttp not intended for use with requests > 2 GB, as transfer |
759 | * of body would take the majority of the time in that case)*/ |
760 | uint64_t x = 0; |
761 | for (int i = 0; (unsigned int)(str[i] - '0') < 10u; ++i) { |
762 | x *= 10; |
763 | x += (unsigned int)(str[i] - '0'); |
764 | } |
765 | return x; |
766 | } |
767 | |
768 | |
769 | __attribute_hot____attribute__((__hot__)) |
770 | __attribute_noinline____attribute__ ((__noinline__)) |
771 | static int |
772 | client_parse (Client * const restrict client) |
773 | { |
774 | char *end; |
775 | uint32_t len; |
776 | |
777 | /* future: might combine PARSER_START and PARSER_HEADER states by |
778 | * collecting entire set of headers (reading until "\r\n\r\n") |
779 | * prior to parsing */ |
780 | |
781 | switch (client->parser_state) { |
782 | |
783 | case PARSER_START: |
784 | /* look for HTTP/1.1 200 OK (though also accept HTTP/1.0 200) |
785 | * Note: does not support 1xx intermediate messages */ |
786 | /* Note: not validating response line; assume valid */ |
787 | /*end = strchr(client->buffer+client->parser_offset, '\n');*/ |
788 | end = memchr(client->buffer+client->parser_offset, '\n', |
789 | client->buffer_offset - client->parser_offset); |
790 | if (NULL((void*)0) != end) { |
791 | len = (uint32_t)(end - client->buffer - client->parser_offset + 1); |
792 | if (len < sizeof("HTTP/1.1 200\r\n")-1) { |
793 | client_error(client); |
794 | return 0; |
795 | } |
796 | } |
797 | else /*(partial response line; incomplete)*/ |
798 | return 1; |
799 | |
800 | client->content_length = -1; |
801 | client->chunked = 0; |
802 | client->http_status_success = 1; |
803 | switch (client->buffer[client->parser_offset + sizeof("HTTP/1.1 ")-1] |
804 | - '0') { |
805 | case 2: |
806 | ++client->stats->req_2xx; |
807 | break; |
808 | case 3: |
809 | ++client->stats->req_3xx; |
810 | break; |
811 | case 4: |
812 | client->http_status_success = 0; |
813 | ++client->stats->req_4xx; |
814 | break; |
815 | case 5: |
816 | client->http_status_success = 0; |
817 | ++client->stats->req_5xx; |
818 | break; |
819 | default: |
820 | /* invalid status code */ |
821 | client_error(client); |
822 | return 0; |
823 | } |
824 | client->stats->bytes_headers += len; |
825 | client->parser_offset += len; |
826 | client->parser_state = PARSER_HEADER; |
827 | /* fall through */ |
828 | |
829 | case PARSER_HEADER: |
830 | /* minimally peek at Content-Length, Connection, Transfer-Encoding */ |
831 | do { |
832 | const char *str = client->buffer+client->parser_offset; |
833 | /*end = strchr(str, '\n');*/ |
834 | end = |
835 | memchr(str, '\n', client->buffer_offset - client->parser_offset); |
836 | if (NULL((void*)0) == end) |
837 | return 1; |
838 | len = (uint32_t)(end - str + 1); |
839 | client->stats->bytes_headers += len; |
840 | client->parser_offset += len; |
841 | |
842 | /* minimum lengths for us to check for ':' in the following: |
843 | * "Content-Length:0\r\n" |
844 | * "Connection:close\r\n" |
845 | * "Transfer-Encoding:chunked\r\n"*/ |
846 | if (end - str < 17) |
847 | continue; |
848 | |
849 | if (str[14] == ':' |
850 | && (0 == memcmp(str, "Content-Length", |
851 | sizeof("Content-Length")-1) |
852 | || 0 == strncasecmp(str, "Content-Length", |
853 | sizeof("Content-Length")-1))) { |
854 | str += sizeof("Content-Length:")-1; |
855 | if (__builtin_expect( (*str == ' '), 1)) |
856 | ++str; |
857 | while (__builtin_expect( (*str == ' '), 0) |
858 | || __builtin_expect( (*str == '\t'), 0)) |
859 | ++str; |
860 | client->content_length = client_parse_uint64(str); |
861 | } |
862 | else if (str[10] == ':' |
863 | && (0 == memcmp(str, "Connection", |
864 | sizeof("Connection")-1) |
865 | || 0 == strncasecmp(str, "Connection", |
866 | sizeof("Connection")-1))) { |
867 | str += sizeof("Connection:")-1; |
868 | if (__builtin_expect( (*str == ' '), 1)) |
869 | ++str; |
870 | while (__builtin_expect( (*str == ' '), 0) |
871 | || __builtin_expect( (*str == '\t'), 0)) |
872 | ++str; |
873 | if ((*str | 0x20) == 'c') /*(assume "close")*/ |
874 | client->keepalive = 0; |
875 | } |
876 | else if (str[17] == ':' |
877 | && (0 == memcmp(str, "Transfer-Encoding", |
878 | sizeof("Transfer-Encoding")-1) |
879 | || 0 == strncasecmp(str, "Transfer-Encoding", |
880 | sizeof("Transfer-Encoding")-1))) { |
881 | client->chunked = 1; /*(assume "chunked")*/ |
882 | client->chunk_size = -1; |
883 | client->chunk_received = 0; |
884 | } |
885 | |
886 | } while (end[1] != '\r' || end[2] != '\n'); |
887 | |
888 | /* body reached */ |
889 | client->stats->bytes_headers += 2; |
890 | client->parser_offset += 2; |
891 | client->parser_state = PARSER_BODY; |
892 | if (client->http_head) |
893 | client->content_length = 0; |
894 | else if (!client->chunked && -1 == client->content_length) |
895 | client->keepalive = 0; |
896 | /* fall through */ |
897 | |
898 | case PARSER_BODY: |
899 | /* consume and discard response body */ |
900 | |
901 | if (client->chunked) |
902 | return client_parse_chunks(client); |
903 | else { |
904 | /* consume all data until content-length reached (or EOF) */ |
905 | if (-1 != client->content_length) { |
906 | uint32_t rd = client->buffer_offset - client->parser_offset; |
907 | if (client->content_length > rd) |
908 | client->content_length -= rd; |
909 | else { /* full response received */ |
910 | client->parser_offset += client->content_length; |
911 | client_reset(client, client->http_status_success); |
912 | return 0; /*(trigger loop continue in caller)*/ |
913 | } |
914 | } |
915 | |
916 | client->buffer_offset = 0; |
917 | client->parser_offset = 0; |
918 | return 1; |
919 | } |
920 | |
921 | case PARSER_CONNECT: /*(should not happen here)*/ |
922 | break; |
923 | } |
924 | |
925 | return 1; |
926 | } |
927 | |
928 | |
929 | |
930 | static void |
931 | client_revents (Client * const restrict client) |
932 | { |
933 | while (client->revents & POLLIN0x001) { |
934 | /* parse pipelined responses */ |
935 | if (client->buffer_offset && !client_parse(client)) |
936 | continue; |
937 | |
938 | ssize_t r; |
939 | do { |
940 | r = recv(client->pfd->fd, client->buffer+client->buffer_offset, |
941 | sizeof(client->buffer) - client->buffer_offset - 1, |
942 | MSG_DONTWAITMSG_DONTWAIT); |
943 | } while (__builtin_expect( (-1 == r), 0) && errno(*__errno_location ()) == EINTR4); |
944 | if (__builtin_expect( (r > 0), 1)) { |
945 | if (r < (ssize_t)(sizeof(client->buffer)-client->buffer_offset-1)) |
946 | client->revents &= ~POLLIN0x001; |
947 | client->buffer[(client->buffer_offset += (uint32_t)r)] = '\0'; |
948 | client->stats->bytes_total += r; |
949 | |
950 | if (!client_parse(client)) |
951 | continue; |
952 | |
953 | /* PARSER_BODY handling consumes data, so buffer full might happen |
954 | * only when parsing response header line or chunked header line. |
955 | * If buffer is full, then line is *way* too long. However, if |
956 | * client->parser_offset is non-zero, then move data to beginning |
957 | * of buffer and attempt to read() more */ |
958 | if (__builtin_expect( |
959 | (client->buffer_offset == sizeof(client->buffer)-1), 0)) { |
960 | if (0 == client->parser_offset) { |
961 | client_error(client); /* response header too big */ |
962 | break; |
963 | } |
964 | else { |
965 | memmove(client->buffer,client->buffer+client->parser_offset, |
966 | client->buffer_offset - client->parser_offset + 1); |
967 | client->buffer_offset -= client->parser_offset; |
968 | client->parser_offset = 0; |
969 | } |
970 | } |
971 | } |
972 | else { |
973 | if (-1 == r) { /* error */ |
974 | if (errno(*__errno_location ()) == EAGAIN11 |
975 | #if EAGAIN11 != EWOULDBLOCK11 |
976 | || errno(*__errno_location ()) == EWOULDBLOCK11 |
977 | #endif |
978 | ) { |
979 | client->revents &= ~POLLIN0x001; |
980 | client->pfd->events |= POLLIN0x001; |
981 | break; |
982 | } |
983 | else |
984 | client_perror(client, "read()"); |
985 | } |
986 | else { /* disconnect; evaluate if end-of-response or error */ |
987 | if (client->http_status_success |
988 | && client->parser_state == PARSER_BODY |
989 | && !client->chunked && -1 == client->content_length) { |
990 | client->keepalive = 0; |
991 | client_reset(client, 1); |
992 | } |
993 | else { |
994 | if (client->keptalive |
995 | && client->parser_state == PARSER_START |
996 | && 0 == client->buffer_offset) { |
997 | /* (server might still read and discard request, |
998 | * but has initiated connection close) |
999 | * (decrement counters to redo request, including |
1000 | * decrementing counters that will be incremented |
1001 | * by call to client_error() directly below) */ |
1002 | --client->stats->req_started; |
1003 | --client->stats->req_failed; |
1004 | --client->stats->req_error; |
1005 | --client->stats->req_done; |
1006 | } |
1007 | client_error(client); |
1008 | } |
1009 | } |
1010 | } |
1011 | } |
1012 | |
1013 | if (__builtin_expect( (client->revents & (POLLERR0x008|POLLHUP0x010)), 0)) { |
1014 | client->keepalive = 0; |
1015 | client_reset(client, 0); |
1016 | } |
1017 | |
1018 | while (client->revents & POLLOUT0x004) { |
1019 | ssize_t r; |
1020 | if (client->parser_state == PARSER_CONNECT && !client_connect(client)) |
1021 | continue; |
1022 | |
1023 | do { |
1024 | r = send(client->pfd->fd, |
1025 | client->request+client->request_offset, |
1026 | client->request_size - client->request_offset, |
1027 | MSG_DONTWAITMSG_DONTWAIT | MSG_NOSIGNALMSG_NOSIGNAL); |
1028 | } while (__builtin_expect( (-1 == r), 0) && errno(*__errno_location ()) == EINTR4); |
1029 | if (__builtin_expect( (r > 0), 1)) { |
1030 | if (client->request_size == (uint32_t)r |
1031 | || client->request_size==(client->request_offset+=(uint32_t)r)){ |
1032 | /* request sent; register read interest for response */ |
1033 | client->request_offset = 0; |
1034 | client->pfd->events |= POLLIN0x001; |
1035 | if (++client->pipelined < client->pipeline_max) |
1036 | continue; |
1037 | else { |
1038 | client->revents &= ~POLLOUT0x004; /*(trigger write() loop exit)*/ |
1039 | client->pfd->events &= ~POLLOUT0x004; |
1040 | } |
1041 | } |
1042 | else { |
1043 | client->revents &= ~POLLOUT0x004; /*(trigger write() loop exit)*/ |
1044 | client->pfd->events |= POLLOUT0x004; |
1045 | } |
1046 | } |
1047 | else { |
1048 | if (-1 == r) { /* error */ |
1049 | if (errno(*__errno_location ()) == EAGAIN11 |
1050 | #if EAGAIN11 != EWOULDBLOCK11 |
1051 | || errno(*__errno_location ()) == EWOULDBLOCK11 |
1052 | #endif |
1053 | ) { |
1054 | client->revents &= ~POLLOUT0x004; |
1055 | client->pfd->events |= POLLOUT0x004; |
1056 | break; |
1057 | } |
1058 | else |
1059 | client_perror(client, "write()"); |
1060 | } |
1061 | else { /* (0 == r); not expected; not attempting to write 0 bytes */ |
1062 | client->keepalive = 0; |
1063 | client_reset(client, 0); |
1064 | } |
1065 | } |
1066 | } |
1067 | } |
1068 | |
1069 | |
1070 | |
1071 | static void * |
1072 | worker_thread (void * const arg) |
1073 | { |
1074 | Worker worker; |
1075 | int i, nready; |
1076 | Worker_Config * const restrict wconf = (Worker_Config *)arg; |
1077 | worker_init(&worker, wconf); |
1078 | |
1079 | const int num_clients = wconf->num_clients; |
1080 | const int progress = |
1081 | (0==wconf->id && !wconf->config->quiet); /* report only in first thread */ |
1082 | const uint64_t progress_interval = /* print every 10% done */ |
1083 | (worker.stats.req_todo > 10) ? worker.stats.req_todo / 10 : 1; |
1084 | uint64_t progress_next = progress_interval; |
1085 | |
1086 | /* start all clients */ |
1087 | for (i = 0; i < num_clients; ++i) { |
1088 | if (worker.stats.req_started < worker.stats.req_todo) { |
1089 | worker.clients[i].revents = POLLOUT0x004; |
1090 | client_revents(worker.clients+i); |
1091 | } |
1092 | } |
1093 | |
1094 | while (worker.stats.req_done < worker.stats.req_todo) { |
1095 | do { /*(infinite wait)*/ |
1096 | nready = poll(worker.pfds, (nfds_t)num_clients, -1); |
1097 | } while (__builtin_expect( (-1 == nready), 0) && errno(*__errno_location ()) == EINTR4); |
1098 | if (__builtin_expect( (-1 == nready), 0)) { |
1099 | /*(repurpose client_perror(); use client buffer for strerror_r())*/ |
1100 | client_perror(worker.clients+0, "poll()"); /* fatal; ENOMEM */ |
1101 | return NULL((void*)0); |
1102 | } |
1103 | |
1104 | i = 0; |
1105 | do { |
1106 | while (0 == worker.pfds[i].revents) |
1107 | ++i; |
1108 | worker.clients[i].revents |= worker.pfds[i].revents; |
1109 | worker.pfds[i].revents = 0; |
1110 | client_revents(worker.clients+i); |
1111 | } while (--nready); |
1112 | |
1113 | if (progress) { |
1114 | /*(assume progress of one thread approximates that of all threads)*/ |
1115 | /*(RFE: main thread could poll and report progress of all workers)*/ |
1116 | while (__builtin_expect( worker.stats.req_done >= progress_next,0)){ |
1117 | printf("progress: %3d%% done\n", (int) |
1118 | (worker.stats.req_done * 100 / worker.stats.req_todo)); |
1119 | if (progress_next == worker.stats.req_todo) |
1120 | break; |
1121 | progress_next += progress_interval; |
1122 | if (__builtin_expect( progress_next > worker.stats.req_todo, 0)) |
1123 | progress_next = worker.stats.req_todo; |
1124 | } |
1125 | } |
1126 | } |
1127 | |
1128 | worker_delete(&worker, wconf); |
1129 | return NULL((void*)0); |
1130 | } |
1131 | |
1132 | |
1133 | __attribute_cold____attribute__((__cold__)) |
1134 | __attribute_noinline____attribute__ ((__noinline__)) |
1135 | static void |
1136 | config_error_diagnostic (const char * const restrict errfmt, |
1137 | const int perr, va_list ap) |
1138 | { |
1139 | const int errnum = errno(*__errno_location ()); |
1140 | show_version(); |
1141 | show_help(); |
1142 | fflush(stdoutstdout); |
1143 | |
1144 | fprintf(stderrstderr, "\nerror: "); |
1145 | vfprintf(stderrstderr, errfmt, ap); |
1146 | |
1147 | if (!perr) |
1148 | fprintf(stderrstderr, "\n\n"); |
1149 | else { |
1150 | char buf[1024]; |
1151 | buf[0] = '\0'; |
1152 | #if defined(_GNU_SOURCE1) && defined(__GLIBC__2) |
1153 | const char * const errstr = strerror_r(errnum, buf, sizeof(buf)); |
1154 | #else /* XSI-compliant strerror_r() */ |
1155 | const char * const errstr = buf; |
1156 | strerror_r(errnum, buf, sizeof(buf)); |
1157 | #endif |
1158 | |
1159 | fprintf(stderrstderr, ": (%d) %s\n\n", errnum, errstr); |
1160 | } |
1161 | } |
1162 | |
1163 | |
1164 | __attribute_cold____attribute__((__cold__)) |
1165 | __attribute_format__((__printf__, 1, 2))__attribute__((__format__ (__printf__, 1, 2))) |
1166 | __attribute_noinline____attribute__ ((__noinline__)) |
1167 | __attribute_noreturn____attribute__((__noreturn__)) |
1168 | static void |
1169 | config_error (const char * const restrict errfmt, ...) |
1170 | { |
1171 | va_list ap; |
1172 | va_start(ap, errfmt)__builtin_va_start(ap, errfmt); |
1173 | config_error_diagnostic(errfmt, 0, ap); |
1174 | va_end(ap)__builtin_va_end(ap); |
1175 | exit(1); |
1176 | } |
1177 | |
1178 | |
1179 | __attribute_cold____attribute__((__cold__)) |
1180 | __attribute_format__((__printf__, 1, 2))__attribute__((__format__ (__printf__, 1, 2))) |
1181 | __attribute_noinline____attribute__ ((__noinline__)) |
1182 | __attribute_noreturn____attribute__((__noreturn__)) |
1183 | static void |
1184 | config_perror (const char * const restrict errfmt, ...) |
1185 | { |
1186 | va_list ap; |
1187 | va_start(ap, errfmt)__builtin_va_start(ap, errfmt); |
1188 | config_error_diagnostic(errfmt, 1, ap); |
1189 | va_end(ap)__builtin_va_end(ap); |
1190 | exit(1); |
1191 | } |
1192 | |
1193 | |
1194 | typedef struct config_params { |
1195 | const char *method; |
1196 | const char *uri; |
1197 | char *laddrstr; |
1198 | int use_ipv6; |
1199 | int headers_num; |
1200 | int cookies_num; |
1201 | const char *headers[64]; |
1202 | const char *cookies[64]; |
1203 | const char *body_content_type; |
1204 | const char *body_filename; |
1205 | const char *authorization; |
1206 | const char *proxy_authorization; |
1207 | } config_params; |
1208 | |
1209 | |
1210 | __attribute_cold____attribute__((__cold__)) |
1211 | static int |
1212 | config_laddr (Config * const restrict config, |
1213 | const char * const restrict laddrstr) |
1214 | { |
1215 | struct addrinfo hints, *res = NULL((void*)0); |
1216 | memset(&hints, 0, sizeof(hints)); |
1217 | /*hints.ai_flags |= AI_NUMERICHOST;*/ |
1218 | hints.ai_family = config->raddr.ai_family; |
1219 | hints.ai_socktype = SOCK_STREAMSOCK_STREAM; |
1220 | |
1221 | if (0 != getaddrinfo(laddrstr, NULL((void*)0), &hints, &res) || NULL((void*)0) == res) |
1222 | return 0; |
1223 | |
1224 | config->laddr.ai_family = res->ai_family; |
1225 | config->laddr.ai_socktype = res->ai_socktype; |
1226 | config->laddr.ai_protocol = res->ai_protocol; |
1227 | config->laddr.ai_addrlen = res->ai_addrlen; |
1228 | config->laddr.ai_addr = (struct sockaddr *) |
1229 | memcpy(&config->laddr_storage, res->ai_addr, res->ai_addrlen); |
1230 | |
1231 | freeaddrinfo(res); |
1232 | return 1; |
1233 | } |
1234 | |
1235 | |
1236 | __attribute_cold____attribute__((__cold__)) |
1237 | static int |
1238 | config_laddrs (Config * const restrict config, |
1239 | char * const restrict laddrstr) |
1240 | { |
1241 | char *s; |
1242 | int num = 1; |
1243 | for (s = laddrstr; NULL((void*)0) != (s = strchr(s, ',')); s = s+1) ++num; |
1244 | if (1 == num) return config_laddr(config, laddrstr); |
1245 | |
1246 | struct addrinfo hints, **res; |
1247 | memset(&hints, 0, sizeof(hints)); |
1248 | /*hints.ai_flags |= AI_NUMERICHOST;*/ |
1249 | hints.ai_family = config->raddr.ai_family; |
1250 | hints.ai_socktype = SOCK_STREAMSOCK_STREAM; |
1251 | |
1252 | config->laddrs.num = num; |
1253 | config->laddrs.addrs = res = |
1254 | (struct addrinfo **)calloc((size_t)num, sizeof(struct addrinfo *)); |
1255 | |
1256 | s = laddrstr; |
1257 | for (int i = 0; i < num; ++i, ++res) { |
1258 | char *e = strchr(s, ','); |
1259 | if (NULL((void*)0) != e) *e = '\0'; |
1260 | |
1261 | *res = NULL((void*)0); |
1262 | if (0 != getaddrinfo(s, NULL((void*)0), &hints, res) || NULL((void*)0) == *res) |
1263 | return 0; /*(leave laddrstr modified so last addr is one w/ error)*/ |
1264 | |
1265 | if (NULL((void*)0) == e) break; |
1266 | *e = ','; |
1267 | s = e+1; |
1268 | } |
1269 | |
1270 | return 1; |
1271 | } |
1272 | |
1273 | |
1274 | __attribute_cold____attribute__((__cold__)) |
1275 | static void |
1276 | config_raddr (Config * const restrict config, |
1277 | const char * restrict hostname, uint16_t port, const int use_ipv6) |
1278 | { |
1279 | if (config->proxy && config->proxy[0] == '/') { |
1280 | #ifndef UNIX_PATH_MAX108 |
1281 | #define UNIX_PATH_MAX108 108 |
1282 | #endif |
1283 | const size_t len = strlen(config->proxy); |
1284 | if (len >= UNIX_PATH_MAX108) |
1285 | config_error("socket path too long: %s", config->proxy); |
1286 | |
1287 | config->raddr.ai_family = AF_UNIX1; |
1288 | config->raddr.ai_socktype = SOCK_STREAMSOCK_STREAM | SOCK_NONBLOCKSOCK_NONBLOCK; |
1289 | config->raddr.ai_protocol = 0; |
1290 | /* calculate effective SUN_LEN(); macro not always available)*/ |
1291 | config->raddr.ai_addrlen = |
1292 | (socklen_t)((size_t)(((struct sockaddr_un *) 0)->sun_path) + len); |
1293 | config->raddr.ai_addr = (struct sockaddr *)&config->raddr_storage; |
1294 | memset(&config->raddr_storage, 0, sizeof(config->raddr_storage)); |
1295 | config->raddr_storage.ss_family = AF_UNIX1; |
1296 | memcpy(((struct sockaddr_un *)&config->raddr_storage)->sun_path, |
1297 | config->proxy, len+1); |
1298 | return; |
1299 | } |
1300 | |
1301 | char host[1024]; /*(host should be < 256 chars)*/ |
1302 | if (config->proxy) { /* (&& config->proxy[0] != '/') */ |
1303 | char * const colon = strrchr(config->proxy, ':'); |
1304 | if (colon) { |
1305 | char *endptr; |
1306 | unsigned long i = strtoul(colon+1, &endptr, 10); |
1307 | if (*endptr == '\0' && 0 != i && i <= USHRT_MAX(32767 *2 +1)) |
1308 | port = (unsigned short)i; |
1309 | else /*(might mis-parse IPv6 addr which omitted port)*/ |
1310 | config_error("could not parse -X proxy: %s", config->proxy); |
1311 | |
1312 | const size_t len = (size_t)(colon - config->proxy); |
1313 | if (len >= sizeof(host)) |
1314 | config_error("proxy host path too long: %s", config->proxy); |
1315 | memcpy(host, config->proxy, len); |
1316 | host[len] = '\0'; |
1317 | hostname = host; |
1318 | } |
1319 | else { |
1320 | hostname = config->proxy; |
1321 | port = 80; /* default HTTP port */ |
1322 | } |
1323 | } |
1324 | |
1325 | struct addrinfo hints, *res, *res_first; |
1326 | memset(&hints, 0, sizeof(hints)); |
1327 | hints.ai_flags |= AI_NUMERICSERV0x0400; |
1328 | hints.ai_family = PF_UNSPEC0; |
1329 | hints.ai_socktype = SOCK_STREAMSOCK_STREAM; |
1330 | |
1331 | char port_str[6]; |
1332 | snprintf(port_str, sizeof(port_str), "%hu", port); |
1333 | |
1334 | if (0 != getaddrinfo(hostname, port_str, &hints, &res_first)) |
1335 | config_error("could not resolve hostname: %s", hostname); |
1336 | |
1337 | for (res = res_first; res != NULL((void*)0); res = res->ai_next) { |
1338 | if (res->ai_family == (use_ipv6 ? AF_INET610 : AF_INET2)) { |
1339 | config->raddr.ai_family = res->ai_family; |
1340 | config->raddr.ai_socktype = res->ai_socktype | SOCK_NONBLOCKSOCK_NONBLOCK; |
1341 | config->raddr.ai_protocol = res->ai_protocol; |
1342 | config->raddr.ai_addrlen = res->ai_addrlen; |
1343 | config->raddr.ai_addr = (struct sockaddr *) |
1344 | memcpy(&config->raddr_storage, res->ai_addr, res->ai_addrlen); |
1345 | break; |
1346 | } |
1347 | } |
1348 | |
1349 | freeaddrinfo(res_first); |
1350 | if (NULL((void*)0) == res) |
1351 | config_error("could not resolve hostname: %s", hostname); |
1352 | } |
1353 | |
1354 | |
1355 | __attribute_cold____attribute__((__cold__)) |
1356 | static int |
1357 | config_base64_encode_pad (char * const restrict dst, const size_t dstsz, |
1358 | const char * const restrict ssrc) |
1359 | { |
1360 | static const char base64_table[] = |
1361 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; |
1362 | |
1363 | const size_t srclen = strlen(ssrc); |
1364 | const int rem = (int)(srclen % 3); |
1365 | const int tuples = (int)(srclen / 3); |
1366 | const int tuplen = (int)(srclen - (size_t)rem); |
1367 | if (srclen > INT_MAX2147483647/2) /*(ridiculous size; prevent integer overflow)*/ |
1368 | return -1; |
1369 | if (dstsz < (size_t)(4*tuples + (rem ? 4 : 0) + 1)) |
1370 | return -1; |
1371 | |
1372 | int s = 0, d = 0; |
1373 | unsigned int v; |
1374 | const unsigned char * const src = (const unsigned char *)ssrc; |
1375 | for (; s < tuplen; s += 3, d += 4) { |
1376 | v = (src[s+0] << 16) | (src[s+1] << 8) | src[s+2]; |
1377 | dst[d+0] = base64_table[(v >> 18) & 0x3f]; |
1378 | dst[d+1] = base64_table[(v >> 12) & 0x3f]; |
1379 | dst[d+2] = base64_table[(v >> 6) & 0x3f]; |
1380 | dst[d+3] = base64_table[(v ) & 0x3f]; |
1381 | } |
1382 | |
1383 | if (rem) { |
1384 | if (1 == rem) { |
1385 | v = (src[s+0] << 4); |
1386 | dst[d+2] = base64_table[64]; /* pad */ |
1387 | } |
1388 | else { /*(2 == rem)*/ |
1389 | v = (src[s+0] << 10) | (src[s+1] << 2); |
1390 | dst[d+2] = base64_table[v & 0x3f]; v >>= 6; |
1391 | } |
1392 | dst[d+0] = base64_table[(v >> 6) & 0x3f]; |
1393 | dst[d+1] = base64_table[(v ) & 0x3f]; |
1394 | dst[d+3] = base64_table[64]; /* pad */ |
1395 | d += 4; |
1396 | } |
1397 | |
1398 | dst[d] = '\0'; |
1399 | return d; /*(base64-encoded string length; might be 0)*/ |
1400 | } |
1401 | |
1402 | |
1403 | __attribute_cold____attribute__((__cold__)) |
1404 | static void |
1405 | config_request (Config * const restrict config, |
1406 | const config_params * const restrict params) |
1407 | { |
1408 | const char * restrict uri = params->uri; |
1409 | uint16_t port = 80; |
1410 | uint16_t default_port = 80; |
1411 | char host[1024]; /*(host should be < 256 chars)*/ |
1412 | |
1413 | if (0 == strncmp(uri, "http://", sizeof("http://")-1)) |
1414 | uri += 7; |
1415 | else if (0 == strncmp(uri, "https://", sizeof("https://")-1)) { |
1416 | uri += 8; |
1417 | port = default_port = 443; |
Although the value stored to 'default_port' is used in the enclosing expression, the value is never actually read from 'default_port' | |
1418 | config_error("no ssl support yet"); |
1419 | } |
1420 | |
1421 | /* XXX: note that this is not a fully proper URI parse */ |
1422 | const char *c; |
1423 | if ((c = strchr(uri, ':'))) { /* found ':' => host:port */ |
1424 | if (c - uri + 1 > (int)sizeof(host)) |
1425 | config_error("host name in URI is too long"); |
1426 | memcpy(host, uri, c - uri); |
1427 | host[c - uri] = '\0'; |
1428 | |
1429 | char *endptr; |
1430 | unsigned long i = strtoul(c+1, &endptr, 10); |
1431 | if (0 != i && i <= USHRT_MAX(32767 *2 +1)) { |
1432 | port = (unsigned short)i; |
1433 | uri = endptr; |
1434 | } |
1435 | else |
1436 | config_error("could not parse URI"); |
1437 | } |
1438 | else { |
1439 | if ((c = strchr(uri, '/'))) { |
1440 | if (c - uri + 1 > (int)sizeof(host)) |
1441 | config_error("host name in URI is too long"); |
1442 | memcpy(host, uri, c - uri); |
1443 | host[c - uri] = '\0'; |
1444 | uri = c; |
1445 | } |
1446 | else { |
1447 | size_t len = strlen(uri); |
1448 | if (len + 1 > (int)sizeof(host)) |
1449 | config_error("host name in URI is too long"); |
1450 | memcpy(host, uri, len); |
1451 | host[len] = '\0'; |
1452 | uri += len; |
1453 | } |
1454 | } |
1455 | |
1456 | /* resolve hostname to sockaddr */ |
1457 | config_raddr(config, host, port, params->use_ipv6); |
1458 | |
1459 | int idx_host = -1; |
1460 | int idx_user_agent = -1; |
1461 | int idx_content_type = -1; |
1462 | int idx_content_length = -1; |
1463 | int idx_transfer_encoding = -1; |
1464 | const char * const * const restrict headers = params->headers; |
1465 | for (int i = 0; i < params->headers_num; i++) { |
1466 | if (0 == strncasecmp(headers[i],"Host:",sizeof("Host:")-1)) { |
1467 | if (-1 != idx_host) |
1468 | config_error("duplicate Host header"); |
1469 | idx_host = i; |
1470 | } |
1471 | if (0 == strncasecmp(headers[i],"User-Agent:",sizeof("User-Agent:")-1)) |
1472 | idx_user_agent = i; |
1473 | if (0 == strncasecmp(headers[i],"Connection:",sizeof("Connection:")-1)) |
1474 | config_error("Connection request header not allowed; " |
1475 | "use -k param to enable keep-alive"); |
1476 | if (0 == strncasecmp(headers[i],"Content-Type:", |
1477 | sizeof("Content-Type:")-1)) |
1478 | idx_content_type = i; |
1479 | if (0 == strncasecmp(headers[i],"Content-Length:", |
1480 | sizeof("Content-Length:")-1)) |
1481 | idx_content_length = i; |
1482 | if (0 == strncasecmp(headers[i],"Transfer-Encoding:", |
1483 | sizeof("Transfer-Encoding:")-1)) |
1484 | idx_transfer_encoding = i; |
1485 | } |
1486 | |
1487 | /*(simple 8k memaligned request buffer (part of struct Config))*/ |
1488 | config->request = |
1489 | (char *)((uintptr_t)(config->buf + (8*1024-1)) & ~(uintptr_t)(8*1024-1)); |
1490 | char * const restrict req = config->request; |
1491 | const size_t sz = sizeof(config->buf) >> 1; |
1492 | int offset = snprintf(req, sz, "%s %s HTTP/1.1\r\n", params->method, |
1493 | config->proxy && config->proxy[0] != '/' |
1494 | ? params->uri /*(provide full URI to proxy host)*/ |
1495 | : *uri != '\0' ? uri : "/"); |
1496 | if (offset >= (int)sz) |
1497 | config_error("request too large"); |
1498 | |
1499 | int len = (-1 != idx_host) |
1500 | ? snprintf(req+offset, sz-offset, "%s\r\n", headers[idx_host]) |
1501 | : (port == default_port) |
1502 | ? snprintf(req+offset, sz-offset, "Host: %s\r\n", host) |
1503 | : snprintf(req+offset, sz-offset, "Host: %s:%hu\r\n", host, port); |
1504 | if (len >= (int)sz - offset) |
1505 | config_error("request too large"); |
1506 | offset += len; |
1507 | |
1508 | if (!config->keep_alive) { |
1509 | len = sizeof("Connection: close\r\n")-1; |
1510 | if (len >= (int)sz - offset) |
1511 | config_error("request too large"); |
1512 | memcpy(req+offset, "Connection: close\r\n", len); |
1513 | offset += len; |
1514 | } |
1515 | |
1516 | int fd = -1; |
1517 | off_t fsize = 0; |
1518 | if (params->body_filename) { |
1519 | #ifndef O_BINARY0 |
1520 | #define O_BINARY0 0 |
1521 | #endif |
1522 | #ifndef O_LARGEFILE0 |
1523 | #define O_LARGEFILE0 0 |
1524 | #endif |
1525 | #ifndef O_NOATIME01000000 |
1526 | #define O_NOATIME01000000 0 |
1527 | #endif |
1528 | fd = open(params->body_filename, |
1529 | O_RDONLY00|O_BINARY0|O_LARGEFILE0|O_NOATIME01000000|O_NONBLOCK04000, 0); |
1530 | if (-1 == fd) |
1531 | config_perror("open(%s)", params->body_filename); |
1532 | struct stat st; |
1533 | if (0 != fstat(fd, &st)) |
1534 | config_perror("fstat(%s)", params->body_filename); |
1535 | fsize = st.st_size; |
1536 | if (fsize > UINT32_MAX(4294967295U) - (8*1024)) |
1537 | config_error("file size too large (not supported > ~4GB) (%s)", |
1538 | params->body_filename); |
1539 | |
1540 | /* If user specified Transfer-Encoding, trust that it is proper, |
1541 | * e.g. chunked, and that body_filename contains already-chunked data */ |
1542 | if (-1 == idx_transfer_encoding) { |
1543 | if (-1 == idx_content_length) { |
1544 | len = snprintf(req+offset, sz-offset, |
1545 | "Content-Length: %"PRId64"l" "d""\r\n", (int64_t)fsize); |
1546 | if (len >= (int)sz - offset) |
1547 | config_error("request too large"); |
1548 | offset += len; |
1549 | } /*(else trust user specified length matching body_filename size)*/ |
1550 | } |
1551 | else if (-1 != idx_content_length) |
1552 | config_error("Content-Length must be omitted " |
1553 | "if Transfer-Encoding provided"); |
1554 | |
1555 | if (params->body_content_type) { |
1556 | if (-1 == idx_content_type) |
1557 | config_error("Content-Type duplicated in -H and -T params"); |
1558 | len = snprintf(req+offset, sz-offset, |
1559 | "Content-Type: %s\r\n", params->body_content_type); |
1560 | if (len >= (int)sz - offset) |
1561 | config_error("request too large"); |
1562 | offset += len; |
1563 | } |
1564 | else if (-1 == idx_content_type) { |
1565 | len = sizeof("Content-Type: text/plain\r\n")-1; |
1566 | if (len >= (int)sz - offset) |
1567 | config_error("request too large"); |
1568 | memcpy(req+offset, "Content-Type: text/plain\r\n", len); |
1569 | offset += len; |
1570 | } |
1571 | } |
1572 | |
1573 | for (int i = 0; i < params->headers_num; ++i) { |
1574 | if (i == idx_host) |
1575 | continue; |
1576 | len = snprintf(req+offset, sz-offset, "%s\r\n", headers[i]); |
1577 | if (len >= (int)sz - offset) |
1578 | config_error("request too large"); |
1579 | offset += len; |
1580 | } |
1581 | |
1582 | if (params->authorization) { |
1583 | len = snprintf(req+offset, sz-offset, "Authorization: Basic "); |
1584 | if (len >= (int)sz - offset) |
1585 | config_error("request too large"); |
1586 | offset += len; |
1587 | |
1588 | len = config_base64_encode_pad(req+offset, sz-offset, |
1589 | params->authorization); |
1590 | if (len < 0) |
1591 | config_error("request too large"); |
1592 | offset += len; |
1593 | |
1594 | if (2 >= (int)sz - offset) |
1595 | config_error("request too large"); |
1596 | memcpy(req+offset, "\r\n", 3); |
1597 | offset += 2; |
1598 | } |
1599 | |
1600 | if (params->proxy_authorization) { |
1601 | len = snprintf(req+offset, sz-offset, "Proxy-Authorization: Basic "); |
1602 | if (len >= (int)sz - offset) |
1603 | config_error("request too large"); |
1604 | offset += len; |
1605 | |
1606 | len = config_base64_encode_pad(req+offset, sz-offset, |
1607 | params->proxy_authorization); |
1608 | if (len < 0) |
1609 | config_error("request too large"); |
1610 | offset += len; |
1611 | |
1612 | if (2 >= (int)sz - offset) |
1613 | config_error("request too large"); |
1614 | memcpy(req+offset, "\r\n", 3); |
1615 | offset += 2; |
1616 | } |
1617 | |
1618 | if (-1 == idx_user_agent) { |
1619 | len = sizeof("User-Agent: weighttp/" PACKAGE_VERSION"" "\r\n")-1; |
1620 | if (len >= (int)sz - offset) |
1621 | config_error("request too large"); |
1622 | memcpy(req+offset, |
1623 | "User-Agent: weighttp/" PACKAGE_VERSION"" "\r\n", len); |
1624 | offset += len; |
1625 | } |
1626 | |
1627 | const char * const * const restrict cookies = params->cookies; |
1628 | for (int i = 0; i < params->cookies_num; ++i) { |
1629 | len = snprintf(req+offset, sz-offset, "Cookie: %s\r\n",cookies[i]); |
1630 | if (len >= (int)sz - offset) |
1631 | config_error("request too large"); |
1632 | offset += len; |
1633 | } |
1634 | |
1635 | if (3 > (int)sz - offset) |
1636 | config_error("request too large"); |
1637 | memcpy(req+offset, "\r\n", 3); /*(including terminating '\0')*/ |
1638 | offset += 2; /*(not including terminating '\0')*/ |
1639 | |
1640 | config->request_size = (uint32_t)(offset + fsize); |
1641 | |
1642 | if (-1 != fd && 0 != fsize) { |
1643 | /*(not checking if file changed between fstat() and read())*/ |
1644 | /*(not using mmap() since we expect benchmark test file to be smallish |
1645 | * and able to fit in memory, or */ |
1646 | config->request = malloc(config->request_size); |
1647 | memcpy(config->request, req, (size_t)offset); |
1648 | off_t reqsz = offset; |
1649 | ssize_t rd; |
1650 | do { |
1651 | rd = read(fd, config->request+reqsz, config->request_size-reqsz); |
1652 | } while (rd > 0 ? (reqsz += rd) < config->request_size |
1653 | : (rd < 0 && errno(*__errno_location ()) == EINTR4)); |
1654 | if (reqsz != config->request_size) |
1655 | config_perror("read(%s)", params->body_filename); |
1656 | } |
1657 | } |
1658 | |
1659 | |
1660 | __attribute_cold____attribute__((__cold__)) |
1661 | __attribute_noinline____attribute__ ((__noinline__)) |
1662 | static void |
1663 | weighttp_setup (Config * const restrict config, const int argc, char *argv[]) |
1664 | { |
1665 | int opt_show_help = 0; |
1666 | int opt_show_version = 0; |
1667 | config_params params; |
1668 | memset(¶ms, 0, sizeof(params)); |
1669 | |
1670 | /* default settings */ |
1671 | config->thread_count = 1; |
1672 | config->concur_count = 1; |
1673 | config->req_count = 0; |
1674 | config->keep_alive = 0; |
1675 | config->proxy = NULL((void*)0); |
1676 | config->pipeline_max = 0; |
1677 | config->tcp_fastopen = 0; |
1678 | config->http_head = 0; |
1679 | config->so_bufsz = 0; |
1680 | config->quiet = 0; |
1681 | |
1682 | setlocale(LC_ALL6, "C"); |
1683 | signal(SIGPIPE13, SIG_IGN((__sighandler_t) 1)); |
1684 | |
1685 | const char * const optstr = ":hVikqdlr6Fm:n:t:c:b:p:u:A:B:C:H:K:P:T:X:"; |
1686 | int opt; |
1687 | while (-1 != (opt = getopt(argc, argv, optstr))) { |
1688 | switch (opt) { |
1689 | case '6': |
1690 | params.use_ipv6 = 1; |
1691 | break; |
1692 | case 'A': |
1693 | params.authorization = optarg; |
1694 | break; |
1695 | case 'B': |
1696 | params.laddrstr = optarg; |
1697 | break; |
1698 | case 'C': |
1699 | if (params.cookies_num == sizeof(params.cookies)/sizeof(char *)) |
1700 | config_error("too many cookies"); |
1701 | params.cookies[params.cookies_num++] = optarg; |
1702 | break; |
1703 | case 'F': |
1704 | config->tcp_fastopen = 1; |
1705 | break; |
1706 | case 'H': |
1707 | if (params.headers_num == sizeof(params.headers)/sizeof(char *)) |
1708 | config_error("too many headers"); |
1709 | params.headers[params.headers_num++] = optarg; |
1710 | break; |
1711 | case 'K': |
1712 | config->pipeline_max = (int)strtoul(optarg, NULL((void*)0), 10); |
1713 | if (config->pipeline_max >= 2) |
1714 | config->keep_alive = 1; |
1715 | break; |
1716 | case 'P': |
1717 | params.proxy_authorization = optarg; |
1718 | break; |
1719 | case 'T': |
1720 | params.body_content_type = optarg; |
1721 | break; |
1722 | case 'X': |
1723 | config->proxy = optarg; |
1724 | break; |
1725 | case 'b': |
1726 | config->so_bufsz = (int)strtoul(optarg, NULL((void*)0), 10); |
1727 | break; |
1728 | case 'c': |
1729 | config->concur_count = (int)strtoul(optarg, NULL((void*)0), 10); |
1730 | break; |
1731 | case 'i': |
1732 | config->http_head = 1; |
1733 | break; |
1734 | case 'k': |
1735 | config->keep_alive = 1; |
1736 | break; |
1737 | case 'm': |
1738 | params.method = optarg; |
1739 | config->http_head = (0 == strcasecmp(optarg, "HEAD")); |
1740 | break; |
1741 | case 'n': |
1742 | config->req_count = strtoull(optarg, NULL((void*)0), 10); |
1743 | break; |
1744 | case 'p': |
1745 | params.body_filename = optarg; |
1746 | params.method = "POST"; |
1747 | config->http_head = 0; |
1748 | break; |
1749 | case 'q': |
1750 | config->quiet = 1; |
1751 | break; |
1752 | case 'd': |
1753 | case 'l': |
1754 | case 'r': |
1755 | /*(ignored; compatibility with Apache Bench (ab))*/ |
1756 | break; |
1757 | case 't': |
1758 | config->thread_count = (int)strtoul(optarg, NULL((void*)0), 10); |
1759 | break; |
1760 | case 'u': |
1761 | params.body_filename = optarg; |
1762 | params.method = "PUT"; |
1763 | config->http_head = 0; |
1764 | break; |
1765 | case ':': |
1766 | config_error("option requires an argument: -%c", optopt); |
1767 | case '?': |
1768 | if ('?' != optopt) |
1769 | config_error("unknown option: -%c", optopt); |
1770 | /* fall through */ |
1771 | case 'h': |
1772 | opt_show_help = 1; |
1773 | /* fall through */ |
1774 | case 'V': |
1775 | opt_show_version = 1; |
1776 | break; |
1777 | } |
1778 | } |
1779 | |
1780 | if (opt_show_version || !config->quiet) |
1781 | show_version(); |
1782 | |
1783 | if (opt_show_help) |
1784 | show_help(); |
1785 | |
1786 | if (opt_show_version) |
1787 | exit(0); |
1788 | |
1789 | if ((argc - optind) < 1) |
1790 | config_error("missing URI argument"); |
1791 | else if ((argc - optind) > 1) |
1792 | config_error("too many arguments"); |
1793 | params.uri = argv[optind]; |
1794 | |
1795 | /* check for sane arguments */ |
1796 | if (!config->req_count) |
1797 | config_error("num of requests has to be > 0"); |
1798 | if (config->req_count == UINT64_MAX(18446744073709551615UL)) |
1799 | config_error("invalid req_count"); |
1800 | if (!config->thread_count) |
1801 | config_error("thread count has to be > 0"); |
1802 | if ((uint64_t)config->thread_count > config->req_count) |
1803 | config_error("thread_count > req_count"); |
1804 | if (!config->concur_count) |
1805 | config_error("num of concurrent clients has to be > 0"); |
1806 | if ((uint64_t)config->concur_count > config->req_count) |
1807 | config_error("concur_count > req_count"); |
1808 | if (config->thread_count > config->concur_count) |
1809 | config_error("thread_count > concur_count"); |
1810 | if (config->pipeline_max < 1) |
1811 | config->pipeline_max = 1; |
1812 | if (NULL((void*)0) == params.method) |
1813 | params.method = config->http_head ? "HEAD" : "GET"; |
1814 | |
1815 | config_request(config, ¶ms); |
1816 | |
1817 | config->laddr.ai_addrlen = 0; |
1818 | config->laddrs.addrs = NULL((void*)0); |
1819 | config->laddrs.num = 0; |
1820 | if (params.laddrstr && !config_laddrs(config, params.laddrstr)) |
1821 | config_error("could not resolve local bind address: %s", |
1822 | params.laddrstr); |
1823 | |
1824 | if (config->concur_count > 32768 && config->raddr.ai_family != AF_UNIX1) { |
1825 | int need = config->concur_count; |
1826 | int avail = 32768; |
1827 | int fd = open("/proc/sys/net/ipv4/ip_local_port_range", |
1828 | O_RDONLY00|O_BINARY0|O_LARGEFILE0|O_NONBLOCK04000, 0); |
1829 | if (fd >= 0) { |
1830 | char buf[32]; |
1831 | ssize_t rd = read(fd, buf, sizeof(buf)); |
1832 | if (rd >= 3 && rd < (ssize_t)sizeof(buf)) { |
1833 | long lb, ub; |
1834 | char *e; |
1835 | buf[rd] = '\0'; |
1836 | lb = strtoul(buf, &e, 10); |
1837 | if (lb > 0 && lb < USHRT_MAX(32767 *2 +1) && *e) { |
1838 | ub = strtoul(e, &e, 10); |
1839 | if (ub > 0 && ub <= USHRT_MAX(32767 *2 +1) && (*e=='\0' || *e=='\n')) { |
1840 | if (lb <= ub) |
1841 | avail = ub - lb + 1; |
1842 | } |
1843 | } |
1844 | } |
1845 | close(fd); |
1846 | } |
1847 | if (config->laddrs.num) |
1848 | need = (need + config->laddrs.num - 1) / config->laddrs.num; |
1849 | if (need > avail) |
1850 | config_error("not enough local ports for concurrency\n" |
1851 | "Reduce concur or provide -B addr,addr,addr " |
1852 | "to specify multiple local bind addrs"); |
1853 | } |
1854 | |
1855 | /* (see [RFC7413] 4.1.3. Client Cookie Handling) */ |
1856 | if ((config->proxy && config->proxy[0] == '/') |
1857 | || config->request_size > (params.use_ipv6 ? 1440 : 1460)) |
1858 | config->tcp_fastopen = 0; |
1859 | } |
1860 | |
1861 | |
1862 | __attribute_cold____attribute__((__cold__)) |
1863 | __attribute_noinline____attribute__ ((__noinline__)) |
1864 | static void |
1865 | weighttp_report (const Config * const restrict config) |
1866 | { |
1867 | /* collect worker stats and releaes resources */ |
1868 | Stats stats; |
1869 | memset(&stats, 0, sizeof(stats)); |
1870 | for (int i = 0; i < config->thread_count; ++i) { |
1871 | const Stats * const restrict wstats = &config->wconfs[i].stats; |
1872 | |
1873 | stats.req_started += wstats->req_started; |
1874 | stats.req_done += wstats->req_done; |
1875 | stats.req_success += wstats->req_success; |
1876 | stats.req_failed += wstats->req_failed; |
1877 | stats.bytes_total += wstats->bytes_total; |
1878 | stats.bytes_headers += wstats->bytes_headers; |
1879 | stats.req_2xx += wstats->req_2xx; |
1880 | stats.req_3xx += wstats->req_3xx; |
1881 | stats.req_4xx += wstats->req_4xx; |
1882 | stats.req_5xx += wstats->req_5xx; |
1883 | } |
1884 | |
1885 | /* report cumulative stats */ |
1886 | struct timeval tdiff; |
1887 | tdiff.tv_sec = config->ts_end.tv_sec - config->ts_start.tv_sec; |
1888 | tdiff.tv_usec = config->ts_end.tv_usec - config->ts_start.tv_usec; |
1889 | if (tdiff.tv_usec < 0) { |
1890 | --tdiff.tv_sec; |
1891 | tdiff.tv_usec += 1000000; |
1892 | } |
1893 | const uint64_t total_usecs = tdiff.tv_sec * 1000000 + tdiff.tv_usec; |
1894 | const uint64_t rps = stats.req_done * 1000000 / total_usecs; |
1895 | const uint64_t kbps= stats.bytes_total / 1024 * 1000000 / total_usecs; |
1896 | #if 1 /* JSON-style formatted output */ |
1897 | printf("{\n" |
1898 | " \"reqs_per_sec\": %"PRIu64"l" "u"",\n" |
1899 | " \"kBps_per_sec\": %"PRIu64"l" "u"",\n" |
1900 | " \"secs_elapsed\": %01d.%06ld,\n", |
1901 | rps, kbps, (int)tdiff.tv_sec, (long)tdiff.tv_usec); |
1902 | printf(" \"request_counts\": {\n" |
1903 | " \"started\": %"PRIu64"l" "u"",\n" |
1904 | " \"retired\": %"PRIu64"l" "u"",\n" |
1905 | " \"total\": %"PRIu64"l" "u""\n" |
1906 | " },\n", |
1907 | stats.req_started, stats.req_done, config->req_count); |
1908 | printf(" \"response_counts\": {\n" |
1909 | " \"pass\": %"PRIu64"l" "u"",\n" |
1910 | " \"fail\": %"PRIu64"l" "u"",\n" |
1911 | " \"errs\": %"PRIu64"l" "u""\n" |
1912 | " },\n", |
1913 | stats.req_success, stats.req_failed, stats.req_error); |
1914 | printf(" \"status_codes\": {\n" |
1915 | " \"2xx\": %"PRIu64"l" "u"",\n" |
1916 | " \"3xx\": %"PRIu64"l" "u"",\n" |
1917 | " \"4xx\": %"PRIu64"l" "u"",\n" |
1918 | " \"5xx\": %"PRIu64"l" "u""\n" |
1919 | " },\n", |
1920 | stats.req_2xx, stats.req_3xx, stats.req_4xx, stats.req_5xx); |
1921 | printf(" \"traffic\": {\n" |
1922 | " \"bytes_total\": %12."PRIu64"l" "u"",\n" |
1923 | " \"bytes_headers\": %12."PRIu64"l" "u"",\n" |
1924 | " \"bytes_body\": %12."PRIu64"l" "u""\n" |
1925 | " }\n" |
1926 | "}\n", |
1927 | stats.bytes_total, stats.bytes_headers, |
1928 | stats.bytes_total - stats.bytes_headers); |
1929 | #else |
1930 | printf("\nfinished in %01d.%06ld sec, %"PRIu64"l" "u"" req/s, %"PRIu64"l" "u"" kbyte/s\n", |
1931 | (int)tdiff.tv_sec, (long)tdiff.tv_usec, rps, kbps); |
1932 | |
1933 | printf("requests: %"PRIu64"l" "u"" started, %"PRIu64"l" "u"" done, %"PRIu64"l" "u"" total\n" |
1934 | "responses: %"PRIu64"l" "u"" success, %"PRIu64"l" "u"" fail, %"PRIu64"l" "u"" error\n", |
1935 | stats.req_started, stats.req_done, config->req_count, |
1936 | stats.req_success, stats.req_failed, stats.req_error); |
1937 | |
1938 | printf("status codes: " |
1939 | "%"PRIu64"l" "u"" 2xx, %"PRIu64"l" "u"" 3xx, %"PRIu64"l" "u"" 4xx, %"PRIu64"l" "u"" 5xx\n", |
1940 | stats.req_2xx, stats.req_3xx, stats.req_4xx, stats.req_5xx); |
1941 | |
1942 | printf("traffic: %"PRIu64"l" "u"" bytes total, %"PRIu64"l" "u"" bytes headers, " |
1943 | "%"PRIu64"l" "u"" bytes body\n", stats.bytes_total, |
1944 | stats.bytes_headers, stats.bytes_total - stats.bytes_headers); |
1945 | #endif |
1946 | } |
1947 | |
1948 | |
1949 | int main (int argc, char *argv[]) |
1950 | { |
1951 | Config config; |
1952 | weighttp_setup(&config, argc, argv); /* exits if error (or done) */ |
1953 | wconfs_init(&config); |
1954 | #if defined(__STDC_VERSION__199901L) && __STDC_VERSION__199901L-0 >= 199901L /* C99 */ |
1955 | pthread_t threads[config.thread_count]; /*(C99 dynamic array)*/ |
1956 | #else |
1957 | pthread_t * const restrict threads = |
1958 | (pthread_t *)calloc(config.thread_count, sizeof(pthread_t)); |
1959 | #endif |
1960 | |
1961 | if (!config.quiet) |
1962 | puts("starting benchmark..."); |
1963 | gettimeofday(&config.ts_start, NULL((void*)0)); |
1964 | |
1965 | for (int i = 0; i < config.thread_count; ++i) { |
1966 | int err = pthread_create(threads+i, NULL((void*)0), |
1967 | worker_thread, (void*)(config.wconfs+i)); |
1968 | if (__builtin_expect( (0 != err), 0)) { |
1969 | fprintf(stderrstderr, "error: failed spawning thread (%d)\n", err); |
1970 | /*(XXX: leaks resources and does not attempt pthread_cancel())*/ |
1971 | return 2; /* (unexpected) fatal error */ |
1972 | } |
1973 | } |
1974 | |
1975 | for (int i = 0; i < config.thread_count; ++i) { |
1976 | int err = pthread_join(threads[i], NULL((void*)0)); |
1977 | if (__builtin_expect( (0 != err), 0)) { |
1978 | fprintf(stderrstderr, "error: failed joining thread (%d)\n", err); |
1979 | /*(XXX: leaks resources and does not attempt pthread_cancel())*/ |
1980 | return 3; /* (unexpected) fatal error */ |
1981 | } |
1982 | } |
1983 | |
1984 | gettimeofday(&config.ts_end, NULL((void*)0)); |
1985 | |
1986 | #if !(defined(__STDC_VERSION__199901L) && __STDC_VERSION__199901L-0 >= 199901L) /* !C99 */ |
1987 | free(threads); |
1988 | #endif |
1989 | weighttp_report(&config); |
1990 | wconfs_delete(&config); |
1991 | |
1992 | return 0; |
1993 | } |