Bug Summary

File:bin/tools/weighttp.c
Warning:line 1416, column 9
Value stored to 'uri' is never read

Annotated Source Code

Press '?' to see keyboard shortcuts

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