Bug Summary

File:bin/tools/weighttp.c
Warning:line 1103, column 13
Potential leak of memory pointed to by 'worker.pfds'

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