mirror of
https://github.com/wg/wrk
synced 2026-06-10 00:55:51 +08:00
Compare commits
20 Commits
@@ -1 +1,2 @@
|
|||||||
obj/*
|
obj/*
|
||||||
|
wrk
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
CFLAGS := -std=c99 -Wall -O2 -pthread
|
CFLAGS := -std=c99 -Wall -O2 -D_REENTRANT
|
||||||
LDFLAGS := -pthread
|
LIBS := -lpthread -lm -lcrypto -lssl
|
||||||
LIBS := -lm
|
|
||||||
|
|
||||||
SRC := wrk.c aprintf.c stats.c units.c ae.c zmalloc.c http_parser.c tinymt64.c
|
TARGET := $(shell uname -s | tr [A-Z] [a-z] 2>/dev/null || echo unknown)
|
||||||
|
|
||||||
|
ifeq ($(TARGET), sunos)
|
||||||
|
CFLAGS += -D_PTHREADS -D_POSIX_C_SOURCE=200112L
|
||||||
|
LIBS += -lsocket
|
||||||
|
endif
|
||||||
|
|
||||||
|
SRC := wrk.c net.c ssl.c aprintf.c stats.c units.c ae.c zmalloc.c http_parser.c tinymt64.c
|
||||||
BIN := wrk
|
BIN := wrk
|
||||||
|
|
||||||
ODIR := obj
|
ODIR := obj
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ This product includes software developed by Salvatore Sanfilippo and
|
|||||||
other contributors to the redis project.
|
other contributors to the redis project.
|
||||||
|
|
||||||
Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
Copyright (C) 2009 Harish Mallipeddi - harish.mallipeddi@gmail.com
|
Copyright (C) 2009 Harish Mallipeddi - harish.mallipeddi@gmail.com
|
||||||
|
Copyright (c) 2012, Joyent, Inc. All rights reserved.
|
||||||
|
|
||||||
Copyright (c) 2006-2009, Salvatore Sanfilippo
|
Copyright (c) 2006-2009, Salvatore Sanfilippo
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|||||||
@@ -6,21 +6,21 @@ wrk - a HTTP benchmarking tool
|
|||||||
|
|
||||||
Basic Usage
|
Basic Usage
|
||||||
|
|
||||||
wrk -t8 -c400 -r10m http://localhost:8080/index.html
|
wrk -t12 -c400 -d30s http://127.0.0.1:8080/index.html
|
||||||
|
|
||||||
This runs wrk with 8 threads, keeping 400 connections open, and making a
|
This runs a benchmark for 30 seconds, using 12 threads, and keeping
|
||||||
total of 10 million HTTP GET requests to http://localhost:8080/index.html
|
400 HTTP connections open.
|
||||||
|
|
||||||
Output:
|
Output:
|
||||||
|
|
||||||
Making 10000000 requests to http://localhost:8080/index.html
|
Running 30s test @ http://127.0.0.1:8080/index.html
|
||||||
8 threads and 400 connections
|
12 threads and 400 connections
|
||||||
Thread Stats Avg Stdev Max +/- Stdev
|
Thread Stats Avg Stdev Max +/- Stdev
|
||||||
Latency 439.75us 350.49us 7.60ms 92.88%
|
Latency 635.91us 0.89ms 12.92ms 93.69%
|
||||||
Req/Sec 61.13k 8.26k 72.00k 87.54%
|
Req/Sec 56.20k 8.07k 62.00k 86.54%
|
||||||
10000088 requests in 19.87s, 3.42GB read
|
22464657 requests in 30.00s, 17.76GB read
|
||||||
Requests/sec: 503396.23
|
Requests/sec: 748868.53
|
||||||
Transfer/sec: 176.16MB
|
Transfer/sec: 606.33MB
|
||||||
|
|
||||||
Benchmarking Tips
|
Benchmarking Tips
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,10 @@
|
|||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <poll.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
#include "ae.h"
|
#include "ae.h"
|
||||||
#include "zmalloc.h"
|
#include "zmalloc.h"
|
||||||
@@ -43,13 +46,17 @@
|
|||||||
|
|
||||||
/* Include the best multiplexing layer supported by this system.
|
/* Include the best multiplexing layer supported by this system.
|
||||||
* The following should be ordered by performances, descending. */
|
* The following should be ordered by performances, descending. */
|
||||||
#ifdef HAVE_EPOLL
|
#ifdef HAVE_EVPORT
|
||||||
#include "ae_epoll.c"
|
#include "ae_evport.c"
|
||||||
#else
|
#else
|
||||||
#ifdef HAVE_KQUEUE
|
#ifdef HAVE_EPOLL
|
||||||
#include "ae_kqueue.c"
|
#include "ae_epoll.c"
|
||||||
#else
|
#else
|
||||||
#include "ae_select.c"
|
#ifdef HAVE_KQUEUE
|
||||||
|
#include "ae_kqueue.c"
|
||||||
|
#else
|
||||||
|
#include "ae_select.c"
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -62,6 +69,7 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
|
|||||||
eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
|
eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize);
|
||||||
if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
|
if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
|
||||||
eventLoop->setsize = setsize;
|
eventLoop->setsize = setsize;
|
||||||
|
eventLoop->lastTime = time(NULL);
|
||||||
eventLoop->timeEventHead = NULL;
|
eventLoop->timeEventHead = NULL;
|
||||||
eventLoop->timeEventNextId = 0;
|
eventLoop->timeEventNextId = 0;
|
||||||
eventLoop->stop = 0;
|
eventLoop->stop = 0;
|
||||||
@@ -97,7 +105,10 @@ void aeStop(aeEventLoop *eventLoop) {
|
|||||||
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
|
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
|
||||||
aeFileProc *proc, void *clientData)
|
aeFileProc *proc, void *clientData)
|
||||||
{
|
{
|
||||||
if (fd >= eventLoop->setsize) return AE_ERR;
|
if (fd >= eventLoop->setsize) {
|
||||||
|
errno = ERANGE;
|
||||||
|
return AE_ERR;
|
||||||
|
}
|
||||||
aeFileEvent *fe = &eventLoop->events[fd];
|
aeFileEvent *fe = &eventLoop->events[fd];
|
||||||
|
|
||||||
if (aeApiAddEvent(eventLoop, fd, mask) == -1)
|
if (aeApiAddEvent(eventLoop, fd, mask) == -1)
|
||||||
@@ -231,6 +242,24 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
|
|||||||
int processed = 0;
|
int processed = 0;
|
||||||
aeTimeEvent *te;
|
aeTimeEvent *te;
|
||||||
long long maxId;
|
long long maxId;
|
||||||
|
time_t now = time(NULL);
|
||||||
|
|
||||||
|
/* If the system clock is moved to the future, and then set back to the
|
||||||
|
* right value, time events may be delayed in a random way. Often this
|
||||||
|
* means that scheduled operations will not be performed soon enough.
|
||||||
|
*
|
||||||
|
* Here we try to detect system clock skews, and force all the time
|
||||||
|
* events to be processed ASAP when this happens: the idea is that
|
||||||
|
* processing events earlier is less dangerous than delaying them
|
||||||
|
* indefinitely, and practice suggests it is. */
|
||||||
|
if (now < eventLoop->lastTime) {
|
||||||
|
te = eventLoop->timeEventHead;
|
||||||
|
while(te) {
|
||||||
|
te->when_sec = 0;
|
||||||
|
te = te->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eventLoop->lastTime = now;
|
||||||
|
|
||||||
te = eventLoop->timeEventHead;
|
te = eventLoop->timeEventHead;
|
||||||
maxId = eventLoop->timeEventNextId-1;
|
maxId = eventLoop->timeEventNextId-1;
|
||||||
@@ -369,21 +398,19 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
|
|||||||
/* Wait for millseconds until the given file descriptor becomes
|
/* Wait for millseconds until the given file descriptor becomes
|
||||||
* writable/readable/exception */
|
* writable/readable/exception */
|
||||||
int aeWait(int fd, int mask, long long milliseconds) {
|
int aeWait(int fd, int mask, long long milliseconds) {
|
||||||
struct timeval tv;
|
struct pollfd pfd;
|
||||||
fd_set rfds, wfds, efds;
|
|
||||||
int retmask = 0, retval;
|
int retmask = 0, retval;
|
||||||
|
|
||||||
tv.tv_sec = milliseconds/1000;
|
memset(&pfd, 0, sizeof(pfd));
|
||||||
tv.tv_usec = (milliseconds%1000)*1000;
|
pfd.fd = fd;
|
||||||
FD_ZERO(&rfds);
|
if (mask & AE_READABLE) pfd.events |= POLLIN;
|
||||||
FD_ZERO(&wfds);
|
if (mask & AE_WRITABLE) pfd.events |= POLLOUT;
|
||||||
FD_ZERO(&efds);
|
|
||||||
|
|
||||||
if (mask & AE_READABLE) FD_SET(fd,&rfds);
|
if ((retval = poll(&pfd, 1, milliseconds))== 1) {
|
||||||
if (mask & AE_WRITABLE) FD_SET(fd,&wfds);
|
if (pfd.revents & POLLIN) retmask |= AE_READABLE;
|
||||||
if ((retval = select(fd+1, &rfds, &wfds, &efds, &tv)) > 0) {
|
if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE;
|
||||||
if (FD_ISSET(fd,&rfds)) retmask |= AE_READABLE;
|
if (pfd.revents & POLLERR) retmask |= AE_WRITABLE;
|
||||||
if (FD_ISSET(fd,&wfds)) retmask |= AE_WRITABLE;
|
if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE;
|
||||||
return retmask;
|
return retmask;
|
||||||
} else {
|
} else {
|
||||||
return retval;
|
return retval;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
|
* for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
|
||||||
* it in form of a library for easy reuse.
|
* it in form of a library for easy reuse.
|
||||||
*
|
*
|
||||||
* Copyright (c) 2006-2010, Salvatore Sanfilippo <antirez at gmail dot com>
|
* Copyright (c) 2006-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* Redistribution and use in source and binary forms, with or without
|
* Redistribution and use in source and binary forms, with or without
|
||||||
@@ -88,6 +88,7 @@ typedef struct aeEventLoop {
|
|||||||
int maxfd; /* highest file descriptor currently registered */
|
int maxfd; /* highest file descriptor currently registered */
|
||||||
int setsize; /* max number of file descriptors tracked */
|
int setsize; /* max number of file descriptors tracked */
|
||||||
long long timeEventNextId;
|
long long timeEventNextId;
|
||||||
|
time_t lastTime; /* Used to detect system clock skew */
|
||||||
aeFileEvent *events; /* Registered events */
|
aeFileEvent *events; /* Registered events */
|
||||||
aeFiredEvent *fired; /* Fired events */
|
aeFiredEvent *fired; /* Fired events */
|
||||||
aeTimeEvent *timeEventHead;
|
aeTimeEvent *timeEventHead;
|
||||||
|
|||||||
+31
-2
@@ -1,6 +1,33 @@
|
|||||||
/* Linux epoll(2) based ae.c module
|
/* Linux epoll(2) based ae.c module
|
||||||
* Copyright (C) 2009-2010 Salvatore Sanfilippo - antirez@gmail.com
|
*
|
||||||
* Released under the BSD license. See the COPYING file for more info. */
|
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include <sys/epoll.h>
|
#include <sys/epoll.h>
|
||||||
|
|
||||||
@@ -89,6 +116,8 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
|
|||||||
|
|
||||||
if (e->events & EPOLLIN) mask |= AE_READABLE;
|
if (e->events & EPOLLIN) mask |= AE_READABLE;
|
||||||
if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
|
if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
|
||||||
|
if (e->events & EPOLLERR) mask |= AE_WRITABLE;
|
||||||
|
if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
|
||||||
eventLoop->fired[j].fd = e->data.fd;
|
eventLoop->fired[j].fd = e->data.fd;
|
||||||
eventLoop->fired[j].mask = mask;
|
eventLoop->fired[j].mask = mask;
|
||||||
}
|
}
|
||||||
|
|||||||
+315
@@ -0,0 +1,315 @@
|
|||||||
|
/* ae.c module for illumos event ports.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2012, Joyent, Inc. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <port.h>
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static int evport_debug = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file implements the ae API using event ports, present on Solaris-based
|
||||||
|
* systems since Solaris 10. Using the event port interface, we associate file
|
||||||
|
* descriptors with the port. Each association also includes the set of poll(2)
|
||||||
|
* events that the consumer is interested in (e.g., POLLIN and POLLOUT).
|
||||||
|
*
|
||||||
|
* There's one tricky piece to this implementation: when we return events via
|
||||||
|
* aeApiPoll, the corresponding file descriptors become dissociated from the
|
||||||
|
* port. This is necessary because poll events are level-triggered, so if the
|
||||||
|
* fd didn't become dissociated, it would immediately fire another event since
|
||||||
|
* the underlying state hasn't changed yet. We must reassociate the file
|
||||||
|
* descriptor, but only after we know that our caller has actually read from it.
|
||||||
|
* The ae API does not tell us exactly when that happens, but we do know that
|
||||||
|
* it must happen by the time aeApiPoll is called again. Our solution is to
|
||||||
|
* keep track of the last fds returned by aeApiPoll and reassociate them next
|
||||||
|
* time aeApiPoll is invoked.
|
||||||
|
*
|
||||||
|
* To summarize, in this module, each fd association is EITHER (a) represented
|
||||||
|
* only via the in-kernel assocation OR (b) represented by pending_fds and
|
||||||
|
* pending_masks. (b) is only true for the last fds we returned from aeApiPoll,
|
||||||
|
* and only until we enter aeApiPoll again (at which point we restore the
|
||||||
|
* in-kernel association).
|
||||||
|
*/
|
||||||
|
#define MAX_EVENT_BATCHSZ 512
|
||||||
|
|
||||||
|
typedef struct aeApiState {
|
||||||
|
int portfd; /* event port */
|
||||||
|
int npending; /* # of pending fds */
|
||||||
|
int pending_fds[MAX_EVENT_BATCHSZ]; /* pending fds */
|
||||||
|
int pending_masks[MAX_EVENT_BATCHSZ]; /* pending fds' masks */
|
||||||
|
} aeApiState;
|
||||||
|
|
||||||
|
static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||||
|
int i;
|
||||||
|
aeApiState *state = zmalloc(sizeof(aeApiState));
|
||||||
|
if (!state) return -1;
|
||||||
|
|
||||||
|
state->portfd = port_create();
|
||||||
|
if (state->portfd == -1) {
|
||||||
|
zfree(state);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->npending = 0;
|
||||||
|
|
||||||
|
for (i = 0; i < MAX_EVENT_BATCHSZ; i++) {
|
||||||
|
state->pending_fds[i] = -1;
|
||||||
|
state->pending_masks[i] = AE_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
eventLoop->apidata = state;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void aeApiFree(aeEventLoop *eventLoop) {
|
||||||
|
aeApiState *state = eventLoop->apidata;
|
||||||
|
|
||||||
|
close(state->portfd);
|
||||||
|
zfree(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int aeApiLookupPending(aeApiState *state, int fd) {
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < state->npending; i++) {
|
||||||
|
if (state->pending_fds[i] == fd)
|
||||||
|
return (i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper function to invoke port_associate for the given fd and mask.
|
||||||
|
*/
|
||||||
|
static int aeApiAssociate(const char *where, int portfd, int fd, int mask) {
|
||||||
|
int events = 0;
|
||||||
|
int rv, err;
|
||||||
|
|
||||||
|
if (mask & AE_READABLE)
|
||||||
|
events |= POLLIN;
|
||||||
|
if (mask & AE_WRITABLE)
|
||||||
|
events |= POLLOUT;
|
||||||
|
|
||||||
|
if (evport_debug)
|
||||||
|
fprintf(stderr, "%s: port_associate(%d, 0x%x) = ", where, fd, events);
|
||||||
|
|
||||||
|
rv = port_associate(portfd, PORT_SOURCE_FD, fd, events,
|
||||||
|
(void *)(uintptr_t)mask);
|
||||||
|
err = errno;
|
||||||
|
|
||||||
|
if (evport_debug)
|
||||||
|
fprintf(stderr, "%d (%s)\n", rv, rv == 0 ? "no error" : strerror(err));
|
||||||
|
|
||||||
|
if (rv == -1) {
|
||||||
|
fprintf(stderr, "%s: port_associate: %s\n", where, strerror(err));
|
||||||
|
|
||||||
|
if (err == EAGAIN)
|
||||||
|
fprintf(stderr, "aeApiAssociate: event port limit exceeded.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
|
||||||
|
aeApiState *state = eventLoop->apidata;
|
||||||
|
int fullmask, pfd;
|
||||||
|
|
||||||
|
if (evport_debug)
|
||||||
|
fprintf(stderr, "aeApiAddEvent: fd %d mask 0x%x\n", fd, mask);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since port_associate's "events" argument replaces any existing events, we
|
||||||
|
* must be sure to include whatever events are already associated when
|
||||||
|
* we call port_associate() again.
|
||||||
|
*/
|
||||||
|
fullmask = mask | eventLoop->events[fd].mask;
|
||||||
|
pfd = aeApiLookupPending(state, fd);
|
||||||
|
|
||||||
|
if (pfd != -1) {
|
||||||
|
/*
|
||||||
|
* This fd was recently returned from aeApiPoll. It should be safe to
|
||||||
|
* assume that the consumer has processed that poll event, but we play
|
||||||
|
* it safer by simply updating pending_mask. The fd will be
|
||||||
|
* reassociated as usual when aeApiPoll is called again.
|
||||||
|
*/
|
||||||
|
if (evport_debug)
|
||||||
|
fprintf(stderr, "aeApiAddEvent: adding to pending fd %d\n", fd);
|
||||||
|
state->pending_masks[pfd] |= fullmask;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (aeApiAssociate("aeApiAddEvent", state->portfd, fd, fullmask));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) {
|
||||||
|
aeApiState *state = eventLoop->apidata;
|
||||||
|
int fullmask, pfd;
|
||||||
|
|
||||||
|
if (evport_debug)
|
||||||
|
fprintf(stderr, "del fd %d mask 0x%x\n", fd, mask);
|
||||||
|
|
||||||
|
pfd = aeApiLookupPending(state, fd);
|
||||||
|
|
||||||
|
if (pfd != -1) {
|
||||||
|
if (evport_debug)
|
||||||
|
fprintf(stderr, "deleting event from pending fd %d\n", fd);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This fd was just returned from aeApiPoll, so it's not currently
|
||||||
|
* associated with the port. All we need to do is update
|
||||||
|
* pending_mask appropriately.
|
||||||
|
*/
|
||||||
|
state->pending_masks[pfd] &= ~mask;
|
||||||
|
|
||||||
|
if (state->pending_masks[pfd] == AE_NONE)
|
||||||
|
state->pending_fds[pfd] = -1;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The fd is currently associated with the port. Like with the add case
|
||||||
|
* above, we must look at the full mask for the file descriptor before
|
||||||
|
* updating that association. We don't have a good way of knowing what the
|
||||||
|
* events are without looking into the eventLoop state directly. We rely on
|
||||||
|
* the fact that our caller has already updated the mask in the eventLoop.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fullmask = eventLoop->events[fd].mask;
|
||||||
|
if (fullmask == AE_NONE) {
|
||||||
|
/*
|
||||||
|
* We're removing *all* events, so use port_dissociate to remove the
|
||||||
|
* association completely. Failure here indicates a bug.
|
||||||
|
*/
|
||||||
|
if (evport_debug)
|
||||||
|
fprintf(stderr, "aeApiDelEvent: port_dissociate(%d)\n", fd);
|
||||||
|
|
||||||
|
if (port_dissociate(state->portfd, PORT_SOURCE_FD, fd) != 0) {
|
||||||
|
perror("aeApiDelEvent: port_dissociate");
|
||||||
|
abort(); /* will not return */
|
||||||
|
}
|
||||||
|
} else if (aeApiAssociate("aeApiDelEvent", state->portfd, fd,
|
||||||
|
fullmask) != 0) {
|
||||||
|
/*
|
||||||
|
* ENOMEM is a potentially transient condition, but the kernel won't
|
||||||
|
* generally return it unless things are really bad. EAGAIN indicates
|
||||||
|
* we've reached an resource limit, for which it doesn't make sense to
|
||||||
|
* retry (counterintuitively). All other errors indicate a bug. In any
|
||||||
|
* of these cases, the best we can do is to abort.
|
||||||
|
*/
|
||||||
|
abort(); /* will not return */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
|
||||||
|
aeApiState *state = eventLoop->apidata;
|
||||||
|
struct timespec timeout, *tsp;
|
||||||
|
int mask, i;
|
||||||
|
uint_t nevents;
|
||||||
|
port_event_t event[MAX_EVENT_BATCHSZ];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we've returned fd events before, we must reassociate them with the
|
||||||
|
* port now, before calling port_get(). See the block comment at the top of
|
||||||
|
* this file for an explanation of why.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < state->npending; i++) {
|
||||||
|
if (state->pending_fds[i] == -1)
|
||||||
|
/* This fd has since been deleted. */
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (aeApiAssociate("aeApiPoll", state->portfd,
|
||||||
|
state->pending_fds[i], state->pending_masks[i]) != 0) {
|
||||||
|
/* See aeApiDelEvent for why this case is fatal. */
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
state->pending_masks[i] = AE_NONE;
|
||||||
|
state->pending_fds[i] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->npending = 0;
|
||||||
|
|
||||||
|
if (tvp != NULL) {
|
||||||
|
timeout.tv_sec = tvp->tv_sec;
|
||||||
|
timeout.tv_nsec = tvp->tv_usec * 1000;
|
||||||
|
tsp = &timeout;
|
||||||
|
} else {
|
||||||
|
tsp = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* port_getn can return with errno == ETIME having returned some events (!).
|
||||||
|
* So if we get ETIME, we check nevents, too.
|
||||||
|
*/
|
||||||
|
nevents = 1;
|
||||||
|
if (port_getn(state->portfd, event, MAX_EVENT_BATCHSZ, &nevents,
|
||||||
|
tsp) == -1 && (errno != ETIME || nevents == 0)) {
|
||||||
|
if (errno == ETIME || errno == EINTR)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Any other error indicates a bug. */
|
||||||
|
perror("aeApiPoll: port_get");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
state->npending = nevents;
|
||||||
|
|
||||||
|
for (i = 0; i < nevents; i++) {
|
||||||
|
mask = 0;
|
||||||
|
if (event[i].portev_events & POLLIN)
|
||||||
|
mask |= AE_READABLE;
|
||||||
|
if (event[i].portev_events & POLLOUT)
|
||||||
|
mask |= AE_WRITABLE;
|
||||||
|
|
||||||
|
eventLoop->fired[i].fd = event[i].portev_object;
|
||||||
|
eventLoop->fired[i].mask = mask;
|
||||||
|
|
||||||
|
if (evport_debug)
|
||||||
|
fprintf(stderr, "aeApiPoll: fd %d mask 0x%x\n",
|
||||||
|
(int)event[i].portev_object, mask);
|
||||||
|
|
||||||
|
state->pending_fds[i] = event[i].portev_object;
|
||||||
|
state->pending_masks[i] = (uintptr_t)event[i].portev_user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nevents;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *aeApiName(void) {
|
||||||
|
return "evport";
|
||||||
|
}
|
||||||
+28
-1
@@ -1,6 +1,33 @@
|
|||||||
/* Kqueue(2)-based ae.c module
|
/* Kqueue(2)-based ae.c module
|
||||||
|
*
|
||||||
* Copyright (C) 2009 Harish Mallipeddi - harish.mallipeddi@gmail.com
|
* Copyright (C) 2009 Harish Mallipeddi - harish.mallipeddi@gmail.com
|
||||||
* Released under the BSD license. See the COPYING file for more info. */
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/event.h>
|
#include <sys/event.h>
|
||||||
|
|||||||
+30
-3
@@ -1,6 +1,33 @@
|
|||||||
/* Select()-based ae.c module
|
/* Select()-based ae.c module.
|
||||||
* Copyright (C) 2009-2010 Salvatore Sanfilippo - antirez@gmail.com
|
*
|
||||||
* Released under the BSD license. See the COPYING file for more info. */
|
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of Redis nor the names of its contributors may be used
|
||||||
|
* to endorse or promote products derived from this software without
|
||||||
|
* specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||||
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
* POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -1,6 +1,6 @@
|
|||||||
#ifndef __APRINTF_H
|
#ifndef APRINTF_H
|
||||||
#define __APRINTF_H
|
#define APRINTF_H
|
||||||
|
|
||||||
char *aprintf(char **, const char *, ...);
|
char *aprintf(char **, const char *, ...);
|
||||||
|
|
||||||
#endif /* __APRINTF_H */
|
#endif /* APRINTF_H */
|
||||||
|
|||||||
+5
-3
@@ -1,11 +1,13 @@
|
|||||||
#ifndef __CONFIG_H
|
#ifndef CONFIG_H
|
||||||
#define __CONFIG_H
|
#define CONFIG_H
|
||||||
|
|
||||||
#if defined(__FreeBSD__) || defined(__APPLE__)
|
#if defined(__FreeBSD__) || defined(__APPLE__)
|
||||||
#define HAVE_KQUEUE
|
#define HAVE_KQUEUE
|
||||||
#elif defined(__linux__)
|
#elif defined(__linux__)
|
||||||
#define HAVE_EPOLL
|
#define HAVE_EPOLL
|
||||||
#define _POSIX_C_SOURCE 200809L
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
#elif defined (__sun)
|
||||||
|
#define HAVE_EVPORT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* __CONFIG_H */
|
#endif /* CONFIG_H */
|
||||||
|
|||||||
+286
-169
@@ -37,19 +37,24 @@
|
|||||||
# define MIN(a,b) ((a) < (b) ? (a) : (b))
|
# define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef ARRAY_SIZE
|
||||||
|
# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef BIT_AT
|
||||||
|
# define BIT_AT(a, i) \
|
||||||
|
(!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \
|
||||||
|
(1 << ((unsigned int) (i) & 7))))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ELEM_AT
|
||||||
|
# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v))
|
||||||
|
#endif
|
||||||
|
|
||||||
#if HTTP_PARSER_DEBUG
|
|
||||||
#define SET_ERRNO(e) \
|
|
||||||
do { \
|
|
||||||
parser->http_errno = (e); \
|
|
||||||
parser->error_lineno = __LINE__; \
|
|
||||||
} while (0)
|
|
||||||
#else
|
|
||||||
#define SET_ERRNO(e) \
|
#define SET_ERRNO(e) \
|
||||||
do { \
|
do { \
|
||||||
parser->http_errno = (e); \
|
parser->http_errno = (e); \
|
||||||
} while(0)
|
} while(0)
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* Run the notify callback FOR, returning ER if it fails */
|
/* Run the notify callback FOR, returning ER if it fails */
|
||||||
@@ -123,31 +128,10 @@ do { \
|
|||||||
|
|
||||||
|
|
||||||
static const char *method_strings[] =
|
static const char *method_strings[] =
|
||||||
{ "DELETE"
|
{
|
||||||
, "GET"
|
#define XX(num, name, string) #string,
|
||||||
, "HEAD"
|
HTTP_METHOD_MAP(XX)
|
||||||
, "POST"
|
#undef XX
|
||||||
, "PUT"
|
|
||||||
, "CONNECT"
|
|
||||||
, "OPTIONS"
|
|
||||||
, "TRACE"
|
|
||||||
, "COPY"
|
|
||||||
, "LOCK"
|
|
||||||
, "MKCOL"
|
|
||||||
, "MOVE"
|
|
||||||
, "PROPFIND"
|
|
||||||
, "PROPPATCH"
|
|
||||||
, "UNLOCK"
|
|
||||||
, "REPORT"
|
|
||||||
, "MKACTIVITY"
|
|
||||||
, "CHECKOUT"
|
|
||||||
, "MERGE"
|
|
||||||
, "M-SEARCH"
|
|
||||||
, "NOTIFY"
|
|
||||||
, "SUBSCRIBE"
|
|
||||||
, "UNSUBSCRIBE"
|
|
||||||
, "PATCH"
|
|
||||||
, "PURGE"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -205,41 +189,49 @@ static const int8_t unhex[256] =
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
static const uint8_t normal_url_char[256] = {
|
#if HTTP_PARSER_STRICT
|
||||||
/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
|
# define T(v) 0
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
#else
|
||||||
/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
|
# define T(v) v
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
#endif
|
||||||
/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
|
||||||
/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
|
|
||||||
0, 1, 1, 0, 1, 1, 1, 1,
|
|
||||||
/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1,
|
|
||||||
/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1,
|
|
||||||
/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 0,
|
|
||||||
/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1,
|
|
||||||
/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1,
|
|
||||||
/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1,
|
|
||||||
/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1,
|
|
||||||
/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1,
|
|
||||||
/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1,
|
|
||||||
/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 1,
|
|
||||||
/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
|
|
||||||
1, 1, 1, 1, 1, 1, 1, 0, };
|
|
||||||
|
|
||||||
|
|
||||||
|
static const uint8_t normal_url_char[32] = {
|
||||||
|
/* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */
|
||||||
|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
|
||||||
|
/* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */
|
||||||
|
0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0,
|
||||||
|
/* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */
|
||||||
|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
|
||||||
|
/* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */
|
||||||
|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
|
||||||
|
/* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */
|
||||||
|
0 | 2 | 4 | 0 | 16 | 32 | 64 | 128,
|
||||||
|
/* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */
|
||||||
|
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||||
|
/* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */
|
||||||
|
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||||
|
/* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */
|
||||||
|
1 | 2 | 4 | 8 | 16 | 32 | 64 | 0,
|
||||||
|
/* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */
|
||||||
|
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||||
|
/* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */
|
||||||
|
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||||
|
/* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */
|
||||||
|
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||||
|
/* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */
|
||||||
|
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||||
|
/* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */
|
||||||
|
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||||
|
/* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */
|
||||||
|
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||||
|
/* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */
|
||||||
|
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
|
||||||
|
/* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */
|
||||||
|
1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, };
|
||||||
|
|
||||||
|
#undef T
|
||||||
|
|
||||||
enum state
|
enum state
|
||||||
{ s_dead = 1 /* important that this is > 0 */
|
{ s_dead = 1 /* important that this is > 0 */
|
||||||
|
|
||||||
@@ -266,13 +258,9 @@ enum state
|
|||||||
, s_req_schema
|
, s_req_schema
|
||||||
, s_req_schema_slash
|
, s_req_schema_slash
|
||||||
, s_req_schema_slash_slash
|
, s_req_schema_slash_slash
|
||||||
, s_req_host_start
|
, s_req_server_start
|
||||||
, s_req_host_v6_start
|
, s_req_server
|
||||||
, s_req_host_v6
|
, s_req_server_with_at
|
||||||
, s_req_host_v6_end
|
|
||||||
, s_req_host
|
|
||||||
, s_req_port_start
|
|
||||||
, s_req_port
|
|
||||||
, s_req_path
|
, s_req_path
|
||||||
, s_req_query_string_start
|
, s_req_query_string_start
|
||||||
, s_req_query_string
|
, s_req_query_string
|
||||||
@@ -350,6 +338,19 @@ enum header_states
|
|||||||
, h_connection_close
|
, h_connection_close
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum http_host_state
|
||||||
|
{
|
||||||
|
s_http_host_dead = 1
|
||||||
|
, s_http_userinfo_start
|
||||||
|
, s_http_userinfo
|
||||||
|
, s_http_host_start
|
||||||
|
, s_http_host_v6_start
|
||||||
|
, s_http_host
|
||||||
|
, s_http_host_v6
|
||||||
|
, s_http_host_v6_end
|
||||||
|
, s_http_host_port_start
|
||||||
|
, s_http_host_port
|
||||||
|
};
|
||||||
|
|
||||||
/* Macros for character classes; depends on strict-mode */
|
/* Macros for character classes; depends on strict-mode */
|
||||||
#define CR '\r'
|
#define CR '\r'
|
||||||
@@ -359,15 +360,21 @@ enum header_states
|
|||||||
#define IS_NUM(c) ((c) >= '0' && (c) <= '9')
|
#define IS_NUM(c) ((c) >= '0' && (c) <= '9')
|
||||||
#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c))
|
#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c))
|
||||||
#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
|
#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
|
||||||
|
#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \
|
||||||
|
(c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \
|
||||||
|
(c) == ')')
|
||||||
|
#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \
|
||||||
|
(c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \
|
||||||
|
(c) == '$' || (c) == ',')
|
||||||
|
|
||||||
#if HTTP_PARSER_STRICT
|
#if HTTP_PARSER_STRICT
|
||||||
#define TOKEN(c) (tokens[(unsigned char)c])
|
#define TOKEN(c) (tokens[(unsigned char)c])
|
||||||
#define IS_URL_CHAR(c) (normal_url_char[(unsigned char) (c)])
|
#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c))
|
||||||
#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
|
#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
|
||||||
#else
|
#else
|
||||||
#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c])
|
#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c])
|
||||||
#define IS_URL_CHAR(c) \
|
#define IS_URL_CHAR(c) \
|
||||||
(normal_url_char[(unsigned char) (c)] || ((c) & 0x80))
|
(BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80))
|
||||||
#define IS_HOST_CHAR(c) \
|
#define IS_HOST_CHAR(c) \
|
||||||
(IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
|
(IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
|
||||||
#endif
|
#endif
|
||||||
@@ -401,7 +408,7 @@ static struct {
|
|||||||
};
|
};
|
||||||
#undef HTTP_STRERROR_GEN
|
#undef HTTP_STRERROR_GEN
|
||||||
|
|
||||||
int http_message_needs_eof(http_parser *parser);
|
int http_message_needs_eof(const http_parser *parser);
|
||||||
|
|
||||||
/* Our URL parser.
|
/* Our URL parser.
|
||||||
*
|
*
|
||||||
@@ -417,7 +424,15 @@ int http_message_needs_eof(http_parser *parser);
|
|||||||
static enum state
|
static enum state
|
||||||
parse_url_char(enum state s, const char ch)
|
parse_url_char(enum state s, const char ch)
|
||||||
{
|
{
|
||||||
assert(!isspace(ch));
|
if (ch == ' ' || ch == '\r' || ch == '\n') {
|
||||||
|
return s_dead;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if HTTP_PARSER_STRICT
|
||||||
|
if (ch == '\t' || ch == '\f') {
|
||||||
|
return s_dead;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
switch (s) {
|
switch (s) {
|
||||||
case s_req_spaces_before_url:
|
case s_req_spaces_before_url:
|
||||||
@@ -455,67 +470,33 @@ parse_url_char(enum state s, const char ch)
|
|||||||
|
|
||||||
case s_req_schema_slash_slash:
|
case s_req_schema_slash_slash:
|
||||||
if (ch == '/') {
|
if (ch == '/') {
|
||||||
return s_req_host_start;
|
return s_req_server_start;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case s_req_host_start:
|
case s_req_server_with_at:
|
||||||
if (ch == '[') {
|
if (ch == '@') {
|
||||||
return s_req_host_v6_start;
|
return s_dead;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IS_HOST_CHAR(ch)) {
|
/* FALLTHROUGH */
|
||||||
return s_req_host;
|
case s_req_server_start:
|
||||||
|
case s_req_server:
|
||||||
|
if (ch == '/') {
|
||||||
|
return s_req_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
if (ch == '?') {
|
||||||
|
return s_req_query_string_start;
|
||||||
case s_req_host:
|
|
||||||
if (IS_HOST_CHAR(ch)) {
|
|
||||||
return s_req_host;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* FALLTHROUGH */
|
if (ch == '@') {
|
||||||
case s_req_host_v6_end:
|
return s_req_server_with_at;
|
||||||
switch (ch) {
|
|
||||||
case ':':
|
|
||||||
return s_req_port_start;
|
|
||||||
|
|
||||||
case '/':
|
|
||||||
return s_req_path;
|
|
||||||
|
|
||||||
case '?':
|
|
||||||
return s_req_query_string_start;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') {
|
||||||
|
return s_req_server;
|
||||||
case s_req_host_v6:
|
|
||||||
if (ch == ']') {
|
|
||||||
return s_req_host_v6_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FALLTHROUGH */
|
|
||||||
case s_req_host_v6_start:
|
|
||||||
if (IS_HEX(ch) || ch == ':') {
|
|
||||||
return s_req_host_v6;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case s_req_port:
|
|
||||||
switch (ch) {
|
|
||||||
case '/':
|
|
||||||
return s_req_path;
|
|
||||||
|
|
||||||
case '?':
|
|
||||||
return s_req_query_string_start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FALLTHROUGH */
|
|
||||||
case s_req_port_start:
|
|
||||||
if (IS_NUM(ch)) {
|
|
||||||
return s_req_port;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -637,13 +618,9 @@ size_t http_parser_execute (http_parser *parser,
|
|||||||
case s_req_schema:
|
case s_req_schema:
|
||||||
case s_req_schema_slash:
|
case s_req_schema_slash:
|
||||||
case s_req_schema_slash_slash:
|
case s_req_schema_slash_slash:
|
||||||
case s_req_host_start:
|
case s_req_server_start:
|
||||||
case s_req_host_v6_start:
|
case s_req_server:
|
||||||
case s_req_host_v6:
|
case s_req_server_with_at:
|
||||||
case s_req_host_v6_end:
|
|
||||||
case s_req_host:
|
|
||||||
case s_req_port_start:
|
|
||||||
case s_req_port:
|
|
||||||
case s_req_query_string_start:
|
case s_req_query_string_start:
|
||||||
case s_req_query_string:
|
case s_req_query_string:
|
||||||
case s_req_fragment_start:
|
case s_req_fragment_start:
|
||||||
@@ -889,6 +866,7 @@ size_t http_parser_execute (http_parser *parser,
|
|||||||
case s_res_line_almost_done:
|
case s_res_line_almost_done:
|
||||||
STRICT_CHECK(ch != LF);
|
STRICT_CHECK(ch != LF);
|
||||||
parser->state = s_header_field_start;
|
parser->state = s_header_field_start;
|
||||||
|
CALLBACK_NOTIFY(status_complete);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case s_start_req:
|
case s_start_req:
|
||||||
@@ -918,7 +896,7 @@ size_t http_parser_execute (http_parser *parser,
|
|||||||
/* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
|
/* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
|
||||||
break;
|
break;
|
||||||
case 'R': parser->method = HTTP_REPORT; break;
|
case 'R': parser->method = HTTP_REPORT; break;
|
||||||
case 'S': parser->method = HTTP_SUBSCRIBE; break;
|
case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break;
|
||||||
case 'T': parser->method = HTTP_TRACE; break;
|
case 'T': parser->method = HTTP_TRACE; break;
|
||||||
case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;
|
case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;
|
||||||
default:
|
default:
|
||||||
@@ -965,6 +943,12 @@ size_t http_parser_execute (http_parser *parser,
|
|||||||
} else {
|
} else {
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
} else if (parser->method == HTTP_SUBSCRIBE) {
|
||||||
|
if (parser->index == 1 && ch == 'E') {
|
||||||
|
parser->method = HTTP_SEARCH;
|
||||||
|
} else {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
} else if (parser->index == 1 && parser->method == HTTP_POST) {
|
} else if (parser->index == 1 && parser->method == HTTP_POST) {
|
||||||
if (ch == 'R') {
|
if (ch == 'R') {
|
||||||
parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */
|
parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */
|
||||||
@@ -998,7 +982,7 @@ size_t http_parser_execute (http_parser *parser,
|
|||||||
|
|
||||||
MARK(url);
|
MARK(url);
|
||||||
if (parser->method == HTTP_CONNECT) {
|
if (parser->method == HTTP_CONNECT) {
|
||||||
parser->state = s_req_host_start;
|
parser->state = s_req_server_start;
|
||||||
}
|
}
|
||||||
|
|
||||||
parser->state = parse_url_char((enum state)parser->state, ch);
|
parser->state = parse_url_char((enum state)parser->state, ch);
|
||||||
@@ -1013,10 +997,7 @@ size_t http_parser_execute (http_parser *parser,
|
|||||||
case s_req_schema:
|
case s_req_schema:
|
||||||
case s_req_schema_slash:
|
case s_req_schema_slash:
|
||||||
case s_req_schema_slash_slash:
|
case s_req_schema_slash_slash:
|
||||||
case s_req_host_start:
|
case s_req_server_start:
|
||||||
case s_req_host_v6_start:
|
|
||||||
case s_req_host_v6:
|
|
||||||
case s_req_port_start:
|
|
||||||
{
|
{
|
||||||
switch (ch) {
|
switch (ch) {
|
||||||
/* No whitespace allowed here */
|
/* No whitespace allowed here */
|
||||||
@@ -1036,9 +1017,8 @@ size_t http_parser_execute (http_parser *parser,
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case s_req_host:
|
case s_req_server:
|
||||||
case s_req_host_v6_end:
|
case s_req_server_with_at:
|
||||||
case s_req_port:
|
|
||||||
case s_req_path:
|
case s_req_path:
|
||||||
case s_req_query_string_start:
|
case s_req_query_string_start:
|
||||||
case s_req_query_string:
|
case s_req_query_string:
|
||||||
@@ -1867,7 +1847,7 @@ error:
|
|||||||
|
|
||||||
/* Does the parser need to see an EOF to find the end of the message? */
|
/* Does the parser need to see an EOF to find the end of the message? */
|
||||||
int
|
int
|
||||||
http_message_needs_eof (http_parser *parser)
|
http_message_needs_eof (const http_parser *parser)
|
||||||
{
|
{
|
||||||
if (parser->type == HTTP_REQUEST) {
|
if (parser->type == HTTP_REQUEST) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1890,7 +1870,7 @@ http_message_needs_eof (http_parser *parser)
|
|||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
http_should_keep_alive (http_parser *parser)
|
http_should_keep_alive (const http_parser *parser)
|
||||||
{
|
{
|
||||||
if (parser->http_major > 0 && parser->http_minor > 0) {
|
if (parser->http_major > 0 && parser->http_minor > 0) {
|
||||||
/* HTTP/1.1 */
|
/* HTTP/1.1 */
|
||||||
@@ -1908,9 +1888,10 @@ http_should_keep_alive (http_parser *parser)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const char * http_method_str (enum http_method m)
|
const char *
|
||||||
|
http_method_str (enum http_method m)
|
||||||
{
|
{
|
||||||
return method_strings[m];
|
return ELEM_AT(method_strings, m, "<unknown>");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1937,6 +1918,144 @@ http_errno_description(enum http_errno err) {
|
|||||||
return http_strerror_tab[err].description;
|
return http_strerror_tab[err].description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static enum http_host_state
|
||||||
|
http_parse_host_char(enum http_host_state s, const char ch) {
|
||||||
|
switch(s) {
|
||||||
|
case s_http_userinfo:
|
||||||
|
case s_http_userinfo_start:
|
||||||
|
if (ch == '@') {
|
||||||
|
return s_http_host_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_USERINFO_CHAR(ch)) {
|
||||||
|
return s_http_userinfo;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case s_http_host_start:
|
||||||
|
if (ch == '[') {
|
||||||
|
return s_http_host_v6_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_HOST_CHAR(ch)) {
|
||||||
|
return s_http_host;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case s_http_host:
|
||||||
|
if (IS_HOST_CHAR(ch)) {
|
||||||
|
return s_http_host;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FALLTHROUGH */
|
||||||
|
case s_http_host_v6_end:
|
||||||
|
if (ch == ':') {
|
||||||
|
return s_http_host_port_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case s_http_host_v6:
|
||||||
|
if (ch == ']') {
|
||||||
|
return s_http_host_v6_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FALLTHROUGH */
|
||||||
|
case s_http_host_v6_start:
|
||||||
|
if (IS_HEX(ch) || ch == ':' || ch == '.') {
|
||||||
|
return s_http_host_v6;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case s_http_host_port:
|
||||||
|
case s_http_host_port_start:
|
||||||
|
if (IS_NUM(ch)) {
|
||||||
|
return s_http_host_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return s_http_host_dead;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
http_parse_host(const char * buf, struct http_parser_url *u, int found_at) {
|
||||||
|
enum http_host_state s;
|
||||||
|
|
||||||
|
const char *p;
|
||||||
|
size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;
|
||||||
|
|
||||||
|
u->field_data[UF_HOST].len = 0;
|
||||||
|
|
||||||
|
s = found_at ? s_http_userinfo_start : s_http_host_start;
|
||||||
|
|
||||||
|
for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {
|
||||||
|
enum http_host_state new_s = http_parse_host_char(s, *p);
|
||||||
|
|
||||||
|
if (new_s == s_http_host_dead) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(new_s) {
|
||||||
|
case s_http_host:
|
||||||
|
if (s != s_http_host) {
|
||||||
|
u->field_data[UF_HOST].off = p - buf;
|
||||||
|
}
|
||||||
|
u->field_data[UF_HOST].len++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case s_http_host_v6:
|
||||||
|
if (s != s_http_host_v6) {
|
||||||
|
u->field_data[UF_HOST].off = p - buf;
|
||||||
|
}
|
||||||
|
u->field_data[UF_HOST].len++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case s_http_host_port:
|
||||||
|
if (s != s_http_host_port) {
|
||||||
|
u->field_data[UF_PORT].off = p - buf;
|
||||||
|
u->field_data[UF_PORT].len = 0;
|
||||||
|
u->field_set |= (1 << UF_PORT);
|
||||||
|
}
|
||||||
|
u->field_data[UF_PORT].len++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case s_http_userinfo:
|
||||||
|
if (s != s_http_userinfo) {
|
||||||
|
u->field_data[UF_USERINFO].off = p - buf ;
|
||||||
|
u->field_data[UF_USERINFO].len = 0;
|
||||||
|
u->field_set |= (1 << UF_USERINFO);
|
||||||
|
}
|
||||||
|
u->field_data[UF_USERINFO].len++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
s = new_s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure we don't end somewhere unexpected */
|
||||||
|
switch (s) {
|
||||||
|
case s_http_host_start:
|
||||||
|
case s_http_host_v6_start:
|
||||||
|
case s_http_host_v6:
|
||||||
|
case s_http_host_port_start:
|
||||||
|
case s_http_userinfo:
|
||||||
|
case s_http_userinfo_start:
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
|
http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
|
||||||
struct http_parser_url *u)
|
struct http_parser_url *u)
|
||||||
@@ -1944,9 +2063,10 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
|
|||||||
enum state s;
|
enum state s;
|
||||||
const char *p;
|
const char *p;
|
||||||
enum http_parser_url_fields uf, old_uf;
|
enum http_parser_url_fields uf, old_uf;
|
||||||
|
int found_at = 0;
|
||||||
|
|
||||||
u->port = u->field_set = 0;
|
u->port = u->field_set = 0;
|
||||||
s = is_connect ? s_req_host_start : s_req_spaces_before_url;
|
s = is_connect ? s_req_server_start : s_req_spaces_before_url;
|
||||||
uf = old_uf = UF_MAX;
|
uf = old_uf = UF_MAX;
|
||||||
|
|
||||||
for (p = buf; p < buf + buflen; p++) {
|
for (p = buf; p < buf + buflen; p++) {
|
||||||
@@ -1960,10 +2080,7 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
|
|||||||
/* Skip delimeters */
|
/* Skip delimeters */
|
||||||
case s_req_schema_slash:
|
case s_req_schema_slash:
|
||||||
case s_req_schema_slash_slash:
|
case s_req_schema_slash_slash:
|
||||||
case s_req_host_start:
|
case s_req_server_start:
|
||||||
case s_req_host_v6_start:
|
|
||||||
case s_req_host_v6_end:
|
|
||||||
case s_req_port_start:
|
|
||||||
case s_req_query_string_start:
|
case s_req_query_string_start:
|
||||||
case s_req_fragment_start:
|
case s_req_fragment_start:
|
||||||
continue;
|
continue;
|
||||||
@@ -1972,13 +2089,12 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
|
|||||||
uf = UF_SCHEMA;
|
uf = UF_SCHEMA;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case s_req_host:
|
case s_req_server_with_at:
|
||||||
case s_req_host_v6:
|
found_at = 1;
|
||||||
uf = UF_HOST;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case s_req_port:
|
/* FALLTROUGH */
|
||||||
uf = UF_PORT;
|
case s_req_server:
|
||||||
|
uf = UF_HOST;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case s_req_path:
|
case s_req_path:
|
||||||
@@ -2011,23 +2127,19 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect,
|
|||||||
old_uf = uf;
|
old_uf = uf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* host must be present if there is a schema */
|
||||||
|
/* parsing http:///toto will fail */
|
||||||
|
if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) {
|
||||||
|
if (http_parse_host(buf, u, found_at) != 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* CONNECT requests can only contain "hostname:port" */
|
/* CONNECT requests can only contain "hostname:port" */
|
||||||
if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {
|
if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make sure we don't end somewhere unexpected */
|
|
||||||
switch (s) {
|
|
||||||
case s_req_host_v6_start:
|
|
||||||
case s_req_host_v6:
|
|
||||||
case s_req_host_v6_end:
|
|
||||||
case s_req_host:
|
|
||||||
case s_req_port_start:
|
|
||||||
return 1;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (u->field_set & (1 << UF_PORT)) {
|
if (u->field_set & (1 << UF_PORT)) {
|
||||||
/* Don't bother with endp; we've already validated the string */
|
/* Don't bother with endp; we've already validated the string */
|
||||||
unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10);
|
unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10);
|
||||||
@@ -2056,3 +2168,8 @@ http_parser_pause(http_parser *parser, int paused) {
|
|||||||
assert(0 && "Attempting to pause parser in error state");
|
assert(0 && "Attempting to pause parser in error state");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
http_body_is_final(const struct http_parser *parser) {
|
||||||
|
return parser->state == s_message_done;
|
||||||
|
}
|
||||||
|
|||||||
+48
-61
@@ -24,11 +24,13 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define HTTP_PARSER_VERSION_MAJOR 1
|
#define HTTP_PARSER_VERSION_MAJOR 2
|
||||||
#define HTTP_PARSER_VERSION_MINOR 0
|
#define HTTP_PARSER_VERSION_MINOR 1
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)
|
#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)
|
||||||
|
#include <BaseTsd.h>
|
||||||
|
#include <stddef.h>
|
||||||
typedef __int8 int8_t;
|
typedef __int8 int8_t;
|
||||||
typedef unsigned __int8 uint8_t;
|
typedef unsigned __int8 uint8_t;
|
||||||
typedef __int16 int16_t;
|
typedef __int16 int16_t;
|
||||||
@@ -37,9 +39,6 @@ typedef __int32 int32_t;
|
|||||||
typedef unsigned __int32 uint32_t;
|
typedef unsigned __int32 uint32_t;
|
||||||
typedef __int64 int64_t;
|
typedef __int64 int64_t;
|
||||||
typedef unsigned __int64 uint64_t;
|
typedef unsigned __int64 uint64_t;
|
||||||
|
|
||||||
typedef unsigned int size_t;
|
|
||||||
typedef int ssize_t;
|
|
||||||
#else
|
#else
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -51,14 +50,6 @@ typedef int ssize_t;
|
|||||||
# define HTTP_PARSER_STRICT 1
|
# define HTTP_PARSER_STRICT 1
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Compile with -DHTTP_PARSER_DEBUG=1 to add extra debugging information to
|
|
||||||
* the error reporting facility.
|
|
||||||
*/
|
|
||||||
#ifndef HTTP_PARSER_DEBUG
|
|
||||||
# define HTTP_PARSER_DEBUG 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
/* Maximium header size allowed */
|
/* Maximium header size allowed */
|
||||||
#define HTTP_MAX_HEADER_SIZE (80*1024)
|
#define HTTP_MAX_HEADER_SIZE (80*1024)
|
||||||
|
|
||||||
@@ -77,7 +68,7 @@ typedef struct http_parser_settings http_parser_settings;
|
|||||||
* chunked' headers that indicate the presence of a body.
|
* chunked' headers that indicate the presence of a body.
|
||||||
*
|
*
|
||||||
* http_data_cb does not return data chunks. It will be call arbitrarally
|
* http_data_cb does not return data chunks. It will be call arbitrarally
|
||||||
* many times for each string. E.G. you might get 10 callbacks for "on_path"
|
* many times for each string. E.G. you might get 10 callbacks for "on_url"
|
||||||
* each providing just a few characters more data.
|
* each providing just a few characters more data.
|
||||||
*/
|
*/
|
||||||
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
|
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
|
||||||
@@ -85,43 +76,44 @@ typedef int (*http_cb) (http_parser*);
|
|||||||
|
|
||||||
|
|
||||||
/* Request Methods */
|
/* Request Methods */
|
||||||
#define HTTP_METHOD_MAP(XX) \
|
#define HTTP_METHOD_MAP(XX) \
|
||||||
XX(0, DELETE) \
|
XX(0, DELETE, DELETE) \
|
||||||
XX(1, GET) \
|
XX(1, GET, GET) \
|
||||||
XX(2, HEAD) \
|
XX(2, HEAD, HEAD) \
|
||||||
XX(3, POST) \
|
XX(3, POST, POST) \
|
||||||
XX(4, PUT) \
|
XX(4, PUT, PUT) \
|
||||||
/* pathological */ \
|
/* pathological */ \
|
||||||
XX(5, CONNECT) \
|
XX(5, CONNECT, CONNECT) \
|
||||||
XX(6, OPTIONS) \
|
XX(6, OPTIONS, OPTIONS) \
|
||||||
XX(7, TRACE) \
|
XX(7, TRACE, TRACE) \
|
||||||
/* webdav */ \
|
/* webdav */ \
|
||||||
XX(8, COPY) \
|
XX(8, COPY, COPY) \
|
||||||
XX(9, LOCK) \
|
XX(9, LOCK, LOCK) \
|
||||||
XX(10, MKCOL) \
|
XX(10, MKCOL, MKCOL) \
|
||||||
XX(11, MOVE) \
|
XX(11, MOVE, MOVE) \
|
||||||
XX(12, PROPFIND) \
|
XX(12, PROPFIND, PROPFIND) \
|
||||||
XX(13, PROPPATCH) \
|
XX(13, PROPPATCH, PROPPATCH) \
|
||||||
XX(14, UNLOCK) \
|
XX(14, SEARCH, SEARCH) \
|
||||||
/* subversion */ \
|
XX(15, UNLOCK, UNLOCK) \
|
||||||
XX(15, REPORT) \
|
/* subversion */ \
|
||||||
XX(16, MKACTIVITY) \
|
XX(16, REPORT, REPORT) \
|
||||||
XX(17, CHECKOUT) \
|
XX(17, MKACTIVITY, MKACTIVITY) \
|
||||||
XX(18, MERGE) \
|
XX(18, CHECKOUT, CHECKOUT) \
|
||||||
/* upnp */ \
|
XX(19, MERGE, MERGE) \
|
||||||
XX(19, MSEARCH) \
|
/* upnp */ \
|
||||||
XX(20, NOTIFY) \
|
XX(20, MSEARCH, M-SEARCH) \
|
||||||
XX(21, SUBSCRIBE) \
|
XX(21, NOTIFY, NOTIFY) \
|
||||||
XX(22, UNSUBSCRIBE) \
|
XX(22, SUBSCRIBE, SUBSCRIBE) \
|
||||||
/* RFC-5789 */ \
|
XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \
|
||||||
XX(23, PATCH) \
|
/* RFC-5789 */ \
|
||||||
XX(24, PURGE) \
|
XX(24, PATCH, PATCH) \
|
||||||
|
XX(25, PURGE, PURGE) \
|
||||||
|
|
||||||
enum http_method
|
enum http_method
|
||||||
{
|
{
|
||||||
#define XX(num, name) HTTP_##name = num,
|
#define XX(num, name, string) HTTP_##name = num,
|
||||||
HTTP_METHOD_MAP(XX)
|
HTTP_METHOD_MAP(XX)
|
||||||
#undef X
|
#undef XX
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -149,6 +141,7 @@ enum flags
|
|||||||
\
|
\
|
||||||
/* Callback-related errors */ \
|
/* Callback-related errors */ \
|
||||||
XX(CB_message_begin, "the on_message_begin callback failed") \
|
XX(CB_message_begin, "the on_message_begin callback failed") \
|
||||||
|
XX(CB_status_complete, "the on_status_complete callback failed") \
|
||||||
XX(CB_url, "the on_url callback failed") \
|
XX(CB_url, "the on_url callback failed") \
|
||||||
XX(CB_header_field, "the on_header_field callback failed") \
|
XX(CB_header_field, "the on_header_field callback failed") \
|
||||||
XX(CB_header_value, "the on_header_value callback failed") \
|
XX(CB_header_value, "the on_header_value callback failed") \
|
||||||
@@ -195,13 +188,6 @@ enum http_errno {
|
|||||||
/* Get an http_errno value from an http_parser */
|
/* Get an http_errno value from an http_parser */
|
||||||
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
|
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
|
||||||
|
|
||||||
/* Get the line number that generated the current error */
|
|
||||||
#if HTTP_PARSER_DEBUG
|
|
||||||
#define HTTP_PARSER_ERRNO_LINE(p) ((p)->error_lineno)
|
|
||||||
#else
|
|
||||||
#define HTTP_PARSER_ERRNO_LINE(p) 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
struct http_parser {
|
struct http_parser {
|
||||||
/** PRIVATE **/
|
/** PRIVATE **/
|
||||||
@@ -228,10 +214,6 @@ struct http_parser {
|
|||||||
*/
|
*/
|
||||||
unsigned char upgrade : 1;
|
unsigned char upgrade : 1;
|
||||||
|
|
||||||
#if HTTP_PARSER_DEBUG
|
|
||||||
uint32_t error_lineno;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/** PUBLIC **/
|
/** PUBLIC **/
|
||||||
void *data; /* A pointer to get hook to the "connection" or "socket" object */
|
void *data; /* A pointer to get hook to the "connection" or "socket" object */
|
||||||
};
|
};
|
||||||
@@ -240,6 +222,7 @@ struct http_parser {
|
|||||||
struct http_parser_settings {
|
struct http_parser_settings {
|
||||||
http_cb on_message_begin;
|
http_cb on_message_begin;
|
||||||
http_data_cb on_url;
|
http_data_cb on_url;
|
||||||
|
http_cb on_status_complete;
|
||||||
http_data_cb on_header_field;
|
http_data_cb on_header_field;
|
||||||
http_data_cb on_header_value;
|
http_data_cb on_header_value;
|
||||||
http_cb on_headers_complete;
|
http_cb on_headers_complete;
|
||||||
@@ -255,7 +238,8 @@ enum http_parser_url_fields
|
|||||||
, UF_PATH = 3
|
, UF_PATH = 3
|
||||||
, UF_QUERY = 4
|
, UF_QUERY = 4
|
||||||
, UF_FRAGMENT = 5
|
, UF_FRAGMENT = 5
|
||||||
, UF_MAX = 6
|
, UF_USERINFO = 6
|
||||||
|
, UF_MAX = 7
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -287,12 +271,12 @@ size_t http_parser_execute(http_parser *parser,
|
|||||||
|
|
||||||
|
|
||||||
/* If http_should_keep_alive() in the on_headers_complete or
|
/* If http_should_keep_alive() in the on_headers_complete or
|
||||||
* on_message_complete callback returns true, then this will be should be
|
* on_message_complete callback returns 0, then this should be
|
||||||
* the last message on the connection.
|
* the last message on the connection.
|
||||||
* If you are the server, respond with the "Connection: close" header.
|
* If you are the server, respond with the "Connection: close" header.
|
||||||
* If you are the client, close the connection.
|
* If you are the client, close the connection.
|
||||||
*/
|
*/
|
||||||
int http_should_keep_alive(http_parser *parser);
|
int http_should_keep_alive(const http_parser *parser);
|
||||||
|
|
||||||
/* Returns a string version of the HTTP method. */
|
/* Returns a string version of the HTTP method. */
|
||||||
const char *http_method_str(enum http_method m);
|
const char *http_method_str(enum http_method m);
|
||||||
@@ -311,6 +295,9 @@ int http_parser_parse_url(const char *buf, size_t buflen,
|
|||||||
/* Pause or un-pause the parser; a nonzero value pauses */
|
/* Pause or un-pause the parser; a nonzero value pauses */
|
||||||
void http_parser_pause(http_parser *parser, int paused);
|
void http_parser_pause(http_parser *parser, int paused);
|
||||||
|
|
||||||
|
/* Checks if this is the final chunk of the body. */
|
||||||
|
int http_body_is_final(const http_parser *parser);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef MAIN_H
|
||||||
|
#define MAIN_H
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/uio.h>
|
||||||
|
|
||||||
|
#include "ssl.h"
|
||||||
|
#include "aprintf.h"
|
||||||
|
#include "stats.h"
|
||||||
|
#include "units.h"
|
||||||
|
#include "zmalloc.h"
|
||||||
|
|
||||||
|
struct config;
|
||||||
|
|
||||||
|
static void *thread_main(void *);
|
||||||
|
static int connect_socket(thread *, connection *);
|
||||||
|
static int reconnect_socket(thread *, connection *);
|
||||||
|
|
||||||
|
static int calibrate(aeEventLoop *, long long, void *);
|
||||||
|
static int sample_rate(aeEventLoop *, long long, void *);
|
||||||
|
static int check_timeouts(aeEventLoop *, long long, void *);
|
||||||
|
|
||||||
|
static void socket_connected(aeEventLoop *, int, void *, int);
|
||||||
|
static void socket_writeable(aeEventLoop *, int, void *, int);
|
||||||
|
static void socket_readable(aeEventLoop *, int, void *, int);
|
||||||
|
static int request_complete(http_parser *);
|
||||||
|
|
||||||
|
static uint64_t time_us();
|
||||||
|
|
||||||
|
static char *extract_url_part(char *, struct http_parser_url *, enum http_parser_url_fields);
|
||||||
|
static char *format_request(char *, char *, char *, char **);
|
||||||
|
|
||||||
|
static int parse_args(struct config *, char **, char **, int, char **);
|
||||||
|
static void print_stats_header();
|
||||||
|
static void print_stats(char *, stats *, char *(*)(long double));
|
||||||
|
static void print_stats_latency(stats *);
|
||||||
|
|
||||||
|
#endif /* MAIN_H */
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Copyright (C) 2013 - Will Glozer. All rights reserved.
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "net.h"
|
||||||
|
|
||||||
|
status sock_connect(connection *c) {
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status sock_close(connection *c) {
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status sock_read(connection *c, size_t *n) {
|
||||||
|
ssize_t r = read(c->fd, c->buf, sizeof(c->buf));
|
||||||
|
*n = (size_t) r;
|
||||||
|
return r > 0 ? OK : ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
status sock_write(connection *c, char *buf, size_t len, size_t *n) {
|
||||||
|
ssize_t r;
|
||||||
|
if ((r = write(c->fd, buf, len)) == -1) {
|
||||||
|
switch (errno) {
|
||||||
|
case EAGAIN: return RETRY;
|
||||||
|
default: return ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*n = (size_t) r;
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef NET_H
|
||||||
|
#define NET_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
#include "wrk.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
OK,
|
||||||
|
ERROR,
|
||||||
|
RETRY
|
||||||
|
} status;
|
||||||
|
|
||||||
|
struct sock {
|
||||||
|
status (*connect)(connection *);
|
||||||
|
status ( *close)(connection *);
|
||||||
|
status ( *read)(connection *, size_t *);
|
||||||
|
status ( *write)(connection *, char *, size_t, size_t *);
|
||||||
|
};
|
||||||
|
|
||||||
|
status sock_connect(connection *);
|
||||||
|
status sock_close(connection *);
|
||||||
|
status sock_read(connection *, size_t *);
|
||||||
|
status sock_write(connection *, char *, size_t, size_t *);
|
||||||
|
|
||||||
|
#endif /* NET_H */
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// Copyright (C) 2013 - Will Glozer. All rights reserved.
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
|
||||||
|
#include "ssl.h"
|
||||||
|
|
||||||
|
static pthread_mutex_t *locks;
|
||||||
|
|
||||||
|
static void ssl_lock(int mode, int n, const char *file, int line) {
|
||||||
|
pthread_mutex_t *lock = &locks[n];
|
||||||
|
if (mode & CRYPTO_LOCK) {
|
||||||
|
pthread_mutex_lock(lock);
|
||||||
|
} else {
|
||||||
|
pthread_mutex_unlock(lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long ssl_id() {
|
||||||
|
return (unsigned long) pthread_self();
|
||||||
|
}
|
||||||
|
|
||||||
|
SSL_CTX *ssl_init() {
|
||||||
|
SSL_CTX *ctx = NULL;
|
||||||
|
|
||||||
|
SSL_load_error_strings();
|
||||||
|
SSL_library_init();
|
||||||
|
OpenSSL_add_all_algorithms();
|
||||||
|
|
||||||
|
if ((locks = calloc(CRYPTO_num_locks(), sizeof(pthread_mutex_t)))) {
|
||||||
|
for (int i = 0; i < CRYPTO_num_locks(); i++) {
|
||||||
|
pthread_mutex_init(&locks[i], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
CRYPTO_set_locking_callback(ssl_lock);
|
||||||
|
CRYPTO_set_id_callback(ssl_id);
|
||||||
|
|
||||||
|
if ((ctx = SSL_CTX_new(TLSv1_client_method()))) {
|
||||||
|
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
|
||||||
|
SSL_CTX_set_verify_depth(ctx, 0);
|
||||||
|
SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
|
||||||
|
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_CLIENT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
status ssl_connect(connection *c) {
|
||||||
|
int r;
|
||||||
|
SSL_set_fd(c->ssl, c->fd);
|
||||||
|
if ((r = SSL_connect(c->ssl)) != 1) {
|
||||||
|
switch (SSL_get_error(c->ssl, r)) {
|
||||||
|
case SSL_ERROR_WANT_READ: return RETRY;
|
||||||
|
case SSL_ERROR_WANT_WRITE: return RETRY;
|
||||||
|
default: return ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status ssl_close(connection *c) {
|
||||||
|
SSL_shutdown(c->ssl);
|
||||||
|
SSL_clear(c->ssl);
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status ssl_read(connection *c, size_t *n) {
|
||||||
|
int r;
|
||||||
|
if ((r = SSL_read(c->ssl, c->buf, sizeof(c->buf))) <= 0) {
|
||||||
|
switch (SSL_get_error(c->ssl, r)) {
|
||||||
|
case SSL_ERROR_WANT_READ: return RETRY;
|
||||||
|
case SSL_ERROR_WANT_WRITE: return RETRY;
|
||||||
|
default: return ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*n = (size_t) r;
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
status ssl_write(connection *c, char *buf, size_t len, size_t *n) {
|
||||||
|
int r;
|
||||||
|
if ((r = SSL_write(c->ssl, buf, len)) <= 0) {
|
||||||
|
switch (SSL_get_error(c->ssl, r)) {
|
||||||
|
case SSL_ERROR_WANT_READ: return RETRY;
|
||||||
|
case SSL_ERROR_WANT_WRITE: return RETRY;
|
||||||
|
default: return ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*n = (size_t) r;
|
||||||
|
return OK;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef SSL_H
|
||||||
|
#define SSL_H
|
||||||
|
|
||||||
|
#include "net.h"
|
||||||
|
|
||||||
|
SSL_CTX *ssl_init();
|
||||||
|
|
||||||
|
status ssl_connect(connection *);
|
||||||
|
status ssl_close(connection *);
|
||||||
|
status ssl_read(connection *, size_t *);
|
||||||
|
status ssl_write(connection *, char *, size_t, size_t *);
|
||||||
|
|
||||||
|
#endif /* SSL_H */
|
||||||
+46
-24
@@ -7,44 +7,45 @@
|
|||||||
#include "stats.h"
|
#include "stats.h"
|
||||||
#include "zmalloc.h"
|
#include "zmalloc.h"
|
||||||
|
|
||||||
stats *stats_alloc(int samples) {
|
stats *stats_alloc(uint64_t samples) {
|
||||||
stats *stats = zcalloc(sizeof(stats) + sizeof(uint64_t) * samples);
|
stats *s = zcalloc(sizeof(stats) + sizeof(uint64_t) * samples);
|
||||||
stats->samples = samples;
|
s->samples = samples;
|
||||||
return stats;
|
s->min = UINT64_MAX;
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void stats_free(stats *stats) {
|
void stats_free(stats *stats) {
|
||||||
zfree(stats);
|
zfree(stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void stats_reset(stats *stats) {
|
||||||
|
stats->limit = 0;
|
||||||
|
stats->index = 0;
|
||||||
|
stats->min = UINT64_MAX;
|
||||||
|
stats->max = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void stats_record(stats *stats, uint64_t x) {
|
void stats_record(stats *stats, uint64_t x) {
|
||||||
stats->data[stats->index++] = x;
|
stats->data[stats->index++] = x;
|
||||||
|
if (x < stats->min) stats->min = x;
|
||||||
|
if (x > stats->max) stats->max = x;
|
||||||
if (stats->limit < stats->samples) stats->limit++;
|
if (stats->limit < stats->samples) stats->limit++;
|
||||||
if (stats->index == stats->samples) stats->index = 0;
|
if (stats->index == stats->samples) stats->index = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t stats_min(stats *stats) {
|
static int stats_compare(const void *a, const void *b) {
|
||||||
uint64_t min = 0;
|
uint64_t *x = (uint64_t *) a;
|
||||||
for (int i = 0; i < stats->limit; i++) {
|
uint64_t *y = (uint64_t *) b;
|
||||||
uint64_t x = stats->data[i];
|
return *x - *y;
|
||||||
if (x < min || min == 0) min = x;
|
|
||||||
}
|
|
||||||
return min;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t stats_max(stats *stats) {
|
long double stats_summarize(stats *stats) {
|
||||||
uint64_t max = 0;
|
qsort(stats->data, stats->limit, sizeof(uint64_t), &stats_compare);
|
||||||
for (int i = 0; i < stats->limit; i++) {
|
|
||||||
uint64_t x = stats->data[i];
|
|
||||||
if (x > max || max == 0) max = x;
|
|
||||||
}
|
|
||||||
return max;
|
|
||||||
}
|
|
||||||
|
|
||||||
long double stats_mean(stats *stats) {
|
|
||||||
uint64_t sum = 0;
|
|
||||||
if (stats->limit == 0) return 0.0;
|
if (stats->limit == 0) return 0.0;
|
||||||
for (int i = 0; i < stats->limit; i++) {
|
|
||||||
|
uint64_t sum = 0;
|
||||||
|
for (uint64_t i = 0; i < stats->limit; i++) {
|
||||||
sum += stats->data[i];
|
sum += stats->data[i];
|
||||||
}
|
}
|
||||||
return sum / (long double) stats->limit;
|
return sum / (long double) stats->limit;
|
||||||
@@ -53,7 +54,7 @@ long double stats_mean(stats *stats) {
|
|||||||
long double stats_stdev(stats *stats, long double mean) {
|
long double stats_stdev(stats *stats, long double mean) {
|
||||||
long double sum = 0.0;
|
long double sum = 0.0;
|
||||||
if (stats->limit < 2) return 0.0;
|
if (stats->limit < 2) return 0.0;
|
||||||
for (int i = 0; i < stats->limit; i++) {
|
for (uint64_t i = 0; i < stats->limit; i++) {
|
||||||
sum += powl(stats->data[i] - mean, 2);
|
sum += powl(stats->data[i] - mean, 2);
|
||||||
}
|
}
|
||||||
return sqrtl(sum / (stats->limit - 1));
|
return sqrtl(sum / (stats->limit - 1));
|
||||||
@@ -64,10 +65,31 @@ long double stats_within_stdev(stats *stats, long double mean, long double stdev
|
|||||||
long double lower = mean - (stdev * n);
|
long double lower = mean - (stdev * n);
|
||||||
uint64_t sum = 0;
|
uint64_t sum = 0;
|
||||||
|
|
||||||
for (int i = 0; i < stats->limit; i++) {
|
for (uint64_t i = 0; i < stats->limit; i++) {
|
||||||
uint64_t x = stats->data[i];
|
uint64_t x = stats->data[i];
|
||||||
if (x >= lower && x <= upper) sum++;
|
if (x >= lower && x <= upper) sum++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (sum / (long double) stats->limit) * 100;
|
return (sum / (long double) stats->limit) * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint64_t stats_percentile(stats *stats, long double p) {
|
||||||
|
uint64_t rank = round((p / 100.0) * stats->limit + 0.5);
|
||||||
|
return stats->data[rank - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
void stats_sample(stats *dst, tinymt64_t *state, uint64_t count, stats *src) {
|
||||||
|
for (uint64_t i = 0; i < count; i++) {
|
||||||
|
uint64_t n = rand64(state, src->limit);
|
||||||
|
stats_record(dst, src->data[n]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t rand64(tinymt64_t *state, uint64_t n) {
|
||||||
|
uint64_t x, max = ~UINT64_C(0);
|
||||||
|
max -= max % n;
|
||||||
|
do {
|
||||||
|
x = tinymt64_generate_uint64(state);
|
||||||
|
} while (x >= max);
|
||||||
|
return x % n;
|
||||||
|
}
|
||||||
|
|||||||
+22
-10
@@ -1,21 +1,33 @@
|
|||||||
#ifndef __STATS_H
|
#ifndef STATS_H
|
||||||
#define __STATS_H
|
#define STATS_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "tinymt64.h"
|
||||||
|
|
||||||
|
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
|
||||||
|
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int samples;
|
uint64_t samples;
|
||||||
int index;
|
uint64_t index;
|
||||||
int limit;
|
uint64_t limit;
|
||||||
|
uint64_t min;
|
||||||
|
uint64_t max;
|
||||||
uint64_t data[];
|
uint64_t data[];
|
||||||
} stats;
|
} stats;
|
||||||
|
|
||||||
stats *stats_alloc(int);
|
stats *stats_alloc(uint64_t);
|
||||||
void stats_free(stats *);
|
void stats_free(stats *);
|
||||||
|
void stats_reset(stats *);
|
||||||
|
|
||||||
void stats_record(stats *, uint64_t);
|
void stats_record(stats *, uint64_t);
|
||||||
uint64_t stats_min(stats *);
|
|
||||||
uint64_t stats_max(stats *);
|
long double stats_summarize(stats *);
|
||||||
long double stats_mean(stats *);
|
|
||||||
long double stats_stdev(stats *stats, long double);
|
long double stats_stdev(stats *stats, long double);
|
||||||
long double stats_within_stdev(stats *, long double, long double, uint64_t);
|
long double stats_within_stdev(stats *, long double, long double, uint64_t);
|
||||||
|
uint64_t stats_percentile(stats *, long double);
|
||||||
|
|
||||||
#endif /* __STATS_H */
|
void stats_sample(stats *, tinymt64_t *, uint64_t, stats *);
|
||||||
|
uint64_t rand64(tinymt64_t *, uint64_t);
|
||||||
|
|
||||||
|
#endif /* STATS_H */
|
||||||
|
|||||||
+10
-2
@@ -62,10 +62,10 @@ static int scan_units(char *s, uint64_t *n, units *m) {
|
|||||||
|
|
||||||
if ((c = sscanf(s, "%"SCNu64"%2s", &base, unit)) < 1) return -1;
|
if ((c = sscanf(s, "%"SCNu64"%2s", &base, unit)) < 1) return -1;
|
||||||
|
|
||||||
if (c == 2) {
|
if (c == 2 && strncasecmp(unit, m->base, 3)) {
|
||||||
for (i = 0; m->units[i] != NULL; i++) {
|
for (i = 0; m->units[i] != NULL; i++) {
|
||||||
scale *= m->scale;
|
scale *= m->scale;
|
||||||
if (!strncasecmp(unit, m->units[i], sizeof(unit))) break;
|
if (!strncasecmp(unit, m->units[i], 3)) break;
|
||||||
}
|
}
|
||||||
if (m->units[i] == NULL) return -1;
|
if (m->units[i] == NULL) return -1;
|
||||||
}
|
}
|
||||||
@@ -91,6 +91,14 @@ char *format_time_us(long double n) {
|
|||||||
return format_units(n, units, 2);
|
return format_units(n, units, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *format_time_s(long double n) {
|
||||||
|
return format_units(n, &time_units_s, 0);
|
||||||
|
}
|
||||||
|
|
||||||
int scan_metric(char *s, uint64_t *n) {
|
int scan_metric(char *s, uint64_t *n) {
|
||||||
return scan_units(s, n, &metric_units);
|
return scan_units(s, n, &metric_units);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int scan_time(char *s, uint64_t *n) {
|
||||||
|
return scan_units(s, n, &time_units_s);
|
||||||
|
}
|
||||||
|
|||||||
+5
-4
@@ -1,11 +1,12 @@
|
|||||||
#ifndef __UNITS_H
|
#ifndef UNITS_H
|
||||||
#define __UNITS_H
|
#define UNITS_H
|
||||||
|
|
||||||
char *format_binary(long double);
|
char *format_binary(long double);
|
||||||
char *format_metric(long double);
|
char *format_metric(long double);
|
||||||
char *format_time_us(long double);
|
char *format_time_us(long double);
|
||||||
|
char *format_time_s(long double);
|
||||||
|
|
||||||
int scan_metric(char *, uint64_t *);
|
int scan_metric(char *, uint64_t *);
|
||||||
|
int scan_time(char *, uint64_t *);
|
||||||
|
|
||||||
#endif /* __UNITS_H */
|
#endif /* UNITS_H */
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,24 @@
|
|||||||
// Copyright (C) 2012 - Will Glozer. All rights reserved.
|
// Copyright (C) 2012 - Will Glozer. All rights reserved.
|
||||||
|
|
||||||
#include "wrk.h"
|
#include "wrk.h"
|
||||||
|
#include "main.h"
|
||||||
#include <ctype.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <netinet/tcp.h>
|
|
||||||
#include <stdarg.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <time.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/time.h>
|
|
||||||
#include <sys/uio.h>
|
|
||||||
|
|
||||||
#include "aprintf.h"
|
|
||||||
#include "stats.h"
|
|
||||||
#include "units.h"
|
|
||||||
#include "zmalloc.h"
|
|
||||||
#include "tinymt64.h"
|
|
||||||
|
|
||||||
static struct config {
|
static struct config {
|
||||||
struct addrinfo addr;
|
struct addrinfo addr;
|
||||||
uint64_t threads;
|
uint64_t threads;
|
||||||
uint64_t connections;
|
uint64_t connections;
|
||||||
uint64_t requests;
|
uint64_t duration;
|
||||||
uint64_t timeout;
|
uint64_t timeout;
|
||||||
|
bool latency;
|
||||||
|
SSL_CTX *ctx;
|
||||||
} cfg;
|
} cfg;
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
|
char *method;
|
||||||
|
char *body;
|
||||||
size_t size;
|
size_t size;
|
||||||
char *buf;
|
char *buf;
|
||||||
} request;
|
} req;
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
stats *latency;
|
stats *latency;
|
||||||
@@ -45,21 +26,39 @@ static struct {
|
|||||||
pthread_mutex_t mutex;
|
pthread_mutex_t mutex;
|
||||||
} statistics;
|
} statistics;
|
||||||
|
|
||||||
|
static struct sock sock = {
|
||||||
|
.connect = sock_connect,
|
||||||
|
.close = sock_close,
|
||||||
|
.read = sock_read,
|
||||||
|
.write = sock_write
|
||||||
|
};
|
||||||
|
|
||||||
static const struct http_parser_settings parser_settings = {
|
static const struct http_parser_settings parser_settings = {
|
||||||
.on_message_complete = request_complete
|
.on_message_complete = request_complete
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static volatile sig_atomic_t stop = 0;
|
||||||
|
|
||||||
|
static void handler(int sig) {
|
||||||
|
stop = 1;
|
||||||
|
}
|
||||||
|
|
||||||
static void usage() {
|
static void usage() {
|
||||||
printf("Usage: wrk <options> <url> \n"
|
printf("Usage: wrk <options> <url> \n"
|
||||||
" Options: \n"
|
" Options: \n"
|
||||||
" -c, --connections <n> Connections to keep open \n"
|
" -c, --connections <N> Connections to keep open \n"
|
||||||
" -r, --requests <n> Total requests to make \n"
|
" -d, --duration <T> Duration of test \n"
|
||||||
" -t, --threads <n> Number of threads to use \n"
|
" -t, --threads <N> Number of threads to use \n"
|
||||||
" \n"
|
" \n"
|
||||||
" -H, --header <h> Add header to request \n"
|
" -H, --header <H> Add header to request \n"
|
||||||
|
" -M, --method <M> HTTP method \n"
|
||||||
|
" --body <B> Request body \n"
|
||||||
|
" --latency Print latency statistics \n"
|
||||||
|
" --timeout <T> Socket/request timeout \n"
|
||||||
" -v, --version Print version details \n"
|
" -v, --version Print version details \n"
|
||||||
" \n"
|
" \n"
|
||||||
" Numeric arguments may include a SI unit (2k, 2M, 2G)\n");
|
" Numeric arguments may include a SI unit (1k, 1M, 1G)\n"
|
||||||
|
" Time arguments may include a time unit (2s, 2m, 2h)\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
@@ -80,10 +79,15 @@ int main(int argc, char **argv) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
char *host = extract_url_part(url, &parser_url, UF_HOST);
|
char *schema = extract_url_part(url, &parser_url, UF_SCHEMA);
|
||||||
char *port = extract_url_part(url, &parser_url, UF_PORT);
|
char *host = extract_url_part(url, &parser_url, UF_HOST);
|
||||||
char *service = port ? port : extract_url_part(url, &parser_url, UF_SCHEMA);
|
char *port = extract_url_part(url, &parser_url, UF_PORT);
|
||||||
char *path = &url[parser_url.field_data[UF_PATH].off];
|
char *service = port ? port : schema;
|
||||||
|
char *path = "/";
|
||||||
|
|
||||||
|
if (parser_url.field_set & (1 << UF_PATH)) {
|
||||||
|
path = &url[parser_url.field_data[UF_PATH].off];
|
||||||
|
}
|
||||||
|
|
||||||
struct addrinfo hints = {
|
struct addrinfo hints = {
|
||||||
.ai_family = AF_UNSPEC,
|
.ai_family = AF_UNSPEC,
|
||||||
@@ -99,14 +103,9 @@ int main(int argc, char **argv) {
|
|||||||
for (addr = addrs; addr != NULL; addr = addr->ai_next) {
|
for (addr = addrs; addr != NULL; addr = addr->ai_next) {
|
||||||
int fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
|
int fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
|
||||||
if (fd == -1) continue;
|
if (fd == -1) continue;
|
||||||
if (connect(fd, addr->ai_addr, addr->ai_addrlen) == -1) {
|
rc = connect(fd, addr->ai_addr, addr->ai_addrlen);
|
||||||
if (errno == EHOSTUNREACH || errno == ECONNREFUSED) {
|
|
||||||
close(fd);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(fd);
|
close(fd);
|
||||||
break;
|
if (rc == 0) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addr == NULL) {
|
if (addr == NULL) {
|
||||||
@@ -115,9 +114,23 @@ int main(int argc, char **argv) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.addr = *addr;
|
if (!strncmp("https", schema, 5)) {
|
||||||
request.buf = format_request(host, port, path, headers);
|
if ((cfg.ctx = ssl_init()) == NULL) {
|
||||||
request.size = strlen(request.buf);
|
fprintf(stderr, "unable to initialize SSL\n");
|
||||||
|
ERR_print_errors_fp(stderr);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
sock.connect = ssl_connect;
|
||||||
|
sock.close = ssl_close;
|
||||||
|
sock.read = ssl_read;
|
||||||
|
sock.write = ssl_write;
|
||||||
|
}
|
||||||
|
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
signal(SIGINT, SIG_IGN);
|
||||||
|
cfg.addr = *addr;
|
||||||
|
req.buf = format_request(host, port, path, headers);
|
||||||
|
req.size = strlen(req.buf);
|
||||||
|
|
||||||
pthread_mutex_init(&statistics.mutex, NULL);
|
pthread_mutex_init(&statistics.mutex, NULL);
|
||||||
statistics.latency = stats_alloc(SAMPLES);
|
statistics.latency = stats_alloc(SAMPLES);
|
||||||
@@ -125,29 +138,37 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
thread *threads = zcalloc(cfg.threads * sizeof(thread));
|
thread *threads = zcalloc(cfg.threads * sizeof(thread));
|
||||||
uint64_t connections = cfg.connections / cfg.threads;
|
uint64_t connections = cfg.connections / cfg.threads;
|
||||||
uint64_t requests = cfg.requests / cfg.threads;
|
uint64_t stop_at = time_us() + (cfg.duration * 1000000);
|
||||||
|
|
||||||
for (int i = 0; i < cfg.threads; i++) {
|
for (uint64_t i = 0; i < cfg.threads; i++) {
|
||||||
thread *t = &threads[i];
|
thread *t = &threads[i];
|
||||||
t->connections = connections;
|
t->connections = connections;
|
||||||
t->requests = requests;
|
t->stop_at = stop_at;
|
||||||
|
|
||||||
if (pthread_create(&t->thread, NULL, &thread_main, t)) {
|
if (pthread_create(&t->thread, NULL, &thread_main, t)) {
|
||||||
char *msg = strerror(errno);
|
char *msg = strerror(errno);
|
||||||
fprintf(stderr, "unable to create thread %d %s\n", i, msg);
|
fprintf(stderr, "unable to create thread %"PRIu64" %s\n", i, msg);
|
||||||
exit(2);
|
exit(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Making %"PRIu64" requests to %s\n", cfg.requests, url);
|
struct sigaction sa = {
|
||||||
|
.sa_handler = handler,
|
||||||
|
.sa_flags = 0,
|
||||||
|
};
|
||||||
|
sigfillset(&sa.sa_mask);
|
||||||
|
sigaction(SIGINT, &sa, NULL);
|
||||||
|
|
||||||
|
char *time = format_time_s(cfg.duration);
|
||||||
|
printf("Running %s test @ %s\n", time, url);
|
||||||
printf(" %"PRIu64" threads and %"PRIu64" connections\n", cfg.threads, cfg.connections);
|
printf(" %"PRIu64" threads and %"PRIu64" connections\n", cfg.threads, cfg.connections);
|
||||||
|
|
||||||
uint64_t start = time_us();
|
uint64_t start = time_us();
|
||||||
uint64_t complete = 0;
|
uint64_t complete = 0;
|
||||||
uint64_t bytes = 0;
|
uint64_t bytes = 0;
|
||||||
errors_t errors = { 0 };
|
errors errors = { 0 };
|
||||||
|
|
||||||
for (int i = 0; i < cfg.threads; i++) {
|
for (uint64_t i = 0; i < cfg.threads; i++) {
|
||||||
thread *t = &threads[i];
|
thread *t = &threads[i];
|
||||||
pthread_join(t->thread, NULL);
|
pthread_join(t->thread, NULL);
|
||||||
|
|
||||||
@@ -169,6 +190,7 @@ int main(int argc, char **argv) {
|
|||||||
print_stats_header();
|
print_stats_header();
|
||||||
print_stats("Latency", statistics.latency, format_time_us);
|
print_stats("Latency", statistics.latency, format_time_us);
|
||||||
print_stats("Req/Sec", statistics.requests, format_metric);
|
print_stats("Req/Sec", statistics.requests, format_metric);
|
||||||
|
if (cfg.latency) print_stats_latency(statistics.latency);
|
||||||
|
|
||||||
char *runtime_msg = format_time_us(runtime_us);
|
char *runtime_msg = format_time_us(runtime_us);
|
||||||
|
|
||||||
@@ -195,16 +217,17 @@ void *thread_main(void *arg) {
|
|||||||
thread->cs = zmalloc(thread->connections * sizeof(connection));
|
thread->cs = zmalloc(thread->connections * sizeof(connection));
|
||||||
thread->loop = loop;
|
thread->loop = loop;
|
||||||
tinymt64_init(&thread->rand, time_us());
|
tinymt64_init(&thread->rand, time_us());
|
||||||
|
thread->latency = stats_alloc(100000);
|
||||||
|
|
||||||
connection *c = thread->cs;
|
connection *c = thread->cs;
|
||||||
|
|
||||||
for (int i = 0; i < thread->connections; i++, c++) {
|
for (uint64_t i = 0; i < thread->connections; i++, c++) {
|
||||||
c->thread = thread;
|
c->thread = thread;
|
||||||
c->latency = 0;
|
c->ssl = cfg.ctx ? SSL_new(cfg.ctx) : NULL;
|
||||||
connect_socket(thread, c);
|
connect_socket(thread, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
aeCreateTimeEvent(loop, SAMPLE_INTERVAL_MS, sample_rate, thread, NULL);
|
aeCreateTimeEvent(loop, CALIBRATE_DELAY_MS, calibrate, thread, NULL);
|
||||||
aeCreateTimeEvent(loop, TIMEOUT_INTERVAL_MS, check_timeouts, thread, NULL);
|
aeCreateTimeEvent(loop, TIMEOUT_INTERVAL_MS, check_timeouts, thread, NULL);
|
||||||
|
|
||||||
thread->start = time_us();
|
thread->start = time_us();
|
||||||
@@ -213,6 +236,15 @@ void *thread_main(void *arg) {
|
|||||||
aeDeleteEventLoop(loop);
|
aeDeleteEventLoop(loop);
|
||||||
zfree(thread->cs);
|
zfree(thread->cs);
|
||||||
|
|
||||||
|
uint64_t max = thread->latency->max;
|
||||||
|
stats_free(thread->latency);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&statistics.mutex);
|
||||||
|
for (uint64_t i = 0; i < thread->missed; i++) {
|
||||||
|
stats_record(statistics.latency, max);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&statistics.mutex);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,70 +259,95 @@ static int connect_socket(thread *thread, connection *c) {
|
|||||||
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
||||||
|
|
||||||
if (connect(fd, addr.ai_addr, addr.ai_addrlen) == -1) {
|
if (connect(fd, addr.ai_addr, addr.ai_addrlen) == -1) {
|
||||||
if (errno != EINPROGRESS) {
|
if (errno != EINPROGRESS) goto error;
|
||||||
thread->errors.connect++;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
flags = 1;
|
flags = 1;
|
||||||
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags));
|
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags));
|
||||||
|
|
||||||
if (aeCreateFileEvent(loop, fd, AE_WRITABLE, socket_writeable, c) != AE_OK) {
|
flags = AE_READABLE | AE_WRITABLE;
|
||||||
goto error;
|
if (aeCreateFileEvent(loop, fd, flags, socket_connected, c) == AE_OK) {
|
||||||
|
c->parser.data = c;
|
||||||
|
c->fd = fd;
|
||||||
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
http_parser_init(&c->parser, HTTP_RESPONSE);
|
|
||||||
c->parser.data = c;
|
|
||||||
c->fd = fd;
|
|
||||||
|
|
||||||
return fd;
|
|
||||||
|
|
||||||
error:
|
error:
|
||||||
|
thread->errors.connect++;
|
||||||
close(fd);
|
close(fd);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int reconnect_socket(thread *thread, connection *c) {
|
static int reconnect_socket(thread *thread, connection *c) {
|
||||||
aeDeleteFileEvent(thread->loop, c->fd, AE_WRITABLE | AE_READABLE);
|
aeDeleteFileEvent(thread->loop, c->fd, AE_WRITABLE | AE_READABLE);
|
||||||
|
sock.close(c);
|
||||||
close(c->fd);
|
close(c->fd);
|
||||||
return connect_socket(thread, c);
|
return connect_socket(thread, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int calibrate(aeEventLoop *loop, long long id, void *data) {
|
||||||
|
thread *thread = data;
|
||||||
|
|
||||||
|
uint64_t elapsed_ms = (time_us() - thread->start) / 1000;
|
||||||
|
uint64_t req_per_ms = thread->requests / elapsed_ms;
|
||||||
|
|
||||||
|
if (!req_per_ms) return CALIBRATE_DELAY_MS / 2;
|
||||||
|
|
||||||
|
thread->rate = (req_per_ms * SAMPLE_INTERVAL_MS) / 10;
|
||||||
|
thread->start = time_us();
|
||||||
|
thread->requests = 0;
|
||||||
|
stats_reset(thread->latency);
|
||||||
|
|
||||||
|
aeCreateTimeEvent(loop, SAMPLE_INTERVAL_MS, sample_rate, thread, NULL);
|
||||||
|
|
||||||
|
return AE_NOMORE;
|
||||||
|
}
|
||||||
|
|
||||||
static int sample_rate(aeEventLoop *loop, long long id, void *data) {
|
static int sample_rate(aeEventLoop *loop, long long id, void *data) {
|
||||||
thread *thread = data;
|
thread *thread = data;
|
||||||
|
|
||||||
uint64_t n = rand64(&thread->rand, thread->connections);
|
|
||||||
uint64_t elapsed_ms = (time_us() - thread->start) / 1000;
|
uint64_t elapsed_ms = (time_us() - thread->start) / 1000;
|
||||||
connection *c = thread->cs + n;
|
uint64_t requests = (thread->requests / elapsed_ms) * 1000;
|
||||||
uint64_t requests = (thread->complete / elapsed_ms) * 1000;
|
uint64_t missed = thread->rate - MIN(thread->rate, thread->latency->limit);
|
||||||
|
uint64_t count = thread->rate - missed;
|
||||||
|
|
||||||
pthread_mutex_lock(&statistics.mutex);
|
pthread_mutex_lock(&statistics.mutex);
|
||||||
stats_record(statistics.latency, c->latency);
|
stats_sample(statistics.latency, &thread->rand, count, thread->latency);
|
||||||
stats_record(statistics.requests, requests);
|
stats_record(statistics.requests, requests);
|
||||||
pthread_mutex_unlock(&statistics.mutex);
|
pthread_mutex_unlock(&statistics.mutex);
|
||||||
|
|
||||||
return SAMPLE_INTERVAL_MS + rand64(&thread->rand, SAMPLE_INTERVAL_MS);
|
uint64_t max = thread->latency->max;
|
||||||
|
thread->missed += missed;
|
||||||
|
thread->requests = 0;
|
||||||
|
thread->start = time_us();
|
||||||
|
stats_reset(thread->latency);
|
||||||
|
thread->latency->max = max;
|
||||||
|
|
||||||
|
return SAMPLE_INTERVAL_MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int request_complete(http_parser *parser) {
|
static int request_complete(http_parser *parser) {
|
||||||
connection *c = parser->data;
|
connection *c = parser->data;
|
||||||
thread *thread = c->thread;
|
thread *thread = c->thread;
|
||||||
|
uint64_t now = time_us();
|
||||||
|
|
||||||
|
thread->complete++;
|
||||||
|
thread->requests++;
|
||||||
|
|
||||||
|
stats_record(thread->latency, now - c->start);
|
||||||
|
|
||||||
if (parser->status_code > 399) {
|
if (parser->status_code > 399) {
|
||||||
thread->errors.status++;
|
thread->errors.status++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (++thread->complete >= thread->requests) {
|
if (now >= thread->stop_at) {
|
||||||
aeStop(thread->loop);
|
aeStop(thread->loop);
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
c->latency = time_us() - c->start;
|
|
||||||
if (!http_should_keep_alive(parser)) goto reconnect;
|
if (!http_should_keep_alive(parser)) goto reconnect;
|
||||||
|
|
||||||
http_parser_init(parser, HTTP_RESPONSE);
|
http_parser_init(parser, HTTP_RESPONSE);
|
||||||
aeDeleteFileEvent(thread->loop, c->fd, AE_READABLE);
|
|
||||||
aeCreateFileEvent(thread->loop, c->fd, AE_WRITABLE, socket_writeable, c);
|
aeCreateFileEvent(thread->loop, c->fd, AE_WRITABLE, socket_writeable, c);
|
||||||
|
|
||||||
goto done;
|
goto done;
|
||||||
@@ -304,26 +361,65 @@ static int request_complete(http_parser *parser) {
|
|||||||
|
|
||||||
static int check_timeouts(aeEventLoop *loop, long long id, void *data) {
|
static int check_timeouts(aeEventLoop *loop, long long id, void *data) {
|
||||||
thread *thread = data;
|
thread *thread = data;
|
||||||
connection *c = thread->cs;
|
connection *c = thread->cs;
|
||||||
|
uint64_t now = time_us();
|
||||||
|
|
||||||
uint64_t maxAge = time_us() - (cfg.timeout * 1000);
|
uint64_t maxAge = now - (cfg.timeout * 1000);
|
||||||
|
|
||||||
for (int i = 0; i < thread->connections; i++, c++) {
|
for (uint64_t i = 0; i < thread->connections; i++, c++) {
|
||||||
if (maxAge > c->start) {
|
if (maxAge > c->start) {
|
||||||
thread->errors.timeout++;
|
thread->errors.timeout++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stop || now >= thread->stop_at) {
|
||||||
|
aeStop(loop);
|
||||||
|
}
|
||||||
|
|
||||||
return TIMEOUT_INTERVAL_MS;
|
return TIMEOUT_INTERVAL_MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void socket_connected(aeEventLoop *loop, int fd, void *data, int mask) {
|
||||||
|
connection *c = data;
|
||||||
|
|
||||||
|
switch (sock.connect(c)) {
|
||||||
|
case OK: break;
|
||||||
|
case ERROR: goto error;
|
||||||
|
case RETRY: return;
|
||||||
|
}
|
||||||
|
|
||||||
|
http_parser_init(&c->parser, HTTP_RESPONSE);
|
||||||
|
c->written = 0;
|
||||||
|
|
||||||
|
aeCreateFileEvent(c->thread->loop, c->fd, AE_READABLE, socket_readable, c);
|
||||||
|
aeCreateFileEvent(c->thread->loop, c->fd, AE_WRITABLE, socket_writeable, c);
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
error:
|
||||||
|
c->thread->errors.connect++;
|
||||||
|
reconnect_socket(c->thread, c);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static void socket_writeable(aeEventLoop *loop, int fd, void *data, int mask) {
|
static void socket_writeable(aeEventLoop *loop, int fd, void *data, int mask) {
|
||||||
connection *c = data;
|
connection *c = data;
|
||||||
|
size_t len = req.size - c->written;
|
||||||
|
size_t n;
|
||||||
|
|
||||||
if (write(fd, request.buf, request.size) < request.size) goto error;
|
switch (sock.write(c, req.buf + c->written, len, &n)) {
|
||||||
c->start = time_us();
|
case OK: break;
|
||||||
aeDeleteFileEvent(loop, fd, AE_WRITABLE);
|
case ERROR: goto error;
|
||||||
aeCreateFileEvent(loop, fd, AE_READABLE, socket_readable, c);
|
case RETRY: return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!c->written) c->start = time_us();
|
||||||
|
|
||||||
|
c->written += n;
|
||||||
|
if (c->written == req.size) {
|
||||||
|
c->written = 0;
|
||||||
|
aeDeleteFileEvent(loop, fd, AE_WRITABLE);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -332,11 +428,17 @@ static void socket_writeable(aeEventLoop *loop, int fd, void *data, int mask) {
|
|||||||
reconnect_socket(c->thread, c);
|
reconnect_socket(c->thread, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void socket_readable(aeEventLoop *loop, int fd, void *data, int mask) {
|
static void socket_readable(aeEventLoop *loop, int fd, void *data, int mask) {
|
||||||
connection *c = data;
|
connection *c = data;
|
||||||
int n;
|
size_t n;
|
||||||
|
|
||||||
|
switch (sock.read(c, &n)) {
|
||||||
|
case OK: break;
|
||||||
|
case ERROR: goto error;
|
||||||
|
case RETRY: return;
|
||||||
|
}
|
||||||
|
|
||||||
if ((n = read(fd, c->buf, sizeof(c->buf))) == -1) goto error;
|
|
||||||
if (http_parser_execute(&c->parser, &parser_settings, c->buf, n) != n) goto error;
|
if (http_parser_execute(&c->parser, &parser_settings, c->buf, n) != n) goto error;
|
||||||
c->thread->bytes += n;
|
c->thread->bytes += n;
|
||||||
|
|
||||||
@@ -353,15 +455,6 @@ static uint64_t time_us() {
|
|||||||
return (t.tv_sec * 1000000) + t.tv_usec;
|
return (t.tv_sec * 1000000) + t.tv_usec;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint64_t rand64(tinymt64_t *state, uint64_t n) {
|
|
||||||
uint64_t x, max = ~UINT64_C(0);
|
|
||||||
max -= max % n;
|
|
||||||
do {
|
|
||||||
x = tinymt64_generate_uint64(state);
|
|
||||||
} while (x >= max);
|
|
||||||
return x % n;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *extract_url_part(char *url, struct http_parser_url *parser_url, enum http_parser_url_fields field) {
|
static char *extract_url_part(char *url, struct http_parser_url *parser_url, enum http_parser_url_fields field) {
|
||||||
char *part = NULL;
|
char *part = NULL;
|
||||||
|
|
||||||
@@ -376,26 +469,43 @@ static char *extract_url_part(char *url, struct http_parser_url *parser_url, enu
|
|||||||
}
|
}
|
||||||
|
|
||||||
static char *format_request(char *host, char *port, char *path, char **headers) {
|
static char *format_request(char *host, char *port, char *path, char **headers) {
|
||||||
char *req = NULL;
|
char *buf = NULL;
|
||||||
|
char *head = NULL;
|
||||||
aprintf(&req, "GET %s HTTP/1.1\r\n", path);
|
|
||||||
aprintf(&req, "Host: %s", host);
|
|
||||||
if (port) aprintf(&req, ":%s", port);
|
|
||||||
aprintf(&req, "\r\n");
|
|
||||||
|
|
||||||
for (char **h = headers; *h != NULL; h++) {
|
for (char **h = headers; *h != NULL; h++) {
|
||||||
aprintf(&req, "%s\r\n", *h);
|
aprintf(&head, "%s\r\n", *h);
|
||||||
|
if (!strncasecmp(*h, "Host:", 5)) {
|
||||||
|
host = NULL;
|
||||||
|
port = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aprintf(&req, "\r\n");
|
if (req.body) {
|
||||||
return req;
|
size_t len = strlen(req.body);
|
||||||
|
aprintf(&head, "Content-Length: %zd\r\n", len);
|
||||||
|
}
|
||||||
|
|
||||||
|
aprintf(&buf, "%s %s HTTP/1.1\r\n", req.method, path);
|
||||||
|
if (host) aprintf(&buf, "Host: %s", host);
|
||||||
|
if (port) aprintf(&buf, ":%s", port);
|
||||||
|
if (host) aprintf(&buf, "\r\n");
|
||||||
|
|
||||||
|
aprintf(&buf, "%s\r\n", head ? head : "");
|
||||||
|
aprintf(&buf, "%s", req.body ? req.body : "");
|
||||||
|
|
||||||
|
free(head);
|
||||||
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct option longopts[] = {
|
static struct option longopts[] = {
|
||||||
{ "connections", required_argument, NULL, 'c' },
|
{ "connections", required_argument, NULL, 'c' },
|
||||||
{ "requests", required_argument, NULL, 'r' },
|
{ "duration", required_argument, NULL, 'd' },
|
||||||
{ "threads", required_argument, NULL, 't' },
|
{ "threads", required_argument, NULL, 't' },
|
||||||
{ "header", required_argument, NULL, 'H' },
|
{ "header", required_argument, NULL, 'H' },
|
||||||
|
{ "method", required_argument, NULL, 'M' },
|
||||||
|
{ "body", required_argument, NULL, 'B' },
|
||||||
|
{ "latency", no_argument, NULL, 'L' },
|
||||||
|
{ "timeout", required_argument, NULL, 'T' },
|
||||||
{ "help", no_argument, NULL, 'h' },
|
{ "help", no_argument, NULL, 'h' },
|
||||||
{ "version", no_argument, NULL, 'v' },
|
{ "version", no_argument, NULL, 'v' },
|
||||||
{ NULL, 0, NULL, 0 }
|
{ NULL, 0, NULL, 0 }
|
||||||
@@ -407,10 +517,11 @@ static int parse_args(struct config *cfg, char **url, char **headers, int argc,
|
|||||||
memset(cfg, 0, sizeof(struct config));
|
memset(cfg, 0, sizeof(struct config));
|
||||||
cfg->threads = 2;
|
cfg->threads = 2;
|
||||||
cfg->connections = 10;
|
cfg->connections = 10;
|
||||||
cfg->requests = 100;
|
cfg->duration = 10;
|
||||||
cfg->timeout = SOCKET_TIMEOUT_MS;
|
cfg->timeout = SOCKET_TIMEOUT_MS;
|
||||||
|
req.method = "GET";
|
||||||
|
|
||||||
while ((c = getopt_long(argc, argv, "t:c:r:H:v?", longopts, NULL)) != -1) {
|
while ((c = getopt_long(argc, argv, "t:c:d:H:M:B:T:Lrv?", longopts, NULL)) != -1) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case 't':
|
case 't':
|
||||||
if (scan_metric(optarg, &cfg->threads)) return -1;
|
if (scan_metric(optarg, &cfg->threads)) return -1;
|
||||||
@@ -418,16 +529,31 @@ static int parse_args(struct config *cfg, char **url, char **headers, int argc,
|
|||||||
case 'c':
|
case 'c':
|
||||||
if (scan_metric(optarg, &cfg->connections)) return -1;
|
if (scan_metric(optarg, &cfg->connections)) return -1;
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'd':
|
||||||
if (scan_metric(optarg, &cfg->requests)) return -1;
|
if (scan_time(optarg, &cfg->duration)) return -1;
|
||||||
break;
|
break;
|
||||||
case 'H':
|
case 'H':
|
||||||
*header++ = optarg;
|
*header++ = optarg;
|
||||||
break;
|
break;
|
||||||
|
case 'M':
|
||||||
|
req.method = optarg;
|
||||||
|
break;
|
||||||
|
case 'B':
|
||||||
|
req.body = optarg;
|
||||||
|
break;
|
||||||
|
case 'L':
|
||||||
|
cfg->latency = true;
|
||||||
|
break;
|
||||||
|
case 'T':
|
||||||
|
if (scan_time(optarg, &cfg->timeout)) return -1;
|
||||||
|
cfg->timeout *= 1000;
|
||||||
|
break;
|
||||||
case 'v':
|
case 'v':
|
||||||
printf("wrk %s [%s] ", VERSION, aeGetApiName());
|
printf("wrk %s [%s] ", VERSION, aeGetApiName());
|
||||||
printf("Copyright (C) 2012 Will Glozer\n");
|
printf("Copyright (C) 2012 Will Glozer\n");
|
||||||
break;
|
break;
|
||||||
|
case 'r':
|
||||||
|
fprintf(stderr, "wrk 2.0.0+ uses -d instead of -r\n");
|
||||||
case 'h':
|
case 'h':
|
||||||
case '?':
|
case '?':
|
||||||
case ':':
|
case ':':
|
||||||
@@ -436,7 +562,7 @@ static int parse_args(struct config *cfg, char **url, char **headers, int argc,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (optind == argc || !cfg->threads || !cfg->requests) return -1;
|
if (optind == argc || !cfg->threads || !cfg->duration) return -1;
|
||||||
|
|
||||||
if (!cfg->connections || cfg->connections < cfg->threads) {
|
if (!cfg->connections || cfg->connections < cfg->threads) {
|
||||||
fprintf(stderr, "number of connections must be >= threads\n");
|
fprintf(stderr, "number of connections must be >= threads\n");
|
||||||
@@ -467,8 +593,8 @@ static void print_units(long double n, char *(*fmt)(long double), int width) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void print_stats(char *name, stats *stats, char *(*fmt)(long double)) {
|
static void print_stats(char *name, stats *stats, char *(*fmt)(long double)) {
|
||||||
long double mean = stats_mean(stats);
|
uint64_t max = stats->max;
|
||||||
long double max = stats_max(stats);
|
long double mean = stats_summarize(stats);
|
||||||
long double stdev = stats_stdev(stats, mean);
|
long double stdev = stats_stdev(stats, mean);
|
||||||
|
|
||||||
printf(" %-10s", name);
|
printf(" %-10s", name);
|
||||||
@@ -477,3 +603,15 @@ static void print_stats(char *name, stats *stats, char *(*fmt)(long double)) {
|
|||||||
print_units(max, fmt, 9);
|
print_units(max, fmt, 9);
|
||||||
printf("%8.2Lf%%\n", stats_within_stdev(stats, mean, stdev, 1));
|
printf("%8.2Lf%%\n", stats_within_stdev(stats, mean, stdev, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void print_stats_latency(stats *stats) {
|
||||||
|
long double percentiles[] = { 50.0, 75.0, 90.0, 99.0 };
|
||||||
|
printf(" Latency Distribution\n");
|
||||||
|
for (size_t i = 0; i < sizeof(percentiles) / sizeof(long double); i++) {
|
||||||
|
long double p = percentiles[i];
|
||||||
|
uint64_t n = stats_percentile(stats, p);
|
||||||
|
printf("%7.0Lf%%", p);
|
||||||
|
print_units(n, format_time_us, 10);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
#ifndef __WRK_H
|
#ifndef WRK_H
|
||||||
#define __WRK_H
|
#define WRK_H
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <openssl/ssl.h>
|
||||||
|
#include <openssl/err.h>
|
||||||
|
|
||||||
#include "stats.h"
|
#include "stats.h"
|
||||||
#include "ae.h"
|
#include "ae.h"
|
||||||
#include "http_parser.h"
|
#include "http_parser.h"
|
||||||
#include "tinymt64.h"
|
|
||||||
|
|
||||||
#define VERSION "1.0.0"
|
#define VERSION "2.2.0"
|
||||||
#define RECVBUF 8192
|
#define RECVBUF 8192
|
||||||
#define SAMPLES 100000
|
#define SAMPLES 100000000
|
||||||
|
|
||||||
#define SOCKET_TIMEOUT_MS 2000
|
#define SOCKET_TIMEOUT_MS 2000
|
||||||
#define SAMPLE_INTERVAL_MS 100
|
#define SAMPLE_INTERVAL_MS 10
|
||||||
|
#define CALIBRATE_DELAY_MS 500
|
||||||
#define TIMEOUT_INTERVAL_MS 2000
|
#define TIMEOUT_INTERVAL_MS 2000
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -25,51 +28,33 @@ typedef struct {
|
|||||||
uint32_t write;
|
uint32_t write;
|
||||||
uint32_t status;
|
uint32_t status;
|
||||||
uint32_t timeout;
|
uint32_t timeout;
|
||||||
} errors_t;
|
} errors;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
pthread_t thread;
|
pthread_t thread;
|
||||||
aeEventLoop *loop;
|
aeEventLoop *loop;
|
||||||
uint64_t connections;
|
uint64_t connections;
|
||||||
uint64_t requests;
|
uint64_t stop_at;
|
||||||
uint64_t complete;
|
uint64_t complete;
|
||||||
|
uint64_t requests;
|
||||||
uint64_t bytes;
|
uint64_t bytes;
|
||||||
uint64_t start;
|
uint64_t start;
|
||||||
|
uint64_t rate;
|
||||||
|
uint64_t missed;
|
||||||
|
stats *latency;
|
||||||
tinymt64_t rand;
|
tinymt64_t rand;
|
||||||
errors_t errors;
|
errors errors;
|
||||||
struct _connection *cs;
|
struct connection *cs;
|
||||||
} thread;
|
} thread;
|
||||||
|
|
||||||
typedef struct _connection {
|
typedef struct connection {
|
||||||
thread *thread;
|
thread *thread;
|
||||||
http_parser parser;
|
http_parser parser;
|
||||||
int fd;
|
int fd;
|
||||||
|
SSL *ssl;
|
||||||
uint64_t start;
|
uint64_t start;
|
||||||
uint64_t latency;
|
size_t written;
|
||||||
char buf[RECVBUF];
|
char buf[RECVBUF];
|
||||||
} connection;
|
} connection;
|
||||||
|
|
||||||
struct config;
|
#endif /* WRK_H */
|
||||||
|
|
||||||
static void *thread_main(void *);
|
|
||||||
static int connect_socket(thread *, connection *);
|
|
||||||
static int reconnect_socket(thread *, connection *);
|
|
||||||
|
|
||||||
static int sample_rate(aeEventLoop *, long long, void *);
|
|
||||||
static int check_timeouts(aeEventLoop *, long long, void *);
|
|
||||||
|
|
||||||
static void socket_writeable(aeEventLoop *, int, void *, int);
|
|
||||||
static void socket_readable(aeEventLoop *, int, void *, int);
|
|
||||||
static int request_complete(http_parser *);
|
|
||||||
|
|
||||||
static uint64_t time_us();
|
|
||||||
static uint64_t rand64(tinymt64_t *, uint64_t);
|
|
||||||
|
|
||||||
static char *extract_url_part(char *, struct http_parser_url *, enum http_parser_url_fields);
|
|
||||||
static char *format_request(char *, char *, char *, char **);
|
|
||||||
|
|
||||||
static int parse_args(struct config *, char **, char **, int, char **);
|
|
||||||
static void print_stats_header();
|
|
||||||
static void print_stats(char *, stats *, char *(*)(long double));
|
|
||||||
|
|
||||||
#endif /* __WRK_H */
|
|
||||||
|
|||||||
Reference in New Issue
Block a user