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