Bug Summary

File:bin/tools/weighttp.c
Warning:line 460, column 5
Access to field 'req_done' results in a dereference of a null pointer (loaded from variable 'stats')

Annotated Source Code

Press '?' to see keyboard shortcuts

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