clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name weighttp.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -pic-is-pie -mframe-pointer=all -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -fno-plt -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools -resource-dir /usr/lib/clang/14.0.6 -include /home/buildbot/lwan-worker/clang-analyze/build/lwan-build-config.h -D _FILE_OFFSET_BITS=64 -D _TIME_BITS=64 -I /home/buildbot/lwan-worker/clang-analyze/build/src/lib/missing -I /usr/include/python3.10 -I /usr/include/luajit-2.1 -I /usr/include/valgrind -I /home/buildbot/lwan-worker/clang-analyze/build/src/lib -internal-isystem /usr/lib/clang/14.0.6/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/12.1.1/../../../../x86_64-pc-linux-gnu/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wno-unused-parameter -Wno-free-nonheap-object -std=gnu99 -fdebug-compilation-dir=/home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools -ferror-limit 19 -stack-protector 2 -fgnuc-version=4.2.1 -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/buildbot/lwan-worker/clang-analyze/CLANG/2022-08-14-233151-3822485-1 -x c /home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools/weighttp.c
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
14 | |
15 | #ifndef _GNU_SOURCE |
16 | #define _GNU_SOURCE 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.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_DONTWAIT |
50 | #define MSG_DONTWAIT 0 |
51 | #endif |
52 | #ifndef MSG_NOSIGNAL |
53 | #define MSG_NOSIGNAL 0 |
54 | #endif |
55 | #ifndef SOCK_NONBLOCK |
56 | #define SOCK_NONBLOCK 0 |
57 | #endif |
58 | |
59 | #ifndef PACKAGE_VERSION |
60 | #define PACKAGE_VERSION "" |
61 | #endif |
62 | |
63 | |
64 | |
65 | #if defined(__GNUC__) || defined(__clang__) |
66 | #ifndef __attribute_cold__ |
67 | #define __attribute_cold__ __attribute__((__cold__)) |
68 | #endif |
69 | #ifndef __attribute_hot__ |
70 | #define __attribute_hot__ __attribute__((__hot__)) |
71 | #endif |
72 | #ifndef __attribute_noinline__ |
73 | #define __attribute_noinline__ __attribute__((__noinline__)) |
74 | #endif |
75 | #ifndef __attribute_noreturn__ |
76 | #define __attribute_noreturn__ __attribute__((__noreturn__)) |
77 | #endif |
78 | #ifndef __attribute_pure__ |
79 | #define __attribute_pure__ __attribute__((__pure__)) |
80 | #endif |
81 | #ifndef __attribute_format__ |
82 | #define __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__ |
89 | #define __attribute_cold__ |
90 | #endif |
91 | #ifndef __attribute_hot__ |
92 | #define __attribute_hot__ |
93 | #endif |
94 | #ifndef __attribute_noinline__ |
95 | #define __attribute_noinline__ |
96 | #endif |
97 | #ifndef __attribute_noreturn__ |
98 | #define __attribute_noreturn__ |
99 | #endif |
100 | #ifndef __attribute_pure__ |
101 | #define __attribute_pure__ |
102 | #endif |
103 | #ifndef __attribute_format__ |
104 | #define __attribute_format__(x) |
105 | #endif |
106 | #endif |
107 | |
108 | |
109 | __attribute_cold__ |
110 | __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__ |
120 | __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 | |
158 | |
159 | |
160 | |
161 | |
162 | |
163 | |
164 | |
165 | |
166 | |
167 | |
168 | |
169 | |
170 | #define CLIENT_BUFFER_SIZE 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; |
187 | uint64_t req_started; |
188 | uint64_t req_done; |
189 | uint64_t req_success; |
190 | uint64_t req_failed; |
191 | uint64_t req_error; |
192 | uint64_t bytes_total; |
193 | uint64_t bytes_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; |
210 | uint32_t parser_offset; |
211 | uint32_t request_offset; |
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_SIZE]; |
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 | |
252 | |
253 | |
254 | |
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]; |
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__ |
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; |
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 | |
316 | |
317 | |
318 | } |
319 | |
320 | |
321 | __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__ |
331 | __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) |
| 2 | | Assuming field 'ai_addrlen' is 0 | |
|
| |
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) |
| 5 | | Assuming 'i' is >= 'num_clients' | |
|
| 6 | | Loop condition is false. Execution continues on line 351 | |
|
352 | client_init(worker, wconf->config, i); |
353 | } |
354 | |
355 | |
356 | __attribute_cold__ |
357 | __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 | |
366 | |
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__ |
383 | __attribute_noinline__ |
384 | static void |
385 | wconfs_init (Config * const restrict config) |
386 | { |
387 | |
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" 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__ |
423 | __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__ |
441 | static void |
442 | client_reset (Client * const restrict client, const int success) |
443 | { |
444 | |
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) ? POLLOUT : 0; |
454 | if (client->revents && client->keepalive) { |
455 | |
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_SIZE/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 | |
471 | |
472 | |
473 | |
474 | |
475 | |
476 | #endif |
477 | if (--client->pipelined && client->buffer_offset) |
478 | client->revents |= POLLIN; |
479 | } |
480 | else { |
481 | close(client->pfd->fd); |
482 | client->pfd->fd = -1; |
483 | client->pfd->events = 0; |
484 | |
485 | client->parser_state = PARSER_CONNECT; |
486 | } |
487 | } |
488 | |
489 | |
490 | __attribute_cold__ |
491 | __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 | |
498 | |
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__ |
510 | __attribute_noinline__ |
511 | static void |
512 | client_perror (Client * const restrict client, const char * const restrict tag) |
513 | { |
514 | const int errnum = errno; |
515 | client->buffer[0] = '\0'; |
516 | #if defined(_GNU_SOURCE) && defined(__GLIBC__) |
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(stderr, "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 | |
540 | } |
541 | |
542 | |
543 | __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 == EINTR); |
557 | |
558 | if (fd >= 0) { |
559 | #if !SOCK_NONBLOCK |
560 | fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR); |
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_UNIX) { |
570 | |
571 | |
572 | |
573 | |
574 | opt = 1; |
575 | setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); |
576 | } |
577 | |
578 | if (0 != client->so_bufsz) { |
579 | opt = client->so_bufsz; |
580 | if (0 != setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(opt))) |
581 | client_perror(client, "setsockopt() SO_SNDBUF"); |
582 | if (0 != setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &opt, sizeof(opt))) |
583 | client_perror(client, "setsockopt() SO_RCVBUF"); |
584 | } |
585 | |
586 | if (raddr->ai_family != AF_UNIX) { |
587 | |
588 | struct linger l = { .l_onoff = 1, .l_linger = 0 }; |
589 | if (0 != setsockopt(fd, SOL_SOCKET, SO_LINGER, &l, sizeof(l))) |
590 | client_perror(client, "setsockopt() SO_LINGER"); |
591 | } |
592 | |
593 | if (NULL != 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_FASTOPEN |
602 | ssize_t wr = 0; |
603 | if (client->tcp_fastopen) { |
604 | wr = sendto(fd, client->request, client->request_size, |
605 | MSG_FASTOPEN | MSG_DONTWAIT | MSG_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 |= POLLIN; |
611 | if (++client->pipelined == client->pipeline_max) { |
612 | client->revents &= ~POLLOUT; |
613 | client->pfd->events &= ~POLLOUT; |
614 | } |
615 | } |
616 | else |
617 | client->request_offset = (uint32_t)wr; |
618 | return 1; |
619 | } |
620 | else if (-1 == wr && errno == EOPNOTSUPP) |
621 | wr = 0; |
622 | else { |
623 | |
624 | |
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 == EINTR); |
634 | |
635 | if (0 != rc) { |
636 | switch (errno) { |
637 | case EINPROGRESS: |
638 | case EALREADY: |
639 | |
640 | client->revents &= ~POLLOUT; |
641 | client->pfd->events |= POLLOUT; |
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_SOCKET,SO_ERROR,&opt,&optlen) || 0 != opt) { |
653 | if (0 != opt) errno = opt; |
654 | client_perror(client, "connect() getsockopt()"); |
655 | return 0; |
656 | } |
657 | } |
658 | |
659 | |
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 | |
674 | |
675 | char *end = |
676 | memchr(str, '\n', client->buffer_offset - client->parser_offset); |
677 | if (!end) |
678 | return 1; |
679 | ++end; |
680 | |
681 | |
682 | |
683 | |
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 | |
702 | |
703 | |
704 | if (end + 2 > client->buffer + client->buffer_offset) { |
705 | client->chunk_size = -1; |
706 | return 1; |
707 | } |
708 | if (end[0] == '\r' && end[1] == '\n') |
709 | client->stats->bytes_headers += 2; |
710 | else |
711 | client->keepalive = 0; |
712 | client->parser_offset = end - client->buffer + 2; |
713 | client_reset(client, client->http_status_success); |
714 | return 0; |
715 | } |
716 | |
717 | client->parser_offset = end - client->buffer; |
718 | client->chunk_received = 0; |
719 | client->chunk_size += 2; |
720 | } |
721 | |
722 | |
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 | |
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); |
744 | |
745 | client->parser_offset = 0; |
746 | client->buffer_offset = 0; |
747 | return 1; |
748 | } |
749 | |
750 | |
751 | __attribute_hot__ |
752 | __attribute_pure__ |
753 | static uint64_t |
754 | client_parse_uint64 (const char * const restrict str) |
755 | { |
756 | |
757 | |
758 | |
759 | |
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__ |
770 | __attribute_noinline__ |
771 | static int |
772 | client_parse (Client * const restrict client) |
773 | { |
774 | char *end; |
775 | uint32_t len; |
776 | |
777 | |
778 | |
779 | |
780 | |
781 | switch (client->parser_state) { |
782 | |
783 | case PARSER_START: |
784 | |
785 | |
786 | |
787 | |
788 | end = memchr(client->buffer+client->parser_offset, '\n', |
789 | client->buffer_offset - client->parser_offset); |
790 | if (NULL != 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 |
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 | |
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 | |
828 | |
829 | case PARSER_HEADER: |
830 | |
831 | do { |
832 | const char *str = client->buffer+client->parser_offset; |
833 | |
834 | end = |
835 | memchr(str, '\n', client->buffer_offset - client->parser_offset); |
836 | if (NULL == end) |
837 | return 1; |
838 | len = (uint32_t)(end - str + 1); |
839 | client->stats->bytes_headers += len; |
840 | client->parser_offset += len; |
841 | |
842 | |
843 | |
844 | |
845 | |
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') |
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; |
882 | client->chunk_size = -1; |
883 | client->chunk_received = 0; |
884 | } |
885 | |
886 | } while (end[1] != '\r' || end[2] != '\n'); |
887 | |
888 | |
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 | |
897 | |
898 | case PARSER_BODY: |
899 | |
900 | |
901 | if (client->chunked) |
902 | return client_parse_chunks(client); |
903 | else { |
904 | |
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 { |
910 | client->parser_offset += client->content_length; |
911 | client_reset(client, client->http_status_success); |
912 | return 0; |
913 | } |
914 | } |
915 | |
916 | client->buffer_offset = 0; |
917 | client->parser_offset = 0; |
918 | return 1; |
919 | } |
920 | |
921 | case PARSER_CONNECT: |
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 & POLLIN) { |
934 | |
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_DONTWAIT); |
943 | } while (__builtin_expect( (-1 == r), 0) && errno == EINTR); |
944 | if (__builtin_expect( (r > 0), 1)) { |
945 | if (r < (ssize_t)(sizeof(client->buffer)-client->buffer_offset-1)) |
946 | client->revents &= ~POLLIN; |
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 | |
954 | |
955 | |
956 | |
957 | |
958 | if (__builtin_expect( |
959 | (client->buffer_offset == sizeof(client->buffer)-1), 0)) { |
960 | if (0 == client->parser_offset) { |
961 | client_error(client); |
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) { |
974 | if (errno == EAGAIN |
975 | #if EAGAIN != EWOULDBLOCK |
976 | || errno == EWOULDBLOCK |
977 | #endif |
978 | ) { |
979 | client->revents &= ~POLLIN; |
980 | client->pfd->events |= POLLIN; |
981 | break; |
982 | } |
983 | else |
984 | client_perror(client, "read()"); |
985 | } |
986 | else { |
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 | |
998 | |
999 | |
1000 | |
1001 | |
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 & (POLLERR|POLLHUP)), 0)) { |
1014 | client->keepalive = 0; |
1015 | client_reset(client, 0); |
1016 | } |
1017 | |
1018 | while (client->revents & POLLOUT) { |
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_DONTWAIT | MSG_NOSIGNAL); |
1028 | } while (__builtin_expect( (-1 == r), 0) && errno == EINTR); |
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 | |
1033 | client->request_offset = 0; |
1034 | client->pfd->events |= POLLIN; |
1035 | if (++client->pipelined < client->pipeline_max) |
1036 | continue; |
1037 | else { |
1038 | client->revents &= ~POLLOUT; |
1039 | client->pfd->events &= ~POLLOUT; |
1040 | } |
1041 | } |
1042 | else { |
1043 | client->revents &= ~POLLOUT; |
1044 | client->pfd->events |= POLLOUT; |
1045 | } |
1046 | } |
1047 | else { |
1048 | if (-1 == r) { |
1049 | if (errno == EAGAIN |
1050 | #if EAGAIN != EWOULDBLOCK |
1051 | || errno == EWOULDBLOCK |
1052 | #endif |
1053 | ) { |
1054 | client->revents &= ~POLLOUT; |
1055 | client->pfd->events |= POLLOUT; |
1056 | break; |
1057 | } |
1058 | else |
1059 | client_perror(client, "write()"); |
1060 | } |
1061 | else { |
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); |
| |
| 7 | | Returned allocated memory | |
|
1078 | |
1079 | const int num_clients = wconf->num_clients; |
1080 | const int progress = |
1081 | (0==wconf->id && !wconf->config->quiet); |
| 8 | | Assuming 0 is not equal to field 'id' | |
|
1082 | const uint64_t progress_interval = |
1083 | (worker.stats.req_todo > 10) ? worker.stats.req_todo / 10 : 1; |
| 9 | | Assuming field 'req_todo' is <= 10 | |
|
| |
1084 | uint64_t progress_next = progress_interval; |
1085 | |
1086 | |
1087 | for (i = 0; i < num_clients; ++i) { |
| 11 | | Loop condition is false. Execution continues on line 1094 | |
|
1088 | if (worker.stats.req_started < worker.stats.req_todo) { |
1089 | worker.clients[i].revents = POLLOUT; |
1090 | client_revents(worker.clients+i); |
1091 | } |
1092 | } |
1093 | |
1094 | while (worker.stats.req_done < worker.stats.req_todo) { |
| 12 | | Assuming field 'req_done' is < field 'req_todo' | |
|
| 13 | | Loop condition is true. Entering loop body | |
|
1095 | do { |
| 16 | | Loop condition is false. Exiting loop | |
|
1096 | nready = poll(worker.pfds, (nfds_t)num_clients, -1); |
1097 | } while (__builtin_expect( (-1 == nready), 0) && errno == EINTR); |
| 14 | | Assuming the condition is true | |
|
| 15 | | Assuming the condition is false | |
|
1098 | if (__builtin_expect( (-1 == nready), 0)) { |
| |
1099 | |
1100 | client_perror(worker.clients+0, "poll()"); |
| 18 | | Potential leak of memory pointed to by 'worker.pfds' |
|
1101 | return NULL; |
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 | |
1115 | |
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; |
1130 | } |
1131 | |
1132 | |
1133 | __attribute_cold__ |
1134 | __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; |
1140 | show_version(); |
1141 | show_help(); |
1142 | fflush(stdout); |
1143 | |
1144 | fprintf(stderr, "\nerror: "); |
1145 | vfprintf(stderr, errfmt, ap); |
1146 | |
1147 | if (!perr) |
1148 | fprintf(stderr, "\n\n"); |
1149 | else { |
1150 | char buf[1024]; |
1151 | buf[0] = '\0'; |
1152 | #if defined(_GNU_SOURCE) && defined(__GLIBC__) |
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(stderr, ": (%d) %s\n\n", errnum, errstr); |
1160 | } |
1161 | } |
1162 | |
1163 | |
1164 | __attribute_cold__ |
1165 | __attribute_format__((__printf__, 1, 2)) |
1166 | __attribute_noinline__ |
1167 | __attribute_noreturn__ |
1168 | static void |
1169 | config_error (const char * const restrict errfmt, ...) |
1170 | { |
1171 | va_list ap; |
1172 | va_start(ap, errfmt); |
1173 | config_error_diagnostic(errfmt, 0, ap); |
1174 | va_end(ap); |
1175 | exit(1); |
1176 | } |
1177 | |
1178 | |
1179 | __attribute_cold__ |
1180 | __attribute_format__((__printf__, 1, 2)) |
1181 | __attribute_noinline__ |
1182 | __attribute_noreturn__ |
1183 | static void |
1184 | config_perror (const char * const restrict errfmt, ...) |
1185 | { |
1186 | va_list ap; |
1187 | va_start(ap, errfmt); |
1188 | config_error_diagnostic(errfmt, 1, ap); |
1189 | 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__ |
1211 | static int |
1212 | config_laddr (Config * const restrict config, |
1213 | const char * const restrict laddrstr) |
1214 | { |
1215 | struct addrinfo hints, *res = NULL; |
1216 | memset(&hints, 0, sizeof(hints)); |
1217 | |
1218 | hints.ai_family = config->raddr.ai_family; |
1219 | hints.ai_socktype = SOCK_STREAM; |
1220 | |
1221 | if (0 != getaddrinfo(laddrstr, NULL, &hints, &res) || NULL == 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__ |
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 != (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 | |
1249 | hints.ai_family = config->raddr.ai_family; |
1250 | hints.ai_socktype = SOCK_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 != e) *e = '\0'; |
1260 | |
1261 | *res = NULL; |
1262 | if (0 != getaddrinfo(s, NULL, &hints, res) || NULL == *res) |
1263 | return 0; |
1264 | |
1265 | if (NULL == e) break; |
1266 | *e = ','; |
1267 | s = e+1; |
1268 | } |
1269 | |
1270 | return 1; |
1271 | } |
1272 | |
1273 | |
1274 | __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_MAX |
1281 | #define UNIX_PATH_MAX 108 |
1282 | #endif |
1283 | const size_t len = strlen(config->proxy); |
1284 | if (len >= UNIX_PATH_MAX) |
1285 | config_error("socket path too long: %s", config->proxy); |
1286 | |
1287 | config->raddr.ai_family = AF_UNIX; |
1288 | config->raddr.ai_socktype = SOCK_STREAM | SOCK_NONBLOCK; |
1289 | config->raddr.ai_protocol = 0; |
1290 | |
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_UNIX; |
1296 | memcpy(((struct sockaddr_un *)&config->raddr_storage)->sun_path, |
1297 | config->proxy, len+1); |
1298 | return; |
1299 | } |
1300 | |
1301 | char host[1024]; |
1302 | if (config->proxy) { |
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) |
1308 | port = (unsigned short)i; |
1309 | else |
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; |
1322 | } |
1323 | } |
1324 | |
1325 | struct addrinfo hints, *res, *res_first; |
1326 | memset(&hints, 0, sizeof(hints)); |
1327 | hints.ai_flags |= AI_NUMERICSERV; |
1328 | hints.ai_family = PF_UNSPEC; |
1329 | hints.ai_socktype = SOCK_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; res = res->ai_next) { |
1338 | if (res->ai_family == (use_ipv6 ? AF_INET6 : AF_INET)) { |
1339 | config->raddr.ai_family = res->ai_family; |
1340 | config->raddr.ai_socktype = res->ai_socktype | SOCK_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 == res) |
1351 | config_error("could not resolve hostname: %s", hostname); |
1352 | } |
1353 | |
1354 | |
1355 | __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_MAX/2) |
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]; |
1387 | } |
1388 | else { |
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]; |
1395 | d += 4; |
1396 | } |
1397 | |
1398 | dst[d] = '\0'; |
1399 | return d; |
1400 | } |
1401 | |
1402 | |
1403 | __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]; |
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; |
1418 | config_error("no ssl support yet"); |
1419 | } |
1420 | |
1421 | |
1422 | const char *c; |
1423 | if ((c = strchr(uri, ':'))) { |
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) { |
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 | |
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 | |
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 |
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_BINARY |
1520 | #define O_BINARY 0 |
1521 | #endif |
1522 | #ifndef O_LARGEFILE |
1523 | #define O_LARGEFILE 0 |
1524 | #endif |
1525 | #ifndef O_NOATIME |
1526 | #define O_NOATIME 0 |
1527 | #endif |
1528 | fd = open(params->body_filename, |
1529 | O_RDONLY|O_BINARY|O_LARGEFILE|O_NOATIME|O_NONBLOCK, 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 - (8*1024)) |
1537 | config_error("file size too large (not supported > ~4GB) (%s)", |
1538 | params->body_filename); |
1539 | |
1540 | |
1541 | |
1542 | if (-1 == idx_transfer_encoding) { |
1543 | if (-1 == idx_content_length) { |
1544 | len = snprintf(req+offset, sz-offset, |
1545 | "Content-Length: %"PRId64"\r\n", (int64_t)fsize); |
1546 | if (len >= (int)sz - offset) |
1547 | config_error("request too large"); |
1548 | offset += len; |
1549 | } |
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); |
1638 | offset += 2; |
1639 | |
1640 | config->request_size = (uint32_t)(offset + fsize); |
1641 | |
1642 | if (-1 != fd && 0 != fsize) { |
1643 | |
1644 | |
1645 | |
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 == EINTR)); |
1654 | if (reqsz != config->request_size) |
1655 | config_perror("read(%s)", params->body_filename); |
1656 | } |
1657 | } |
1658 | |
1659 | |
1660 | __attribute_cold__ |
1661 | __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 | |
1671 | config->thread_count = 1; |
1672 | config->concur_count = 1; |
1673 | config->req_count = 0; |
1674 | config->keep_alive = 0; |
1675 | config->proxy = NULL; |
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_ALL, "C"); |
1683 | signal(SIGPIPE, SIG_IGN); |
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, 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, 10); |
1727 | break; |
1728 | case 'c': |
1729 | config->concur_count = (int)strtoul(optarg, NULL, 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, 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 | |
1756 | break; |
1757 | case 't': |
1758 | config->thread_count = (int)strtoul(optarg, NULL, 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 | |
1771 | case 'h': |
1772 | opt_show_help = 1; |
1773 | |
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 | |
1796 | if (!config->req_count) |
1797 | config_error("num of requests has to be > 0"); |
1798 | if (config->req_count == UINT64_MAX) |
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 == 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; |
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_UNIX) { |
1825 | int need = config->concur_count; |
1826 | int avail = 32768; |
1827 | int fd = open("/proc/sys/net/ipv4/ip_local_port_range", |
1828 | O_RDONLY|O_BINARY|O_LARGEFILE|O_NONBLOCK, 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 && *e) { |
1838 | ub = strtoul(e, &e, 10); |
1839 | if (ub > 0 && ub <= USHRT_MAX && (*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 | |
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__ |
1863 | __attribute_noinline__ |
1864 | static void |
1865 | weighttp_report (const Config * const restrict config) |
1866 | { |
1867 | |
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 | |
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",\n" |
1899 | " \"kBps_per_sec\": %"PRIu64",\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",\n" |
1904 | " \"retired\": %"PRIu64",\n" |
1905 | " \"total\": %"PRIu64"\n" |
1906 | " },\n", |
1907 | stats.req_started, stats.req_done, config->req_count); |
1908 | printf(" \"response_counts\": {\n" |
1909 | " \"pass\": %"PRIu64",\n" |
1910 | " \"fail\": %"PRIu64",\n" |
1911 | " \"errs\": %"PRIu64"\n" |
1912 | " },\n", |
1913 | stats.req_success, stats.req_failed, stats.req_error); |
1914 | printf(" \"status_codes\": {\n" |
1915 | " \"2xx\": %"PRIu64",\n" |
1916 | " \"3xx\": %"PRIu64",\n" |
1917 | " \"4xx\": %"PRIu64",\n" |
1918 | " \"5xx\": %"PRIu64"\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",\n" |
1923 | " \"bytes_headers\": %12."PRIu64",\n" |
1924 | " \"bytes_body\": %12."PRIu64"\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" req/s, %"PRIu64" kbyte/s\n", |
1931 | (int)tdiff.tv_sec, (long)tdiff.tv_usec, rps, kbps); |
1932 | |
1933 | printf("requests: %"PRIu64" started, %"PRIu64" done, %"PRIu64" total\n" |
1934 | "responses: %"PRIu64" success, %"PRIu64" fail, %"PRIu64" 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" 2xx, %"PRIu64" 3xx, %"PRIu64" 4xx, %"PRIu64" 5xx\n", |
1940 | stats.req_2xx, stats.req_3xx, stats.req_4xx, stats.req_5xx); |
1941 | |
1942 | printf("traffic: %"PRIu64" bytes total, %"PRIu64" bytes headers, " |
1943 | "%"PRIu64" 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); |
1953 | wconfs_init(&config); |
1954 | #if defined(__STDC_VERSION__) && __STDC_VERSION__-0 >= 199901L /* C99 */ |
1955 | pthread_t threads[config.thread_count]; |
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); |
1964 | |
1965 | for (int i = 0; i < config.thread_count; ++i) { |
1966 | int err = pthread_create(threads+i, NULL, |
1967 | worker_thread, (void*)(config.wconfs+i)); |
1968 | if (__builtin_expect( (0 != err), 0)) { |
1969 | fprintf(stderr, "error: failed spawning thread (%d)\n", err); |
1970 | |
1971 | return 2; |
1972 | } |
1973 | } |
1974 | |
1975 | for (int i = 0; i < config.thread_count; ++i) { |
1976 | int err = pthread_join(threads[i], NULL); |
1977 | if (__builtin_expect( (0 != err), 0)) { |
1978 | fprintf(stderr, "error: failed joining thread (%d)\n", err); |
1979 | |
1980 | return 3; |
1981 | } |
1982 | } |
1983 | |
1984 | gettimeofday(&config.ts_end, NULL); |
1985 | |
1986 | #if !(defined(__STDC_VERSION__) && __STDC_VERSION__-0 >= 199901L) /* !C99 */ |
1987 | free(threads); |
1988 | #endif |
1989 | weighttp_report(&config); |
1990 | wconfs_delete(&config); |
1991 | |
1992 | return 0; |
1993 | } |