├── .gitignore ├── deps ├── LuaJIT-2.0.4.tar.gz └── openssl-1.0.2g.tar.gz ├── src ├── aprintf.h ├── units.h ├── ssl.h ├── config.h ├── aprintf.c ├── net.h ├── net.c ├── stats.h ├── script.h ├── wrk.h ├── main.h ├── wrk.lua ├── units.c ├── ssl.c ├── stats.c ├── zmalloc.h ├── ae_select.c ├── ae_kqueue.c ├── ae.h ├── ae_epoll.c ├── zmalloc.c ├── ae_evport.c ├── http_parser.h ├── ae.c ├── script.c └── wrk.c ├── scripts ├── delay.lua ├── stop.lua ├── post.lua ├── pipeline.lua ├── report.lua ├── auth.lua ├── counter.lua ├── addr.lua └── setup.lua ├── CHANGES ├── INSTALL ├── README ├── Makefile ├── SCRIPTING ├── NOTICE └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | wrk 3 | -------------------------------------------------------------------------------- /deps/LuaJIT-2.0.4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imos/wrk/master/deps/LuaJIT-2.0.4.tar.gz -------------------------------------------------------------------------------- /deps/openssl-1.0.2g.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imos/wrk/master/deps/openssl-1.0.2g.tar.gz -------------------------------------------------------------------------------- /src/aprintf.h: -------------------------------------------------------------------------------- 1 | #ifndef APRINTF_H 2 | #define APRINTF_H 3 | 4 | char *aprintf(char **, const char *, ...); 5 | 6 | #endif /* APRINTF_H */ 7 | -------------------------------------------------------------------------------- /scripts/delay.lua: -------------------------------------------------------------------------------- 1 | -- example script that demonstrates adding a random 2 | -- 10-50ms delay before each request 3 | 4 | function delay() 5 | return math.random(10, 50) 6 | end 7 | -------------------------------------------------------------------------------- /scripts/stop.lua: -------------------------------------------------------------------------------- 1 | -- example script that demonstrates use of thread:stop() 2 | 3 | local counter = 1 4 | 5 | function response() 6 | if counter == 100 then 7 | wrk.thread:stop() 8 | end 9 | counter = counter + 1 10 | end 11 | -------------------------------------------------------------------------------- /scripts/post.lua: -------------------------------------------------------------------------------- 1 | -- example HTTP POST script which demonstrates setting the 2 | -- HTTP method, body, and adding a header 3 | 4 | wrk.method = "POST" 5 | wrk.body = "foo=bar&baz=quux" 6 | wrk.headers["Content-Type"] = "application/x-www-form-urlencoded" 7 | -------------------------------------------------------------------------------- /src/units.h: -------------------------------------------------------------------------------- 1 | #ifndef UNITS_H 2 | #define UNITS_H 3 | 4 | char *format_binary(long double); 5 | char *format_metric(long double); 6 | char *format_time_us(long double); 7 | char *format_time_s(long double); 8 | 9 | int scan_metric(char *, uint64_t *); 10 | int scan_time(char *, uint64_t *); 11 | 12 | #endif /* UNITS_H */ 13 | -------------------------------------------------------------------------------- /scripts/pipeline.lua: -------------------------------------------------------------------------------- 1 | -- example script demonstrating HTTP pipelining 2 | 3 | init = function(args) 4 | local r = {} 5 | r[1] = wrk.format(nil, "/?foo") 6 | r[2] = wrk.format(nil, "/?bar") 7 | r[3] = wrk.format(nil, "/?baz") 8 | 9 | req = table.concat(r) 10 | end 11 | 12 | request = function() 13 | return req 14 | end 15 | -------------------------------------------------------------------------------- /src/ssl.h: -------------------------------------------------------------------------------- 1 | #ifndef SSL_H 2 | #define SSL_H 3 | 4 | #include "net.h" 5 | 6 | SSL_CTX *ssl_init(); 7 | 8 | status ssl_connect(connection *, char *); 9 | status ssl_close(connection *); 10 | status ssl_read(connection *, size_t *); 11 | status ssl_write(connection *, char *, size_t, size_t *); 12 | size_t ssl_readable(connection *); 13 | 14 | #endif /* SSL_H */ 15 | -------------------------------------------------------------------------------- /scripts/report.lua: -------------------------------------------------------------------------------- 1 | -- example reporting script which demonstrates a custom 2 | -- done() function that prints latency percentiles as CSV 3 | 4 | done = function(summary, latency, requests) 5 | io.write("------------------------------\n") 6 | for _, p in pairs({ 50, 90, 99, 99.999 }) do 7 | n = latency:percentile(p) 8 | io.write(string.format("%g%%,%d\n", p, n)) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #if defined(__FreeBSD__) || defined(__APPLE__) 5 | #define HAVE_KQUEUE 6 | #elif defined(__linux__) 7 | #define HAVE_EPOLL 8 | #elif defined (__sun) 9 | #define HAVE_EVPORT 10 | #define _XPG6 11 | #define __EXTENSIONS__ 12 | #include 13 | #include 14 | #include 15 | #endif 16 | 17 | #endif /* CONFIG_H */ 18 | -------------------------------------------------------------------------------- /scripts/auth.lua: -------------------------------------------------------------------------------- 1 | -- example script that demonstrates response handling and 2 | -- retrieving an authentication token to set on all future 3 | -- requests 4 | 5 | token = nil 6 | path = "/authenticate" 7 | 8 | request = function() 9 | return wrk.format("GET", path) 10 | end 11 | 12 | response = function(status, headers, body) 13 | if not token and status == 200 then 14 | token = headers["X-Token"] 15 | path = "/resource" 16 | wrk.headers["X-Token"] = token 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /scripts/counter.lua: -------------------------------------------------------------------------------- 1 | -- example dynamic request script which demonstrates changing 2 | -- the request path and a header for each request 3 | ------------------------------------------------------------- 4 | -- NOTE: each wrk thread has an independent Lua scripting 5 | -- context and thus there will be one counter per thread 6 | 7 | counter = 0 8 | 9 | request = function() 10 | path = "/" .. counter 11 | wrk.headers["X-Counter"] = counter 12 | counter = counter + 1 13 | return wrk.format(nil, path) 14 | end 15 | -------------------------------------------------------------------------------- /scripts/addr.lua: -------------------------------------------------------------------------------- 1 | -- example script that demonstrates use of setup() to pass 2 | -- a random server address to each thread 3 | 4 | local addrs = nil 5 | 6 | function setup(thread) 7 | if not addrs then 8 | addrs = wrk.lookup(wrk.host, wrk.port or "http") 9 | for i = #addrs, 1, -1 do 10 | if not wrk.connect(addrs[i]) then 11 | table.remove(addrs, i) 12 | end 13 | end 14 | end 15 | 16 | thread.addr = addrs[math.random(#addrs)] 17 | end 18 | 19 | function init(args) 20 | local msg = "thread addr: %s" 21 | print(msg:format(wrk.thread.addr)) 22 | end 23 | -------------------------------------------------------------------------------- /src/aprintf.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 - Will Glozer. All rights reserved. 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | char *aprintf(char **s, const char *fmt, ...) { 9 | char *c = NULL; 10 | int n, len; 11 | va_list ap; 12 | 13 | va_start(ap, fmt); 14 | n = vsnprintf(NULL, 0, fmt, ap) + 1; 15 | va_end(ap); 16 | 17 | len = *s ? strlen(*s) : 0; 18 | 19 | if ((*s = realloc(*s, (len + n) * sizeof(char)))) { 20 | c = *s + len; 21 | va_start(ap, fmt); 22 | vsnprintf(c, n, fmt, ap); 23 | va_end(ap); 24 | } 25 | 26 | return c; 27 | } 28 | -------------------------------------------------------------------------------- /src/net.h: -------------------------------------------------------------------------------- 1 | #ifndef NET_H 2 | #define NET_H 3 | 4 | #include "config.h" 5 | #include 6 | #include 7 | #include "wrk.h" 8 | 9 | typedef enum { 10 | OK, 11 | ERROR, 12 | RETRY 13 | } status; 14 | 15 | struct sock { 16 | status ( *connect)(connection *, char *); 17 | status ( *close)(connection *); 18 | status ( *read)(connection *, size_t *); 19 | status ( *write)(connection *, char *, size_t, size_t *); 20 | size_t (*readable)(connection *); 21 | }; 22 | 23 | status sock_connect(connection *, char *); 24 | status sock_close(connection *); 25 | status sock_read(connection *, size_t *); 26 | status sock_write(connection *, char *, size_t, size_t *); 27 | size_t sock_readable(connection *); 28 | 29 | #endif /* NET_H */ 30 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | wrk 4.0.2 2 | 3 | * Send hostname using TLS SNI. 4 | * Add optional WITH_OPENSSL and WITH_LUAJIT to use system libs. 5 | * Bundle OpenSSL 1.0.2. 6 | * delay() can return milliseconds to delay sending next request. 7 | 8 | wrk 4.0.0 9 | 10 | * The wrk global variable is the only global defined by default. 11 | * wrk.init() calls the global init(), remove calls to wrk.init(). 12 | * Add wrk.lookup(host, port) and wrk.connect(addr) functions. 13 | * Add setup phase that calls the global setup() for each thread. 14 | * Allow assignment to thread.addr to specify the server address. 15 | * Add thread:set(name, value), thread:get(name), and thread:stop(). 16 | * Record latency for every request instead of random samples. 17 | * Latency and requests in done() are now callable, not indexable. 18 | * Only record timeouts when a response is actually received. 19 | * Remove calibration phase and record rate at fixed interval. 20 | * Improve correction of coordinated omission. 21 | -------------------------------------------------------------------------------- /src/net.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 - Will Glozer. All rights reserved. 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "net.h" 8 | 9 | status sock_connect(connection *c, char *host) { 10 | return OK; 11 | } 12 | 13 | status sock_close(connection *c) { 14 | return OK; 15 | } 16 | 17 | status sock_read(connection *c, size_t *n) { 18 | ssize_t r = read(c->fd, c->buf, sizeof(c->buf)); 19 | *n = (size_t) r; 20 | return r >= 0 ? OK : ERROR; 21 | } 22 | 23 | status sock_write(connection *c, char *buf, size_t len, size_t *n) { 24 | ssize_t r; 25 | if ((r = write(c->fd, buf, len)) == -1) { 26 | switch (errno) { 27 | case EAGAIN: return RETRY; 28 | default: return ERROR; 29 | } 30 | } 31 | *n = (size_t) r; 32 | return OK; 33 | } 34 | 35 | size_t sock_readable(connection *c) { 36 | int n, rc; 37 | rc = ioctl(c->fd, FIONREAD, &n); 38 | return rc == -1 ? 0 : n; 39 | } 40 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Overview 2 | 3 | wrk should build on most UNIX-like operating systems and 4 | architectures that have GNU make and are supported by LuaJIT and 5 | OpenSSL. Some systems may require additional CFLAGS or LDFLAGS, 6 | see the top of the Makefile for examples 7 | 8 | In many cases simply running `make` (often `gmake` on *BSD) will 9 | do the trick. 10 | 11 | Dependencies 12 | 13 | wrk requires LuaJIT and OpenSSL and is distributed with appropriate 14 | versions that will be unpacked and built as necessary. 15 | 16 | If you are building wrk packages for an OS distribution or otherwise 17 | prefer to use system versions of dependencies you may specify their 18 | location when invoking make with one or more of: 19 | 20 | WITH_LUAJIT 21 | WITH_OPENSSL 22 | 23 | For example to use the system version of both libraries on Linux: 24 | 25 | make WITH_LUAJIT=/usr WITH_OPENSSL=/usr 26 | 27 | Or to use the Homebrew version of OpenSSL on Mac OS X: 28 | 29 | make WITH_OPENSSL=/usr/local/opt/openssl 30 | -------------------------------------------------------------------------------- /scripts/setup.lua: -------------------------------------------------------------------------------- 1 | -- example script that demonstrates use of setup() to pass 2 | -- data to and from the threads 3 | 4 | local counter = 1 5 | local threads = {} 6 | 7 | function setup(thread) 8 | thread:set("id", counter) 9 | table.insert(threads, thread) 10 | counter = counter + 1 11 | end 12 | 13 | function init(args) 14 | requests = 0 15 | responses = 0 16 | 17 | local msg = "thread %d created" 18 | print(msg:format(id)) 19 | end 20 | 21 | function request() 22 | requests = requests + 1 23 | return wrk.request() 24 | end 25 | 26 | function response(status, headers, body) 27 | responses = responses + 1 28 | end 29 | 30 | function done(summary, latency, requests) 31 | for index, thread in ipairs(threads) do 32 | local id = thread:get("id") 33 | local requests = thread:get("requests") 34 | local responses = thread:get("responses") 35 | local msg = "thread %d made %d requests and got %d responses" 36 | print(msg:format(id, requests, responses)) 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /src/stats.h: -------------------------------------------------------------------------------- 1 | #ifndef STATS_H 2 | #define STATS_H 3 | 4 | #include 5 | #include 6 | 7 | #define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) 8 | #define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) 9 | 10 | typedef struct { 11 | uint32_t connect; 12 | uint32_t read; 13 | uint32_t write; 14 | uint32_t status; 15 | uint32_t timeout; 16 | } errors; 17 | 18 | typedef struct { 19 | uint64_t count; 20 | uint64_t limit; 21 | uint64_t min; 22 | uint64_t max; 23 | uint64_t data[]; 24 | } stats; 25 | 26 | stats *stats_alloc(uint64_t); 27 | void stats_free(stats *); 28 | 29 | int stats_record(stats *, uint64_t); 30 | void stats_correct(stats *, int64_t); 31 | 32 | long double stats_mean(stats *); 33 | long double stats_stdev(stats *stats, long double); 34 | long double stats_within_stdev(stats *, long double, long double, uint64_t); 35 | uint64_t stats_percentile(stats *, long double); 36 | 37 | uint64_t stats_popcount(stats *); 38 | uint64_t stats_value_at(stats *stats, uint64_t, uint64_t *); 39 | 40 | #endif /* STATS_H */ 41 | -------------------------------------------------------------------------------- /src/script.h: -------------------------------------------------------------------------------- 1 | #ifndef SCRIPT_H 2 | #define SCRIPT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "stats.h" 10 | #include "wrk.h" 11 | 12 | lua_State *script_create(char *, char *, char **); 13 | 14 | bool script_resolve(lua_State *, char *, char *); 15 | void script_setup(lua_State *, thread *); 16 | void script_done(lua_State *, stats *, stats *); 17 | 18 | void script_init(lua_State *, thread *, int, char **); 19 | uint64_t script_delay(lua_State *); 20 | void script_request(lua_State *, char **, size_t *); 21 | void script_response(lua_State *, int, buffer *, buffer *); 22 | size_t script_verify_request(lua_State *L); 23 | 24 | bool script_is_static(lua_State *); 25 | bool script_want_response(lua_State *L); 26 | bool script_has_delay(lua_State *L); 27 | bool script_has_done(lua_State *L); 28 | void script_summary(lua_State *, uint64_t, uint64_t, uint64_t); 29 | void script_errors(lua_State *, errors *); 30 | 31 | void script_copy_value(lua_State *, lua_State *, int); 32 | int script_parse_url(char *, struct http_parser_url *); 33 | 34 | void buffer_append(buffer *, const char *, size_t); 35 | void buffer_reset(buffer *); 36 | char *buffer_pushlstring(lua_State *, char *); 37 | 38 | #endif /* SCRIPT_H */ 39 | -------------------------------------------------------------------------------- /src/wrk.h: -------------------------------------------------------------------------------- 1 | #ifndef WRK_H 2 | #define WRK_H 3 | 4 | #include "config.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "stats.h" 16 | #include "ae.h" 17 | #include "http_parser.h" 18 | 19 | #define RECVBUF 8192 20 | 21 | #define MAX_THREAD_RATE_S 10000000 22 | #define SOCKET_TIMEOUT_MS 2000 23 | #define RECORD_INTERVAL_MS 100 24 | 25 | extern const char *VERSION; 26 | 27 | typedef struct { 28 | pthread_t thread; 29 | aeEventLoop *loop; 30 | struct addrinfo *addr; 31 | uint64_t connections; 32 | uint64_t complete; 33 | uint64_t requests; 34 | uint64_t bytes; 35 | uint64_t start; 36 | lua_State *L; 37 | errors errors; 38 | struct connection *cs; 39 | } thread; 40 | 41 | typedef struct { 42 | char *buffer; 43 | size_t length; 44 | char *cursor; 45 | } buffer; 46 | 47 | typedef struct connection { 48 | thread *thread; 49 | http_parser parser; 50 | enum { 51 | FIELD, VALUE 52 | } state; 53 | int fd; 54 | SSL *ssl; 55 | bool delayed; 56 | uint64_t start; 57 | char *request; 58 | size_t length; 59 | size_t written; 60 | uint64_t pending; 61 | buffer headers; 62 | buffer body; 63 | char buf[RECVBUF]; 64 | } connection; 65 | 66 | #endif /* WRK_H */ 67 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #ifndef MAIN_H 2 | #define MAIN_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "ssl.h" 23 | #include "aprintf.h" 24 | #include "stats.h" 25 | #include "units.h" 26 | #include "zmalloc.h" 27 | 28 | struct config; 29 | 30 | static void *thread_main(void *); 31 | static int connect_socket(thread *, connection *); 32 | static int reconnect_socket(thread *, connection *); 33 | 34 | static int record_rate(aeEventLoop *, long long, void *); 35 | 36 | static void socket_connected(aeEventLoop *, int, void *, int); 37 | static void socket_writeable(aeEventLoop *, int, void *, int); 38 | static void socket_readable(aeEventLoop *, int, void *, int); 39 | 40 | static int response_complete(http_parser *); 41 | static int header_field(http_parser *, const char *, size_t); 42 | static int header_value(http_parser *, const char *, size_t); 43 | static int response_body(http_parser *, const char *, size_t); 44 | 45 | static uint64_t time_us(); 46 | 47 | static int parse_args(struct config *, char **, struct http_parser_url *, char **, int, char **); 48 | static char *copy_url_part(char *, struct http_parser_url *, enum http_parser_url_fields); 49 | 50 | static void print_stats_header(); 51 | static void print_stats(char *, stats *, char *(*)(long double)); 52 | static void print_stats_latency(stats *); 53 | 54 | #endif /* MAIN_H */ 55 | -------------------------------------------------------------------------------- /src/wrk.lua: -------------------------------------------------------------------------------- 1 | local wrk = { 2 | scheme = "http", 3 | host = "localhost", 4 | port = nil, 5 | method = "GET", 6 | path = "/", 7 | headers = {}, 8 | body = nil, 9 | thread = nil, 10 | } 11 | 12 | function wrk.resolve(host, service) 13 | local addrs = wrk.lookup(host, service) 14 | for i = #addrs, 1, -1 do 15 | if not wrk.connect(addrs[i]) then 16 | table.remove(addrs, i) 17 | end 18 | end 19 | wrk.addrs = addrs 20 | end 21 | 22 | function wrk.setup(thread) 23 | thread.addr = wrk.addrs[1] 24 | if type(setup) == "function" then 25 | setup(thread) 26 | end 27 | end 28 | 29 | function wrk.init(args) 30 | if not wrk.headers["Host"] then 31 | local host = wrk.host 32 | local port = wrk.port 33 | 34 | host = host:find(":") and ("[" .. host .. "]") or host 35 | host = port and (host .. ":" .. port) or host 36 | 37 | wrk.headers["Host"] = host 38 | end 39 | 40 | if type(init) == "function" then 41 | init(args) 42 | end 43 | 44 | local req = wrk.format() 45 | wrk.request = function() 46 | return req 47 | end 48 | end 49 | 50 | function wrk.format(method, path, headers, body) 51 | local method = method or wrk.method 52 | local path = path or wrk.path 53 | local headers = headers or wrk.headers 54 | local body = body or wrk.body 55 | local s = {} 56 | 57 | if not headers["Host"] then 58 | headers["Host"] = wrk.headers["Host"] 59 | end 60 | 61 | headers["Content-Length"] = body and string.len(body) 62 | 63 | s[1] = string.format("%s %s HTTP/1.1", method, path) 64 | for name, value in pairs(headers) do 65 | s[#s+1] = string.format("%s: %s", name, value) 66 | end 67 | 68 | s[#s+1] = "" 69 | s[#s+1] = body or "" 70 | 71 | return table.concat(s, "\r\n") 72 | end 73 | 74 | return wrk 75 | -------------------------------------------------------------------------------- /src/units.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 - Will Glozer. All rights reserved. 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "units.h" 9 | #include "aprintf.h" 10 | 11 | typedef struct { 12 | int scale; 13 | char *base; 14 | char *units[]; 15 | } units; 16 | 17 | units time_units_us = { 18 | .scale = 1000, 19 | .base = "us", 20 | .units = { "ms", "s", NULL } 21 | }; 22 | 23 | units time_units_s = { 24 | .scale = 60, 25 | .base = "s", 26 | .units = { "m", "h", NULL } 27 | }; 28 | 29 | units binary_units = { 30 | .scale = 1024, 31 | .base = "", 32 | .units = { "K", "M", "G", "T", "P", NULL } 33 | }; 34 | 35 | units metric_units = { 36 | .scale = 1000, 37 | .base = "", 38 | .units = { "k", "M", "G", "T", "P", NULL } 39 | }; 40 | 41 | static char *format_units(long double n, units *m, int p) { 42 | long double amt = n, scale; 43 | char *unit = m->base; 44 | char *msg = NULL; 45 | 46 | scale = m->scale * 0.85; 47 | 48 | for (int i = 0; m->units[i+1] && amt >= scale; i++) { 49 | amt /= m->scale; 50 | unit = m->units[i]; 51 | } 52 | 53 | aprintf(&msg, "%.*Lf%s", p, amt, unit); 54 | 55 | return msg; 56 | } 57 | 58 | static int scan_units(char *s, uint64_t *n, units *m) { 59 | uint64_t base, scale = 1; 60 | char unit[3] = { 0, 0, 0 }; 61 | int i, c; 62 | 63 | if ((c = sscanf(s, "%"SCNu64"%2s", &base, unit)) < 1) return -1; 64 | 65 | if (c == 2 && strncasecmp(unit, m->base, 3)) { 66 | for (i = 0; m->units[i] != NULL; i++) { 67 | scale *= m->scale; 68 | if (!strncasecmp(unit, m->units[i], 3)) break; 69 | } 70 | if (m->units[i] == NULL) return -1; 71 | } 72 | 73 | *n = base * scale; 74 | return 0; 75 | } 76 | 77 | char *format_binary(long double n) { 78 | return format_units(n, &binary_units, 2); 79 | } 80 | 81 | char *format_metric(long double n) { 82 | return format_units(n, &metric_units, 2); 83 | } 84 | 85 | char *format_time_us(long double n) { 86 | units *units = &time_units_us; 87 | if (n >= 1000000.0) { 88 | n /= 1000000.0; 89 | units = &time_units_s; 90 | } 91 | return format_units(n, units, 2); 92 | } 93 | 94 | char *format_time_s(long double n) { 95 | return format_units(n, &time_units_s, 0); 96 | } 97 | 98 | int scan_metric(char *s, uint64_t *n) { 99 | return scan_units(s, n, &metric_units); 100 | } 101 | 102 | int scan_time(char *s, uint64_t *n) { 103 | return scan_units(s, n, &time_units_s); 104 | } 105 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | wrk - a HTTP benchmarking tool 2 | 3 | wrk is a modern HTTP benchmarking tool capable of generating significant 4 | load when run on a single multi-core CPU. It combines a multithreaded 5 | design with scalable event notification systems such as epoll and kqueue. 6 | 7 | An optional LuaJIT script can perform HTTP request generation, response 8 | processing, and custom reporting. Details are available in SCRIPTING and 9 | several examples are located in scripts/ 10 | 11 | Basic Usage 12 | 13 | wrk -t12 -c400 -d30s http://127.0.0.1:8080/index.html 14 | 15 | This runs a benchmark for 30 seconds, using 12 threads, and keeping 16 | 400 HTTP connections open. 17 | 18 | Output: 19 | 20 | Running 30s test @ http://127.0.0.1:8080/index.html 21 | 12 threads and 400 connections 22 | Thread Stats Avg Stdev Max +/- Stdev 23 | Latency 635.91us 0.89ms 12.92ms 93.69% 24 | Req/Sec 56.20k 8.07k 62.00k 86.54% 25 | 22464657 requests in 30.00s, 17.76GB read 26 | Requests/sec: 748868.53 27 | Transfer/sec: 606.33MB 28 | 29 | Benchmarking Tips 30 | 31 | The machine running wrk must have a sufficient number of ephemeral ports 32 | available and closed sockets should be recycled quickly. To handle the 33 | initial connection burst the server's listen(2) backlog should be greater 34 | than the number of concurrent connections being tested. 35 | 36 | A user script that only changes the HTTP method, path, adds headers or 37 | a body, will have no performance impact. Per-request actions, particularly 38 | building a new HTTP request, and use of response() will necessarily reduce 39 | the amount of load that can be generated. 40 | 41 | Acknowledgements 42 | 43 | wrk contains code from a number of open source projects including the 44 | 'ae' event loop from redis, the nginx/joyent/node.js 'http-parser', 45 | and Mike Pall's LuaJIT. Please consult the NOTICE file for licensing 46 | details. 47 | 48 | Cryptography Notice 49 | 50 | This distribution includes cryptographic software. The country in 51 | which you currently reside may have restrictions on the import, 52 | possession, use, and/or re-export to another country, of encryption 53 | software. BEFORE using any encryption software, please check your 54 | country's laws, regulations and policies concerning the import, 55 | possession, or use, and re-export of encryption software, to see if 56 | this is permitted. See for more 57 | information. 58 | 59 | The U.S. Government Department of Commerce, Bureau of Industry and 60 | Security (BIS), has classified this software as Export Commodity 61 | Control Number (ECCN) 5D002.C.1, which includes information security 62 | software using or performing cryptographic functions with symmetric 63 | algorithms. The form and manner of this distribution makes it 64 | eligible for export under the License Exception ENC Technology 65 | Software Unrestricted (TSU) exception (see the BIS Export 66 | Administration Regulations, Section 740.13) for both object code and 67 | source code. 68 | -------------------------------------------------------------------------------- /src/ssl.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 - Will Glozer. All rights reserved. 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "ssl.h" 10 | 11 | static pthread_mutex_t *locks; 12 | 13 | static void ssl_lock(int mode, int n, const char *file, int line) { 14 | pthread_mutex_t *lock = &locks[n]; 15 | if (mode & CRYPTO_LOCK) { 16 | pthread_mutex_lock(lock); 17 | } else { 18 | pthread_mutex_unlock(lock); 19 | } 20 | } 21 | 22 | static unsigned long ssl_id() { 23 | return (unsigned long) pthread_self(); 24 | } 25 | 26 | SSL_CTX *ssl_init() { 27 | SSL_CTX *ctx = NULL; 28 | 29 | SSL_load_error_strings(); 30 | SSL_library_init(); 31 | OpenSSL_add_all_algorithms(); 32 | 33 | if ((locks = calloc(CRYPTO_num_locks(), sizeof(pthread_mutex_t)))) { 34 | for (int i = 0; i < CRYPTO_num_locks(); i++) { 35 | pthread_mutex_init(&locks[i], NULL); 36 | } 37 | 38 | CRYPTO_set_locking_callback(ssl_lock); 39 | CRYPTO_set_id_callback(ssl_id); 40 | 41 | if ((ctx = SSL_CTX_new(SSLv23_client_method()))) { 42 | SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); 43 | SSL_CTX_set_verify_depth(ctx, 0); 44 | SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); 45 | SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_CLIENT); 46 | } 47 | } 48 | 49 | return ctx; 50 | } 51 | 52 | status ssl_connect(connection *c, char *host) { 53 | int r; 54 | SSL_set_fd(c->ssl, c->fd); 55 | SSL_set_tlsext_host_name(c->ssl, host); 56 | if ((r = SSL_connect(c->ssl)) != 1) { 57 | switch (SSL_get_error(c->ssl, r)) { 58 | case SSL_ERROR_WANT_READ: return RETRY; 59 | case SSL_ERROR_WANT_WRITE: return RETRY; 60 | default: return ERROR; 61 | } 62 | } 63 | return OK; 64 | } 65 | 66 | status ssl_close(connection *c) { 67 | SSL_shutdown(c->ssl); 68 | SSL_clear(c->ssl); 69 | return OK; 70 | } 71 | 72 | status ssl_read(connection *c, size_t *n) { 73 | int r; 74 | if ((r = SSL_read(c->ssl, c->buf, sizeof(c->buf))) <= 0) { 75 | switch (SSL_get_error(c->ssl, r)) { 76 | case SSL_ERROR_WANT_READ: return RETRY; 77 | case SSL_ERROR_WANT_WRITE: return RETRY; 78 | default: return ERROR; 79 | } 80 | } 81 | *n = (size_t) r; 82 | return OK; 83 | } 84 | 85 | status ssl_write(connection *c, char *buf, size_t len, size_t *n) { 86 | int r; 87 | if ((r = SSL_write(c->ssl, buf, len)) <= 0) { 88 | switch (SSL_get_error(c->ssl, r)) { 89 | case SSL_ERROR_WANT_READ: return RETRY; 90 | case SSL_ERROR_WANT_WRITE: return RETRY; 91 | default: return ERROR; 92 | } 93 | } 94 | *n = (size_t) r; 95 | return OK; 96 | } 97 | 98 | size_t ssl_readable(connection *c) { 99 | return SSL_pending(c->ssl); 100 | } 101 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS += -std=c99 -Wall -O2 -D_REENTRANT 2 | LIBS := -lpthread -lm -lssl -lcrypto 3 | 4 | TARGET := $(shell uname -s | tr '[A-Z]' '[a-z]' 2>/dev/null || echo unknown) 5 | 6 | ifeq ($(TARGET), sunos) 7 | CFLAGS += -D_PTHREADS -D_POSIX_C_SOURCE=200112L 8 | LIBS += -lsocket 9 | else ifeq ($(TARGET), darwin) 10 | LDFLAGS += -pagezero_size 10000 -image_base 100000000 11 | else ifeq ($(TARGET), linux) 12 | CFLAGS += -D_POSIX_C_SOURCE=200112L -D_BSD_SOURCE 13 | LIBS += -ldl 14 | LDFLAGS += -Wl,-E 15 | else ifeq ($(TARGET), freebsd) 16 | CFLAGS += -D_DECLARE_C99_LDBL_MATH 17 | LDFLAGS += -Wl,-E 18 | endif 19 | 20 | SRC := wrk.c net.c ssl.c aprintf.c stats.c script.c units.c \ 21 | ae.c zmalloc.c http_parser.c 22 | BIN := wrk 23 | VER ?= $(shell git describe --tags --always --dirty) 24 | 25 | ODIR := obj 26 | OBJ := $(patsubst %.c,$(ODIR)/%.o,$(SRC)) $(ODIR)/bytecode.o $(ODIR)/version.o 27 | LIBS := -lluajit-5.1 $(LIBS) 28 | 29 | DEPS := 30 | CFLAGS += -I$(ODIR)/include 31 | LDFLAGS += -L$(ODIR)/lib 32 | 33 | ifneq ($(WITH_LUAJIT),) 34 | CFLAGS += -I$(WITH_LUAJIT)/include 35 | LDFLAGS += -L$(WITH_LUAJIT)/lib 36 | else 37 | DEPS += $(ODIR)/lib/libluajit-5.1.a 38 | endif 39 | 40 | ifneq ($(WITH_OPENSSL),) 41 | CFLAGS += -I$(WITH_OPENSSL)/include 42 | LDFLAGS += -L$(WITH_OPENSSL)/lib 43 | else 44 | DEPS += $(ODIR)/lib/libssl.a 45 | endif 46 | 47 | all: $(BIN) 48 | 49 | clean: 50 | $(RM) -rf $(BIN) obj/* 51 | 52 | $(BIN): $(OBJ) 53 | @echo LINK $(BIN) 54 | @$(CC) $(LDFLAGS) -o $@ $^ $(LIBS) 55 | 56 | $(OBJ): config.h Makefile $(DEPS) | $(ODIR) 57 | 58 | $(ODIR): 59 | @mkdir -p $@ 60 | 61 | $(ODIR)/bytecode.o: src/wrk.lua 62 | @echo LUAJIT $< 63 | @$(SHELL) -c 'PATH=obj/bin:$(PATH) luajit -b $(CURDIR)/$< $(CURDIR)/$@' 64 | 65 | $(ODIR)/version.o: 66 | @echo 'const char *VERSION="$(VER)";' | $(CC) -xc -c -o $@ - 67 | 68 | $(ODIR)/%.o : %.c 69 | @echo CC $< 70 | @$(CC) $(CFLAGS) -c -o $@ $< 71 | 72 | # Dependencies 73 | 74 | LUAJIT := $(notdir $(patsubst %.tar.gz,%,$(wildcard deps/LuaJIT*.tar.gz))) 75 | OPENSSL := $(notdir $(patsubst %.tar.gz,%,$(wildcard deps/openssl*.tar.gz))) 76 | 77 | OPENSSL_OPTS = no-shared no-ssl2 no-psk no-srp no-dtls no-idea --prefix=$(abspath $(ODIR)) 78 | 79 | $(ODIR)/$(LUAJIT): deps/$(LUAJIT).tar.gz | $(ODIR) 80 | @tar -C $(ODIR) -xf $< 81 | 82 | $(ODIR)/$(OPENSSL): deps/$(OPENSSL).tar.gz | $(ODIR) 83 | @tar -C $(ODIR) -xf $< 84 | 85 | $(ODIR)/lib/libluajit-5.1.a: $(ODIR)/$(LUAJIT) 86 | @echo Building LuaJIT... 87 | @$(MAKE) -C $< PREFIX=$(abspath $(ODIR)) BUILDMODE=static install 88 | 89 | $(ODIR)/lib/libssl.a: $(ODIR)/$(OPENSSL) 90 | @echo Building OpenSSL... 91 | ifeq ($(TARGET), darwin) 92 | @$(SHELL) -c "cd $< && ./Configure $(OPENSSL_OPTS) darwin64-x86_64-cc" 93 | else 94 | @$(SHELL) -c "cd $< && ./config $(OPENSSL_OPTS)" 95 | endif 96 | @$(MAKE) -C $< depend install 97 | 98 | # ------------ 99 | 100 | .PHONY: all clean 101 | .PHONY: $(ODIR)/version.o 102 | 103 | .SUFFIXES: 104 | .SUFFIXES: .c .o .lua 105 | 106 | vpath %.c src 107 | vpath %.h src 108 | vpath %.lua scripts 109 | -------------------------------------------------------------------------------- /src/stats.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 - Will Glozer. All rights reserved. 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "stats.h" 8 | #include "zmalloc.h" 9 | 10 | stats *stats_alloc(uint64_t max) { 11 | uint64_t limit = max + 1; 12 | stats *s = zcalloc(sizeof(stats) + sizeof(uint64_t) * limit); 13 | s->limit = limit; 14 | s->min = UINT64_MAX; 15 | return s; 16 | } 17 | 18 | void stats_free(stats *stats) { 19 | zfree(stats); 20 | } 21 | 22 | int stats_record(stats *stats, uint64_t n) { 23 | if (n >= stats->limit) return 0; 24 | __sync_fetch_and_add(&stats->data[n], 1); 25 | __sync_fetch_and_add(&stats->count, 1); 26 | uint64_t min = stats->min; 27 | uint64_t max = stats->max; 28 | while (n < min) min = __sync_val_compare_and_swap(&stats->min, min, n); 29 | while (n > max) max = __sync_val_compare_and_swap(&stats->max, max, n); 30 | return 1; 31 | } 32 | 33 | void stats_correct(stats *stats, int64_t expected) { 34 | for (uint64_t n = expected * 2; n <= stats->max; n++) { 35 | uint64_t count = stats->data[n]; 36 | int64_t m = (int64_t) n - expected; 37 | while (count && m > expected) { 38 | stats->data[m] += count; 39 | stats->count += count; 40 | m -= expected; 41 | } 42 | } 43 | } 44 | 45 | long double stats_mean(stats *stats) { 46 | if (stats->count == 0) return 0.0; 47 | 48 | uint64_t sum = 0; 49 | for (uint64_t i = stats->min; i <= stats->max; i++) { 50 | sum += stats->data[i] * i; 51 | } 52 | return sum / (long double) stats->count; 53 | } 54 | 55 | long double stats_stdev(stats *stats, long double mean) { 56 | long double sum = 0.0; 57 | if (stats->count < 2) return 0.0; 58 | for (uint64_t i = stats->min; i <= stats->max; i++) { 59 | if (stats->data[i]) { 60 | sum += powl(i - mean, 2) * stats->data[i]; 61 | } 62 | } 63 | return sqrtl(sum / (stats->count - 1)); 64 | } 65 | 66 | long double stats_within_stdev(stats *stats, long double mean, long double stdev, uint64_t n) { 67 | long double upper = mean + (stdev * n); 68 | long double lower = mean - (stdev * n); 69 | uint64_t sum = 0; 70 | 71 | for (uint64_t i = stats->min; i <= stats->max; i++) { 72 | if (i >= lower && i <= upper) { 73 | sum += stats->data[i]; 74 | } 75 | } 76 | 77 | return (sum / (long double) stats->count) * 100; 78 | } 79 | 80 | uint64_t stats_percentile(stats *stats, long double p) { 81 | uint64_t rank = round((p / 100.0) * stats->count + 0.5); 82 | uint64_t total = 0; 83 | for (uint64_t i = stats->min; i <= stats->max; i++) { 84 | total += stats->data[i]; 85 | if (total >= rank) return i; 86 | } 87 | return 0; 88 | } 89 | 90 | uint64_t stats_popcount(stats *stats) { 91 | uint64_t count = 0; 92 | for (uint64_t i = stats->min; i <= stats->max; i++) { 93 | if (stats->data[i]) count++; 94 | } 95 | return count; 96 | } 97 | 98 | uint64_t stats_value_at(stats *stats, uint64_t index, uint64_t *count) { 99 | *count = 0; 100 | for (uint64_t i = stats->min; i <= stats->max; i++) { 101 | if (stats->data[i] && (*count)++ == index) { 102 | *count = stats->data[i]; 103 | return i; 104 | } 105 | } 106 | return 0; 107 | } 108 | -------------------------------------------------------------------------------- /src/zmalloc.h: -------------------------------------------------------------------------------- 1 | /* zmalloc - total amount of allocated memory aware version of malloc() 2 | * 3 | * Copyright (c) 2009-2010, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef __ZMALLOC_H 32 | #define __ZMALLOC_H 33 | 34 | /* Double expansion needed for stringification of macro values. */ 35 | #define __xstr(s) __str(s) 36 | #define __str(s) #s 37 | 38 | #if defined(USE_TCMALLOC) 39 | #define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR)) 40 | #include 41 | #if TC_VERSION_MAJOR >= 1 && TC_VERSION_MINOR >= 6 42 | #define HAVE_MALLOC_SIZE 1 43 | #define zmalloc_size(p) tc_malloc_size(p) 44 | #else 45 | #error "Newer version of tcmalloc required" 46 | #endif 47 | 48 | #elif defined(USE_JEMALLOC) 49 | #define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX)) 50 | #define JEMALLOC_MANGLE 51 | #include 52 | #if JEMALLOC_VERSION_MAJOR >= 2 && JEMALLOC_VERSION_MINOR >= 1 53 | #define HAVE_MALLOC_SIZE 1 54 | #define zmalloc_size(p) JEMALLOC_P(malloc_usable_size)(p) 55 | #else 56 | #error "Newer version of jemalloc required" 57 | #endif 58 | 59 | #elif defined(__APPLE__) 60 | #include 61 | #define HAVE_MALLOC_SIZE 1 62 | #define zmalloc_size(p) malloc_size(p) 63 | #endif 64 | 65 | #ifndef ZMALLOC_LIB 66 | #define ZMALLOC_LIB "libc" 67 | #endif 68 | 69 | void *zmalloc(size_t size); 70 | void *zcalloc(size_t size); 71 | void *zrealloc(void *ptr, size_t size); 72 | void zfree(void *ptr); 73 | char *zstrdup(const char *s); 74 | size_t zmalloc_used_memory(void); 75 | void zmalloc_enable_thread_safeness(void); 76 | float zmalloc_get_fragmentation_ratio(void); 77 | size_t zmalloc_get_rss(void); 78 | 79 | #ifndef HAVE_MALLOC_SIZE 80 | size_t zmalloc_size(void *ptr); 81 | #endif 82 | 83 | #endif /* __ZMALLOC_H */ 84 | -------------------------------------------------------------------------------- /src/ae_select.c: -------------------------------------------------------------------------------- 1 | /* Select()-based ae.c module. 2 | * 3 | * Copyright (c) 2009-2012, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | 32 | #include 33 | 34 | typedef struct aeApiState { 35 | fd_set rfds, wfds; 36 | /* We need to have a copy of the fd sets as it's not safe to reuse 37 | * FD sets after select(). */ 38 | fd_set _rfds, _wfds; 39 | } aeApiState; 40 | 41 | static int aeApiCreate(aeEventLoop *eventLoop) { 42 | aeApiState *state = zmalloc(sizeof(aeApiState)); 43 | 44 | if (!state) return -1; 45 | FD_ZERO(&state->rfds); 46 | FD_ZERO(&state->wfds); 47 | eventLoop->apidata = state; 48 | return 0; 49 | } 50 | 51 | static void aeApiFree(aeEventLoop *eventLoop) { 52 | zfree(eventLoop->apidata); 53 | } 54 | 55 | static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { 56 | aeApiState *state = eventLoop->apidata; 57 | 58 | if (mask & AE_READABLE) FD_SET(fd,&state->rfds); 59 | if (mask & AE_WRITABLE) FD_SET(fd,&state->wfds); 60 | return 0; 61 | } 62 | 63 | static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { 64 | aeApiState *state = eventLoop->apidata; 65 | 66 | if (mask & AE_READABLE) FD_CLR(fd,&state->rfds); 67 | if (mask & AE_WRITABLE) FD_CLR(fd,&state->wfds); 68 | } 69 | 70 | static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { 71 | aeApiState *state = eventLoop->apidata; 72 | int retval, j, numevents = 0; 73 | 74 | memcpy(&state->_rfds,&state->rfds,sizeof(fd_set)); 75 | memcpy(&state->_wfds,&state->wfds,sizeof(fd_set)); 76 | 77 | retval = select(eventLoop->maxfd+1, 78 | &state->_rfds,&state->_wfds,NULL,tvp); 79 | if (retval > 0) { 80 | for (j = 0; j <= eventLoop->maxfd; j++) { 81 | int mask = 0; 82 | aeFileEvent *fe = &eventLoop->events[j]; 83 | 84 | if (fe->mask == AE_NONE) continue; 85 | if (fe->mask & AE_READABLE && FD_ISSET(j,&state->_rfds)) 86 | mask |= AE_READABLE; 87 | if (fe->mask & AE_WRITABLE && FD_ISSET(j,&state->_wfds)) 88 | mask |= AE_WRITABLE; 89 | eventLoop->fired[numevents].fd = j; 90 | eventLoop->fired[numevents].mask = mask; 91 | numevents++; 92 | } 93 | } 94 | return numevents; 95 | } 96 | 97 | static char *aeApiName(void) { 98 | return "select"; 99 | } 100 | -------------------------------------------------------------------------------- /SCRIPTING: -------------------------------------------------------------------------------- 1 | Overview 2 | 3 | wrk supports executing a LuaJIT script during three distinct phases: setup, 4 | running, and done. Each wrk thread has an independent scripting environment 5 | and the setup & done phases execute in a separate environment which does 6 | not participate in the running phase. 7 | 8 | The public Lua API consists of a global table and a number of global 9 | functions: 10 | 11 | wrk = { 12 | scheme = "http", 13 | host = "localhost", 14 | port = nil, 15 | method = "GET", 16 | path = "/", 17 | headers = {}, 18 | body = nil, 19 | thread = , 20 | } 21 | 22 | function wrk.format(method, path, headers, body) 23 | 24 | wrk.format returns a HTTP request string containing the passed parameters 25 | merged with values from the wrk table. 26 | 27 | function wrk.lookup(host, service) 28 | 29 | wrk.lookup returns a table containing all known addresses for the host 30 | and service pair. This corresponds to the POSIX getaddrinfo() function. 31 | 32 | function wrk.connect(addr) 33 | 34 | wrk.connect returns true if the address can be connected to, otherwise 35 | it returns false. The address must be one returned from wrk.lookup(). 36 | 37 | The following globals are optional, and if defined must be functions: 38 | 39 | global setup -- called during thread setup 40 | global init -- called when the thread is starting 41 | global delay -- called to get the request delay 42 | global request -- called to generate the HTTP request 43 | global response -- called with HTTP response data 44 | global done -- called with results of run 45 | 46 | Setup 47 | 48 | function setup(thread) 49 | 50 | The setup phase begins after the target IP address has been resolved and all 51 | threads have been initialized but not yet started. 52 | 53 | setup() is called once for each thread and receives a userdata object 54 | representing the thread. 55 | 56 | thread.addr - get or set the thread's server address 57 | thread:get(name) - get the value of a global in the thread's env 58 | thread:set(name, value) - set the value of a global in the thread's env 59 | thread:stop() - stop the thread 60 | 61 | Only boolean, nil, number, and string values or tables of the same may be 62 | transfered via get()/set() and thread:stop() can only be called while the 63 | thread is running. 64 | 65 | Running 66 | 67 | function init(args) 68 | function delay() 69 | function request() 70 | function response(status, headers, body) 71 | 72 | The running phase begins with a single call to init(), followed by 73 | a call to request() and response() for each request cycle. 74 | 75 | The init() function receives any extra command line arguments for the 76 | script which must be separated from wrk arguments with "--". 77 | 78 | delay() returns the number of milliseconds to delay sending the next 79 | request. 80 | 81 | request() returns a string containing the HTTP request. Building a new 82 | request each time is expensive, when testing a high performance server 83 | one solution is to pre-generate all requests in init() and do a quick 84 | lookup in request(). 85 | 86 | response() is called with the HTTP response status, headers, and body. 87 | Parsing the headers and body is expensive, so if the response global is 88 | nil after the call to init() wrk will ignore the headers and body. 89 | 90 | Done 91 | 92 | function done(summary, latency, requests) 93 | 94 | The done() function receives a table containing result data, and two 95 | statistics objects representing the per-request latency and per-thread 96 | request rate. Duration and latency are microsecond values and rate is 97 | measured in requests per second. 98 | 99 | latency.min -- minimum value seen 100 | latency.max -- maximum value seen 101 | latency.mean -- average value seen 102 | latency.stdev -- standard deviation 103 | latency:percentile(99.0) -- 99th percentile value 104 | latency(i) -- raw value and count 105 | 106 | summary = { 107 | duration = N, -- run duration in microseconds 108 | requests = N, -- total completed requests 109 | bytes = N, -- total bytes received 110 | errors = { 111 | connect = N, -- total socket connection errors 112 | read = N, -- total socket read errors 113 | write = N, -- total socket write errors 114 | status = N, -- total HTTP status codes > 399 115 | timeout = N -- total request timeouts 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/ae_kqueue.c: -------------------------------------------------------------------------------- 1 | /* Kqueue(2)-based ae.c module 2 | * 3 | * Copyright (C) 2009 Harish Mallipeddi - harish.mallipeddi@gmail.com 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | typedef struct aeApiState { 37 | int kqfd; 38 | struct kevent *events; 39 | } aeApiState; 40 | 41 | static int aeApiCreate(aeEventLoop *eventLoop) { 42 | aeApiState *state = zmalloc(sizeof(aeApiState)); 43 | 44 | if (!state) return -1; 45 | state->events = zmalloc(sizeof(struct kevent)*eventLoop->setsize); 46 | if (!state->events) { 47 | zfree(state); 48 | return -1; 49 | } 50 | state->kqfd = kqueue(); 51 | if (state->kqfd == -1) { 52 | zfree(state->events); 53 | zfree(state); 54 | return -1; 55 | } 56 | eventLoop->apidata = state; 57 | 58 | return 0; 59 | } 60 | 61 | static void aeApiFree(aeEventLoop *eventLoop) { 62 | aeApiState *state = eventLoop->apidata; 63 | 64 | close(state->kqfd); 65 | zfree(state->events); 66 | zfree(state); 67 | } 68 | 69 | static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { 70 | aeApiState *state = eventLoop->apidata; 71 | struct kevent ke; 72 | 73 | if (mask & AE_READABLE) { 74 | EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, NULL); 75 | if (kevent(state->kqfd, &ke, 1, NULL, 0, NULL) == -1) return -1; 76 | } 77 | if (mask & AE_WRITABLE) { 78 | EV_SET(&ke, fd, EVFILT_WRITE, EV_ADD, 0, 0, NULL); 79 | if (kevent(state->kqfd, &ke, 1, NULL, 0, NULL) == -1) return -1; 80 | } 81 | return 0; 82 | } 83 | 84 | static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { 85 | aeApiState *state = eventLoop->apidata; 86 | struct kevent ke; 87 | 88 | if (mask & AE_READABLE) { 89 | EV_SET(&ke, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); 90 | kevent(state->kqfd, &ke, 1, NULL, 0, NULL); 91 | } 92 | if (mask & AE_WRITABLE) { 93 | EV_SET(&ke, fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); 94 | kevent(state->kqfd, &ke, 1, NULL, 0, NULL); 95 | } 96 | } 97 | 98 | static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { 99 | aeApiState *state = eventLoop->apidata; 100 | int retval, numevents = 0; 101 | 102 | if (tvp != NULL) { 103 | struct timespec timeout; 104 | timeout.tv_sec = tvp->tv_sec; 105 | timeout.tv_nsec = tvp->tv_usec * 1000; 106 | retval = kevent(state->kqfd, NULL, 0, state->events, eventLoop->setsize, 107 | &timeout); 108 | } else { 109 | retval = kevent(state->kqfd, NULL, 0, state->events, eventLoop->setsize, 110 | NULL); 111 | } 112 | 113 | if (retval > 0) { 114 | int j; 115 | 116 | numevents = retval; 117 | for(j = 0; j < numevents; j++) { 118 | int mask = 0; 119 | struct kevent *e = state->events+j; 120 | 121 | if (e->filter == EVFILT_READ) mask |= AE_READABLE; 122 | if (e->filter == EVFILT_WRITE) mask |= AE_WRITABLE; 123 | eventLoop->fired[j].fd = e->ident; 124 | eventLoop->fired[j].mask = mask; 125 | } 126 | } 127 | return numevents; 128 | } 129 | 130 | static char *aeApiName(void) { 131 | return "kqueue"; 132 | } 133 | -------------------------------------------------------------------------------- /src/ae.h: -------------------------------------------------------------------------------- 1 | /* A simple event-driven programming library. Originally I wrote this code 2 | * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated 3 | * it in form of a library for easy reuse. 4 | * 5 | * Copyright (c) 2006-2012, Salvatore Sanfilippo 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of Redis nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without 18 | * specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #ifndef __AE_H__ 34 | #define __AE_H__ 35 | 36 | #define AE_OK 0 37 | #define AE_ERR -1 38 | 39 | #define AE_NONE 0 40 | #define AE_READABLE 1 41 | #define AE_WRITABLE 2 42 | 43 | #define AE_FILE_EVENTS 1 44 | #define AE_TIME_EVENTS 2 45 | #define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS) 46 | #define AE_DONT_WAIT 4 47 | 48 | #define AE_NOMORE -1 49 | 50 | /* Macros */ 51 | #define AE_NOTUSED(V) ((void) V) 52 | 53 | struct aeEventLoop; 54 | 55 | /* Types and data structures */ 56 | typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask); 57 | typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData); 58 | typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData); 59 | typedef void aeBeforeSleepProc(struct aeEventLoop *eventLoop); 60 | 61 | /* File event structure */ 62 | typedef struct aeFileEvent { 63 | int mask; /* one of AE_(READABLE|WRITABLE) */ 64 | aeFileProc *rfileProc; 65 | aeFileProc *wfileProc; 66 | void *clientData; 67 | } aeFileEvent; 68 | 69 | /* Time event structure */ 70 | typedef struct aeTimeEvent { 71 | long long id; /* time event identifier. */ 72 | long when_sec; /* seconds */ 73 | long when_ms; /* milliseconds */ 74 | aeTimeProc *timeProc; 75 | aeEventFinalizerProc *finalizerProc; 76 | void *clientData; 77 | struct aeTimeEvent *next; 78 | } aeTimeEvent; 79 | 80 | /* A fired event */ 81 | typedef struct aeFiredEvent { 82 | int fd; 83 | int mask; 84 | } aeFiredEvent; 85 | 86 | /* State of an event based program */ 87 | typedef struct aeEventLoop { 88 | int maxfd; /* highest file descriptor currently registered */ 89 | int setsize; /* max number of file descriptors tracked */ 90 | long long timeEventNextId; 91 | time_t lastTime; /* Used to detect system clock skew */ 92 | aeFileEvent *events; /* Registered events */ 93 | aeFiredEvent *fired; /* Fired events */ 94 | aeTimeEvent *timeEventHead; 95 | int stop; 96 | void *apidata; /* This is used for polling API specific data */ 97 | aeBeforeSleepProc *beforesleep; 98 | } aeEventLoop; 99 | 100 | /* Prototypes */ 101 | aeEventLoop *aeCreateEventLoop(int setsize); 102 | void aeDeleteEventLoop(aeEventLoop *eventLoop); 103 | void aeStop(aeEventLoop *eventLoop); 104 | int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, 105 | aeFileProc *proc, void *clientData); 106 | void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask); 107 | int aeGetFileEvents(aeEventLoop *eventLoop, int fd); 108 | long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, 109 | aeTimeProc *proc, void *clientData, 110 | aeEventFinalizerProc *finalizerProc); 111 | int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id); 112 | int aeProcessEvents(aeEventLoop *eventLoop, int flags); 113 | int aeWait(int fd, int mask, long long milliseconds); 114 | void aeMain(aeEventLoop *eventLoop); 115 | char *aeGetApiName(void); 116 | void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep); 117 | 118 | #endif 119 | -------------------------------------------------------------------------------- /src/ae_epoll.c: -------------------------------------------------------------------------------- 1 | /* Linux epoll(2) based ae.c module 2 | * 3 | * Copyright (c) 2009-2012, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | 32 | #include 33 | 34 | typedef struct aeApiState { 35 | int epfd; 36 | struct epoll_event *events; 37 | } aeApiState; 38 | 39 | static int aeApiCreate(aeEventLoop *eventLoop) { 40 | aeApiState *state = zmalloc(sizeof(aeApiState)); 41 | 42 | if (!state) return -1; 43 | state->events = zmalloc(sizeof(struct epoll_event)*eventLoop->setsize); 44 | if (!state->events) { 45 | zfree(state); 46 | return -1; 47 | } 48 | state->epfd = epoll_create(1024); /* 1024 is just an hint for the kernel */ 49 | if (state->epfd == -1) { 50 | zfree(state->events); 51 | zfree(state); 52 | return -1; 53 | } 54 | eventLoop->apidata = state; 55 | return 0; 56 | } 57 | 58 | static void aeApiFree(aeEventLoop *eventLoop) { 59 | aeApiState *state = eventLoop->apidata; 60 | 61 | close(state->epfd); 62 | zfree(state->events); 63 | zfree(state); 64 | } 65 | 66 | static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { 67 | aeApiState *state = eventLoop->apidata; 68 | struct epoll_event ee; 69 | /* If the fd was already monitored for some event, we need a MOD 70 | * operation. Otherwise we need an ADD operation. */ 71 | int op = eventLoop->events[fd].mask == AE_NONE ? 72 | EPOLL_CTL_ADD : EPOLL_CTL_MOD; 73 | 74 | ee.events = 0; 75 | mask |= eventLoop->events[fd].mask; /* Merge old events */ 76 | if (mask & AE_READABLE) ee.events |= EPOLLIN; 77 | if (mask & AE_WRITABLE) ee.events |= EPOLLOUT; 78 | ee.data.u64 = 0; /* avoid valgrind warning */ 79 | ee.data.fd = fd; 80 | if (epoll_ctl(state->epfd,op,fd,&ee) == -1) return -1; 81 | return 0; 82 | } 83 | 84 | static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int delmask) { 85 | aeApiState *state = eventLoop->apidata; 86 | struct epoll_event ee; 87 | int mask = eventLoop->events[fd].mask & (~delmask); 88 | 89 | ee.events = 0; 90 | if (mask & AE_READABLE) ee.events |= EPOLLIN; 91 | if (mask & AE_WRITABLE) ee.events |= EPOLLOUT; 92 | ee.data.u64 = 0; /* avoid valgrind warning */ 93 | ee.data.fd = fd; 94 | if (mask != AE_NONE) { 95 | epoll_ctl(state->epfd,EPOLL_CTL_MOD,fd,&ee); 96 | } else { 97 | /* Note, Kernel < 2.6.9 requires a non null event pointer even for 98 | * EPOLL_CTL_DEL. */ 99 | epoll_ctl(state->epfd,EPOLL_CTL_DEL,fd,&ee); 100 | } 101 | } 102 | 103 | static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { 104 | aeApiState *state = eventLoop->apidata; 105 | int retval, numevents = 0; 106 | 107 | retval = epoll_wait(state->epfd,state->events,eventLoop->setsize, 108 | tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1); 109 | if (retval > 0) { 110 | int j; 111 | 112 | numevents = retval; 113 | for (j = 0; j < numevents; j++) { 114 | int mask = 0; 115 | struct epoll_event *e = state->events+j; 116 | 117 | if (e->events & EPOLLIN) mask |= AE_READABLE; 118 | if (e->events & EPOLLOUT) mask |= AE_WRITABLE; 119 | if (e->events & EPOLLERR) mask |= AE_WRITABLE; 120 | if (e->events & EPOLLHUP) mask |= AE_WRITABLE; 121 | eventLoop->fired[j].fd = e->data.fd; 122 | eventLoop->fired[j].mask = mask; 123 | } 124 | } 125 | return numevents; 126 | } 127 | 128 | static char *aeApiName(void) { 129 | return "epoll"; 130 | } 131 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | ========================================================================= 2 | == NOTICE file corresponding to section 4(d) of the Apache License, == 3 | == Version 2.0, in this case for the wrk distribution. == 4 | ========================================================================= 5 | 6 | wrk 7 | Copyright 2012 Will Glozer, http://glozer.net 8 | 9 | ========================================================================= 10 | == Redis Event Library Notice == 11 | ========================================================================= 12 | 13 | This product includes software developed by Salvatore Sanfilippo and 14 | other contributors to the redis project. 15 | 16 | Copyright (c) 2006-2010, Salvatore Sanfilippo 17 | Copyright (c) 2009-2012, Salvatore Sanfilippo 18 | Copyright (C) 2009 Harish Mallipeddi - harish.mallipeddi@gmail.com 19 | Copyright (c) 2012, Joyent, Inc. All rights reserved. 20 | 21 | Copyright (c) 2006-2009, Salvatore Sanfilippo 22 | All rights reserved. 23 | 24 | Redistribution and use in source and binary forms, with or without 25 | modification, are permitted provided that the following conditions are 26 | met: 27 | 28 | * Redistributions of source code must retain the above copyright 29 | notice, this list of conditions and the following disclaimer. 30 | 31 | * Redistributions in binary form must reproduce the above copyright 32 | notice, this list of conditions and the following disclaimer in the 33 | documentation and/or other materials provided with the * 34 | distribution. 35 | 36 | * Neither the name of Redis nor the names of its contributors may be 37 | used to endorse or promote products derived from this software 38 | without specific prior written permission. 39 | 40 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 41 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 42 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 43 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 44 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 45 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 46 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 47 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 48 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 49 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 50 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 51 | 52 | ========================================================================= 53 | == HTTP Parser Notice == 54 | ========================================================================= 55 | 56 | This product includes software developed by Igor Sysoev, Joyent, Inc., 57 | and other Node contributors. 58 | 59 | http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright 60 | Igor Sysoev. 61 | 62 | Additional changes are licensed under the same terms as NGINX and 63 | copyright Joyent, Inc. and other Node contributors. All rights reserved. 64 | 65 | Permission is hereby granted, free of charge, to any person obtaining a 66 | copy of this software and associated documentation files (the 67 | "Software"), to deal in the Software without restriction, including 68 | without limitation the rights to use, copy, modify, merge, publish, 69 | distribute, sublicense, and/or sell copies of the Software, and to permit 70 | persons to whom the Software is furnished to do so, subject to the 71 | following conditions: 72 | 73 | The above copyright notice and this permission notice shall be included 74 | in all copies or substantial portions of the Software. 75 | 76 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 77 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 78 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 79 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 80 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 81 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 82 | USE OR OTHER DEALINGS IN THE SOFTWARE. 83 | 84 | ========================================================================= 85 | == LuaJIT Notice == 86 | ========================================================================= 87 | 88 | LuaJIT -- a Just-In-Time Compiler for Lua. http://luajit.org/ 89 | 90 | Copyright (C) 2005-2013 Mike Pall. All rights reserved. 91 | 92 | Permission is hereby granted, free of charge, to any person obtaining a copy 93 | of this software and associated documentation files (the "Software"), to deal 94 | in the Software without restriction, including without limitation the rights 95 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 96 | copies of the Software, and to permit persons to whom the Software is 97 | furnished to do so, subject to the following conditions: 98 | 99 | The above copyright notice and this permission notice shall be included in 100 | all copies or substantial portions of the Software. 101 | 102 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 103 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 104 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 105 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 106 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 107 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 108 | THE SOFTWARE. 109 | -------------------------------------------------------------------------------- /src/zmalloc.c: -------------------------------------------------------------------------------- 1 | /* zmalloc - total amount of allocated memory aware version of malloc() 2 | * 3 | * Copyright (c) 2009-2010, Salvatore Sanfilippo 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * * Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * * Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * * Neither the name of Redis nor the names of its contributors may be used 15 | * to endorse or promote products derived from this software without 16 | * specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 22 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | * POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "config.h" 36 | #include "zmalloc.h" 37 | 38 | #ifdef HAVE_MALLOC_SIZE 39 | #define PREFIX_SIZE (0) 40 | #else 41 | #if defined(__sun) || defined(__sparc) || defined(__sparc__) 42 | #define PREFIX_SIZE (sizeof(long long)) 43 | #else 44 | #define PREFIX_SIZE (sizeof(size_t)) 45 | #endif 46 | #endif 47 | 48 | /* Explicitly override malloc/free etc when using tcmalloc. */ 49 | #if defined(USE_TCMALLOC) 50 | #define malloc(size) tc_malloc(size) 51 | #define calloc(count,size) tc_calloc(count,size) 52 | #define realloc(ptr,size) tc_realloc(ptr,size) 53 | #define free(ptr) tc_free(ptr) 54 | #elif defined(USE_JEMALLOC) 55 | #define malloc(size) je_malloc(size) 56 | #define calloc(count,size) je_calloc(count,size) 57 | #define realloc(ptr,size) je_realloc(ptr,size) 58 | #define free(ptr) je_free(ptr) 59 | #endif 60 | 61 | #define update_zmalloc_stat_alloc(__n,__size) do { \ 62 | size_t _n = (__n); \ 63 | if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ 64 | if (zmalloc_thread_safe) { \ 65 | pthread_mutex_lock(&used_memory_mutex); \ 66 | used_memory += _n; \ 67 | pthread_mutex_unlock(&used_memory_mutex); \ 68 | } else { \ 69 | used_memory += _n; \ 70 | } \ 71 | } while(0) 72 | 73 | #define update_zmalloc_stat_free(__n) do { \ 74 | size_t _n = (__n); \ 75 | if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ 76 | if (zmalloc_thread_safe) { \ 77 | pthread_mutex_lock(&used_memory_mutex); \ 78 | used_memory -= _n; \ 79 | pthread_mutex_unlock(&used_memory_mutex); \ 80 | } else { \ 81 | used_memory -= _n; \ 82 | } \ 83 | } while(0) 84 | 85 | static size_t used_memory = 0; 86 | static int zmalloc_thread_safe = 0; 87 | pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER; 88 | 89 | static void zmalloc_oom(size_t size) { 90 | fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n", 91 | size); 92 | fflush(stderr); 93 | abort(); 94 | } 95 | 96 | void *zmalloc(size_t size) { 97 | void *ptr = malloc(size+PREFIX_SIZE); 98 | 99 | if (!ptr) zmalloc_oom(size); 100 | #ifdef HAVE_MALLOC_SIZE 101 | update_zmalloc_stat_alloc(zmalloc_size(ptr),size); 102 | return ptr; 103 | #else 104 | *((size_t*)ptr) = size; 105 | update_zmalloc_stat_alloc(size+PREFIX_SIZE,size); 106 | return (char*)ptr+PREFIX_SIZE; 107 | #endif 108 | } 109 | 110 | void *zcalloc(size_t size) { 111 | void *ptr = calloc(1, size+PREFIX_SIZE); 112 | 113 | if (!ptr) zmalloc_oom(size); 114 | #ifdef HAVE_MALLOC_SIZE 115 | update_zmalloc_stat_alloc(zmalloc_size(ptr),size); 116 | return ptr; 117 | #else 118 | *((size_t*)ptr) = size; 119 | update_zmalloc_stat_alloc(size+PREFIX_SIZE,size); 120 | return (char*)ptr+PREFIX_SIZE; 121 | #endif 122 | } 123 | 124 | void *zrealloc(void *ptr, size_t size) { 125 | #ifndef HAVE_MALLOC_SIZE 126 | void *realptr; 127 | #endif 128 | size_t oldsize; 129 | void *newptr; 130 | 131 | if (ptr == NULL) return zmalloc(size); 132 | #ifdef HAVE_MALLOC_SIZE 133 | oldsize = zmalloc_size(ptr); 134 | newptr = realloc(ptr,size); 135 | if (!newptr) zmalloc_oom(size); 136 | 137 | update_zmalloc_stat_free(oldsize); 138 | update_zmalloc_stat_alloc(zmalloc_size(newptr),size); 139 | return newptr; 140 | #else 141 | realptr = (char*)ptr-PREFIX_SIZE; 142 | oldsize = *((size_t*)realptr); 143 | newptr = realloc(realptr,size+PREFIX_SIZE); 144 | if (!newptr) zmalloc_oom(size); 145 | 146 | *((size_t*)newptr) = size; 147 | update_zmalloc_stat_free(oldsize); 148 | update_zmalloc_stat_alloc(size,size); 149 | return (char*)newptr+PREFIX_SIZE; 150 | #endif 151 | } 152 | 153 | /* Provide zmalloc_size() for systems where this function is not provided by 154 | * malloc itself, given that in that case we store an header with this 155 | * information as the first bytes of every allocation. */ 156 | #ifndef HAVE_MALLOC_SIZE 157 | size_t zmalloc_size(void *ptr) { 158 | void *realptr = (char*)ptr-PREFIX_SIZE; 159 | size_t size = *((size_t*)realptr); 160 | /* Assume at least that all the allocations are padded at sizeof(long) by 161 | * the underlying allocator. */ 162 | if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1)); 163 | return size+PREFIX_SIZE; 164 | } 165 | #endif 166 | 167 | void zfree(void *ptr) { 168 | #ifndef HAVE_MALLOC_SIZE 169 | void *realptr; 170 | size_t oldsize; 171 | #endif 172 | 173 | if (ptr == NULL) return; 174 | #ifdef HAVE_MALLOC_SIZE 175 | update_zmalloc_stat_free(zmalloc_size(ptr)); 176 | free(ptr); 177 | #else 178 | realptr = (char*)ptr-PREFIX_SIZE; 179 | oldsize = *((size_t*)realptr); 180 | update_zmalloc_stat_free(oldsize+PREFIX_SIZE); 181 | free(realptr); 182 | #endif 183 | } 184 | 185 | char *zstrdup(const char *s) { 186 | size_t l = strlen(s)+1; 187 | char *p = zmalloc(l); 188 | 189 | memcpy(p,s,l); 190 | return p; 191 | } 192 | 193 | size_t zmalloc_used_memory(void) { 194 | size_t um; 195 | 196 | if (zmalloc_thread_safe) pthread_mutex_lock(&used_memory_mutex); 197 | um = used_memory; 198 | if (zmalloc_thread_safe) pthread_mutex_unlock(&used_memory_mutex); 199 | return um; 200 | } 201 | 202 | void zmalloc_enable_thread_safeness(void) { 203 | zmalloc_thread_safe = 1; 204 | } 205 | 206 | /* Get the RSS information in an OS-specific way. 207 | * 208 | * WARNING: the function zmalloc_get_rss() is not designed to be fast 209 | * and may not be called in the busy loops where Redis tries to release 210 | * memory expiring or swapping out objects. 211 | * 212 | * For this kind of "fast RSS reporting" usages use instead the 213 | * function RedisEstimateRSS() that is a much faster (and less precise) 214 | * version of the funciton. */ 215 | 216 | #if defined(HAVE_PROCFS) 217 | #include 218 | #include 219 | #include 220 | #include 221 | 222 | size_t zmalloc_get_rss(void) { 223 | int page = sysconf(_SC_PAGESIZE); 224 | size_t rss; 225 | char buf[4096]; 226 | char filename[256]; 227 | int fd, count; 228 | char *p, *x; 229 | 230 | snprintf(filename,256,"/proc/%d/stat",getpid()); 231 | if ((fd = open(filename,O_RDONLY)) == -1) return 0; 232 | if (read(fd,buf,4096) <= 0) { 233 | close(fd); 234 | return 0; 235 | } 236 | close(fd); 237 | 238 | p = buf; 239 | count = 23; /* RSS is the 24th field in /proc//stat */ 240 | while(p && count--) { 241 | p = strchr(p,' '); 242 | if (p) p++; 243 | } 244 | if (!p) return 0; 245 | x = strchr(p,' '); 246 | if (!x) return 0; 247 | *x = '\0'; 248 | 249 | rss = strtoll(p,NULL,10); 250 | rss *= page; 251 | return rss; 252 | } 253 | #elif defined(HAVE_TASKINFO) 254 | #include 255 | #include 256 | #include 257 | #include 258 | #include 259 | #include 260 | #include 261 | 262 | size_t zmalloc_get_rss(void) { 263 | task_t task = MACH_PORT_NULL; 264 | struct task_basic_info t_info; 265 | mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; 266 | 267 | if (task_for_pid(current_task(), getpid(), &task) != KERN_SUCCESS) 268 | return 0; 269 | task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count); 270 | 271 | return t_info.resident_size; 272 | } 273 | #else 274 | size_t zmalloc_get_rss(void) { 275 | /* If we can't get the RSS in an OS-specific way for this system just 276 | * return the memory usage we estimated in zmalloc().. 277 | * 278 | * Fragmentation will appear to be always 1 (no fragmentation) 279 | * of course... */ 280 | return zmalloc_used_memory(); 281 | } 282 | #endif 283 | 284 | /* Fragmentation = RSS / allocated-bytes */ 285 | float zmalloc_get_fragmentation_ratio(void) { 286 | return (float)zmalloc_get_rss()/zmalloc_used_memory(); 287 | } 288 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Modified Apache 2.0 License 3 | Version 2.0.1, February 2015 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | (e) If the Derivative Work includes substantial changes to features 124 | or functionality of the Work, then you must remove the name of 125 | the Work, and any derivation thereof, from all copies that you 126 | distribute, whether in Source or Object form, except as required 127 | in copyright, patent, trademark, and attribution notices. 128 | 129 | You may add Your own copyright statement to Your modifications and 130 | may provide additional or different license terms and conditions 131 | for use, reproduction, or distribution of Your modifications, or 132 | for any such Derivative Works as a whole, provided Your use, 133 | reproduction, and distribution of the Work otherwise complies with 134 | the conditions stated in this License. 135 | 136 | 5. Submission of Contributions. Unless You explicitly state otherwise, 137 | any Contribution intentionally submitted for inclusion in the Work 138 | by You to the Licensor shall be under the terms and conditions of 139 | this License, without any additional terms or conditions. 140 | Notwithstanding the above, nothing herein shall supersede or modify 141 | the terms of any separate license agreement you may have executed 142 | with Licensor regarding such Contributions. 143 | 144 | 6. Trademarks. This License does not grant permission to use the trade 145 | names, trademarks, service marks, or product names of the Licensor, 146 | except as required for reasonable and customary use in describing the 147 | origin of the Work and reproducing the content of the NOTICE file. 148 | 149 | 7. Disclaimer of Warranty. Unless required by applicable law or 150 | agreed to in writing, Licensor provides the Work (and each 151 | Contributor provides its Contributions) on an "AS IS" BASIS, 152 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 153 | implied, including, without limitation, any warranties or conditions 154 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 155 | PARTICULAR PURPOSE. You are solely responsible for determining the 156 | appropriateness of using or redistributing the Work and assume any 157 | risks associated with Your exercise of permissions under this License. 158 | 159 | 8. Limitation of Liability. In no event and under no legal theory, 160 | whether in tort (including negligence), contract, or otherwise, 161 | unless required by applicable law (such as deliberate and grossly 162 | negligent acts) or agreed to in writing, shall any Contributor be 163 | liable to You for damages, including any direct, indirect, special, 164 | incidental, or consequential damages of any character arising as a 165 | result of this License or out of the use or inability to use the 166 | Work (including but not limited to damages for loss of goodwill, 167 | work stoppage, computer failure or malfunction, or any and all 168 | other commercial damages or losses), even if such Contributor 169 | has been advised of the possibility of such damages. 170 | 171 | 9. Accepting Warranty or Additional Liability. While redistributing 172 | the Work or Derivative Works thereof, You may choose to offer, 173 | and charge a fee for, acceptance of support, warranty, indemnity, 174 | or other liability obligations and/or rights consistent with this 175 | License. However, in accepting such obligations, You may act only 176 | on Your own behalf and on Your sole responsibility, not on behalf 177 | of any other Contributor, and only if You agree to indemnify, 178 | defend, and hold each Contributor harmless for any liability 179 | incurred by, or claims asserted against, such Contributor by reason 180 | of your accepting any such warranty or additional liability. 181 | 182 | END OF TERMS AND CONDITIONS 183 | -------------------------------------------------------------------------------- /src/ae_evport.c: -------------------------------------------------------------------------------- 1 | /* ae.c module for illumos event ports. 2 | * 3 | * Copyright (c) 2012, Joyent, Inc. All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * * Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * * Neither the name of Redis nor the names of its contributors may be used 14 | * to endorse or promote products derived from this software without 15 | * specific prior written permission. 16 | * 17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 18 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 21 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | * POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | 39 | #include 40 | 41 | static int evport_debug = 0; 42 | 43 | /* 44 | * This file implements the ae API using event ports, present on Solaris-based 45 | * systems since Solaris 10. Using the event port interface, we associate file 46 | * descriptors with the port. Each association also includes the set of poll(2) 47 | * events that the consumer is interested in (e.g., POLLIN and POLLOUT). 48 | * 49 | * There's one tricky piece to this implementation: when we return events via 50 | * aeApiPoll, the corresponding file descriptors become dissociated from the 51 | * port. This is necessary because poll events are level-triggered, so if the 52 | * fd didn't become dissociated, it would immediately fire another event since 53 | * the underlying state hasn't changed yet. We must reassociate the file 54 | * descriptor, but only after we know that our caller has actually read from it. 55 | * The ae API does not tell us exactly when that happens, but we do know that 56 | * it must happen by the time aeApiPoll is called again. Our solution is to 57 | * keep track of the last fds returned by aeApiPoll and reassociate them next 58 | * time aeApiPoll is invoked. 59 | * 60 | * To summarize, in this module, each fd association is EITHER (a) represented 61 | * only via the in-kernel assocation OR (b) represented by pending_fds and 62 | * pending_masks. (b) is only true for the last fds we returned from aeApiPoll, 63 | * and only until we enter aeApiPoll again (at which point we restore the 64 | * in-kernel association). 65 | */ 66 | #define MAX_EVENT_BATCHSZ 512 67 | 68 | typedef struct aeApiState { 69 | int portfd; /* event port */ 70 | int npending; /* # of pending fds */ 71 | int pending_fds[MAX_EVENT_BATCHSZ]; /* pending fds */ 72 | int pending_masks[MAX_EVENT_BATCHSZ]; /* pending fds' masks */ 73 | } aeApiState; 74 | 75 | static int aeApiCreate(aeEventLoop *eventLoop) { 76 | int i; 77 | aeApiState *state = zmalloc(sizeof(aeApiState)); 78 | if (!state) return -1; 79 | 80 | state->portfd = port_create(); 81 | if (state->portfd == -1) { 82 | zfree(state); 83 | return -1; 84 | } 85 | 86 | state->npending = 0; 87 | 88 | for (i = 0; i < MAX_EVENT_BATCHSZ; i++) { 89 | state->pending_fds[i] = -1; 90 | state->pending_masks[i] = AE_NONE; 91 | } 92 | 93 | eventLoop->apidata = state; 94 | return 0; 95 | } 96 | 97 | static void aeApiFree(aeEventLoop *eventLoop) { 98 | aeApiState *state = eventLoop->apidata; 99 | 100 | close(state->portfd); 101 | zfree(state); 102 | } 103 | 104 | static int aeApiLookupPending(aeApiState *state, int fd) { 105 | int i; 106 | 107 | for (i = 0; i < state->npending; i++) { 108 | if (state->pending_fds[i] == fd) 109 | return (i); 110 | } 111 | 112 | return (-1); 113 | } 114 | 115 | /* 116 | * Helper function to invoke port_associate for the given fd and mask. 117 | */ 118 | static int aeApiAssociate(const char *where, int portfd, int fd, int mask) { 119 | int events = 0; 120 | int rv, err; 121 | 122 | if (mask & AE_READABLE) 123 | events |= POLLIN; 124 | if (mask & AE_WRITABLE) 125 | events |= POLLOUT; 126 | 127 | if (evport_debug) 128 | fprintf(stderr, "%s: port_associate(%d, 0x%x) = ", where, fd, events); 129 | 130 | rv = port_associate(portfd, PORT_SOURCE_FD, fd, events, 131 | (void *)(uintptr_t)mask); 132 | err = errno; 133 | 134 | if (evport_debug) 135 | fprintf(stderr, "%d (%s)\n", rv, rv == 0 ? "no error" : strerror(err)); 136 | 137 | if (rv == -1) { 138 | fprintf(stderr, "%s: port_associate: %s\n", where, strerror(err)); 139 | 140 | if (err == EAGAIN) 141 | fprintf(stderr, "aeApiAssociate: event port limit exceeded."); 142 | } 143 | 144 | return rv; 145 | } 146 | 147 | static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) { 148 | aeApiState *state = eventLoop->apidata; 149 | int fullmask, pfd; 150 | 151 | if (evport_debug) 152 | fprintf(stderr, "aeApiAddEvent: fd %d mask 0x%x\n", fd, mask); 153 | 154 | /* 155 | * Since port_associate's "events" argument replaces any existing events, we 156 | * must be sure to include whatever events are already associated when 157 | * we call port_associate() again. 158 | */ 159 | fullmask = mask | eventLoop->events[fd].mask; 160 | pfd = aeApiLookupPending(state, fd); 161 | 162 | if (pfd != -1) { 163 | /* 164 | * This fd was recently returned from aeApiPoll. It should be safe to 165 | * assume that the consumer has processed that poll event, but we play 166 | * it safer by simply updating pending_mask. The fd will be 167 | * reassociated as usual when aeApiPoll is called again. 168 | */ 169 | if (evport_debug) 170 | fprintf(stderr, "aeApiAddEvent: adding to pending fd %d\n", fd); 171 | state->pending_masks[pfd] |= fullmask; 172 | return 0; 173 | } 174 | 175 | return (aeApiAssociate("aeApiAddEvent", state->portfd, fd, fullmask)); 176 | } 177 | 178 | static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) { 179 | aeApiState *state = eventLoop->apidata; 180 | int fullmask, pfd; 181 | 182 | if (evport_debug) 183 | fprintf(stderr, "del fd %d mask 0x%x\n", fd, mask); 184 | 185 | pfd = aeApiLookupPending(state, fd); 186 | 187 | if (pfd != -1) { 188 | if (evport_debug) 189 | fprintf(stderr, "deleting event from pending fd %d\n", fd); 190 | 191 | /* 192 | * This fd was just returned from aeApiPoll, so it's not currently 193 | * associated with the port. All we need to do is update 194 | * pending_mask appropriately. 195 | */ 196 | state->pending_masks[pfd] &= ~mask; 197 | 198 | if (state->pending_masks[pfd] == AE_NONE) 199 | state->pending_fds[pfd] = -1; 200 | 201 | return; 202 | } 203 | 204 | /* 205 | * The fd is currently associated with the port. Like with the add case 206 | * above, we must look at the full mask for the file descriptor before 207 | * updating that association. We don't have a good way of knowing what the 208 | * events are without looking into the eventLoop state directly. We rely on 209 | * the fact that our caller has already updated the mask in the eventLoop. 210 | */ 211 | 212 | fullmask = eventLoop->events[fd].mask; 213 | if (fullmask == AE_NONE) { 214 | /* 215 | * We're removing *all* events, so use port_dissociate to remove the 216 | * association completely. Failure here indicates a bug. 217 | */ 218 | if (evport_debug) 219 | fprintf(stderr, "aeApiDelEvent: port_dissociate(%d)\n", fd); 220 | 221 | if (port_dissociate(state->portfd, PORT_SOURCE_FD, fd) != 0) { 222 | perror("aeApiDelEvent: port_dissociate"); 223 | abort(); /* will not return */ 224 | } 225 | } else if (aeApiAssociate("aeApiDelEvent", state->portfd, fd, 226 | fullmask) != 0) { 227 | /* 228 | * ENOMEM is a potentially transient condition, but the kernel won't 229 | * generally return it unless things are really bad. EAGAIN indicates 230 | * we've reached an resource limit, for which it doesn't make sense to 231 | * retry (counterintuitively). All other errors indicate a bug. In any 232 | * of these cases, the best we can do is to abort. 233 | */ 234 | abort(); /* will not return */ 235 | } 236 | } 237 | 238 | static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { 239 | aeApiState *state = eventLoop->apidata; 240 | struct timespec timeout, *tsp; 241 | int mask, i; 242 | uint_t nevents; 243 | port_event_t event[MAX_EVENT_BATCHSZ]; 244 | 245 | /* 246 | * If we've returned fd events before, we must reassociate them with the 247 | * port now, before calling port_get(). See the block comment at the top of 248 | * this file for an explanation of why. 249 | */ 250 | for (i = 0; i < state->npending; i++) { 251 | if (state->pending_fds[i] == -1) 252 | /* This fd has since been deleted. */ 253 | continue; 254 | 255 | if (aeApiAssociate("aeApiPoll", state->portfd, 256 | state->pending_fds[i], state->pending_masks[i]) != 0) { 257 | /* See aeApiDelEvent for why this case is fatal. */ 258 | abort(); 259 | } 260 | 261 | state->pending_masks[i] = AE_NONE; 262 | state->pending_fds[i] = -1; 263 | } 264 | 265 | state->npending = 0; 266 | 267 | if (tvp != NULL) { 268 | timeout.tv_sec = tvp->tv_sec; 269 | timeout.tv_nsec = tvp->tv_usec * 1000; 270 | tsp = &timeout; 271 | } else { 272 | tsp = NULL; 273 | } 274 | 275 | /* 276 | * port_getn can return with errno == ETIME having returned some events (!). 277 | * So if we get ETIME, we check nevents, too. 278 | */ 279 | nevents = 1; 280 | if (port_getn(state->portfd, event, MAX_EVENT_BATCHSZ, &nevents, 281 | tsp) == -1 && (errno != ETIME || nevents == 0)) { 282 | if (errno == ETIME || errno == EINTR) 283 | return 0; 284 | 285 | /* Any other error indicates a bug. */ 286 | perror("aeApiPoll: port_get"); 287 | abort(); 288 | } 289 | 290 | state->npending = nevents; 291 | 292 | for (i = 0; i < nevents; i++) { 293 | mask = 0; 294 | if (event[i].portev_events & POLLIN) 295 | mask |= AE_READABLE; 296 | if (event[i].portev_events & POLLOUT) 297 | mask |= AE_WRITABLE; 298 | 299 | eventLoop->fired[i].fd = event[i].portev_object; 300 | eventLoop->fired[i].mask = mask; 301 | 302 | if (evport_debug) 303 | fprintf(stderr, "aeApiPoll: fd %d mask 0x%x\n", 304 | (int)event[i].portev_object, mask); 305 | 306 | state->pending_fds[i] = event[i].portev_object; 307 | state->pending_masks[i] = (uintptr_t)event[i].portev_user; 308 | } 309 | 310 | return nevents; 311 | } 312 | 313 | static char *aeApiName(void) { 314 | return "evport"; 315 | } 316 | -------------------------------------------------------------------------------- /src/http_parser.h: -------------------------------------------------------------------------------- 1 | /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifndef http_parser_h 22 | #define http_parser_h 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | /* Also update SONAME in the Makefile whenever you change these. */ 28 | #define HTTP_PARSER_VERSION_MAJOR 2 29 | #define HTTP_PARSER_VERSION_MINOR 4 30 | #define HTTP_PARSER_VERSION_PATCH 2 31 | 32 | #include 33 | #if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) 34 | #include 35 | #include 36 | typedef __int8 int8_t; 37 | typedef unsigned __int8 uint8_t; 38 | typedef __int16 int16_t; 39 | typedef unsigned __int16 uint16_t; 40 | typedef __int32 int32_t; 41 | typedef unsigned __int32 uint32_t; 42 | typedef __int64 int64_t; 43 | typedef unsigned __int64 uint64_t; 44 | #else 45 | #include 46 | #endif 47 | 48 | /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run 49 | * faster 50 | */ 51 | #ifndef HTTP_PARSER_STRICT 52 | # define HTTP_PARSER_STRICT 1 53 | #endif 54 | 55 | /* Maximium header size allowed. If the macro is not defined 56 | * before including this header then the default is used. To 57 | * change the maximum header size, define the macro in the build 58 | * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove 59 | * the effective limit on the size of the header, define the macro 60 | * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) 61 | */ 62 | #ifndef HTTP_MAX_HEADER_SIZE 63 | # define HTTP_MAX_HEADER_SIZE (80*1024) 64 | #endif 65 | 66 | typedef struct http_parser http_parser; 67 | typedef struct http_parser_settings http_parser_settings; 68 | 69 | 70 | /* Callbacks should return non-zero to indicate an error. The parser will 71 | * then halt execution. 72 | * 73 | * The one exception is on_headers_complete. In a HTTP_RESPONSE parser 74 | * returning '1' from on_headers_complete will tell the parser that it 75 | * should not expect a body. This is used when receiving a response to a 76 | * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: 77 | * chunked' headers that indicate the presence of a body. 78 | * 79 | * http_data_cb does not return data chunks. It will be called arbitrarily 80 | * many times for each string. E.G. you might get 10 callbacks for "on_url" 81 | * each providing just a few characters more data. 82 | */ 83 | typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); 84 | typedef int (*http_cb) (http_parser*); 85 | 86 | 87 | /* Request Methods */ 88 | #define HTTP_METHOD_MAP(XX) \ 89 | XX(0, DELETE, DELETE) \ 90 | XX(1, GET, GET) \ 91 | XX(2, HEAD, HEAD) \ 92 | XX(3, POST, POST) \ 93 | XX(4, PUT, PUT) \ 94 | /* pathological */ \ 95 | XX(5, CONNECT, CONNECT) \ 96 | XX(6, OPTIONS, OPTIONS) \ 97 | XX(7, TRACE, TRACE) \ 98 | /* webdav */ \ 99 | XX(8, COPY, COPY) \ 100 | XX(9, LOCK, LOCK) \ 101 | XX(10, MKCOL, MKCOL) \ 102 | XX(11, MOVE, MOVE) \ 103 | XX(12, PROPFIND, PROPFIND) \ 104 | XX(13, PROPPATCH, PROPPATCH) \ 105 | XX(14, SEARCH, SEARCH) \ 106 | XX(15, UNLOCK, UNLOCK) \ 107 | /* subversion */ \ 108 | XX(16, REPORT, REPORT) \ 109 | XX(17, MKACTIVITY, MKACTIVITY) \ 110 | XX(18, CHECKOUT, CHECKOUT) \ 111 | XX(19, MERGE, MERGE) \ 112 | /* upnp */ \ 113 | XX(20, MSEARCH, M-SEARCH) \ 114 | XX(21, NOTIFY, NOTIFY) \ 115 | XX(22, SUBSCRIBE, SUBSCRIBE) \ 116 | XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ 117 | /* RFC-5789 */ \ 118 | XX(24, PATCH, PATCH) \ 119 | XX(25, PURGE, PURGE) \ 120 | /* CalDAV */ \ 121 | XX(26, MKCALENDAR, MKCALENDAR) \ 122 | 123 | enum http_method 124 | { 125 | #define XX(num, name, string) HTTP_##name = num, 126 | HTTP_METHOD_MAP(XX) 127 | #undef XX 128 | }; 129 | 130 | 131 | enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; 132 | 133 | 134 | /* Flag values for http_parser.flags field */ 135 | enum flags 136 | { F_CHUNKED = 1 << 0 137 | , F_CONNECTION_KEEP_ALIVE = 1 << 1 138 | , F_CONNECTION_CLOSE = 1 << 2 139 | , F_CONNECTION_UPGRADE = 1 << 3 140 | , F_TRAILING = 1 << 4 141 | , F_UPGRADE = 1 << 5 142 | , F_SKIPBODY = 1 << 6 143 | }; 144 | 145 | 146 | /* Map for errno-related constants 147 | * 148 | * The provided argument should be a macro that takes 2 arguments. 149 | */ 150 | #define HTTP_ERRNO_MAP(XX) \ 151 | /* No error */ \ 152 | XX(OK, "success") \ 153 | \ 154 | /* Callback-related errors */ \ 155 | XX(CB_message_begin, "the on_message_begin callback failed") \ 156 | XX(CB_url, "the on_url callback failed") \ 157 | XX(CB_header_field, "the on_header_field callback failed") \ 158 | XX(CB_header_value, "the on_header_value callback failed") \ 159 | XX(CB_headers_complete, "the on_headers_complete callback failed") \ 160 | XX(CB_body, "the on_body callback failed") \ 161 | XX(CB_message_complete, "the on_message_complete callback failed") \ 162 | XX(CB_status, "the on_status callback failed") \ 163 | \ 164 | /* Parsing-related errors */ \ 165 | XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ 166 | XX(HEADER_OVERFLOW, \ 167 | "too many header bytes seen; overflow detected") \ 168 | XX(CLOSED_CONNECTION, \ 169 | "data received after completed connection: close message") \ 170 | XX(INVALID_VERSION, "invalid HTTP version") \ 171 | XX(INVALID_STATUS, "invalid HTTP status code") \ 172 | XX(INVALID_METHOD, "invalid HTTP method") \ 173 | XX(INVALID_URL, "invalid URL") \ 174 | XX(INVALID_HOST, "invalid host") \ 175 | XX(INVALID_PORT, "invalid port") \ 176 | XX(INVALID_PATH, "invalid path") \ 177 | XX(INVALID_QUERY_STRING, "invalid query string") \ 178 | XX(INVALID_FRAGMENT, "invalid fragment") \ 179 | XX(LF_EXPECTED, "LF character expected") \ 180 | XX(INVALID_HEADER_TOKEN, "invalid character in header") \ 181 | XX(INVALID_CONTENT_LENGTH, \ 182 | "invalid character in content-length header") \ 183 | XX(INVALID_CHUNK_SIZE, \ 184 | "invalid character in chunk size header") \ 185 | XX(INVALID_CONSTANT, "invalid constant string") \ 186 | XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ 187 | XX(STRICT, "strict mode assertion failed") \ 188 | XX(PAUSED, "parser is paused") \ 189 | XX(UNKNOWN, "an unknown error occurred") 190 | 191 | 192 | /* Define HPE_* values for each errno value above */ 193 | #define HTTP_ERRNO_GEN(n, s) HPE_##n, 194 | enum http_errno { 195 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 196 | }; 197 | #undef HTTP_ERRNO_GEN 198 | 199 | 200 | /* Get an http_errno value from an http_parser */ 201 | #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) 202 | 203 | 204 | struct http_parser { 205 | /** PRIVATE **/ 206 | unsigned int type : 2; /* enum http_parser_type */ 207 | unsigned int flags : 6; /* F_* values from 'flags' enum; semi-public */ 208 | unsigned int state : 8; /* enum state from http_parser.c */ 209 | unsigned int header_state : 8; /* enum header_state from http_parser.c */ 210 | unsigned int index : 8; /* index into current matcher */ 211 | 212 | uint32_t nread; /* # bytes read in various scenarios */ 213 | uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ 214 | 215 | /** READ-ONLY **/ 216 | unsigned short http_major; 217 | unsigned short http_minor; 218 | unsigned int status_code : 16; /* responses only */ 219 | unsigned int method : 8; /* requests only */ 220 | unsigned int http_errno : 7; 221 | 222 | /* 1 = Upgrade header was present and the parser has exited because of that. 223 | * 0 = No upgrade header present. 224 | * Should be checked when http_parser_execute() returns in addition to 225 | * error checking. 226 | */ 227 | unsigned int upgrade : 1; 228 | 229 | /** PUBLIC **/ 230 | void *data; /* A pointer to get hook to the "connection" or "socket" object */ 231 | }; 232 | 233 | 234 | struct http_parser_settings { 235 | http_cb on_message_begin; 236 | http_data_cb on_url; 237 | http_data_cb on_status; 238 | http_data_cb on_header_field; 239 | http_data_cb on_header_value; 240 | http_cb on_headers_complete; 241 | http_data_cb on_body; 242 | http_cb on_message_complete; 243 | }; 244 | 245 | 246 | enum http_parser_url_fields 247 | { UF_SCHEMA = 0 248 | , UF_HOST = 1 249 | , UF_PORT = 2 250 | , UF_PATH = 3 251 | , UF_QUERY = 4 252 | , UF_FRAGMENT = 5 253 | , UF_USERINFO = 6 254 | , UF_MAX = 7 255 | }; 256 | 257 | 258 | /* Result structure for http_parser_parse_url(). 259 | * 260 | * Callers should index into field_data[] with UF_* values iff field_set 261 | * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and 262 | * because we probably have padding left over), we convert any port to 263 | * a uint16_t. 264 | */ 265 | struct http_parser_url { 266 | uint16_t field_set; /* Bitmask of (1 << UF_*) values */ 267 | uint16_t port; /* Converted UF_PORT string */ 268 | 269 | struct { 270 | uint16_t off; /* Offset into buffer in which field starts */ 271 | uint16_t len; /* Length of run in buffer */ 272 | } field_data[UF_MAX]; 273 | }; 274 | 275 | 276 | /* Returns the library version. Bits 16-23 contain the major version number, 277 | * bits 8-15 the minor version number and bits 0-7 the patch level. 278 | * Usage example: 279 | * 280 | * unsigned long version = http_parser_version(); 281 | * unsigned major = (version >> 16) & 255; 282 | * unsigned minor = (version >> 8) & 255; 283 | * unsigned patch = version & 255; 284 | * printf("http_parser v%u.%u.%u\n", major, minor, patch); 285 | */ 286 | unsigned long http_parser_version(void); 287 | 288 | void http_parser_init(http_parser *parser, enum http_parser_type type); 289 | 290 | 291 | /* Executes the parser. Returns number of parsed bytes. Sets 292 | * `parser->http_errno` on error. */ 293 | size_t http_parser_execute(http_parser *parser, 294 | const http_parser_settings *settings, 295 | const char *data, 296 | size_t len); 297 | 298 | 299 | /* If http_should_keep_alive() in the on_headers_complete or 300 | * on_message_complete callback returns 0, then this should be 301 | * the last message on the connection. 302 | * If you are the server, respond with the "Connection: close" header. 303 | * If you are the client, close the connection. 304 | */ 305 | int http_should_keep_alive(const http_parser *parser); 306 | 307 | /* Returns a string version of the HTTP method. */ 308 | const char *http_method_str(enum http_method m); 309 | 310 | /* Return a string name of the given error */ 311 | const char *http_errno_name(enum http_errno err); 312 | 313 | /* Return a string description of the given error */ 314 | const char *http_errno_description(enum http_errno err); 315 | 316 | /* Parse a URL; return nonzero on failure */ 317 | int http_parser_parse_url(const char *buf, size_t buflen, 318 | int is_connect, 319 | struct http_parser_url *u); 320 | 321 | /* Pause or un-pause the parser; a nonzero value pauses */ 322 | void http_parser_pause(http_parser *parser, int paused); 323 | 324 | /* Checks if this is the final chunk of the body. */ 325 | int http_body_is_final(const http_parser *parser); 326 | 327 | #ifdef __cplusplus 328 | } 329 | #endif 330 | #endif 331 | -------------------------------------------------------------------------------- /src/ae.c: -------------------------------------------------------------------------------- 1 | /* A simple event-driven programming library. Originally I wrote this code 2 | * for the Jim's event-loop (Jim is a Tcl interpreter) but later translated 3 | * it in form of a library for easy reuse. 4 | * 5 | * Copyright (c) 2006-2010, Salvatore Sanfilippo 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * * Redistributions of source code must retain the above copyright notice, 12 | * this list of conditions and the following disclaimer. 13 | * * Redistributions in binary form must reproduce the above copyright 14 | * notice, this list of conditions and the following disclaimer in the 15 | * documentation and/or other materials provided with the distribution. 16 | * * Neither the name of Redis nor the names of its contributors may be used 17 | * to endorse or promote products derived from this software without 18 | * specific prior written permission. 19 | * 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 24 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 | * POSSIBILITY OF SUCH DAMAGE. 31 | */ 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include "ae.h" 44 | #include "zmalloc.h" 45 | #include "config.h" 46 | 47 | /* Include the best multiplexing layer supported by this system. 48 | * The following should be ordered by performances, descending. */ 49 | #ifdef HAVE_EVPORT 50 | #include "ae_evport.c" 51 | #else 52 | #ifdef HAVE_EPOLL 53 | #include "ae_epoll.c" 54 | #else 55 | #ifdef HAVE_KQUEUE 56 | #include "ae_kqueue.c" 57 | #else 58 | #include "ae_select.c" 59 | #endif 60 | #endif 61 | #endif 62 | 63 | aeEventLoop *aeCreateEventLoop(int setsize) { 64 | aeEventLoop *eventLoop; 65 | int i; 66 | 67 | if ((eventLoop = zmalloc(sizeof(*eventLoop))) == NULL) goto err; 68 | eventLoop->events = zmalloc(sizeof(aeFileEvent)*setsize); 69 | eventLoop->fired = zmalloc(sizeof(aeFiredEvent)*setsize); 70 | if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err; 71 | eventLoop->setsize = setsize; 72 | eventLoop->lastTime = time(NULL); 73 | eventLoop->timeEventHead = NULL; 74 | eventLoop->timeEventNextId = 0; 75 | eventLoop->stop = 0; 76 | eventLoop->maxfd = -1; 77 | eventLoop->beforesleep = NULL; 78 | if (aeApiCreate(eventLoop) == -1) goto err; 79 | /* Events with mask == AE_NONE are not set. So let's initialize the 80 | * vector with it. */ 81 | for (i = 0; i < setsize; i++) 82 | eventLoop->events[i].mask = AE_NONE; 83 | return eventLoop; 84 | 85 | err: 86 | if (eventLoop) { 87 | zfree(eventLoop->events); 88 | zfree(eventLoop->fired); 89 | zfree(eventLoop); 90 | } 91 | return NULL; 92 | } 93 | 94 | void aeDeleteEventLoop(aeEventLoop *eventLoop) { 95 | aeApiFree(eventLoop); 96 | zfree(eventLoop->events); 97 | zfree(eventLoop->fired); 98 | zfree(eventLoop); 99 | } 100 | 101 | void aeStop(aeEventLoop *eventLoop) { 102 | eventLoop->stop = 1; 103 | } 104 | 105 | int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask, 106 | aeFileProc *proc, void *clientData) 107 | { 108 | if (fd >= eventLoop->setsize) { 109 | errno = ERANGE; 110 | return AE_ERR; 111 | } 112 | aeFileEvent *fe = &eventLoop->events[fd]; 113 | 114 | if (aeApiAddEvent(eventLoop, fd, mask) == -1) 115 | return AE_ERR; 116 | fe->mask |= mask; 117 | if (mask & AE_READABLE) fe->rfileProc = proc; 118 | if (mask & AE_WRITABLE) fe->wfileProc = proc; 119 | fe->clientData = clientData; 120 | if (fd > eventLoop->maxfd) 121 | eventLoop->maxfd = fd; 122 | return AE_OK; 123 | } 124 | 125 | void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask) 126 | { 127 | if (fd >= eventLoop->setsize) return; 128 | aeFileEvent *fe = &eventLoop->events[fd]; 129 | 130 | if (fe->mask == AE_NONE) return; 131 | fe->mask = fe->mask & (~mask); 132 | if (fd == eventLoop->maxfd && fe->mask == AE_NONE) { 133 | /* Update the max fd */ 134 | int j; 135 | 136 | for (j = eventLoop->maxfd-1; j >= 0; j--) 137 | if (eventLoop->events[j].mask != AE_NONE) break; 138 | eventLoop->maxfd = j; 139 | } 140 | aeApiDelEvent(eventLoop, fd, mask); 141 | } 142 | 143 | int aeGetFileEvents(aeEventLoop *eventLoop, int fd) { 144 | if (fd >= eventLoop->setsize) return 0; 145 | aeFileEvent *fe = &eventLoop->events[fd]; 146 | 147 | return fe->mask; 148 | } 149 | 150 | static void aeGetTime(long *seconds, long *milliseconds) 151 | { 152 | struct timeval tv; 153 | 154 | gettimeofday(&tv, NULL); 155 | *seconds = tv.tv_sec; 156 | *milliseconds = tv.tv_usec/1000; 157 | } 158 | 159 | static void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms) { 160 | long cur_sec, cur_ms, when_sec, when_ms; 161 | 162 | aeGetTime(&cur_sec, &cur_ms); 163 | when_sec = cur_sec + milliseconds/1000; 164 | when_ms = cur_ms + milliseconds%1000; 165 | if (when_ms >= 1000) { 166 | when_sec ++; 167 | when_ms -= 1000; 168 | } 169 | *sec = when_sec; 170 | *ms = when_ms; 171 | } 172 | 173 | long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, 174 | aeTimeProc *proc, void *clientData, 175 | aeEventFinalizerProc *finalizerProc) 176 | { 177 | long long id = eventLoop->timeEventNextId++; 178 | aeTimeEvent *te; 179 | 180 | te = zmalloc(sizeof(*te)); 181 | if (te == NULL) return AE_ERR; 182 | te->id = id; 183 | aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms); 184 | te->timeProc = proc; 185 | te->finalizerProc = finalizerProc; 186 | te->clientData = clientData; 187 | te->next = eventLoop->timeEventHead; 188 | eventLoop->timeEventHead = te; 189 | return id; 190 | } 191 | 192 | int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id) 193 | { 194 | aeTimeEvent *te, *prev = NULL; 195 | 196 | te = eventLoop->timeEventHead; 197 | while(te) { 198 | if (te->id == id) { 199 | if (prev == NULL) 200 | eventLoop->timeEventHead = te->next; 201 | else 202 | prev->next = te->next; 203 | if (te->finalizerProc) 204 | te->finalizerProc(eventLoop, te->clientData); 205 | zfree(te); 206 | return AE_OK; 207 | } 208 | prev = te; 209 | te = te->next; 210 | } 211 | return AE_ERR; /* NO event with the specified ID found */ 212 | } 213 | 214 | /* Search the first timer to fire. 215 | * This operation is useful to know how many time the select can be 216 | * put in sleep without to delay any event. 217 | * If there are no timers NULL is returned. 218 | * 219 | * Note that's O(N) since time events are unsorted. 220 | * Possible optimizations (not needed by Redis so far, but...): 221 | * 1) Insert the event in order, so that the nearest is just the head. 222 | * Much better but still insertion or deletion of timers is O(N). 223 | * 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)). 224 | */ 225 | static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop) 226 | { 227 | aeTimeEvent *te = eventLoop->timeEventHead; 228 | aeTimeEvent *nearest = NULL; 229 | 230 | while(te) { 231 | if (!nearest || te->when_sec < nearest->when_sec || 232 | (te->when_sec == nearest->when_sec && 233 | te->when_ms < nearest->when_ms)) 234 | nearest = te; 235 | te = te->next; 236 | } 237 | return nearest; 238 | } 239 | 240 | /* Process time events */ 241 | static int processTimeEvents(aeEventLoop *eventLoop) { 242 | int processed = 0; 243 | aeTimeEvent *te; 244 | long long maxId; 245 | time_t now = time(NULL); 246 | 247 | /* If the system clock is moved to the future, and then set back to the 248 | * right value, time events may be delayed in a random way. Often this 249 | * means that scheduled operations will not be performed soon enough. 250 | * 251 | * Here we try to detect system clock skews, and force all the time 252 | * events to be processed ASAP when this happens: the idea is that 253 | * processing events earlier is less dangerous than delaying them 254 | * indefinitely, and practice suggests it is. */ 255 | if (now < eventLoop->lastTime) { 256 | te = eventLoop->timeEventHead; 257 | while(te) { 258 | te->when_sec = 0; 259 | te = te->next; 260 | } 261 | } 262 | eventLoop->lastTime = now; 263 | 264 | te = eventLoop->timeEventHead; 265 | maxId = eventLoop->timeEventNextId-1; 266 | while(te) { 267 | long now_sec, now_ms; 268 | long long id; 269 | 270 | if (te->id > maxId) { 271 | te = te->next; 272 | continue; 273 | } 274 | aeGetTime(&now_sec, &now_ms); 275 | if (now_sec > te->when_sec || 276 | (now_sec == te->when_sec && now_ms >= te->when_ms)) 277 | { 278 | int retval; 279 | 280 | id = te->id; 281 | retval = te->timeProc(eventLoop, id, te->clientData); 282 | processed++; 283 | /* After an event is processed our time event list may 284 | * no longer be the same, so we restart from head. 285 | * Still we make sure to don't process events registered 286 | * by event handlers itself in order to don't loop forever. 287 | * To do so we saved the max ID we want to handle. 288 | * 289 | * FUTURE OPTIMIZATIONS: 290 | * Note that this is NOT great algorithmically. Redis uses 291 | * a single time event so it's not a problem but the right 292 | * way to do this is to add the new elements on head, and 293 | * to flag deleted elements in a special way for later 294 | * deletion (putting references to the nodes to delete into 295 | * another linked list). */ 296 | if (retval != AE_NOMORE) { 297 | aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms); 298 | } else { 299 | aeDeleteTimeEvent(eventLoop, id); 300 | } 301 | te = eventLoop->timeEventHead; 302 | } else { 303 | te = te->next; 304 | } 305 | } 306 | return processed; 307 | } 308 | 309 | /* Process every pending time event, then every pending file event 310 | * (that may be registered by time event callbacks just processed). 311 | * Without special flags the function sleeps until some file event 312 | * fires, or when the next time event occurrs (if any). 313 | * 314 | * If flags is 0, the function does nothing and returns. 315 | * if flags has AE_ALL_EVENTS set, all the kind of events are processed. 316 | * if flags has AE_FILE_EVENTS set, file events are processed. 317 | * if flags has AE_TIME_EVENTS set, time events are processed. 318 | * if flags has AE_DONT_WAIT set the function returns ASAP until all 319 | * the events that's possible to process without to wait are processed. 320 | * 321 | * The function returns the number of events processed. */ 322 | int aeProcessEvents(aeEventLoop *eventLoop, int flags) 323 | { 324 | int processed = 0, numevents; 325 | 326 | /* Nothing to do? return ASAP */ 327 | if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0; 328 | 329 | /* Note that we want call select() even if there are no 330 | * file events to process as long as we want to process time 331 | * events, in order to sleep until the next time event is ready 332 | * to fire. */ 333 | if (eventLoop->maxfd != -1 || 334 | ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) { 335 | int j; 336 | aeTimeEvent *shortest = NULL; 337 | struct timeval tv, *tvp; 338 | 339 | if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT)) 340 | shortest = aeSearchNearestTimer(eventLoop); 341 | if (shortest) { 342 | long now_sec, now_ms; 343 | 344 | /* Calculate the time missing for the nearest 345 | * timer to fire. */ 346 | aeGetTime(&now_sec, &now_ms); 347 | tvp = &tv; 348 | tvp->tv_sec = shortest->when_sec - now_sec; 349 | if (shortest->when_ms < now_ms) { 350 | tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000; 351 | tvp->tv_sec --; 352 | } else { 353 | tvp->tv_usec = (shortest->when_ms - now_ms)*1000; 354 | } 355 | if (tvp->tv_sec < 0) tvp->tv_sec = 0; 356 | if (tvp->tv_usec < 0) tvp->tv_usec = 0; 357 | } else { 358 | /* If we have to check for events but need to return 359 | * ASAP because of AE_DONT_WAIT we need to se the timeout 360 | * to zero */ 361 | if (flags & AE_DONT_WAIT) { 362 | tv.tv_sec = tv.tv_usec = 0; 363 | tvp = &tv; 364 | } else { 365 | /* Otherwise we can block */ 366 | tvp = NULL; /* wait forever */ 367 | } 368 | } 369 | 370 | numevents = aeApiPoll(eventLoop, tvp); 371 | for (j = 0; j < numevents; j++) { 372 | aeFileEvent *fe = &eventLoop->events[eventLoop->fired[j].fd]; 373 | int mask = eventLoop->fired[j].mask; 374 | int fd = eventLoop->fired[j].fd; 375 | int rfired = 0; 376 | 377 | /* note the fe->mask & mask & ... code: maybe an already processed 378 | * event removed an element that fired and we still didn't 379 | * processed, so we check if the event is still valid. */ 380 | if (fe->mask & mask & AE_READABLE) { 381 | rfired = 1; 382 | fe->rfileProc(eventLoop,fd,fe->clientData,mask); 383 | } 384 | if (fe->mask & mask & AE_WRITABLE) { 385 | if (!rfired || fe->wfileProc != fe->rfileProc) 386 | fe->wfileProc(eventLoop,fd,fe->clientData,mask); 387 | } 388 | processed++; 389 | } 390 | } 391 | /* Check time events */ 392 | if (flags & AE_TIME_EVENTS) 393 | processed += processTimeEvents(eventLoop); 394 | 395 | return processed; /* return the number of processed file/time events */ 396 | } 397 | 398 | /* Wait for millseconds until the given file descriptor becomes 399 | * writable/readable/exception */ 400 | int aeWait(int fd, int mask, long long milliseconds) { 401 | struct pollfd pfd; 402 | int retmask = 0, retval; 403 | 404 | memset(&pfd, 0, sizeof(pfd)); 405 | pfd.fd = fd; 406 | if (mask & AE_READABLE) pfd.events |= POLLIN; 407 | if (mask & AE_WRITABLE) pfd.events |= POLLOUT; 408 | 409 | if ((retval = poll(&pfd, 1, milliseconds))== 1) { 410 | if (pfd.revents & POLLIN) retmask |= AE_READABLE; 411 | if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE; 412 | if (pfd.revents & POLLERR) retmask |= AE_WRITABLE; 413 | if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE; 414 | return retmask; 415 | } else { 416 | return retval; 417 | } 418 | } 419 | 420 | void aeMain(aeEventLoop *eventLoop) { 421 | eventLoop->stop = 0; 422 | while (!eventLoop->stop) { 423 | if (eventLoop->beforesleep != NULL) 424 | eventLoop->beforesleep(eventLoop); 425 | aeProcessEvents(eventLoop, AE_ALL_EVENTS); 426 | } 427 | } 428 | 429 | char *aeGetApiName(void) { 430 | return aeApiName(); 431 | } 432 | 433 | void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep) { 434 | eventLoop->beforesleep = beforesleep; 435 | } 436 | -------------------------------------------------------------------------------- /src/script.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 - Will Glozer. All rights reserved. 2 | 3 | #include 4 | #include 5 | #include "script.h" 6 | #include "http_parser.h" 7 | #include "zmalloc.h" 8 | 9 | typedef struct { 10 | char *name; 11 | int type; 12 | void *value; 13 | } table_field; 14 | 15 | static int script_addr_tostring(lua_State *); 16 | static int script_addr_gc(lua_State *); 17 | static int script_stats_call(lua_State *); 18 | static int script_stats_len(lua_State *); 19 | static int script_stats_index(lua_State *); 20 | static int script_thread_index(lua_State *); 21 | static int script_thread_newindex(lua_State *); 22 | static int script_wrk_lookup(lua_State *); 23 | static int script_wrk_connect(lua_State *); 24 | 25 | static void set_fields(lua_State *, int, const table_field *); 26 | static void set_field(lua_State *, int, char *, int); 27 | static int push_url_part(lua_State *, char *, struct http_parser_url *, enum http_parser_url_fields); 28 | 29 | static const struct luaL_reg addrlib[] = { 30 | { "__tostring", script_addr_tostring }, 31 | { "__gc" , script_addr_gc }, 32 | { NULL, NULL } 33 | }; 34 | 35 | static const struct luaL_reg statslib[] = { 36 | { "__call", script_stats_call }, 37 | { "__index", script_stats_index }, 38 | { "__len", script_stats_len }, 39 | { NULL, NULL } 40 | }; 41 | 42 | static const struct luaL_reg threadlib[] = { 43 | { "__index", script_thread_index }, 44 | { "__newindex", script_thread_newindex }, 45 | { NULL, NULL } 46 | }; 47 | 48 | lua_State *script_create(char *file, char *url, char **headers) { 49 | lua_State *L = luaL_newstate(); 50 | luaL_openlibs(L); 51 | (void) luaL_dostring(L, "wrk = require \"wrk\""); 52 | 53 | luaL_newmetatable(L, "wrk.addr"); 54 | luaL_register(L, NULL, addrlib); 55 | luaL_newmetatable(L, "wrk.stats"); 56 | luaL_register(L, NULL, statslib); 57 | luaL_newmetatable(L, "wrk.thread"); 58 | luaL_register(L, NULL, threadlib); 59 | 60 | struct http_parser_url parts = {}; 61 | script_parse_url(url, &parts); 62 | char *path = "/"; 63 | 64 | if (parts.field_set & (1 << UF_PATH)) { 65 | path = &url[parts.field_data[UF_PATH].off]; 66 | } 67 | 68 | const table_field fields[] = { 69 | { "lookup", LUA_TFUNCTION, script_wrk_lookup }, 70 | { "connect", LUA_TFUNCTION, script_wrk_connect }, 71 | { "path", LUA_TSTRING, path }, 72 | { NULL, 0, NULL }, 73 | }; 74 | 75 | lua_getglobal(L, "wrk"); 76 | 77 | set_field(L, 4, "scheme", push_url_part(L, url, &parts, UF_SCHEMA)); 78 | set_field(L, 4, "host", push_url_part(L, url, &parts, UF_HOST)); 79 | set_field(L, 4, "port", push_url_part(L, url, &parts, UF_PORT)); 80 | set_fields(L, 4, fields); 81 | 82 | lua_getfield(L, 4, "headers"); 83 | for (char **h = headers; *h; h++) { 84 | char *p = strchr(*h, ':'); 85 | if (p && p[1] == ' ') { 86 | lua_pushlstring(L, *h, p - *h); 87 | lua_pushstring(L, p + 2); 88 | lua_settable(L, 5); 89 | } 90 | } 91 | lua_pop(L, 5); 92 | 93 | if (file && luaL_dofile(L, file)) { 94 | const char *cause = lua_tostring(L, -1); 95 | fprintf(stderr, "%s: %s\n", file, cause); 96 | } 97 | 98 | return L; 99 | } 100 | 101 | bool script_resolve(lua_State *L, char *host, char *service) { 102 | lua_getglobal(L, "wrk"); 103 | 104 | lua_getfield(L, -1, "resolve"); 105 | lua_pushstring(L, host); 106 | lua_pushstring(L, service); 107 | lua_call(L, 2, 0); 108 | 109 | lua_getfield(L, -1, "addrs"); 110 | size_t count = lua_objlen(L, -1); 111 | lua_pop(L, 2); 112 | return count > 0; 113 | } 114 | 115 | void script_push_thread(lua_State *L, thread *t) { 116 | thread **ptr = (thread **) lua_newuserdata(L, sizeof(thread **)); 117 | *ptr = t; 118 | luaL_getmetatable(L, "wrk.thread"); 119 | lua_setmetatable(L, -2); 120 | } 121 | 122 | void script_init(lua_State *L, thread *t, int argc, char **argv) { 123 | lua_getglobal(t->L, "wrk"); 124 | 125 | script_push_thread(t->L, t); 126 | lua_setfield(t->L, -2, "thread"); 127 | 128 | lua_getglobal(L, "wrk"); 129 | lua_getfield(L, -1, "setup"); 130 | script_push_thread(L, t); 131 | lua_call(L, 1, 0); 132 | lua_pop(L, 1); 133 | 134 | lua_getfield(t->L, -1, "init"); 135 | lua_newtable(t->L); 136 | for (int i = 0; i < argc; i++) { 137 | lua_pushstring(t->L, argv[i]); 138 | lua_rawseti(t->L, -2, i); 139 | } 140 | lua_call(t->L, 1, 0); 141 | lua_pop(t->L, 1); 142 | } 143 | 144 | uint64_t script_delay(lua_State *L) { 145 | lua_getglobal(L, "delay"); 146 | lua_call(L, 0, 1); 147 | uint64_t delay = lua_tonumber(L, -1); 148 | lua_pop(L, 1); 149 | return delay; 150 | } 151 | 152 | void script_request(lua_State *L, char **buf, size_t *len) { 153 | int pop = 1; 154 | lua_getglobal(L, "request"); 155 | if (!lua_isfunction(L, -1)) { 156 | lua_getglobal(L, "wrk"); 157 | lua_getfield(L, -1, "request"); 158 | pop += 2; 159 | } 160 | lua_call(L, 0, 1); 161 | const char *str = lua_tolstring(L, -1, len); 162 | *buf = realloc(*buf, *len); 163 | memcpy(*buf, str, *len); 164 | lua_pop(L, pop); 165 | } 166 | 167 | void script_response(lua_State *L, int status, buffer *headers, buffer *body) { 168 | lua_getglobal(L, "response"); 169 | lua_pushinteger(L, status); 170 | lua_newtable(L); 171 | 172 | for (char *c = headers->buffer; c < headers->cursor; ) { 173 | c = buffer_pushlstring(L, c); 174 | c = buffer_pushlstring(L, c); 175 | lua_rawset(L, -3); 176 | } 177 | 178 | lua_pushlstring(L, body->buffer, body->cursor - body->buffer); 179 | lua_call(L, 3, 0); 180 | 181 | buffer_reset(headers); 182 | buffer_reset(body); 183 | } 184 | 185 | bool script_is_function(lua_State *L, char *name) { 186 | lua_getglobal(L, name); 187 | bool is_function = lua_isfunction(L, -1); 188 | lua_pop(L, 1); 189 | return is_function; 190 | } 191 | 192 | bool script_is_static(lua_State *L) { 193 | return !script_is_function(L, "request"); 194 | } 195 | 196 | bool script_want_response(lua_State *L) { 197 | return script_is_function(L, "response"); 198 | } 199 | 200 | bool script_has_delay(lua_State *L) { 201 | return script_is_function(L, "delay"); 202 | } 203 | 204 | bool script_has_done(lua_State *L) { 205 | return script_is_function(L, "done"); 206 | } 207 | 208 | void script_header_done(lua_State *L, luaL_Buffer *buffer) { 209 | luaL_pushresult(buffer); 210 | } 211 | 212 | void script_summary(lua_State *L, uint64_t duration, uint64_t requests, uint64_t bytes) { 213 | const table_field fields[] = { 214 | { "duration", LUA_TNUMBER, &duration }, 215 | { "requests", LUA_TNUMBER, &requests }, 216 | { "bytes", LUA_TNUMBER, &bytes }, 217 | { NULL, 0, NULL }, 218 | }; 219 | lua_newtable(L); 220 | set_fields(L, 1, fields); 221 | } 222 | 223 | void script_errors(lua_State *L, errors *errors) { 224 | uint64_t e[] = { 225 | errors->connect, 226 | errors->read, 227 | errors->write, 228 | errors->status, 229 | errors->timeout 230 | }; 231 | const table_field fields[] = { 232 | { "connect", LUA_TNUMBER, &e[0] }, 233 | { "read", LUA_TNUMBER, &e[1] }, 234 | { "write", LUA_TNUMBER, &e[2] }, 235 | { "status", LUA_TNUMBER, &e[3] }, 236 | { "timeout", LUA_TNUMBER, &e[4] }, 237 | { NULL, 0, NULL }, 238 | }; 239 | lua_newtable(L); 240 | set_fields(L, 2, fields); 241 | lua_setfield(L, 1, "errors"); 242 | } 243 | 244 | void script_push_stats(lua_State *L, stats *s) { 245 | stats **ptr = (stats **) lua_newuserdata(L, sizeof(stats **)); 246 | *ptr = s; 247 | luaL_getmetatable(L, "wrk.stats"); 248 | lua_setmetatable(L, -2); 249 | } 250 | 251 | void script_done(lua_State *L, stats *latency, stats *requests) { 252 | lua_getglobal(L, "done"); 253 | lua_pushvalue(L, 1); 254 | 255 | script_push_stats(L, latency); 256 | script_push_stats(L, requests); 257 | 258 | lua_call(L, 3, 0); 259 | lua_pop(L, 1); 260 | } 261 | 262 | static int verify_request(http_parser *parser) { 263 | size_t *count = parser->data; 264 | (*count)++; 265 | return 0; 266 | } 267 | 268 | size_t script_verify_request(lua_State *L) { 269 | http_parser_settings settings = { 270 | .on_message_complete = verify_request 271 | }; 272 | http_parser parser; 273 | char *request = NULL; 274 | size_t len, count = 0; 275 | 276 | script_request(L, &request, &len); 277 | http_parser_init(&parser, HTTP_REQUEST); 278 | parser.data = &count; 279 | 280 | size_t parsed = http_parser_execute(&parser, &settings, request, len); 281 | 282 | if (parsed != len || count == 0) { 283 | enum http_errno err = HTTP_PARSER_ERRNO(&parser); 284 | const char *desc = http_errno_description(err); 285 | const char *msg = err != HPE_OK ? desc : "incomplete request"; 286 | int line = 1, column = 1; 287 | 288 | for (char *c = request; c < request + parsed; c++) { 289 | column++; 290 | if (*c == '\n') { 291 | column = 1; 292 | line++; 293 | } 294 | } 295 | 296 | fprintf(stderr, "%s at %d:%d\n", msg, line, column); 297 | exit(1); 298 | } 299 | 300 | return count; 301 | } 302 | 303 | static struct addrinfo *checkaddr(lua_State *L) { 304 | struct addrinfo *addr = luaL_checkudata(L, -1, "wrk.addr"); 305 | luaL_argcheck(L, addr != NULL, 1, "`addr' expected"); 306 | return addr; 307 | } 308 | 309 | void script_addr_copy(struct addrinfo *src, struct addrinfo *dst) { 310 | *dst = *src; 311 | dst->ai_addr = zmalloc(src->ai_addrlen); 312 | memcpy(dst->ai_addr, src->ai_addr, src->ai_addrlen); 313 | } 314 | 315 | struct addrinfo *script_addr_clone(lua_State *L, struct addrinfo *addr) { 316 | struct addrinfo *udata = lua_newuserdata(L, sizeof(*udata)); 317 | luaL_getmetatable(L, "wrk.addr"); 318 | lua_setmetatable(L, -2); 319 | script_addr_copy(addr, udata); 320 | return udata; 321 | } 322 | 323 | static int script_addr_tostring(lua_State *L) { 324 | struct addrinfo *addr = checkaddr(L); 325 | char host[NI_MAXHOST]; 326 | char service[NI_MAXSERV]; 327 | 328 | int flags = NI_NUMERICHOST | NI_NUMERICSERV; 329 | int rc = getnameinfo(addr->ai_addr, addr->ai_addrlen, host, NI_MAXHOST, service, NI_MAXSERV, flags); 330 | if (rc != 0) { 331 | const char *msg = gai_strerror(rc); 332 | return luaL_error(L, "addr tostring failed %s", msg); 333 | } 334 | 335 | lua_pushfstring(L, "%s:%s", host, service); 336 | return 1; 337 | } 338 | 339 | static int script_addr_gc(lua_State *L) { 340 | struct addrinfo *addr = checkaddr(L); 341 | zfree(addr->ai_addr); 342 | return 0; 343 | } 344 | 345 | static stats *checkstats(lua_State *L) { 346 | stats **s = luaL_checkudata(L, 1, "wrk.stats"); 347 | luaL_argcheck(L, s != NULL, 1, "`stats' expected"); 348 | return *s; 349 | } 350 | 351 | static int script_stats_percentile(lua_State *L) { 352 | stats *s = checkstats(L); 353 | lua_Number p = luaL_checknumber(L, 2); 354 | lua_pushnumber(L, stats_percentile(s, p)); 355 | return 1; 356 | } 357 | 358 | static int script_stats_call(lua_State *L) { 359 | stats *s = checkstats(L); 360 | uint64_t index = lua_tonumber(L, 2); 361 | uint64_t count; 362 | lua_pushnumber(L, stats_value_at(s, index - 1, &count)); 363 | lua_pushnumber(L, count); 364 | return 2; 365 | } 366 | 367 | static int script_stats_index(lua_State *L) { 368 | stats *s = checkstats(L); 369 | const char *method = lua_tostring(L, 2); 370 | if (!strcmp("min", method)) lua_pushnumber(L, s->min); 371 | if (!strcmp("max", method)) lua_pushnumber(L, s->max); 372 | if (!strcmp("mean", method)) lua_pushnumber(L, stats_mean(s)); 373 | if (!strcmp("stdev", method)) lua_pushnumber(L, stats_stdev(s, stats_mean(s))); 374 | if (!strcmp("percentile", method)) { 375 | lua_pushcfunction(L, script_stats_percentile); 376 | } 377 | return 1; 378 | } 379 | 380 | static int script_stats_len(lua_State *L) { 381 | stats *s = checkstats(L); 382 | lua_pushinteger(L, stats_popcount(s)); 383 | return 1; 384 | } 385 | 386 | static thread *checkthread(lua_State *L) { 387 | thread **t = luaL_checkudata(L, 1, "wrk.thread"); 388 | luaL_argcheck(L, t != NULL, 1, "`thread' expected"); 389 | return *t; 390 | } 391 | 392 | static int script_thread_get(lua_State *L) { 393 | thread *t = checkthread(L); 394 | const char *key = lua_tostring(L, -1); 395 | lua_getglobal(t->L, key); 396 | script_copy_value(t->L, L, -1); 397 | lua_pop(t->L, 1); 398 | return 1; 399 | } 400 | 401 | static int script_thread_set(lua_State *L) { 402 | thread *t = checkthread(L); 403 | const char *name = lua_tostring(L, -2); 404 | script_copy_value(L, t->L, -1); 405 | lua_setglobal(t->L, name); 406 | return 0; 407 | } 408 | 409 | static int script_thread_stop(lua_State *L) { 410 | thread *t = checkthread(L); 411 | aeStop(t->loop); 412 | return 0; 413 | } 414 | 415 | static int script_thread_index(lua_State *L) { 416 | thread *t = checkthread(L); 417 | const char *key = lua_tostring(L, 2); 418 | if (!strcmp("get", key)) lua_pushcfunction(L, script_thread_get); 419 | if (!strcmp("set", key)) lua_pushcfunction(L, script_thread_set); 420 | if (!strcmp("stop", key)) lua_pushcfunction(L, script_thread_stop); 421 | if (!strcmp("addr", key)) script_addr_clone(L, t->addr); 422 | return 1; 423 | } 424 | 425 | static int script_thread_newindex(lua_State *L) { 426 | thread *t = checkthread(L); 427 | const char *key = lua_tostring(L, -2); 428 | if (!strcmp("addr", key)) { 429 | struct addrinfo *addr = checkaddr(L); 430 | if (t->addr) zfree(t->addr->ai_addr); 431 | t->addr = zrealloc(t->addr, sizeof(*addr)); 432 | script_addr_copy(addr, t->addr); 433 | } else { 434 | luaL_error(L, "cannot set '%s' on thread", luaL_typename(L, -1)); 435 | } 436 | return 0; 437 | } 438 | 439 | static int script_wrk_lookup(lua_State *L) { 440 | struct addrinfo *addrs; 441 | struct addrinfo hints = { 442 | .ai_family = AF_UNSPEC, 443 | .ai_socktype = SOCK_STREAM 444 | }; 445 | int rc, index = 1; 446 | 447 | const char *host = lua_tostring(L, -2); 448 | const char *service = lua_tostring(L, -1); 449 | 450 | if ((rc = getaddrinfo(host, service, &hints, &addrs)) != 0) { 451 | const char *msg = gai_strerror(rc); 452 | fprintf(stderr, "unable to resolve %s:%s %s\n", host, service, msg); 453 | exit(1); 454 | } 455 | 456 | lua_newtable(L); 457 | for (struct addrinfo *addr = addrs; addr != NULL; addr = addr->ai_next) { 458 | script_addr_clone(L, addr); 459 | lua_rawseti(L, -2, index++); 460 | } 461 | 462 | freeaddrinfo(addrs); 463 | return 1; 464 | } 465 | 466 | static int script_wrk_connect(lua_State *L) { 467 | struct addrinfo *addr = checkaddr(L); 468 | int fd, connected = 0; 469 | if ((fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol)) != -1) { 470 | connected = connect(fd, addr->ai_addr, addr->ai_addrlen) == 0; 471 | close(fd); 472 | } 473 | lua_pushboolean(L, connected); 474 | return 1; 475 | } 476 | 477 | void script_copy_value(lua_State *src, lua_State *dst, int index) { 478 | switch (lua_type(src, index)) { 479 | case LUA_TBOOLEAN: 480 | lua_pushboolean(dst, lua_toboolean(src, index)); 481 | break; 482 | case LUA_TNIL: 483 | lua_pushnil(dst); 484 | break; 485 | case LUA_TNUMBER: 486 | lua_pushnumber(dst, lua_tonumber(src, index)); 487 | break; 488 | case LUA_TSTRING: 489 | lua_pushstring(dst, lua_tostring(src, index)); 490 | break; 491 | case LUA_TTABLE: 492 | lua_newtable(dst); 493 | lua_pushnil(src); 494 | while (lua_next(src, index - 1)) { 495 | script_copy_value(src, dst, -2); 496 | script_copy_value(src, dst, -1); 497 | lua_settable(dst, -3); 498 | lua_pop(src, 1); 499 | } 500 | lua_pop(src, 1); 501 | break; 502 | default: 503 | luaL_error(src, "cannot transfer '%s' to thread", luaL_typename(src, index)); 504 | } 505 | } 506 | 507 | int script_parse_url(char *url, struct http_parser_url *parts) { 508 | if (!http_parser_parse_url(url, strlen(url), 0, parts)) { 509 | if (!(parts->field_set & (1 << UF_SCHEMA))) return 0; 510 | if (!(parts->field_set & (1 << UF_HOST))) return 0; 511 | return 1; 512 | } 513 | return 0; 514 | } 515 | 516 | static int push_url_part(lua_State *L, char *url, struct http_parser_url *parts, enum http_parser_url_fields field) { 517 | int type = parts->field_set & (1 << field) ? LUA_TSTRING : LUA_TNIL; 518 | uint16_t off, len; 519 | switch (type) { 520 | case LUA_TSTRING: 521 | off = parts->field_data[field].off; 522 | len = parts->field_data[field].len; 523 | lua_pushlstring(L, &url[off], len); 524 | break; 525 | case LUA_TNIL: 526 | lua_pushnil(L); 527 | } 528 | return type; 529 | } 530 | 531 | static void set_field(lua_State *L, int index, char *field, int type) { 532 | (void) type; 533 | lua_setfield(L, index, field); 534 | } 535 | 536 | static void set_fields(lua_State *L, int index, const table_field *fields) { 537 | for (int i = 0; fields[i].name; i++) { 538 | table_field f = fields[i]; 539 | switch (f.value == NULL ? LUA_TNIL : f.type) { 540 | case LUA_TFUNCTION: 541 | lua_pushcfunction(L, (lua_CFunction) f.value); 542 | break; 543 | case LUA_TNUMBER: 544 | lua_pushinteger(L, *((lua_Integer *) f.value)); 545 | break; 546 | case LUA_TSTRING: 547 | lua_pushstring(L, (const char *) f.value); 548 | break; 549 | case LUA_TNIL: 550 | lua_pushnil(L); 551 | break; 552 | } 553 | lua_setfield(L, index, f.name); 554 | } 555 | } 556 | 557 | void buffer_append(buffer *b, const char *data, size_t len) { 558 | size_t used = b->cursor - b->buffer; 559 | while (used + len + 1 >= b->length) { 560 | b->length += 1024; 561 | b->buffer = realloc(b->buffer, b->length); 562 | b->cursor = b->buffer + used; 563 | } 564 | memcpy(b->cursor, data, len); 565 | b->cursor += len; 566 | } 567 | 568 | void buffer_reset(buffer *b) { 569 | b->cursor = b->buffer; 570 | } 571 | 572 | char *buffer_pushlstring(lua_State *L, char *start) { 573 | char *end = strchr(start, 0); 574 | lua_pushlstring(L, start, end - start); 575 | return end + 1; 576 | } 577 | -------------------------------------------------------------------------------- /src/wrk.c: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2012 - Will Glozer. All rights reserved. 2 | 3 | #include "wrk.h" 4 | #include "script.h" 5 | #include "main.h" 6 | 7 | static struct config { 8 | uint64_t connections; 9 | uint64_t duration; 10 | uint64_t threads; 11 | uint64_t timeout; 12 | uint64_t pipeline; 13 | bool delay; 14 | bool dynamic; 15 | bool latency; 16 | char *host; 17 | char *script; 18 | SSL_CTX *ctx; 19 | } cfg; 20 | 21 | static struct { 22 | stats *latency; 23 | stats *requests; 24 | } statistics; 25 | 26 | static struct sock sock = { 27 | .connect = sock_connect, 28 | .close = sock_close, 29 | .read = sock_read, 30 | .write = sock_write, 31 | .readable = sock_readable 32 | }; 33 | 34 | static struct http_parser_settings parser_settings = { 35 | .on_message_complete = response_complete 36 | }; 37 | 38 | static volatile sig_atomic_t stop = 0; 39 | 40 | static void handler(int sig) { 41 | stop = 1; 42 | } 43 | 44 | static void usage() { 45 | printf("Usage: wrk \n" 46 | " Options: \n" 47 | " -c, --connections Connections to keep open \n" 48 | " -d, --duration Duration of test \n" 49 | " -t, --threads Number of threads to use \n" 50 | " \n" 51 | " -s, --script Load Lua script file \n" 52 | " -H, --header Add header to request \n" 53 | " --latency Print latency statistics \n" 54 | " --timeout Socket/request timeout \n" 55 | " -v, --version Print version details \n" 56 | " \n" 57 | " Numeric arguments may include a SI unit (1k, 1M, 1G)\n" 58 | " Time arguments may include a time unit (2s, 2m, 2h)\n"); 59 | } 60 | 61 | int main(int argc, char **argv) { 62 | char *url, **headers = zmalloc(argc * sizeof(char *)); 63 | struct http_parser_url parts = {}; 64 | 65 | if (parse_args(&cfg, &url, &parts, headers, argc, argv)) { 66 | usage(); 67 | exit(1); 68 | } 69 | 70 | char *schema = copy_url_part(url, &parts, UF_SCHEMA); 71 | char *host = copy_url_part(url, &parts, UF_HOST); 72 | char *port = copy_url_part(url, &parts, UF_PORT); 73 | char *service = port ? port : schema; 74 | 75 | if (!strncmp("https", schema, 5)) { 76 | if ((cfg.ctx = ssl_init()) == NULL) { 77 | fprintf(stderr, "unable to initialize SSL\n"); 78 | ERR_print_errors_fp(stderr); 79 | exit(1); 80 | } 81 | sock.connect = ssl_connect; 82 | sock.close = ssl_close; 83 | sock.read = ssl_read; 84 | sock.write = ssl_write; 85 | sock.readable = ssl_readable; 86 | } 87 | 88 | signal(SIGPIPE, SIG_IGN); 89 | signal(SIGINT, SIG_IGN); 90 | 91 | statistics.latency = stats_alloc(cfg.timeout * 1000); 92 | statistics.requests = stats_alloc(MAX_THREAD_RATE_S); 93 | thread *threads = zcalloc(cfg.threads * sizeof(thread)); 94 | 95 | lua_State *L = script_create(cfg.script, url, headers); 96 | if (!script_resolve(L, host, service)) { 97 | char *msg = strerror(errno); 98 | fprintf(stderr, "unable to connect to %s:%s %s\n", host, service, msg); 99 | exit(1); 100 | } 101 | 102 | cfg.host = host; 103 | 104 | for (uint64_t i = 0; i < cfg.threads; i++) { 105 | thread *t = &threads[i]; 106 | t->loop = aeCreateEventLoop(10 + cfg.connections * 3); 107 | t->connections = cfg.connections / cfg.threads; 108 | 109 | t->L = script_create(cfg.script, url, headers); 110 | script_init(L, t, argc - optind, &argv[optind]); 111 | 112 | if (i == 0) { 113 | cfg.pipeline = script_verify_request(t->L); 114 | cfg.dynamic = !script_is_static(t->L); 115 | cfg.delay = script_has_delay(t->L); 116 | if (script_want_response(t->L)) { 117 | parser_settings.on_header_field = header_field; 118 | parser_settings.on_header_value = header_value; 119 | parser_settings.on_body = response_body; 120 | } 121 | } 122 | 123 | if (!t->loop || pthread_create(&t->thread, NULL, &thread_main, t)) { 124 | char *msg = strerror(errno); 125 | fprintf(stderr, "unable to create thread %"PRIu64": %s\n", i, msg); 126 | exit(2); 127 | } 128 | } 129 | 130 | struct sigaction sa = { 131 | .sa_handler = handler, 132 | .sa_flags = 0, 133 | }; 134 | sigfillset(&sa.sa_mask); 135 | sigaction(SIGINT, &sa, NULL); 136 | 137 | char *time = format_time_s(cfg.duration); 138 | printf("Running %s test @ %s\n", time, url); 139 | printf(" %"PRIu64" threads and %"PRIu64" connections\n", cfg.threads, cfg.connections); 140 | 141 | uint64_t start = time_us(); 142 | uint64_t complete = 0; 143 | uint64_t bytes = 0; 144 | errors errors = { 0 }; 145 | 146 | sleep(cfg.duration); 147 | stop = 1; 148 | 149 | for (uint64_t i = 0; i < cfg.threads; i++) { 150 | thread *t = &threads[i]; 151 | pthread_join(t->thread, NULL); 152 | 153 | complete += t->complete; 154 | bytes += t->bytes; 155 | 156 | errors.connect += t->errors.connect; 157 | errors.read += t->errors.read; 158 | errors.write += t->errors.write; 159 | errors.timeout += t->errors.timeout; 160 | errors.status += t->errors.status; 161 | } 162 | 163 | uint64_t runtime_us = time_us() - start; 164 | long double runtime_s = runtime_us / 1000000.0; 165 | long double req_per_s = complete / runtime_s; 166 | long double bytes_per_s = bytes / runtime_s; 167 | 168 | if (complete / cfg.connections > 0) { 169 | int64_t interval = runtime_us / (complete / cfg.connections); 170 | stats_correct(statistics.latency, interval); 171 | } 172 | 173 | print_stats_header(); 174 | print_stats("Latency", statistics.latency, format_time_us); 175 | print_stats("Req/Sec", statistics.requests, format_metric); 176 | if (cfg.latency) print_stats_latency(statistics.latency); 177 | 178 | char *runtime_msg = format_time_us(runtime_us); 179 | 180 | printf(" %"PRIu64" requests in %s, %sB read\n", complete, runtime_msg, format_binary(bytes)); 181 | if (errors.connect || errors.read || errors.write || errors.timeout) { 182 | printf(" Socket errors: connect %d, read %d, write %d, timeout %d\n", 183 | errors.connect, errors.read, errors.write, errors.timeout); 184 | } 185 | 186 | if (errors.status) { 187 | printf(" Non-2xx or 3xx responses: %d\n", errors.status); 188 | } 189 | 190 | printf("Requests/sec: %9.2Lf\n", req_per_s); 191 | printf("Transfer/sec: %10sB\n", format_binary(bytes_per_s)); 192 | 193 | if (script_has_done(L)) { 194 | script_summary(L, runtime_us, complete, bytes); 195 | script_errors(L, &errors); 196 | script_done(L, statistics.latency, statistics.requests); 197 | } 198 | 199 | return 0; 200 | } 201 | 202 | void *thread_main(void *arg) { 203 | thread *thread = arg; 204 | 205 | char *request = NULL; 206 | size_t length = 0; 207 | 208 | if (!cfg.dynamic) { 209 | script_request(thread->L, &request, &length); 210 | } 211 | 212 | thread->cs = zcalloc(thread->connections * sizeof(connection)); 213 | connection *c = thread->cs; 214 | 215 | for (uint64_t i = 0; i < thread->connections; i++, c++) { 216 | c->thread = thread; 217 | c->ssl = cfg.ctx ? SSL_new(cfg.ctx) : NULL; 218 | c->request = request; 219 | c->length = length; 220 | c->delayed = cfg.delay; 221 | connect_socket(thread, c); 222 | } 223 | 224 | aeEventLoop *loop = thread->loop; 225 | aeCreateTimeEvent(loop, RECORD_INTERVAL_MS, record_rate, thread, NULL); 226 | 227 | thread->start = time_us(); 228 | aeMain(loop); 229 | 230 | aeDeleteEventLoop(loop); 231 | zfree(thread->cs); 232 | 233 | return NULL; 234 | } 235 | 236 | static int connect_socket(thread *thread, connection *c) { 237 | struct addrinfo *addr = thread->addr; 238 | struct aeEventLoop *loop = thread->loop; 239 | int fd, flags; 240 | 241 | fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); 242 | 243 | flags = fcntl(fd, F_GETFL, 0); 244 | fcntl(fd, F_SETFL, flags | O_NONBLOCK); 245 | 246 | if (connect(fd, addr->ai_addr, addr->ai_addrlen) == -1) { 247 | if (errno != EINPROGRESS) goto error; 248 | } 249 | 250 | flags = 1; 251 | setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flags, sizeof(flags)); 252 | 253 | flags = AE_READABLE | AE_WRITABLE; 254 | if (aeCreateFileEvent(loop, fd, flags, socket_connected, c) == AE_OK) { 255 | c->parser.data = c; 256 | c->fd = fd; 257 | return fd; 258 | } 259 | 260 | error: 261 | thread->errors.connect++; 262 | close(fd); 263 | return -1; 264 | } 265 | 266 | static int reconnect_socket(thread *thread, connection *c) { 267 | aeDeleteFileEvent(thread->loop, c->fd, AE_WRITABLE | AE_READABLE); 268 | sock.close(c); 269 | close(c->fd); 270 | return connect_socket(thread, c); 271 | } 272 | 273 | static int record_rate(aeEventLoop *loop, long long id, void *data) { 274 | thread *thread = data; 275 | 276 | if (thread->requests > 0) { 277 | uint64_t elapsed_ms = (time_us() - thread->start) / 1000; 278 | uint64_t requests = (thread->requests / (double) elapsed_ms) * 1000; 279 | 280 | stats_record(statistics.requests, requests); 281 | 282 | thread->requests = 0; 283 | thread->start = time_us(); 284 | } 285 | 286 | if (stop) aeStop(loop); 287 | 288 | return RECORD_INTERVAL_MS; 289 | } 290 | 291 | static int delay_request(aeEventLoop *loop, long long id, void *data) { 292 | connection *c = data; 293 | c->delayed = false; 294 | aeCreateFileEvent(loop, c->fd, AE_WRITABLE, socket_writeable, c); 295 | return AE_NOMORE; 296 | } 297 | 298 | static int header_field(http_parser *parser, const char *at, size_t len) { 299 | connection *c = parser->data; 300 | if (c->state == VALUE) { 301 | *c->headers.cursor++ = '\0'; 302 | c->state = FIELD; 303 | } 304 | buffer_append(&c->headers, at, len); 305 | return 0; 306 | } 307 | 308 | static int header_value(http_parser *parser, const char *at, size_t len) { 309 | connection *c = parser->data; 310 | if (c->state == FIELD) { 311 | *c->headers.cursor++ = '\0'; 312 | c->state = VALUE; 313 | } 314 | buffer_append(&c->headers, at, len); 315 | return 0; 316 | } 317 | 318 | static int response_body(http_parser *parser, const char *at, size_t len) { 319 | connection *c = parser->data; 320 | buffer_append(&c->body, at, len); 321 | return 0; 322 | } 323 | 324 | static int response_complete(http_parser *parser) { 325 | connection *c = parser->data; 326 | thread *thread = c->thread; 327 | uint64_t now = time_us(); 328 | int status = parser->status_code; 329 | 330 | thread->complete++; 331 | thread->requests++; 332 | 333 | if (status > 399) { 334 | thread->errors.status++; 335 | } 336 | 337 | if (c->headers.buffer) { 338 | *c->headers.cursor++ = '\0'; 339 | script_response(thread->L, status, &c->headers, &c->body); 340 | c->state = FIELD; 341 | } 342 | 343 | if (--c->pending == 0) { 344 | if (!stats_record(statistics.latency, now - c->start)) { 345 | thread->errors.timeout++; 346 | } 347 | c->delayed = cfg.delay; 348 | aeCreateFileEvent(thread->loop, c->fd, AE_WRITABLE, socket_writeable, c); 349 | } 350 | 351 | if (!http_should_keep_alive(parser)) { 352 | reconnect_socket(thread, c); 353 | goto done; 354 | } 355 | 356 | http_parser_init(parser, HTTP_RESPONSE); 357 | 358 | done: 359 | return 0; 360 | } 361 | 362 | static void socket_connected(aeEventLoop *loop, int fd, void *data, int mask) { 363 | connection *c = data; 364 | 365 | switch (sock.connect(c, cfg.host)) { 366 | case OK: break; 367 | case ERROR: goto error; 368 | case RETRY: return; 369 | } 370 | 371 | http_parser_init(&c->parser, HTTP_RESPONSE); 372 | c->written = 0; 373 | 374 | aeCreateFileEvent(c->thread->loop, fd, AE_READABLE, socket_readable, c); 375 | aeCreateFileEvent(c->thread->loop, fd, AE_WRITABLE, socket_writeable, c); 376 | 377 | return; 378 | 379 | error: 380 | c->thread->errors.connect++; 381 | reconnect_socket(c->thread, c); 382 | } 383 | 384 | static void socket_writeable(aeEventLoop *loop, int fd, void *data, int mask) { 385 | connection *c = data; 386 | thread *thread = c->thread; 387 | 388 | if (c->delayed) { 389 | uint64_t delay = script_delay(thread->L); 390 | aeDeleteFileEvent(loop, fd, AE_WRITABLE); 391 | aeCreateTimeEvent(loop, delay, delay_request, c, NULL); 392 | return; 393 | } 394 | 395 | if (!c->written) { 396 | if (cfg.dynamic) { 397 | script_request(thread->L, &c->request, &c->length); 398 | } 399 | c->start = time_us(); 400 | c->pending = cfg.pipeline; 401 | } 402 | 403 | char *buf = c->request + c->written; 404 | size_t len = c->length - c->written; 405 | size_t n; 406 | 407 | switch (sock.write(c, buf, len, &n)) { 408 | case OK: break; 409 | case ERROR: goto error; 410 | case RETRY: return; 411 | } 412 | 413 | c->written += n; 414 | if (c->written == c->length) { 415 | c->written = 0; 416 | aeDeleteFileEvent(loop, fd, AE_WRITABLE); 417 | } 418 | 419 | return; 420 | 421 | error: 422 | thread->errors.write++; 423 | reconnect_socket(thread, c); 424 | } 425 | 426 | static void socket_readable(aeEventLoop *loop, int fd, void *data, int mask) { 427 | connection *c = data; 428 | size_t n; 429 | 430 | do { 431 | switch (sock.read(c, &n)) { 432 | case OK: break; 433 | case ERROR: goto error; 434 | case RETRY: return; 435 | } 436 | 437 | if (http_parser_execute(&c->parser, &parser_settings, c->buf, n) != n) goto error; 438 | if (n == 0 && !http_body_is_final(&c->parser)) goto error; 439 | 440 | c->thread->bytes += n; 441 | } while (n == RECVBUF && sock.readable(c) > 0); 442 | 443 | return; 444 | 445 | error: 446 | c->thread->errors.read++; 447 | reconnect_socket(c->thread, c); 448 | } 449 | 450 | static uint64_t time_us() { 451 | struct timeval t; 452 | gettimeofday(&t, NULL); 453 | return (t.tv_sec * 1000000) + t.tv_usec; 454 | } 455 | 456 | static char *copy_url_part(char *url, struct http_parser_url *parts, enum http_parser_url_fields field) { 457 | char *part = NULL; 458 | 459 | if (parts->field_set & (1 << field)) { 460 | uint16_t off = parts->field_data[field].off; 461 | uint16_t len = parts->field_data[field].len; 462 | part = zcalloc(len + 1 * sizeof(char)); 463 | memcpy(part, &url[off], len); 464 | } 465 | 466 | return part; 467 | } 468 | 469 | static struct option longopts[] = { 470 | { "connections", required_argument, NULL, 'c' }, 471 | { "duration", required_argument, NULL, 'd' }, 472 | { "threads", required_argument, NULL, 't' }, 473 | { "script", required_argument, NULL, 's' }, 474 | { "header", required_argument, NULL, 'H' }, 475 | { "latency", no_argument, NULL, 'L' }, 476 | { "timeout", required_argument, NULL, 'T' }, 477 | { "help", no_argument, NULL, 'h' }, 478 | { "version", no_argument, NULL, 'v' }, 479 | { NULL, 0, NULL, 0 } 480 | }; 481 | 482 | static int parse_args(struct config *cfg, char **url, struct http_parser_url *parts, char **headers, int argc, char **argv) { 483 | char **header = headers; 484 | int c; 485 | 486 | memset(cfg, 0, sizeof(struct config)); 487 | cfg->threads = 2; 488 | cfg->connections = 10; 489 | cfg->duration = 10; 490 | cfg->timeout = SOCKET_TIMEOUT_MS; 491 | 492 | while ((c = getopt_long(argc, argv, "t:c:d:s:H:T:Lrv?", longopts, NULL)) != -1) { 493 | switch (c) { 494 | case 't': 495 | if (scan_metric(optarg, &cfg->threads)) return -1; 496 | break; 497 | case 'c': 498 | if (scan_metric(optarg, &cfg->connections)) return -1; 499 | break; 500 | case 'd': 501 | if (scan_time(optarg, &cfg->duration)) return -1; 502 | break; 503 | case 's': 504 | cfg->script = optarg; 505 | break; 506 | case 'H': 507 | *header++ = optarg; 508 | break; 509 | case 'L': 510 | cfg->latency = true; 511 | break; 512 | case 'T': 513 | if (scan_time(optarg, &cfg->timeout)) return -1; 514 | cfg->timeout *= 1000; 515 | break; 516 | case 'v': 517 | printf("wrk %s [%s] ", VERSION, aeGetApiName()); 518 | printf("Copyright (C) 2012 Will Glozer\n"); 519 | break; 520 | case 'h': 521 | case '?': 522 | case ':': 523 | default: 524 | return -1; 525 | } 526 | } 527 | 528 | if (optind == argc || !cfg->threads || !cfg->duration) return -1; 529 | 530 | if (!script_parse_url(argv[optind], parts)) { 531 | fprintf(stderr, "invalid URL: %s\n", argv[optind]); 532 | return -1; 533 | } 534 | 535 | if (!cfg->connections || cfg->connections < cfg->threads) { 536 | fprintf(stderr, "number of connections must be >= threads\n"); 537 | return -1; 538 | } 539 | 540 | *url = argv[optind]; 541 | *header = NULL; 542 | 543 | return 0; 544 | } 545 | 546 | static void print_stats_header() { 547 | printf(" Thread Stats%6s%11s%8s%12s\n", "Avg", "Stdev", "Max", "+/- Stdev"); 548 | } 549 | 550 | static void print_units(long double n, char *(*fmt)(long double), int width) { 551 | char *msg = fmt(n); 552 | int len = strlen(msg), pad = 2; 553 | 554 | if (isalpha(msg[len-1])) pad--; 555 | if (isalpha(msg[len-2])) pad--; 556 | width -= pad; 557 | 558 | printf("%*.*s%.*s", width, width, msg, pad, " "); 559 | 560 | free(msg); 561 | } 562 | 563 | static void print_stats(char *name, stats *stats, char *(*fmt)(long double)) { 564 | uint64_t max = stats->max; 565 | long double mean = stats_mean(stats); 566 | long double stdev = stats_stdev(stats, mean); 567 | 568 | printf(" %-10s", name); 569 | print_units(mean, fmt, 8); 570 | print_units(stdev, fmt, 10); 571 | print_units(max, fmt, 9); 572 | printf("%8.2Lf%%\n", stats_within_stdev(stats, mean, stdev, 1)); 573 | } 574 | 575 | static void print_stats_latency(stats *stats) { 576 | long double percentiles[] = { 50.0, 75.0, 90.0, 99.0 }; 577 | printf(" Latency Distribution\n"); 578 | for (size_t i = 0; i < sizeof(percentiles) / sizeof(long double); i++) { 579 | long double p = percentiles[i]; 580 | uint64_t n = stats_percentile(stats, p); 581 | printf("%7.0Lf%%", p); 582 | print_units(n, format_time_us, 10); 583 | printf("\n"); 584 | } 585 | } 586 | --------------------------------------------------------------------------------